View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.imap;
19  
20  import java.io.BufferedReader;
21  import java.io.BufferedWriter;
22  import java.io.EOFException;
23  import java.io.InputStreamReader;
24  import java.io.IOException;
25  import java.io.OutputStreamWriter;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.apache.commons.net.SocketClient;
30  import org.apache.commons.net.io.CRLFLineReader;
31  
32  
33  /**
34   * The IMAP class provides the basic the functionality necessary to implement your
35   * own IMAP client.
36   */
37  public class IMAP extends SocketClient
38  {
39      /** The default IMAP port (RFC 3501). */
40      public static final int DEFAULT_PORT = 143;
41  
42      public enum IMAPState
43      {
44          /** A constant representing the state where the client is not yet connected to a server. */
45          DISCONNECTED_STATE,
46          /**  A constant representing the "not authenticated" state. */
47          NOT_AUTH_STATE,
48          /**  A constant representing the "authenticated" state. */
49          AUTH_STATE,
50          /**  A constant representing the "logout" state. */
51          LOGOUT_STATE;
52      }
53  
54      // RFC 3501, section 5.1.3. It should be "modified UTF-7".
55      /**
56       * The default control socket ecoding.
57       */
58      protected static final String __DEFAULT_ENCODING = "ISO-8859-1";
59  
60      private IMAPState __state;
61      protected BufferedWriter __writer;
62  
63      protected BufferedReader _reader;
64      private int _replyCode;
65      private final List<String> _replyLines;
66  
67      private final char[] _initialID = { 'A', 'A', 'A', 'A' };
68  
69      /**
70       * The default IMAPClient constructor.  Initializes the state
71       * to <code>DISCONNECTED_STATE</code>.
72       */
73      public IMAP()
74      {
75          setDefaultPort(DEFAULT_PORT);
76          __state = IMAPState.DISCONNECTED_STATE;
77          _reader = null;
78          __writer = null;
79          _replyLines = new ArrayList<String>();
80          createCommandSupport();
81      }
82  
83      /**
84       * Get the reply for a command that expects a tagged response.
85       *
86       * @throws IOException
87       */
88      private void __getReply() throws IOException
89      {
90          __getReply(true); // tagged response
91      }
92  
93      /**
94       * Get the reply for a command, reading the response until the
95       * reply is found.
96       *
97       * @param wantTag {@code true} if the command expects a tagged response.
98       * @throws IOException
99       */
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; */