View Javadoc
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    *      http://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      /** The default POP3 port. Set to 110 according to RFC 1288. */
53      public static final int DEFAULT_PORT = 110;
54      /**
55       * A constant representing the state where the client is not yet connected to a POP3 server.
56       */
57      public static final int DISCONNECTED_STATE = -1;
58      /** A constant representing the POP3 authorization state. */
59      public static final int AUTHORIZATION_STATE = 0;
60      /** A constant representing the POP3 transaction state. */
61      public static final int TRANSACTION_STATE = 1;
62      /** A constant representing the POP3 update state. */
63      public static final int UPDATE_STATE = 2;
64  
65      static final String OK = "+OK";
66      // The reply indicating intermediate response to a command.
67      static final String OK_INT = "+ ";
68      static final String ERROR = "-ERR";
69  
70      // We have to ensure that the protocol communication is in ASCII,
71      // but we use ISO-8859-1 just in case 8-bit characters cross
72      // the wire.
73      static final Charset DEFAULT_ENCODING = StandardCharsets.ISO_8859_1;
74  
75      private int popState;
76      BufferedWriter writer;
77  
78      BufferedReader reader;
79      int replyCode;
80      String lastReplyLine;
81      List<String> replyLines;
82  
83      /**
84       * A ProtocolCommandSupport object used to manage the registering of ProtocolCommandListeners and the firing of ProtocolCommandEvents.
85       */
86      protected ProtocolCommandSupport _commandSupport_;
87  
88      /**
89       * The default POP3Client constructor. Initializes the state to <code>DISCONNECTED_STATE</code>.
90       */
91      public POP3() {
92          setDefaultPort(DEFAULT_PORT);
93          popState = DISCONNECTED_STATE;
94          reader = null;
95          writer = null;
96          replyLines = new ArrayList<>();
97          _commandSupport_ = new ProtocolCommandSupport(this);
98      }
99  
100     /**
101      * Performs connection initialization and sets state to <code> AUTHORIZATION_STATE </code>.
102      */
103     @Override
104     protected void _connectAction_() throws IOException {
105         super._connectAction_();
106         reader = new CRLFLineReader(new InputStreamReader(_input_, DEFAULT_ENCODING));
107         writer = new BufferedWriter(new OutputStreamWriter(_output_, DEFAULT_ENCODING));
108         getReply();
109         setState(AUTHORIZATION_STATE);
110     }
111 
112     /**
113      * Disconnects the client from the server, and sets the state to <code> DISCONNECTED_STATE </code>. The reply text information from the last issued command
114      * is voided to allow garbage collection of the memory used to store that information.
115      *
116      * @throws IOException If there is an error in disconnecting.
117      */
118     @Override
119     public void disconnect() throws IOException {
120         super.disconnect();
121         reader = null;
122         writer = null;
123         lastReplyLine = null;
124         replyLines.clear();
125         setState(DISCONNECTED_STATE);
126     }
127 
128     /**
129      * Retrieves the additional lines of a multi-line server reply.
130      *
131      * @throws IOException on error
132      */
133     public void getAdditionalReply() throws IOException {
134         String line;
135 
136         line = reader.readLine();
137         while (line != null) {
138             replyLines.add(line);
139             if (line.equals(".")) {
140                 break;
141             }
142             line = reader.readLine();
143         }
144     }
145 
146     /**
147      * Provide command support to super-class
148      */
149     @Override
150     protected ProtocolCommandSupport getCommandSupport() {
151         return _commandSupport_;
152     }
153 
154     private void getReply() throws IOException {
155         final String line;
156 
157         replyLines.clear();
158         line = reader.readLine();
159 
160         if (line == null) {
161             throw new EOFException("Connection closed without indication.");
162         }
163 
164         if (line.startsWith(OK)) {
165             replyCode = POP3Reply.OK;
166         } else if (line.startsWith(ERROR)) {
167             replyCode = POP3Reply.ERROR;
168         } else if (line.startsWith(OK_INT)) {
169             replyCode = POP3Reply.OK_INT;
170         } else {
171             throw new MalformedServerReplyException("Received invalid POP3 protocol response from server." + line);
172         }
173 
174         replyLines.add(line);
175         lastReplyLine = line;
176 
177         fireReplyReceived(replyCode, getReplyString());
178     }
179 
180     /**
181      * Returns 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
182      * a single line, but its format ndicates it should be a multiline reply, then you must call {@link #getAdditionalReply getAdditionalReply() } to fetch the
183      * rest of the reply, and then call <code>getReplyString</code> again. You only have to worry about this if you are implementing your own client using the
184      * {@link #sendCommand sendCommand } methods.
185      *
186      * @return The last server response.
187      */
188     public String getReplyString() {
189         final StringBuilder buffer = new StringBuilder(256);
190 
191         for (final String entry : replyLines) {
192             buffer.append(entry);
193             buffer.append(SocketClient.NETASCII_EOL);
194         }
195 
196         return buffer.toString();
197     }
198 
199     /**
200      * Returns 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
201      * line, but its format ndicates it should be a multiline reply, then you must call {@link #getAdditionalReply getAdditionalReply() } to fetch the rest of
202      * the reply, and then call <code>getReplyStrings</code> again. You only have to worry about this if you are implementing your own client using the
203      * {@link #sendCommand sendCommand } methods.
204      *
205      * @return The last server response.
206      */
207     public String[] getReplyStrings() {
208         return replyLines.toArray(NetConstants.EMPTY_STRING_ARRAY);
209     }
210 
211     /**
212      * Returns the current POP3 client state.
213      *
214      * @return The current POP3 client state.
215      */
216     public int getState() {
217         return popState;
218     }
219 
220     /**
221      * Removes a ProtocolCommandListener.
222      *
223      * Delegates this incorrectly named method - removeProtocolCommandistener (note the missing "L")- to the correct method
224      * {@link SocketClient#removeProtocolCommandListener}
225      *
226      * @param listener The ProtocolCommandListener to remove
227      */
228     public void removeProtocolCommandistener(final org.apache.commons.net.ProtocolCommandListener listener) {
229         removeProtocolCommandListener(listener);
230     }
231 
232     /**
233      * Sends a command with no arguments to the server and returns the reply code.
234      *
235      * @param command The POP3 command to send (one of the POP3Command constants).
236      * @return The server reply code (either {@code POP3Reply.OK}, {@code POP3Reply.ERROR} or {@code POP3Reply.OK_INT}).
237      * @throws IOException on error
238      */
239     public int sendCommand(final int command) throws IOException {
240         return sendCommand(POP3Command.commands[command], null);
241     }
242 
243     /**
244      * Sends a command an arguments to the server and returns the reply code.
245      *
246      * @param command The POP3 command to send (one of the POP3Command constants).
247      * @param args    The command arguments.
248      * @return The server reply code (either {@code POP3Reply.OK}, {@code POP3Reply.ERROR} or {@code POP3Reply.OK_INT}).
249      * @throws IOException on error
250      */
251     public int sendCommand(final int command, final String args) throws IOException {
252         return sendCommand(POP3Command.commands[command], args);
253     }
254 
255     /**
256      * Sends a command with no arguments to the server and returns the reply code.
257      *
258      * @param command The POP3 command to send.
259      * @return The server reply code (either {@code POP3Reply.OK}, {@code POP3Reply.ERROR} or {@code POP3Reply.OK_INT}).
260      * @throws IOException on error
261      */
262     public int sendCommand(final String command) throws IOException {
263         return sendCommand(command, null);
264     }
265 
266     /**
267      * Sends a command an arguments to the server and returns the reply code.
268      *
269      * @param command The POP3 command to send.
270      * @param args    The command arguments.
271      * @return The server reply code (either {@code POP3Reply.OK}, {@code POP3Reply.ERROR} or {@code POP3Reply.OK_INT}).
272      * @throws IOException on error
273      */
274     public int sendCommand(final String command, final String args) throws IOException {
275         if (writer == null) {
276             throw new IllegalStateException("Socket is not connected");
277         }
278         final StringBuilder __commandBuffer = new StringBuilder();
279         __commandBuffer.append(command);
280 
281         if (args != null) {
282             __commandBuffer.append(' ');
283             __commandBuffer.append(args);
284         }
285         __commandBuffer.append(SocketClient.NETASCII_EOL);
286 
287         final String message = __commandBuffer.toString();
288         writer.write(message);
289         writer.flush();
290 
291         fireCommandSent(command, message);
292 
293         getReply();
294         return replyCode;
295     }
296 
297     /**
298      * Set the internal POP3 state.
299      *
300      * @param state the new state. This must be one of the <code>_STATE</code> constants.
301      */
302     public void setState(final int state) {
303         popState = state;
304     }
305 }