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         * @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         * <p>
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         * @exception 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         * <p>
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         * @exception IOException If a network I/O error occurs in the process of
199         *            logging in.
200         * @exception 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());
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         * <p>
254         * @return True if the logout attempt was successful, false if not.
255         * @exception 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         * <p>
276         * @return True if the noop attempt was successful, false if not.
277         * @exception 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         * <p>
299         * @param messageId  The message number to delete.
300         * @return True if the deletion attempt was successful, false if not.
301         * @exception 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         * <p>
321         * @return True if the reset attempt was successful, false if not.
322         * @exception 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         * <p>
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         * @exception 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         * <p>
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         * @exception 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         * <p>
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         * @exception 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         * <p>
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         * @exception 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         * <p>
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         * @exception 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         * <p>
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         * @exception 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         * <p>
556         * @param messageId  The number of the message to fetch.
557         * @param numLines  The top number of lines to fetch. This must be >= 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         * @exception 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