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