зеркало из https://github.com/mozilla/pjs.git
519 строки
16 KiB
Java
519 строки
16 KiB
Java
|
/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||
|
*
|
||
|
* The contents of this file are subject to the Mozilla Public License
|
||
|
* Version 1.0 (the "License"); you may not use this file except in
|
||
|
* compliance with the License. You may obtain a copy of the License at
|
||
|
* http://www.mozilla.org/MPL/
|
||
|
*
|
||
|
* Software distributed under the License is distributed on an "AS IS"
|
||
|
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||
|
* the License for the specific language governing rights and limitations
|
||
|
* under the License.
|
||
|
*
|
||
|
* The Original Code is the Grendel mail/news client.
|
||
|
*
|
||
|
* The Initial Developer of the Original Code is Netscape Communications
|
||
|
* Corporation. Portions created by Netscape are Copyright (C) 1997
|
||
|
* Netscape Communications Corporation. All Rights Reserved.
|
||
|
*
|
||
|
* Created: Terry Weissman <terry@netscape.com>, 28 Aug 1997.
|
||
|
*/
|
||
|
|
||
|
package grendel.storage;
|
||
|
|
||
|
import java.io.BufferedReader;
|
||
|
import java.io.DataOutputStream;
|
||
|
import java.io.File;
|
||
|
import java.io.InputStream;
|
||
|
import java.io.InputStreamReader;
|
||
|
import java.io.IOException;
|
||
|
import java.io.InterruptedIOException;
|
||
|
import java.io.RandomAccessFile;
|
||
|
import java.io.Reader;
|
||
|
import java.net.Socket;
|
||
|
import java.net.UnknownHostException;
|
||
|
import java.util.Enumeration;
|
||
|
import java.util.Hashtable;
|
||
|
import java.util.StringTokenizer;
|
||
|
import java.util.Vector;
|
||
|
|
||
|
import javax.mail.Folder;
|
||
|
import javax.mail.Message;
|
||
|
import javax.mail.MessagingException;
|
||
|
|
||
|
//import netscape.pop3.POP3Client;
|
||
|
//import netscape.pop3.POP3Status;
|
||
|
//import netscape.pop3.POP3MailDropStats;
|
||
|
|
||
|
class PopMailDrop implements MailDrop {
|
||
|
static final String KEEP = "k";
|
||
|
static final String DELETE = "d";
|
||
|
|
||
|
String fHost;
|
||
|
int fPort = 110;
|
||
|
String fUser;
|
||
|
String fPass;
|
||
|
Folder fFolder;
|
||
|
int fNumWaiting = -1;
|
||
|
Socket fSocket;
|
||
|
InputStream fInput;
|
||
|
DataOutputStream fOutput;
|
||
|
int stat_msgcount;
|
||
|
int stat_octetcount;
|
||
|
File popstatefile;
|
||
|
boolean uidlUnimplemented;
|
||
|
boolean xtndUnimplemented;
|
||
|
boolean leaveMessagesOnServer;
|
||
|
|
||
|
static final boolean DEBUG = false;
|
||
|
static void Spew(String s) {
|
||
|
if (DEBUG) System.out.println("PopMailDrop: " + s);
|
||
|
}
|
||
|
|
||
|
PopMailDrop() {
|
||
|
}
|
||
|
|
||
|
protected void clearAssumptionsAboutServer() {
|
||
|
uidlUnimplemented = false;
|
||
|
xtndUnimplemented = false;
|
||
|
}
|
||
|
|
||
|
void setHost(String hostname) {
|
||
|
fHost = hostname;
|
||
|
clearAssumptionsAboutServer();
|
||
|
}
|
||
|
|
||
|
void setPort(int port) {
|
||
|
fPort = port;
|
||
|
clearAssumptionsAboutServer();
|
||
|
}
|
||
|
|
||
|
void setUser(String username) {
|
||
|
fUser = username;
|
||
|
clearAssumptionsAboutServer();
|
||
|
}
|
||
|
|
||
|
void setPassword(String password) {
|
||
|
fPass = password;
|
||
|
clearAssumptionsAboutServer();
|
||
|
}
|
||
|
|
||
|
void setDestinationFolder(Folder dest) {
|
||
|
fFolder = dest;
|
||
|
}
|
||
|
|
||
|
Folder getDestinationFolder() {
|
||
|
return fFolder;
|
||
|
}
|
||
|
|
||
|
void setPopStateFile(File f) {
|
||
|
popstatefile = f;
|
||
|
Spew("popstatefile set to " + f);
|
||
|
}
|
||
|
|
||
|
File getPopStateFile() {
|
||
|
return popstatefile;
|
||
|
}
|
||
|
|
||
|
void setLeaveMessagesOnServer(boolean value) {
|
||
|
leaveMessagesOnServer = value;
|
||
|
}
|
||
|
|
||
|
boolean getLeaveMessagesOnServer() {
|
||
|
return leaveMessagesOnServer;
|
||
|
}
|
||
|
|
||
|
/** All the real work is done here. Go to the POP server, determine
|
||
|
what messages need downloading, and optionally actually download them.
|
||
|
(The only reason not to go ahead and download them is if we're doing a
|
||
|
biff check.) */
|
||
|
protected void fetchOrCheck(boolean dofetch) throws IOException {
|
||
|
try {
|
||
|
fNumWaiting = -1;
|
||
|
String response;
|
||
|
int bytesToLoad = 0; // How many bytes total we're going to
|
||
|
// download. Someday, this will be used for
|
||
|
// progress indication. ###
|
||
|
login();
|
||
|
Hashtable leave = loadLeaveOnServerList();
|
||
|
Hashtable newleave = null;
|
||
|
stat();
|
||
|
Vector getlist = new Vector();
|
||
|
if (leave == null && !leaveMessagesOnServer) {
|
||
|
for (int id=1 ; id<=stat_msgcount ; id++) {
|
||
|
getlist.addElement(new Integer(id));
|
||
|
}
|
||
|
bytesToLoad = stat_octetcount;
|
||
|
} else {
|
||
|
int sizes[] = new int[stat_msgcount + 1];
|
||
|
if (dofetch) { // Only bother getting sizes if we're going
|
||
|
// to be downloading some of these.
|
||
|
writeln("LIST");
|
||
|
check(readln(), "LIST command failed");
|
||
|
for (;;) {
|
||
|
String line = readln();
|
||
|
if (line.charAt(0) == '.') break;
|
||
|
StringTokenizer st = new StringTokenizer(line);
|
||
|
int id = Integer.parseInt(st.nextToken());
|
||
|
int count = Integer.parseInt(st.nextToken());
|
||
|
if (id >= 1 && id <= stat_msgcount) {
|
||
|
sizes[id] = count;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
boolean processingXtnd = false;
|
||
|
for (int id=1 ; id<=stat_msgcount ; id++) {
|
||
|
String uidl = null;
|
||
|
if (!uidlUnimplemented) {
|
||
|
writeln("UIDL " + id);
|
||
|
response = readln();
|
||
|
if (!resultOK(response)) {
|
||
|
uidlUnimplemented = true;
|
||
|
} else {
|
||
|
StringTokenizer st = new StringTokenizer(response);
|
||
|
st.nextToken();
|
||
|
st.nextToken();
|
||
|
uidl = st.nextToken();
|
||
|
}
|
||
|
}
|
||
|
if (uidl == null && !xtndUnimplemented && id == 1) {
|
||
|
writeln("XTND XLST Message-Id");
|
||
|
response = readln();
|
||
|
if (!resultOK(response)) {
|
||
|
xtndUnimplemented = true;
|
||
|
} else {
|
||
|
processingXtnd = true;
|
||
|
}
|
||
|
}
|
||
|
if (processingXtnd) {
|
||
|
response = readln();
|
||
|
if (response.startsWith(".")) {
|
||
|
processingXtnd = false;
|
||
|
} else {
|
||
|
StringTokenizer st = new StringTokenizer(response);
|
||
|
int i = Integer.parseInt(st.nextToken());
|
||
|
if (i != id) {
|
||
|
// Huh? xtnd skipped a message or something. Fine, throw
|
||
|
// away all the xtnd data, go use TOP instead.
|
||
|
do {
|
||
|
response = readln();
|
||
|
} while (!response.startsWith("."));
|
||
|
processingXtnd = false;
|
||
|
} else {
|
||
|
st.nextToken();
|
||
|
uidl = st.nextToken();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (uidl == null) {
|
||
|
writeln("TOP " + id + " 0");
|
||
|
check(readln(), "The POP3 server (%s) does not\n" +
|
||
|
"support UIDL, which Netscape Mail needs to implement\n" +
|
||
|
"the ``Leave on Server'' and ``Maximum Message Size''\n" +
|
||
|
"options.\n\n" +
|
||
|
"To download your mail, turn off these options in the\n" +
|
||
|
"Servers panel of the ``Mail and News'' Preferences.");
|
||
|
|
||
|
do {
|
||
|
response = readln();
|
||
|
if (uidl == null &&
|
||
|
response.regionMatches(true, 0, "Message-Id:", 0, 11)) {
|
||
|
uidl = response.substring(11).trim();
|
||
|
Spew("Top found message-id: " + uidl);
|
||
|
}
|
||
|
} while (!response.startsWith("."));
|
||
|
if (uidl == null) {
|
||
|
// Gack. No message-id in this message. Um, well, choke.
|
||
|
uidl = "unknown"; // ### Any better ideas???
|
||
|
Spew("Top couldn't find message-id.");
|
||
|
}
|
||
|
}
|
||
|
if (uidl.charAt(0) == '<' && uidl.endsWith(">")) {
|
||
|
uidl = uidl.substring(1, uidl.length() - 1);
|
||
|
}
|
||
|
|
||
|
String code = null;
|
||
|
if (leave != null) {
|
||
|
code = (String) leave.get(uidl);
|
||
|
if (code != null && !leaveMessagesOnServer && code.equals(KEEP)) {
|
||
|
// ### This causes us to redownload anything that we were
|
||
|
// keeping on the server if you turn off "keep messages on
|
||
|
// server". This is arguably wrong, because it floods peoples
|
||
|
// mailboxes with messages they've probably already seen. On
|
||
|
// the other hand, just deleting it could potentially lose
|
||
|
// mail for people. Bleah!
|
||
|
code = null;
|
||
|
}
|
||
|
}
|
||
|
if (code == null) {
|
||
|
getlist.addElement(new Integer(id));
|
||
|
bytesToLoad += sizes[id];
|
||
|
if (leaveMessagesOnServer) {
|
||
|
code = KEEP;
|
||
|
}
|
||
|
}
|
||
|
if (code != null) {
|
||
|
if (newleave == null) {
|
||
|
newleave = new Hashtable();
|
||
|
}
|
||
|
newleave.put(uidl, code);
|
||
|
}
|
||
|
}
|
||
|
if (processingXtnd) {
|
||
|
do {
|
||
|
response = readln();
|
||
|
} while (!response.startsWith("."));
|
||
|
processingXtnd = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fNumWaiting = getlist.size();
|
||
|
|
||
|
// if (dofetch && fNumWaiting > 0) {
|
||
|
// for (Enumeration e = getlist.elements() ; e.hasMoreElements() ; ) {
|
||
|
// int id = ((Integer) e.nextElement()).intValue();
|
||
|
// // ### Insert code here to do partial messages...
|
||
|
// writeln("RETR " + id);
|
||
|
// check(readln(), "Requesting message from server");
|
||
|
// InputStream in = new DotTerminatedInputStream(fInput);
|
||
|
// Message m = new MessageFromStream(in);
|
||
|
// Message[] stupidlist = { m };
|
||
|
// try {
|
||
|
// fFolder.appendMessages(stupidlist);
|
||
|
// } catch (MessagingException err) {
|
||
|
// throw new IOException(err.toString());
|
||
|
// }
|
||
|
//
|
||
|
// // Do filters, something like:
|
||
|
// FilterMaster.Get().applyFilters(m);
|
||
|
// // though it probably makes more sense to do this before the
|
||
|
// // fFolder.appendMessages() and not do that if the filter
|
||
|
// // has already saved the message...djw
|
||
|
//
|
||
|
// if (!leaveMessagesOnServer) {
|
||
|
// writeln("DELE " + id);
|
||
|
// check(readln(), "Deleting retrieved message from server");
|
||
|
// }
|
||
|
// }
|
||
|
// fNumWaiting = 0;
|
||
|
// if (leave != null || newleave != null) {
|
||
|
// saveLeaveOnServerList(newleave);
|
||
|
// }
|
||
|
// }
|
||
|
logout();
|
||
|
} finally {
|
||
|
cleanup();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected Hashtable loadLeaveOnServerList() {
|
||
|
if (popstatefile == null) return null;
|
||
|
RandomAccessFile f = null;
|
||
|
Hashtable result = null;
|
||
|
try {
|
||
|
f = new RandomAccessFile(popstatefile, "r");
|
||
|
boolean found = false;
|
||
|
for (String line = f.readLine() ; line != null ; line = f.readLine()) {
|
||
|
if (line.length() == 0) continue;
|
||
|
if (!found) {
|
||
|
if (line.charAt(0) == '*') {
|
||
|
StringTokenizer st = new StringTokenizer(line.substring(1));
|
||
|
if (fHost.equals(st.nextToken())) {
|
||
|
if (fUser.equals(st.nextToken())) {
|
||
|
found = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if (line.charAt(0) == '*') break; // Start of next host; we're done.
|
||
|
if (result == null) {
|
||
|
result = new Hashtable();
|
||
|
}
|
||
|
result.put(line.substring(2).trim(), line.substring(0, 1));
|
||
|
}
|
||
|
}
|
||
|
} catch (IOException e) {
|
||
|
} finally {
|
||
|
if (f != null) {
|
||
|
try {
|
||
|
f.close();
|
||
|
} catch (IOException e) {
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
protected void saveLeaveOnServerList(Hashtable table) throws IOException {
|
||
|
synchronized(popstatefile) {
|
||
|
// This is all done synchronized via the popstatefile, so that if we
|
||
|
// have multiple pop servers they will not attempt to write to the file
|
||
|
// at the same time. This assumes that every pop server was given the
|
||
|
// same File object to represent the popstate file.
|
||
|
|
||
|
RandomAccessFile in = null;
|
||
|
RandomAccessFile out = null;
|
||
|
File outfile = new File(popstatefile.getPath() + "-temp");
|
||
|
try {
|
||
|
out = new RandomAccessFile(outfile, "rw");
|
||
|
out.writeBytes("# Netscape POP3 State File" + Constants.LINEBREAK +
|
||
|
"# This is a generated file! Do not edit." +
|
||
|
Constants.LINEBREAK + Constants.LINEBREAK);
|
||
|
|
||
|
if (table != null) {
|
||
|
out.writeBytes("*" + fHost + " " + fUser + Constants.LINEBREAK);
|
||
|
for (Enumeration e = table.keys() ; e.hasMoreElements() ; ) {
|
||
|
String key = (String) e.nextElement();
|
||
|
out.writeBytes((String) (table.get(key)) + " " + key +
|
||
|
Constants.LINEBREAK);
|
||
|
}
|
||
|
}
|
||
|
if (popstatefile.exists()) {
|
||
|
in = new RandomAccessFile(popstatefile, "r");
|
||
|
boolean copyit = false;
|
||
|
String line;
|
||
|
for (line = in.readLine() ; line != null ; line = in.readLine()) {
|
||
|
if (line.length() == 0) continue;
|
||
|
if (line.charAt(0) == '*') {
|
||
|
copyit = true;
|
||
|
StringTokenizer st = new StringTokenizer(line.substring(1));
|
||
|
if (fHost.equals(st.nextToken())) {
|
||
|
if (fUser.equals(st.nextToken())) {
|
||
|
copyit = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (copyit) {
|
||
|
out.writeBytes(line.trim() + Constants.LINEBREAK);
|
||
|
}
|
||
|
}
|
||
|
in.close();
|
||
|
in = null;
|
||
|
}
|
||
|
out.close();
|
||
|
out = null;
|
||
|
outfile.renameTo(popstatefile);
|
||
|
} finally {
|
||
|
if (in != null) in.close();
|
||
|
if (out != null) out.close();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
synchronized public void fetchNewMail() throws IOException {
|
||
|
fetchOrCheck(true);
|
||
|
}
|
||
|
|
||
|
synchronized public void doBiff() throws IOException {
|
||
|
fetchOrCheck(false);
|
||
|
}
|
||
|
|
||
|
protected void login() throws IOException {
|
||
|
// ### I18N -- all these exceptions need to be internationalized.
|
||
|
if (fHost == null) {
|
||
|
throw new MailDropException("No host specified for POP3 maildrop");
|
||
|
}
|
||
|
if (fUser == null) {
|
||
|
throw new MailDropException("No user specified for POP3 maildrop");
|
||
|
}
|
||
|
try {
|
||
|
fSocket = new Socket(fHost, fPort);
|
||
|
} catch (UnknownHostException e) {
|
||
|
throw new MailDropException("Unknown host: " + e.getMessage());
|
||
|
}
|
||
|
|
||
|
fInput = fSocket.getInputStream();
|
||
|
fOutput = new DataOutputStream(fSocket.getOutputStream());
|
||
|
|
||
|
check(readln(), "Connecting to server");
|
||
|
writeln("USER " + fUser);
|
||
|
check(readln(), "USER command not accepted");
|
||
|
writeln("PASS " + fPass);
|
||
|
check(readln(), "Login failed");
|
||
|
// ### Should probably do GURL at this point, if necessary, to remember
|
||
|
// the URL used to manage this account.
|
||
|
}
|
||
|
|
||
|
protected void cleanup() {
|
||
|
if (fSocket != null) {
|
||
|
try {
|
||
|
fSocket.close();
|
||
|
} catch (IOException e) {
|
||
|
}
|
||
|
}
|
||
|
fSocket = null;
|
||
|
fInput = null;
|
||
|
fOutput = null;
|
||
|
}
|
||
|
|
||
|
protected void logout() throws IOException {
|
||
|
writeln("QUIT");
|
||
|
check(readln(), "Logout failed");
|
||
|
}
|
||
|
|
||
|
protected void stat() throws IOException {
|
||
|
writeln("STAT");
|
||
|
String line = readln();
|
||
|
check(line, "Stat failed");
|
||
|
StringTokenizer st = new StringTokenizer(line);
|
||
|
st.nextToken(); //skip +OK
|
||
|
stat_msgcount = Integer.parseInt(st.nextToken());
|
||
|
stat_octetcount = Integer.parseInt(st.nextToken());
|
||
|
}
|
||
|
|
||
|
protected void writeln(String line) throws IOException {
|
||
|
Spew(">> " + line);
|
||
|
fOutput.writeBytes(line + "\n");
|
||
|
}
|
||
|
|
||
|
StringBuffer inbuffer = new StringBuffer();
|
||
|
protected String readln() throws IOException {
|
||
|
inbuffer.setLength(0);
|
||
|
int c;
|
||
|
do {
|
||
|
c = fInput.read();
|
||
|
if (c >= 0) inbuffer.append((char) c);
|
||
|
} while (c != '\n');
|
||
|
Spew("<< " + inbuffer);
|
||
|
return inbuffer.toString();
|
||
|
}
|
||
|
|
||
|
protected void check(String line, String err) throws MailDropException {
|
||
|
if (!resultOK(line)) {
|
||
|
throw new MailDropException(err + " (Server response: " + line + ")");
|
||
|
// ### I18N
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected boolean resultOK(String line) {
|
||
|
return line.startsWith("+OK");
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
public int getBiffState() {
|
||
|
if (fNumWaiting < 0) return UNKNOWN;
|
||
|
if (fNumWaiting == 0) return NONE;
|
||
|
return NEW;
|
||
|
}
|
||
|
|
||
|
public int getNumMessagesWaiting() {
|
||
|
return fNumWaiting;
|
||
|
}
|
||
|
|
||
|
// protected void check(POP3Status status) throws MailDropException {
|
||
|
// check(status.getStatus(), status.getServerResponse());
|
||
|
// }
|
||
|
|
||
|
protected void check(boolean status, String response)
|
||
|
throws MailDropException
|
||
|
{
|
||
|
if (!status) {
|
||
|
throw new MailDropException("Pop server reports: " + response);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|