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