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