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