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