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.BufferedReader; 021import java.io.BufferedWriter; 022import java.io.EOFException; 023import java.io.IOException; 024import java.io.InputStreamReader; 025import java.io.OutputStreamWriter; 026import java.nio.charset.Charset; 027import java.nio.charset.StandardCharsets; 028import java.util.ArrayList; 029import java.util.List; 030 031import org.apache.commons.net.MalformedServerReplyException; 032import org.apache.commons.net.ProtocolCommandSupport; 033import org.apache.commons.net.SocketClient; 034import org.apache.commons.net.io.CRLFLineReader; 035import org.apache.commons.net.util.NetConstants; 036 037/** 038 * The POP3 class is not meant to be used by itself and is provided 039 * only so that you may easily implement your own POP3 client if 040 * you so desire. If you have no need to perform your own implementation, 041 * you should use {@link org.apache.commons.net.pop3.POP3Client}. 042 * <p> 043 * Rather than list it separately for each method, we mention here that 044 * every method communicating with the server and throwing an IOException 045 * can also throw a 046 * {@link org.apache.commons.net.MalformedServerReplyException} 047 * , which is a subclass 048 * of IOException. A MalformedServerReplyException will be thrown when 049 * the reply received from the server deviates enough from the protocol 050 * specification that it cannot be interpreted in a useful manner despite 051 * attempts to be as lenient as possible. 052 * 053 * 054 * @see POP3Client 055 * @see org.apache.commons.net.MalformedServerReplyException 056 */ 057 058public class POP3 extends SocketClient 059{ 060 /** The default POP3 port. Set to 110 according to RFC 1288. */ 061 public static final int DEFAULT_PORT = 110; 062 /** 063 * A constant representing the state where the client is not yet connected 064 * to a POP3 server. 065 */ 066 public static final int DISCONNECTED_STATE = -1; 067 /** A constant representing the POP3 authorization state. */ 068 public static final int AUTHORIZATION_STATE = 0; 069 /** A constant representing the POP3 transaction state. */ 070 public static final int TRANSACTION_STATE = 1; 071 /** A constant representing the POP3 update state. */ 072 public static final int UPDATE_STATE = 2; 073 074 static final String OK = "+OK"; 075 // The reply indicating intermediate response to a command. 076 static final String OK_INT = "+ "; 077 static final String ERROR = "-ERR"; 078 079 // We have to ensure that the protocol communication is in ASCII 080 // but we use ISO-8859-1 just in case 8-bit characters cross 081 // the wire. 082 static final Charset DEFAULT_ENCODING = StandardCharsets.ISO_8859_1; 083 084 private int popState; 085 BufferedWriter writer; 086 087 BufferedReader reader; 088 int replyCode; 089 String lastReplyLine; 090 List<String> replyLines; 091 092 /** 093 * A ProtocolCommandSupport object used to manage the registering of 094 * ProtocolCommandListeners and the firing of ProtocolCommandEvents. 095 */ 096 protected ProtocolCommandSupport _commandSupport_; 097 098 /** 099 * The default POP3Client constructor. Initializes the state 100 * to <code>DISCONNECTED_STATE</code>. 101 */ 102 public POP3() 103 { 104 setDefaultPort(DEFAULT_PORT); 105 popState = DISCONNECTED_STATE; 106 reader = null; 107 writer = null; 108 replyLines = new ArrayList<>(); 109 _commandSupport_ = new ProtocolCommandSupport(this); 110 } 111 112 private void getReply() throws IOException 113 { 114 final String line; 115 116 replyLines.clear(); 117 line = reader.readLine(); 118 119 if (line == null) { 120 throw new EOFException("Connection closed without indication."); 121 } 122 123 if (line.startsWith(OK)) { 124 replyCode = POP3Reply.OK; 125 } else if (line.startsWith(ERROR)) { 126 replyCode = POP3Reply.ERROR; 127 } else if (line.startsWith(OK_INT)) { 128 replyCode = POP3Reply.OK_INT; 129 } else { 130 throw new 131 MalformedServerReplyException( 132 "Received invalid POP3 protocol response from server." + line); 133 } 134 135 replyLines.add(line); 136 lastReplyLine = line; 137 138 fireReplyReceived(replyCode, getReplyString()); 139 } 140 141 142 /** 143 * Performs connection initialization and sets state to 144 * <code> AUTHORIZATION_STATE </code>. 145 */ 146 @Override 147 protected void _connectAction_() throws IOException 148 { 149 super._connectAction_(); 150 reader = 151 new CRLFLineReader(new InputStreamReader(_input_, 152 DEFAULT_ENCODING)); 153 writer = 154 new BufferedWriter(new OutputStreamWriter(_output_, 155 DEFAULT_ENCODING)); 156 getReply(); 157 setState(AUTHORIZATION_STATE); 158 } 159 160 161 /** 162 * Set the internal POP3 state. 163 * @param state the new state. This must be one of the <code>_STATE</code> constants. 164 */ 165 public void setState(final int state) 166 { 167 popState = state; 168 } 169 170 171 /** 172 * Returns the current POP3 client state. 173 * 174 * @return The current POP3 client state. 175 */ 176 public int getState() 177 { 178 return popState; 179 } 180 181 182 /** 183 * Retrieves the additional lines of a multi-line server reply. 184 * @throws IOException on error 185 */ 186 public void getAdditionalReply() throws IOException 187 { 188 String line; 189 190 line = reader.readLine(); 191 while (line != null) 192 { 193 replyLines.add(line); 194 if (line.equals(".")) { 195 break; 196 } 197 line = reader.readLine(); 198 } 199 } 200 201 202 /** 203 * Disconnects the client from the server, and sets the state to 204 * <code> DISCONNECTED_STATE </code>. The reply text information 205 * from the last issued command is voided to allow garbage collection 206 * of the memory used to store that information. 207 * 208 * @throws IOException If there is an error in disconnecting. 209 */ 210 @Override 211 public void disconnect() throws IOException 212 { 213 super.disconnect(); 214 reader = null; 215 writer = null; 216 lastReplyLine = null; 217 replyLines.clear(); 218 setState(DISCONNECTED_STATE); 219 } 220 221 222 /** 223 * Sends a command an arguments to the server and returns the reply code. 224 * 225 * @param command The POP3 command to send. 226 * @param args The command arguments. 227 * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). 228 * @throws IOException on error 229 */ 230 public int sendCommand(final String command, final String args) throws IOException 231 { 232 if (writer == null) { 233 throw new IllegalStateException("Socket is not connected"); 234 } 235 final StringBuilder __commandBuffer = new StringBuilder(); 236 __commandBuffer.append(command); 237 238 if (args != null) 239 { 240 __commandBuffer.append(' '); 241 __commandBuffer.append(args); 242 } 243 __commandBuffer.append(SocketClient.NETASCII_EOL); 244 245 final String message = __commandBuffer.toString(); 246 writer.write(message); 247 writer.flush(); 248 249 fireCommandSent(command, message); 250 251 getReply(); 252 return replyCode; 253 } 254 255 /** 256 * Sends a command with no arguments to the server and returns the 257 * reply code. 258 * 259 * @param command The POP3 command to send. 260 * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). 261 * @throws IOException on error 262 */ 263 public int sendCommand(final String command) throws IOException 264 { 265 return sendCommand(command, null); 266 } 267 268 /** 269 * Sends a command an arguments to the server and returns the reply code. 270 * 271 * @param command The POP3 command to send 272 * (one of the POP3Command constants). 273 * @param args The command arguments. 274 * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). 275 * @throws IOException on error 276 */ 277 public int sendCommand(final int command, final String args) throws IOException 278 { 279 return sendCommand(POP3Command.commands[command], args); 280 } 281 282 /** 283 * Sends a command with no arguments to the server and returns the 284 * reply code. 285 * 286 * @param command The POP3 command to send 287 * (one of the POP3Command constants). 288 * @return The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT). 289 * @throws IOException on error 290 */ 291 public int sendCommand(final int command) throws IOException 292 { 293 return sendCommand(POP3Command.commands[command], null); 294 } 295 296 297 /** 298 * Returns an array of lines received as a reply to the last command 299 * sent to the server. The lines have end of lines truncated. If 300 * the reply is a single line, but its format ndicates it should be 301 * a multiline reply, then you must call 302 * {@link #getAdditionalReply getAdditionalReply() } to 303 * fetch the rest of the reply, and then call <code>getReplyStrings</code> 304 * again. You only have to worry about this if you are implementing 305 * your own client using the {@link #sendCommand sendCommand } methods. 306 * 307 * @return The last server response. 308 */ 309 public String[] getReplyStrings() 310 { 311 return replyLines.toArray(NetConstants.EMPTY_STRING_ARRAY); 312 } 313 314 /** 315 * Returns the reply to the last command sent to the server. 316 * The value is a single string containing all the reply lines including 317 * newlines. If the reply is a single line, but its format ndicates it 318 * should be a multiline reply, then you must call 319 * {@link #getAdditionalReply getAdditionalReply() } to 320 * fetch the rest of the reply, and then call <code>getReplyString</code> 321 * again. You only have to worry about this if you are implementing 322 * your own client using the {@link #sendCommand sendCommand } methods. 323 * 324 * @return The last server response. 325 */ 326 public String getReplyString() 327 { 328 final StringBuilder buffer = new StringBuilder(256); 329 330 for (final String entry : replyLines) 331 { 332 buffer.append(entry); 333 buffer.append(SocketClient.NETASCII_EOL); 334 } 335 336 return buffer.toString(); 337 } 338 339 /** 340 * Removes a ProtocolCommandListener. 341 * 342 * Delegates this incorrectly named method - removeProtocolCommandistener (note the missing "L")- to 343 * the correct method {@link SocketClient#removeProtocolCommandListener} 344 * @param listener The ProtocolCommandListener to remove 345 */ 346 public void removeProtocolCommandistener(final org.apache.commons.net.ProtocolCommandListener listener){ 347 removeProtocolCommandListener(listener); 348 } 349 350 /** 351 * Provide command support to super-class 352 */ 353 @Override 354 protected ProtocolCommandSupport getCommandSupport() { 355 return _commandSupport_; 356 } 357} 358