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