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