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