1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.net.pop3;
19
20 import java.io.BufferedReader;
21 import java.io.BufferedWriter;
22 import java.io.EOFException;
23 import java.io.IOException;
24 import java.io.InputStreamReader;
25 import java.io.OutputStreamWriter;
26 import java.nio.charset.Charset;
27 import java.nio.charset.StandardCharsets;
28 import java.util.ArrayList;
29 import java.util.List;
30
31 import org.apache.commons.net.MalformedServerReplyException;
32 import org.apache.commons.net.ProtocolCommandSupport;
33 import org.apache.commons.net.SocketClient;
34 import org.apache.commons.net.io.CRLFLineReader;
35 import org.apache.commons.net.util.NetConstants;
36
37 /**
38 * 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
39 * need to perform your own implementation, you should use {@link org.apache.commons.net.pop3.POP3Client}.
40 * <p>
41 * 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
42 * {@link org.apache.commons.net.MalformedServerReplyException} , which is a subclass of IOException. A MalformedServerReplyException will be thrown when the
43 * 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
44 * lenient as possible.
45 *
46 *
47 * @see POP3Client
48 * @see org.apache.commons.net.MalformedServerReplyException
49 */
50
51 public class POP3 extends SocketClient {
52
53 /** The default POP3 port. Set to 110 according to RFC 1288. */
54 public static final int DEFAULT_PORT = 110;
55
56 /**
57 * A constant representing the state where the client is not yet connected to a POP3 server.
58 */
59 public static final int DISCONNECTED_STATE = -1;
60
61 /** A constant representing the POP3 authorization state. */
62 public static final int AUTHORIZATION_STATE = 0;
63
64 /** A constant representing the POP3 transaction state. */
65 public static final int TRANSACTION_STATE = 1;
66
67 /** A constant representing the POP3 update state. */
68 public static final int UPDATE_STATE = 2;
69
70 static final String OK = "+OK";
71 // The reply indicating intermediate response to a command.
72 static final String OK_INT = "+ ";
73 static final String ERROR = "-ERR";
74
75 // We have to ensure that the protocol communication is in ASCII,
76 // but we use ISO-8859-1 just in case 8-bit characters cross
77 // the wire.
78 static final Charset DEFAULT_ENCODING = StandardCharsets.ISO_8859_1;
79
80 private int popState;
81 BufferedWriter writer;
82
83 BufferedReader reader;
84 int replyCode;
85 String lastReplyLine;
86 List<String> replyLines;
87
88 /**
89 * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners and the firing of ProtocolCommandEvents.
90 */
91 protected ProtocolCommandSupport _commandSupport_;
92
93 /**
94 * The default POP3Client constructor. Initializes the state to {@code DISCONNECTED_STATE}.
95 */
96 public POP3() {
97 setDefaultPort(DEFAULT_PORT);
98 popState = DISCONNECTED_STATE;
99 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 }