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