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.Arrays; 025import java.util.ListIterator; 026import java.util.StringTokenizer; 027 028import org.apache.commons.net.io.DotTerminatedMessageReader; 029 030/** 031 * The POP3Client class implements the client side of the Internet POP3 Protocol defined in RFC 1939. All commands are supported, including the APOP command 032 * which requires MD5 encryption. See RFC 1939 for more details on the POP3 protocol. 033 * <p> 034 * Rather than list it separately for each method, we mention here that every method communicating with the server and throwing an IOException can also throw a 035 * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the 036 * reply received from the server deviates enough from the protocol specification that it cannot be interpreted in a useful manner despite attempts to be as 037 * lenient as possible. 038 * 039 * 040 * @see POP3MessageInfo 041 * @see org.apache.commons.net.io.DotTerminatedMessageReader 042 * @see org.apache.commons.net.MalformedServerReplyException 043 */ 044 045public class POP3Client extends POP3 { 046 047 private static POP3MessageInfo parseStatus(final String line) { 048 int num, size; 049 final StringTokenizer tokenizer; 050 051 tokenizer = new StringTokenizer(line); 052 053 if (!tokenizer.hasMoreElements()) { 054 return null; 055 } 056 057 num = size = 0; 058 059 try { 060 num = Integer.parseInt(tokenizer.nextToken()); 061 062 if (!tokenizer.hasMoreElements()) { 063 return null; 064 } 065 066 size = Integer.parseInt(tokenizer.nextToken()); 067 } catch (final NumberFormatException e) { 068 return null; 069 } 070 071 return new POP3MessageInfo(num, size); 072 } 073 074 private static POP3MessageInfo parseUID(String line) { 075 int num; 076 final StringTokenizer tokenizer; 077 078 tokenizer = new StringTokenizer(line); 079 080 if (!tokenizer.hasMoreElements()) { 081 return null; 082 } 083 084 num = 0; 085 086 try { 087 num = Integer.parseInt(tokenizer.nextToken()); 088 089 if (!tokenizer.hasMoreElements()) { 090 return null; 091 } 092 093 line = tokenizer.nextToken(); 094 } catch (final NumberFormatException e) { 095 return null; 096 } 097 098 return new POP3MessageInfo(num, line); 099 } 100 101 /** 102 * Send a CAPA command to the POP3 server. 103 * 104 * @return True if the command was successful, false if not. 105 * @throws IOException If a network I/O error occurs in the process of sending the CAPA command. 106 * @since 3.1 (was previously in ExtendedPOP3Client) 107 */ 108 public boolean capa() throws IOException { 109 if (sendCommand(POP3Command.CAPA) == POP3Reply.OK) { 110 getAdditionalReply(); 111 return true; 112 } 113 return false; 114 115 } 116 117 /** 118 * Delete a message from the POP3 server. The message is only marked for deletion by the server. If you decide to unmark the message, you must issue a 119 * {@link #reset reset } command. Messages marked for deletion are only deleted by the server on {@link #logout logout }. 120 * A deletion attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . 121 * 122 * @param messageId The message number to delete. 123 * @return True if the deletion attempt was successful, false if not. 124 * @throws IOException If a network I/O error occurs in the process of sending the delete command. 125 */ 126 public boolean deleteMessage(final int messageId) throws IOException { 127 if (getState() == TRANSACTION_STATE) { 128 return sendCommand(POP3Command.DELE, Integer.toString(messageId)) == POP3Reply.OK; 129 } 130 return false; 131 } 132 133 /** 134 * List an individual message. A list attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE 135 * TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of the listed message and the size of the message in bytes. Returns null 136 * if the list attempt fails (e.g., if the specified message number does not exist). 137 * 138 * @param messageId The number of the message list. 139 * @return A POP3MessageInfo instance containing the number of the listed message and the size of the message in bytes. Returns null if the list attempt 140 * fails. 141 * @throws IOException If a network I/O error occurs in the process of sending the list command. 142 */ 143 public POP3MessageInfo listMessage(final int messageId) throws IOException { 144 if (getState() != TRANSACTION_STATE) { 145 return null; 146 } 147 if (sendCommand(POP3Command.LIST, Integer.toString(messageId)) != POP3Reply.OK) { 148 return null; 149 } 150 return parseStatus(lastReplyLine.substring(3)); 151 } 152 153 /** 154 * List all messages. A list attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } 155 * . Returns an array of POP3MessageInfo instances, each containing the number of a message and its size in bytes. If there are no messages, this method 156 * returns a zero length array. If the list attempt fails, it returns null. 157 * 158 * @return An array of POP3MessageInfo instances representing all messages in the order they appear in the mailbox, each containing the number of a message 159 * and its size in bytes. If there are no messages, this method returns a zero length array. If the list attempt fails, it returns null. 160 * @throws IOException If a network I/O error occurs in the process of sending the list command. 161 */ 162 public POP3MessageInfo[] listMessages() throws IOException { 163 if (getState() != TRANSACTION_STATE) { 164 return null; 165 } 166 if (sendCommand(POP3Command.LIST) != POP3Reply.OK) { 167 return null; 168 } 169 getAdditionalReply(); 170 171 // This could be a zero length array if no messages present 172 final POP3MessageInfo[] messages = new POP3MessageInfo[replyLines.size() - 2]; // skip first and last lines 173 174 final ListIterator<String> en = replyLines.listIterator(1); // Skip first line 175 176 // Fetch lines. 177 Arrays.setAll(messages, i -> parseStatus(en.next())); 178 179 return messages; 180 } 181 182 /** 183 * List the unique identifier for a message. A list attempt can only succeed if the client is in the 184 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of the listed 185 * message and the unique identifier for that message. Returns null if the list attempt fails (e.g., if the specified message number does not exist). 186 * 187 * @param messageId The number of the message list. 188 * @return A POP3MessageInfo instance containing the number of the listed message and the unique identifier for that message. Returns null if the list 189 * attempt fails. 190 * @throws IOException If a network I/O error occurs in the process of sending the list unique identifier command. 191 */ 192 public POP3MessageInfo listUniqueIdentifier(final int messageId) throws IOException { 193 if (getState() != TRANSACTION_STATE) { 194 return null; 195 } 196 if (sendCommand(POP3Command.UIDL, Integer.toString(messageId)) != POP3Reply.OK) { 197 return null; 198 } 199 return parseUID(lastReplyLine.substring(3)); 200 } 201 202 /** 203 * List the unique identifiers for all messages. A list attempt can only succeed if the client is in the 204 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . Returns an array of POP3MessageInfo instances, each containing the number 205 * of a message and its unique identifier. If there are no messages, this method returns a zero length array. If the list attempt fails, it returns null. 206 * 207 * @return An array of POP3MessageInfo instances representing all messages in the order they appear in the mailbox, each containing the number of a message 208 * and its unique identifier If there are no messages, this method returns a zero length array. If the list attempt fails, it returns null. 209 * @throws IOException If a network I/O error occurs in the process of sending the list unique identifier command. 210 */ 211 public POP3MessageInfo[] listUniqueIdentifiers() throws IOException { 212 if (getState() != TRANSACTION_STATE) { 213 return null; 214 } 215 if (sendCommand(POP3Command.UIDL) != POP3Reply.OK) { 216 return null; 217 } 218 getAdditionalReply(); 219 220 // This could be a zero length array if no messages present 221 final POP3MessageInfo[] messages = new POP3MessageInfo[replyLines.size() - 2]; // skip first and last lines 222 223 final ListIterator<String> en = replyLines.listIterator(1); // skip first line 224 225 // Fetch lines. 226 Arrays.setAll(messages, i -> parseUID(en.next())); 227 228 return messages; 229 } 230 231 /** 232 * Login to the POP3 server with the given user and password. You must first connect to the server with 233 * {@link org.apache.commons.net.SocketClient#connect connect } before attempting to log in. A login attempt is only valid if the client is in the 234 * {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }. After logging in, the client enters the 235 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }. 236 * 237 * @param user The account name being logged in to. 238 * @param password The plain text password of the account. 239 * @return True if the login attempt was successful, false if not. 240 * @throws IOException If a network I/O error occurs in the process of logging in. 241 */ 242 public boolean login(final String user, final String password) throws IOException { 243 if (getState() != AUTHORIZATION_STATE) { 244 return false; 245 } 246 247 if (sendCommand(POP3Command.USER, user) != POP3Reply.OK) { 248 return false; 249 } 250 251 if (sendCommand(POP3Command.PASS, password) != POP3Reply.OK) { 252 return false; 253 } 254 255 setState(TRANSACTION_STATE); 256 257 return true; 258 } 259 260 /** 261 * Login to the POP3 server with the given username and authentication information. Use this method when connecting to a server requiring authentication 262 * using the APOP command. Because the timestamp produced in the greeting banner varies from server to server, it is not possible to consistently extract 263 * the information. Therefore, after connecting to the server, you must call {@link org.apache.commons.net.pop3.POP3#getReplyString getReplyString } and 264 * parse out the timestamp information yourself. 265 * <p> 266 * You must first connect to the server with {@link org.apache.commons.net.SocketClient#connect connect } before attempting to log in. A login attempt is 267 * only valid if the client is in the {@link org.apache.commons.net.pop3.POP3#AUTHORIZATION_STATE AUTHORIZATION_STATE }. After logging in, the client 268 * enters the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE }. After connecting, you must parse out the server specific 269 * information to use as a timestamp, and pass that information to this method. The secret is a shared secret known to you and the server. See RFC 1939 for 270 * more details regarding the APOP command. 271 * 272 * @param user The account name being logged in to. 273 * @param timestamp The timestamp string to combine with the secret. 274 * @param secret The shared secret which produces the MD5 digest when combined with the timestamp. 275 * @return True if the login attempt was successful, false if not. 276 * @throws IOException If a network I/O error occurs in the process of logging in. 277 * @throws NoSuchAlgorithmException If the MD5 encryption algorithm cannot be instantiated by the Java runtime system. 278 */ 279 public boolean login(final String user, String timestamp, final String secret) throws IOException, NoSuchAlgorithmException { 280 int i; 281 final byte[] digest; 282 final StringBuilder buffer; 283 final StringBuilder digestBuffer; 284 final MessageDigest md5; 285 286 if (getState() != AUTHORIZATION_STATE) { 287 return false; 288 } 289 290 md5 = MessageDigest.getInstance("MD5"); 291 timestamp += secret; 292 digest = md5.digest(timestamp.getBytes(getCharset())); 293 digestBuffer = new StringBuilder(128); 294 295 for (i = 0; i < digest.length; i++) { 296 final int digit = digest[i] & 0xff; 297 if (digit <= 15) { // Add leading zero if necessary (NET-351) 298 digestBuffer.append("0"); 299 } 300 digestBuffer.append(Integer.toHexString(digit)); 301 } 302 303 buffer = new StringBuilder(256); 304 buffer.append(user); 305 buffer.append(' '); 306 buffer.append(digestBuffer.toString()); 307 308 if (sendCommand(POP3Command.APOP, buffer.toString()) != POP3Reply.OK) { 309 return false; 310 } 311 312 setState(TRANSACTION_STATE); 313 314 return true; 315 } 316 317 /** 318 * Logout of the POP3 server. To fully disconnect from the server you must call {@link org.apache.commons.net.pop3.POP3#disconnect disconnect }. A logout 319 * attempt is valid in any state. If the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } , it enters the 320 * {@link org.apache.commons.net.pop3.POP3#UPDATE_STATE UPDATE_STATE } on a successful logout. 321 * 322 * @return True if the logout attempt was successful, false if not. 323 * @throws IOException If a network I/O error occurs in the process of logging out. 324 */ 325 public boolean logout() throws IOException { 326 if (getState() == TRANSACTION_STATE) { 327 setState(UPDATE_STATE); 328 } 329 sendCommand(POP3Command.QUIT); 330 return replyCode == POP3Reply.OK; 331 } 332 333 /** 334 * Send a NOOP command to the POP3 server. This is useful for keeping a connection alive since most POP3 servers will time out after 10 minutes of 335 * inactivity. A noop attempt will only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . 336 * 337 * @return True if the noop attempt was successful, false if not. 338 * @throws IOException If a network I/O error occurs in the process of sending the NOOP command. 339 */ 340 public boolean noop() throws IOException { 341 if (getState() == TRANSACTION_STATE) { 342 return sendCommand(POP3Command.NOOP) == POP3Reply.OK; 343 } 344 return false; 345 } 346 347 /** 348 * Reset the POP3 session. This is useful for undoing any message deletions that may have been performed. A reset attempt can only succeed if the client is 349 * in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } . 350 * 351 * @return True if the reset attempt was successful, false if not. 352 * @throws IOException If a network I/O error occurs in the process of sending the reset command. 353 */ 354 public boolean reset() throws IOException { 355 if (getState() == TRANSACTION_STATE) { 356 return sendCommand(POP3Command.RSET) == POP3Reply.OK; 357 } 358 return false; 359 } 360 361 /** 362 * Retrieve a message from the POP3 server. A retrieve message attempt can only succeed if the client is in the 363 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } 364 * <p> 365 * You must not issue any commands to the POP3 server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader 366 * instance. The POP3 protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader actually 367 * reads directly from the POP3 connection. After the end of message has been reached, new commands can be executed and their replies read. If you do not 368 * follow these requirements, your program will not work properly. 369 * 370 * @param messageId The number of the message to fetch. 371 * @return A DotTerminatedMessageReader instance from which the entire message can be read. This can safely be cast to a {@link java.io.BufferedReader 372 * BufferedReader} in order to use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method. Returns null if the retrieval 373 * attempt fails (e.g., if the specified message number does not exist). 374 * @throws IOException If a network I/O error occurs in the process of sending the retrieve message command. 375 */ 376 public Reader retrieveMessage(final int messageId) throws IOException { 377 if (getState() != TRANSACTION_STATE) { 378 return null; 379 } 380 if (sendCommand(POP3Command.RETR, Integer.toString(messageId)) != POP3Reply.OK) { 381 return null; 382 } 383 384 return new DotTerminatedMessageReader(reader); 385 } 386 387 /** 388 * Retrieve only the specified top number of lines of a message from the POP3 server. A retrieve top lines attempt can only succeed if the client is in the 389 * {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE TRANSACTION_STATE } 390 * <p> 391 * You must not issue any commands to the POP3 server (i.e., call any other methods) until you finish reading the message from the returned BufferedReader 392 * instance. The POP3 protocol uses the same stream for issuing commands as it does for returning results. Therefore, the returned BufferedReader 393 * actually reads directly from the POP3 connection. After the end of message has been reached, new commands can be executed and their replies read. 394 * If you do not follow these requirements, your program will not work properly. 395 * 396 * @param messageId The number of the message to fetch. 397 * @param numLines The top number of lines to fetch. This must be >= 0. 398 * @return A DotTerminatedMessageReader instance from which the specified top number of lines of the message can be read. This can safely be cast to a 399 * {@link java.io.BufferedReader BufferedReader} in order to use the {@link java.io.BufferedReader#readLine() BufferedReader#readLine()} method. 400 * Returns null if the retrieval attempt fails (e.g., if the specified message number does not exist). 401 * @throws IOException If a network I/O error occurs in the process of sending the top command. 402 */ 403 public Reader retrieveMessageTop(final int messageId, final int numLines) throws IOException { 404 if (numLines < 0 || getState() != TRANSACTION_STATE) { 405 return null; 406 } 407 if (sendCommand(POP3Command.TOP, Integer.toString(messageId) + " " + Integer.toString(numLines)) != POP3Reply.OK) { 408 return null; 409 } 410 411 return new DotTerminatedMessageReader(reader); 412 } 413 414 /** 415 * Gets the mailbox status. A status attempt can only succeed if the client is in the {@link org.apache.commons.net.pop3.POP3#TRANSACTION_STATE 416 * TRANSACTION_STATE } . Returns a POP3MessageInfo instance containing the number of messages in the mailbox and the total size of the messages in bytes. 417 * Returns null if the status the attempt fails. 418 * 419 * @return A POP3MessageInfo instance containing the number of messages in the mailbox and the total size of the messages in bytes. Returns null if the 420 * status the attempt fails. 421 * @throws IOException If a network I/O error occurs in the process of sending the status command. 422 */ 423 public POP3MessageInfo status() throws IOException { 424 if (getState() != TRANSACTION_STATE) { 425 return null; 426 } 427 if (sendCommand(POP3Command.STAT) != POP3Reply.OK) { 428 return null; 429 } 430 return parseStatus(lastReplyLine.substring(3)); 431 } 432 433}