    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
    018    package org.apache.commons.net.imap;
    019
    020    import java.io.BufferedReader;
    021    import java.io.BufferedWriter;
    022    import java.io.EOFException;
    023    import java.io.InputStreamReader;
    024    import java.io.IOException;
    025    import java.io.OutputStreamWriter;
    026    import java.util.ArrayList;
    027    import java.util.List;
    028
    029    import org.apache.commons.net.SocketClient;
    030    import 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     */
    037    public 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 List<String> _replyLines;
    066
    067        private 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; */