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 *      https://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
018package org.apache.commons.net.imap;
019
020import java.io.IOException;
021
022/**
023 * The IMAPClient class provides the basic functionalities found in an IMAP client.
024 */
025public class IMAPClient extends IMAP {
026
027    /**
028     * The message data item names for the FETCH command defined in RFC 3501.
029     */
030    public enum FETCH_ITEM_NAMES {
031        /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE). */
032        ALL,
033        /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE). */
034        FAST,
035        /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY). */
036        FULL,
037        /** Non-extensible form of BODYSTRUCTURE or the text of a particular body section. */
038        BODY,
039        /** The [MIME-IMB] body structure of the message. */
040        BODYSTRUCTURE,
041        /** The envelope structure of the message. */
042        ENVELOPE,
043        /** The flags that are set for this message. */
044        FLAGS,
045        /** The internal date of the message. */
046        INTERNALDATE,
047        /** A prefix for RFC-822 item names. */
048        RFC822,
049        /** The unique identifier for the message. */
050        UID
051    }
052
053    /**
054     * The search criteria defined in RFC 3501.
055     */
056    public enum SEARCH_CRITERIA {
057        /** All messages in the mailbox. */
058        ALL,
059        /** Messages with the \Answered flag set. */
060        ANSWERED,
061        /**
062         * Messages that contain the specified string in the envelope structure's BCC field.
063         */
064        BCC,
065        /**
066         * Messages whose internal date (disregarding time and time zone) is earlier than the specified date.
067         */
068        BEFORE,
069        /**
070         * Messages that contain the specified string in the body of the message.
071         */
072        BODY,
073        /**
074         * Messages that contain the specified string in the envelope structure's CC field.
075         */
076        CC,
077        /** Messages with the \Deleted flag set. */
078        DELETED,
079        /** Messages with the \Draft flag set. */
080        DRAFT,
081        /** Messages with the \Flagged flag set. */
082        FLAGGED,
083        /**
084         * Messages that contain the specified string in the envelope structure's FROM field.
085         */
086        FROM,
087        /**
088         * 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
089         * (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
090         * regardless of the contents.
091         */
092        HEADER,
093        /** Messages with the specified keyword flag set. */
094        KEYWORD,
095        /**
096         * Messages with an [RFC-2822] size larger than the specified number of octets.
097         */
098        LARGER,
099        /**
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