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