    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            _replyLines.add(line);
    109
    110            if (wantTag) {
    111                while(IMAPReply.isUntagged(line)) {
    112                    line = _reader.readLine();
    113                    if (line == null) {
    114                        throw new EOFException("Connection closed without indication.");
    115                    }
    116                    _replyLines.add(line);
    117                }
    118                // check the response code on the last line
    119                _replyCode = IMAPReply.getReplyCode(line);
    120            } else {
    121                _replyCode = IMAPReply.getUntaggedReplyCode(line);
    122            }
    123
    124            fireReplyReceived(_replyCode, getReplyString());
    125        }
    126
    127        /**
    128         * Performs connection initialization and sets state to
    129         * {@link IMAPState#NOT_AUTH_STATE}.
    130         */
    131        @Override
    132        protected void _connectAction_() throws IOException
    133        {
    134            super._connectAction_();
    135            _reader =
    136              new CRLFLineReader(new InputStreamReader(_input_,
    137                                                       __DEFAULT_ENCODING));
    138            __writer =
    139              new BufferedWriter(new OutputStreamWriter(_output_,
    140                                                        __DEFAULT_ENCODING));
    141            int tmo = getSoTimeout();
    142            if (tmo <= 0) { // none set currently
    143                setSoTimeout(connectTimeout); // use connect timeout to ensure we don't block forever
    144            }
    145            __getReply(false); // untagged response
    146            if (tmo <= 0) {
    147                setSoTimeout(tmo); // restore the original value
    148            }
    149            setState(IMAPState.NOT_AUTH_STATE);
    150        }
    151
    152        /**
    153         * Sets IMAP client state.  This must be one of the
    154         * <code>_STATE</code> constants.
    155         * <p>
    156         * @param state  The new state.
    157         */
    158        protected void setState(IMAP.IMAPState state)
    159        {
    160            __state = state;
    161        }
    162
    163
    164        /**
    165         * Returns the current IMAP client state.
    166         * <p>
    167         * @return The current IMAP client state.
    168         */
    169        public IMAP.IMAPState getState()
    170        {
    171            return __state;
    172        }
    173
    174        /**
    175         * Disconnects the client from the server, and sets the state to
    176         * <code> DISCONNECTED_STATE </code>.  The reply text information
    177         * from the last issued command is voided to allow garbage collection
    178         * of the memory used to store that information.
    179         * <p>
    180         * @exception IOException  If there is an error in disconnecting.
    181         */
    182        @Override
    183        public void disconnect() throws IOException
    184        {
    185            super.disconnect();
    186            _reader = null;
    187            __writer = null;
    188            _replyLines.clear();
    189            setState(IMAPState.DISCONNECTED_STATE);
    190        }
    191
    192
    193        /**
    194         * Sends a command an arguments to the server and returns the reply code.
    195         * <p>
    196         * @param commandID The ID (tag) of the command.
    197         * @param command  The IMAP command to send.
    198         * @param args     The command arguments.
    199         * @return  The server reply code (either IMAPReply.OK, IMAPReply.NO or IMAPReply.BAD).
    200         */
    201        private int sendCommandWithID(String commandID, String command, String args) throws IOException
    202        {
    203            StringBuilder __commandBuffer = new StringBuilder();
    204            if (commandID != null)
    205            {
    206                __commandBuffer.append(commandID);
    207                __commandBuffer.append(' ');
    208            }
    209            __commandBuffer.append(command);
    210
    211            if (args != null)
    212            {
    213                __commandBuffer.append(' ');
    214                __commandBuffer.append(args);
    215            }
    216            __commandBuffer.append(SocketClient.NETASCII_EOL);
    217
    218            String message = __commandBuffer.toString();
    219            __writer.write(message);
    220            __writer.flush();
    221
    222            fireCommandSent(command, message);
    223
    224            __getReply();
    225            return _replyCode;
    226        }
    227
    228        /**
    229         * Sends a command an arguments to the server and returns the reply code.
    230         * <p>
    231         * @param command  The IMAP command to send.
    232         * @param args     The command arguments.
    233         * @return  The server reply code (see IMAPReply).
    234         */
    235        public int sendCommand(String command, String args) throws IOException
    236        {
    237            return sendCommandWithID(generateCommandID(), command, args);
    238        }
    239
    240        /**
    241         * Sends a command with no arguments to the server and returns the
    242         * reply code.
    243         * <p>
    244         * @param command  The IMAP command to send.
    245         * @return  The server reply code (see IMAPReply).
    246         */
    247        public int sendCommand(String command) throws IOException
    248        {
    249            return sendCommand(command, null);
    250        }
    251
    252        /**
    253         * Sends a command and arguments to the server and returns the reply code.
    254         * <p>
    255         * @param command  The IMAP command to send
    256         *                  (one of the IMAPCommand constants).
    257         * @param args     The command arguments.
    258         * @return  The server reply code (see IMAPReply).
    259         */
    260        public int sendCommand(IMAPCommand command, String args) throws IOException
    261        {
    262            return sendCommand(command.getIMAPCommand(), args);
    263        }
    264
    265        /**
    266         * Sends a command and arguments to the server and return whether successful.
    267         * <p>
    268         * @param command  The IMAP command to send
    269         *                  (one of the IMAPCommand constants).
    270         * @param args     The command arguments.
    271         * @return  {@code true} if the command was successful
    272         */
    273        public boolean doCommand(IMAPCommand command, String args) throws IOException
    274        {
    275            return IMAPReply.isSuccess(sendCommand(command, args));
    276        }
    277
    278        /**
    279         * Sends a command with no arguments to the server and returns the
    280         * reply code.
    281         *
    282         * @param command  The IMAP command to send
    283         *                  (one of the IMAPCommand constants).
    284         * @return  The server reply code (see IMAPReply).
    285        **/
    286        public int sendCommand(IMAPCommand command) throws IOException
    287        {
    288            return sendCommand(command, null);
    289        }
    290
    291        /**
    292         * Sends a command to the server and return whether successful.
    293         *
    294         * @param command  The IMAP command to send
    295         *                  (one of the IMAPCommand constants).
    296         * @return  {@code true} if the command was successful
    297         */
    298        public boolean doCommand(IMAPCommand command) throws IOException
    299        {
    300            return IMAPReply.isSuccess(sendCommand(command));
    301        }
    302
    303        /**
    304         * Sends data to the server and returns the reply code.
    305         * <p>
    306         * @param command  The IMAP command to send.
    307         * @return  The server reply code (see IMAPReply).
    308         */
    309        public int sendData(String command) throws IOException
    310        {
    311            return sendCommandWithID(null, command, null);
    312        }
    313
    314        /**
    315         * Returns an array of lines received as a reply to the last command
    316         * sent to the server.  The lines have end of lines truncated.
    317         * @return The last server response.
    318         */
    319        public String[] getReplyStrings()
    320        {
    321            return _replyLines.toArray(new String[_replyLines.size()]);
    322        }
    323
    324        /**
    325         * Returns the reply to the last command sent to the server.
    326         * The value is a single string containing all the reply lines including
    327         * newlines.
    328         * <p>
    329         * @return The last server response.
    330         */
    331        public String getReplyString()
    332        {
    333            StringBuilder buffer = new StringBuilder(256);
    334            for (String s : _replyLines)
    335            {
    336                buffer.append(s);
    337                buffer.append(SocketClient.NETASCII_EOL);
    338            }
    339
    340            return buffer.toString();
    341        }
    342
    343        /**
    344         * Generates a new command ID (tag) for a command.
    345         * @return a new command ID (tag) for an IMAP command.
    346         */
    347        protected String generateCommandID()
    348        {
    349            String res = new String (_initialID);
    350            // "increase" the ID for the next call
    351            boolean carry = true; // want to increment initially
    352            for (int i = _initialID.length-1; carry && i>=0; i--)
    353            {
    354                if (_initialID[i] == 'Z')
    355                {
    356                    _initialID[i] = 'A';
    357                }
    358                else
    359                {
    360                    _initialID[i]++;
    361                    carry = false; // did not wrap round
    362                }
    363            }
    364            return res;
    365        }
    366    }
    367    /* kate: indent-width 4; replace-tabs on; */