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