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 * https://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.IOException;
21
22 /**
23 * The IMAPClient class provides the basic functionalities found in an IMAP client.
24 */
25 public class IMAPClient extends IMAP {
26
27 /**
28 * The message data item names for the FETCH command defined in RFC 3501.
29 */
30 public enum FETCH_ITEM_NAMES {
31
32 /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE). */
33 ALL,
34
35 /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE). */
36 FAST,
37
38 /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY). */
39 FULL,
40
41 /** Non-extensible form of BODYSTRUCTURE or the text of a particular body section. */
42 BODY,
43
44 /** The [MIME-IMB] body structure of the message. */
45 BODYSTRUCTURE,
46
47 /** The envelope structure of the message. */
48 ENVELOPE,
49
50 /** The flags that are set for this message. */
51 FLAGS,
52
53 /** The internal date of the message. */
54 INTERNALDATE,
55
56 /** A prefix for RFC-822 item names. */
57 RFC822,
58
59 /** The unique identifier for the message. */
60 UID
61 }
62
63 /**
64 * The search criteria defined in RFC 3501.
65 */
66 public enum SEARCH_CRITERIA {
67
68 /** All messages in the mailbox. */
69 ALL,
70
71 /** Messages with the \Answered flag set. */
72 ANSWERED,
73
74 /**
75 * Messages that contain the specified string in the envelope structure's BCC field.
76 */
77 BCC,
78
79 /**
80 * Messages whose internal date (disregarding time and time zone) is earlier than the specified date.
81 */
82 BEFORE,
83
84 /**
85 * Messages that contain the specified string in the body of the message.
86 */
87 BODY,
88
89 /**
90 * Messages that contain the specified string in the envelope structure's CC field.
91 */
92 CC,
93
94 /** Messages with the \Deleted flag set. */
95 DELETED,
96
97 /** Messages with the \Draft flag set. */
98 DRAFT,
99
100 /** Messages with the \Flagged flag set. */
101 FLAGGED,
102
103 /**
104 * Messages that contain the specified string in the envelope structure's FROM field.
105 */
106 FROM,
107
108 /**
109 * Messages that have a header with the specified field-name (as defined in [RFC-2822]) and that contains the specified string in the text of the header
110 * (what comes after the colon). If the string to search is zero-length, this matches all messages that have a header line with the specified field-name
111 * regardless of the contents.
112 */
113 HEADER,
114
115 /** Messages with the specified keyword flag set. */
116 KEYWORD,
117
118 /**
119 * Messages with an [RFC-2822] size larger than the specified number of octets.
120 */
121 LARGER,
122
123 /**
124 * Messages that have the \Recent flag set but not the \Seen flag. This is functionally equivalent to "(RECENT UNSEEN)".
125 */
126 NEW,
127
128 /** Messages that do not match the specified search key. */
129 NOT,
130
131 /**
132 * Messages that do not have the \Recent flag set. This is functionally equivalent to "NOT RECENT" (as opposed to "NOT NEW").
133 */
134 OLD,
135
136 /**
137 * Messages whose internal date (disregarding time and time zone) is within the specified date.
138 */
139 ON,
140
141 /** Messages that match either search key. */
142 OR,
143
144 /** Messages that have the \Recent flag set. */
145 RECENT,
146
147 /** Messages that have the \Seen flag set. */
148 SEEN,
149
150 /**
151 * Messages whose [RFC-2822] Date: header (disregarding time and time zone) is earlier than the specified date.
152 */
153 SENTBEFORE,
154
155 /**
156 * Messages whose [RFC-2822] Date: header (disregarding time and time zone) is within the specified date.
157 */
158 SENTON,
159
160 /**
161 * Messages whose [RFC-2822] Date: header (disregarding time and time zone) is within or later than the specified date.
162 */
163 SENTSINCE,
164
165 /**
166 * Messages whose internal date (disregarding time and time zone) is within or later than the specified date.
167 */
168 SINCE,
169
170 /**
171 * Messages with an [RFC-2822] size smaller than the specified number of octets.
172 */
173 SMALLER,
174
175 /**
176 * Messages that contain the specified string in the envelope structure's SUBJECT field.
177 */
178 SUBJECT,
179
180 /**
181 * Messages that contain the specified string in the header or body of the message.
182 */
183 TEXT,
184
185 /**
186 * Messages that contain the specified string in the envelope structure's TO field.
187 */
188 TO,
189
190 /**
191 * Messages with unique identifiers corresponding to the specified unique identifier set. Sequence set ranges are permitted.
192 */
193 UID,
194
195 /** Messages that do not have the \Answered flag set. */
196 UNANSWERED,
197
198 /** Messages that do not have the \Deleted flag set. */
199 UNDELETED,
200
201 /** Messages that do not have the \Draft flag set. */
202 UNDRAFT,
203
204 /** Messages that do not have the \Flagged flag set. */
205 UNFLAGGED,
206
207 /** Messages that do not have the specified keyword flag set. */
208 UNKEYWORD,
209
210 /** Messages that do not have the \Seen flag set. */
211 UNSEEN
212 }
213
214 // commands available in all states
215
216 /**
217 * The status data items defined in RFC 3501.
218 */
219 public enum STATUS_DATA_ITEMS {
220
221 /** The number of messages in the mailbox. */
222 MESSAGES,
223
224 /** The number of messages with the \Recent flag set. */
225 RECENT,
226
227 /** The next unique identifier value of the mailbox. */
228 UIDNEXT,
229
230 /** The unique identifier validity value of the mailbox. */
231 UIDVALIDITY,
232
233 /** The number of messages which do not have the \Seen flag set. */
234 UNSEEN
235 }
236
237 private static final char DQUOTE = '"';
238
239 private static final String DQUOTE_S = "\"";
240
241 // commands available in the not-authenticated state
242 // STARTTLS skipped - see IMAPSClient.
243 // AUTHENTICATE skipped - see AuthenticatingIMAPClient.
244
245 /**
246 * Constructs a new instance.
247 */
248 public IMAPClient() {
249 // empty
250 }
251
252 /**
253 * Send an APPEND command to the server.
254 *
255 * @param mailboxName The mailbox name.
256 * @return {@code true} if the command was successful,{@code false} if not.
257 * @throws IOException If a network I/O error occurs.
258 * @deprecated (3.4) Does not work; the message body is not optional. Use {@link #append(String, String, String, String)} instead.
259 */
260 @Deprecated
261 public boolean append(final String mailboxName) throws IOException {
262 return append(mailboxName, null, null);
263 }
264
265 // commands available in the authenticated state
266
267 /**
268 * Send an APPEND command to the server.
269 *
270 * @param mailboxName The mailbox name.
271 * @param flags The flag parenthesized list (optional).
272 * @param datetime The date/time string (optional).
273 * @return {@code true} if the command was successful,{@code false} if not.
274 * @throws IOException If a network I/O error occurs.
275 * @deprecated (3.4) Does not work; the message body is not optional. Use {@link #append(String, String, String, String)} instead.
276 */
277 @Deprecated
278 public boolean append(final String mailboxName, final String flags, final String datetime) throws IOException {
279 final StringBuilder args = new StringBuilder().append(mailboxName);
280 if (flags != null) {
281 args.append(" ").append(flags);
282 }
283 if (datetime != null) {
284 if (datetime.charAt(0) == '{') {
285 args.append(" ").append(datetime);
286 } else {
287 args.append(" {").append(datetime).append("}");
288 }
289 }
290 return doCommand(IMAPCommand.APPEND, args.toString());
291 }
292
293 /**
294 * Send an APPEND command to the server.
295 *
296 * @param mailboxName The mailbox name.
297 * @param flags The flag parenthesized list (optional).
298 * @param datetime The date/time string (optional).
299 * @param message The message to append.
300 * @return {@code true} if the command was successful,{@code false} if not.
301 * @throws IOException If a network I/O error occurs.
302 * @since 3.4
303 */
304 public boolean append(final String mailboxName, final String flags, final String datetime, final String message) throws IOException {
305 final StringBuilder args = new StringBuilder(quoteMailboxName(mailboxName));
306 if (flags != null) {
307 args.append(" ").append(flags);
308 }
309 if (datetime != null) {
310 args.append(" ");
311 if (datetime.charAt(0) == DQUOTE) {
312 args.append(datetime);
313 } else {
314 args.append(DQUOTE).append(datetime).append(DQUOTE);
315 }
316 }
317 args.append(" ");
318 // String literal (probably not used much - if at all)
319 if (message.startsWith(DQUOTE_S) && message.endsWith(DQUOTE_S)) {
320 args.append(message);
321 return doCommand(IMAPCommand.APPEND, args.toString());
322 }
323 args.append('{').append(message.getBytes(__DEFAULT_ENCODING).length).append('}'); // length of message
324 final int status = sendCommand(IMAPCommand.APPEND, args.toString());
325 return IMAPReply.isContinuation(status) // expecting continuation response
326 && IMAPReply.isSuccess(sendData(message)); // if so, send the data
327 }
328
329 /**
330 * Send a CAPABILITY command to the server.
331 *
332 * @return {@code true} if the command was successful,{@code false} if not.
333 * @throws IOException If a network I/O error occurs
334 */
335 public boolean capability() throws IOException {
336 return doCommand(IMAPCommand.CAPABILITY);
337 }
338
339 /**
340 * Send a CHECK command to the server.
341 *
342 * @return {@code true} if the command was successful,{@code false} if not.
343 * @throws IOException If a network I/O error occurs.
344 */
345 public boolean check() throws IOException {
346 return doCommand(IMAPCommand.CHECK);
347 }
348
349 /**
350 * Send a CLOSE command to the server.
351 *
352 * @return {@code true} if the command was successful,{@code false} if not.
353 * @throws IOException If a network I/O error occurs.
354 */
355 public boolean close() throws IOException {
356 return doCommand(IMAPCommand.CLOSE);
357 }
358
359 /**
360 * Send a COPY command to the server.
361 *
362 * @param sequenceSet The sequence set to fetch.
363 * @param mailboxName The mailbox name.
364 * @return {@code true} if the command was successful,{@code false} if not.
365 * @throws IOException If a network I/O error occurs.
366 */
367 public boolean copy(final String sequenceSet, final String mailboxName) throws IOException {
368 return doCommand(IMAPCommand.COPY, sequenceSet + " " + quoteMailboxName(mailboxName));
369 }
370
371 /**
372 * Send a CREATE command to the server.
373 *
374 * @param mailboxName The mailbox name to create.
375 * @return {@code true} if the command was successful,{@code false} if not.
376 * @throws IOException If a network I/O error occurs.
377 */
378 public boolean create(final String mailboxName) throws IOException {
379 return doCommand(IMAPCommand.CREATE, quoteMailboxName(mailboxName));
380 }
381
382 /**
383 * Send a DELETE command to the server.
384 *
385 * @param mailboxName The mailbox name to delete.
386 * @return {@code true} if the command was successful,{@code false} if not.
387 * @throws IOException If a network I/O error occurs.
388 */
389 public boolean delete(final String mailboxName) throws IOException {
390 return doCommand(IMAPCommand.DELETE, quoteMailboxName(mailboxName));
391 }
392
393 /**
394 * Send an EXAMINE command to the server.
395 *
396 * @param mailboxName The mailbox name to examine.
397 * @return {@code true} if the command was successful,{@code false} if not.
398 * @throws IOException If a network I/O error occurs.
399 */
400 public boolean examine(final String mailboxName) throws IOException {
401 return doCommand(IMAPCommand.EXAMINE, quoteMailboxName(mailboxName));
402 }
403
404 /**
405 * Send an EXPUNGE command to the server.
406 *
407 * @return {@code true} if the command was successful,{@code false} if not.
408 * @throws IOException If a network I/O error occurs.
409 */
410 public boolean expunge() throws IOException {
411 return doCommand(IMAPCommand.EXPUNGE);
412 }
413
414 /**
415 * Send a FETCH command to the server.
416 *
417 * @param sequenceSet The sequence set to fetch (e.g. 1:4,6,11,100:*)
418 * @param itemNames The item names for the FETCH command. (e.g. BODY.PEEK[HEADER.FIELDS (SUBJECT)]) If multiple item names are requested, these must be
419 * enclosed in parentheses, e.g. "(UID FLAGS BODY.PEEK[])"
420 * @return {@code true} if the command was successful,{@code false} if not.
421 * @throws IOException If a network I/O error occurs.
422 * @see #getReplyString()
423 * @see #getReplyStrings()
424 */
425 public boolean fetch(final String sequenceSet, final String itemNames) throws IOException {
426 return doCommand(IMAPCommand.FETCH, sequenceSet + " " + itemNames);
427 }
428
429 /**
430 * Send a LIST command to the server. Quotes the parameters if necessary.
431 *
432 * @param refName The reference name If empty, indicates that the mailbox name is interpreted as by SELECT.
433 * @param mailboxName The mailbox name. If empty, this is a special request to return the hierarchy delimiter and the root name of the name given in the
434 * reference
435 * @return {@code true} if the command was successful,{@code false} if not.
436 * @throws IOException If a network I/O error occurs.
437 */
438 public boolean list(final String refName, final String mailboxName) throws IOException {
439 return doCommand(IMAPCommand.LIST, quoteMailboxName(refName) + " " + quoteMailboxName(mailboxName));
440 }
441
442 /**
443 * Login to the IMAP server with the given user and password. You must first connect to the server with
444 * {@link org.apache.commons.net.SocketClient#connect connect} before attempting to log in. A login attempt is only valid if the client is in the
445 * NOT_AUTH_STATE. After logging in, the client enters the AUTH_STATE.
446 *
447 * @param user The account name being logged in to.
448 * @param password The plain text password of the account.
449 * @return True if the login attempt was successful, false if not.
450 * @throws IOException If a network I/O error occurs in the process of logging in.
451 */
452 public boolean login(final String user, final String password) throws IOException {
453 if (getState() != IMAP.IMAPState.NOT_AUTH_STATE || !doCommand(IMAPCommand.LOGIN, user + " " + password)) {
454 return false;
455 }
456
457 setState(IMAP.IMAPState.AUTH_STATE);
458
459 return true;
460 }
461
462 // commands available in the selected state
463
464 /**
465 * Send a LOGOUT command to the server. To fully disconnect from the server you must call disconnect(). A logout attempt is valid in any state. If the
466 * client is in the not authenticated or authenticated state, it enters the logout on a successful logout.
467 *
468 * @return {@code true} if the command was successful,{@code false} if not.
469 * @throws IOException If a network I/O error occurs.
470 */
471 public boolean logout() throws IOException {
472 return doCommand(IMAPCommand.LOGOUT);
473 }
474
475 /**
476 * Send an LSUB command to the server. Quotes the parameters if necessary.
477 *
478 * @param refName The reference name.
479 * @param mailboxName The mailbox name.
480 * @return {@code true} if the command was successful,{@code false} if not.
481 * @throws IOException If a network I/O error occurs.
482 */
483 public boolean lsub(final String refName, final String mailboxName) throws IOException {
484 return doCommand(IMAPCommand.LSUB, quoteMailboxName(refName) + " " + quoteMailboxName(mailboxName));
485 }
486
487 /**
488 * Send a NOOP command to the server. This is useful for keeping a connection alive since most IMAP servers will time out after 10 minutes of inactivity.
489 *
490 * @return {@code true} if the command was successful,{@code false} if not.
491 * @throws IOException If a network I/O error occurs.
492 */
493 public boolean noop() throws IOException {
494 return doCommand(IMAPCommand.NOOP);
495 }
496
497 /**
498 * Send a RENAME command to the server.
499 *
500 * @param oldMailboxName The existing mailbox name to rename.
501 * @param newMailboxName The new mailbox name.
502 * @return {@code true} if the command was successful,{@code false} if not.
503 * @throws IOException If a network I/O error occurs.
504 */
505 public boolean rename(final String oldMailboxName, final String newMailboxName) throws IOException {
506 return doCommand(IMAPCommand.RENAME, quoteMailboxName(oldMailboxName) + " " + quoteMailboxName(newMailboxName));
507 }
508
509 /**
510 * Send a SEARCH command to the server.
511 *
512 * @param criteria The search criteria.
513 * @return {@code true} if the command was successful,{@code false} if not.
514 * @throws IOException If a network I/O error occurs.
515 */
516 public boolean search(final String criteria) throws IOException {
517 return search(null, criteria);
518 }
519
520 /**
521 * Send a SEARCH command to the server.
522 *
523 * @param charset The charset (optional).
524 * @param criteria The search criteria.
525 * @return {@code true} if the command was successful,{@code false} if not.
526 * @throws IOException If a network I/O error occurs.
527 */
528 public boolean search(final String charset, final String criteria) throws IOException {
529 final StringBuilder args = new StringBuilder();
530 if (charset != null) {
531 args.append("CHARSET ").append(charset);
532 }
533 args.append(criteria);
534 return doCommand(IMAPCommand.SEARCH, args.toString());
535 }
536
537 /**
538 * Send a SELECT command to the server.
539 *
540 * @param mailboxName The mailbox name to select.
541 * @return {@code true} if the command was successful,{@code false} if not.
542 * @throws IOException If a network I/O error occurs.
543 */
544 public boolean select(final String mailboxName) throws IOException {
545 return doCommand(IMAPCommand.SELECT, quoteMailboxName(mailboxName));
546 }
547
548 /**
549 * Send a STATUS command to the server.
550 *
551 * @param mailboxName The reference name.
552 * @param itemNames The status data item names.
553 * @return {@code true} if the command was successful,{@code false} if not.
554 * @throws IOException If a network I/O error occurs.
555 */
556 public boolean status(final String mailboxName, final String[] itemNames) throws IOException {
557 if (itemNames == null || itemNames.length < 1) {
558 throw new IllegalArgumentException("STATUS command requires at least one data item name");
559 }
560
561 final StringBuilder sb = new StringBuilder();
562 sb.append(quoteMailboxName(mailboxName));
563
564 sb.append(" (");
565 for (int i = 0; i < itemNames.length; i++) {
566 if (i > 0) {
567 sb.append(" ");
568 }
569 sb.append(itemNames[i]);
570 }
571 sb.append(")");
572
573 return doCommand(IMAPCommand.STATUS, sb.toString());
574 }
575
576 /**
577 * Send a STORE command to the server.
578 *
579 * @param sequenceSet The sequence set to update (e.g. 2:5)
580 * @param itemNames The item name for the STORE command (i.e. [+|-]FLAGS[.SILENT])
581 * @param itemValues The item values for the STORE command. (e.g. (\Deleted) )
582 * @return {@code true} if the command was successful,{@code false} if not.
583 * @throws IOException If a network I/O error occurs.
584 */
585 public boolean store(final String sequenceSet, final String itemNames, final String itemValues) throws IOException {
586 return doCommand(IMAPCommand.STORE, sequenceSet + " " + itemNames + " " + itemValues);
587 }
588
589 /**
590 * Send a SUBSCRIBE command to the server.
591 *
592 * @param mailboxName The mailbox name to subscribe to.
593 * @return {@code true} if the command was successful,{@code false} if not.
594 * @throws IOException If a network I/O error occurs.
595 */
596 public boolean subscribe(final String mailboxName) throws IOException {
597 return doCommand(IMAPCommand.SUBSCRIBE, quoteMailboxName(mailboxName));
598 }
599
600 /**
601 * Send a UID command to the server.
602 *
603 * @param command The command for UID.
604 * @param commandArgs The arguments for the command.
605 * @return {@code true} if the command was successful,{@code false} if not.
606 * @throws IOException If a network I/O error occurs.
607 */
608 public boolean uid(final String command, final String commandArgs) throws IOException {
609 return doCommand(IMAPCommand.UID, command + " " + commandArgs);
610 }
611
612 /**
613 * Send a UNSUBSCRIBE command to the server.
614 *
615 * @param mailboxName The mailbox name to unsubscribe from.
616 * @return {@code true} if the command was successful,{@code false} if not.
617 * @throws IOException If a network I/O error occurs.
618 */
619 public boolean unsubscribe(final String mailboxName) throws IOException {
620 return doCommand(IMAPCommand.UNSUBSCRIBE, quoteMailboxName(mailboxName));
621 }
622
623 }
624