    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 *      http://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     * Send an APPEND command to the server.
    197     *
    198     * @param mailboxName The mailbox name.
    199     * @return {@code true} if the command was successful,{@code false} if not.
    200     * @throws IOException If a network I/O error occurs.
    201     * @deprecated (3.4) Does not work; the message body is not optional. Use {@link #append(String, String, String, String)} instead.
    202     */
    203    @Deprecated
    204    public boolean append(final String mailboxName) throws IOException {
    205        return append(mailboxName, null, null);
    206    }
    207
    208    // --------- commands available in the authenticated state
    209
    210    /**
    211     * Send an APPEND command to the server.
    212     *
    213     * @param mailboxName The mailbox name.
    214     * @param flags       The flag parenthesized list (optional).
    215     * @param datetime    The date/time string (optional).
    216     * @return {@code true} if the command was successful,{@code false} if not.
    217     * @throws IOException If a network I/O error occurs.
    218     * @deprecated (3.4) Does not work; the message body is not optional. Use {@link #append(String, String, String, String)} instead.
    219     */
    220    @Deprecated
    221    public boolean append(final String mailboxName, final String flags, final String datetime) throws IOException {
    222        String args = mailboxName;
    223        if (flags != null) {
    224            args += " " + flags;
    225        }
    226        if (datetime != null) {
    227            if (datetime.charAt(0) == '{') {
    228                args += " " + datetime;
    229            } else {
    230                args += " {" + datetime + "}";
    231            }
    232        }
    233        return doCommand(IMAPCommand.APPEND, args);
    234    }
    235
    236    /**
    237     * Send an APPEND command to the server.
    238     *
    239     * @param mailboxName The mailbox name.
    240     * @param flags       The flag parenthesized list (optional).
    241     * @param datetime    The date/time string (optional).
    242     * @param message     The message to append.
    243     * @return {@code true} if the command was successful,{@code false} if not.
    244     * @throws IOException If a network I/O error occurs.
    245     * @since 3.4
    246     */
    247    public boolean append(final String mailboxName, final String flags, final String datetime, final String message) throws IOException {
    248        final StringBuilder args = new StringBuilder(quoteMailboxName(mailboxName));
    249        if (flags != null) {
    250            args.append(" ").append(flags);
    251        }
    252        if (datetime != null) {
    253            args.append(" ");
    254            if (datetime.charAt(0) == DQUOTE) {
    255                args.append(datetime);
    256            } else {
    257                args.append(DQUOTE).append(datetime).append(DQUOTE);
    258            }
    259        }
    260        args.append(" ");
    261        // String literal (probably not used much - if at all)
    262        if (message.startsWith(DQUOTE_S) && message.endsWith(DQUOTE_S)) {
    263            args.append(message);
    264            return doCommand(IMAPCommand.APPEND, args.toString());
    265        }
    266        args.append('{').append(message.getBytes(IMAP.__DEFAULT_ENCODING).length).append('}'); // length of message
    267        final int status = sendCommand(IMAPCommand.APPEND, args.toString());
    268        return IMAPReply.isContinuation(status) // expecting continuation response
    269                && IMAPReply.isSuccess(sendData(message)); // if so, send the data
    270    }
    271
    272    /**
    273     * Send a CAPABILITY command to the server.
    274     *
    275     * @return {@code true} if the command was successful,{@code false} if not.
    276     * @throws IOException If a network I/O error occurs
    277     */
    278    public boolean capability() throws IOException {
    279        return doCommand(IMAPCommand.CAPABILITY);
    280    }
    281
    282    /**
    283     * Send a CHECK command to the server.
    284     *
    285     * @return {@code true} if the command was successful,{@code false} if not.
    286     * @throws IOException If a network I/O error occurs.
    287     */
    288    public boolean check() throws IOException {
    289        return doCommand(IMAPCommand.CHECK);
    290    }
    291
    292    /**
    293     * Send a CLOSE command to the server.
    294     *
    295     * @return {@code true} if the command was successful,{@code false} if not.
    296     * @throws IOException If a network I/O error occurs.
    297     */
    298    public boolean close() throws IOException {
    299        return doCommand(IMAPCommand.CLOSE);
    300    }
    301
    302    /**
    303     * Send a COPY command to the server.
    304     *
    305     * @param sequenceSet The sequence set to fetch.
    306     * @param mailboxName The mailbox name.
    307     * @return {@code true} if the command was successful,{@code false} if not.
    308     * @throws IOException If a network I/O error occurs.
    309     */
    310    public boolean copy(final String sequenceSet, final String mailboxName) throws IOException {
    311        return doCommand(IMAPCommand.COPY, sequenceSet + " " + quoteMailboxName(mailboxName));
    312    }
    313
    314    /**
    315     * Send a CREATE command to the server.
    316     *
    317     * @param mailboxName The mailbox name to create.
    318     * @return {@code true} if the command was successful,{@code false} if not.
    319     * @throws IOException If a network I/O error occurs.
    320     */
    321    public boolean create(final String mailboxName) throws IOException {
    322        return doCommand(IMAPCommand.CREATE, quoteMailboxName(mailboxName));
    323    }
    324
    325    /**
    326     * Send a DELETE command to the server.
    327     *
    328     * @param mailboxName The mailbox name to delete.
    329     * @return {@code true} if the command was successful,{@code false} if not.
    330     * @throws IOException If a network I/O error occurs.
    331     */
    332    public boolean delete(final String mailboxName) throws IOException {
    333        return doCommand(IMAPCommand.DELETE, quoteMailboxName(mailboxName));
    334    }
    335
    336    /**
    337     * Send an EXAMINE command to the server.
    338     *
    339     * @param mailboxName The mailbox name to examine.
    340     * @return {@code true} if the command was successful,{@code false} if not.
    341     * @throws IOException If a network I/O error occurs.
    342     */
    343    public boolean examine(final String mailboxName) throws IOException {
    344        return doCommand(IMAPCommand.EXAMINE, quoteMailboxName(mailboxName));
    345    }
    346
    347    /**
    348     * Send an EXPUNGE command to the server.
    349     *
    350     * @return {@code true} if the command was successful,{@code false} if not.
    351     * @throws IOException If a network I/O error occurs.
    352     */
    353    public boolean expunge() throws IOException {
    354        return doCommand(IMAPCommand.EXPUNGE);
    355    }
    356
    357    /**
    358     * Send a FETCH command to the server.
    359     *
    360     * @param sequenceSet The sequence set to fetch (e.g. 1:4,6,11,100:*)
    361     * @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
    362     *                    enclosed in parentheses, e.g. "(UID FLAGS BODY.PEEK[])"
    363     * @return {@code true} if the command was successful,{@code false} if not.
    364     * @throws IOException If a network I/O error occurs.
    365     * @see #getReplyString()
    366     * @see #getReplyStrings()
    367     */
    368    public boolean fetch(final String sequenceSet, final String itemNames) throws IOException {
    369        return doCommand(IMAPCommand.FETCH, sequenceSet + " " + itemNames);
    370    }
    371
    372    /**
    373     * Send a LIST command to the server. Quotes the parameters if necessary.
    374     *
    375     * @param refName     The reference name If empty, indicates that the mailbox name is interpreted as by SELECT.
    376     * @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
    377     *                    reference
    378     * @return {@code true} if the command was successful,{@code false} if not.
    379     * @throws IOException If a network I/O error occurs.
    380     */
    381    public boolean list(final String refName, final String mailboxName) throws IOException {
    382        return doCommand(IMAPCommand.LIST, quoteMailboxName(refName) + " " + quoteMailboxName(mailboxName));
    383    }
    384
    385    /**
    386     * Login to the IMAP server with the given user and password. You must first connect to the server with
    387     * {@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
    388     * NOT_AUTH_STATE. After logging in, the client enters the AUTH_STATE.
    389     *
    390     * @param user The account name being logged in to.
    391     * @param password The plain text password of the account.
    392     * @return True if the login attempt was successful, false if not.
    393     * @throws IOException If a network I/O error occurs in the process of logging in.
    394     */
    395    public boolean login(final String user, final String password) throws IOException {
    396        if (getState() != IMAP.IMAPState.NOT_AUTH_STATE) {
    397            return false;
    398        }
    399
    400        if (!doCommand(IMAPCommand.LOGIN, user + " " + password)) {
    401            return false;
    402        }
    403
    404        setState(IMAP.IMAPState.AUTH_STATE);
    405
    406        return true;
    407    }
    408
    409    // --------- commands available in the selected state
    410
    411    /**
    412     * 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
    413     * client is in the not authenticated or authenticated state, it enters the logout on a successful logout.
    414     *
    415     * @return {@code true} if the command was successful,{@code false} if not.
    416     * @throws IOException If a network I/O error occurs.
    417     */
    418    public boolean logout() throws IOException {
    419        return doCommand(IMAPCommand.LOGOUT);
    420    }
    421
    422    /**
    423     * Send an LSUB command to the server. Quotes the parameters if necessary.
    424     *
    425     * @param refName     The reference name.
    426     * @param mailboxName The mailbox name.
    427     * @return {@code true} if the command was successful,{@code false} if not.
    428     * @throws IOException If a network I/O error occurs.
    429     */
    430    public boolean lsub(final String refName, final String mailboxName) throws IOException {
    431        return doCommand(IMAPCommand.LSUB, quoteMailboxName(refName) + " " + quoteMailboxName(mailboxName));
    432    }
    433
    434    /**
    435     * 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.
    436     *
    437     * @return {@code true} if the command was successful,{@code false} if not.
    438     * @throws IOException If a network I/O error occurs.
    439     */
    440    public boolean noop() throws IOException {
    441        return doCommand(IMAPCommand.NOOP);
    442    }
    443
    444    /**
    445     * Send a RENAME command to the server.
    446     *
    447     * @param oldMailboxName The existing mailbox name to rename.
    448     * @param newMailboxName The new mailbox name.
    449     * @return {@code true} if the command was successful,{@code false} if not.
    450     * @throws IOException If a network I/O error occurs.
    451     */
    452    public boolean rename(final String oldMailboxName, final String newMailboxName) throws IOException {
    453        return doCommand(IMAPCommand.RENAME, quoteMailboxName(oldMailboxName) + " " + quoteMailboxName(newMailboxName));
    454    }
    455
    456    /**
    457     * Send a SEARCH command to the server.
    458     *
    459     * @param criteria The search criteria.
    460     * @return {@code true} if the command was successful,{@code false} if not.
    461     * @throws IOException If a network I/O error occurs.
    462     */
    463    public boolean search(final String criteria) throws IOException {
    464        return search(null, criteria);
    465    }
    466
    467    /**
    468     * Send a SEARCH command to the server.
    469     *
    470     * @param charset  The charset (optional).
    471     * @param criteria The search criteria.
    472     * @return {@code true} if the command was successful,{@code false} if not.
    473     * @throws IOException If a network I/O error occurs.
    474     */
    475    public boolean search(final String charset, final String criteria) throws IOException {
    476        String args = "";
    477        if (charset != null) {
    478            args += "CHARSET " + charset;
    479        }
    480        args += criteria;
    481        return doCommand(IMAPCommand.SEARCH, args);
    482    }
    483
    484    /**
    485     * Send a SELECT command to the server.
    486     *
    487     * @param mailboxName The mailbox name to select.
    488     * @return {@code true} if the command was successful,{@code false} if not.
    489     * @throws IOException If a network I/O error occurs.
    490     */
    491    public boolean select(final String mailboxName) throws IOException {
    492        return doCommand(IMAPCommand.SELECT, quoteMailboxName(mailboxName));
    493    }
    494
    495    /**
    496     * Send a STATUS command to the server.
    497     *
    498     * @param mailboxName The reference name.
    499     * @param itemNames   The status data item names.
    500     * @return {@code true} if the command was successful,{@code false} if not.
    501     * @throws IOException If a network I/O error occurs.
    502     */
    503    public boolean status(final String mailboxName, final String[] itemNames) throws IOException {
    504        if (itemNames == null || itemNames.length < 1) {
    505            throw new IllegalArgumentException("STATUS command requires at least one data item name");
    506        }
    507
    508        final StringBuilder sb = new StringBuilder();
    509        sb.append(quoteMailboxName(mailboxName));
    510
    511        sb.append(" (");
    512        for (int i = 0; i < itemNames.length; i++) {
    513            if (i > 0) {
    514                sb.append(" ");
    515            }
    516            sb.append(itemNames[i]);
    517        }
    518        sb.append(")");
    519
    520        return doCommand(IMAPCommand.STATUS, sb.toString());
    521    }
    522
    523    /**
    524     * Send a STORE command to the server.
    525     *
    526     * @param sequenceSet The sequence set to update (e.g. 2:5)
    527     * @param itemNames   The item name for the STORE command (i.e. [+|-]FLAGS[.SILENT])
    528     * @param itemValues  The item values for the STORE command. (e.g. (\Deleted) )
    529     * @return {@code true} if the command was successful,{@code false} if not.
    530     * @throws IOException If a network I/O error occurs.
    531     */
    532    public boolean store(final String sequenceSet, final String itemNames, final String itemValues) throws IOException {
    533        return doCommand(IMAPCommand.STORE, sequenceSet + " " + itemNames + " " + itemValues);
    534    }
    535
    536    /**
    537     * Send a SUBSCRIBE command to the server.
    538     *
    539     * @param mailboxName The mailbox name to subscribe to.
    540     * @return {@code true} if the command was successful,{@code false} if not.
    541     * @throws IOException If a network I/O error occurs.
    542     */
    543    public boolean subscribe(final String mailboxName) throws IOException {
    544        return doCommand(IMAPCommand.SUBSCRIBE, quoteMailboxName(mailboxName));
    545    }
    546
    547    /**
    548     * Send a UID command to the server.
    549     *
    550     * @param command     The command for UID.
    551     * @param commandArgs The arguments for the command.
    552     * @return {@code true} if the command was successful,{@code false} if not.
    553     * @throws IOException If a network I/O error occurs.
    554     */
    555    public boolean uid(final String command, final String commandArgs) throws IOException {
    556        return doCommand(IMAPCommand.UID, command + " " + commandArgs);
    557    }
    558
    559    /**
    560     * Send a UNSUBSCRIBE command to the server.
    561     *
    562     * @param mailboxName The mailbox name to unsubscribe from.
    563     * @return {@code true} if the command was successful,{@code false} if not.
    564     * @throws IOException If a network I/O error occurs.
    565     */
    566    public boolean unsubscribe(final String mailboxName) throws IOException {
    567        return doCommand(IMAPCommand.UNSUBSCRIBE, quoteMailboxName(mailboxName));
    568    }
    569
    570}
    571