    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