View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.pop3;
19  
20  import java.io.IOException;
21  import java.io.Reader;
22  import java.security.MessageDigest;
23  import java.security.NoSuchAlgorithmException;
24  import java.util.ListIterator;
25  import java.util.StringTokenizer;
26  
27  import org.apache.commons.net.io.DotTerminatedMessageReader;
28  
29  /***
30   * The POP3Client class implements the client side of the Internet POP3
31   * Protocol defined in RFC 1939.  All commands are supported, including
32   * the APOP command which requires MD5 encryption.  See RFC 1939 for
33   * more details on the POP3 protocol.
34   * <p>
35   * Rather than list it separately for each method, we mention here that
36   * every method communicating with the server and throwing an IOException
37   * can also throw a
38   * {@link org.apache.commons.net.MalformedServerReplyException}
39   * , which is a subclass
40   * of IOException.  A MalformedServerReplyException will be thrown when
41   * the reply received from the server deviates enough from the protocol
42   * specification that it cannot be interpreted in a useful manner despite
43   * attempts to be as lenient as possible.
44   * <p>
45   * <p>
46   * @see POP3MessageInfo
47   * @see org.apache.commons.net.io.DotTerminatedMessageReader
48   * @see org.apache.commons.net.MalformedServerReplyException
49   ***/
50  
51  public class POP3Client extends POP3
52  {
53  
54      private static POP3MessageInfo __parseStatus(String line)
55      {
56          int num, size;
57          StringTokenizer tokenizer;
58  
59          tokenizer = new StringTokenizer(line);
60  
61          if (!tokenizer.hasMoreElements()) {
62              return null;
63          }
64  
65          num = size = 0;
66  
67          try
68          {
69              num = Integer.parseInt(tokenizer.nextToken());
70  
71              if (!tokenizer.hasMoreElements()) {
72                  return null;
73              }
74  
75              size = Integer.parseInt(tokenizer.nextToken());
76          }
77          catch (NumberFormatException e)
78          {
79              return null;
80          }
81  
82          return new POP3MessageInfo(num, size);
83      }
84  
85      private static POP3MessageInfo __parseUID(String line)
86      {
87          int num;
88          StringTokenizer tokenizer;
89  
90          tokenizer = new StringTokenizer(line);
91  
92          if (!tokenizer.hasMoreElements()) {
93              return null;
94          }
95  
96          num = 0;
97  
98          try
99          {
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(getCharsetName())); // Java 1.6 can use getCharset()
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