77 |
*/ |
*/ |
78 |
public static final int DEFAULT_PORT = 110; |
public static final int DEFAULT_PORT = 110; |
79 |
|
|
80 |
// -- POP3 vocabulary -- |
// -- POP3 vocabulary -- |
81 |
private static final String _OK = "+OK"; |
private static final String _OK = "+OK"; |
82 |
private static final String _ERR = "-ERR"; |
private static final String _ERR = "-ERR"; |
83 |
private static final String _READY = "+ "; |
private static final String _READY = "+ "; |
84 |
|
|
85 |
protected static final String STAT = "STAT"; |
protected static final String STAT = "STAT"; |
86 |
protected static final String LIST = "LIST"; |
protected static final String LIST = "LIST"; |
87 |
protected static final String RETR = "RETR"; |
protected static final String RETR = "RETR"; |
88 |
protected static final String DELE = "DELE"; |
protected static final String DELE = "DELE"; |
89 |
protected static final String NOOP = "NOOP"; |
protected static final String NOOP = "NOOP"; |
90 |
protected static final String RSET = "RSET"; |
protected static final String RSET = "RSET"; |
91 |
protected static final String QUIT = "QUIT"; |
protected static final String QUIT = "QUIT"; |
92 |
protected static final String TOP = "TOP"; |
protected static final String TOP = "TOP"; |
93 |
protected static final String UIDL = "UIDL"; |
protected static final String UIDL = "UIDL"; |
94 |
protected static final String USER = "USER"; |
protected static final String USER = "USER"; |
95 |
protected static final String PASS = "PASS"; |
protected static final String PASS = "PASS"; |
96 |
protected static final String APOP = "APOP"; |
protected static final String APOP = "APOP"; |
97 |
protected static final String CAPA = "CAPA"; |
protected static final String CAPA = "CAPA"; |
98 |
protected static final String STLS = "STLS"; |
protected static final String STLS = "STLS"; |
99 |
protected static final String AUTH = "AUTH"; |
protected static final String AUTH = "AUTH"; |
100 |
|
|
101 |
protected static final int OK = 0; |
protected static final int OK = 0; |
102 |
protected static final int ERR = 1; |
protected static final int ERR = 1; |
103 |
protected static final int READY = 2; |
protected static final int READY = 2; |
104 |
|
|
105 |
/** |
/** |
106 |
* The socket used to communicate with the server. |
* The socket used to communicate with the server. |
107 |
*/ |
*/ |
108 |
protected Socket socket; |
protected Socket socket; |
109 |
|
|
110 |
/** |
/** |
111 |
* The socket input stream. |
* The socket input stream. |
112 |
*/ |
*/ |
113 |
protected LineInputStream in; |
protected LineInputStream in; |
114 |
|
|
115 |
/** |
/** |
116 |
* The socket output stream. |
* The socket output stream. |
117 |
*/ |
*/ |
118 |
protected CRLFOutputStream out; |
protected CRLFOutputStream out; |
119 |
|
|
120 |
/** |
/** |
121 |
* The last response received from the server. |
* The last response received from the server. |
122 |
* The status code (+OK or -ERR) is stripped from the line. |
* The status code (+OK or -ERR) is stripped from the line. |
123 |
*/ |
*/ |
124 |
protected String response; |
protected String response; |
125 |
|
|
126 |
/** |
/** |
127 |
* If true, print debugging information. |
* If true, print debugging information. |
128 |
*/ |
*/ |
129 |
protected boolean debug; |
protected boolean debug; |
130 |
|
|
131 |
/** |
/** |
132 |
* The APOP timestamp, if sent by the server on connection. |
* The APOP timestamp, if sent by the server on connection. |
133 |
* Otherwise null. |
* Otherwise null. |
134 |
*/ |
*/ |
135 |
protected byte[] timestamp; |
protected byte[] timestamp; |
136 |
|
|
137 |
/** |
/** |
138 |
* Creates a new connection to the server. |
* Creates a new connection to the server. |
140 |
* @param port the port to connect to (if <=0, use default POP3 port) |
* @param port the port to connect to (if <=0, use default POP3 port) |
141 |
*/ |
*/ |
142 |
public POP3Connection(String hostname, int port) |
public POP3Connection(String hostname, int port) |
143 |
throws UnknownHostException, IOException |
throws UnknownHostException, IOException |
144 |
{ |
{ |
145 |
this(hostname, port, -1, -1, false); |
this(hostname, port, -1, -1, false); |
146 |
} |
} |
147 |
|
|
148 |
/** |
/** |
149 |
* Creates a new connection to the server. |
* Creates a new connection to the server. |
150 |
* @param hostname the hostname of the server to connect to |
* @param hostname the hostname of the server to connect to |
154 |
* @param debug print debugging information |
* @param debug print debugging information |
155 |
*/ |
*/ |
156 |
public POP3Connection(String hostname, int port, |
public POP3Connection(String hostname, int port, |
157 |
int connectionTimeout, int timeout, boolean debug) |
int connectionTimeout, int timeout, boolean debug) |
158 |
throws UnknownHostException, IOException |
throws UnknownHostException, IOException |
159 |
{ |
{ |
160 |
this.debug = debug; |
this.debug = debug; |
161 |
if (port<=0) |
if (port <= 0) |
162 |
port = DEFAULT_PORT; |
port = DEFAULT_PORT; |
163 |
|
|
164 |
// Set up socket |
// Set up socket |
165 |
// TODO connection timeout |
// TODO connection timeout |
166 |
socket = new Socket(hostname, port); |
socket = new Socket(hostname, port); |
167 |
if (timeout>0) |
if (timeout > 0) |
168 |
socket.setSoTimeout(timeout); |
socket.setSoTimeout(timeout); |
169 |
|
|
170 |
InputStream in = socket.getInputStream(); |
InputStream in = socket.getInputStream(); |
171 |
in = new BufferedInputStream(in); |
in = new BufferedInputStream(in); |
172 |
in = new CRLFInputStream(in); |
in = new CRLFInputStream(in); |
173 |
this.in = new LineInputStream(in); |
this.in = new LineInputStream(in); |
174 |
OutputStream out = socket.getOutputStream(); |
OutputStream out = socket.getOutputStream(); |
175 |
out = new BufferedOutputStream(out); |
out = new BufferedOutputStream(out); |
176 |
this.out = new CRLFOutputStream(out); |
this.out = new CRLFOutputStream(out); |
177 |
|
|
178 |
if (getResponse()!=OK) |
if (getResponse() != OK) |
179 |
throw new ProtocolException("Connect failed: "+response); |
throw new ProtocolException("Connect failed: " + response); |
180 |
// APOP timestamp |
// APOP timestamp |
181 |
timestamp = parseTimestamp(response); |
timestamp = parseTimestamp(response); |
182 |
} |
} |
183 |
|
|
184 |
/** |
/** |
185 |
* Authenticates the connection using the specified SASL mechanism, |
* Authenticates the connection using the specified SASL mechanism, |
186 |
* username, and password. |
* username, and password. |
187 |
* @param mechanism a SASL authentication mechanism, e.g. LOGIN, PLAIN, |
* @param mechanism a SASL authentication mechanism, e.g. LOGIN, PLAIN, |
190 |
* @param password the authentication credentials |
* @param password the authentication credentials |
191 |
* @return true if authentication was successful, false otherwise |
* @return true if authentication was successful, false otherwise |
192 |
*/ |
*/ |
193 |
public boolean auth(String mechanism, String username, String password) |
public boolean auth(String mechanism, String username, String password) |
194 |
throws IOException |
throws IOException |
195 |
{ |
{ |
196 |
try |
try |
197 |
{ |
{ |
198 |
String[] m = new String[] { mechanism }; |
String[]m = new String[] |
199 |
CallbackHandler ch = new SaslCallbackHandler(username, password); |
{ |
200 |
// Avoid lengthy callback procedure for GNU Crypto |
mechanism}; |
201 |
Properties p = new Properties(); |
CallbackHandler ch = new SaslCallbackHandler(username, password); |
202 |
p.put("gnu.crypto.sasl.username", username); |
// Avoid lengthy callback procedure for GNU Crypto |
203 |
p.put("gnu.crypto.sasl.password", password); |
Properties p = new Properties(); |
204 |
SaslClient sasl = Sasl.createSaslClient(m, null, "smtp", |
p.put("gnu.crypto.sasl.username", username); |
205 |
socket.getInetAddress().getHostName(), p, ch); |
p.put("gnu.crypto.sasl.password", password); |
206 |
|
SaslClient sasl = Sasl.createSaslClient(m, null, "smtp", |
207 |
StringBuffer cmd = new StringBuffer(AUTH); |
socket.getInetAddress(). |
208 |
cmd.append(' '); |
getHostName(), p, ch); |
209 |
cmd.append(mechanism); |
|
210 |
if (sasl.hasInitialResponse()) |
StringBuffer cmd = new StringBuffer(AUTH); |
211 |
{ |
cmd.append(' '); |
212 |
cmd.append(' '); |
cmd.append(mechanism); |
213 |
byte[] init = sasl.evaluateChallenge(new byte[0]); |
if (sasl.hasInitialResponse()) |
214 |
cmd.append(new String(init, "US-ASCII")); |
{ |
215 |
} |
cmd.append(' '); |
216 |
send(cmd.toString()); |
byte[] init = sasl.evaluateChallenge(new byte[0]); |
217 |
while (true) |
cmd.append(new String(init, "US-ASCII")); |
218 |
{ |
} |
219 |
switch (getResponse()) |
send(cmd.toString()); |
220 |
{ |
while (true) |
221 |
case ERR: |
{ |
222 |
return false; |
switch (getResponse()) |
223 |
case OK: |
{ |
224 |
String qop = (String)sasl.getNegotiatedProperty(Sasl.QOP); |
case ERR: |
225 |
if ("auth-int".equalsIgnoreCase(qop) |
return false; |
226 |
|| "auth-conf".equalsIgnoreCase(qop)) |
case OK: |
227 |
{ |
String qop = (String) sasl.getNegotiatedProperty(Sasl.QOP); |
228 |
InputStream in = socket.getInputStream(); |
if ("auth-int".equalsIgnoreCase(qop) |
229 |
in = new BufferedInputStream(in); |
|| "auth-conf".equalsIgnoreCase(qop)) |
230 |
in = new SaslInputStream(sasl, in); |
{ |
231 |
in = new CRLFInputStream(in); |
InputStream in = socket.getInputStream(); |
232 |
this.in = new LineInputStream(in); |
in = new BufferedInputStream(in); |
233 |
OutputStream out = socket.getOutputStream(); |
in = new SaslInputStream(sasl, in); |
234 |
out = new BufferedOutputStream(out); |
in = new CRLFInputStream(in); |
235 |
out = new SaslOutputStream(sasl, out); |
this.in = new LineInputStream(in); |
236 |
this.out = new CRLFOutputStream(out); |
OutputStream out = socket.getOutputStream(); |
237 |
} |
out = new BufferedOutputStream(out); |
238 |
return true; |
out = new SaslOutputStream(sasl, out); |
239 |
case READY: |
this.out = new CRLFOutputStream(out); |
240 |
try |
} |
241 |
{ |
return true; |
242 |
byte[] c0 = response.getBytes("US-ASCII"); |
case READY: |
243 |
byte[] c1 = BASE64.decode(c0); // challenge |
try |
244 |
byte[] r0 = sasl.evaluateChallenge(c1); |
{ |
245 |
byte[] r1 = BASE64.encode(r0); // response |
byte[]c0 = response.getBytes("US-ASCII"); |
246 |
out.write(r1); |
byte[]c1 = BASE64.decode(c0); // challenge |
247 |
out.write(0x0d); |
byte[]r0 = sasl.evaluateChallenge(c1); |
248 |
out.flush(); |
byte[]r1 = BASE64.encode(r0); // response |
249 |
} |
out.write(r1); |
250 |
catch (SaslException e) |
out.write(0x0d); |
251 |
{ |
out.flush(); |
252 |
// Error in SASL challenge evaluation - cancel exchange |
} |
253 |
out.write(0x2a); |
catch(SaslException e) |
254 |
out.write(0x0d); |
{ |
255 |
out.flush(); |
// Error in SASL challenge evaluation - cancel exchange |
256 |
} |
out.write(0x2a); |
257 |
} |
out.write(0x0d); |
258 |
} |
out.flush(); |
259 |
} |
} |
260 |
catch (SaslException e) |
} |
261 |
{ |
} |
262 |
return false; // No provider for mechanism |
} |
263 |
} |
catch(SaslException e) |
264 |
catch (ClassNotFoundException e) |
{ |
265 |
{ |
return false; // No provider for mechanism |
266 |
return false; // No javax.security.sasl classes |
} |
267 |
} |
catch(ClassNotFoundException e) |
268 |
} |
{ |
269 |
|
return false; // No javax.security.sasl classes |
270 |
|
} |
271 |
|
} |
272 |
|
|
273 |
/** |
/** |
274 |
* Authenticate the specified user using the APOP MD5-based method. |
* Authenticate the specified user using the APOP MD5-based method. |
275 |
* This does not transmit the password in the clear, but doesn't provide |
* This does not transmit the password in the clear, but doesn't provide |
276 |
* any transport-level privacy features either. |
* any transport-level privacy features either. |
277 |
* @param username the user to authenticate |
* @param username the user to authenticate |
278 |
* @param password the user's password |
* @param password the user's password |
279 |
*/ |
*/ |
280 |
public boolean apop(String username, String password) |
public boolean apop(String username, String password) throws IOException |
281 |
throws IOException |
{ |
282 |
{ |
if (username == null || password == null || timestamp != null) |
283 |
if (username==null || password==null || timestamp!=null) |
return false; |
284 |
return false; |
// APOP <username> <digest> |
285 |
// APOP <username> <digest> |
try |
286 |
try |
{ |
287 |
{ |
byte[]secret = password.getBytes("US-ASCII"); |
288 |
byte[] secret = password.getBytes("US-ASCII"); |
// compute digest |
289 |
// compute digest |
byte[] target = new byte[timestamp.length + secret.length]; |
290 |
byte[] target = new byte[timestamp.length + secret.length]; |
System.arraycopy(timestamp, 0, target, 0, timestamp.length); |
291 |
System.arraycopy(timestamp, 0, target, 0, timestamp.length); |
System.arraycopy(secret, 0, target, timestamp.length, secret.length); |
292 |
System.arraycopy(secret, 0, target, timestamp.length, secret.length); |
MessageDigest md5 = MessageDigest.getInstance("MD5"); |
293 |
MessageDigest md5 = MessageDigest.getInstance("MD5"); |
byte[] db = md5.digest(target); |
294 |
byte[] db = md5.digest(target); |
// create hexadecimal representation |
295 |
// create hexadecimal representation |
StringBuffer digest = new StringBuffer(); |
296 |
StringBuffer digest = new StringBuffer(); |
for (int i = 0; i < db.length; i++) |
297 |
for (int i=0; i<db.length; i++) |
{ |
298 |
{ |
int c = (int) db[i]; |
299 |
int c = (int)db[i]; |
if (c < 0) |
300 |
if (c<0) |
c += 256; |
301 |
c += 256; |
digest.append(Integer.toHexString(c)); |
302 |
digest.append(Integer.toHexString(c)); |
} |
303 |
} |
// send command |
304 |
// send command |
String cmd = |
305 |
String cmd = new StringBuffer(APOP) |
new StringBuffer(APOP).append(' ').append(username).append(' '). |
306 |
.append(' ') |
append(digest.toString()).toString(); |
307 |
.append(username) |
send(cmd); |
308 |
.append(' ') |
return getResponse() == OK; |
309 |
.append(digest.toString()) |
} |
310 |
.toString(); |
catch(NoSuchAlgorithmException e) |
311 |
send(cmd); |
{ |
312 |
return getResponse()==OK; |
Logger logger = Logger.getInstance(); |
313 |
} |
logger.log("pop3", "MD5 algorithm not found"); |
314 |
catch (NoSuchAlgorithmException e) |
return false; |
315 |
{ |
} |
316 |
Logger logger = Logger.getInstance(); |
} |
|
logger.log("pop3", "MD5 algorithm not found"); |
|
|
return false; |
|
|
} |
|
|
} |
|
317 |
|
|
318 |
/** |
/** |
319 |
* Authenticate the user using the basic USER and PASS handshake. |
* Authenticate the user using the basic USER and PASS handshake. |
320 |
* It is recommended to use a more secure authentication method such as |
* It is recommended to use a more secure authentication method such as |
321 |
* the <code>auth</code> or <code>apop</code> method if the server |
* the <code>auth</code> or <code>apop</code> method if the server |
323 |
* @param username the user to authenticate |
* @param username the user to authenticate |
324 |
* @param password the user's password |
* @param password the user's password |
325 |
*/ |
*/ |
326 |
public boolean login(String username, String password) |
public boolean login(String username, String password) throws IOException |
327 |
throws IOException |
{ |
328 |
{ |
if (username == null || password == null) |
329 |
if (username==null || password==null) |
return false; |
330 |
return false; |
// USER <username> |
331 |
// USER <username> |
String cmd = |
332 |
String cmd = new StringBuffer(USER) |
new StringBuffer(USER).append(' ').append(username).toString(); |
333 |
.append(' ') |
send(cmd); |
334 |
.append(username) |
if (getResponse() != OK) |
335 |
.toString(); |
return false; |
336 |
send(cmd); |
// PASS <password> |
337 |
if (getResponse()!=OK) |
cmd = new StringBuffer(PASS).append(' ').append(password).toString(); |
338 |
return false; |
send(cmd); |
339 |
// PASS <password> |
return getResponse() == OK; |
340 |
cmd = new StringBuffer(PASS) |
} |
|
.append(' ') |
|
|
.append(password) |
|
|
.toString(); |
|
|
send(cmd); |
|
|
return getResponse()==OK; |
|
|
} |
|
341 |
|
|
342 |
/** |
/** |
343 |
* Attempts to start TLS on the specified connection. |
* Attempts to start TLS on the specified connection. |
344 |
* See RFC 2595 for details |
* See RFC 2595 for details |
345 |
* @return true if successful, false otherwise |
* @return true if successful, false otherwise |
346 |
*/ |
*/ |
347 |
public boolean stls() |
public boolean stls() throws IOException |
|
throws IOException |
|
348 |
{ |
{ |
349 |
try |
try |
350 |
{ |
{ |
351 |
// Use SSLSocketFactory to negotiate a TLS session and wrap the |
// Use SSLSocketFactory to negotiate a TLS session and wrap the |
352 |
// current socket. |
// current socket. |
353 |
SSLSocketFactory factory = |
SSLSocketFactory factory = |
354 |
(SSLSocketFactory)SSLSocketFactory.getDefault(); |
(SSLSocketFactory) SSLSocketFactory.getDefault(); |
355 |
|
|
356 |
send(STLS); |
send(STLS); |
357 |
if (getResponse()!=OK) |
if (getResponse() != OK) |
358 |
return false; |
return false; |
359 |
|
|
360 |
socket = factory.createSocket(socket, |
socket = factory.createSocket(socket, |
361 |
socket.getInetAddress().getHostName(), |
socket.getInetAddress().getHostName(), |
362 |
socket.getPort(), |
socket.getPort(), true); |
363 |
true); |
|
364 |
|
// set up streams |
365 |
// set up streams |
InputStream in = socket.getInputStream(); |
366 |
InputStream in = socket.getInputStream(); |
in = new BufferedInputStream(in); |
367 |
in = new BufferedInputStream(in); |
in = new CRLFInputStream(in); |
368 |
in = new CRLFInputStream(in); |
this.in = new LineInputStream(in); |
369 |
this.in = new LineInputStream(in); |
OutputStream out = socket.getOutputStream(); |
370 |
OutputStream out = socket.getOutputStream(); |
out = new BufferedOutputStream(out); |
371 |
out = new BufferedOutputStream(out); |
this.out = new CRLFOutputStream(out); |
372 |
this.out = new CRLFOutputStream(out); |
|
373 |
|
return true; |
|
return true; |
|
374 |
} |
} |
375 |
catch (ClassNotFoundException e) |
catch(ClassNotFoundException e) |
376 |
{ |
{ |
377 |
return false; // No javax.net classes in runtime |
return false; // No javax.net classes in runtime |
378 |
} |
} |
379 |
} |
} |
380 |
|
|
381 |
/** |
/** |
382 |
* Returns the number of messages in the maildrop. |
* Returns the number of messages in the maildrop. |
383 |
*/ |
*/ |
384 |
public int stat() |
public int stat() throws IOException |
385 |
throws IOException |
{ |
386 |
{ |
send(STAT); |
387 |
send(STAT); |
if (getResponse() != OK) |
388 |
if (getResponse()!=OK) |
throw new ProtocolException("STAT failed: " + response); |
389 |
throw new ProtocolException("STAT failed: "+response); |
try |
390 |
try |
{ |
391 |
{ |
return Integer.parseInt(response.substring(0, response.indexOf(' '))); |
392 |
return Integer.parseInt(response.substring(0, response.indexOf(' '))); |
} |
393 |
} |
catch(NumberFormatException e) |
394 |
catch (NumberFormatException e) |
{ |
395 |
{ |
throw new ProtocolException("Not a number: " + response); |
396 |
throw new ProtocolException("Not a number: "+response); |
} |
397 |
} |
catch(ArrayIndexOutOfBoundsException e) |
398 |
catch (ArrayIndexOutOfBoundsException e) |
{ |
399 |
{ |
throw new ProtocolException("Not a STAT response: " + response); |
400 |
throw new ProtocolException("Not a STAT response: "+response); |
} |
401 |
} |
} |
|
} |
|
402 |
|
|
403 |
/** |
/** |
404 |
* Returns the size of the specified message. |
* Returns the size of the specified message. |
405 |
* @param msgnum the message number |
* @param msgnum the message number |
406 |
*/ |
*/ |
407 |
public int list(int msgnum) |
public int list(int msgnum) throws IOException |
408 |
throws IOException |
{ |
409 |
{ |
String cmd = new StringBuffer(LIST).append(' ').append(msgnum).toString(); |
410 |
String cmd = new StringBuffer(LIST) |
send(cmd); |
411 |
.append(' ') |
if (getResponse() != OK) |
412 |
.append(msgnum) |
throw new ProtocolException("LIST failed: " + response); |
413 |
.toString(); |
try |
414 |
send(cmd); |
{ |
415 |
if (getResponse()!=OK) |
return Integer.parseInt(response.substring(response.indexOf(' ') + 1)); |
416 |
throw new ProtocolException("LIST failed: "+response); |
} |
417 |
try |
catch(NumberFormatException e) |
418 |
{ |
{ |
419 |
return Integer.parseInt(response.substring(response.indexOf(' ')+1)); |
throw new ProtocolException("Not a number: " + response); |
420 |
} |
} |
421 |
catch (NumberFormatException e) |
} |
|
{ |
|
|
throw new ProtocolException("Not a number: "+response); |
|
|
} |
|
|
} |
|
422 |
|
|
423 |
/** |
/** |
424 |
* Returns an input stream containing the entire message. |
* Returns an input stream containing the entire message. |
425 |
* This input stream must be read in its entirety before further commands |
* This input stream must be read in its entirety before further commands |
426 |
* can be issued on this connection. |
* can be issued on this connection. |
427 |
* @param msgnum the message number |
* @param msgnum the message number |
428 |
*/ |
*/ |
429 |
public InputStream retr(int msgnum) |
public InputStream retr(int msgnum) throws IOException |
430 |
throws IOException |
{ |
431 |
{ |
String cmd = new StringBuffer(RETR).append(' ').append(msgnum).toString(); |
432 |
String cmd = new StringBuffer(RETR) |
send(cmd); |
433 |
.append(' ') |
if (getResponse() != OK) |
434 |
.append(msgnum) |
throw new ProtocolException("RETR failed: " + response); |
435 |
.toString(); |
return new MessageInputStream(in); |
436 |
send(cmd); |
} |
|
if (getResponse()!=OK) |
|
|
throw new ProtocolException("RETR failed: "+response); |
|
|
return new MessageInputStream(in); |
|
|
} |
|
437 |
|
|
438 |
/** |
/** |
439 |
* Marks the specified message as deleted. |
* Marks the specified message as deleted. |
440 |
* @param msgnum the message number |
* @param msgnum the message number |
441 |
*/ |
*/ |
442 |
public void dele(int msgnum) |
public void dele(int msgnum) throws IOException |
443 |
throws IOException |
{ |
444 |
{ |
String cmd = new StringBuffer(DELE).append(' ').append(msgnum).toString(); |
445 |
String cmd = new StringBuffer(DELE) |
send(cmd); |
446 |
.append(' ') |
if (getResponse() != OK) |
447 |
.append(msgnum) |
throw new ProtocolException("DELE failed: " + response); |
448 |
.toString(); |
} |
|
send(cmd); |
|
|
if (getResponse()!=OK) |
|
|
throw new ProtocolException("DELE failed: "+response); |
|
|
} |
|
449 |
|
|
450 |
/** |
/** |
451 |
* Does nothing. |
* Does nothing. |
452 |
* This can be used to keep the connection alive. |
* This can be used to keep the connection alive. |
453 |
*/ |
*/ |
454 |
public void noop() |
public void noop() throws IOException |
455 |
throws IOException |
{ |
456 |
{ |
send(NOOP); |
457 |
send(NOOP); |
if (getResponse() != OK) |
458 |
if (getResponse()!=OK) |
throw new ProtocolException("NOOP failed: " + response); |
459 |
throw new ProtocolException("NOOP failed: "+response); |
} |
|
} |
|
460 |
|
|
461 |
/** |
/** |
462 |
* If any messages have been marked as deleted, they are unmarked. |
* If any messages have been marked as deleted, they are unmarked. |
463 |
*/ |
*/ |
464 |
public void rset() |
public void rset() throws IOException |
465 |
throws IOException |
{ |
466 |
{ |
send(RSET); |
467 |
send(RSET); |
if (getResponse() != OK) |
468 |
if (getResponse()!=OK) |
throw new ProtocolException("RSET failed: " + response); |
469 |
throw new ProtocolException("RSET failed: "+response); |
} |
|
} |
|
470 |
|
|
471 |
/** |
/** |
472 |
* Closes the connection. |
* Closes the connection. |
475 |
* @return true if all deleted messages were successfully removed, false |
* @return true if all deleted messages were successfully removed, false |
476 |
* otherwise |
* otherwise |
477 |
*/ |
*/ |
478 |
public boolean quit() |
public boolean quit() throws IOException |
|
throws IOException |
|
479 |
{ |
{ |
480 |
send(QUIT); |
send(QUIT); |
481 |
int ret = getResponse(); |
int ret = getResponse(); |
482 |
socket.close(); |
socket.close(); |
483 |
return ret==OK; |
return ret == OK; |
484 |
} |
} |
485 |
|
|
486 |
/** |
/** |
487 |
* Returns just the headers of the specified message as an input stream. |
* Returns just the headers of the specified message as an input stream. |
488 |
* The stream must be read in its entirety before further commands can be |
* The stream must be read in its entirety before further commands can be |
489 |
* issued. |
* issued. |
490 |
* @param msgnum the message number |
* @param msgnum the message number |
491 |
*/ |
*/ |
492 |
public InputStream top(int msgnum) |
public InputStream top(int msgnum) throws IOException |
493 |
throws IOException |
{ |
494 |
{ |
String cmd = |
495 |
String cmd = new StringBuffer(TOP) |
new StringBuffer(TOP).append(' ').append(msgnum).append(' ').append(0). |
496 |
.append(' ') |
toString(); |
497 |
.append(msgnum) |
send(cmd); |
498 |
.append(' ') |
if (getResponse() != OK) |
499 |
.append(0) |
throw new ProtocolException("TOP failed: " + response); |
500 |
.toString(); |
return new MessageInputStream(in); |
501 |
send(cmd); |
} |
|
if (getResponse()!=OK) |
|
|
throw new ProtocolException("TOP failed: "+response); |
|
|
return new MessageInputStream(in); |
|
|
} |
|
502 |
|
|
503 |
/** |
/** |
504 |
* Returns a unique identifier for the specified message. |
* Returns a unique identifier for the specified message. |
505 |
* @param msgnum the message number |
* @param msgnum the message number |
506 |
*/ |
*/ |
507 |
public String uidl(int msgnum) |
public String uidl(int msgnum) throws IOException |
508 |
throws IOException |
{ |
509 |
{ |
String cmd = new StringBuffer(UIDL).append(' ').append(msgnum).toString(); |
510 |
String cmd = new StringBuffer(UIDL) |
send(cmd); |
511 |
.append(' ') |
if (getResponse() != OK) |
512 |
.append(msgnum) |
throw new ProtocolException("UIDL failed: " + response); |
513 |
.toString(); |
return response.substring(response.indexOf(' ') + 1); |
514 |
send(cmd); |
} |
|
if (getResponse()!=OK) |
|
|
throw new ProtocolException("UIDL failed: "+response); |
|
|
return response.substring(response.indexOf(' ')+1); |
|
|
} |
|
515 |
|
|
516 |
/** |
/** |
517 |
* Returns a list of capabilities supported by the POP3 server. |
* Returns a list of capabilities supported by the POP3 server. |
518 |
* If the server does not support POP3 extensions, returns |
* If the server does not support POP3 extensions, returns |
519 |
* <code>null</code>. |
* <code>null</code>. |
520 |
*/ |
*/ |
521 |
public List capa() |
public List capa() throws IOException |
|
throws IOException |
|
522 |
{ |
{ |
523 |
send(CAPA); |
send(CAPA); |
524 |
if (getResponse()==OK) |
if (getResponse() == OK) |
525 |
{ |
{ |
526 |
final String DOT = "."; |
final String DOT = "."; |
527 |
List list = new ArrayList(); |
List list = new ArrayList(); |
528 |
for (String line = in.readLine(); |
for (String line = in.readLine(); |
529 |
!DOT.equals(line); |
!DOT.equals(line); line = in.readLine()) |
|
line = in.readLine()) |
|
530 |
list.add(line); |
list.add(line); |
531 |
return list; |
return list; |
532 |
} |
} |
533 |
return null; |
return null; |
534 |
} |
} |
538 |
* If <code>debug</code> is <code>true</code>, |
* If <code>debug</code> is <code>true</code>, |
539 |
* the command is logged. |
* the command is logged. |
540 |
*/ |
*/ |
541 |
protected void send(String command) |
protected void send(String command) throws IOException |
|
throws IOException |
|
542 |
{ |
{ |
543 |
if (debug) |
if (debug) |
544 |
{ |
{ |
545 |
Logger logger = Logger.getInstance(); |
Logger logger = Logger.getInstance(); |
546 |
logger.log("pop3", "> "+command); |
logger.log("pop3", "> " + command); |
547 |
} |
} |
548 |
out.write(command); |
out.write(command); |
549 |
out.writeln(); |
out.writeln(); |
550 |
out.flush(); |
out.flush(); |
555 |
* If <code>debug</code> is <code>true</code>, |
* If <code>debug</code> is <code>true</code>, |
556 |
* the response is logged. |
* the response is logged. |
557 |
*/ |
*/ |
558 |
protected int getResponse() |
protected int getResponse() throws IOException |
|
throws IOException |
|
559 |
{ |
{ |
560 |
response = in.readLine(); |
response = in.readLine(); |
561 |
if (debug) |
if (debug) |
562 |
{ |
{ |
563 |
Logger logger = Logger.getInstance(); |
Logger logger = Logger.getInstance(); |
564 |
logger.log("pop3", "< "+response); |
logger.log("pop3", "< " + response); |
565 |
} |
} |
566 |
if (response.indexOf(_OK)==0) |
if (response.indexOf(_OK) == 0) |
567 |
{ |
{ |
568 |
response = response.substring(3).trim(); |
response = response.substring(3).trim(); |
569 |
return OK; |
return OK; |
570 |
} |
} |
571 |
else if (response.indexOf(_ERR)==0) |
else if (response.indexOf(_ERR) == 0) |
572 |
{ |
{ |
573 |
response = response.substring(4).trim(); |
response = response.substring(4).trim(); |
574 |
return ERR; |
return ERR; |
575 |
} |
} |
576 |
else if (response.indexOf(_READY)==0) |
else if (response.indexOf(_READY) == 0) |
577 |
{ |
{ |
578 |
response = response.substring(2).trim(); |
response = response.substring(2).trim(); |
579 |
return READY; |
return READY; |
580 |
} |
} |
581 |
else |
else |
582 |
{ |
{ |
583 |
throw new ProtocolException("Unexpected response: "+response); |
throw new ProtocolException("Unexpected response: " + response); |
584 |
} |
} |
585 |
} |
} |
586 |
|
|
587 |
/* |
/* |
588 |
* Parse the APOP timestamp from the server's banner greeting. |
* Parse the APOP timestamp from the server's banner greeting. |
589 |
*/ |
*/ |
590 |
byte[] parseTimestamp(String greeting) |
byte[]parseTimestamp(String greeting) throws IOException |
591 |
throws IOException |
{ |
592 |
{ |
int bra = greeting.indexOf('<'); |
593 |
int bra = greeting.indexOf('<'); |
if (bra != -1) |
594 |
if (bra!=-1) |
{ |
595 |
{ |
int ket = greeting.indexOf('>', bra); |
596 |
int ket = greeting.indexOf('>', bra); |
if (ket != -1) |
597 |
if (ket!=-1) |
{ |
598 |
{ |
String mid = greeting.substring(bra, ket + 1); |
599 |
String mid = greeting.substring(bra, ket+1); |
int at = mid.indexOf('@'); |
600 |
int at = mid.indexOf('@'); |
if (at != -1) // This is a valid RFC822 msg-id |
601 |
if (at!=-1) // This is a valid RFC822 msg-id |
return mid.getBytes("US-ASCII"); |
602 |
return mid.getBytes("US-ASCII"); |
} |
603 |
} |
} |
604 |
} |
return null; |
605 |
return null; |
} |
|
} |
|
606 |
|
|
607 |
} |
} |