001 /*
002 * Copyright 2001-2005 The Apache Software Foundation
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.apache.commons.net.pop3;
017
018 import java.io.IOException;
019 import java.io.Reader;
020 import java.security.MessageDigest;
021 import java.security.NoSuchAlgorithmException;
022 import java.util.Enumeration;
023 import java.util.StringTokenizer;
024 import org.apache.commons.net.io.DotTerminatedMessageReader;
025
026 /***
027 * The POP3Client class implements the client side of the Internet POP3
028 * Protocol defined in RFC 1939. All commands are supported, including
029 * the APOP command which requires MD5 encryption. See RFC 1939 for
030 * more details on the POP3 protocol.
031 * <p>
032 * Rather than list it separately for each method, we mention here that
033 * every method communicating with the server and throwing an IOException
034 * can also throw a
035 * {@link org.apache.commons.net.MalformedServerReplyException}
036 * , which is a subclass
037 * of IOException. A MalformedServerReplyException will be thrown when
038 * the reply received from the server deviates enough from the protocol
039 * specification that it cannot be interpreted in a useful manner despite
040 * attempts to be as lenient as possible.
041 * <p>
042 * <p>
043 * @author Daniel F. Savarese
044 * @see POP3MessageInfo
045 * @see org.apache.commons.net.io.DotTerminatedMessageReader
046 * @see org.apache.commons.net.MalformedServerReplyException
047 ***/
048
049 public class POP3Client extends POP3
050 {
051
052 private static POP3MessageInfo __parseStatus(String line)
053 {
054 int num, size;
055 StringTokenizer tokenizer;
056
057 tokenizer = new StringTokenizer(line);
058
059 if (!tokenizer.hasMoreElements())
060 return null;
061
062 num = size = 0;
063
064 try
065 {
066 num = Integer.parseInt(tokenizer.nextToken());
067
068 if (!tokenizer.hasMoreElements())
069 return null;
070
071 size = Integer.parseInt(tokenizer.nextToken());
072 }
073 catch (NumberFormatException e)
074 {
075 return null;
076 }
077
078 return new POP3MessageInfo(num, size);
079 }
080
081 private static POP3MessageInfo __parseUID(String line)
082 {
083 int num;
084 StringTokenizer tokenizer;
085
086 tokenizer = new StringTokenizer(line);
087
088 if (!tokenizer.hasMoreElements())
089 return null;
090
091 num = 0;
092
093 try
094 {
095 num = Integer.parseInt(tokenizer.nextToken());
096
097 if (!tokenizer.hasMoreElements())
098 return null;
099
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 // This could be a zero length array if no messages present
379 messages = new POP3MessageInfo[_replyLines.size() - 2];
380 en = _replyLines.elements();
381
382 // Skip first line
383 en.nextElement();
384
385 // Fetch lines.
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 // This could be a zero length array if no messages present
451 messages = new POP3MessageInfo[_replyLines.size() - 2];
452 en = _replyLines.elements();
453
454 // Skip first line
455 en.nextElement();
456
457 // Fetch lines.
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