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