001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.net.pop3;
019
020 import java.io.IOException;
021 import java.io.Reader;
022 import java.security.MessageDigest;
023 import java.security.NoSuchAlgorithmException;
024 import java.util.ListIterator;
025 import java.util.StringTokenizer;
026
027 import org.apache.commons.net.io.DotTerminatedMessageReader;
028
029 /***
030 * The POP3Client class implements the client side of the Internet POP3
031 * Protocol defined in RFC 1939. All commands are supported, including
032 * the APOP command which requires MD5 encryption. See RFC 1939 for
033 * more details on the POP3 protocol.
034 * <p>
035 * Rather than list it separately for each method, we mention here that
036 * every method communicating with the server and throwing an IOException
037 * can also throw a
038 * {@link org.apache.commons.net.MalformedServerReplyException}
039 * , which is a subclass
040 * of IOException. A MalformedServerReplyException will be thrown when
041 * the reply received from the server deviates enough from the protocol
042 * specification that it cannot be interpreted in a useful manner despite
043 * attempts to be as lenient as possible.
044 * <p>
045 * <p>
046 * @see POP3MessageInfo
047 * @see org.apache.commons.net.io.DotTerminatedMessageReader
048 * @see org.apache.commons.net.MalformedServerReplyException
049 ***/
050
051 public class POP3Client extends POP3
052 {
053
054 private static POP3MessageInfo __parseStatus(String line)
055 {
056 int num, size;
057 StringTokenizer tokenizer;
058
059 tokenizer = new StringTokenizer(line);
060
061 if (!tokenizer.hasMoreElements()) {
062 return null;
063 }
064
065 num = size = 0;
066
067 try
068 {
069 num = Integer.parseInt(tokenizer.nextToken());
070
071 if (!tokenizer.hasMoreElements()) {
072 return null;
073 }
074
075 size = Integer.parseInt(tokenizer.nextToken());
076 }
077 catch (NumberFormatException e)
078 {
079 return null;
080 }
081
082 return new POP3MessageInfo(num, size);
083 }
084
085 private static POP3MessageInfo __parseUID(String line)
086 {
087 int num;
088 StringTokenizer tokenizer;
089
090 tokenizer = new StringTokenizer(line);
091
092 if (!tokenizer.hasMoreElements()) {
093 return null;
094 }
095
096 num = 0;
097
098 try
099 {
100 num = Integer.parseInt(tokenizer.nextToken());
101
102 if (!tokenizer.hasMoreElements()) {
103 return null;
104 }
105
106 line = tokenizer.nextToken();
107 }
108 catch (NumberFormatException e)
109 {
110 return null;
111 }
112
113 return new POP3MessageInfo(num, line);
114 }
115
116 /***
117 * Send a CAPA command to the POP3 server.
118 * @return True if the command was successful, false if not.
119 * @exception IOException If a network I/O error occurs in the process of
120 * sending the CAPA command.
121 * @since 3.1 (was previously in ExtendedPOP3Client)
122 ***/
123 public boolean capa() throws IOException
124 {
125 if (sendCommand(POP3Command.CAPA) == POP3Reply.OK) {
126 getAdditionalReply();
127 return true;
128 }
129 return false;
130
131 }
132
133 /***
134 * Login to the POP3 server with the given username and password. You
135 * must first connect to the server with
136 * {@link org.apache.commons.net.SocketClient#connect connect }
137 * before attempting to login. A login attempt is only valid if
138 * the client is in the
139 * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }
140 * . After logging in, the client enters the
141 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
142 * .
143 * <p>
144 * @param username The account name being logged in to.
145 * @param password The plain text password of the account.
146 * @return True if the login attempt was successful, false if not.
147 * @exception IOException If a network I/O error occurs in the process of
148 * logging in.
149 ***/
150 public boolean login(String username, String password) throws IOException
151 {
152 if (getState() != AUTHORIZATION_STATE) {
153 return false;
154 }
155
156 if (sendCommand(POP3Command.USER, username) != POP3Reply.OK) {
157 return false;
158 }
159
160 if (sendCommand(POP3Command.PASS, password) != POP3Reply.OK) {
161 return false;
162 }
163
164 setState(TRANSACTION_STATE);
165
166 return true;
167 }
168
169
170 /***
171 * Login to the POP3 server with the given username and authentication
172 * information. Use this method when connecting to a server requiring
173 * authentication using the APOP command. Because the timestamp
174 * produced in the greeting banner varies from server to server, it is
175 * not possible to consistently extract the information. Therefore,
176 * after connecting to the server, you must call
177 * {@link org.apache.commons.net.pop3.POP3#getReplyString getReplyString }
178 * and parse out the timestamp information yourself.
179 * <p>
180 * You must first connect to the server with
181 * {@link org.apache.commons.net.SocketClient#connect connect }
182 * before attempting to login. A login attempt is only valid if
183 * the client is in the
184 * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }
185 * . After logging in, the client enters the
186 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
187 * . After connecting, you must parse out the
188 * server specific information to use as a timestamp, and pass that
189 * information to this method. The secret is a shared secret known
190 * to you and the server. See RFC 1939 for more details regarding
191 * the APOP command.
192 * <p>
193 * @param username The account name being logged in to.
194 * @param timestamp The timestamp string to combine with the secret.
195 * @param secret The shared secret which produces the MD5 digest when
196 * combined with the timestamp.
197 * @return True if the login attempt was successful, false if not.
198 * @exception IOException If a network I/O error occurs in the process of
199 * logging in.
200 * @exception NoSuchAlgorithmException If the MD5 encryption algorithm
201 * cannot be instantiated by the Java runtime system.
202 ***/
203 public boolean login(String username, String timestamp, String secret)
204 throws IOException, NoSuchAlgorithmException
205 {
206 int i;
207 byte[] digest;
208 StringBuilder buffer, digestBuffer;
209 MessageDigest md5;
210
211 if (getState() != AUTHORIZATION_STATE) {
212 return false;
213 }
214
215 md5 = MessageDigest.getInstance("MD5");
216 timestamp += secret;
217 digest = md5.digest(timestamp.getBytes());
218 digestBuffer = new StringBuilder(128);
219
220 for (i = 0; i < digest.length; i++) {
221 int digit = digest[i] & 0xff;
222 if (digit <= 15) { // Add leading zero if necessary (NET-351)
223 digestBuffer.append("0");
224 }
225 digestBuffer.append(Integer.toHexString(digit));
226 }
227
228 buffer = new StringBuilder(256);
229 buffer.append(username);
230 buffer.append(' ');
231 buffer.append(digestBuffer.toString());
232
233 if (sendCommand(POP3Command.APOP, buffer.toString()) != POP3Reply.OK) {
234 return false;
235 }
236
237 setState(TRANSACTION_STATE);
238
239 return true;
240 }
241
242
243 /***
244 * Logout of the POP3 server. To fully disconnect from the server
245 * you must call
246 * {@link org.apache.commons.net.pop3.POP3#disconnect disconnect }.
247 * A logout attempt is valid in any state. If
248 * the client is in the
249 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
250 * , it enters the
251 * {@link org.apache.commons.net.pop3.POP3#UPDATE_STATE UPDATE_STATE }
252 * on a successful logout.
253 * <p>
254 * @return True if the logout attempt was successful, false if not.
255 * @exception IOException If a network I/O error occurs in the process
256 * of logging out.
257 ***/
258 public boolean logout() throws IOException
259 {
260 if (getState() == TRANSACTION_STATE) {
261 setState(UPDATE_STATE);
262 }
263 sendCommand(POP3Command.QUIT);
264 return (_replyCode == POP3Reply.OK);
265 }
266
267
268 /***
269 * Send a NOOP command to the POP3 server. This is useful for keeping
270 * a connection alive since most POP3 servers will timeout after 10
271 * minutes of inactivity. A noop attempt will only succeed if
272 * the client is in the
273 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
274 * .
275 * <p>
276 * @return True if the noop attempt was successful, false if not.
277 * @exception IOException If a network I/O error occurs in the process of
278 * sending the NOOP command.
279 ***/
280 public boolean noop() throws IOException
281 {
282 if (getState() == TRANSACTION_STATE) {
283 return (sendCommand(POP3Command.NOOP) == POP3Reply.OK);
284 }
285 return false;
286 }
287
288
289 /***
290 * Delete a message from the POP3 server. The message is only marked
291 * for deletion by the server. If you decide to unmark the message, you
292 * must issuse a {@link #reset reset } command. Messages marked
293 * for deletion are only deleted by the server on
294 * {@link #logout logout }.
295 * A delete attempt can only succeed if the client is in the
296 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
297 * .
298 * <p>
299 * @param messageId The message number to delete.
300 * @return True if the deletion attempt was successful, false if not.
301 * @exception IOException If a network I/O error occurs in the process of
302 * sending the delete command.
303 ***/
304 public boolean deleteMessage(int messageId) throws IOException
305 {
306 if (getState() == TRANSACTION_STATE) {
307 return (sendCommand(POP3Command.DELE, Integer.toString(messageId))
308 == POP3Reply.OK);
309 }
310 return false;
311 }
312
313
314 /***
315 * Reset the POP3 session. This is useful for undoing any message
316 * deletions that may have been performed. A reset attempt can only
317 * succeed if the client is in the
318 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
319 * .
320 * <p>
321 * @return True if the reset attempt was successful, false if not.
322 * @exception IOException If a network I/O error occurs in the process of
323 * sending the reset command.
324 ***/
325 public boolean reset() throws IOException
326 {
327 if (getState() == TRANSACTION_STATE) {
328 return (sendCommand(POP3Command.RSET) == POP3Reply.OK);
329 }
330 return false;
331 }
332
333 /***
334 * Get the mailbox status. A status attempt can only
335 * succeed if the client is in the
336 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
337 * . Returns a POP3MessageInfo instance
338 * containing the number of messages in the mailbox and the total
339 * size of the messages in bytes. Returns null if the status the
340 * attempt fails.
341 * <p>
342 * @return A POP3MessageInfo instance containing the number of
343 * messages in the mailbox and the total size of the messages
344 * in bytes. Returns null if the status the attempt fails.
345 * @exception IOException If a network I/O error occurs in the process of
346 * sending the status command.
347 ***/
348 public POP3MessageInfo status() throws IOException
349 {
350 if (getState() != TRANSACTION_STATE) {
351 return null;
352 }
353 if (sendCommand(POP3Command.STAT) != POP3Reply.OK) {
354 return null;
355 }
356 return __parseStatus(_lastReplyLine.substring(3));
357 }
358
359
360 /***
361 * List an individual message. A list attempt can only
362 * succeed if the client is in the
363 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
364 * . Returns a POP3MessageInfo instance
365 * containing the number of the listed message and the
366 * size of the message in bytes. Returns null if the list
367 * attempt fails (e.g., if the specified message number does
368 * not exist).
369 * <p>
370 * @param messageId The number of the message list.
371 * @return A POP3MessageInfo instance containing the number of the
372 * listed message and the size of the message in bytes. Returns
373 * null if the list attempt fails.
374 * @exception IOException If a network I/O error occurs in the process of
375 * sending the list command.
376 ***/
377 public POP3MessageInfo listMessage(int messageId) throws IOException
378 {
379 if (getState() != TRANSACTION_STATE) {
380 return null;
381 }
382 if (sendCommand(POP3Command.LIST, Integer.toString(messageId))
383 != POP3Reply.OK) {
384 return null;
385 }
386 return __parseStatus(_lastReplyLine.substring(3));
387 }
388
389
390 /***
391 * List all messages. A list attempt can only
392 * succeed if the client is in the
393 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
394 * . Returns an array of POP3MessageInfo instances,
395 * each containing the number of a message and its size in bytes.
396 * If there are no messages, this method returns a zero length array.
397 * If the list attempt fails, it returns null.
398 * <p>
399 * @return An array of POP3MessageInfo instances representing all messages
400 * in the order they appear in the mailbox,
401 * each containing the number of a message and its size in bytes.
402 * If there are no messages, this method returns a zero length array.
403 * If the list attempt fails, it returns null.
404 * @exception IOException If a network I/O error occurs in the process of
405 * sending the list command.
406 ***/
407 public POP3MessageInfo[] listMessages() throws IOException
408 {
409 if (getState() != TRANSACTION_STATE) {
410 return null;
411 }
412 if (sendCommand(POP3Command.LIST) != POP3Reply.OK) {
413 return null;
414 }
415 getAdditionalReply();
416
417 // This could be a zero length array if no messages present
418 POP3MessageInfo[] messages = new POP3MessageInfo[_replyLines.size() - 2]; // skip first and last lines
419
420 ListIterator<String> en = _replyLines.listIterator(1); // Skip first line
421
422 // Fetch lines.
423 for (int line = 0; line < messages.length; line++) {
424 messages[line] = __parseStatus(en.next());
425 }
426
427 return messages;
428 }
429
430 /***
431 * List the unique identifier for a message. A list attempt can only
432 * succeed if the client is in the
433 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
434 * . Returns a POP3MessageInfo instance
435 * containing the number of the listed message and the
436 * unique identifier for that message. Returns null if the list
437 * attempt fails (e.g., if the specified message number does
438 * not exist).
439 * <p>
440 * @param messageId The number of the message list.
441 * @return A POP3MessageInfo instance containing the number of the
442 * listed message and the unique identifier for that message.
443 * Returns null if the list attempt fails.
444 * @exception IOException If a network I/O error occurs in the process of
445 * sending the list unique identifier command.
446 ***/
447 public POP3MessageInfo listUniqueIdentifier(int messageId)
448 throws IOException
449 {
450 if (getState() != TRANSACTION_STATE) {
451 return null;
452 }
453 if (sendCommand(POP3Command.UIDL, Integer.toString(messageId))
454 != POP3Reply.OK) {
455 return null;
456 }
457 return __parseUID(_lastReplyLine.substring(3));
458 }
459
460
461 /***
462 * List the unique identifiers for all messages. A list attempt can only
463 * succeed if the client is in the
464 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
465 * . Returns an array of POP3MessageInfo instances,
466 * each containing the number of a message and its unique identifier.
467 * If there are no messages, this method returns a zero length array.
468 * If the list attempt fails, it returns null.
469 * <p>
470 * @return An array of POP3MessageInfo instances representing all messages
471 * in the order they appear in the mailbox,
472 * each containing the number of a message and its unique identifier
473 * If there are no messages, this method returns a zero length array.
474 * If the list attempt fails, it returns null.
475 * @exception IOException If a network I/O error occurs in the process of
476 * sending the list unique identifier command.
477 ***/
478 public POP3MessageInfo[] listUniqueIdentifiers() throws IOException
479 {
480 if (getState() != TRANSACTION_STATE) {
481 return null;
482 }
483 if (sendCommand(POP3Command.UIDL) != POP3Reply.OK) {
484 return null;
485 }
486 getAdditionalReply();
487
488 // This could be a zero length array if no messages present
489 POP3MessageInfo[] messages = new POP3MessageInfo[_replyLines.size() - 2]; // skip first and last lines
490
491 ListIterator<String> en = _replyLines.listIterator(1); // skip first line
492
493 // Fetch lines.
494 for (int line = 0; line < messages.length; line++) {
495 messages[line] = __parseUID(en.next());
496 }
497
498 return messages;
499 }
500
501
502 /**
503 * Retrieve a message from the POP3 server. A retrieve message attempt
504 * can only succeed if the client is in the
505 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
506 * <p>
507 * You must not issue any commands to the POP3 server (i.e., call any
508 * other methods) until you finish reading the message from the
509 * returned BufferedReader instance.
510 * The POP3 protocol uses the same stream for issuing commands as it does
511 * for returning results. Therefore the returned BufferedReader actually reads
512 * directly from the POP3 connection. After the end of message has been
513 * reached, new commands can be executed and their replies read. If
514 * you do not follow these requirements, your program will not work
515 * properly.
516 * <p>
517 * @param messageId The number of the message to fetch.
518 * @return A DotTerminatedMessageReader instance
519 * from which the entire message can be read.
520 * This can safely be cast to a {@link java.io.BufferedReader BufferedReader} in order to
521 * use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method.
522 * Returns null if the retrieval attempt fails (e.g., if the specified
523 * message number does not exist).
524 * @exception IOException If a network I/O error occurs in the process of
525 * sending the retrieve message command.
526 */
527 public Reader retrieveMessage(int messageId) throws IOException
528 {
529 if (getState() != TRANSACTION_STATE) {
530 return null;
531 }
532 if (sendCommand(POP3Command.RETR, Integer.toString(messageId)) != POP3Reply.OK) {
533 return null;
534 }
535
536 return new DotTerminatedMessageReader(_reader);
537 }
538
539
540 /**
541 * Retrieve only the specified top number of lines of a message from the
542 * POP3 server. A retrieve top lines attempt
543 * can only succeed if the client is in the
544 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
545 * <p>
546 * You must not issue any commands to the POP3 server (i.e., call any
547 * other methods) until you finish reading the message from the returned
548 * BufferedReader instance.
549 * The POP3 protocol uses the same stream for issuing commands as it does
550 * for returning results. Therefore the returned BufferedReader actually reads
551 * directly from the POP3 connection. After the end of message has been
552 * reached, new commands can be executed and their replies read. If
553 * you do not follow these requirements, your program will not work
554 * properly.
555 * <p>
556 * @param messageId The number of the message to fetch.
557 * @param numLines The top number of lines to fetch. This must be >= 0.
558 * @return A DotTerminatedMessageReader instance
559 * from which the specified top number of lines of the message can be
560 * read.
561 * This can safely be cast to a {@link java.io.BufferedReader BufferedReader} in order to
562 * use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method.
563 * Returns null if the retrieval attempt fails (e.g., if the specified
564 * message number does not exist).
565 * @exception IOException If a network I/O error occurs in the process of
566 * sending the top command.
567 */
568 public Reader retrieveMessageTop(int messageId, int numLines)
569 throws IOException
570 {
571 if (numLines < 0 || getState() != TRANSACTION_STATE) {
572 return null;
573 }
574 if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " +
575 Integer.toString(numLines)) != POP3Reply.OK) {
576 return null;
577 }
578
579 return new DotTerminatedMessageReader(_reader);
580 }
581
582
583 }
584