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