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