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