    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 * <p>
    045 * <p>
    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(String line)
    055    {
    056        int num, size;
    057        StringTokenizer tokenizer;
    058
    059        tokenizer = new StringTokenizer(line);
    060
    061        if (!tokenizer.hasMoreElements()) {
    062            return null;
    063        }
    064
    065        num = size = 0;
    066
    067        try
    068        {
    069            num = Integer.parseInt(tokenizer.nextToken());
    070
    071            if (!tokenizer.hasMoreElements()) {
    072                return null;
    073            }
    074
    075            size = Integer.parseInt(tokenizer.nextToken());
    076        }
    077        catch (NumberFormatException e)
    078        {
    079            return null;
    080        }
    081
    082        return new POP3MessageInfo(num, size);
    083    }
    084
    085    private static POP3MessageInfo __parseUID(String line)
    086    {
    087        int num;
    088        StringTokenizer tokenizer;
    089
    090        tokenizer = new StringTokenizer(line);
    091
    092        if (!tokenizer.hasMoreElements()) {
    093            return null;
    094        }
    095
    096        num = 0;
    097
    098        try
    099        {
    100            num = Integer.parseInt(tokenizer.nextToken());
    101
    102            if (!tokenizer.hasMoreElements()) {
    103                return null;
    104            }
    105
    106            line = tokenizer.nextToken();
    107        }
    108        catch (NumberFormatException e)
    109        {
    110            return null;
    111        }
    112
    113        return new POP3MessageInfo(num, line);
    114    }
    115
    116    /***
    117     * Send a CAPA command to the POP3 server.
    118     * @return True if the command was successful, false if not.
    119     * @exception IOException If a network I/O error occurs in the process of
    120     *        sending the CAPA command.
    121     * @since 3.1 (was previously in ExtendedPOP3Client)
    122     ***/
    123    public boolean capa() throws IOException
    124    {
    125        if (sendCommand(POP3Command.CAPA) == POP3Reply.OK) {
    126            getAdditionalReply();
    127            return true;
    128        }
    129        return false;
    130
    131    }
    132
    133    /***
    134     * Login to the POP3 server with the given username and password.  You
    135     * must first connect to the server with
    136     * {@link org.apache.commons.net.SocketClient#connect  connect }
    137     * before attempting to login.  A login attempt is only valid if
    138     * the client is in the
    139     * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }
    140     * .  After logging in, the client enters the
    141     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    142     * .
    143     * <p>
    144     * @param username  The account name being logged in to.
    145     * @param password  The plain text password of the account.
    146     * @return True if the login attempt was successful, false if not.
    147     * @exception IOException If a network I/O error occurs in the process of
    148     *            logging in.
    149     ***/
    150    public boolean login(String username, String password) throws IOException
    151    {
    152        if (getState() != AUTHORIZATION_STATE) {
    153            return false;
    154        }
    155
    156        if (sendCommand(POP3Command.USER, username) != POP3Reply.OK) {
    157            return false;
    158        }
    159
    160        if (sendCommand(POP3Command.PASS, password) != POP3Reply.OK) {
    161            return false;
    162        }
    163
    164        setState(TRANSACTION_STATE);
    165
    166        return true;
    167    }
    168
    169
    170    /***
    171     * Login to the POP3 server with the given username and authentication
    172     * information.  Use this method when connecting to a server requiring
    173     * authentication using the APOP command.  Because the timestamp
    174     * produced in the greeting banner varies from server to server, it is
    175     * not possible to consistently extract the information.  Therefore,
    176     * after connecting to the server, you must call
    177     * {@link org.apache.commons.net.pop3.POP3#getReplyString getReplyString }
    178     *  and parse out the timestamp information yourself.
    179     * <p>
    180     * You must first connect to the server with
    181     * {@link org.apache.commons.net.SocketClient#connect  connect }
    182     * before attempting to login.  A login attempt is only valid if
    183     * the client is in the
    184     * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }
    185     * .  After logging in, the client enters the
    186     * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
    187     * .  After connecting, you must parse out the
    188     * server specific information to use as a timestamp, and pass that
    189     * information to this method.  The secret is a shared secret known
    190     * to you and the server.  See RFC 1939 for more details regarding
    191     * the APOP command.
    192     * <p>
    193     * @param username  The account name being logged in to.
    194     * @param timestamp  The timestamp string to combine with the secret.
    195     * @param secret  The shared secret which produces the MD5 digest when
    196     *        combined with the timestamp.
    197     * @return True if the login attempt was successful, false if not.
    198     * @exception IOException If a network I/O error occurs in the process of
    199     *            logging in.
    200     * @exception NoSuchAlgorithmException If the MD5 encryption algorithm
    201     *      cannot be instantiated by the Java runtime system.
    202     ***/
    203    public boolean login(String username, String timestamp, String secret)
    204    throws IOException, NoSuchAlgorithmException
    205    {
    206        int i;
    207        byte[] digest;
    208        StringBuilder buffer, digestBuffer;
    209        MessageDigest md5;
    210
    211        if (getState() != AUTHORIZATION_STATE) {
    212            return false;
    213        }
    214
    215        md5 = MessageDigest.getInstance("MD5");
    216        timestamp += secret;
    217        digest = md5.digest(timestamp.getBytes(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