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