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.IOException;
021    import java.io.Reader;
022    import java.security.MessageDigest;
023    import java.security.NoSuchAlgorithmException;
024    import java.util.ListIterator;
025    import java.util.StringTokenizer;
026    
027    import 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    
051    public 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