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