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