All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.angus.mail.imap.protocol.IMAPProtocol Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.eclipse.angus.mail.imap.protocol;

import jakarta.mail.Flags;
import jakarta.mail.Folder;
import jakarta.mail.Quota;
import jakarta.mail.UIDFolder;
import jakarta.mail.internet.MimeUtility;
import jakarta.mail.search.SearchException;
import jakarta.mail.search.SearchTerm;
import org.eclipse.angus.mail.auth.Ntlm;
import org.eclipse.angus.mail.iap.Argument;
import org.eclipse.angus.mail.iap.BadCommandException;
import org.eclipse.angus.mail.iap.ByteArray;
import org.eclipse.angus.mail.iap.CommandFailedException;
import org.eclipse.angus.mail.iap.ConnectionException;
import org.eclipse.angus.mail.iap.Literal;
import org.eclipse.angus.mail.iap.LiteralException;
import org.eclipse.angus.mail.iap.ParsingException;
import org.eclipse.angus.mail.iap.Protocol;
import org.eclipse.angus.mail.iap.ProtocolException;
import org.eclipse.angus.mail.iap.Response;
import org.eclipse.angus.mail.imap.ACL;
import org.eclipse.angus.mail.imap.AppendUID;
import org.eclipse.angus.mail.imap.CopyUID;
import org.eclipse.angus.mail.imap.ResyncData;
import org.eclipse.angus.mail.imap.Rights;
import org.eclipse.angus.mail.imap.SortTerm;
import org.eclipse.angus.mail.imap.Utility;
import org.eclipse.angus.mail.util.ASCIIUtility;
import org.eclipse.angus.mail.util.BASE64EncoderStream;
import org.eclipse.angus.mail.util.MailLogger;
import org.eclipse.angus.mail.util.PropUtil;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;

/**
 * This class extends the iap.Protocol object and implements IMAP
 * semantics. In general, there is a method corresponding to each
 * IMAP protocol command. The typical implementation issues the
 * appropriate protocol command, collects all responses, processes
 * those responses that are specific to this command and then
 * dispatches the rest (the unsolicited ones) to the dispatcher
 * using the notifyResponseHandlers(r).
 *
 * @author John Mani
 * @author Bill Shannon
 */

public class IMAPProtocol extends Protocol {

    private boolean connected = false;    // did constructor succeed?
    private boolean rev1 = false;    // REV1 server ?
    private boolean referralException;    // throw exception for IMAP REFERRAL?
    private boolean noauthdebug = true;    // hide auth info in debug output
    private boolean authenticated;    // authenticated?
    // WARNING: authenticated may be set to true in superclass
    //		constructor, don't initialize it here.

    private Map capabilities;
    // WARNING: capabilities may be initialized as a result of superclass
    //		constructor, don't initialize it here.
    private List authmechs;
    // WARNING: authmechs may be initialized as a result of superclass
    //		constructor, don't initialize it here.
    private boolean utf8;        // UTF-8 support enabled?

    protected SearchSequence searchSequence;
    protected String[] searchCharsets;    // array of search charsets

    protected Set enabled;    // enabled capabilities - RFC 5161

    private String name;
    private SaslAuthenticator saslAuthenticator;    // if SASL is being used
    private String proxyAuthUser;    // user name used with PROXYAUTH

    private ByteArray ba;        // a buffer for fetchBody

    private static final byte[] CRLF = {(byte) '\r', (byte) '\n'};

    private static final FetchItem[] fetchItems = {};

    /**
     * Constructor.
     * Opens a connection to the given host at given port.
     *
     * @throws ProtocolException for protocol failures
     * @param    name    the protocol name
     * @param    host    host to connect to
     * @param    port    port number to connect to
     * @param    props    Properties object used by this protocol
     * @param    isSSL    true if SSL should be used
     * @param    logger    the MailLogger to use for debug output
     * @exception IOException    for I/O errors
     */
    public IMAPProtocol(String name, String host, int port,
                        Properties props, boolean isSSL, MailLogger logger)
            throws IOException, ProtocolException {
        super(host, port, props, "mail." + name, isSSL, logger);

        try {
            this.name = name;
            noauthdebug =
                    !PropUtil.getBooleanProperty(props, "mail.debug.auth", false);

            // in case it was not initialized in processGreeting
            referralException = PropUtil.getBooleanProperty(props,
                    prefix + ".referralexception", false);

            if (capabilities == null)
                capability();

            if (hasCapability("IMAP4rev1"))
                rev1 = true;

            searchCharsets = new String[2]; // 2, for now.
            searchCharsets[0] = "UTF-8";
            searchCharsets[1] = MimeUtility.mimeCharset(
                    MimeUtility.getDefaultJavaCharset()
            );

            connected = true;    // must be last statement in constructor
        } finally {
            /*
             * If we get here because an exception was thrown, we need
             * to disconnect to avoid leaving a connected socket that
             * no one will be able to use because this object was never
             * completely constructed.
             */
            if (!connected)
                disconnect();
        }
    }

    /**
     * Constructor for debugging.
     *
     * @param    in    the InputStream from which to read
     * @param    out    the PrintStream to which to write
     * @param    props    Properties object used by this protocol
     * @param    debug    true to enable debugging output
     * @exception IOException    for I/O errors
     */
    public IMAPProtocol(InputStream in, PrintStream out,
                        Properties props, boolean debug)
            throws IOException {
        super(in, out, props, debug);

        this.name = "imap";
        noauthdebug =
                !PropUtil.getBooleanProperty(props, "mail.debug.auth", false);

        if (capabilities == null)
            capabilities = new HashMap<>();

        searchCharsets = new String[2]; // 2, for now.
        searchCharsets[0] = "UTF-8";
        searchCharsets[1] = MimeUtility.mimeCharset(
                MimeUtility.getDefaultJavaCharset()
        );

        connected = true;    // must be last statement in constructor
    }

    /**
     * Return an array of FetchItem objects describing the
     * FETCH items supported by this protocol.  Subclasses may
     * override this method to combine their FetchItems with
     * the FetchItems returned by the superclass.
     *
     * @return an array of FetchItem objects
     * @since JavaMail 1.4.6
     */
    public FetchItem[] getFetchItems() {
        return fetchItems;
    }

    /**
     * CAPABILITY command.
     *
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.1.1"
     */
    public void capability() throws ProtocolException {
        // Check CAPABILITY
        Response[] r = command("CAPABILITY", null);
        Response response = r[r.length - 1];

        if (response.isOK())
            handleCapabilityResponse(r);
        handleResult(response);
    }

    /**
     * Handle any untagged CAPABILITY response in the Response array.
     *
     * @param    r    the responses
     */
    public void handleCapabilityResponse(Response[] r) {
        boolean first = true;
        for (int i = 0, len = r.length; i < len; i++) {
            if (!(r[i] instanceof IMAPResponse))
                continue;

            IMAPResponse ir = (IMAPResponse) r[i];

            // Handle *all* untagged CAPABILITY responses.
            // Though the spec seemingly states that only
            // one CAPABILITY response string is allowed (6.1.1),
            // some server vendors claim otherwise.
            if (ir.keyEquals("CAPABILITY")) {
                if (first) {
                    // clear out current when first response seen
                    capabilities = new HashMap<>(10);
                    authmechs = new ArrayList<>(5);
                    first = false;
                }
                parseCapabilities(ir);
            }
        }
    }

    /**
     * If the response contains a CAPABILITY response code, extract
     * it and save the capabilities.
     *
     * @param    r    the response
     */
    protected void setCapabilities(Response r) {
        byte b;
        while ((b = r.readByte()) > 0 && b != (byte) '[')
            ;
        if (b == 0)
            return;
        String s;
        s = r.readAtom();
        if (!s.equalsIgnoreCase("CAPABILITY"))
            return;
        capabilities = new HashMap<>(10);
        authmechs = new ArrayList<>(5);
        parseCapabilities(r);
    }

    /**
     * Parse the capabilities from a CAPABILITY response or from
     * a CAPABILITY response code attached to (e.g.) an OK response.
     *
     * @param    r    the CAPABILITY response
     */
    protected void parseCapabilities(Response r) {
        String s;
        while ((s = r.readAtom()) != null) {
            if (s.length() == 0) {
                if (r.peekByte() == (byte) ']')
                    break;
                /*
                 * Probably found something here that's not an atom.
                 * Rather than loop forever or fail completely, we'll
                 * try to skip this bogus capability.  This is known
                 * to happen with:
                 *   Netscape Messaging Server 4.03 (built Apr 27 1999)
                 * that returns:
                 *   * CAPABILITY * CAPABILITY IMAP4 IMAP4rev1 ...
                 * The "*" in the middle of the capability list causes
                 * us to loop forever here.
                 */
                r.skipToken();
            } else {
                capabilities.put(s.toUpperCase(Locale.ENGLISH), s);
                if (s.regionMatches(true, 0, "AUTH=", 0, 5)) {
                    authmechs.add(s.substring(5));
                    if (logger.isLoggable(Level.FINE))
                        logger.fine("AUTH: " + s.substring(5));
                }
            }
        }
    }

    /**
     * Check the greeting when first connecting; look for PREAUTH response.
     *
     * @param    r    the greeting response
     * @exception ProtocolException    for protocol failures
     */
    @Override
    protected void processGreeting(Response r) throws ProtocolException {
        if (r.isBYE()) {
            checkReferral(r);    // may throw exception
            throw new ConnectionException(this, r);
        }
        if (r.isOK()) {            // check if it's OK
            // XXX - is a REFERRAL response code really allowed here?
            // XXX - referralException hasn't been initialized in c'tor yet
            referralException = PropUtil.getBooleanProperty(props,
                    prefix + ".referralexception", false);
            if (referralException)
                checkReferral(r);
            setCapabilities(r);
            return;
        }
        // only other choice is PREAUTH
        assert r instanceof IMAPResponse;
        IMAPResponse ir = (IMAPResponse) r;
        if (ir.keyEquals("PREAUTH")) {
            authenticated = true;
            setCapabilities(r);
        } else {
            disconnect();
            throw new ConnectionException(this, r);
        }
    }

    /**
     * Check for an IMAP login REFERRAL response code.
     *
     * @exception IMAPReferralException    if REFERRAL response code found
     * @see "RFC 2221"
     */
    private void checkReferral(Response r) throws IMAPReferralException {
        String s = r.getRest();    // get the text after the response
        if (s.startsWith("[")) {    // a response code
            int i = s.indexOf(' ');
            if (i > 0 && s.substring(1, i).equalsIgnoreCase("REFERRAL")) {
                String url, msg;
                int j = s.indexOf(']');
                if (j > 0) {    // should always be true;
                    url = s.substring(i + 1, j);
                    msg = s.substring(j + 1).trim();
                } else {
                    url = s.substring(i + 1);
                    msg = "";
                }
                if (r.isBYE())
                    disconnect();
                throw new IMAPReferralException(msg, url);
            }
        }
    }

    /**
     * Returns true if the connection has been authenticated,
     * either due to a successful login, or due to a PREAUTH greeting response.
     *
     * @return true if the connection has been authenticated
     */
    public boolean isAuthenticated() {
        return authenticated;
    }

    /**
     * Returns true if this is an IMAP4rev1 server
     *
     * @return true if this is an IMAP4rev1 server
     */
    public boolean isREV1() {
        return rev1;
    }

    /**
     * Returns whether this Protocol supports non-synchronizing literals.
     *
     * @return true if non-synchronizing literals are supported
     */
    @Override
    protected boolean supportsNonSyncLiterals() {
        return hasCapability("LITERAL+");
    }

    /**
     * Read a response from the server.
     *
     * @return the response
     * @exception IOException    for I/O errors
     * @exception ProtocolException    for protocol failures
     */
    @Override
    public Response readResponse() throws IOException, ProtocolException {
        // assert Thread.holdsLock(this);
        // can't assert because it's called from constructor
        IMAPResponse r = new IMAPResponse(this);
        if (r.keyEquals("FETCH"))
            r = new FetchResponse(r, getFetchItems());
        return r;
    }

    /**
     * Check whether the given capability is supported by
     * this server. Returns true if so, otherwise
     * returns false.
     *
     * @param    c    the capability name
     * @return true if the server has the capability
     */
    public boolean hasCapability(String c) {
        if (c.endsWith("*")) {
            c = c.substring(0, c.length() - 1).toUpperCase(Locale.ENGLISH);
            Iterator it = capabilities.keySet().iterator();
            while (it.hasNext()) {
                if (it.next().startsWith(c))
                    return true;
            }
            return false;
        }
        return capabilities.containsKey(c.toUpperCase(Locale.ENGLISH));
    }

    /**
     * Return the map of capabilities returned by the server.
     *
     * @return the Map of capabilities
     * @since JavaMail 1.4.1
     */
    public Map getCapabilities() {
        return capabilities;
    }

    /**
     * Does the server support UTF-8?
     *
     * @since JavaMail 1.6.0
     */
    public boolean supportsUtf8() {
        return utf8;
    }

    /**
     * Close socket connection.
     *
     * This method just makes the Protocol.disconnect() method
     * public.
     */
    @Override
    public void disconnect() {
        super.disconnect();
        authenticated = false;    // just in case
    }

    /**
     * The NOOP command.
     *
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.1.2"
     */
    public void noop() throws ProtocolException {
        logger.fine("IMAPProtocol noop");
        simpleCommand("NOOP", null);
    }

    /**
     * LOGOUT Command.
     *
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.1.3"
     */
    public void logout() throws ProtocolException {
        try {
            Response[] r = command("LOGOUT", null);

            authenticated = false;
            // dispatch any unsolicited responses.
            //  NOTE that the BYE response is dispatched here as well
            notifyResponseHandlers(r);
        } finally {
            disconnect();
        }
    }

    /**
     * LOGIN Command.
     *
     * @param u the username
     * @param p the password
     * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
     * @see "RFC2060, section 6.2.2"
     */
    public void login(String u, String p) throws ProtocolException {
        Argument args = new Argument();
        args.writeString(u);
        args.writeString(p);

        Response[] r = null;
        try {
            if (noauthdebug && isTracing()) {
                logger.fine("LOGIN command trace suppressed");
                suspendTracing();
            }
            r = command("LOGIN", args);
        } finally {
            resumeTracing();
        }

        // handle an illegal but not uncommon untagged CAPABILTY response
        handleCapabilityResponse(r);

        // dispatch untagged responses
        notifyResponseHandlers(r);

        // Handle result of this command
        if (noauthdebug && isTracing())
            logger.fine("LOGIN command result: " + r[r.length - 1]);
        handleLoginResult(r[r.length - 1]);
        // If the response includes a CAPABILITY response code, process it
        setCapabilities(r[r.length - 1]);
        // if we get this far without an exception, we're authenticated
        authenticated = true;
    }

    /**
     * The AUTHENTICATE command with AUTH=LOGIN authenticate scheme
     *
     * @param u the username
     * @param p the password
     * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
     * @see "RFC2060, section 6.2.1"
     */
    public synchronized void authlogin(String u, String p)
            throws ProtocolException {
        List v = new ArrayList<>();
        String tag = null;
        Response r = null;
        boolean done = false;

        try {

            if (noauthdebug && isTracing()) {
                logger.fine("AUTHENTICATE LOGIN command trace suppressed");
                suspendTracing();
            }

            try {
                tag = writeCommand("AUTHENTICATE LOGIN", null);
            } catch (Exception ex) {
                // Convert this into a BYE response
                r = Response.byeResponse(ex);
                done = true;
            }

            OutputStream os = getOutputStream(); // stream to IMAP server

            /* Wrap a BASE64Encoder around a ByteArrayOutputstream
             * to craft b64 encoded username and password strings
             *
             * Note that the encoded bytes should be sent "as-is" to the
             * server, *not* as literals or quoted-strings.
             *
             * Also note that unlike the B64 definition in MIME, CRLFs
             * should *not* be inserted during the encoding process. So, I
             * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine,
             * which should be sufficiently large !
             *
             * Finally, format the line in a buffer so it can be sent as
             * a single packet, to avoid triggering a bug in SUN's SIMS 2.0
             * server caused by patch 105346.
             */

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
            boolean first = true;

            while (!done) { // loop till we are done
                try {
                    r = readResponse();
                    if (r.isContinuation()) {
                        // Server challenge ..
                        String s;
                        if (first) { // Send encoded username
                            s = u;
                            first = false;
                        } else    // Send encoded password
                            s = p;

                        // obtain b64 encoded bytes
                        b64os.write(s.getBytes(StandardCharsets.UTF_8));
                        b64os.flush();    // complete the encoding

                        bos.write(CRLF);    // CRLF termination
                        os.write(bos.toByteArray()); // write out line
                        os.flush();    // flush the stream
                        bos.reset();    // reset buffer
                    } else if (r.isTagged() && r.getTag().equals(tag))
                        // Ah, our tagged response
                        done = true;
                    else if (r.isBYE()) // outta here
                        done = true;
                    // hmm .. unsolicited response here ?!
                } catch (Exception ioex) {
                    // convert this into a BYE response
                    r = Response.byeResponse(ioex);
                    done = true;
                }
                v.add(r);
            }

        } finally {
            resumeTracing();
        }

        Response[] responses = v.toArray(new Response[0]);

        // handle an illegal but not uncommon untagged CAPABILTY response
        handleCapabilityResponse(responses);

        /*
         * Dispatch untagged responses.
         * NOTE: in our current upper level IMAP classes, we add the
         * responseHandler to the Protocol object only *after* the
         * connection has been authenticated. So, for now, the below
         * code really ends up being just a no-op.
         */
        notifyResponseHandlers(responses);

        // Handle the final OK, NO, BAD or BYE response
        if (noauthdebug && isTracing())
            logger.fine("AUTHENTICATE LOGIN command result: " + r);
        handleLoginResult(r);
        // If the response includes a CAPABILITY response code, process it
        setCapabilities(r);
        // if we get this far without an exception, we're authenticated
        authenticated = true;
    }


    /**
     * The AUTHENTICATE command with AUTH=PLAIN authentication scheme.
     * This is based heavly on the {@link #authlogin} method.
     *
     * @param authzid the authorization id
     * @param u       the username
     * @param p       the password
     * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
     * @see "RFC3501, section 6.2.2"
     * @see "RFC2595, section 6"
     * @since JavaMail 1.3.2
     */
    public synchronized void authplain(String authzid, String u, String p)
            throws ProtocolException {
        List v = new ArrayList<>();
        String tag = null;
        Response r = null;
        boolean done = false;

        try {

            if (noauthdebug && isTracing()) {
                logger.fine("AUTHENTICATE PLAIN command trace suppressed");
                suspendTracing();
            }

            try {
                tag = writeCommand("AUTHENTICATE PLAIN", null);
            } catch (Exception ex) {
                // Convert this into a BYE response
                r = Response.byeResponse(ex);
                done = true;
            }

            OutputStream os = getOutputStream(); // stream to IMAP server

            /* Wrap a BASE64Encoder around a ByteArrayOutputstream
             * to craft b64 encoded username and password strings
             *
             * Note that the encoded bytes should be sent "as-is" to the
             * server, *not* as literals or quoted-strings.
             *
             * Also note that unlike the B64 definition in MIME, CRLFs
             * should *not* be inserted during the encoding process. So, I
             * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine,
             * which should be sufficiently large !
             *
             * Finally, format the line in a buffer so it can be sent as
             * a single packet, to avoid triggering a bug in SUN's SIMS 2.0
             * server caused by patch 105346.
             */

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);

            while (!done) { // loop till we are done
                try {
                    r = readResponse();
                    if (r.isContinuation()) {
                        // Server challenge ..
                        final String nullByte = "\0";
                        String s = (authzid == null ? "" : authzid) +
                                nullByte + u + nullByte + p;

                        // obtain b64 encoded bytes
                        b64os.write(s.getBytes(StandardCharsets.UTF_8));
                        b64os.flush();    // complete the encoding

                        bos.write(CRLF);    // CRLF termination
                        os.write(bos.toByteArray()); // write out line
                        os.flush();    // flush the stream
                        bos.reset();    // reset buffer
                    } else if (r.isTagged() && r.getTag().equals(tag))
                        // Ah, our tagged response
                        done = true;
                    else if (r.isBYE()) // outta here
                        done = true;
                    // hmm .. unsolicited response here ?!
                } catch (Exception ioex) {
                    // convert this into a BYE response
                    r = Response.byeResponse(ioex);
                    done = true;
                }
                v.add(r);
            }

        } finally {
            resumeTracing();
        }

        Response[] responses = v.toArray(new Response[0]);

        // handle an illegal but not uncommon untagged CAPABILTY response
        handleCapabilityResponse(responses);

        /*
         * Dispatch untagged responses.
         * NOTE: in our current upper level IMAP classes, we add the
         * responseHandler to the Protocol object only *after* the
         * connection has been authenticated. So, for now, the below
         * code really ends up being just a no-op.
         */
        notifyResponseHandlers(responses);

        // Handle the final OK, NO, BAD or BYE response
        if (noauthdebug && isTracing())
            logger.fine("AUTHENTICATE PLAIN command result: " + r);
        handleLoginResult(r);
        // If the response includes a CAPABILITY response code, process it
        setCapabilities(r);
        // if we get this far without an exception, we're authenticated
        authenticated = true;
    }

    /**
     * The AUTHENTICATE command with AUTH=NTLM authentication scheme.
     * This is based heavly on the {@link #authlogin} method.
     *
     * @param authzid the authorization id
     * @param u       the username
     * @param p       the password
     * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
     * @see "RFC3501, section 6.2.2"
     * @see "RFC2595, section 6"
     * @since JavaMail 1.4.3
     */
    public synchronized void authntlm(String authzid, String u, String p)
            throws ProtocolException {
        List v = new ArrayList<>();
        String tag = null;
        Response r = null;
        boolean done = false;

        int flags = PropUtil.getIntProperty(props,
                "mail." + name + ".auth.ntlm.flags", 0);
        boolean v2 = PropUtil.getBooleanProperty(props,
                "mail." + name + ".auth.ntlm.v2", true);
        String domain = props.getProperty(
                "mail." + name + ".auth.ntlm.domain", "");
        Ntlm ntlm = new Ntlm(domain, getLocalHost(), u, p, logger);

        try {

            if (noauthdebug && isTracing()) {
                logger.fine("AUTHENTICATE NTLM command trace suppressed");
                suspendTracing();
            }

            try {
                tag = writeCommand("AUTHENTICATE NTLM", null);
            } catch (Exception ex) {
                // Convert this into a BYE response
                r = Response.byeResponse(ex);
                done = true;
            }

            OutputStream os = getOutputStream(); // stream to IMAP server
            boolean first = true;

            while (!done) { // loop till we are done
                try {
                    r = readResponse();
                    if (r.isContinuation()) {
                        // Server challenge ..
                        String s;
                        if (first) {
                            s = ntlm.generateType1Msg(flags, v2);
                            first = false;
                        } else {
                            s = ntlm.generateType3Msg(r.getRest());
                        }

                        os.write(s.getBytes(StandardCharsets.UTF_8));
                        os.write(CRLF);    // CRLF termination
                        os.flush();    // flush the stream
                    } else if (r.isTagged() && r.getTag().equals(tag))
                        // Ah, our tagged response
                        done = true;
                    else if (r.isBYE()) // outta here
                        done = true;
                    // hmm .. unsolicited response here ?!
                } catch (Exception ioex) {
                    // convert this into a BYE response
                    r = Response.byeResponse(ioex);
                    done = true;
                }
                v.add(r);
            }

        } finally {
            resumeTracing();
        }

        Response[] responses = v.toArray(new Response[0]);

        // handle an illegal but not uncommon untagged CAPABILTY response
        handleCapabilityResponse(responses);

        /*
         * Dispatch untagged responses.
         * NOTE: in our current upper level IMAP classes, we add the
         * responseHandler to the Protocol object only *after* the
         * connection has been authenticated. So, for now, the below
         * code really ends up being just a no-op.
         */
        notifyResponseHandlers(responses);

        // Handle the final OK, NO, BAD or BYE response
        if (noauthdebug && isTracing())
            logger.fine("AUTHENTICATE NTLM command result: " + r);
        handleLoginResult(r);
        // If the response includes a CAPABILITY response code, process it
        setCapabilities(r);
        // if we get this far without an exception, we're authenticated
        authenticated = true;
    }

    /**
     * The AUTHENTICATE command with AUTH=XOAUTH2 authentication scheme.
     * This is based heavly on the {@link #authlogin} method.
     *
     * @param u the username
     * @param p the password
     * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
     * @see "RFC3501, section 6.2.2"
     * @see "RFC2595, section 6"
     * @since JavaMail 1.5.5
     */
    public synchronized void authoauth2(String u, String p)
            throws ProtocolException {
        List v = new ArrayList<>();
        String tag = null;
        Response r = null;
        boolean done = false;

        try {

            if (noauthdebug && isTracing()) {
                logger.fine("AUTHENTICATE XOAUTH2 command trace suppressed");
                suspendTracing();
            }

            try {
                Argument args = new Argument();
                args.writeAtom("XOAUTH2");
                if (hasCapability("SASL-IR")) {
                    String resp = "user=" + u + "\001auth=Bearer " + p + "\001\001";
                    byte[] ba = Base64.getEncoder().encode(
                            resp.getBytes(StandardCharsets.UTF_8));
                    String irs = ASCIIUtility.toString(ba, 0, ba.length);
                    args.writeAtom(irs);
                }
                tag = writeCommand("AUTHENTICATE", args);
            } catch (Exception ex) {
                // Convert this into a BYE response
                r = Response.byeResponse(ex);
                done = true;
            }

            OutputStream os = getOutputStream(); // stream to IMAP server

            while (!done) { // loop till we are done
                try {
                    r = readResponse();
                    if (r.isContinuation()) {
                        // Server challenge ..
                        String resp = "user=" + u + "\001auth=Bearer " +
                                p + "\001\001";
                        byte[] b = Base64.getEncoder().encode(
                                resp.getBytes(StandardCharsets.UTF_8));
                        os.write(b);    // write out response
                        os.write(CRLF);    // CRLF termination
                        os.flush();    // flush the stream
                    } else if (r.isTagged() && r.getTag().equals(tag))
                        // Ah, our tagged response
                        done = true;
                    else if (r.isBYE()) // outta here
                        done = true;
                    // hmm .. unsolicited response here ?!
                } catch (Exception ioex) {
                    // convert this into a BYE response
                    r = Response.byeResponse(ioex);
                    done = true;
                }
                v.add(r);
            }

        } finally {
            resumeTracing();
        }

        Response[] responses = v.toArray(new Response[0]);

        // handle an illegal but not uncommon untagged CAPABILTY response
        handleCapabilityResponse(responses);

        /*
         * Dispatch untagged responses.
         * NOTE: in our current upper level IMAP classes, we add the
         * responseHandler to the Protocol object only *after* the
         * connection has been authenticated. So, for now, the below
         * code really ends up being just a no-op.
         */
        notifyResponseHandlers(responses);

        // Handle the final OK, NO, BAD or BYE response
        if (noauthdebug && isTracing())
            logger.fine("AUTHENTICATE XOAUTH2 command result: " + r);
        handleLoginResult(r);
        // If the response includes a CAPABILITY response code, process it
        setCapabilities(r);
        // if we get this far without an exception, we're authenticated
        authenticated = true;
    }

    /**
     * SASL-based login.
     *
     * @param    allowed    the SASL mechanisms we're allowed to use
     * @param    realm    the SASL realm
     * @param    authzid    the authorization id
     * @param    u    the username
     * @param    p    the password
     * @exception ProtocolException    for protocol failures
     */
    public void sasllogin(String[] allowed, String realm, String authzid,
                          String u, String p) throws ProtocolException {
        boolean useCanonicalHostName = PropUtil.getBooleanProperty(props,
                "mail." + name + ".sasl.usecanonicalhostname", false);
        String serviceHost;
        if (useCanonicalHostName)
            serviceHost = getInetAddress().getCanonicalHostName();
        else
            serviceHost = host;
        if (saslAuthenticator == null) {
            try {
                Class sac = Class.forName(
                        "org.eclipse.angus.mail.imap.protocol.IMAPSaslAuthenticator");
                Constructor c = sac.getConstructor(IMAPProtocol.class,
                        String.class,
                        Properties.class,
                        MailLogger.class,
                        String.class);
                saslAuthenticator = (SaslAuthenticator) c.newInstance(
                        new Object[]{
                                this,
                                name,
                                props,
                                logger,
                                serviceHost
                        });
            } catch (Exception ex) {
                logger.log(Level.FINE, "Can't load SASL authenticator", ex);
                // probably because we're running on a system without SASL
                return;    // not authenticated, try without SASL
            }
        }

        // were any allowed mechanisms specified?
        List v;
        if (allowed != null && allowed.length > 0) {
            // remove anything not supported by the server
            v = new ArrayList<>(allowed.length);
            for (int i = 0; i < allowed.length; i++)
                if (authmechs.contains(allowed[i]))    // XXX - case must match
                    v.add(allowed[i]);
        } else {
            // everything is allowed
            v = authmechs;
        }
        String[] mechs = v.toArray(new String[0]);

        try {

            if (noauthdebug && isTracing()) {
                logger.fine("SASL authentication command trace suppressed");
                suspendTracing();
            }

            if (saslAuthenticator.authenticate(mechs, realm, authzid, u, p)) {
                if (noauthdebug && isTracing())
                    logger.fine("SASL authentication succeeded");
                authenticated = true;
            } else {
                if (noauthdebug && isTracing())
                    logger.fine("SASL authentication failed");
            }
        } finally {
            resumeTracing();
        }
    }

    // XXX - for IMAPSaslAuthenticator access to protected method
    OutputStream getIMAPOutputStream() {
        return getOutputStream();
    }

    /**
     * Handle the result response for a LOGIN or AUTHENTICATE command.
     * Look for IMAP login REFERRAL.
     *
     * @param    r    the response
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.5
     */
    protected void handleLoginResult(Response r) throws ProtocolException {
        if (hasCapability("LOGIN-REFERRALS") &&
                (!r.isOK() || referralException))
            checkReferral(r);
        handleResult(r);
    }

    /**
     * PROXYAUTH Command.
     *
     * @param    u    the PROXYAUTH user name
     * @exception ProtocolException    for protocol failures
     * @see "Netscape/iPlanet/SunONE Messaging Server extension"
     */
    public void proxyauth(String u) throws ProtocolException {
        Argument args = new Argument();
        args.writeString(u);

        simpleCommand("PROXYAUTH", args);
        proxyAuthUser = u;
    }

    /**
     * Get the user name used with the PROXYAUTH command.
     * Returns null if PROXYAUTH was not used.
     *
     * @return the PROXYAUTH user name
     * @since JavaMail 1.5.1
     */
    public String getProxyAuthUser() {
        return proxyAuthUser;
    }

    /**
     * UNAUTHENTICATE Command.
     *
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.1
     * @see "Netscape/iPlanet/SunONE Messaging Server extension"
     */
    public void unauthenticate() throws ProtocolException {
        if (!hasCapability("X-UNAUTHENTICATE"))
            throw new BadCommandException("UNAUTHENTICATE not supported");
        simpleCommand("UNAUTHENTICATE", null);
        authenticated = false;
    }

    /**
     * ID Command, for Yahoo! Mail IMAP server.
     *
     * @param    guid    the GUID
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.4.4
     * @deprecated As of JavaMail 1.5.1, replaced by
     * {@link #id(Map) id(Map<String,String>)}
     */
    @Deprecated
    public void id(String guid) throws ProtocolException {
        // support this for now, but remove it soon
        Map gmap = new HashMap<>();
        gmap.put("GUID", guid);
        id(gmap);
    }

    /**
     * STARTTLS Command.
     *
     * @exception ProtocolException    for protocol failures
     * @see "RFC3501, section 6.2.1"
     */
    public void startTLS() throws ProtocolException {
        try {
            super.startTLS("STARTTLS");
        } catch (ProtocolException pex) {
            logger.log(Level.FINE, "STARTTLS ProtocolException", pex);
            // ProtocolException just means the command wasn't recognized,
            // or failed.  This should never happen if we check the
            // CAPABILITY first.
            throw pex;
        } catch (Exception ex) {
            logger.log(Level.FINE, "STARTTLS Exception", ex);
            // any other exception means we have to shut down the connection
            // generate an artificial BYE response and disconnect
            Response[] r = {Response.byeResponse(ex)};
            notifyResponseHandlers(r);
            disconnect();
            throw new ProtocolException("STARTTLS failure", ex);
        }
    }

    /**
     * COMPRESS Command.  Only supports DEFLATE.
     *
     * @exception ProtocolException    for protocol failures
     * @see "RFC 4978"
     */
    public void compress() throws ProtocolException {
        try {
            super.startCompression("COMPRESS DEFLATE");
        } catch (ProtocolException pex) {
            logger.log(Level.FINE, "COMPRESS ProtocolException", pex);
            // ProtocolException just means the command wasn't recognized,
            // or failed.  This should never happen if we check the
            // CAPABILITY first.
            throw pex;
        } catch (Exception ex) {
            logger.log(Level.FINE, "COMPRESS Exception", ex);
            // any other exception means we have to shut down the connection
            // generate an artificial BYE response and disconnect
            Response[] r = {Response.byeResponse(ex)};
            notifyResponseHandlers(r);
            disconnect();
            throw new ProtocolException("COMPRESS failure", ex);
        }
    }

    /**
     * Encode a mailbox name appropriately depending on whether or not
     * the server supports UTF-8, and add the encoded name to the
     * Argument.
     *
     * @param    args    the arguments
     * @param    name    the name to encode
     * @since JavaMail 1.6.0
     */
    protected void writeMailboxName(Argument args, String name) {
        if (utf8)
            args.writeString(name, StandardCharsets.UTF_8);
        else
            // encode the mbox as per RFC2060
            args.writeString(BASE64MailboxEncoder.encode(name));
    }

    /**
     * SELECT Command.
     *
     * @param    mbox    the mailbox name
     * @return MailboxInfo if successful
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.1"
     */
    public MailboxInfo select(String mbox) throws ProtocolException {
        return select(mbox, null);
    }

    /**
     * SELECT Command with QRESYNC data.
     *
     * @param    mbox    the mailbox name
     * @param    rd    the ResyncData
     * @return MailboxInfo if successful
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.1
     * @see "RFC2060, section 6.3.1"
     * @see "RFC5162, section 3.1"
     */
    public MailboxInfo select(String mbox, ResyncData rd)
            throws ProtocolException {
        Argument args = new Argument();
        writeMailboxName(args, mbox);

        if (rd != null) {
            if (rd == ResyncData.CONDSTORE) {
                if (!hasCapability("CONDSTORE"))
                    throw new BadCommandException("CONDSTORE not supported");
                args.writeArgument(new Argument().writeAtom("CONDSTORE"));
            } else {
                if (!hasCapability("QRESYNC"))
                    throw new BadCommandException("QRESYNC not supported");
                args.writeArgument(resyncArgs(rd));
            }
        }

        Response[] r = command("SELECT", args);

        // Note that MailboxInfo also removes those responses
        // it knows about
        MailboxInfo minfo = new MailboxInfo(r);

        // dispatch any remaining untagged responses
        notifyResponseHandlers(r);

        Response response = r[r.length - 1];

        if (response.isOK()) { // command succesful
            if (response.toString().contains("READ-ONLY"))
                minfo.mode = Folder.READ_ONLY;
            else
                minfo.mode = Folder.READ_WRITE;
        }

        handleResult(response);
        return minfo;
    }

    /**
     * EXAMINE Command.
     *
     * @param    mbox    the mailbox name
     * @return MailboxInfo if successful
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.2"
     */
    public MailboxInfo examine(String mbox) throws ProtocolException {
        return examine(mbox, null);
    }

    /**
     * EXAMINE Command with QRESYNC data.
     *
     * @param    mbox    the mailbox name
     * @param    rd    the ResyncData
     * @return MailboxInfo if successful
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.1
     * @see "RFC2060, section 6.3.2"
     * @see "RFC5162, section 3.1"
     */
    public MailboxInfo examine(String mbox, ResyncData rd)
            throws ProtocolException {
        Argument args = new Argument();
        writeMailboxName(args, mbox);

        if (rd != null) {
            if (rd == ResyncData.CONDSTORE) {
                if (!hasCapability("CONDSTORE"))
                    throw new BadCommandException("CONDSTORE not supported");
                args.writeArgument(new Argument().writeAtom("CONDSTORE"));
            } else {
                if (!hasCapability("QRESYNC"))
                    throw new BadCommandException("QRESYNC not supported");
                args.writeArgument(resyncArgs(rd));
            }
        }

        Response[] r = command("EXAMINE", args);

        // Note that MailboxInfo also removes those responses
        // it knows about
        MailboxInfo minfo = new MailboxInfo(r);
        minfo.mode = Folder.READ_ONLY; // Obviously

        // dispatch any remaining untagged responses
        notifyResponseHandlers(r);

        handleResult(r[r.length - 1]);
        return minfo;
    }

    /**
     * Generate a QRESYNC argument list based on the ResyncData.
     */
    private static Argument resyncArgs(ResyncData rd) {
        Argument cmd = new Argument();
        cmd.writeAtom("QRESYNC");
        Argument args = new Argument();
        args.writeNumber(rd.getUIDValidity());
        args.writeNumber(rd.getModSeq());
        UIDSet[] uids = Utility.getResyncUIDSet(rd);
        if (uids != null)
            args.writeString(UIDSet.toString(uids));
        cmd.writeArgument(args);
        return cmd;
    }

    /**
     * ENABLE Command.
     *
     * @param    cap    the name of the capability to enable
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.1
     * @see "RFC 5161"
     */
    public void enable(String cap) throws ProtocolException {
        if (!hasCapability("ENABLE"))
            throw new BadCommandException("ENABLE not supported");
        Argument args = new Argument();
        args.writeAtom(cap);
        simpleCommand("ENABLE", args);
        if (enabled == null)
            enabled = new HashSet<>();
        enabled.add(cap.toUpperCase(Locale.ENGLISH));

        // update the utf8 flag
        utf8 = isEnabled("UTF8=ACCEPT");
    }

    /**
     * Is the capability/extension enabled?
     *
     * @param    cap    the capability name
     * @return true if enabled
     * @since JavaMail 1.5.1
     * @see "RFC 5161"
     */
    public boolean isEnabled(String cap) {
        if (enabled == null)
            return false;
        else
            return enabled.contains(cap.toUpperCase(Locale.ENGLISH));
    }

    /**
     * UNSELECT Command.
     *
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.4.4
     * @see "RFC 3691"
     */
    public void unselect() throws ProtocolException {
        if (!hasCapability("UNSELECT"))
            throw new BadCommandException("UNSELECT not supported");
        simpleCommand("UNSELECT", null);
    }

    /**
     * STATUS Command.
     *
     * @param    mbox    the mailbox
     * @param    items    the STATUS items to request
     * @return STATUS results
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.10"
     */
    public Status status(String mbox, String[] items)
            throws ProtocolException {
        if (!isREV1() && !hasCapability("IMAP4SUNVERSION"))
            // STATUS is rev1 only, however the non-rev1 SIMS2.0
            // does support this.
            throw new BadCommandException("STATUS not supported");

        Argument args = new Argument();
        writeMailboxName(args, mbox);

        Argument itemArgs = new Argument();
        if (items == null)
            items = Status.standardItems;

        for (int i = 0, len = items.length; i < len; i++)
            itemArgs.writeAtom(items[i]);
        args.writeArgument(itemArgs);

        Response[] r = command("STATUS", args);

        Status status = null;
        Response response = r[r.length - 1];

        // Grab all STATUS responses
        if (response.isOK()) { // command succesful
            for (int i = 0, len = r.length; i < len; i++) {
                if (!(r[i] instanceof IMAPResponse))
                    continue;

                IMAPResponse ir = (IMAPResponse) r[i];
                if (ir.keyEquals("STATUS")) {
                    if (status == null)
                        status = new Status(ir);
                    else // collect 'em all
                        Status.add(status, new Status(ir));
                    r[i] = null;
                }
            }
        }

        // dispatch remaining untagged responses
        notifyResponseHandlers(r);
        handleResult(response);
        return status;
    }

    /**
     * CREATE Command.
     *
     * @param    mbox    the mailbox to create
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.3"
     */
    public void create(String mbox) throws ProtocolException {
        Argument args = new Argument();
        writeMailboxName(args, mbox);

        simpleCommand("CREATE", args);
    }

    /**
     * DELETE Command.
     *
     * @param    mbox    the mailbox to delete
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.4"
     */
    public void delete(String mbox) throws ProtocolException {
        Argument args = new Argument();
        writeMailboxName(args, mbox);

        simpleCommand("DELETE", args);
    }

    /**
     * RENAME Command.
     *
     * @param    o    old mailbox name
     * @param    n    new mailbox name
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.5"
     */
    public void rename(String o, String n) throws ProtocolException {
        Argument args = new Argument();
        writeMailboxName(args, o);
        writeMailboxName(args, n);

        simpleCommand("RENAME", args);
    }

    /**
     * SUBSCRIBE Command.
     *
     * @param    mbox    the mailbox
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.6"
     */
    public void subscribe(String mbox) throws ProtocolException {
        Argument args = new Argument();
        writeMailboxName(args, mbox);

        simpleCommand("SUBSCRIBE", args);
    }

    /**
     * UNSUBSCRIBE Command.
     *
     * @param    mbox    the mailbox
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.7"
     */
    public void unsubscribe(String mbox) throws ProtocolException {
        Argument args = new Argument();
        writeMailboxName(args, mbox);

        simpleCommand("UNSUBSCRIBE", args);
    }

    /**
     * LIST Command.
     *
     * @param    ref    reference string
     * @param    pattern    pattern to list
     * @return LIST results
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.8"
     */
    public ListInfo[] list(String ref, String pattern)
            throws ProtocolException {
        return doList("LIST", ref, pattern);
    }

    /**
     * LSUB Command.
     *
     * @param    ref    reference string
     * @param    pattern    pattern to list
     * @return LSUB results
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.9"
     */
    public ListInfo[] lsub(String ref, String pattern)
            throws ProtocolException {
        return doList("LSUB", ref, pattern);
    }

    /**
     * Execute the specified LIST-like command (e.g., "LIST" or "LSUB"),
     * using the reference and pattern.
     *
     * @param    cmd    the list command
     * @param    ref    the reference string
     * @param    pat    the pattern
     * @return array of ListInfo results
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.4.6
     */
    protected ListInfo[] doList(String cmd, String ref, String pat)
            throws ProtocolException {
        Argument args = new Argument();
        writeMailboxName(args, ref);
        writeMailboxName(args, pat);

        Response[] r = command(cmd, args);

        ListInfo[] linfo = null;
        Response response = r[r.length - 1];

        if (response.isOK()) { // command succesful
            List v = new ArrayList<>(1);
            for (int i = 0, len = r.length; i < len; i++) {
                if (!(r[i] instanceof IMAPResponse))
                    continue;

                IMAPResponse ir = (IMAPResponse) r[i];
                if (ir.keyEquals(cmd)) {
                    v.add(new ListInfo(ir));
                    r[i] = null;
                }
            }
            if (v.size() > 0) {
                linfo = v.toArray(new ListInfo[0]);
            }
        }

        // Dispatch remaining untagged responses
        notifyResponseHandlers(r);
        handleResult(response);
        return linfo;
    }

    /**
     * APPEND Command.
     *
     * @param    mbox    the mailbox
     * @param    f    the message Flags
     * @param    d    the message date
     * @param    data    the message data
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.11"
     */
    public void append(String mbox, Flags f, Date d,
                       Literal data) throws ProtocolException {
        appenduid(mbox, f, d, data, false);    // ignore return value
    }

    /**
     * APPEND Command, return uid from APPENDUID response code.
     *
     * @param    mbox    the mailbox
     * @param    f    the message Flags
     * @param    d    the message date
     * @param    data    the message data
     * @return APPENDUID data
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.3.11"
     */
    public AppendUID appenduid(String mbox, Flags f, Date d,
                               Literal data) throws ProtocolException {
        return appenduid(mbox, f, d, data, true);
    }

    public AppendUID appenduid(String mbox, Flags f, Date d,
                               Literal data, boolean uid) throws ProtocolException {
        Argument args = new Argument();
        writeMailboxName(args, mbox);

        if (f != null) { // set Flags in appended message
            // can't set the \Recent flag in APPEND
            if (f.contains(Flags.Flag.RECENT)) {
                f = new Flags(f);        // copy, don't modify orig
                f.remove(Flags.Flag.RECENT);    // remove RECENT from copy
            }

            /*
             * HACK ALERT: We want the flag_list to be written out
             * without any checking/processing of the bytes in it. If
             * I use writeString(), the flag_list will end up being
             * quoted since it contains "illegal" characters. So I
             * am depending on implementation knowledge that writeAtom()
             * does not do any checking/processing - it just writes out
             * the bytes. What we really need is a writeFoo() that just
             * dumps out its argument.
             */
            args.writeAtom(createFlagList(f));
        }
        if (d != null) // set INTERNALDATE in appended message
            args.writeString(INTERNALDATE.format(d));

        args.writeBytes(data);

        Response[] r = command("APPEND", args);

        // dispatch untagged responses
        notifyResponseHandlers(r);

        // Handle result of this command
        handleResult(r[r.length - 1]);

        if (uid)
            return getAppendUID(r[r.length - 1]);
        else
            return null;
    }

    /**
     * If the response contains an APPENDUID response code, extract
     * it and return an AppendUID object with the information.
     */
    private AppendUID getAppendUID(Response r) {
        if (!r.isOK())
            return null;
        byte b;
        while ((b = r.readByte()) > 0 && b != (byte) '[')
            ;
        if (b == 0)
            return null;
        String s;
        s = r.readAtom();
        if (!s.equalsIgnoreCase("APPENDUID"))
            return null;

        long uidvalidity = r.readLong();
        long uid = r.readLong();
        return new AppendUID(uidvalidity, uid);
    }

    /**
     * CHECK Command.
     *
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.4.1"
     */
    public void check() throws ProtocolException {
        simpleCommand("CHECK", null);
    }

    /**
     * CLOSE Command.
     *
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.4.2"
     */
    public void close() throws ProtocolException {
        simpleCommand("CLOSE", null);
    }

    /**
     * EXPUNGE Command.
     *
     * @exception ProtocolException    for protocol failures
     * @see "RFC2060, section 6.4.3"
     */
    public void expunge() throws ProtocolException {
        simpleCommand("EXPUNGE", null);
    }

    /**
     * UID EXPUNGE Command.
     *
     * @param    set    UIDs to expunge
     * @exception ProtocolException    for protocol failures
     * @see "RFC4315, section 2"
     */
    public void uidexpunge(UIDSet[] set) throws ProtocolException {
        if (!hasCapability("UIDPLUS"))
            throw new BadCommandException("UID EXPUNGE not supported");
        simpleCommand("UID EXPUNGE " + UIDSet.toString(set), null);
    }

    /**
     * Fetch the BODYSTRUCTURE of the specified message.
     *
     * @param    msgno    the message number
     * @return the BODYSTRUCTURE item
     * @exception ProtocolException    for protocol failures
     */
    public BODYSTRUCTURE fetchBodyStructure(int msgno)
            throws ProtocolException {
        Response[] r = fetch(msgno, "BODYSTRUCTURE");
        notifyResponseHandlers(r);

        Response response = r[r.length - 1];
        if (response.isOK())
            return FetchResponse.getItem(r, msgno, BODYSTRUCTURE.class);
        else if (response.isNO())
            return null;
        else {
            handleResult(response);
            return null;
        }
    }

    /**
     * Fetch given BODY section, without marking the message
     * as SEEN.
     *
     * @param    msgno    the message number
     * @param    section    the body section
     * @return the BODY item
     * @exception ProtocolException    for protocol failures
     */
    public BODY peekBody(int msgno, String section)
            throws ProtocolException {
        return fetchBody(msgno, section, true);
    }

    /**
     * Fetch given BODY section.
     *
     * @param    msgno    the message number
     * @param    section    the body section
     * @return the BODY item
     * @exception ProtocolException    for protocol failures
     */
    public BODY fetchBody(int msgno, String section)
            throws ProtocolException {
        return fetchBody(msgno, section, false);
    }

    protected BODY fetchBody(int msgno, String section, boolean peek)
            throws ProtocolException {
        Response[] r;

        if (section == null)
            section = "";
        String body = (peek ? "BODY.PEEK[" : "BODY[") + section + "]";
        return fetchSectionBody(msgno, section, body);
    }

    /**
     * Partial FETCH of given BODY section, without setting SEEN flag.
     *
     * @param    msgno    the message number
     * @param    section    the body section
     * @param    start    starting byte count
     * @param    size    number of bytes to fetch
     * @return the BODY item
     * @exception ProtocolException    for protocol failures
     */
    public BODY peekBody(int msgno, String section, int start, int size)
            throws ProtocolException {
        return fetchBody(msgno, section, start, size, true, null);
    }

    /**
     * Partial FETCH of given BODY section.
     *
     * @param    msgno    the message number
     * @param    section    the body section
     * @param    start    starting byte count
     * @param    size    number of bytes to fetch
     * @return the BODY item
     * @exception ProtocolException    for protocol failures
     */
    public BODY fetchBody(int msgno, String section, int start, int size)
            throws ProtocolException {
        return fetchBody(msgno, section, start, size, false, null);
    }

    /**
     * Partial FETCH of given BODY section, without setting SEEN flag.
     *
     * @param    msgno    the message number
     * @param    section    the body section
     * @param    start    starting byte count
     * @param    size    number of bytes to fetch
     * @param    ba    the buffer into which to read the response
     * @return the BODY item
     * @exception ProtocolException    for protocol failures
     */
    public BODY peekBody(int msgno, String section, int start, int size,
                         ByteArray ba) throws ProtocolException {
        return fetchBody(msgno, section, start, size, true, ba);
    }

    /**
     * Partial FETCH of given BODY section.
     *
     * @param    msgno    the message number
     * @param    section    the body section
     * @param    start    starting byte count
     * @param    size    number of bytes to fetch
     * @param    ba    the buffer into which to read the response
     * @return the BODY item
     * @exception ProtocolException    for protocol failures
     */
    public BODY fetchBody(int msgno, String section, int start, int size,
                          ByteArray ba) throws ProtocolException {
        return fetchBody(msgno, section, start, size, false, ba);
    }

    protected BODY fetchBody(int msgno, String section, int start, int size,
                             boolean peek, ByteArray ba) throws ProtocolException {
        this.ba = ba;    // save for later use by getResponseBuffer
        if (section == null)
            section = "";
        String body = (peek ? "BODY.PEEK[" : "BODY[") + section + "]<" +
                String.valueOf(start) + "." +
                String.valueOf(size) + ">";
        return fetchSectionBody(msgno, section, body);
    }

    /**
     * Fetch the given body section of the given message, using the
     * body string "body".
     *
     * @param    msgno    the message number
     * @param    section    the body section
     * @param    body    the body string
     * @return the BODY item
     * @exception ProtocolException    for protocol failures
     */
    protected BODY fetchSectionBody(int msgno, String section, String body)
            throws ProtocolException {
        Response[] r;

        r = fetch(msgno, body);
        notifyResponseHandlers(r);

        Response response = r[r.length - 1];
        if (response.isOK()) {
            List bl = FetchResponse.getItems(r, msgno, BODY.class);
            if (bl.size() == 1)
                return bl.get(0);    // the common case
            if (logger.isLoggable(Level.FINEST))
                logger.finest("got " + bl.size() +
                        " BODY responses for section " + section);
            // more then one BODY response, have to find the right one
            for (BODY br : bl) {
                if (logger.isLoggable(Level.FINEST))
                    logger.finest("got BODY section " + br.getSection());
                if (br.getSection().equalsIgnoreCase(section))
                    return br;    // that's the one!
            }
            return null;    // couldn't find it
        } else if (response.isNO())
            return null;
        else {
            handleResult(response);
            return null;
        }
    }

    /**
     * Return a buffer to read a response into.
     * The buffer is provided by fetchBody and is
     * used only once.
     *
     * @return the buffer to use
     */
    @Override
    protected ByteArray getResponseBuffer() {
        ByteArray ret = ba;
        ba = null;
        return ret;
    }

    /**
     * Fetch the specified RFC822 Data item. 'what' names
     * the item to be fetched. 'what' can be null
     * to fetch the whole message.
     *
     * @param    msgno    the message number
     * @param    what    the item to fetch
     * @return the RFC822DATA item
     * @exception ProtocolException    for protocol failures
     */
    public RFC822DATA fetchRFC822(int msgno, String what)
            throws ProtocolException {
        Response[] r = fetch(msgno,
                what == null ? "RFC822" : "RFC822." + what
        );

        // dispatch untagged responses
        notifyResponseHandlers(r);

        Response response = r[r.length - 1];
        if (response.isOK())
            return FetchResponse.getItem(r, msgno, RFC822DATA.class);
        else if (response.isNO())
            return null;
        else {
            handleResult(response);
            return null;
        }
    }

    /**
     * Fetch the FLAGS for the given message.
     *
     * @param    msgno    the message number
     * @return the Flags
     * @exception ProtocolException    for protocol failures
     */
    public Flags fetchFlags(int msgno) throws ProtocolException {
        Flags flags = null;
        Response[] r = fetch(msgno, "FLAGS");

        // Search for our FLAGS response
        for (int i = 0, len = r.length; i < len; i++) {
            if (r[i] == null ||
                    !(r[i] instanceof FetchResponse) ||
                    ((FetchResponse) r[i]).getNumber() != msgno)
                continue;

            FetchResponse fr = (FetchResponse) r[i];
            if ((flags = fr.getItem(FLAGS.class)) != null) {
                r[i] = null; // remove this response
                break;
            }
        }

        // dispatch untagged responses
        notifyResponseHandlers(r);
        handleResult(r[r.length - 1]);
        return flags;
    }

    /**
     * Fetch the IMAP UID for the given message.
     *
     * @param    msgno    the message number
     * @return the UID
     * @exception ProtocolException    for protocol failures
     */
    public UID fetchUID(int msgno) throws ProtocolException {
        Response[] r = fetch(msgno, "UID");

        // dispatch untagged responses
        notifyResponseHandlers(r);

        Response response = r[r.length - 1];
        if (response.isOK())
            return FetchResponse.getItem(r, msgno, UID.class);
        else if (response.isNO()) // XXX: Issue NOOP ?
            return null;
        else {
            handleResult(response);
            return null; // NOTREACHED
        }
    }

    /**
     * Fetch the IMAP MODSEQ for the given message.
     *
     * @param    msgno    the message number
     * @return the MODSEQ
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.1
     */
    public MODSEQ fetchMODSEQ(int msgno) throws ProtocolException {
        Response[] r = fetch(msgno, "MODSEQ");

        // dispatch untagged responses
        notifyResponseHandlers(r);

        Response response = r[r.length - 1];
        if (response.isOK())
            return FetchResponse.getItem(r, msgno, MODSEQ.class);
        else if (response.isNO()) // XXX: Issue NOOP ?
            return null;
        else {
            handleResult(response);
            return null; // NOTREACHED
        }
    }

    /**
     * Get the sequence number for the given UID.  Nothing is returned;
     * the FETCH UID response must be handled by the reponse handler,
     * along with any possible EXPUNGE responses, to ensure that the
     * UID is matched with the correct sequence number.
     *
     * @param    uid    the UID
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.3
     */
    public void fetchSequenceNumber(long uid) throws ProtocolException {
        Response[] r = fetch(String.valueOf(uid), "UID", true);

        notifyResponseHandlers(r);
        handleResult(r[r.length - 1]);
    }

    /**
     * Get the sequence numbers for UIDs ranging from start till end.
     * Since the range may be large and sparse, an array of the UIDs actually
     * found is returned.  The caller must map these to messages after
     * the FETCH UID responses have been handled by the reponse handler,
     * along with any possible EXPUNGE responses, to ensure that the
     * UIDs are matched with the correct sequence numbers.
     *
     * @param    start    first UID
     * @param    end    last UID
     * @return array of sequence numbers
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.3
     */
    public long[] fetchSequenceNumbers(long start, long end)
            throws ProtocolException {
        Response[] r = fetch(String.valueOf(start) + ":" +
                        (end == UIDFolder.LASTUID ? "*" :
                                String.valueOf(end)),
                "UID", true);

        UID u;
        List v = new ArrayList<>();
        for (int i = 0, len = r.length; i < len; i++) {
            if (r[i] == null || !(r[i] instanceof FetchResponse))
                continue;

            FetchResponse fr = (FetchResponse) r[i];
            if ((u = fr.getItem(UID.class)) != null)
                v.add(u);
        }

        notifyResponseHandlers(r);
        handleResult(r[r.length - 1]);

        long[] lv = new long[v.size()];
        for (int i = 0; i < v.size(); i++)
            lv[i] = v.get(i).uid;
        return lv;
    }

    /**
     * Get the sequence numbers for UIDs specified in the array.
     * Nothing is returned.  The caller must map the UIDs to messages after
     * the FETCH UID responses have been handled by the reponse handler,
     * along with any possible EXPUNGE responses, to ensure that the
     * UIDs are matched with the correct sequence numbers.
     *
     * @param    uids    the UIDs
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.3
     */
    public void fetchSequenceNumbers(long[] uids) throws ProtocolException {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < uids.length; i++) {
            if (i > 0)
                sb.append(",");
            sb.append(String.valueOf(uids[i]));
        }

        Response[] r = fetch(sb.toString(), "UID", true);

        notifyResponseHandlers(r);
        handleResult(r[r.length - 1]);
    }

    /**
     * Get the sequence numbers for messages changed since the given
     * modseq and with UIDs ranging from start till end.
     * Also, prefetch the flags for the returned messages.
     *
     * @param    start    first UID
     * @param    end    last UID
     * @param    modseq    the MODSEQ
     * @return array of sequence numbers
     * @exception ProtocolException    for protocol failures
     * @see    "RFC 4551"
     * @since JavaMail 1.5.1
     */
    public int[] uidfetchChangedSince(long start, long end, long modseq)
            throws ProtocolException {
        String msgSequence = String.valueOf(start) + ":" +
                (end == UIDFolder.LASTUID ? "*" :
                        String.valueOf(end));
        Response[] r = command("UID FETCH " + msgSequence +
                " (FLAGS) (CHANGEDSINCE " + String.valueOf(modseq) + ")", null);

        List v = new ArrayList<>();
        for (int i = 0, len = r.length; i < len; i++) {
            if (r[i] == null || !(r[i] instanceof FetchResponse))
                continue;

            FetchResponse fr = (FetchResponse) r[i];
            v.add(Integer.valueOf(fr.getNumber()));
        }

        notifyResponseHandlers(r);
        handleResult(r[r.length - 1]);

        // Copy the list into 'matches'
        int vsize = v.size();
        int[] matches = new int[vsize];
        for (int i = 0; i < vsize; i++)
            matches[i] = v.get(i).intValue();
        return matches;
    }

    public Response[] fetch(MessageSet[] msgsets, String what)
            throws ProtocolException {
        return fetch(MessageSet.toString(msgsets), what, false);
    }

    public Response[] fetch(int start, int end, String what)
            throws ProtocolException {
        return fetch(String.valueOf(start) + ":" + String.valueOf(end),
                what, false);
    }

    public Response[] fetch(int msg, String what)
            throws ProtocolException {
        return fetch(String.valueOf(msg), what, false);
    }

    private Response[] fetch(String msgSequence, String what, boolean uid)
            throws ProtocolException {
        if (uid)
            return command("UID FETCH " + msgSequence + " (" + what + ")", null);
        else
            return command("FETCH " + msgSequence + " (" + what + ")", null);
    }

    /**
     * COPY command.
     *
     * @param    msgsets    the messages to copy
     * @param    mbox    the mailbox to copy them to
     * @exception ProtocolException    for protocol failures
     */
    public void copy(MessageSet[] msgsets, String mbox)
            throws ProtocolException {
        copyuid(MessageSet.toString(msgsets), mbox, false);
    }

    /**
     * COPY command.
     *
     * @param    start    start message number
     * @param    end    end message number
     * @param    mbox    the mailbox to copy them to
     * @exception ProtocolException    for protocol failures
     */
    public void copy(int start, int end, String mbox)
            throws ProtocolException {
        copyuid(String.valueOf(start) + ":" + String.valueOf(end),
                mbox, false);
    }

    /**
     * COPY command, return uid from COPYUID response code.
     *
     * @param    msgsets    the messages to copy
     * @param    mbox    the mailbox to copy them to
     * @return COPYUID response data
     * @exception ProtocolException    for protocol failures
     * @see "RFC 4315, section 3"
     */
    public CopyUID copyuid(MessageSet[] msgsets, String mbox)
            throws ProtocolException {
        return copyuid(MessageSet.toString(msgsets), mbox, true);
    }

    /**
     * COPY command, return uid from COPYUID response code.
     *
     * @param    start    start message number
     * @param    end    end message number
     * @param    mbox    the mailbox to copy them to
     * @return COPYUID response data
     * @exception ProtocolException    for protocol failures
     * @see "RFC 4315, section 3"
     */
    public CopyUID copyuid(int start, int end, String mbox)
            throws ProtocolException {
        return copyuid(String.valueOf(start) + ":" + String.valueOf(end),
                mbox, true);
    }

    private CopyUID copyuid(String msgSequence, String mbox, boolean uid)
            throws ProtocolException {
        if (uid && !hasCapability("UIDPLUS"))
            throw new BadCommandException("UIDPLUS not supported");

        Argument args = new Argument();
        args.writeAtom(msgSequence);
        writeMailboxName(args, mbox);

        Response[] r = command("COPY", args);

        // dispatch untagged responses
        notifyResponseHandlers(r);

        // Handle result of this command
        handleResult(r[r.length - 1]);

        if (uid)
            return getCopyUID(r);
        else
            return null;
    }

    /**
     * MOVE command.
     *
     * @param    msgsets    the messages to move
     * @param    mbox    the mailbox to move them to
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.4
     * @see "RFC 6851"
     */
    public void move(MessageSet[] msgsets, String mbox)
            throws ProtocolException {
        moveuid(MessageSet.toString(msgsets), mbox, false);
    }

    /**
     * MOVE command.
     *
     * @param    start    start message number
     * @param    end    end message number
     * @param    mbox    the mailbox to move them to
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.4
     * @see "RFC 6851"
     */
    public void move(int start, int end, String mbox)
            throws ProtocolException {
        moveuid(String.valueOf(start) + ":" + String.valueOf(end),
                mbox, false);
    }

    /**
     * MOVE Command, return uid from COPYUID response code.
     *
     * @param    msgsets    the messages to move
     * @param    mbox    the mailbox to move them to
     * @return COPYUID response data
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.4
     * @see "RFC 6851"
     * @see "RFC 4315, section 3"
     */
    public CopyUID moveuid(MessageSet[] msgsets, String mbox)
            throws ProtocolException {
        return moveuid(MessageSet.toString(msgsets), mbox, true);
    }

    /**
     * MOVE Command, return uid from COPYUID response code.
     *
     * @param    start    start message number
     * @param    end    end message number
     * @param    mbox    the mailbox to move them to
     * @return COPYUID response data
     * @exception ProtocolException    for protocol failures
     * @since JavaMail 1.5.4
     * @see "RFC 6851"
     * @see "RFC 4315, section 3"
     */
    public CopyUID moveuid(int start, int end, String mbox)
            throws ProtocolException {
        return moveuid(String.valueOf(start) + ":" + String.valueOf(end),
                mbox, true);
    }

    /**
     * MOVE Command, return uid from COPYUID response code.
     *
     * @since JavaMail 1.5.4
     * @see "RFC 6851"
     * @see "RFC 4315, section 3"
     */
    private CopyUID moveuid(String msgSequence, String mbox, boolean uid)
            throws ProtocolException {
        if (!hasCapability("MOVE"))
            throw new BadCommandException("MOVE not supported");
        if (uid && !hasCapability("UIDPLUS"))
            throw new BadCommandException("UIDPLUS not supported");

        Argument args = new Argument();
        args.writeAtom(msgSequence);
        writeMailboxName(args, mbox);

        Response[] r = command("MOVE", args);

        // dispatch untagged responses
        notifyResponseHandlers(r);

        // Handle result of this command
        handleResult(r[r.length - 1]);

        if (uid)
            return getCopyUID(r);
        else
            return null;
    }

    /**
     * If the response contains a COPYUID response code, extract
     * it and return a CopyUID object with the information.
     *
     * @param    rr    the responses to examine
     * @return the COPYUID response code data, or null if not found
     * @since JavaMail 1.5.4
     */
    protected CopyUID getCopyUID(Response[] rr) {
        // most likely in the last response, so start there and work backward
        for (int i = rr.length - 1; i >= 0; i--) {
            Response r = rr[i];
            if (r == null || !r.isOK())
                continue;
            byte b;
            while ((b = r.readByte()) > 0 && b != (byte) '[')
                ;
            if (b == 0)
                continue;
            String s;
            s = r.readAtom();
            if (!s.equalsIgnoreCase("COPYUID"))
                continue;

            // XXX - need to merge more than one response for MOVE?
            long uidvalidity = r.readLong();
            String src = r.readAtom();
            String dst = r.readAtom();
            return new CopyUID(uidvalidity,
                    UIDSet.parseUIDSets(src), UIDSet.parseUIDSets(dst));
        }
        return null;
    }

    public void storeFlags(MessageSet[] msgsets, Flags flags, boolean set)
            throws ProtocolException {
        storeFlags(MessageSet.toString(msgsets), flags, set);
    }

    public void storeFlags(int start, int end, Flags flags, boolean set)
            throws ProtocolException {
        storeFlags(String.valueOf(start) + ":" + String.valueOf(end),
                flags, set);
    }

    /**
     * Set the specified flags on this message.
     *
     * @param    msg    the message number
     * @param    flags    the flags
     * @param    set    true to set, false to clear
     * @exception ProtocolException    for protocol failures
     */
    public void storeFlags(int msg, Flags flags, boolean set)
            throws ProtocolException {
        storeFlags(String.valueOf(msg), flags, set);
    }

    private void storeFlags(String msgset, Flags flags, boolean set)
            throws ProtocolException {
        Response[] r;
        if (set)
            r = command("STORE " + msgset + " +FLAGS " +
                    createFlagList(flags), null);
        else
            r = command("STORE " + msgset + " -FLAGS " +
                    createFlagList(flags), null);

        // Dispatch untagged responses
        notifyResponseHandlers(r);
        handleResult(r[r.length - 1]);
    }

    /**
     * Creates an IMAP flag_list from the given Flags object.
     *
     * @param    flags    the flags
     * @return the IMAP flag_list
     * @since JavaMail 1.5.4
     */
    protected String createFlagList(Flags flags) {
        StringBuilder sb = new StringBuilder("("); // start of flag_list

        Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags
        boolean first = true;
        for (int i = 0; i < sf.length; i++) {
            String s;
            Flags.Flag f = sf[i];
            if (f == Flags.Flag.ANSWERED)
                s = "\\Answered";
            else if (f == Flags.Flag.DELETED)
                s = "\\Deleted";
            else if (f == Flags.Flag.DRAFT)
                s = "\\Draft";
            else if (f == Flags.Flag.FLAGGED)
                s = "\\Flagged";
            else if (f == Flags.Flag.RECENT)
                s = "\\Recent";
            else if (f == Flags.Flag.SEEN)
                s = "\\Seen";
            else
                continue;    // skip it
            if (first)
                first = false;
            else
                sb.append(' ');
            sb.append(s);
        }

        String[] uf = flags.getUserFlags(); // get the user flag strings
        for (int i = 0; i < uf.length; i++) {
            if (first)
                first = false;
            else
                sb.append(' ');
            sb.append(uf[i]);
        }

        sb.append(")"); // terminate flag_list
        return sb.toString();
    }

    /**
     * Issue the given search criterion on the specified message sets.
     * Returns array of matching sequence numbers. An empty array
     * is returned if no matches are found.
     *
     * @param    msgsets    array of MessageSets
     * @param    term    SearchTerm
     * @return array of matching sequence numbers.
     * @exception ProtocolException    for protocol failures
     * @exception SearchException    for search failures
     */
    public int[] search(MessageSet[] msgsets, SearchTerm term)
            throws ProtocolException, SearchException {
        return search(MessageSet.toString(msgsets), term);
    }

    /**
     * Issue the given search criterion on all messages in this folder.
     * Returns array of matching sequence numbers. An empty array
     * is returned if no matches are found.
     *
     * @param    term    SearchTerm
     * @return array of matching sequence numbers.
     * @exception ProtocolException    for protocol failures
     * @exception SearchException    for search failures
     */
    public int[] search(SearchTerm term)
            throws ProtocolException, SearchException {
        return search("ALL", term);
    }

    /*
     * Apply the given SearchTerm on the specified sequence.
     * Returns array of matching sequence numbers. Note that an empty
     * array is returned for no matches.
     */
    private int[] search(String msgSequence, SearchTerm term)
            throws ProtocolException, SearchException {
        // Check if the search "text" terms contain only ASCII chars,
        // or if utf8 support has been enabled (in which case CHARSET
        // is not allowed; see RFC 6855, section 3, last paragraph)
        if (supportsUtf8() || SearchSequence.isAscii(term)) {
            try {
                return issueSearch(msgSequence, term, null);
            } catch (IOException ioex) { /* will not happen */ }
        }

        /*
         * The search "text" terms do contain non-ASCII chars and utf8
         * support has not been enabled.  We need to use:
         * "SEARCH CHARSET  ..."
         * The charsets we try to use are UTF-8 and the locale's
         * default charset. If the server supports UTF-8, great,
         * always use it. Else we try to use the default charset.
         */

        // Cycle thru the list of charsets
        for (int i = 0; i < searchCharsets.length; i++) {
            if (searchCharsets[i] == null)
                continue;

            try {
                return issueSearch(msgSequence, term, searchCharsets[i]);
            } catch (CommandFailedException cfx) {
                /*
                 * Server returned NO. For now, I'll just assume that
                 * this indicates that this charset is unsupported.
                 * We can check the BADCHARSET response code once
                 * that's spec'd into the IMAP RFC ..
                 */
                searchCharsets[i] = null;
                continue;
            } catch (IOException ioex) {
                /* Charset conversion failed. Try the next one */
                continue;
            } catch (ProtocolException | SearchException pex) {
                throw pex;
            }
        }

        // No luck.
        throw new SearchException("Search failed");
    }

    /* Apply the given SearchTerm on the specified sequence, using the
     * given charset. 

* Returns array of matching sequence numbers. Note that an empty * array is returned for no matches. */ private int[] issueSearch(String msgSequence, SearchTerm term, String charset) throws ProtocolException, SearchException, IOException { // Generate a search-sequence with the given charset Argument args = getSearchSequence().generateSequence(term, charset == null ? null : MimeUtility.javaCharset(charset) ); args.writeAtom(msgSequence); Response[] r; if (charset == null) // text is all US-ASCII r = command("SEARCH", args); else r = command("SEARCH CHARSET " + charset, args); Response response = r[r.length - 1]; int[] matches = null; // Grab all SEARCH responses if (response.isOK()) { // command succesful List v = new ArrayList<>(); int num; for (int i = 0, len = r.length; i < len; i++) { if (!(r[i] instanceof IMAPResponse)) continue; IMAPResponse ir = (IMAPResponse) r[i]; // There *will* be one SEARCH response. if (ir.keyEquals("SEARCH")) { while ((num = ir.readNumber()) != -1) v.add(Integer.valueOf(num)); r[i] = null; } } // Copy the list into 'matches' int vsize = v.size(); matches = new int[vsize]; for (int i = 0; i < vsize; i++) matches[i] = v.get(i).intValue(); } // dispatch remaining untagged responses notifyResponseHandlers(r); handleResult(response); return matches; } /** * Get the SearchSequence object. * The SearchSequence object instance is saved in the searchSequence * field. Subclasses of IMAPProtocol may override this method to * return a subclass of SearchSequence, in order to add support for * product-specific search terms. * * @return the SearchSequence * @since JavaMail 1.4.6 */ protected SearchSequence getSearchSequence() { if (searchSequence == null) searchSequence = new SearchSequence(this); return searchSequence; } /** * Sort messages in the folder according to the specified sort criteria. * If the search term is not null, limit the sort to only the messages * that match the search term. * Returns an array of sorted sequence numbers. An empty array * is returned if no matches are found. * * @param term sort criteria * @param sterm SearchTerm * @return array of matching sequence numbers. * @exception ProtocolException for protocol failures * @exception SearchException for search failures * @see "RFC 5256" * @since JavaMail 1.4.4 */ public int[] sort(SortTerm[] term, SearchTerm sterm) throws ProtocolException, SearchException { if (!hasCapability("SORT*")) throw new BadCommandException("SORT not supported"); if (term == null || term.length == 0) throw new BadCommandException("Must have at least one sort term"); Argument args = new Argument(); Argument sargs = new Argument(); for (int i = 0; i < term.length; i++) sargs.writeAtom(term[i].toString()); args.writeArgument(sargs); // sort criteria args.writeAtom("UTF-8"); // charset specification if (sterm != null) { try { args.append( getSearchSequence().generateSequence(sterm, "UTF-8")); } catch (IOException ioex) { // should never happen throw new SearchException(ioex.toString()); } } else args.writeAtom("ALL"); Response[] r = command("SORT", args); Response response = r[r.length - 1]; int[] matches = null; // Grab all SORT responses if (response.isOK()) { // command succesful List v = new ArrayList<>(); int num; for (int i = 0, len = r.length; i < len; i++) { if (!(r[i] instanceof IMAPResponse)) continue; IMAPResponse ir = (IMAPResponse) r[i]; if (ir.keyEquals("SORT")) { while ((num = ir.readNumber()) != -1) v.add(Integer.valueOf(num)); r[i] = null; } } // Copy the list into 'matches' int vsize = v.size(); matches = new int[vsize]; for (int i = 0; i < vsize; i++) matches[i] = v.get(i).intValue(); } // dispatch remaining untagged responses notifyResponseHandlers(r); handleResult(response); return matches; } /** * NAMESPACE Command. * * @return the namespaces * @exception ProtocolException for protocol failures * @see "RFC2342" */ public Namespaces namespace() throws ProtocolException { if (!hasCapability("NAMESPACE")) throw new BadCommandException("NAMESPACE not supported"); Response[] r = command("NAMESPACE", null); Namespaces namespace = null; Response response = r[r.length - 1]; // Grab NAMESPACE response if (response.isOK()) { // command succesful for (int i = 0, len = r.length; i < len; i++) { if (!(r[i] instanceof IMAPResponse)) continue; IMAPResponse ir = (IMAPResponse) r[i]; if (ir.keyEquals("NAMESPACE")) { if (namespace == null) namespace = new Namespaces(ir); r[i] = null; } } } // dispatch remaining untagged responses notifyResponseHandlers(r); handleResult(response); return namespace; } /** * GETQUOTAROOT Command. * * Returns an array of Quota objects, representing the quotas * for this mailbox and, indirectly, the quotaroots for this * mailbox. * * @param mbox the mailbox * @return array of Quota objects * @exception ProtocolException for protocol failures * @see "RFC2087" */ public Quota[] getQuotaRoot(String mbox) throws ProtocolException { if (!hasCapability("QUOTA")) throw new BadCommandException("GETQUOTAROOT not supported"); Argument args = new Argument(); writeMailboxName(args, mbox); Response[] r = command("GETQUOTAROOT", args); Response response = r[r.length - 1]; Map tab = new HashMap<>(); // Grab all QUOTAROOT and QUOTA responses if (response.isOK()) { // command succesful for (int i = 0, len = r.length; i < len; i++) { if (!(r[i] instanceof IMAPResponse)) continue; IMAPResponse ir = (IMAPResponse) r[i]; if (ir.keyEquals("QUOTAROOT")) { // quotaroot_response // ::= "QUOTAROOT" SP astring *(SP astring) // read name of mailbox and throw away ir.readAtomString(); // for each quotaroot add a placeholder quota String root = null; while ((root = ir.readAtomString()) != null && root.length() > 0) tab.put(root, new Quota(root)); r[i] = null; } else if (ir.keyEquals("QUOTA")) { Quota quota = parseQuota(ir); Quota q = tab.get(quota.quotaRoot); if (q != null && q.resources != null) { // merge resources int newl = q.resources.length + quota.resources.length; Quota.Resource[] newr = new Quota.Resource[newl]; System.arraycopy(q.resources, 0, newr, 0, q.resources.length); System.arraycopy(quota.resources, 0, newr, q.resources.length, quota.resources.length); quota.resources = newr; } tab.put(quota.quotaRoot, quota); r[i] = null; } } } // dispatch remaining untagged responses notifyResponseHandlers(r); handleResult(response); return tab.values().toArray(new Quota[0]); } /** * GETQUOTA Command. * * Returns an array of Quota objects, representing the quotas * for this quotaroot. * * @param root the quotaroot * @return the quotas * @exception ProtocolException for protocol failures * @see "RFC2087" */ public Quota[] getQuota(String root) throws ProtocolException { if (!hasCapability("QUOTA")) throw new BadCommandException("QUOTA not supported"); Argument args = new Argument(); args.writeString(root); // XXX - could be UTF-8? Response[] r = command("GETQUOTA", args); Quota quota = null; List v = new ArrayList<>(); Response response = r[r.length - 1]; // Grab all QUOTA responses if (response.isOK()) { // command succesful for (int i = 0, len = r.length; i < len; i++) { if (!(r[i] instanceof IMAPResponse)) continue; IMAPResponse ir = (IMAPResponse) r[i]; if (ir.keyEquals("QUOTA")) { quota = parseQuota(ir); v.add(quota); r[i] = null; } } } // dispatch remaining untagged responses notifyResponseHandlers(r); handleResult(response); return v.toArray(new Quota[0]); } /** * SETQUOTA Command. * * Set the indicated quota on the corresponding quotaroot. * * @param quota the quota to set * @exception ProtocolException for protocol failures * @see "RFC2087" */ public void setQuota(Quota quota) throws ProtocolException { if (!hasCapability("QUOTA")) throw new BadCommandException("QUOTA not supported"); Argument args = new Argument(); args.writeString(quota.quotaRoot); // XXX - could be UTF-8? Argument qargs = new Argument(); if (quota.resources != null) { for (int i = 0; i < quota.resources.length; i++) { qargs.writeAtom(quota.resources[i].name); qargs.writeNumber(quota.resources[i].limit); } } args.writeArgument(qargs); Response[] r = command("SETQUOTA", args); Response response = r[r.length - 1]; // XXX - It's not clear from the RFC whether the SETQUOTA command // will provoke untagged QUOTA responses. If it does, perhaps // we should grab them here and return them? /* Quota quota = null; List v = new ArrayList(); // Grab all QUOTA responses if (response.isOK()) { // command succesful for (int i = 0, len = r.length; i < len; i++) { if (!(r[i] instanceof IMAPResponse)) continue; IMAPResponse ir = (IMAPResponse)r[i]; if (ir.keyEquals("QUOTA")) { quota = parseQuota(ir); v.add(quota); r[i] = null; } } } */ // dispatch remaining untagged responses notifyResponseHandlers(r); handleResult(response); /* return v.toArray(new Quota[v.size()]); */ } /** * Parse a QUOTA response. */ private Quota parseQuota(Response r) throws ParsingException { // quota_response ::= "QUOTA" SP astring SP quota_list String quotaRoot = r.readAtomString(); // quotaroot ::= astring Quota q = new Quota(quotaRoot); r.skipSpaces(); // quota_list ::= "(" #quota_resource ")" if (r.readByte() != '(') throw new ParsingException("parse error in QUOTA"); List v = new ArrayList<>(); while (!r.isNextNonSpace(')')) { // quota_resource ::= atom SP number SP number String name = r.readAtom(); if (name != null) { long usage = r.readLong(); long limit = r.readLong(); Quota.Resource res = new Quota.Resource(name, usage, limit); v.add(res); } } q.resources = v.toArray(new Quota.Resource[0]); return q; } /** * SETACL Command. * * @param mbox the mailbox * @param modifier the ACL modifier * @param acl the ACL * @exception ProtocolException for protocol failures * @see "RFC2086" */ public void setACL(String mbox, char modifier, ACL acl) throws ProtocolException { if (!hasCapability("ACL")) throw new BadCommandException("ACL not supported"); Argument args = new Argument(); writeMailboxName(args, mbox); args.writeString(acl.getName()); String rights = acl.getRights().toString(); if (modifier == '+' || modifier == '-') rights = modifier + rights; args.writeString(rights); Response[] r = command("SETACL", args); Response response = r[r.length - 1]; // dispatch untagged responses notifyResponseHandlers(r); handleResult(response); } /** * DELETEACL Command. * * @param mbox the mailbox * @param user the user * @exception ProtocolException for protocol failures * @see "RFC2086" */ public void deleteACL(String mbox, String user) throws ProtocolException { if (!hasCapability("ACL")) throw new BadCommandException("ACL not supported"); Argument args = new Argument(); writeMailboxName(args, mbox); args.writeString(user); // XXX - could be UTF-8? Response[] r = command("DELETEACL", args); Response response = r[r.length - 1]; // dispatch untagged responses notifyResponseHandlers(r); handleResult(response); } /** * GETACL Command. * * @param mbox the mailbox * @return the ACL array * @exception ProtocolException for protocol failures * @see "RFC2086" */ public ACL[] getACL(String mbox) throws ProtocolException { if (!hasCapability("ACL")) throw new BadCommandException("ACL not supported"); Argument args = new Argument(); writeMailboxName(args, mbox); Response[] r = command("GETACL", args); Response response = r[r.length - 1]; // Grab all ACL responses List v = new ArrayList<>(); if (response.isOK()) { // command succesful for (int i = 0, len = r.length; i < len; i++) { if (!(r[i] instanceof IMAPResponse)) continue; IMAPResponse ir = (IMAPResponse) r[i]; if (ir.keyEquals("ACL")) { // acl_data ::= "ACL" SPACE mailbox // *(SPACE identifier SPACE rights) // read name of mailbox and throw away ir.readAtomString(); String name = null; while ((name = ir.readAtomString()) != null) { String rights = ir.readAtomString(); if (rights == null) break; ACL acl = new ACL(name, new Rights(rights)); v.add(acl); } r[i] = null; } } } // dispatch remaining untagged responses notifyResponseHandlers(r); handleResult(response); return v.toArray(new ACL[0]); } /** * LISTRIGHTS Command. * * @param mbox the mailbox * @param user the user rights to return * @return the rights array * @exception ProtocolException for protocol failures * @see "RFC2086" */ public Rights[] listRights(String mbox, String user) throws ProtocolException { if (!hasCapability("ACL")) throw new BadCommandException("ACL not supported"); Argument args = new Argument(); writeMailboxName(args, mbox); args.writeString(user); // XXX - could be UTF-8? Response[] r = command("LISTRIGHTS", args); Response response = r[r.length - 1]; // Grab LISTRIGHTS response List v = new ArrayList<>(); if (response.isOK()) { // command succesful for (int i = 0, len = r.length; i < len; i++) { if (!(r[i] instanceof IMAPResponse)) continue; IMAPResponse ir = (IMAPResponse) r[i]; if (ir.keyEquals("LISTRIGHTS")) { // listrights_data ::= "LISTRIGHTS" SPACE mailbox // SPACE identifier SPACE rights *(SPACE rights) // read name of mailbox and throw away ir.readAtomString(); // read identifier and throw away ir.readAtomString(); String rights; while ((rights = ir.readAtomString()) != null) v.add(new Rights(rights)); r[i] = null; } } } // dispatch remaining untagged responses notifyResponseHandlers(r); handleResult(response); return v.toArray(new Rights[0]); } /** * MYRIGHTS Command. * * @param mbox the mailbox * @return the rights * @exception ProtocolException for protocol failures * @see "RFC2086" */ public Rights myRights(String mbox) throws ProtocolException { if (!hasCapability("ACL")) throw new BadCommandException("ACL not supported"); Argument args = new Argument(); writeMailboxName(args, mbox); Response[] r = command("MYRIGHTS", args); Response response = r[r.length - 1]; // Grab MYRIGHTS response Rights rights = null; if (response.isOK()) { // command succesful for (int i = 0, len = r.length; i < len; i++) { if (!(r[i] instanceof IMAPResponse)) continue; IMAPResponse ir = (IMAPResponse) r[i]; if (ir.keyEquals("MYRIGHTS")) { // myrights_data ::= "MYRIGHTS" SPACE mailbox SPACE rights // read name of mailbox and throw away ir.readAtomString(); String rs = ir.readAtomString(); if (rights == null) rights = new Rights(rs); r[i] = null; } } } // dispatch remaining untagged responses notifyResponseHandlers(r); handleResult(response); return rights; } /* * The tag used on the IDLE command. Set by idleStart() and * used in processIdleResponse() to determine if the response * is the matching end tag. */ private volatile String idleTag; /** * IDLE Command.

* * If the server supports the IDLE command extension, the IDLE * command is issued and this method blocks until a response has * been received. Once the first response has been received, the * IDLE command is terminated and all responses are collected and * handled and this method returns.

* * Note that while this method is blocked waiting for a response, * no other threads may issue any commands to the server that would * use this same connection. * * @exception ProtocolException for protocol failures * @since JavaMail 1.4.1 * @see "RFC2177" */ public synchronized void idleStart() throws ProtocolException { if (!hasCapability("IDLE")) throw new BadCommandException("IDLE not supported"); List v = new ArrayList<>(); boolean done = false; Response r = null; // write the command try { idleTag = writeCommand("IDLE", null); } catch (LiteralException lex) { v.add(lex.getResponse()); done = true; } catch (Exception ex) { // Convert this into a BYE response v.add(Response.byeResponse(ex)); done = true; } while (!done) { try { r = readResponse(); } catch (IOException ioex) { // convert this into a BYE response r = Response.byeResponse(ioex); } catch (ProtocolException pex) { continue; // skip this response } v.add(r); if (r.isContinuation() || r.isBYE()) done = true; } Response[] responses = v.toArray(new Response[0]); r = responses[responses.length - 1]; // dispatch remaining untagged responses notifyResponseHandlers(responses); if (!r.isContinuation()) handleResult(r); } /** * While an IDLE command is in progress, read a response * sent from the server. The response is read with no locks * held so that when the read blocks waiting for the response * from the server it's not holding locks that would prevent * other threads from interrupting the IDLE command. * * @return the response * @since JavaMail 1.4.1 */ public synchronized Response readIdleResponse() { if (idleTag == null) return null; // IDLE not in progress Response r = null; try { r = readResponse(); } catch (IOException | ProtocolException ioex) { // convert this into a BYE response r = Response.byeResponse(ioex); } return r; } /** * Process a response returned by readIdleResponse(). * This method will be called with appropriate locks * held so that the processing of the response is safe. * * @param r the response * @return true if IDLE is done * @exception ProtocolException for protocol failures * @since JavaMail 1.4.1 */ public boolean processIdleResponse(Response r) throws ProtocolException { Response[] responses = new Response[1]; responses[0] = r; boolean done = false; // done reading responses? notifyResponseHandlers(responses); if (r.isBYE()) // shouldn't wait for command completion response done = true; // If this is a matching command completion response, we are done if (r.isTagged() && r.getTag().equals(idleTag)) done = true; if (done) idleTag = null; // no longer in IDLE handleResult(r); return !done; } // the DONE command to break out of IDLE private static final byte[] DONE = {'D', 'O', 'N', 'E', '\r', '\n'}; /** * Abort an IDLE command. While one thread is blocked in * readIdleResponse(), another thread will use this method * to abort the IDLE command, which will cause the server * to send the closing tag for the IDLE command, which * readIdleResponse() and processIdleResponse() will see * and terminate the IDLE state. * * @since JavaMail 1.4.1 */ public void idleAbort() { OutputStream os = getOutputStream(); try { os.write(DONE); os.flush(); } catch (Exception ex) { // nothing to do, hope to detect it again later logger.log(Level.FINEST, "Exception aborting IDLE", ex); } } /** * ID Command. * * @param clientParams map of names and values * @return map of names and values from server * @exception ProtocolException for protocol failures * @since JavaMail 1.5.1 * @see "RFC 2971" */ public Map id(Map clientParams) throws ProtocolException { if (!hasCapability("ID")) throw new BadCommandException("ID not supported"); Response[] r = command("ID", ID.getArgumentList(clientParams)); ID id = null; Response response = r[r.length - 1]; // Grab ID response if (response.isOK()) { // command succesful for (int i = 0, len = r.length; i < len; i++) { if (!(r[i] instanceof IMAPResponse)) continue; IMAPResponse ir = (IMAPResponse) r[i]; if (ir.keyEquals("ID")) { if (id == null) id = new ID(ir); r[i] = null; } } } // dispatch remaining untagged responses notifyResponseHandlers(r); handleResult(response); return id == null ? null : id.getServerParams(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy