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