View Javadoc
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  
18  package org.apache.commons.net.pop3;
19  
20  import java.io.IOException;
21  import java.io.Reader;
22  import java.security.MessageDigest;
23  import java.security.NoSuchAlgorithmException;
24  import java.util.Arrays;
25  import java.util.ListIterator;
26  import java.util.StringTokenizer;
27  
28  import org.apache.commons.net.io.DotTerminatedMessageReader;
29  
30  /**
31   * The POP3Client class implements the client side of the Internet POP3 Protocol defined in RFC 1939. All commands are supported, including the APOP command
32   * which requires MD5 encryption. See RFC 1939 for more details on the POP3 protocol.
33   * <p>
34   * 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
35   * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the
36   * 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
37   * lenient as possible.
38   *
39   *
40   * @see POP3MessageInfo
41   * @see org.apache.commons.net.io.DotTerminatedMessageReader
42   * @see org.apache.commons.net.MalformedServerReplyException
43   */
44  
45  public class POP3Client extends POP3 {
46  
47      private static POP3MessageInfo parseStatus(final String line) {
48          int num, size;
49          final StringTokenizer tokenizer;
50  
51          tokenizer = new StringTokenizer(line);
52  
53          if (!tokenizer.hasMoreElements()) {
54              return null;
55          }
56  
57          num = size = 0;
58  
59          try {
60              num = Integer.parseInt(tokenizer.nextToken());
61  
62              if (!tokenizer.hasMoreElements()) {
63                  return null;
64              }
65  
66              size = Integer.parseInt(tokenizer.nextToken());
67          } catch (final NumberFormatException e) {
68              return null;
69          }
70  
71          return new POP3MessageInfo(num, size);
72      }
73  
74      private static POP3MessageInfo parseUID(String line) {
75          int num;
76          final StringTokenizer tokenizer;
77  
78          tokenizer = new StringTokenizer(line);
79  
80          if (!tokenizer.hasMoreElements()) {
81              return null;
82          }
83  
84          num = 0;
85  
86          try {
87              num = Integer.parseInt(tokenizer.nextToken());
88  
89              if (!tokenizer.hasMoreElements()) {
90                  return null;
91              }
92  
93              line = tokenizer.nextToken();
94          } catch (final NumberFormatException e) {
95              return null;
96          }
97  
98          return new POP3MessageInfo(num, line);
99      }
100 
101     /**
102      * Send a CAPA command to the POP3 server.
103      *
104      * @return True if the command was successful, false if not.
105      * @throws IOException If a network I/O error occurs in the process of sending the CAPA command.
106      * @since 3.1 (was previously in ExtendedPOP3Client)
107      */
108     public boolean capa() throws IOException {
109         if (sendCommand(POP3Command.CAPA) == POP3Reply.OK) {
110             getAdditionalReply();
111             return true;
112         }
113         return false;
114 
115     }
116 
117     /**
118      * 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
119      * {@link #reset reset } command. Messages marked for deletion are only deleted by the server on {@link #logout logout }.
120      * A deletion attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } .
121      *
122      * @param messageId The message number to delete.
123      * @return True if the deletion attempt was successful, false if not.
124      * @throws IOException If a network I/O error occurs in the process of sending the delete command.
125      */
126     public boolean deleteMessage(final int messageId) throws IOException {
127         if (getState() == TRANSACTION_STATE) {
128             return sendCommand(POP3Command.DELE, Integer.toString(messageId)) == POP3Reply.OK;
129         }
130         return false;
131     }
132 
133     /**
134      * 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
135      * TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of the listed message and the size of the message in bytes. Returns null
136      * if the list attempt fails (e.g., if the specified message number does not exist).
137      *
138      * @param messageId The number of the message list.
139      * @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
140      *         fails.
141      * @throws IOException If a network I/O error occurs in the process of sending the list command.
142      */
143     public POP3MessageInfo listMessage(final int messageId) throws IOException {
144         if (getState() != TRANSACTION_STATE) {
145             return null;
146         }
147         if (sendCommand(POP3Command.LIST, Integer.toString(messageId)) != POP3Reply.OK) {
148             return null;
149         }
150         return parseStatus(lastReplyLine.substring(3));
151     }
152 
153     /**
154      * 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 }
155      * . 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
156      * returns a zero length array. If the list attempt fails, it returns null.
157      *
158      * @return An array of POP3MessageInfo instances representing all messages in the order they appear in the mailbox, each containing the number of a message
159      *         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.
160      * @throws IOException If a network I/O error occurs in the process of sending the list command.
161      */
162     public POP3MessageInfo[] listMessages() throws IOException {
163         if (getState() != TRANSACTION_STATE) {
164             return null;
165         }
166         if (sendCommand(POP3Command.LIST) != POP3Reply.OK) {
167             return null;
168         }
169         getAdditionalReply();
170 
171         // This could be a zero length array if no messages present
172         final POP3MessageInfo[] messages = new POP3MessageInfo[replyLines.size() - 2]; // skip first and last lines
173 
174         final ListIterator<String> en = replyLines.listIterator(1); // Skip first line
175 
176         // Fetch lines.
177         Arrays.setAll(messages, i -> parseStatus(en.next()));
178 
179         return messages;
180     }
181 
182     /**
183      * List the unique identifier for a message. A list attempt can only succeed if the client is in the
184      * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of the listed
185      * 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).
186      *
187      * @param messageId The number of the message list.
188      * @return A POP3MessageInfo instance containing the number of the listed message and the unique identifier for that message. Returns null if the list
189      *         attempt fails.
190      * @throws IOException If a network I/O error occurs in the process of sending the list unique identifier command.
191      */
192     public POP3MessageInfo listUniqueIdentifier(final int messageId) throws IOException {
193         if (getState() != TRANSACTION_STATE) {
194             return null;
195         }
196         if (sendCommand(POP3Command.UIDL, Integer.toString(messageId)) != POP3Reply.OK) {
197             return null;
198         }
199         return parseUID(lastReplyLine.substring(3));
200     }
201 
202     /**
203      * List the unique identifiers for all messages. A list attempt can only succeed if the client is in the
204      * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . Returns an array of POP3MessageInfo instances, each containing the number
205      * 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.
206      *
207      * @return An array of POP3MessageInfo instances representing all messages in the order they appear in the mailbox, each containing the number of a message
208      *         and its unique identifier If there are no messages, this method returns a zero length array. If the list attempt fails, it returns null.
209      * @throws IOException If a network I/O error occurs in the process of sending the list unique identifier command.
210      */
211     public POP3MessageInfo[] listUniqueIdentifiers() throws IOException {
212         if (getState() != TRANSACTION_STATE) {
213             return null;
214         }
215         if (sendCommand(POP3Command.UIDL) != POP3Reply.OK) {
216             return null;
217         }
218         getAdditionalReply();
219 
220         // This could be a zero length array if no messages present
221         final POP3MessageInfo[] messages = new POP3MessageInfo[replyLines.size() - 2]; // skip first and last lines
222 
223         final ListIterator<String> en = replyLines.listIterator(1); // skip first line
224 
225         // Fetch lines.
226         Arrays.setAll(messages, i -> parseUID(en.next()));
227 
228         return messages;
229     }
230 
231     /**
232      * Login to the POP3 server with the given user and password. You must first connect to the server with
233      * {@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
234      * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }. After logging in, the client enters the
235      * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }.
236      *
237      * @param user The account name being logged in to.
238      * @param password The plain text password of the account.
239      * @return True if the login attempt was successful, false if not.
240      * @throws IOException If a network I/O error occurs in the process of logging in.
241      */
242     public boolean login(final String user, final String password) throws IOException {
243         if (getState() != AUTHORIZATION_STATE) {
244             return false;
245         }
246 
247         if (sendCommand(POP3Command.USER, user) != POP3Reply.OK) {
248             return false;
249         }
250 
251         if (sendCommand(POP3Command.PASS, password) != POP3Reply.OK) {
252             return false;
253         }
254 
255         setState(TRANSACTION_STATE);
256 
257         return true;
258     }
259 
260     /**
261      * Login to the POP3 server with the given username and authentication information. Use this method when connecting to a server requiring authentication
262      * using the APOP command. Because the timestamp produced in the greeting banner varies from server to server, it is not possible to consistently extract
263      * the information. Therefore, after connecting to the server, you must call {@link org.apache.commons.net.pop3.POP3#getReplyString getReplyString } and
264      * parse out the timestamp information yourself.
265      * <p>
266      * 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
267      * only valid if the client is in the {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }. After logging in, the client
268      * enters the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }. After connecting, you must parse out the server specific
269      * 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
270      * more details regarding the APOP command.
271      *
272      * @param user  The account name being logged in to.
273      * @param timestamp The timestamp string to combine with the secret.
274      * @param secret    The shared secret which produces the MD5 digest when combined with the timestamp.
275      * @return True if the login attempt was successful, false if not.
276      * @throws IOException              If a network I/O error occurs in the process of logging in.
277      * @throws NoSuchAlgorithmException If the MD5 encryption algorithm cannot be instantiated by the Java runtime system.
278      */
279     public boolean login(final String user, String timestamp, final String secret) throws IOException, NoSuchAlgorithmException {
280         int i;
281         final byte[] digest;
282         final StringBuilder buffer;
283         final StringBuilder digestBuffer;
284         final MessageDigest md5;
285 
286         if (getState() != AUTHORIZATION_STATE) {
287             return false;
288         }
289 
290         md5 = MessageDigest.getInstance("MD5");
291         timestamp += secret;
292         digest = md5.digest(timestamp.getBytes(getCharset()));
293         digestBuffer = new StringBuilder(128);
294 
295         for (i = 0; i < digest.length; i++) {
296             final int digit = digest[i] & 0xff;
297             if (digit <= 15) { // Add leading zero if necessary (NET-351)
298                 digestBuffer.append("0");
299             }
300             digestBuffer.append(Integer.toHexString(digit));
301         }
302 
303         buffer = new StringBuilder(256);
304         buffer.append(user);
305         buffer.append(' ');
306         buffer.append(digestBuffer.toString());
307 
308         if (sendCommand(POP3Command.APOP, buffer.toString()) != POP3Reply.OK) {
309             return false;
310         }
311 
312         setState(TRANSACTION_STATE);
313 
314         return true;
315     }
316 
317     /**
318      * 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
319      * 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
320      * {@link org.apache.commons.net.pop3.POP3#UPDATE_STATE UPDATE_STATE } on a successful logout.
321      *
322      * @return True if the logout attempt was successful, false if not.
323      * @throws IOException If a network I/O error occurs in the process of logging out.
324      */
325     public boolean logout() throws IOException {
326         if (getState() == TRANSACTION_STATE) {
327             setState(UPDATE_STATE);
328         }
329         sendCommand(POP3Command.QUIT);
330         return replyCode == POP3Reply.OK;
331     }
332 
333     /**
334      * 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
335      * inactivity. A noop attempt will only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } .
336      *
337      * @return True if the noop attempt was successful, false if not.
338      * @throws IOException If a network I/O error occurs in the process of sending the NOOP command.
339      */
340     public boolean noop() throws IOException {
341         if (getState() == TRANSACTION_STATE) {
342             return sendCommand(POP3Command.NOOP) == POP3Reply.OK;
343         }
344         return false;
345     }
346 
347     /**
348      * 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
349      * in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } .
350      *
351      * @return True if the reset attempt was successful, false if not.
352      * @throws IOException If a network I/O error occurs in the process of sending the reset command.
353      */
354     public boolean reset() throws IOException {
355         if (getState() == TRANSACTION_STATE) {
356             return sendCommand(POP3Command.RSET) == POP3Reply.OK;
357         }
358         return false;
359     }
360 
361     /**
362      * Retrieve a message from the POP3 server. A retrieve message attempt can only succeed if the client is in the
363      * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
364      * <p>
365      * 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
366      * instance. The POP3 protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually
367      * 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
368      * follow these requirements, your program will not work properly.
369      *
370      * @param messageId The number of the message to fetch.
371      * @return A DotTerminatedMessageReader instance from which the entire message can be read. This can safely be cast to a {@link java.io.BufferedReader
372      *         BufferedReader} in order to use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method. Returns null if the retrieval
373      *         attempt fails (e.g., if the specified message number does not exist).
374      * @throws IOException If a network I/O error occurs in the process of sending the retrieve message command.
375      */
376     public Reader retrieveMessage(final int messageId) throws IOException {
377         if (getState() != TRANSACTION_STATE) {
378             return null;
379         }
380         if (sendCommand(POP3Command.RETR, Integer.toString(messageId)) != POP3Reply.OK) {
381             return null;
382         }
383 
384         return new DotTerminatedMessageReader(reader);
385     }
386 
387     /**
388      * 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
389      * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
390      * <p>
391      * 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
392      * instance. The POP3 protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader
393      * actually reads directly from the POP3 connection. After the end of message has been reached, new commands can be executed and their replies read.
394      * If you do not follow these requirements, your program will not work properly.
395      *
396      * @param messageId The number of the message to fetch.
397      * @param numLines  The top number of lines to fetch. This must be &gt;= 0.
398      * @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
399      *         {@link java.io.BufferedReader BufferedReader} in order to use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method.
400      *         Returns null if the retrieval attempt fails (e.g., if the specified message number does not exist).
401      * @throws IOException If a network I/O error occurs in the process of sending the top command.
402      */
403     public Reader retrieveMessageTop(final int messageId, final int numLines) throws IOException {
404         if (numLines < 0 || getState() != TRANSACTION_STATE) {
405             return null;
406         }
407         if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " + Integer.toString(numLines)) != POP3Reply.OK) {
408             return null;
409         }
410 
411         return new DotTerminatedMessageReader(reader);
412     }
413 
414     /**
415      * Get the mailbox status. A status attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE
416      * TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of messages in the mailbox and the total size of the messages in bytes.
417      * Returns null if the status the attempt fails.
418      *
419      * @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
420      *         status the attempt fails.
421      * @throws IOException If a network I/O error occurs in the process of sending the status command.
422      */
423     public POP3MessageInfo status() throws IOException {
424         if (getState() != TRANSACTION_STATE) {
425             return null;
426         }
427         if (sendCommand(POP3Command.STAT) != POP3Reply.OK) {
428             return null;
429         }
430         return parseStatus(lastReplyLine.substring(3));
431     }
432 
433 }