    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.imap;
    019
    020import java.io.BufferedReader;
    021import java.io.BufferedWriter;
    022import java.io.EOFException;
    023import java.io.InputStreamReader;
    024import java.io.IOException;
    025import java.io.OutputStreamWriter;
    026import java.util.ArrayList;
    027import java.util.List;
    028
    029import org.apache.commons.net.SocketClient;
    030import org.apache.commons.net.io.CRLFLineReader;
    031
    032
    033/**
    034 * The IMAP class provides the basic the functionality necessary to implement your
    035 * own IMAP client.
    036 */
    037public class IMAP extends SocketClient
    038{
    039    /** The default IMAP port (RFC 3501). */
    040    public static final int DEFAULT_PORT = 143;
    041
    042    public enum IMAPState
    043    {
    044        /** A constant representing the state where the client is not yet connected to a server. */
    045        DISCONNECTED_STATE,
    046        /**  A constant representing the "not authenticated" state. */
    047        NOT_AUTH_STATE,
    048        /**  A constant representing the "authenticated" state. */
    049        AUTH_STATE,
    050        /**  A constant representing the "logout" state. */
    051        LOGOUT_STATE;
    052    }
    053
    054    // RFC 3501, section 5.1.3. It should be "modified UTF-7".
    055    /**
    056     * The default control socket ecoding.
    057     */
    058    protected static final String __DEFAULT_ENCODING = "ISO-8859-1";
    059
    060    private IMAPState __state;
    061    protected BufferedWriter __writer;
    062
    063    protected BufferedReader _reader;
    064    private int _replyCode;
    065    private final List<String> _replyLines;
    066
    067    private final char[] _initialID = { 'A', 'A', 'A', 'A' };
    068
    069    /**
    070     * The default IMAPClient constructor.  Initializes the state
    071     * to <code>DISCONNECTED_STATE</code>.
    072     */
    073    public IMAP()
    074    {
    075        setDefaultPort(DEFAULT_PORT);
    076        __state = IMAPState.DISCONNECTED_STATE;
    077        _reader = null;
    078        __writer = null;
    079        _replyLines = new ArrayList<String>();
    080        createCommandSupport();
    081    }
    082
    083    /**
    084     * Get the reply for a command that expects a tagged response.
    085     *
    086     * @throws IOException
    087     */
    088    private void __getReply() throws IOException
    089    {
    090        __getReply(true); // tagged response
    091    }
    092
    093    /**
    094     * Get the reply for a command, reading the response until the
    095     * reply is found.
    096     *
    097     * @param wantTag {@code true} if the command expects a tagged response.
    098     * @throws IOException
    099     */
    100    private void __getReply(boolean wantTag) throws IOException
    101    {
    102        _replyLines.clear();
    103        String line = _reader.readLine();
    104
    105        if (line == null) {
    106            throw new EOFException("Connection closed without indication.");
    107        }
    108
    109        _replyLines.add(line);
    110
    111        if (wantTag) {
    112            while(IMAPReply.isUntagged(line)) {
    113                int literalCount = IMAPReply.literalCount(line);
    114                while (literalCount >= 0) {
    115                    line=_reader.readLine();
    116                    if (line == null) {
    117                        throw new EOFException("Connection closed without indication.");
    118                    }
    119                    _replyLines.add(line);
    120                    literalCount -= (line.length() + 2); // Allow for CRLF
    121                }
    122                line = _reader.readLine();
    123                if (line == null) {
    124                    throw new EOFException("Connection closed without indication.");
    125                }
    126                _replyLines.add(line);
    127            }
    128            // check the response code on the last line
    129            _replyCode = IMAPReply.getReplyCode(line);
    130        } else {
    131            _replyCode = IMAPReply.getUntaggedReplyCode(line);
    132        }
    133
    134        fireReplyReceived(_replyCode, getReplyString());
    135    }
    136
    137    /**
    138     * Performs connection initialization and sets state to
    139     * {@link IMAPState#NOT_AUTH_STATE}.
    140     */
    141    @Override
    142    protected void _connectAction_() throws IOException
    143    {
    144        super._connectAction_();
    145        _reader =
    146          new CRLFLineReader(new InputStreamReader(_input_,
    147                                                   __DEFAULT_ENCODING));
    148        __writer =
    149          new BufferedWriter(new OutputStreamWriter(_output_,
    150                                                    __DEFAULT_ENCODING));
    151        int tmo = getSoTimeout();
    152        if (tmo <= 0) { // none set currently
    153            setSoTimeout(connectTimeout); // use connect timeout to ensure we don't block forever
    154        }
    155        __getReply(false); // untagged response
    156        if (tmo <= 0) {
    157            setSoTimeout(tmo); // restore the original value
    158        }
    159        setState(IMAPState.NOT_AUTH_STATE);
    160    }
    161
    162    /**
    163     * Sets IMAP client state.  This must be one of the
    164     * <code>_STATE</code> constants.
    165     * <p>
    166     * @param state  The new state.
    167     */
    168    protected void setState(IMAP.IMAPState state)
    169    {
    170        __state = state;
    171    }
    172
    173
    174    /**
    175     * Returns the current IMAP client state.
    176     * <p>
    177     * @return The current IMAP client state.
    178     */
    179    public IMAP.IMAPState getState()
    180    {
    181        return __state;
    182    }
    183
    184    /**
    185     * Disconnects the client from the server, and sets the state to
    186     * <code> DISCONNECTED_STATE </code>.  The reply text information
    187     * from the last issued command is voided to allow garbage collection
    188     * of the memory used to store that information.
    189     * <p>
    190     * @exception IOException  If there is an error in disconnecting.
    191     */
    192    @Override
    193    public void disconnect() throws IOException
    194    {
    195        super.disconnect();
    196        _reader = null;
    197        __writer = null;
    198        _replyLines.clear();
    199        setState(IMAPState.DISCONNECTED_STATE);
    200    }
    201
    202
    203    /**
    204     * Sends a command an arguments to the server and returns the reply code.
    205     * <p>
    206     * @param commandID The ID (tag) of the command.
    207     * @param command  The IMAP command to send.
    208     * @param args     The command arguments.
    209     * @return  The server reply code (either IMAPReply.OK, IMAPReply.NO or IMAPReply.BAD).
    210     */
    211    private int sendCommandWithID(String commandID, String command, String args) throws IOException
    212    {
    213        StringBuilder __commandBuffer = new StringBuilder();
    214        if (commandID != null)
    215        {
    216            __commandBuffer.append(commandID);
    217            __commandBuffer.append(' ');
    218        }
    219        __commandBuffer.append(command);
    220
    221        if (args != null)
    222        {
    223            __commandBuffer.append(' ');
    224            __commandBuffer.append(args);
    225        }
    226        __commandBuffer.append(SocketClient.NETASCII_EOL);
    227
    228        String message = __commandBuffer.toString();
    229        __writer.write(message);
    230        __writer.flush();
    231
    232        fireCommandSent(command, message);
    233
    234        __getReply();
    235        return _replyCode;
    236    }
    237
    238    /**
    239     * Sends a command an arguments to the server and returns the reply code.
    240     * <p>
    241     * @param command  The IMAP command to send.
    242     * @param args     The command arguments.
    243     * @return  The server reply code (see IMAPReply).
    244     */
    245    public int sendCommand(String command, String args) throws IOException
    246    {
    247        return sendCommandWithID(generateCommandID(), command, args);
    248    }
    249
    250    /**
    251     * Sends a command with no arguments to the server and returns the
    252     * reply code.
    253     * <p>
    254     * @param command  The IMAP command to send.
    255     * @return  The server reply code (see IMAPReply).
    256     */
    257    public int sendCommand(String command) throws IOException
    258    {
    259        return sendCommand(command, null);
    260    }
    261
    262    /**
    263     * Sends a command and arguments to the server and returns the reply code.
    264     * <p>
    265     * @param command  The IMAP command to send
    266     *                  (one of the IMAPCommand constants).
    267     * @param args     The command arguments.
    268     * @return  The server reply code (see IMAPReply).
    269     */
    270    public int sendCommand(IMAPCommand command, String args) throws IOException
    271    {
    272        return sendCommand(command.getIMAPCommand(), args);
    273    }
    274
    275    /**
    276     * Sends a command and arguments to the server and return whether successful.
    277     * <p>
    278     * @param command  The IMAP command to send
    279     *                  (one of the IMAPCommand constants).
    280     * @param args     The command arguments.
    281     * @return  {@code true} if the command was successful
    282     */
    283    public boolean doCommand(IMAPCommand command, String args) throws IOException
    284    {
    285        return IMAPReply.isSuccess(sendCommand(command, args));
    286    }
    287
    288    /**
    289     * Sends a command with no arguments to the server and returns the
    290     * reply code.
    291     *
    292     * @param command  The IMAP command to send
    293     *                  (one of the IMAPCommand constants).
    294     * @return  The server reply code (see IMAPReply).
    295    **/
    296    public int sendCommand(IMAPCommand command) throws IOException
    297    {
    298        return sendCommand(command, null);
    299    }
    300
    301    /**
    302     * Sends a command to the server and return whether successful.
    303     *
    304     * @param command  The IMAP command to send
    305     *                  (one of the IMAPCommand constants).
    306     * @return  {@code true} if the command was successful
    307     */
    308    public boolean doCommand(IMAPCommand command) throws IOException
    309    {
    310        return IMAPReply.isSuccess(sendCommand(command));
    311    }
    312
    313    /**
    314     * Sends data to the server and returns the reply code.
    315     * <p>
    316     * @param command  The IMAP command to send.
    317     * @return  The server reply code (see IMAPReply).
    318     */
    319    public int sendData(String command) throws IOException
    320    {
    321        return sendCommandWithID(null, command, null);
    322    }
    323
    324    /**
    325     * Returns an array of lines received as a reply to the last command
    326     * sent to the server.  The lines have end of lines truncated.
    327     * @return The last server response.
    328     */
    329    public String[] getReplyStrings()
    330    {
    331        return _replyLines.toArray(new String[_replyLines.size()]);
    332    }
    333
    334    /**
    335     * Returns the reply to the last command sent to the server.
    336     * The value is a single string containing all the reply lines including
    337     * newlines.
    338     * <p>
    339     * @return The last server response.
    340     */
    341    public String getReplyString()
    342    {
    343        StringBuilder buffer = new StringBuilder(256);
    344        for (String s : _replyLines)
    345        {
    346            buffer.append(s);
    347            buffer.append(SocketClient.NETASCII_EOL);
    348        }
    349
    350        return buffer.toString();
    351    }
    352
    353    /**
    354     * Generates a new command ID (tag) for a command.
    355     * @return a new command ID (tag) for an IMAP command.
    356     */
    357    protected String generateCommandID()
    358    {
    359        String res = new String (_initialID);
    360        // "increase" the ID for the next call
    361        boolean carry = true; // want to increment initially
    362        for (int i = _initialID.length-1; carry && i>=0; i--)
    363        {
    364            if (_initialID[i] == 'Z')
    365            {
    366                _initialID[i] = 'A';
    367            }
    368            else
    369            {
    370                _initialID[i]++;
    371                carry = false; // did not wrap round
    372            }
    373        }
    374        return res;
    375    }
    376}
    377/* kate: indent-width 4; replace-tabs on; */