View Javadoc

    1   /*
    2    * Copyright 2001-2005 The Apache Software Foundation
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    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         // 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