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