    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}