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