1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.net.pop3;
17
18 import java.io.IOException;
19 import java.io.Reader;
20 import java.security.MessageDigest;
21 import java.security.NoSuchAlgorithmException;
22 import java.util.Enumeration;
23 import java.util.StringTokenizer;
24 import org.apache.commons.net.io.DotTerminatedMessageReader;
25
26 /***
27 * The POP3Client class implements the client side of the Internet POP3
28 * Protocol defined in RFC 1939. All commands are supported, including
29 * the APOP command which requires MD5 encryption. See RFC 1939 for
30 * more details on the POP3 protocol.
31 * <p>
32 * Rather than list it separately for each method, we mention here that
33 * every method communicating with the server and throwing an IOException
34 * can also throw a
35 * {@link org.apache.commons.net.MalformedServerReplyException}
36 * , which is a subclass
37 * of IOException. A MalformedServerReplyException will be thrown when
38 * the reply received from the server deviates enough from the protocol
39 * specification that it cannot be interpreted in a useful manner despite
40 * attempts to be as lenient as possible.
41 * <p>
42 * <p>
43 * @author Daniel F. Savarese
44 * @see POP3MessageInfo
45 * @see org.apache.commons.net.io.DotTerminatedMessageReader
46 * @see org.apache.commons.net.MalformedServerReplyException
47 ***/
48
49 public class POP3Client extends POP3
50 {
51
52 private static POP3MessageInfo __parseStatus(String line)
53 {
54 int num, size;
55 StringTokenizer tokenizer;
56
57 tokenizer = new StringTokenizer(line);
58
59 if (!tokenizer.hasMoreElements())
60 return null;
61
62 num = size = 0;
63
64 try
65 {
66 num = Integer.parseInt(tokenizer.nextToken());
67
68 if (!tokenizer.hasMoreElements())
69 return null;
70
71 size = Integer.parseInt(tokenizer.nextToken());
72 }
73 catch (NumberFormatException e)
74 {
75 return null;
76 }
77
78 return new POP3MessageInfo(num, size);
79 }
80
81 private static POP3MessageInfo __parseUID(String line)
82 {
83 int num;
84 StringTokenizer tokenizer;
85
86 tokenizer = new StringTokenizer(line);
87
88 if (!tokenizer.hasMoreElements())
89 return null;
90
91 num = 0;
92
93 try
94 {
95 num = Integer.parseInt(tokenizer.nextToken());
96
97 if (!tokenizer.hasMoreElements())
98 return null;
99
100 line = tokenizer.nextToken();
101 }
102 catch (NumberFormatException e)
103 {
104 return null;
105 }
106
107 return new POP3MessageInfo(num, line);
108 }
109
110 /***
111 * Login to the POP3 server with the given username and password. You
112 * must first connect to the server with
113 * {@link org.apache.commons.net.SocketClient#connect connect }
114 * before attempting to login. A login attempt is only valid if
115 * the client is in the
116 * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }
117 * . After logging in, the client enters the
118 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
119 * .
120 * <p>
121 * @param username The account name being logged in to.
122 * @param password The plain text password of the account.
123 * @return True if the login attempt was successful, false if not.
124 * @exception IOException If a network I/O error occurs in the process of
125 * logging in.
126 ***/
127 public boolean login(String username, String password) throws IOException
128 {
129 if (getState() != AUTHORIZATION_STATE)
130 return false;
131
132 if (sendCommand(POP3Command.USER, username) != POP3Reply.OK)
133 return false;
134
135 if (sendCommand(POP3Command.PASS, password) != POP3Reply.OK)
136 return false;
137
138 setState(TRANSACTION_STATE);
139
140 return true;
141 }
142
143
144 /***
145 * Login to the POP3 server with the given username and authentication
146 * information. Use this method when connecting to a server requiring
147 * authentication using the APOP command. Because the timestamp
148 * produced in the greeting banner varies from server to server, it is
149 * not possible to consistently extract the information. Therefore,
150 * after connecting to the server, you must call
151 * {@link org.apache.commons.net.pop3.POP3#getReplyString getReplyString }
152 * and parse out the timestamp information yourself.
153 * <p>
154 * You must first connect to the server with
155 * {@link org.apache.commons.net.SocketClient#connect connect }
156 * before attempting to login. A login attempt is only valid if
157 * the client is in the
158 * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }
159 * . After logging in, the client enters the
160 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
161 * . After connecting, you must parse out the
162 * server specific information to use as a timestamp, and pass that
163 * information to this method. The secret is a shared secret known
164 * to you and the server. See RFC 1939 for more details regarding
165 * the APOP command.
166 * <p>
167 * @param username The account name being logged in to.
168 * @param timestamp The timestamp string to combine with the secret.
169 * @param secret The shared secret which produces the MD5 digest when
170 * combined with the timestamp.
171 * @return True if the login attempt was successful, false if not.
172 * @exception IOException If a network I/O error occurs in the process of
173 * logging in.
174 * @exception NoSuchAlgorithmException If the MD5 encryption algorithm
175 * cannot be instantiated by the Java runtime system.
176 ***/
177 public boolean login(String username, String timestamp, String secret)
178 throws IOException, NoSuchAlgorithmException
179 {
180 int i;
181 byte[] digest;
182 StringBuffer buffer, digestBuffer;
183 MessageDigest md5;
184
185 if (getState() != AUTHORIZATION_STATE)
186 return false;
187
188 md5 = MessageDigest.getInstance("MD5");
189 timestamp += secret;
190 digest = md5.digest(timestamp.getBytes());
191 digestBuffer = new StringBuffer(128);
192
193 for (i = 0; i < digest.length; i++)
194 digestBuffer.append(Integer.toHexString(digest[i] & 0xff));
195
196 buffer = new StringBuffer(256);
197 buffer.append(username);
198 buffer.append(' ');
199 buffer.append(digestBuffer.toString());
200
201 if (sendCommand(POP3Command.APOP, buffer.toString()) != POP3Reply.OK)
202 return false;
203
204 setState(TRANSACTION_STATE);
205
206 return true;
207 }
208
209
210 /***
211 * Logout of the POP3 server. To fully disconnect from the server
212 * you must call
213 * {@link org.apache.commons.net.pop3.POP3#disconnect disconnect }.
214 * A logout attempt is valid in any state. If
215 * the client is in the
216 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
217 * , it enters the
218 * {@link org.apache.commons.net.pop3.POP3#UPDATE_STATE UPDATE_STATE }
219 * on a successful logout.
220 * <p>
221 * @return True if the logout attempt was successful, false if not.
222 * @exception IOException If a network I/O error occurs in the process
223 * of logging out.
224 ***/
225 public boolean logout() throws IOException
226 {
227 if (getState() == TRANSACTION_STATE)
228 setState(UPDATE_STATE);
229 sendCommand(POP3Command.QUIT);
230 return (_replyCode == POP3Reply.OK);
231 }
232
233
234 /***
235 * Send a NOOP command to the POP3 server. This is useful for keeping
236 * a connection alive since most POP3 servers will timeout after 10
237 * minutes of inactivity. A noop attempt will only succeed if
238 * the client is in the
239 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
240 * .
241 * <p>
242 * @return True if the noop attempt was successful, false if not.
243 * @exception IOException If a network I/O error occurs in the process of
244 * sending the NOOP command.
245 ***/
246 public boolean noop() throws IOException
247 {
248 if (getState() == TRANSACTION_STATE)
249 return (sendCommand(POP3Command.NOOP) == POP3Reply.OK);
250 return false;
251 }
252
253
254 /***
255 * Delete a message from the POP3 server. The message is only marked
256 * for deletion by the server. If you decide to unmark the message, you
257 * must issuse a {@link #reset reset } command. Messages marked
258 * for deletion are only deleted by the server on
259 * {@link #logout logout }.
260 * A delete attempt can only succeed if the client is in the
261 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
262 * .
263 * <p>
264 * @param messageId The message number to delete.
265 * @return True if the deletion attempt was successful, false if not.
266 * @exception IOException If a network I/O error occurs in the process of
267 * sending the delete command.
268 ***/
269 public boolean deleteMessage(int messageId) throws IOException
270 {
271 if (getState() == TRANSACTION_STATE)
272 return (sendCommand(POP3Command.DELE, Integer.toString(messageId))
273 == POP3Reply.OK);
274 return false;
275 }
276
277
278 /***
279 * Reset the POP3 session. This is useful for undoing any message
280 * deletions that may have been performed. A reset attempt can only
281 * succeed if the client is in the
282 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
283 * .
284 * <p>
285 * @return True if the reset attempt was successful, false if not.
286 * @exception IOException If a network I/O error occurs in the process of
287 * sending the reset command.
288 ***/
289 public boolean reset() throws IOException
290 {
291 if (getState() == TRANSACTION_STATE)
292 return (sendCommand(POP3Command.RSET) == POP3Reply.OK);
293 return false;
294 }
295
296 /***
297 * Get the mailbox status. A status attempt can only
298 * succeed if the client is in the
299 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
300 * . Returns a POP3MessageInfo instance
301 * containing the number of messages in the mailbox and the total
302 * size of the messages in bytes. Returns null if the status the
303 * attempt fails.
304 * <p>
305 * @return A POP3MessageInfo instance containing the number of
306 * messages in the mailbox and the total size of the messages
307 * in bytes. Returns null if the status the attempt fails.
308 * @exception IOException If a network I/O error occurs in the process of
309 * sending the status command.
310 ***/
311 public POP3MessageInfo status() throws IOException
312 {
313 if (getState() != TRANSACTION_STATE)
314 return null;
315 if (sendCommand(POP3Command.STAT) != POP3Reply.OK)
316 return null;
317 return __parseStatus(_lastReplyLine.substring(3));
318 }
319
320
321 /***
322 * List an individual message. A list attempt can only
323 * succeed if the client is in the
324 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
325 * . Returns a POP3MessageInfo instance
326 * containing the number of the listed message and the
327 * size of the message in bytes. Returns null if the list
328 * attempt fails (e.g., if the specified message number does
329 * not exist).
330 * <p>
331 * @param messageId The number of the message list.
332 * @return A POP3MessageInfo instance containing the number of the
333 * listed message and the size of the message in bytes. Returns
334 * null if the list attempt fails.
335 * @exception IOException If a network I/O error occurs in the process of
336 * sending the list command.
337 ***/
338 public POP3MessageInfo listMessage(int messageId) throws IOException
339 {
340 if (getState() != TRANSACTION_STATE)
341 return null;
342 if (sendCommand(POP3Command.LIST, Integer.toString(messageId))
343 != POP3Reply.OK)
344 return null;
345 return __parseStatus(_lastReplyLine.substring(3));
346 }
347
348
349 /***
350 * List all messages. A list attempt can only
351 * succeed if the client is in the
352 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
353 * . Returns an array of POP3MessageInfo instances,
354 * each containing the number of a message and its size in bytes.
355 * If there are no messages, this method returns a zero length array.
356 * If the list attempt fails, it returns null.
357 * <p>
358 * @return An array of POP3MessageInfo instances representing all messages
359 * in the order they appear in the mailbox,
360 * each containing the number of a message and its size in bytes.
361 * If there are no messages, this method returns a zero length array.
362 * If the list attempt fails, it returns null.
363 * @exception IOException If a network I/O error occurs in the process of
364 * sending the list command.
365 ***/
366 public POP3MessageInfo[] listMessages() throws IOException
367 {
368 POP3MessageInfo[] messages;
369 Enumeration en;
370 int line;
371
372 if (getState() != TRANSACTION_STATE)
373 return null;
374 if (sendCommand(POP3Command.LIST) != POP3Reply.OK)
375 return null;
376 getAdditionalReply();
377
378
379 messages = new POP3MessageInfo[_replyLines.size() - 2];
380 en = _replyLines.elements();
381
382
383 en.nextElement();
384
385
386 for (line = 0; line < messages.length; line++)
387 messages[line] = __parseStatus((String)en.nextElement());
388
389 return messages;
390 }
391
392 /***
393 * List the unique identifier for a message. A list attempt can only
394 * succeed if the client is in the
395 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
396 * . Returns a POP3MessageInfo instance
397 * containing the number of the listed message and the
398 * unique identifier for that message. Returns null if the list
399 * attempt fails (e.g., if the specified message number does
400 * not exist).
401 * <p>
402 * @param messageId The number of the message list.
403 * @return A POP3MessageInfo instance containing the number of the
404 * listed message and the unique identifier for that message.
405 * Returns null if the list attempt fails.
406 * @exception IOException If a network I/O error occurs in the process of
407 * sending the list unique identifier command.
408 ***/
409 public POP3MessageInfo listUniqueIdentifier(int messageId)
410 throws IOException
411 {
412 if (getState() != TRANSACTION_STATE)
413 return null;
414 if (sendCommand(POP3Command.UIDL, Integer.toString(messageId))
415 != POP3Reply.OK)
416 return null;
417 return __parseUID(_lastReplyLine.substring(3));
418 }
419
420
421 /***
422 * List the unique identifiers for all messages. A list attempt can only
423 * succeed if the client is in the
424 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
425 * . Returns an array of POP3MessageInfo instances,
426 * each containing the number of a message and its unique identifier.
427 * If there are no messages, this method returns a zero length array.
428 * If the list attempt fails, it returns null.
429 * <p>
430 * @return An array of POP3MessageInfo instances representing all messages
431 * in the order they appear in the mailbox,
432 * each containing the number of a message and its unique identifier
433 * If there are no messages, this method returns a zero length array.
434 * If the list attempt fails, it returns null.
435 * @exception IOException If a network I/O error occurs in the process of
436 * sending the list unique identifier command.
437 ***/
438 public POP3MessageInfo[] listUniqueIdentifiers() throws IOException
439 {
440 POP3MessageInfo[] messages;
441 Enumeration en;
442 int line;
443
444 if (getState() != TRANSACTION_STATE)
445 return null;
446 if (sendCommand(POP3Command.UIDL) != POP3Reply.OK)
447 return null;
448 getAdditionalReply();
449
450
451 messages = new POP3MessageInfo[_replyLines.size() - 2];
452 en = _replyLines.elements();
453
454
455 en.nextElement();
456
457
458 for (line = 0; line < messages.length; line++)
459 messages[line] = __parseUID((String)en.nextElement());
460
461 return messages;
462 }
463
464
465 /***
466 * Retrieve a message from the POP3 server. A retrieve message attempt
467 * can only succeed if the client is in the
468 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
469 * . Returns a DotTerminatedMessageReader instance
470 * from which the entire message can be read.
471 * Returns null if the retrieval attempt fails (e.g., if the specified
472 * message number does not exist).
473 * <p>
474 * You must not issue any commands to the POP3 server (i.e., call any
475 * other methods) until you finish reading the message from the
476 * returned Reader instance.
477 * The POP3 protocol uses the same stream for issuing commands as it does
478 * for returning results. Therefore the returned Reader actually reads
479 * directly from the POP3 connection. After the end of message has been
480 * reached, new commands can be executed and their replies read. If
481 * you do not follow these requirements, your program will not work
482 * properly.
483 * <p>
484 * @param messageId The number of the message to fetch.
485 * @return A DotTerminatedMessageReader instance
486 * from which the entire message can be read.
487 * Returns null if the retrieval attempt fails (e.g., if the specified
488 * message number does not exist).
489 * @exception IOException If a network I/O error occurs in the process of
490 * sending the retrieve message command.
491 ***/
492 public Reader retrieveMessage(int messageId) throws IOException
493 {
494 if (getState() != TRANSACTION_STATE)
495 return null;
496 if (sendCommand(POP3Command.RETR, Integer.toString(messageId))
497 != POP3Reply.OK)
498 return null;
499
500 return new DotTerminatedMessageReader(_reader);
501 }
502
503
504 /***
505 * Retrieve only the specified top number of lines of a message from the
506 * POP3 server. A retrieve top lines attempt
507 * can only succeed if the client is in the
508 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
509 * . Returns a DotTerminatedMessageReader instance
510 * from which the specified top number of lines of the message can be
511 * read.
512 * Returns null if the retrieval attempt fails (e.g., if the specified
513 * message number does not exist).
514 * <p>
515 * You must not issue any commands to the POP3 server (i.e., call any
516 * other methods) until you finish reading the message from the returned
517 * Reader instance.
518 * The POP3 protocol uses the same stream for issuing commands as it does
519 * for returning results. Therefore the returned Reader actually reads
520 * directly from the POP3 connection. After the end of message has been
521 * reached, new commands can be executed and their replies read. If
522 * you do not follow these requirements, your program will not work
523 * properly.
524 * <p>
525 * @param messageId The number of the message to fetch.
526 * @param numLines The top number of lines to fetch. This must be >= 0.
527 * @return A DotTerminatedMessageReader instance
528 * from which the specified top number of lines of the message can be
529 * read.
530 * Returns null if the retrieval attempt fails (e.g., if the specified
531 * message number does not exist).
532 * @exception IOException If a network I/O error occurs in the process of
533 * sending the top command.
534 ***/
535 public Reader retrieveMessageTop(int messageId, int numLines)
536 throws IOException
537 {
538 if (numLines < 0 || getState() != TRANSACTION_STATE)
539 return null;
540 if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " +
541 Integer.toString(numLines)) != POP3Reply.OK)
542 return null;
543
544 return new DotTerminatedMessageReader(_reader);
545 }
546
547
548 }
549