    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    /** 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}.
    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}.
    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}. 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     * Gets 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     * 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
    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} 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(NETASCII_EOL);
    194        }
    195
    196        return buffer.toString();
    197    }
    198
    199    /**
    200     * 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
    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} 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     * Gets 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 builder = new StringBuilder(command);
    279        if (args != null) {
    280            builder.append(' ');
    281            builder.append(args);
    282        }
    283        builder.append(NETASCII_EOL);
    284        final String message = builder.toString();
    285        writer.write(message);
    286        writer.flush();
    287        fireCommandSent(command, message);
    288        getReply();
    289        return replyCode;
    290    }
    291
    292    /**
    293     * Sets the internal POP3 state.
    294     *
    295     * @param state the new state. This must be one of the {@code _STATE} constants.
    296     */
    297    public void setState(final int state) {
    298        popState = state;
    299    }
    300}