POP3Client.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.net.pop3;

  18. import java.io.IOException;
  19. import java.io.Reader;
  20. import java.security.MessageDigest;
  21. import java.security.NoSuchAlgorithmException;
  22. import java.util.Arrays;
  23. import java.util.ListIterator;
  24. import java.util.StringTokenizer;

  25. import org.apache.commons.net.io.DotTerminatedMessageReader;

  26. /**
  27.  * The POP3Client class implements the client side of the Internet POP3 Protocol defined in RFC 1939. All commands are supported, including the APOP command
  28.  * which requires MD5 encryption. See RFC 1939 for more details on the POP3 protocol.
  29.  * <p>
  30.  * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a
  31.  * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the
  32.  * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as
  33.  * lenient as possible.
  34.  *
  35.  *
  36.  * @see POP3MessageInfo
  37.  * @see org.apache.commons.net.io.DotTerminatedMessageReader
  38.  * @see org.apache.commons.net.MalformedServerReplyException
  39.  */

  40. public class POP3Client extends POP3 {

  41.     private static POP3MessageInfo parseStatus(final String line) {
  42.         int num, size;
  43.         final StringTokenizer tokenizer;

  44.         tokenizer = new StringTokenizer(line);

  45.         if (!tokenizer.hasMoreElements()) {
  46.             return null;
  47.         }

  48.         num = size = 0;

  49.         try {
  50.             num = Integer.parseInt(tokenizer.nextToken());

  51.             if (!tokenizer.hasMoreElements()) {
  52.                 return null;
  53.             }

  54.             size = Integer.parseInt(tokenizer.nextToken());
  55.         } catch (final NumberFormatException e) {
  56.             return null;
  57.         }

  58.         return new POP3MessageInfo(num, size);
  59.     }

  60.     private static POP3MessageInfo parseUID(String line) {
  61.         int num;
  62.         final StringTokenizer tokenizer;

  63.         tokenizer = new StringTokenizer(line);

  64.         if (!tokenizer.hasMoreElements()) {
  65.             return null;
  66.         }

  67.         num = 0;

  68.         try {
  69.             num = Integer.parseInt(tokenizer.nextToken());

  70.             if (!tokenizer.hasMoreElements()) {
  71.                 return null;
  72.             }

  73.             line = tokenizer.nextToken();
  74.         } catch (final NumberFormatException e) {
  75.             return null;
  76.         }

  77.         return new POP3MessageInfo(num, line);
  78.     }

  79.     /**
  80.      * Send a CAPA command to the POP3 server.
  81.      *
  82.      * @return True if the command was successful, false if not.
  83.      * @throws IOException If a network I/O error occurs in the process of sending the CAPA command.
  84.      * @since 3.1 (was previously in ExtendedPOP3Client)
  85.      */
  86.     public boolean capa() throws IOException {
  87.         if (sendCommand(POP3Command.CAPA) == POP3Reply.OK) {
  88.             getAdditionalReply();
  89.             return true;
  90.         }
  91.         return false;

  92.     }

  93.     /**
  94.      * Delete a message from the POP3 server. The message is only marked for deletion by the server. If you decide to unmark the message, you must issue a
  95.      * {@link #reset reset } command. Messages marked for deletion are only deleted by the server on {@link #logout logout }.
  96.      * A deletion attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } .
  97.      *
  98.      * @param messageId The message number to delete.
  99.      * @return True if the deletion attempt was successful, false if not.
  100.      * @throws IOException If a network I/O error occurs in the process of sending the delete command.
  101.      */
  102.     public boolean deleteMessage(final int messageId) throws IOException {
  103.         if (getState() == TRANSACTION_STATE) {
  104.             return sendCommand(POP3Command.DELE, Integer.toString(messageId)) == POP3Reply.OK;
  105.         }
  106.         return false;
  107.     }

  108.     /**
  109.      * List an individual message. A list attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE
  110.      * TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of the listed message and the size of the message in bytes. Returns null
  111.      * if the list attempt fails (e.g., if the specified message number does not exist).
  112.      *
  113.      * @param messageId The number of the message list.
  114.      * @return A POP3MessageInfo instance containing the number of the listed message and the size of the message in bytes. Returns null if the list attempt
  115.      *         fails.
  116.      * @throws IOException If a network I/O error occurs in the process of sending the list command.
  117.      */
  118.     public POP3MessageInfo listMessage(final int messageId) throws IOException {
  119.         if (getState() != TRANSACTION_STATE) {
  120.             return null;
  121.         }
  122.         if (sendCommand(POP3Command.LIST, Integer.toString(messageId)) != POP3Reply.OK) {
  123.             return null;
  124.         }
  125.         return parseStatus(lastReplyLine.substring(3));
  126.     }

  127.     /**
  128.      * List all messages. A list attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
  129.      * . Returns an array of POP3MessageInfo instances, each containing the number of a message and its size in bytes. If there are no messages, this method
  130.      * returns a zero length array. If the list attempt fails, it returns null.
  131.      *
  132.      * @return An array of POP3MessageInfo instances representing all messages in the order they appear in the mailbox, each containing the number of a message
  133.      *         and its size in bytes. If there are no messages, this method returns a zero length array. If the list attempt fails, it returns null.
  134.      * @throws IOException If a network I/O error occurs in the process of sending the list command.
  135.      */
  136.     public POP3MessageInfo[] listMessages() throws IOException {
  137.         if (getState() != TRANSACTION_STATE) {
  138.             return null;
  139.         }
  140.         if (sendCommand(POP3Command.LIST) != POP3Reply.OK) {
  141.             return null;
  142.         }
  143.         getAdditionalReply();

  144.         // This could be a zero length array if no messages present
  145.         final POP3MessageInfo[] messages = new POP3MessageInfo[replyLines.size() - 2]; // skip first and last lines

  146.         final ListIterator<String> en = replyLines.listIterator(1); // Skip first line

  147.         // Fetch lines.
  148.         Arrays.setAll(messages, i -> parseStatus(en.next()));

  149.         return messages;
  150.     }

  151.     /**
  152.      * List the unique identifier for a message. A list attempt can only succeed if the client is in the
  153.      * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of the listed
  154.      * message and the unique identifier for that message. Returns null if the list attempt fails (e.g., if the specified message number does not exist).
  155.      *
  156.      * @param messageId The number of the message list.
  157.      * @return A POP3MessageInfo instance containing the number of the listed message and the unique identifier for that message. Returns null if the list
  158.      *         attempt fails.
  159.      * @throws IOException If a network I/O error occurs in the process of sending the list unique identifier command.
  160.      */
  161.     public POP3MessageInfo listUniqueIdentifier(final int messageId) throws IOException {
  162.         if (getState() != TRANSACTION_STATE) {
  163.             return null;
  164.         }
  165.         if (sendCommand(POP3Command.UIDL, Integer.toString(messageId)) != POP3Reply.OK) {
  166.             return null;
  167.         }
  168.         return parseUID(lastReplyLine.substring(3));
  169.     }

  170.     /**
  171.      * List the unique identifiers for all messages. A list attempt can only succeed if the client is in the
  172.      * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . Returns an array of POP3MessageInfo instances, each containing the number
  173.      * of a message and its unique identifier. If there are no messages, this method returns a zero length array. If the list attempt fails, it returns null.
  174.      *
  175.      * @return An array of POP3MessageInfo instances representing all messages in the order they appear in the mailbox, each containing the number of a message
  176.      *         and its unique identifier If there are no messages, this method returns a zero length array. If the list attempt fails, it returns null.
  177.      * @throws IOException If a network I/O error occurs in the process of sending the list unique identifier command.
  178.      */
  179.     public POP3MessageInfo[] listUniqueIdentifiers() throws IOException {
  180.         if (getState() != TRANSACTION_STATE) {
  181.             return null;
  182.         }
  183.         if (sendCommand(POP3Command.UIDL) != POP3Reply.OK) {
  184.             return null;
  185.         }
  186.         getAdditionalReply();

  187.         // This could be a zero length array if no messages present
  188.         final POP3MessageInfo[] messages = new POP3MessageInfo[replyLines.size() - 2]; // skip first and last lines

  189.         final ListIterator<String> en = replyLines.listIterator(1); // skip first line

  190.         // Fetch lines.
  191.         Arrays.setAll(messages, i -> parseUID(en.next()));

  192.         return messages;
  193.     }

  194.     /**
  195.      * Login to the POP3 server with the given user and password. You must first connect to the server with
  196.      * {@link org.apache.commons.net.SocketClient#connect connect } before attempting to log in. A login attempt is only valid if the client is in the
  197.      * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }. After logging in, the client enters the
  198.      * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }.
  199.      *
  200.      * @param user The account name being logged in to.
  201.      * @param password The plain text password of the account.
  202.      * @return True if the login attempt was successful, false if not.
  203.      * @throws IOException If a network I/O error occurs in the process of logging in.
  204.      */
  205.     public boolean login(final String user, final String password) throws IOException {
  206.         if (getState() != AUTHORIZATION_STATE) {
  207.             return false;
  208.         }

  209.         if (sendCommand(POP3Command.USER, user) != POP3Reply.OK) {
  210.             return false;
  211.         }

  212.         if (sendCommand(POP3Command.PASS, password) != POP3Reply.OK) {
  213.             return false;
  214.         }

  215.         setState(TRANSACTION_STATE);

  216.         return true;
  217.     }

  218.     /**
  219.      * Login to the POP3 server with the given username and authentication information. Use this method when connecting to a server requiring authentication
  220.      * using the APOP command. Because the timestamp produced in the greeting banner varies from server to server, it is not possible to consistently extract
  221.      * the information. Therefore, after connecting to the server, you must call {@link org.apache.commons.net.pop3.POP3#getReplyString getReplyString } and
  222.      * parse out the timestamp information yourself.
  223.      * <p>
  224.      * You must first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect } before attempting to log in. A login attempt is
  225.      * only valid if the client is in the {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }. After logging in, the client
  226.      * enters the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }. After connecting, you must parse out the server specific
  227.      * information to use as a timestamp, and pass that information to this method. The secret is a shared secret known to you and the server. See RFC 1939 for
  228.      * more details regarding the APOP command.
  229.      *
  230.      * @param user  The account name being logged in to.
  231.      * @param timestamp The timestamp string to combine with the secret.
  232.      * @param secret    The shared secret which produces the MD5 digest when combined with the timestamp.
  233.      * @return True if the login attempt was successful, false if not.
  234.      * @throws IOException              If a network I/O error occurs in the process of logging in.
  235.      * @throws NoSuchAlgorithmException If the MD5 encryption algorithm cannot be instantiated by the Java runtime system.
  236.      */
  237.     public boolean login(final String user, String timestamp, final String secret) throws IOException, NoSuchAlgorithmException {
  238.         int i;
  239.         final byte[] digest;
  240.         final StringBuilder buffer;
  241.         final StringBuilder digestBuffer;
  242.         final MessageDigest md5;

  243.         if (getState() != AUTHORIZATION_STATE) {
  244.             return false;
  245.         }

  246.         md5 = MessageDigest.getInstance("MD5");
  247.         timestamp += secret;
  248.         digest = md5.digest(timestamp.getBytes(getCharset()));
  249.         digestBuffer = new StringBuilder(128);

  250.         for (i = 0; i < digest.length; i++) {
  251.             final int digit = digest[i] & 0xff;
  252.             if (digit <= 15) { // Add leading zero if necessary (NET-351)
  253.                 digestBuffer.append("0");
  254.             }
  255.             digestBuffer.append(Integer.toHexString(digit));
  256.         }

  257.         buffer = new StringBuilder(256);
  258.         buffer.append(user);
  259.         buffer.append(' ');
  260.         buffer.append(digestBuffer.toString());

  261.         if (sendCommand(POP3Command.APOP, buffer.toString()) != POP3Reply.OK) {
  262.             return false;
  263.         }

  264.         setState(TRANSACTION_STATE);

  265.         return true;
  266.     }

  267.     /**
  268.      * Logout of the POP3 server. To fully disconnect from the server you must call {@link org.apache.commons.net.pop3.POP3#disconnect disconnect }. A logout
  269.      * attempt is valid in any state. If the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } , it enters the
  270.      * {@link org.apache.commons.net.pop3.POP3#UPDATE_STATE UPDATE_STATE } on a successful logout.
  271.      *
  272.      * @return True if the logout attempt was successful, false if not.
  273.      * @throws IOException If a network I/O error occurs in the process of logging out.
  274.      */
  275.     public boolean logout() throws IOException {
  276.         if (getState() == TRANSACTION_STATE) {
  277.             setState(UPDATE_STATE);
  278.         }
  279.         sendCommand(POP3Command.QUIT);
  280.         return replyCode == POP3Reply.OK;
  281.     }

  282.     /**
  283.      * Send a NOOP command to the POP3 server. This is useful for keeping a connection alive since most POP3 servers will time out after 10 minutes of
  284.      * inactivity. A noop attempt will only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } .
  285.      *
  286.      * @return True if the noop attempt was successful, false if not.
  287.      * @throws IOException If a network I/O error occurs in the process of sending the NOOP command.
  288.      */
  289.     public boolean noop() throws IOException {
  290.         if (getState() == TRANSACTION_STATE) {
  291.             return sendCommand(POP3Command.NOOP) == POP3Reply.OK;
  292.         }
  293.         return false;
  294.     }

  295.     /**
  296.      * Reset the POP3 session. This is useful for undoing any message deletions that may have been performed. A reset attempt can only succeed if the client is
  297.      * in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } .
  298.      *
  299.      * @return True if the reset attempt was successful, false if not.
  300.      * @throws IOException If a network I/O error occurs in the process of sending the reset command.
  301.      */
  302.     public boolean reset() throws IOException {
  303.         if (getState() == TRANSACTION_STATE) {
  304.             return sendCommand(POP3Command.RSET) == POP3Reply.OK;
  305.         }
  306.         return false;
  307.     }

  308.     /**
  309.      * Retrieve a message from the POP3 server. A retrieve message attempt can only succeed if the client is in the
  310.      * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
  311.      * <p>
  312.      * You must not issue any commands to the POP3 server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader
  313.      * instance. The POP3 protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
  314.      * reads directly from the POP3 connection. After the end of message has been reached, new commands can be executed and their replies read. If you do not
  315.      * follow these requirements, your program will not work properly.
  316.      *
  317.      * @param messageId The number of the message to fetch.
  318.      * @return A DotTerminatedMessageReader instance from which the entire message can be read. This can safely be cast to a {@link java.io.BufferedReader
  319.      *         BufferedReader} in order to use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method. Returns null if the retrieval
  320.      *         attempt fails (e.g., if the specified message number does not exist).
  321.      * @throws IOException If a network I/O error occurs in the process of sending the retrieve message command.
  322.      */
  323.     public Reader retrieveMessage(final int messageId) throws IOException {
  324.         if (getState() != TRANSACTION_STATE) {
  325.             return null;
  326.         }
  327.         if (sendCommand(POP3Command.RETR, Integer.toString(messageId)) != POP3Reply.OK) {
  328.             return null;
  329.         }

  330.         return new DotTerminatedMessageReader(reader);
  331.     }

  332.     /**
  333.      * Retrieve only the specified top number of lines of a message from the POP3 server. A retrieve top lines attempt can only succeed if the client is in the
  334.      * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
  335.      * <p>
  336.      * You must not issue any commands to the POP3 server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader
  337.      * instance. The POP3 protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader
  338.      * actually reads directly from the POP3 connection. After the end of message has been reached, new commands can be executed and their replies read.
  339.      * If you do not follow these requirements, your program will not work properly.
  340.      *
  341.      * @param messageId The number of the message to fetch.
  342.      * @param numLines  The top number of lines to fetch. This must be &gt;= 0.
  343.      * @return A DotTerminatedMessageReader instance from which the specified top number of lines of the message can be read. This can safely be cast to a
  344.      *         {@link java.io.BufferedReader BufferedReader} in order to use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method.
  345.      *         Returns null if the retrieval attempt fails (e.g., if the specified message number does not exist).
  346.      * @throws IOException If a network I/O error occurs in the process of sending the top command.
  347.      */
  348.     public Reader retrieveMessageTop(final int messageId, final int numLines) throws IOException {
  349.         if (numLines < 0 || getState() != TRANSACTION_STATE) {
  350.             return null;
  351.         }
  352.         if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " + Integer.toString(numLines)) != POP3Reply.OK) {
  353.             return null;
  354.         }

  355.         return new DotTerminatedMessageReader(reader);
  356.     }

  357.     /**
  358.      * Gets the mailbox status. A status attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE
  359.      * TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of messages in the mailbox and the total size of the messages in bytes.
  360.      * Returns null if the status the attempt fails.
  361.      *
  362.      * @return A POP3MessageInfo instance containing the number of messages in the mailbox and the total size of the messages in bytes. Returns null if the
  363.      *         status the attempt fails.
  364.      * @throws IOException If a network I/O error occurs in the process of sending the status command.
  365.      */
  366.     public POP3MessageInfo status() throws IOException {
  367.         if (getState() != TRANSACTION_STATE) {
  368.             return null;
  369.         }
  370.         if (sendCommand(POP3Command.STAT) != POP3Reply.OK) {
  371.             return null;
  372.         }
  373.         return parseStatus(lastReplyLine.substring(3));
  374.     }

  375. }