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