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.Enumeration;
027import java.util.Vector;
028
029import org.apache.commons.net.MalformedServerReplyException;
030import org.apache.commons.net.ProtocolCommandListener;
031import org.apache.commons.net.ProtocolCommandSupport;
032import org.apache.commons.net.SocketClient;
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 * @author Daniel F. Savarese
052 * @see POP3Client
053 * @see org.apache.commons.net.MalformedServerReplyException
054 ***/
055
056public class POP3 extends SocketClient
057{
058    /*** The default POP3 port.  Set to 110 according to RFC 1288. ***/
059    public static final int DEFAULT_PORT = 110;
060    /***
061     * A constant representing the state where the client is not yet connected
062     * to a POP3 server.
063     ***/
064    public static final int DISCONNECTED_STATE = -1;
065    /***  A constant representing the POP3 authorization state. ***/
066    public static final int AUTHORIZATION_STATE = 0;
067    /***  A constant representing the POP3 transaction state. ***/
068    public static final int TRANSACTION_STATE = 1;
069    /***  A constant representing the POP3 update state. ***/
070    public static final int UPDATE_STATE = 2;
071
072    static final String _OK = "+OK";
073    static final String _ERROR = "-ERR";
074
075    // We have to ensure that the protocol communication is in ASCII
076    // but we use ISO-8859-1 just in case 8-bit characters cross
077    // the wire.
078    private static final String __DEFAULT_ENCODING = "ISO-8859-1";
079
080    private int __popState;
081    private BufferedWriter __writer;
082    private StringBuffer __commandBuffer;
083
084    BufferedReader _reader;
085    int _replyCode;
086    String _lastReplyLine;
087    Vector<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        __commandBuffer = new StringBuffer();
103        __popState = DISCONNECTED_STATE;
104        _reader = null;
105        __writer = null;
106        _replyLines = new Vector<String>();
107        _commandSupport_ = new ProtocolCommandSupport(this);
108    }
109
110    private void __getReply() throws IOException
111    {
112        String line;
113
114        _replyLines.setSize(0);
115        line = _reader.readLine();
116
117        if (line == null)
118            throw new EOFException("Connection closed without indication.");
119
120        if (line.startsWith(_OK))
121            _replyCode = POP3Reply.OK;
122        else if (line.startsWith(_ERROR))
123            _replyCode = POP3Reply.ERROR;
124        else
125            throw new
126            MalformedServerReplyException(
127                "Received invalid POP3 protocol response from server.");
128
129        _replyLines.addElement(line);
130        _lastReplyLine = line;
131
132        if (_commandSupport_.getListenerCount() > 0)
133            _commandSupport_.fireReplyReceived(_replyCode, getReplyString());
134    }
135
136
137    /***
138     * Performs connection initialization and sets state to
139     * <code> AUTHORIZATION_STATE </code>.
140     ***/
141    @Override
142    protected void _connectAction_() throws IOException
143    {
144        super._connectAction_();
145        _reader =
146          new BufferedReader(new InputStreamReader(_input_,
147                                                   __DEFAULT_ENCODING));
148        __writer =
149          new BufferedWriter(new OutputStreamWriter(_output_,
150                                                    __DEFAULT_ENCODING));
151        __getReply();
152        setState(AUTHORIZATION_STATE);
153    }
154
155
156    /***
157     * Adds a ProtocolCommandListener.  Delegates this task to
158     * {@link #_commandSupport_  _commandSupport_ }.
159     * <p>
160     * @param listener  The ProtocolCommandListener to add.
161     ***/
162    public void addProtocolCommandListener(ProtocolCommandListener listener)
163    {
164        _commandSupport_.addProtocolCommandListener(listener);
165    }
166
167    /***
168     * Removes a ProtocolCommandListener.  Delegates this task to
169     * {@link #_commandSupport_  _commandSupport_ }.
170     * <p>
171     * @param listener  The ProtocolCommandListener to remove.
172     ***/
173    public void removeProtocolCommandistener(ProtocolCommandListener listener)
174    {
175        _commandSupport_.removeProtocolCommandListener(listener);
176    }
177
178
179    /***
180     * Sets POP3 client state.  This must be one of the
181     * <code>_STATE</code> constants.
182     * <p>
183     * @param state  The new state.
184     ***/
185    public void setState(int state)
186    {
187        __popState = state;
188    }
189
190
191    /***
192     * Returns the current POP3 client state.
193     * <p>
194     * @return The current POP3 client state.
195     ***/
196    public int getState()
197    {
198        return __popState;
199    }
200
201
202    /***
203     * Retrieves the additional lines of a multi-line server reply.
204     ***/
205    public void getAdditionalReply() throws IOException
206    {
207        String line;
208
209        line = _reader.readLine();
210        while (line != null)
211        {
212            _replyLines.addElement(line);
213            if (line.equals("."))
214                break;
215            line = _reader.readLine();
216        }
217    }
218
219
220    /***
221     * Disconnects the client from the server, and sets the state to
222     * <code> DISCONNECTED_STATE </code>.  The reply text information
223     * from the last issued command is voided to allow garbage collection
224     * of the memory used to store that information.
225     * <p>
226     * @exception IOException  If there is an error in disconnecting.
227     ***/
228    @Override
229    public void disconnect() throws IOException
230    {
231        super.disconnect();
232        _reader = null;
233        __writer = null;
234        _lastReplyLine = null;
235        _replyLines.setSize(0);
236        setState(DISCONNECTED_STATE);
237    }
238
239
240    /***
241     * Sends a command an arguments to the server and returns the reply code.
242     * <p>
243     * @param command  The POP3 command to send.
244     * @param args     The command arguments.
245     * @return  The server reply code (either POP3Reply.OK or POP3Reply.ERROR).
246     ***/
247    public int sendCommand(String command, String args) throws IOException
248    {
249        String message;
250
251        __commandBuffer.setLength(0);
252        __commandBuffer.append(command);
253
254        if (args != null)
255        {
256            __commandBuffer.append(' ');
257            __commandBuffer.append(args);
258        }
259        __commandBuffer.append(SocketClient.NETASCII_EOL);
260
261        __writer.write(message = __commandBuffer.toString());
262        __writer.flush();
263
264        if (_commandSupport_.getListenerCount() > 0)
265            _commandSupport_.fireCommandSent(command, message);
266
267        __getReply();
268        return _replyCode;
269    }
270
271    /***
272     * Sends a command with no arguments to the server and returns the
273     * reply code.
274     * <p>
275     * @param command  The POP3 command to send.
276     * @return  The server reply code (either POP3Reply.OK or POP3Reply.ERROR).
277     ***/
278    public int sendCommand(String command) throws IOException
279    {
280        return sendCommand(command, null);
281    }
282
283    /***
284     * Sends a command an arguments to the server and returns the reply code.
285     * <p>
286     * @param command  The POP3 command to send
287     *                  (one of the POP3Command constants).
288     * @param args     The command arguments.
289     * @return  The server reply code (either POP3Reply.OK or POP3Reply.ERROR).
290     ***/
291    public int sendCommand(int command, String args) throws IOException
292    {
293        return sendCommand(POP3Command._commands[command], args);
294    }
295
296    /***
297     * Sends a command with no arguments to the server and returns the
298     * reply code.
299     * <p>
300     * @param command  The POP3 command to send
301     *                  (one of the POP3Command constants).
302     * @return  The server reply code (either POP3Reply.OK or POP3Reply.ERROR).
303     ***/
304    public int sendCommand(int command) throws IOException
305    {
306        return sendCommand(POP3Command._commands[command], null);
307    }
308
309
310    /***
311     * Returns an array of lines received as a reply to the last command
312     * sent to the server.  The lines have end of lines truncated.  If
313     * the reply is a single line, but its format ndicates it should be
314     * a multiline reply, then you must call
315     * {@link #getAdditionalReply  getAdditionalReply() } to
316     * fetch the rest of the reply, and then call <code>getReplyStrings</code>
317     * again.  You only have to worry about this if you are implementing
318     * your own client using the {@link #sendCommand  sendCommand } methods.
319     * <p>
320     * @return The last server response.
321     ***/
322    public String[] getReplyStrings()
323    {
324        String[] lines;
325        lines = new String[_replyLines.size()];
326        _replyLines.copyInto(lines);
327        return lines;
328    }
329
330    /***
331     * Returns the reply to the last command sent to the server.
332     * The value is a single string containing all the reply lines including
333     * newlines.  If the reply is a single line, but its format ndicates it
334     * should be a multiline reply, then you must call
335     * {@link #getAdditionalReply  getAdditionalReply() } to
336     * fetch the rest of the reply, and then call <code>getReplyString</code>
337     * again.  You only have to worry about this if you are implementing
338     * your own client using the {@link #sendCommand  sendCommand } methods.
339     * <p>
340     * @return The last server response.
341     ***/
342    public String getReplyString()
343    {
344        Enumeration<String> en;
345        StringBuffer buffer = new StringBuffer(256);
346
347        en = _replyLines.elements();
348        while (en.hasMoreElements())
349        {
350            buffer.append(en.nextElement());
351            buffer.append(SocketClient.NETASCII_EOL);
352        }
353
354        return buffer.toString();
355    }
356
357}
358