    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         * @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());
    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