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