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 *
050 *
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     *
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     * @throws IOException on error
182     ***/
183    public void getAdditionalReply() throws IOException
184    {
185        String line;
186
187        line = _reader.readLine();
188        while (line != null)
189        {
190            _replyLines.add(line);
191            if (line.equals(".")) {
192                break;
193            }
194            line = _reader.readLine();
195        }
196    }
197
198
199    /***
200     * Disconnects the client from the server, and sets the state to
201     * <code> DISCONNECTED_STATE </code>.  The reply text information
202     * from the last issued command is voided to allow garbage collection
203     * of the memory used to store that information.
204     *
205     * @throws IOException  If there is an error in disconnecting.
206     ***/
207    @Override
208    public void disconnect() throws IOException
209    {
210        super.disconnect();
211        _reader = null;
212        _writer = null;
213        _lastReplyLine = null;
214        _replyLines.clear();
215        setState(DISCONNECTED_STATE);
216    }
217
218
219    /***
220     * Sends a command an arguments to the server and returns the reply code.
221     *
222     * @param command  The POP3 command to send.
223     * @param args     The command arguments.
224     * @return  The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT).
225     * @throws IOException on error
226     ***/
227    public int sendCommand(String command, String args) throws IOException
228    {
229        if (_writer == null) {
230            throw new IllegalStateException("Socket is not connected");
231        }
232        StringBuilder __commandBuffer = new StringBuilder();
233        __commandBuffer.append(command);
234
235        if (args != null)
236        {
237            __commandBuffer.append(' ');
238            __commandBuffer.append(args);
239        }
240        __commandBuffer.append(SocketClient.NETASCII_EOL);
241
242        String message = __commandBuffer.toString();
243        _writer.write(message);
244        _writer.flush();
245
246        fireCommandSent(command, message);
247
248        __getReply();
249        return _replyCode;
250    }
251
252    /***
253     * Sends a command with no arguments to the server and returns the
254     * reply code.
255     *
256     * @param command  The POP3 command to send.
257     * @return  The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT).
258     * @throws IOException on error
259     ***/
260    public int sendCommand(String command) throws IOException
261    {
262        return sendCommand(command, null);
263    }
264
265    /***
266     * Sends a command an arguments to the server and returns the reply code.
267     *
268     * @param command  The POP3 command to send
269     *                  (one of the POP3Command constants).
270     * @param args     The command arguments.
271     * @return  The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT).
272     * @throws IOException on error
273     ***/
274    public int sendCommand(int command, String args) throws IOException
275    {
276        return sendCommand(POP3Command._commands[command], args);
277    }
278
279    /***
280     * Sends a command with no arguments to the server and returns the
281     * reply code.
282     *
283     * @param command  The POP3 command to send
284     *                  (one of the POP3Command constants).
285     * @return  The server reply code (either POP3Reply.OK, POP3Reply.ERROR or POP3Reply.OK_INT).
286     * @throws IOException on error
287     ***/
288    public int sendCommand(int command) throws IOException
289    {
290        return sendCommand(POP3Command._commands[command], null);
291    }
292
293
294    /***
295     * Returns an array of lines received as a reply to the last command
296     * sent to the server.  The lines have end of lines truncated.  If
297     * the reply is a single line, but its format ndicates it should be
298     * a multiline reply, then you must call
299     * {@link #getAdditionalReply  getAdditionalReply() } to
300     * fetch the rest of the reply, and then call <code>getReplyStrings</code>
301     * again.  You only have to worry about this if you are implementing
302     * your own client using the {@link #sendCommand  sendCommand } methods.
303     *
304     * @return The last server response.
305     ***/
306    public String[] getReplyStrings()
307    {
308        return _replyLines.toArray(new String[_replyLines.size()]);
309    }
310
311    /***
312     * Returns the reply to the last command sent to the server.
313     * The value is a single string containing all the reply lines including
314     * newlines.  If the reply is a single line, but its format ndicates it
315     * should be a multiline reply, then you must call
316     * {@link #getAdditionalReply  getAdditionalReply() } to
317     * fetch the rest of the reply, and then call <code>getReplyString</code>
318     * again.  You only have to worry about this if you are implementing
319     * your own client using the {@link #sendCommand  sendCommand } methods.
320     *
321     * @return The last server response.
322     ***/
323    public String getReplyString()
324    {
325        StringBuilder buffer = new StringBuilder(256);
326
327        for (String entry : _replyLines)
328        {
329            buffer.append(entry);
330            buffer.append(SocketClient.NETASCII_EOL);
331        }
332
333        return buffer.toString();
334    }
335
336    /**
337     * Removes a ProtocolCommandListener.
338     *
339     * Delegates this incorrectly named method - removeProtocolCommandistener (note the missing "L")- to
340     * the correct method {@link SocketClient#removeProtocolCommandListener}
341     * @param listener The ProtocolCommandListener to remove
342     */
343    public void removeProtocolCommandistener(org.apache.commons.net.ProtocolCommandListener listener){
344        removeProtocolCommandListener(listener);
345    }
346
347    /**
348     * Provide command support to super-class
349     */
350    @Override
351    protected ProtocolCommandSupport getCommandSupport() {
352        return _commandSupport_;
353    }
354}
355