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