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 018package org.apache.commons.net.pop3; 019 020import java.io.IOException; 021import java.io.Reader; 022import java.security.MessageDigest; 023import java.security.NoSuchAlgorithmException; 024import java.util.ListIterator; 025import java.util.StringTokenizer; 026 027import 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 051public 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