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