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