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