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