    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
    032        /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE). */
    033        ALL,
    034
    035        /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE). */
    036        FAST,
    037
    038        /** Macro equivalent to: (FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY). */
    039        FULL,
    040
    041        /** Non-extensible form of BODYSTRUCTURE or the text of a particular body section. */
    042        BODY,
    043
    044        /** The [MIME-IMB] body structure of the message. */
    045        BODYSTRUCTURE,
    046
    047        /** The envelope structure of the message. */
    048        ENVELOPE,
    049
    050        /** The flags that are set for this message. */
    051        FLAGS,
    052
    053        /** The internal date of the message. */
    054        INTERNALDATE,
    055
    056        /** A prefix for RFC-822 item names. */
    057        RFC822,
    058
    059        /** The unique identifier for the message. */
    060        UID
    061    }
    062
    063    /**
    064     * The search criteria defined in RFC 3501.
    065     */
    066    public enum SEARCH_CRITERIA {
    067
    068        /** All messages in the mailbox. */
    069        ALL,
    070
    071        /** Messages with the \Answered flag set. */
    072        ANSWERED,
    073
    074        /**
    075         * Messages that contain the specified string in the envelope structure's BCC field.
    076         */
    077        BCC,
    078
    079        /**
    080         * Messages whose internal date (disregarding time and time zone) is earlier than the specified date.
    081         */
    082        BEFORE,
    083
    084        /**
    085         * Messages that contain the specified string in the body of the message.
    086         */
    087        BODY,
    088
    089        /**
    090         * Messages that contain the specified string in the envelope structure's CC field.
    091         */
    092        CC,
    093
    094        /** Messages with the \Deleted flag set. */
    095        DELETED,
    096
    097        /** Messages with the \Draft flag set. */
    098        DRAFT,
    099
    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