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; */