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