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.BufferedReader;
021 import java.io.IOException;
022 import java.io.Reader;
023 import java.security.MessageDigest;
024 import java.security.NoSuchAlgorithmException;
025 import java.util.ListIterator;
026 import java.util.StringTokenizer;
027
028 import org.apache.commons.net.io.DotTerminatedMessageReader;
029
030 /***
031 * The POP3Client class implements the client side of the Internet POP3
032 * Protocol defined in RFC 1939. All commands are supported, including
033 * the APOP command which requires MD5 encryption. See RFC 1939 for
034 * more details on the POP3 protocol.
035 * <p>
036 * Rather than list it separately for each method, we mention here that
037 * every method communicating with the server and throwing an IOException
038 * can also throw a
039 * {@link org.apache.commons.net.MalformedServerReplyException}
040 * , which is a subclass
041 * of IOException. A MalformedServerReplyException will be thrown when
042 * the reply received from the server deviates enough from the protocol
043 * specification that it cannot be interpreted in a useful manner despite
044 * attempts to be as lenient as possible.
045 * <p>
046 * <p>
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 StringBuilder 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 StringBuilder(128);
195
196 for (i = 0; i < digest.length; i++) {
197 int digit = digest[i] & 0xff;
198 if (digit <= 15) digestBuffer.append("0"); // Add leading zero if necessary (NET-351)
199 digestBuffer.append(Integer.toHexString(digit));
200 }
201
202 buffer = new StringBuilder(256);
203 buffer.append(username);
204 buffer.append(' ');
205 buffer.append(digestBuffer.toString());
206
207 if (sendCommand(POP3Command.APOP, buffer.toString()) != POP3Reply.OK)
208 return false;
209
210 setState(TRANSACTION_STATE);
211
212 return true;
213 }
214
215
216 /***
217 * Logout of the POP3 server. To fully disconnect from the server
218 * you must call
219 * {@link org.apache.commons.net.pop3.POP3#disconnect disconnect }.
220 * A logout attempt is valid in any state. If
221 * the client is in the
222 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
223 * , it enters the
224 * {@link org.apache.commons.net.pop3.POP3#UPDATE_STATE UPDATE_STATE }
225 * on a successful logout.
226 * <p>
227 * @return True if the logout attempt was successful, false if not.
228 * @exception IOException If a network I/O error occurs in the process
229 * of logging out.
230 ***/
231 public boolean logout() throws IOException
232 {
233 if (getState() == TRANSACTION_STATE)
234 setState(UPDATE_STATE);
235 sendCommand(POP3Command.QUIT);
236 return (_replyCode == POP3Reply.OK);
237 }
238
239
240 /***
241 * Send a NOOP command to the POP3 server. This is useful for keeping
242 * a connection alive since most POP3 servers will timeout after 10
243 * minutes of inactivity. A noop attempt will only succeed if
244 * the client is in the
245 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
246 * .
247 * <p>
248 * @return True if the noop attempt was successful, false if not.
249 * @exception IOException If a network I/O error occurs in the process of
250 * sending the NOOP command.
251 ***/
252 public boolean noop() throws IOException
253 {
254 if (getState() == TRANSACTION_STATE)
255 return (sendCommand(POP3Command.NOOP) == POP3Reply.OK);
256 return false;
257 }
258
259
260 /***
261 * Delete a message from the POP3 server. The message is only marked
262 * for deletion by the server. If you decide to unmark the message, you
263 * must issuse a {@link #reset reset } command. Messages marked
264 * for deletion are only deleted by the server on
265 * {@link #logout logout }.
266 * A delete attempt can only succeed if the client is in the
267 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
268 * .
269 * <p>
270 * @param messageId The message number to delete.
271 * @return True if the deletion attempt was successful, false if not.
272 * @exception IOException If a network I/O error occurs in the process of
273 * sending the delete command.
274 ***/
275 public boolean deleteMessage(int messageId) throws IOException
276 {
277 if (getState() == TRANSACTION_STATE)
278 return (sendCommand(POP3Command.DELE, Integer.toString(messageId))
279 == POP3Reply.OK);
280 return false;
281 }
282
283
284 /***
285 * Reset the POP3 session. This is useful for undoing any message
286 * deletions that may have been performed. A reset attempt can only
287 * succeed if the client is in the
288 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
289 * .
290 * <p>
291 * @return True if the reset attempt was successful, false if not.
292 * @exception IOException If a network I/O error occurs in the process of
293 * sending the reset command.
294 ***/
295 public boolean reset() throws IOException
296 {
297 if (getState() == TRANSACTION_STATE)
298 return (sendCommand(POP3Command.RSET) == POP3Reply.OK);
299 return false;
300 }
301
302 /***
303 * Get the mailbox status. A status attempt can only
304 * succeed if the client is in the
305 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
306 * . Returns a POP3MessageInfo instance
307 * containing the number of messages in the mailbox and the total
308 * size of the messages in bytes. Returns null if the status the
309 * attempt fails.
310 * <p>
311 * @return A POP3MessageInfo instance containing the number of
312 * messages in the mailbox and the total size of the messages
313 * in bytes. Returns null if the status the attempt fails.
314 * @exception IOException If a network I/O error occurs in the process of
315 * sending the status command.
316 ***/
317 public POP3MessageInfo status() throws IOException
318 {
319 if (getState() != TRANSACTION_STATE)
320 return null;
321 if (sendCommand(POP3Command.STAT) != POP3Reply.OK)
322 return null;
323 return __parseStatus(_lastReplyLine.substring(3));
324 }
325
326
327 /***
328 * List an individual message. A list attempt can only
329 * succeed if the client is in the
330 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
331 * . Returns a POP3MessageInfo instance
332 * containing the number of the listed message and the
333 * size of the message in bytes. Returns null if the list
334 * attempt fails (e.g., if the specified message number does
335 * not exist).
336 * <p>
337 * @param messageId The number of the message list.
338 * @return A POP3MessageInfo instance containing the number of the
339 * listed message and the size of the message in bytes. Returns
340 * null if the list attempt fails.
341 * @exception IOException If a network I/O error occurs in the process of
342 * sending the list command.
343 ***/
344 public POP3MessageInfo listMessage(int messageId) throws IOException
345 {
346 if (getState() != TRANSACTION_STATE)
347 return null;
348 if (sendCommand(POP3Command.LIST, Integer.toString(messageId))
349 != POP3Reply.OK)
350 return null;
351 return __parseStatus(_lastReplyLine.substring(3));
352 }
353
354
355 /***
356 * List all messages. A list attempt can only
357 * succeed if the client is in the
358 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
359 * . Returns an array of POP3MessageInfo instances,
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 * <p>
364 * @return An array of POP3MessageInfo instances representing all messages
365 * in the order they appear in the mailbox,
366 * each containing the number of a message and its size in bytes.
367 * If there are no messages, this method returns a zero length array.
368 * If the list attempt fails, it returns null.
369 * @exception IOException If a network I/O error occurs in the process of
370 * sending the list command.
371 ***/
372 public POP3MessageInfo[] listMessages() throws IOException
373 {
374 if (getState() != TRANSACTION_STATE)
375 return null;
376 if (sendCommand(POP3Command.LIST) != POP3Reply.OK)
377 return null;
378 getAdditionalReply();
379
380 // This could be a zero length array if no messages present
381 POP3MessageInfo[] messages = new POP3MessageInfo[_replyLines.size() - 2]; // skip first and last lines
382
383 ListIterator<String> en = _replyLines.listIterator(1); // Skip first line
384
385 // Fetch lines.
386 for (int line = 0; line < messages.length; line++)
387 messages[line] = __parseStatus(en.next());
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 if (getState() != TRANSACTION_STATE)
441 return null;
442 if (sendCommand(POP3Command.UIDL) != POP3Reply.OK)
443 return null;
444 getAdditionalReply();
445
446 // This could be a zero length array if no messages present
447 POP3MessageInfo[] messages = new POP3MessageInfo[_replyLines.size() - 2]; // skip first and last lines
448
449 ListIterator<String> en = _replyLines.listIterator(1); // skip first line
450
451 // Fetch lines.
452 for (int line = 0; line < messages.length; line++)
453 messages[line] = __parseUID(en.next());
454
455 return messages;
456 }
457
458
459 /**
460 * Retrieve a message from the POP3 server. A retrieve message attempt
461 * can only succeed if the client is in the
462 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
463 * <p>
464 * You must not issue any commands to the POP3 server (i.e., call any
465 * other methods) until you finish reading the message from the
466 * returned BufferedReader instance.
467 * The POP3 protocol uses the same stream for issuing commands as it does
468 * for returning results. Therefore the returned BufferedReader actually reads
469 * directly from the POP3 connection. After the end of message has been
470 * reached, new commands can be executed and their replies read. If
471 * you do not follow these requirements, your program will not work
472 * properly.
473 * <p>
474 * @param messageId The number of the message to fetch.
475 * @return A DotTerminatedMessageReader instance
476 * from which the entire message can be read.
477 * This can safely be cast to a {@link BufferedReader} in order to
478 * use the {@link BufferedReader#readLine()} method.
479 * Returns null if the retrieval attempt fails (e.g., if the specified
480 * message number does not exist).
481 * @exception IOException If a network I/O error occurs in the process of
482 * sending the retrieve message command.
483 */
484 public Reader retrieveMessage(int messageId) throws IOException
485 {
486 if (getState() != TRANSACTION_STATE) {
487 return null;
488 }
489 if (sendCommand(POP3Command.RETR, Integer.toString(messageId)) != POP3Reply.OK) {
490 return null;
491 }
492
493 return new DotTerminatedMessageReader(_reader);
494 }
495
496
497 /**
498 * Retrieve only the specified top number of lines of a message from the
499 * POP3 server. A retrieve top lines attempt
500 * can only succeed if the client is in the
501 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }
502 * <p>
503 * You must not issue any commands to the POP3 server (i.e., call any
504 * other methods) until you finish reading the message from the returned
505 * BufferedReader instance.
506 * The POP3 protocol uses the same stream for issuing commands as it does
507 * for returning results. Therefore the returned BufferedReader actually reads
508 * directly from the POP3 connection. After the end of message has been
509 * reached, new commands can be executed and their replies read. If
510 * you do not follow these requirements, your program will not work
511 * properly.
512 * <p>
513 * @param messageId The number of the message to fetch.
514 * @param numLines The top number of lines to fetch. This must be >= 0.
515 * @return A DotTerminatedMessageReader instance
516 * from which the specified top number of lines of the message can be
517 * read.
518 * This can safely be cast to a {@link BufferedReader} in order to
519 * use the {@link BufferedReader#readLine()} method.
520 * Returns null if the retrieval attempt fails (e.g., if the specified
521 * message number does not exist).
522 * @exception IOException If a network I/O error occurs in the process of
523 * sending the top command.
524 */
525 public Reader retrieveMessageTop(int messageId, int numLines)
526 throws IOException
527 {
528 if (numLines < 0 || getState() != TRANSACTION_STATE) {
529 return null;
530 }
531 if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " +
532 Integer.toString(numLines)) != POP3Reply.OK) {
533 return null;
534 }
535
536 return new DotTerminatedMessageReader(_reader);
537 }
538
539
540 }
541