1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.IOException;
24 import java.io.InputStreamReader;
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 import org.apache.commons.net.util.NetConstants;
32
33
34
35
36
37
38 public class IMAP extends SocketClient
39 {
40
41 public static final int DEFAULT_PORT = 143;
42
43 public enum IMAPState
44 {
45
46 DISCONNECTED_STATE,
47
48 NOT_AUTH_STATE,
49
50 AUTH_STATE,
51
52 LOGOUT_STATE
53 }
54
55
56
57
58
59 protected static final String __DEFAULT_ENCODING = "ISO-8859-1";
60
61 private IMAPState state;
62 protected BufferedWriter __writer;
63
64 protected BufferedReader _reader;
65 private int replyCode;
66 private final List<String> replyLines;
67
68
69
70
71
72
73 public interface IMAPChunkListener {
74
75
76
77
78
79
80 boolean chunkReceived(IMAP imap);
81 }
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 public static final IMAPChunkListener TRUE_CHUNK_LISTENER = new IMAPChunkListener(){
105 @Override
106 public boolean chunkReceived(final IMAP imap) {
107 return true;
108 }
109
110 };
111 private volatile IMAPChunkListener chunkListener;
112
113 private final char[] initialID = { 'A', 'A', 'A', 'A' };
114
115
116
117
118
119 public IMAP()
120 {
121 setDefaultPort(DEFAULT_PORT);
122 state = IMAPState.DISCONNECTED_STATE;
123 _reader = null;
124 __writer = null;
125 replyLines = new ArrayList<>();
126 createCommandSupport();
127 }
128
129
130
131
132
133
134 private void getReply() throws IOException
135 {
136 getReply(true);
137 }
138
139
140
141
142
143
144
145
146 private void getReply(final boolean wantTag) throws IOException
147 {
148 replyLines.clear();
149 String line = _reader.readLine();
150
151 if (line == null) {
152 throw new EOFException("Connection closed without indication.");
153 }
154
155 replyLines.add(line);
156
157 if (wantTag) {
158 while(IMAPReply.isUntagged(line)) {
159 int literalCount = IMAPReply.literalCount(line);
160 final boolean isMultiLine = literalCount >= 0;
161 while (literalCount >= 0) {
162 line=_reader.readLine();
163 if (line == null) {
164 throw new EOFException("Connection closed without indication.");
165 }
166 replyLines.add(line);
167 literalCount -= line.length() + 2;
168 }
169 if (isMultiLine) {
170 final IMAPChunkListener il = chunkListener;
171 if (il != null) {
172 final boolean clear = il.chunkReceived(this);
173 if (clear) {
174 fireReplyReceived(IMAPReply.PARTIAL, getReplyString());
175 replyLines.clear();
176 }
177 }
178 }
179 line = _reader.readLine();
180 if (line == null) {
181 throw new EOFException("Connection closed without indication.");
182 }
183 replyLines.add(line);
184 }
185
186 replyCode = IMAPReply.getReplyCode(line);
187 } else {
188 replyCode = IMAPReply.getUntaggedReplyCode(line);
189 }
190
191 fireReplyReceived(replyCode, getReplyString());
192 }
193
194
195
196
197
198
199
200
201
202
203 @Override
204 protected void fireReplyReceived(final int replyCode, final String ignored) {
205 if (getCommandSupport().getListenerCount() > 0) {
206 getCommandSupport().fireReplyReceived(replyCode, getReplyString());
207 }
208 }
209
210
211
212
213
214 @Override
215 protected void _connectAction_() throws IOException
216 {
217 super._connectAction_();
218 _reader =
219 new CRLFLineReader(new InputStreamReader(_input_,
220 __DEFAULT_ENCODING));
221 __writer =
222 new BufferedWriter(new OutputStreamWriter(_output_,
223 __DEFAULT_ENCODING));
224 final int tmo = getSoTimeout();
225 if (tmo <= 0) {
226 setSoTimeout(connectTimeout);
227 }
228 getReply(false);
229 if (tmo <= 0) {
230 setSoTimeout(tmo);
231 }
232 setState(IMAPState.NOT_AUTH_STATE);
233 }
234
235
236
237
238
239
240
241 protected void setState(final IMAP.IMAPState state)
242 {
243 this.state = state;
244 }
245
246
247
248
249
250
251
252 public IMAP.IMAPState getState()
253 {
254 return state;
255 }
256
257
258
259
260
261
262
263
264
265 @Override
266 public void disconnect() throws IOException
267 {
268 super.disconnect();
269 _reader = null;
270 __writer = null;
271 replyLines.clear();
272 setState(IMAPState.DISCONNECTED_STATE);
273 }
274
275
276
277
278
279
280
281
282
283
284 private int sendCommandWithID(final String commandID, final String command, final String args) throws IOException
285 {
286 final StringBuilder __commandBuffer = new StringBuilder();
287 if (commandID != null)
288 {
289 __commandBuffer.append(commandID);
290 __commandBuffer.append(' ');
291 }
292 __commandBuffer.append(command);
293
294 if (args != null)
295 {
296 __commandBuffer.append(' ');
297 __commandBuffer.append(args);
298 }
299 __commandBuffer.append(SocketClient.NETASCII_EOL);
300
301 final String message = __commandBuffer.toString();
302 __writer.write(message);
303 __writer.flush();
304
305 fireCommandSent(command, message);
306
307 getReply();
308 return replyCode;
309 }
310
311
312
313
314
315
316
317
318
319 public int sendCommand(final String command, final String args) throws IOException
320 {
321 return sendCommandWithID(generateCommandID(), command, args);
322 }
323
324
325
326
327
328
329
330
331
332 public int sendCommand(final String command) throws IOException
333 {
334 return sendCommand(command, null);
335 }
336
337
338
339
340
341
342
343
344
345
346 public int sendCommand(final IMAPCommand command, final String args) throws IOException
347 {
348 return sendCommand(command.getIMAPCommand(), args);
349 }
350
351
352
353
354
355
356
357
358
359
360 public boolean doCommand(final IMAPCommand command, final String args) throws IOException
361 {
362 return IMAPReply.isSuccess(sendCommand(command, args));
363 }
364
365
366
367
368
369
370
371
372
373
374 public int sendCommand(final IMAPCommand command) throws IOException
375 {
376 return sendCommand(command, null);
377 }
378
379
380
381
382
383
384
385
386
387 public boolean doCommand(final IMAPCommand command) throws IOException
388 {
389 return IMAPReply.isSuccess(sendCommand(command));
390 }
391
392
393
394
395
396
397
398
399 public int sendData(final String command) throws IOException
400 {
401 return sendCommandWithID(null, command, null);
402 }
403
404
405
406
407
408
409 public String[] getReplyStrings()
410 {
411 return replyLines.toArray(NetConstants.EMPTY_STRING_ARRAY);
412 }
413
414
415
416
417
418
419
420
421 public String getReplyString()
422 {
423 final StringBuilder buffer = new StringBuilder(256);
424 for (final String s : replyLines)
425 {
426 buffer.append(s);
427 buffer.append(SocketClient.NETASCII_EOL);
428 }
429
430 return buffer.toString();
431 }
432
433
434
435
436
437
438
439
440
441
442
443
444 public void setChunkListener(final IMAPChunkListener listener) {
445 chunkListener = listener;
446 }
447
448
449
450
451
452 protected String generateCommandID()
453 {
454 final String res = new String (initialID);
455
456 boolean carry = true;
457 for (int i = initialID.length-1; carry && i>=0; i--)
458 {
459 if (initialID[i] == 'Z')
460 {
461 initialID[i] = 'A';
462 }
463 else
464 {
465 initialID[i]++;
466 carry = false;
467 }
468 }
469 return res;
470 }
471
472
473
474
475
476
477
478
479
480
481
482
483
484 static String quoteMailboxName(final String input) {
485 if (input == null) {
486 return null;
487 }
488 if (input.isEmpty()) {
489 return "\"\"";
490 }
491
492 if (input.length() > 1 && input.startsWith("\"") && input.endsWith("\"")) {
493 return input;
494 }
495 if (input.contains(" ")) {
496
497 return "\"" + input.replaceAll("([\\\\\"])", "\\\\$1") + "\"";
498 }
499 return input;
500
501 }
502 }
503