    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