    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
    018package org.apache.commons.net.pop3;
    019
    020import java.io.IOException;
    021import java.io.Reader;
    022import java.security.MessageDigest;
    023import java.security.NoSuchAlgorithmException;
    024import java.util.ListIterator;
    025import java.util.StringTokenizer;
    026
    027import 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 *
    045 *
    046 * @see POP3MessageInfo
    047 * @see org.apache.commons.net.io.DotTerminatedMessageReader
    048 * @see org.apache.commons.net.MalformedServerReplyException
    049 */
    050
    051public class POP3Client extends POP3
    052{
    053
    054    private static POP3MessageInfo parseStatus(final String line)
    055    {
    056        int num, size;
    057        final 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 (final 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        final 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 (final 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     * @throws 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     *
    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     * @throws IOException If a network I/O error occurs in the process of
    148     *            logging in.
    149     */
    150    public boolean login(final String username, final 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     *
    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     * @throws IOException If a network I/O error occurs in the process of
    199     *            logging in.
    200     * @throws NoSuchAlgorithmException If the MD5 encryption algorithm
    201     *      cannot be instantiated by the Java runtime system.
    202     */
    203    public boolean login(final String username, String timestamp, final String secret)
    204    throws IOException, NoSuchAlgorithmException
    205    {
    206        int i;
    207        final byte[] digest;
    208        final StringBuilder buffer;
    209        final StringBuilder digestBuffer;
    210        final MessageDigest md5;
    211
    212        if (getState() != AUTHORIZATION_STATE) {
    213            return false;
    214        }
    215
    216        md5 = MessageDigest.getInstance("MD5");
    217        timestamp += secret;
    218        digest = md5.digest(timestamp.getBytes(getCharset()));
    219        digestBuffer = new StringBuilder(128);
    220
    221        for (i = 0; i < digest.length; i++) {
    222            final int digit = digest[i] & 0xff;
    223            if (digit <= 15) { // Add leading zero if necessary (NET-351)
    224                digestBuffer.append("0");
    225            }
    226            digestBuffer.append(Integer.toHexString(digit));
    227        }
    228
    229        buffer = new StringBuilder(256);
    230        buffer.append(username);
    231        buffer.append(' ');
    232        buffer.append(digestBuffer.toString());
    233
    234        if (sendCommand(POP3Command.APOP, buffer.toString()) != POP3Reply.OK) {
    235            return false;
    236        }
    237
    238        setState(TRANSACTION_STATE);
    239
    240        return true;
    241    }
    242
    243
    244    /**
    245     * Logout of the POP3 server.  To fully disconnect from the server
    246     * you must call
    247     * {@link org.apache.commons.net.pop3.POP3#disconnect  disconnect }.
    248     * A logout attempt is valid in any state.  If
    249     * the client is in the
    250     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    251     * , it enters the
    252     * {@link org.apache.commons.net.pop3.POP3#UPDATE_STATE UPDATE_STATE }
    253     *  on a successful logout.
    254     *
    255     * @return True if the logout attempt was successful, false if not.
    256     * @throws IOException If a network I/O error occurs in the process
    257     *           of logging out.
    258     */
    259    public boolean logout() throws IOException
    260    {
    261        if (getState() == TRANSACTION_STATE) {
    262            setState(UPDATE_STATE);
    263        }
    264        sendCommand(POP3Command.QUIT);
    265        return replyCode == POP3Reply.OK;
    266    }
    267
    268
    269    /**
    270     * Send a NOOP command to the POP3 server.  This is useful for keeping
    271     * a connection alive since most POP3 servers will timeout after 10
    272     * minutes of inactivity.  A noop attempt will only succeed if
    273     * the client is in the
    274     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    275     * .
    276     *
    277     * @return True if the noop attempt was successful, false if not.
    278     * @throws IOException If a network I/O error occurs in the process of
    279     *        sending the NOOP command.
    280     */
    281    public boolean noop() throws IOException
    282    {
    283        if (getState() == TRANSACTION_STATE) {
    284            return sendCommand(POP3Command.NOOP) == POP3Reply.OK;
    285        }
    286        return false;
    287    }
    288
    289
    290    /**
    291     * Delete a message from the POP3 server.  The message is only marked
    292     * for deletion by the server.  If you decide to unmark the message, you
    293     * must issuse a {@link #reset  reset } command.  Messages marked
    294     * for deletion are only deleted by the server on
    295     * {@link #logout  logout }.
    296     * A delete attempt can only succeed if the client is in the
    297     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    298     * .
    299     *
    300     * @param messageId  The message number to delete.
    301     * @return True if the deletion attempt was successful, false if not.
    302     * @throws IOException If a network I/O error occurs in the process of
    303     *           sending the delete command.
    304     */
    305    public boolean deleteMessage(final int messageId) throws IOException
    306    {
    307        if (getState() == TRANSACTION_STATE) {
    308            return sendCommand(POP3Command.DELE, Integer.toString(messageId))
    309                    == POP3Reply.OK;
    310        }
    311        return false;
    312    }
    313
    314
    315    /**
    316     * Reset the POP3 session.  This is useful for undoing any message
    317     * deletions that may have been performed.  A reset attempt can only
    318     * succeed if the client is in the
    319     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    320     * .
    321     *
    322     * @return True if the reset attempt was successful, false if not.
    323     * @throws IOException If a network I/O error occurs in the process of
    324     *      sending the reset command.
    325     */
    326    public boolean reset() throws IOException
    327    {
    328        if (getState() == TRANSACTION_STATE) {
    329            return sendCommand(POP3Command.RSET) == POP3Reply.OK;
    330        }
    331        return false;
    332    }
    333
    334    /**
    335     * Get the mailbox status.  A status attempt can only
    336     * succeed if the client is in the
    337     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    338     * .  Returns a POP3MessageInfo instance
    339     * containing the number of messages in the mailbox and the total
    340     * size of the messages in bytes.  Returns null if the status the
    341     * attempt fails.
    342     *
    343     * @return A POP3MessageInfo instance containing the number of
    344     *         messages in the mailbox and the total size of the messages
    345     *         in bytes.  Returns null if the status the attempt fails.
    346     * @throws IOException If a network I/O error occurs in the process of
    347     *       sending the status command.
    348     */
    349    public POP3MessageInfo status() throws IOException
    350    {
    351        if (getState() != TRANSACTION_STATE) {
    352            return null;
    353        }
    354        if (sendCommand(POP3Command.STAT) != POP3Reply.OK) {
    355            return null;
    356        }
    357        return parseStatus(lastReplyLine.substring(3));
    358    }
    359
    360
    361    /**
    362     * List an individual message.  A list attempt can only
    363     * succeed if the client is in the
    364     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    365     * .  Returns a POP3MessageInfo instance
    366     * containing the number of the listed message and the
    367     * size of the message in bytes.  Returns null if the list
    368     * attempt fails (e.g., if the specified message number does
    369     * not exist).
    370     *
    371     * @param messageId  The number of the message list.
    372     * @return A POP3MessageInfo instance containing the number of the
    373     *         listed message and the size of the message in bytes.  Returns
    374     *         null if the list attempt fails.
    375     * @throws IOException If a network I/O error occurs in the process of
    376     *         sending the list command.
    377     */
    378    public POP3MessageInfo listMessage(final int messageId) throws IOException
    379    {
    380        if (getState() != TRANSACTION_STATE) {
    381            return null;
    382        }
    383        if (sendCommand(POP3Command.LIST, Integer.toString(messageId))
    384                != POP3Reply.OK) {
    385            return null;
    386        }
    387        return parseStatus(lastReplyLine.substring(3));
    388    }
    389
    390
    391    /**
    392     * List all messages.  A list attempt can only
    393     * succeed if the client is in the
    394     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    395     * .  Returns an array of POP3MessageInfo instances,
    396     * each containing the number of a message and its size in bytes.
    397     * If there are no messages, this method returns a zero length array.
    398     * If the list attempt fails, it returns null.
    399     *
    400     * @return An array of POP3MessageInfo instances representing all messages
    401     * in the order they appear in the mailbox,
    402     * each containing the number of a message and its size in bytes.
    403     * If there are no messages, this method returns a zero length array.
    404     * If the list attempt fails, it returns null.
    405     * @throws IOException If a network I/O error occurs in the process of
    406     *     sending the list command.
    407     */
    408    public POP3MessageInfo[] listMessages() throws IOException
    409    {
    410        if (getState() != TRANSACTION_STATE) {
    411            return null;
    412        }
    413        if (sendCommand(POP3Command.LIST) != POP3Reply.OK) {
    414            return null;
    415        }
    416        getAdditionalReply();
    417
    418        // This could be a zero length array if no messages present
    419        final POP3MessageInfo[] messages = new POP3MessageInfo[replyLines.size() - 2]; // skip first and last lines
    420
    421        final ListIterator<String> en = replyLines.listIterator(1); // Skip first line
    422
    423        // Fetch lines.
    424        for (int line = 0; line < messages.length; line++) {
    425            messages[line] = parseStatus(en.next());
    426        }
    427
    428        return messages;
    429    }
    430
    431    /**
    432     * List the unique identifier for a message.  A list attempt can only
    433     * succeed if the client is in the
    434     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    435     * .  Returns a POP3MessageInfo instance
    436     * containing the number of the listed message and the
    437     * unique identifier for that message.  Returns null if the list
    438     * attempt fails  (e.g., if the specified message number does
    439     * not exist).
    440     *
    441     * @param messageId  The number of the message list.
    442     * @return A POP3MessageInfo instance containing the number of the
    443     *         listed message and the unique identifier for that message.
    444     *         Returns null if the list attempt fails.
    445     * @throws IOException If a network I/O error occurs in the process of
    446     *        sending the list unique identifier command.
    447     */
    448    public POP3MessageInfo listUniqueIdentifier(final int messageId)
    449    throws IOException
    450    {
    451        if (getState() != TRANSACTION_STATE) {
    452            return null;
    453        }
    454        if (sendCommand(POP3Command.UIDL, Integer.toString(messageId))
    455                != POP3Reply.OK) {
    456            return null;
    457        }
    458        return parseUID(lastReplyLine.substring(3));
    459    }
    460
    461
    462    /**
    463     * List the unique identifiers for all messages.  A list attempt can only
    464     * succeed if the client is in the
    465     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    466     * .  Returns an array of POP3MessageInfo instances,
    467     * each containing the number of a message and its unique identifier.
    468     * If there are no messages, this method returns a zero length array.
    469     * If the list attempt fails, it returns null.
    470     *
    471     * @return An array of POP3MessageInfo instances representing all messages
    472     * in the order they appear in the mailbox,
    473     * each containing the number of a message and its unique identifier
    474     * If there are no messages, this method returns a zero length array.
    475     * If the list attempt fails, it returns null.
    476     * @throws IOException If a network I/O error occurs in the process of
    477     *     sending the list unique identifier command.
    478     */
    479    public POP3MessageInfo[] listUniqueIdentifiers() throws IOException
    480    {
    481        if (getState() != TRANSACTION_STATE) {
    482            return null;
    483        }
    484        if (sendCommand(POP3Command.UIDL) != POP3Reply.OK) {
    485            return null;
    486        }
    487        getAdditionalReply();
    488
    489        // This could be a zero length array if no messages present
    490        final POP3MessageInfo[] messages = new POP3MessageInfo[replyLines.size() - 2]; // skip first and last lines
    491
    492        final ListIterator<String> en = replyLines.listIterator(1); // skip first line
    493
    494        // Fetch lines.
    495        for (int line = 0; line < messages.length; line++) {
    496            messages[line] = parseUID(en.next());
    497        }
    498
    499        return messages;
    500    }
    501
    502
    503    /**
    504     * Retrieve a message from the POP3 server.  A retrieve message attempt
    505     * can only succeed if the client is in the
    506     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    507     * <p>
    508     * You must not issue any commands to the POP3 server (i.e., call any
    509     * other methods) until you finish reading the message from the
    510     * returned BufferedReader instance.
    511     * The POP3 protocol uses the same stream for issuing commands as it does
    512     * for returning results.  Therefore the returned BufferedReader actually reads
    513     * directly from the POP3 connection.  After the end of message has been
    514     * reached, new commands can be executed and their replies read.  If
    515     * you do not follow these requirements, your program will not work
    516     * properly.
    517     *
    518     * @param messageId  The number of the message to fetch.
    519     * @return A DotTerminatedMessageReader instance
    520     * from which the entire message can be read.
    521     * This can safely be cast to a {@link java.io.BufferedReader BufferedReader} in order to
    522     * use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method.
    523     * Returns null if the retrieval attempt fails  (e.g., if the specified
    524     * message number does not exist).
    525     * @throws IOException If a network I/O error occurs in the process of
    526     *        sending the retrieve message command.
    527     */
    528    public Reader retrieveMessage(final int messageId) throws IOException
    529    {
    530        if (getState() != TRANSACTION_STATE) {
    531            return null;
    532        }
    533        if (sendCommand(POP3Command.RETR, Integer.toString(messageId)) != POP3Reply.OK) {
    534            return null;
    535        }
    536
    537        return new DotTerminatedMessageReader(reader);
    538    }
    539
    540
    541    /**
    542     * Retrieve only the specified top number of lines of a message from the
    543     * POP3 server.  A retrieve top lines attempt
    544     * can only succeed if the client is in the
    545     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    546     * <p>
    547     * You must not issue any commands to the POP3 server (i.e., call any
    548     * other methods) until you finish reading the message from the returned
    549     * BufferedReader instance.
    550     * The POP3 protocol uses the same stream for issuing commands as it does
    551     * for returning results.  Therefore the returned BufferedReader actually reads
    552     * directly from the POP3 connection.  After the end of message has been
    553     * reached, new commands can be executed and their replies read.  If
    554     * you do not follow these requirements, your program will not work
    555     * properly.
    556     *
    557     * @param messageId  The number of the message to fetch.
    558     * @param numLines  The top number of lines to fetch. This must be &gt;= 0.
    559     * @return  A DotTerminatedMessageReader instance
    560     * from which the specified top number of lines of the message can be
    561     * read.
    562     * This can safely be cast to a {@link java.io.BufferedReader BufferedReader} in order to
    563     * use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method.
    564     * Returns null if the retrieval attempt fails  (e.g., if the specified
    565     * message number does not exist).
    566     * @throws IOException If a network I/O error occurs in the process of
    567     *       sending the top command.
    568     */
    569    public Reader retrieveMessageTop(final int messageId, final int numLines)
    570    throws IOException
    571    {
    572        if (numLines < 0 || getState() != TRANSACTION_STATE) {
    573            return null;
    574        }
    575        if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " +
    576                        Integer.toString(numLines)) != POP3Reply.OK) {
    577            return null;
    578        }
    579
    580        return new DotTerminatedMessageReader(reader);
    581    }
    582
    583
    584}
    585