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