geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java [527:1989]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    protected boolean processSaslAuthentication() throws MessagingException {
        // if unable to get an appropriate authenticator, just fail it.
        ClientAuthenticator authenticator = getSaslAuthenticator();
        if (authenticator == null) {
            return false;
        }

        // go process the login.
        return processLogin(authenticator);
    }

    protected ClientAuthenticator getSaslAuthenticator() {
        return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
    }

    /**
     * Process SASL-type PLAIN authentication.
     *
     * @return Returns true if the login is accepted.
     * @exception MessagingException
     */
    protected boolean processPlainAuthentication() throws MessagingException {
        // go process the login.
        return processLogin(new PlainAuthenticator(authid, username, password));
    }


    /**
     * Process SASL-type LOGIN authentication.
     *
     * @return Returns true if the login is accepted.
     * @exception MessagingException
     */
    protected boolean processLoginAuthentication() throws MessagingException {
        // go process the login.
        return processLogin(new LoginAuthenticator(username, password));
    }


    /**
     * Process a LOGIN using the LOGIN command instead of AUTHENTICATE.
     *
     * @return true if the command succeeded, false for any authentication failures.
     * @exception MessagingException
     */
    protected boolean processLogin() throws MessagingException {
        // arguments are "LOGIN userid password"
        IMAPCommand command = new IMAPCommand("LOGIN");
        command.appendAtom(username);
        command.appendAtom(password);

        // go issue the command
        try {
            sendCommand(command);
        } catch (CommandFailedException e) {
            // we'll get a NO response for a rejected login
            return false;
        }
        // seemed to work ok....
        return true;
    }


    /**
     * Process a login using the provided authenticator object.
     *
     * NB:  This method is synchronized because we have a multi-step process going on
     * here.  No other commands should be sent to the server until we complete.
     *
     * @return Returns true if the server support a SASL authentication mechanism and
     * accepted reponse challenges.
     * @exception MessagingException
     */
    protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
        if (debug) {
            debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
        }

        IMAPCommand command = new IMAPCommand("AUTHENTICATE");
        // and tell the server which mechanism we're using.
        command.appendAtom(authenticator.getMechanismName());
        // send the command now

        try {
            IMAPTaggedResponse response = sendCommand(command);

            // now process the challenge sequence.  We get a 235 response back when the server accepts the
            // authentication, and a 334 indicates we have an additional challenge.
            while (true) {
                // this should be a continuation reply, if things are still good.
                if (response.isContinuation()) {
                    // we're passed back a challenge value, Base64 encoded.
                    byte[] challenge = response.decodeChallengeResponse();

                    // have the authenticator evaluate and send back the encoded response.
                    response = sendLine(Base64.encode(authenticator.evaluateChallenge(challenge)));
                }
                else {
                    // there are only two choices here, OK or a continuation.  OK means
                    // we've passed muster and are in.
                    return true;
                }
            }
        } catch (CommandFailedException e ) {
            // a failure at any point in this process will result in a "NO" response.
            // That causes an exception to get thrown, so just fail the login
            // if we get one.
            return false;
        }
    }


    /**
     * Return the server host for this connection.
     *
     * @return The String name of the server host.
     */
    public String getHost() {
        return serverHost;
    }


    /**
     * Attach a handler for untagged responses to this connection.
     *
     * @param h      The new untagged response handler.
     */
    public synchronized void addResponseHandler(IMAPUntaggedResponseHandler h) {
        responseHandlers.add(h);
    }


    /**
     * Remove a response handler from the connection.
     *
     * @param h      The handler to remove.
     */
    public synchronized void removeResponseHandler(IMAPUntaggedResponseHandler h) {
        responseHandlers.remove(h);
    }


    /**
     * Add a response to the pending untagged response queue.
     *
     * @param response The response to add.
     */
    public synchronized void queuePendingResponse(IMAPUntaggedResponse response) {
        queuedResponses.add(response);
    }

    /**
     * Process any untagged responses in the queue.  This will clear out
     * the queue, and send each response to the registered
     * untagged response handlers.
     */
    public void processPendingResponses() throws MessagingException {
        List pendingResponses = null;
        List handlerList = null;

        synchronized(this) {
            if (queuedResponses.isEmpty()) {
                return;
            }
            pendingResponses = queuedResponses;
            queuedResponses = new LinkedList();
            // get a copy of the response handlers so we can
            // release the connection lock before broadcasting
            handlerList = (List)responseHandlers.clone();
        }

        for (int i = 0; i < pendingResponses.size(); i++) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)pendingResponses.get(i);
            for (int j = 0; j < handlerList.size(); j++) {
                // broadcast to each handler.  If a handler returns true, then it
                // handled whatever this message required and we should skip sending
                // it to other handlers.
                IMAPUntaggedResponseHandler h = (IMAPUntaggedResponseHandler)handlerList.get(j);
                if (h.handleResponse(response)) {
                    break;
                }
            }
        }
    }

    /**
     * Extract a single response from the pending queue that
     * match a give keyword type.  All matching responses
     * are removed from the pending queue.
     *
     * @param type   The string name of the keyword.
     *
     * @return A List of all matching queued responses.
     */
    public IMAPUntaggedResponse extractResponse(String type) {
        Iterator i = queuedResponses.iterator();
        while (i.hasNext()) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
            // if this is of the target type, move it to the response set.
            if (response.isKeyword(type)) {
                i.remove();
                return response;
            }
        }
        return null;
    }

    /**
     * Extract all responses from the pending queue that
     * match a give keyword type.  All matching responses
     * are removed from the pending queue.
     *
     * @param type   The string name of the keyword.
     *
     * @return A List of all matching queued responses.
     */
    public List extractResponses(String type) {
        List responses = new ArrayList();

        Iterator i = queuedResponses.iterator();
        while (i.hasNext()) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
            // if this is of the target type, move it to the response set.
            if (response.isKeyword(type)) {
                i.remove();
                responses.add(response);
            }
        }
        return responses;
    }


    /**
     * Extract all responses from the pending queue that
     * are "FETCH" responses for a given message number.  All matching responses
     * are removed from the pending queue.
     *
     * @param type   The string name of the keyword.
     *
     * @return A List of all matching queued responses.
     */
    public List extractFetchResponses(int sequenceNumber) {
        List responses = new ArrayList();

        Iterator i = queuedResponses.iterator();
        while (i.hasNext()) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
            // if this is of the target type, move it to the response set.
            if (response.isKeyword("FETCH")) {
                IMAPFetchResponse fetch = (IMAPFetchResponse)response;
                // a response for the correct message number?
                if (fetch.sequenceNumber == sequenceNumber) {
                    // pluck these from the list and add to the response set.
                    i.remove();
                    responses.add(response);
                }
            }
        }
        return responses;
    }

    /**
     * Extract a fetch response data item from the queued elements.
     *
     * @param sequenceNumber
     *               The message number we're interested in.  Fetch responses for other messages
     *               will be skipped.
     * @param type   The type of body element we need. It is assumed that only one item for
     *               the given message number will exist in the queue.  The located item will
     *               be returned, and that fetch response will be removed from the pending queue.
     *
     * @return The target data item, or null if a match is not found.
     */
    protected IMAPFetchDataItem extractFetchDataItem(long sequenceNumber, int type)
    {
        Iterator i = queuedResponses.iterator();
        while (i.hasNext()) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
            // if this is of the target type, move it to the response set.
            if (response.isKeyword("FETCH")) {
                IMAPFetchResponse fetch = (IMAPFetchResponse)response;
                // a response for the correct message number?
                if (fetch.sequenceNumber == sequenceNumber) {
                    // does this response have the item we're looking for?
                    IMAPFetchDataItem item = fetch.getDataItem(type);
                    if (item != null) {
                        // remove this from the pending queue and return the
                        // located item
                        i.remove();
                        return item;
                    }
                }
            }
        }
        // not located, sorry
        return null;
    }

    /**
     * Extract a all fetch responses that contain a given data item.
     *
     * @param type   The type of body element we need. It is assumed that only one item for
     *               the given message number will exist in the queue.  The located item will
     *               be returned, and that fetch response will be removed from the pending queue.
     *
     * @return A List of all matching Fetch responses.
     */
    protected List extractFetchDataItems(int type)
    {
        Iterator i = queuedResponses.iterator();
        List items = new ArrayList();

        while (i.hasNext()) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
            // if this is of the target type, move it to the response set.
            if (response.isKeyword("FETCH")) {
                IMAPFetchResponse fetch = (IMAPFetchResponse)response;
                // does this response have the item we're looking for?
                IMAPFetchDataItem item = fetch.getDataItem(type);
                if (item != null) {
                    // remove this from the pending queue and return the
                    // located item
                    i.remove();
                    // we want the fetch response, not the data item, because
                    // we're going to require the message sequence number information
                    // too.
                    items.add(fetch);
                }
            }
        }
        // return whatever we have.
        return items;
    }

    /**
     * Make sure we have the latest status information available.  We
     * retreive this by sending a NOOP command to the server, and
     * processing any untagged responses we get back.
     */
    public void updateMailboxStatus() throws MessagingException {
        sendSimpleCommand("NOOP");
    }


    /**
     * check to see if this connection is truely alive.
     *
     * @param timeout The timeout value to control how often we ping
     *                the server to see if we're still good.
     *
     * @return true if the server is responding to requests, false for any
     *         connection errors.  This will also update the folder status
     *         by processing returned unsolicited messages.
     */
    public synchronized boolean isAlive(long timeout) {
        long lastUsed = System.currentTimeMillis() - lastAccess;
        if (lastUsed < timeout) {
            return true;
        }

        try {
            sendSimpleCommand("NOOP");
            return true;
        } catch (MessagingException e) {
            // the NOOP command will throw a MessagingException if we get anything
            // other than an OK response back from the server.
        }
        return false;
    }


    /**
     * Issue a fetch command to retrieve the message ENVELOPE structure.
     *
     * @param sequenceNumber The sequence number of the message.
     *
     * @return The IMAPResponse item containing the ENVELOPE information.
     */
    public synchronized List fetchEnvelope(int sequenceNumber) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("ENVELOPE INTERNALDATE RFC822.SIZE");
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        // these are fairly involved sets, so the caller needs to handle these.
        // we just return all of the FETCH results matching the target message number.
        return extractFetchResponses(sequenceNumber);
    }

    /**
     * Issue a FETCH command to retrieve the message BODYSTRUCTURE structure.
     *
     * @param sequenceNumber The sequence number of the message.
     *
     * @return The IMAPBodyStructure item for the message.
     *         All other untagged responses are queued for processing.
     */
    public synchronized IMAPBodyStructure fetchBodyStructure(int sequenceNumber) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("BODYSTRUCTURE");
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        // locate the response from this
        IMAPBodyStructure bodyStructure = (IMAPBodyStructure)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.BODYSTRUCTURE);

        if (bodyStructure == null) {
            throw new MessagingException("No BODYSTRUCTURE information received from IMAP server");
        }
        // and return the body structure directly.
        return bodyStructure;
    }


    /**
     * Issue a FETCH command to retrieve the message RFC822.HEADERS structure containing the message headers (using PEEK).
     *
     * @param sequenceNumber The sequence number of the message.
     *
     * @return The IMAPRFC822Headers item for the message.
     *         All other untagged responses are queued for processing.
     */
    public synchronized InternetHeaders fetchHeaders(int sequenceNumber, String part) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("BODY.PEEK");
        command.appendBodySection(part, "HEADER");
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        IMAPInternetHeader header = (IMAPInternetHeader)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.HEADER);

        if (header == null) {
            throw new MessagingException("No HEADER information received from IMAP server");
        }
        // and return the body structure directly.
        return header.headers;
    }


    /**
     * Issue a FETCH command to retrieve the message text
     *
     * @param sequenceNumber The sequence number of the message.
     *
     * @return The IMAPMessageText item for the message.
     *         All other untagged responses are queued for processing.
     */
    public synchronized IMAPMessageText fetchText(int sequenceNumber) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("BODY.PEEK");
        command.appendBodySection("TEXT");
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        IMAPMessageText text = (IMAPMessageText)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.TEXT);

        if (text == null) {
            throw new MessagingException("No TEXT information received from IMAP server");
        }
        // and return the body structure directly.
        return text;
    }


    /**
     * Issue a FETCH command to retrieve the message text
     *
     * @param sequenceNumber The sequence number of the message.
     *
     * @return The IMAPMessageText item for the message.
     *         All other untagged responses are queued for processing.
     */
    public synchronized IMAPMessageText fetchBodyPartText(int sequenceNumber, String section) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("BODY.PEEK");
        command.appendBodySection(section, "TEXT");
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        IMAPMessageText text = (IMAPMessageText)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.TEXT);

        if (text == null) {
            throw new MessagingException("No TEXT information received from IMAP server");
        }
        // and return the body structure directly.
        return text;
    }


    /**
     * Issue a FETCH command to retrieve the entire message body in one shot.
     * This may also be used to fetch an embedded message part as a unit.
     *
     * @param sequenceNumber
     *                The sequence number of the message.
     * @param section The section number to fetch.  If null, the entire body of the message
     *                is retrieved.
     *
     * @return The IMAPBody item for the message.
     *         All other untagged responses are queued for processing.
     * @exception MessagingException
     */
    public synchronized IMAPBody fetchBody(int sequenceNumber, String section) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("BODY.PEEK");
        // no part name here, only the section identifier.  This will fetch
        // the entire body, with all of the bits in place.
        command.appendBodySection(section, null);
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        IMAPBody body = (IMAPBody)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.BODY);

        if (body == null) {
            throw new MessagingException("No BODY information received from IMAP server");
        }
        // and return the body structure directly.
        return body;
    }


    /**
     * Fetch the message content.  This sorts out which method should be used
     * based on the server capability.
     *
     * @param sequenceNumber
     *               The sequence number of the target message.
     *
     * @return The byte[] content information.
     * @exception MessagingException
     */
    public byte[] fetchContent(int sequenceNumber) throws MessagingException {
        // fetch the text item and return the data
        IMAPMessageText text = fetchText(sequenceNumber);
        return text.getContent();
    }


    /**
     * Fetch the message content.  This sorts out which method should be used
     * based on the server capability.
     *
     * @param sequenceNumber
     *               The sequence number of the target message.
     *
     * @return The byte[] content information.
     * @exception MessagingException
     */
    public byte[] fetchContent(int sequenceNumber, String section) throws MessagingException {
        if (section == null) {
            IMAPMessageText text = fetchText(sequenceNumber);
            return text.getContent();
        } else {
            IMAPBody body = fetchBody(sequenceNumber, section);
            return body.getContent();
        }
    }


    /**
     * Send an LIST command to the IMAP server, returning all LIST
     * response information.
     *
     * @param mailbox The reference mailbox name sent on the command.
     * @param pattern The match pattern used on the name.
     *
     * @return A List of all LIST response information sent back from the server.
     */
    public synchronized List list(String mailbox, String pattern) throws MessagingException {
        IMAPCommand command = new IMAPCommand("LIST");

        // construct the command, encoding the tokens as required by the content.
        command.appendEncodedString(mailbox);
        command.appendEncodedString(pattern);

        sendCommand(command);

        // pull out the ones we're interested in
        return extractResponses("LIST");
    }


    /**
     * Send an LSUB command to the IMAP server, returning all LSUB
     * response information.
     *
     * @param mailbox The reference mailbox name sent on the command.
     * @param pattern The match pattern used on the name.
     *
     * @return A List of all LSUB response information sent back from the server.
     */
    public List listSubscribed(String mailbox, String pattern) throws MessagingException {
        IMAPCommand command = new IMAPCommand("LSUB");

        // construct the command, encoding the tokens as required by the content.
        command.appendEncodedString(mailbox);
        command.appendEncodedString(pattern);

        sendCommand(command);
        // pull out the ones we're interested in
        return extractResponses("LSUB");
    }


    /**
     * Subscribe to a give mailbox.
     *
     * @param mailbox The desired mailbox name.
     *
     * @exception MessagingException
     */
    public void subscribe(String mailbox) throws MessagingException {
        IMAPCommand command = new IMAPCommand("SUBSCRIBE");
        // add on the encoded mailbox name, as the appropriate token type.
        command.appendEncodedString(mailbox);

        // send this, and ignore the response.
        sendSimpleCommand(command);
    }


    /**
     * Unsubscribe from a mailbox.
     *
     * @param mailbox The mailbox to remove.
     *
     * @exception MessagingException
     */
    public void unsubscribe(String mailbox) throws MessagingException {
        IMAPCommand command = new IMAPCommand("UNSUBSCRIBE");
        // add on the encoded mailbox name, as the appropriate token type.
        command.appendEncodedString(mailbox);

        // send this, and ignore the response.
        sendSimpleCommand(command);
    }


    /**
     * Create a mailbox.
     *
     * @param mailbox The desired new mailbox name (fully qualified);
     *
     * @exception MessagingException
     */
    public void createMailbox(String mailbox) throws MessagingException {
        IMAPCommand command = new IMAPCommand("CREATE");
        // add on the encoded mailbox name, as the appropriate token type.
        command.appendEncodedString(mailbox);

        // send this, and ignore the response.
        sendSimpleCommand(command);
    }


    /**
     * Delete a mailbox.
     *
     * @param mailbox The target mailbox name (fully qualified);
     *
     * @exception MessagingException
     */
    public void deleteMailbox(String mailbox) throws MessagingException {
        IMAPCommand command = new IMAPCommand("DELETE");
        // add on the encoded mailbox name, as the appropriate token type.
        command.appendEncodedString(mailbox);

        // send this, and ignore the response.
        sendSimpleCommand(command);
    }


    /**
     * Rename a mailbox.
     *
     * @param mailbox The target mailbox name (fully qualified);
     *
     * @exception MessagingException
     */
    public void renameMailbox(String oldName, String newName) throws MessagingException {
        IMAPCommand command = new IMAPCommand("RENAME");
        // add on the encoded mailbox name, as the appropriate token type.
        command.appendEncodedString(oldName);
        command.appendEncodedString(newName);

        // send this, and ignore the response.
        sendSimpleCommand(command);
    }


    /**
     * Retrieve a complete set of status items for a mailbox.
     *
     * @param mailbox The mailbox name.
     *
     * @return An IMAPMailboxStatus item filled in with the STATUS responses.
     * @exception MessagingException
     */
    public synchronized IMAPMailboxStatus getMailboxStatus(String mailbox) throws MessagingException {
        IMAPCommand command = new IMAPCommand("STATUS");

        // construct the command, encoding the tokens as required by the content.
        command.appendEncodedString(mailbox);
        // request all of the status items
        command.append(" (MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)");

        sendCommand(command);

        // now harvest each of the respon
        IMAPMailboxStatus status = new IMAPMailboxStatus();
        status.mergeSizeResponses(extractResponses("EXISTS"));
        status.mergeSizeResponses(extractResponses("RECENT"));
        status.mergeOkResponses(extractResponses("UIDNEXT"));
        status.mergeOkResponses(extractResponses("UIDVALIDITY"));
        status.mergeOkResponses(extractResponses("UNSEEN"));
        status.mergeStatus((IMAPStatusResponse)extractResponse("STATUS"));
        status.mergeStatus((IMAPPermanentFlagsResponse)extractResponse("PERMANENTFLAGS"));

        return status;
    }


    /**
     * Select a mailbox, returning the accumulated status information
     * about the mailbox returned with the response.
     *
     * @param mailbox  The desired mailbox name.
     * @param readOnly The open mode.  If readOnly is true, the mailbox is opened
     *                 using EXAMINE rather than SELECT.
     *
     * @return A status object containing the mailbox particulars.
     * @exception MessagingException
     */
    public synchronized IMAPMailboxStatus openMailbox(String mailbox, boolean readOnly) throws MessagingException {
        IMAPCommand command = new IMAPCommand();

        // if readOnly is required, we use EXAMINE to switch to the mailbox rather than SELECT.
        // This returns the same response information, but the mailbox will not accept update operations.
        if (readOnly) {
            command.appendAtom("EXAMINE");
        }
        else {
            command.appendAtom("SELECT");
        }

        // construct the command, encoding the tokens as required by the content.
        command.appendEncodedString(mailbox);

        // issue the select
        IMAPTaggedResponse response = sendCommand(command);

        IMAPMailboxStatus status = new IMAPMailboxStatus();
        // set the mode to the requested open mode.
        status.mode = readOnly ? Folder.READ_ONLY : Folder.READ_WRITE;

        // the server might disagree on the mode, so check to see if
        // it's telling us READ-ONLY.
        if (response.hasStatus("READ-ONLY")) {
            status.mode = Folder.READ_ONLY;
        }

        // some of these are required, some are optional.
        status.mergeFlags((IMAPFlagsResponse)extractResponse("FLAGS"));
        status.mergeStatus((IMAPSizeResponse)extractResponse("EXISTS"));
        status.mergeStatus((IMAPSizeResponse)extractResponse("RECENT"));
        status.mergeStatus((IMAPOkResponse)extractResponse("UIDVALIDITY"));
        status.mergeStatus((IMAPOkResponse)extractResponse("UNSEEN"));
        status.mergeStatus((IMAPPermanentFlagsResponse)extractResponse("PERMANENTFLAGS"));
        // mine the response for status information about the selected mailbox.
        return status;
    }


    /**
     * Tells the IMAP server to expunge messages marked for deletion.
     * The server will send us an untagged EXPUNGE message back for
     * each deleted message.  For explicit expunges we request, we'll
     * grabbed the untagged responses here, rather than force them to
     * be handled as pending responses.  The caller will handle the
     * updates directly.
     *
     * @exception MessagingException
     */
    public synchronized List expungeMailbox() throws MessagingException {
        // send the message, and make sure we got an OK response
        sendCommand("EXPUNGE");
        // extract all of the expunged responses and return.
        return extractResponses("EXPUNGED");
    }

    public int[] searchMailbox(SearchTerm term) throws MessagingException {
        return searchMailbox("ALL", term);
    }

    /**
     * Send a search to the IMAP server using the specified
     * messages selector and search term.  This figures out what
     * to do with CHARSET on the SEARCH command.
     *
     * @param messages The list of messages (comma-separated numbers or "ALL").
     * @param term     The desired search criteria
     *
     * @return Returns an int[] array of message numbers for all matched messages.
     * @exception MessagingException
     */
    public int[] searchMailbox(String messages, SearchTerm term) throws MessagingException {
        // don't use a charset by default, but we need to look at the data to see if we have a problem.
        String charset = null;

        if (IMAPCommand.checkSearchEncoding(term)) {
            // not sure exactly how to decide what to use here.  Two immediate possibilities come to mind,
            // UTF-8 or the MimeUtility.getDefaultJavaCharset() value.  Running a small test against the
            // Sun impl shows them sending a CHARSET value of UTF-8, so that sounds like the winner.  I don't
            // believe there's anything in the CAPABILITY response that would tell us what to use.
            charset = "UTF-8";
        }

        return searchMailbox(messages, term, charset);
    }

    /**
     * Send a search to the IMAP server using the specified
     * messages selector and search term.
     *
     * @param messages The list of messages (comma-separated numbers or "ALL").
     * @param charset  The charset specifier to send to the server.  If null, then
     *                 the CHARSET keyword is omitted.
     * @param term     The desired search criteria
     *
     * @return Returns an int[] array of message numbers for all matched messages.
     * @exception MessagingException
     */
    public synchronized int[] searchMailbox(String messages, SearchTerm term, String charset) throws MessagingException {
        IMAPCommand command = new IMAPCommand("SEARCH");

        // if we have an explicit charset to use, append that.
        if (charset != null) {
            command.appendAtom("CHARSET");
            command.appendAtom(charset);
        }

        // now go through the process of translating the javamail SearchTerm objects into
        // the IMAP command sequence.  The SearchTerm sequence may be a complex tree of comparison terms,
        // so this is not a simple process.
        command.appendSearchTerm(term, charset);
        // need to append the message set
        command.appendAtom(messages);

        // now issue the composed command.
        sendCommand(command);

        // get the list of search responses
        IMAPSearchResponse hits = (IMAPSearchResponse)extractResponse("SEARCH");
        // and return the message hits
        return hits.messageNumbers;
    }


    /**
     * Append a message to a mailbox, given the direct message data.
     *
     * @param mailbox The target mailbox name.
     * @param messageFlags
     *                The initial flag set for the appended message.
     * @param messageDate
     *                The received date the message is created with,
     * @param messageData
     *                The RFC822 Message data stored on the server.
     *
     * @exception MessagingException
     */
    public void appendMessage(String mailbox, Date messageDate, Flags messageFlags, byte[] messageData) throws MessagingException {
        IMAPCommand command = new IMAPCommand("APPEND");

        // the mailbox is encoded.
        command.appendEncodedString(mailbox);

        if (messageFlags != null) {
            // the flags are pulled from an existing object.  We can set most flag values, but the servers
            // reserve RECENT for themselves.  We need to force that one off.
            messageFlags.remove(Flags.Flag.RECENT);
            // and add the flag list to the commmand.
            command.appendFlags(messageFlags);
        }

        if (messageDate != null) {
            command.appendDate(messageDate);
        }

        // this gets appended as a literal.
        command.appendLiteral(messageData);
        // just send this as a simple command...we don't deal with the response other than to verifiy
        // it was ok.
        sendSimpleCommand(command);
    }

    /**
     * Fetch the flag set for a given message sequence number.
     *
     * @param sequenceNumber
     *               The message sequence number.
     *
     * @return The Flags defined for this message.
     * @exception MessagingException
     */
    public synchronized Flags fetchFlags(int sequenceNumber) throws MessagingException {
        // we want just the flag item here.
        sendCommand("FETCH " + String.valueOf(sequenceNumber) + " (FLAGS)");
        // get the return data item, and get the flags from within it
        IMAPFlags flags = (IMAPFlags)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.FLAGS);
        return flags.flags;
    }


    /**
     * Set the flags for a range of messages.
     *
     * @param messageSet The set of message numbers.
     * @param flags      The new flag settings.
     * @param set        true if the flags should be set, false for a clear operation.
     *
     * @return A list containing all of the responses with the new flag values.
     * @exception MessagingException
     */
    public synchronized List setFlags(String messageSet, Flags flags, boolean set) throws MessagingException {
        IMAPCommand command = new IMAPCommand("STORE");
        command.appendAtom(messageSet);
        // the command varies depending on whether this is a set or clear operation
        if (set) {
            command.appendAtom("+FLAGS");
        }
        else {
            command.appendAtom("-FLAGS");
        }

        // append the flag set
        command.appendFlags(flags);

        // we want just the flag item here.
        sendCommand(command);
        // we should have a FETCH response for each of the updated messages.  Return this
        // response, and update the message numbers.
        return extractFetchDataItems(IMAPFetchDataItem.FLAGS);
    }


    /**
     * Set the flags for a single message.
     *
     * @param sequenceNumber
     *               The sequence number of target message.
     * @param flags  The new flag settings.
     * @param set    true if the flags should be set, false for a clear operation.
     *
     * @exception MessagingException
     */
    public synchronized Flags setFlags(int sequenceNumber, Flags flags, boolean set) throws MessagingException {
        IMAPCommand command = new IMAPCommand("STORE");
        command.appendInteger(sequenceNumber);
        // the command varies depending on whether this is a set or clear operation
        if (set) {
            command.appendAtom("+FLAGS");
        }
        else {
            command.appendAtom("-FLAGS");
        }

        // append the flag set
        command.appendFlags(flags);

        // we want just the flag item here.
        sendCommand(command);
        // get the return data item, and get the flags from within it
        IMAPFlags flagResponse = (IMAPFlags)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.FLAGS);
        return flagResponse.flags;
    }


    /**
     * Copy a range of messages to a target mailbox.
     *
     * @param messageSet The set of message numbers.
     * @param target     The target mailbox name.
     *
     * @exception MessagingException
     */
    public void copyMessages(String messageSet, String target) throws MessagingException {
        IMAPCommand command = new IMAPCommand("COPY");
        // the auth command initiates the handshaking.
        command.appendAtom(messageSet);
        // the mailbox is encoded.
        command.appendEncodedString(target);
        // just send this as a simple command...we don't deal with the response other than to verifiy
        // it was ok.
        sendSimpleCommand(command);
    }


    /**
     * Fetch the message number for a give UID.
     *
     * @param uid    The target UID
     *
     * @return An IMAPUid object containing the mapping information.
     */
    public synchronized IMAPUid getSequenceNumberForUid(long uid) throws MessagingException {
        IMAPCommand command = new IMAPCommand("UID FETCH");
        command.appendLong(uid);
        command.appendAtom("(UID)");

        // this situation is a little strange, so it deserves a little explanation.
        // We need the message sequence number for this message from a UID value.
        // we're going to send a UID FETCH command, requesting the UID value back.
        // That seems strange, but the * nnnn FETCH response for the request will
        // be tagged with the message sequence number.  THAT'S the information we
        // really want, and it will be included in the IMAPUid object.

        sendCommand(command);
        // ok, now we need to search through these looking for a FETCH response with a UID element.
        List responses = extractResponses("FETCH");

        // we're looking for a fetch response with a UID data item with the UID information
        // inside of it.
        for (int i = 0; i < responses.size(); i++) {
            IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
            IMAPUid item = (IMAPUid)response.getDataItem(IMAPFetchDataItem.UID);
            // is this the response we're looking for?  The information we
            // need is the message number returned with the response, which is
            // also contained in the UID item.
            if (item != null && item.uid == uid) {
                return item;
            }
            // not one meant for us, add it back to the pending queue.
            queuePendingResponse(response);
        }
        // didn't find this one
        return null;
    }


    /**
     * Fetch the message numbers for a consequetive range
     * of UIDs.
     *
     * @param start  The start of the range.
     * @param end    The end of the uid range.
     *
     * @return A list of UID objects containing the mappings.
     */
    public synchronized List getSequenceNumbersForUids(long start, long end) throws MessagingException {
        IMAPCommand command = new IMAPCommand("UID FETCH");
        // send the request for the range "start:end" so we can fetch all of the info
        // at once.
        command.appendLong(start);
        command.append(":");
        // not the special range marker?  Just append the
        // number.  The LASTUID value needs to be "*" on the command.
        if (end != UIDFolder.LASTUID) {
            command.appendLong(end);
        }
        else {
            command.append("*");
        }
        command.appendAtom("(UID)");

        // this situation is a little strange, so it deserves a little explanation.
        // We need the message sequence number for this message from a UID value.
        // we're going to send a UID FETCH command, requesting the UID value back.
        // That seems strange, but the * nnnn FETCH response for the request will
        // be tagged with the message sequence number.  THAT'S the information we
        // really want, and it will be included in the IMAPUid object.

        sendCommand(command);
        // ok, now we need to search through these looking for a FETCH response with a UID element.
        List responses = extractResponses("FETCH");

        List uids = new ArrayList((int)(end - start + 1));

        // we're looking for a fetch response with a UID data item with the UID information
        // inside of it.
        for (int i = 0; i < responses.size(); i++) {
            IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
            IMAPUid item = (IMAPUid)response.getDataItem(IMAPFetchDataItem.UID);
            // is this the response we're looking for?  The information we
            // need is the message number returned with the response, which is
            // also contained in the UID item.
            if (item != null) {
                uids.add(item);
            }
            else {
                // not one meant for us, add it back to the pending queue.
                queuePendingResponse(response);
            }
        }
        // return the list of uids we located.
        return uids;
    }


    /**
     * Fetch the UID value for a target message number
     *
     * @param sequenceNumber
     *               The target message number.
     *
     * @return An IMAPUid object containing the mapping information.
     */
    public synchronized IMAPUid getUidForSequenceNumber(int sequenceNumber) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.appendAtom("(UID)");

        // similar to the other fetches, but without the strange bit.  We're starting
        // with the message number in this case.

        sendCommand(command);

        // ok, now we need to search through these looking for a FETCH response with a UID element.
        return (IMAPUid)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.UID);
    }


    /**
     * Retrieve the user name space info from the server.
     *
     * @return An IMAPNamespace response item with the information.  If the server
     *         doesn't support the namespace extension, an empty one is returned.
     */
    public synchronized IMAPNamespaceResponse getNamespaces() throws MessagingException {
        // if no namespace capability, then return an empty
        // response, which will trigger the default behavior.
        if (!hasCapability("NAMESPACE")) {
            return new IMAPNamespaceResponse();
        }
        // no arguments on this command, so just send an hope it works.
        sendCommand("NAMESPACE");

        // this should be here, since it's a required response when the
        // command worked.  Just extract, and return.
        return (IMAPNamespaceResponse)extractResponse("NAMESPACE");
    }


    /**
     * Prefetch message information based on the request profile.  We'll return
     * all of the fetch information to the requesting Folder, which will sort
     * out what goes where.
     *
     * @param messageSet The set of message numbers we need to fetch.
     * @param profile    The profile of the required information.
     *
     * @return All FETCH responses resulting from the command.
     * @exception MessagingException
     */
    public synchronized List fetch(String messageSet, FetchProfile profile) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendAtom(messageSet);
        // this is the set of items to append
        command.appendFetchProfile(profile);

        // now send the fetch command, which will likely send back a lot of "FETCH" responses.
        // Suck all of those reponses out of the queue and send them back for processing.
        sendCommand(command);
        // we can have a large number of messages here, so just grab all of the fetches
        // we get back, and let the Folder sort out who gets what.
        return extractResponses("FETCH");
    }


    /**
     * Set the ACL rights for a mailbox.  This replaces
     * any existing ACLs defined.
     *
     * @param mailbox The target mailbox.
     * @param acl     The new ACL to be used for the mailbox.
     *
     * @exception MessagingException
     */
    public synchronized void setACLRights(String mailbox, ACL acl) throws MessagingException {
        IMAPCommand command = new IMAPCommand("SETACL");
        command.appendEncodedString(mailbox);

        command.appendACL(acl);

        sendSimpleCommand(command);
    }


    /**
     * Add a set of ACL rights to a mailbox.
     *
     * @param mailbox The mailbox to alter.
     * @param acl     The ACL to add.
     *
     * @exception MessagingException
     */
    public synchronized void addACLRights(String mailbox, ACL acl) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("SETACL");
        command.appendEncodedString(mailbox);

        command.appendACL(acl, "+");

        sendSimpleCommand(command);
    }


    /**
     * Remove an ACL from a given mailbox.
     *
     * @param mailbox The mailbox to alter.
     * @param acl     The particular ACL to revoke.
     *
     * @exception MessagingException
     */
    public synchronized void removeACLRights(String mailbox, ACL acl) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("SETACL");
        command.appendEncodedString(mailbox);

        command.appendACL(acl, "-");

        sendSimpleCommand(command);
    }


    /**
     * Get the ACL rights assigned to a given mailbox.
     *
     * @param mailbox The target mailbox.
     *
     * @return The an array of ACL items describing the access
     *         rights to the mailbox.
     * @exception MessagingException
     */
    public synchronized ACL[] getACLRights(String mailbox) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("GETACL");
        command.appendEncodedString(mailbox);

        // now send the GETACL command, which will return a single ACL untagged response.
        sendCommand(command);
        // there should be just a single ACL response back from this command.
        IMAPACLResponse response = (IMAPACLResponse)extractResponse("ACL");
        return response.acls;
    }


    /**
     * Get the current user's ACL rights to a given mailbox.
     *
     * @param mailbox The target mailbox.
     *
     * @return The Rights associated with this mailbox.
     * @exception MessagingException
     */
    public synchronized Rights getMyRights(String mailbox) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("MYRIGHTS");
        command.appendEncodedString(mailbox);

        // now send the MYRIGHTS command, which will return a single MYRIGHTS untagged response.
        sendCommand(command);
        // there should be just a single MYRIGHTS response back from this command.
        IMAPMyRightsResponse response = (IMAPMyRightsResponse)extractResponse("MYRIGHTS");
        return response.rights;
    }


    /**
     * List the ACL rights that a particular user has
     * to a mailbox.
     *
     * @param mailbox The target mailbox.
     * @param name    The user we're querying.
     *
     * @return An array of rights the use has to this mailbox.
     * @exception MessagingException
     */
    public synchronized Rights[] listACLRights(String mailbox, String name) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("LISTRIGHTS");
        command.appendEncodedString(mailbox);
        command.appendString(name);

        // now send the GETACL command, which will return a single ACL untagged response.
        sendCommand(command);
        // there should be just a single ACL response back from this command.
        IMAPListRightsResponse response = (IMAPListRightsResponse)extractResponse("LISTRIGHTS");
        return response.rights;
    }


    /**
     * Delete an ACL item for a given user name from
     * a target mailbox.
     *
     * @param mailbox The mailbox we're altering.
     * @param name    The user name.
     *
     * @exception MessagingException
     */
    public synchronized void deleteACL(String mailbox, String name) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("DELETEACL");
        command.appendEncodedString(mailbox);
        command.appendString(name);

        // just send the command.  No response to handle.
        sendSimpleCommand(command);
    }

    /**
     * Fetch the quota root information for a target mailbox.
     *
     * @param mailbox The mailbox of interest.
     *
     * @return An array of quotas describing all of the quota roots
     *         that apply to the target mailbox.
     * @exception MessagingException
     */
    public synchronized Quota[] fetchQuotaRoot(String mailbox) throws MessagingException {
        if (!hasCapability("QUOTA")) {
            throw new MethodNotSupportedException("QUOTA not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("GETQUOTAROOT");
        command.appendEncodedString(mailbox);

        // This will return a single QUOTAROOT response, plust a series of QUOTA responses for
        // each root names in the first response.
        sendCommand(command);
        // we don't really need this, but pull it from the response queue anyway.
        extractResponse("QUOTAROOT");

        // now get the real meat of the matter
        List responses = extractResponses("QUOTA");

        // now copy all of the returned quota items into the response array.
        Quota[] quotas = new Quota[responses.size()];
        for (int i = 0; i < quotas.length; i++) {
            IMAPQuotaResponse q = (IMAPQuotaResponse)responses.get(i);
            quotas[i] = q.quota;
        }

        return quotas;
    }

    /**
     * Fetch QUOTA information from a named QUOTE root.
     *
     * @param root   The target root name.
     *
     * @return An array of Quota items associated with that root name.
     * @exception MessagingException
     */
    public synchronized Quota[] fetchQuota(String root) throws MessagingException {
        if (!hasCapability("QUOTA")) {
            throw new MethodNotSupportedException("QUOTA not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("GETQUOTA");
        command.appendString(root);

        // This will return a single QUOTAROOT response, plust a series of QUOTA responses for
        // each root names in the first response.
        sendCommand(command);

        // now get the real meat of the matter
        List responses = extractResponses("QUOTA");

        // now copy all of the returned quota items into the response array.
        Quota[] quotas = new Quota[responses.size()];
        for (int i = 0; i < quotas.length; i++) {
            IMAPQuotaResponse q = (IMAPQuotaResponse)responses.get(i);
            quotas[i] = q.quota;
        }

        return quotas;
    }

    /**
     * Set a Quota item for the currently accessed
     * userid/folder resource.
     *
     * @param quota  The new QUOTA information.
     *
     * @exception MessagingException
     */
    public synchronized void setQuota(Quota quota) throws MessagingException {
        if (!hasCapability("QUOTA")) {
            throw new MethodNotSupportedException("QUOTA not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("GETQUOTA");
        // this gets appended as a list of resource values
        command.appendQuota(quota);

        // This will return a single QUOTAROOT response, plust a series of QUOTA responses for
        // each root names in the first response.
        sendCommand(command);
        // we don't really need this, but pull it from the response queue anyway.
        extractResponses("QUOTA");
    }


    /**
     * Test if this connection has a given capability.
     *
     * @param capability The capability name.
     *
     * @return true if this capability is in the list, false for a mismatch.
     */
    public boolean hasCapability(String capability) {
        if (capabilities == null) {
            return false;
        }
        return capabilities.containsKey(capability);
    }

    /**
     * Tag this connection as having been closed by the
     * server.  This will not be returned to the
     * connection pool.
     */
    public void setClosed() {
        closed = true;
    }

    /**
     * Test if the connnection has been forcibly closed.
     *
     * @return True if the server disconnected the connection.
     */
    public boolean isClosed() {
        return closed;
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java [556:2018]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    protected boolean processSaslAuthentication() throws MessagingException {
        // if unable to get an appropriate authenticator, just fail it.
        ClientAuthenticator authenticator = getSaslAuthenticator();
        if (authenticator == null) {
            return false;
        }

        // go process the login.
        return processLogin(authenticator);
    }

    protected ClientAuthenticator getSaslAuthenticator() {
        return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
    }

    /**
     * Process SASL-type PLAIN authentication.
     *
     * @return Returns true if the login is accepted.
     * @exception MessagingException
     */
    protected boolean processPlainAuthentication() throws MessagingException {
        // go process the login.
        return processLogin(new PlainAuthenticator(authid, username, password));
    }


    /**
     * Process SASL-type LOGIN authentication.
     *
     * @return Returns true if the login is accepted.
     * @exception MessagingException
     */
    protected boolean processLoginAuthentication() throws MessagingException {
        // go process the login.
        return processLogin(new LoginAuthenticator(username, password));
    }


    /**
     * Process a LOGIN using the LOGIN command instead of AUTHENTICATE.
     *
     * @return true if the command succeeded, false for any authentication failures.
     * @exception MessagingException
     */
    protected boolean processLogin() throws MessagingException {
        // arguments are "LOGIN userid password"
        IMAPCommand command = new IMAPCommand("LOGIN");
        command.appendAtom(username);
        command.appendAtom(password);

        // go issue the command
        try {
            sendCommand(command);
        } catch (CommandFailedException e) {
            // we'll get a NO response for a rejected login
            return false;
        }
        // seemed to work ok....
        return true;
    }


    /**
     * Process a login using the provided authenticator object.
     *
     * NB:  This method is synchronized because we have a multi-step process going on
     * here.  No other commands should be sent to the server until we complete.
     *
     * @return Returns true if the server support a SASL authentication mechanism and
     * accepted reponse challenges.
     * @exception MessagingException
     */
    protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
        if (debug) {
            debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
        }

        IMAPCommand command = new IMAPCommand("AUTHENTICATE");
        // and tell the server which mechanism we're using.
        command.appendAtom(authenticator.getMechanismName());
        // send the command now

        try {
            IMAPTaggedResponse response = sendCommand(command);

            // now process the challenge sequence.  We get a 235 response back when the server accepts the
            // authentication, and a 334 indicates we have an additional challenge.
            while (true) {
                // this should be a continuation reply, if things are still good.
                if (response.isContinuation()) {
                    // we're passed back a challenge value, Base64 encoded.
                    byte[] challenge = response.decodeChallengeResponse();

                    // have the authenticator evaluate and send back the encoded response.
                    response = sendLine(Base64.encode(authenticator.evaluateChallenge(challenge)));
                }
                else {
                    // there are only two choices here, OK or a continuation.  OK means
                    // we've passed muster and are in.
                    return true;
                }
            }
        } catch (CommandFailedException e ) {
            // a failure at any point in this process will result in a "NO" response.
            // That causes an exception to get thrown, so just fail the login
            // if we get one.
            return false;
        }
    }


    /**
     * Return the server host for this connection.
     *
     * @return The String name of the server host.
     */
    public String getHost() {
        return serverHost;
    }


    /**
     * Attach a handler for untagged responses to this connection.
     *
     * @param h      The new untagged response handler.
     */
    public synchronized void addResponseHandler(IMAPUntaggedResponseHandler h) {
        responseHandlers.add(h);
    }


    /**
     * Remove a response handler from the connection.
     *
     * @param h      The handler to remove.
     */
    public synchronized void removeResponseHandler(IMAPUntaggedResponseHandler h) {
        responseHandlers.remove(h);
    }


    /**
     * Add a response to the pending untagged response queue.
     *
     * @param response The response to add.
     */
    public synchronized void queuePendingResponse(IMAPUntaggedResponse response) {
        queuedResponses.add(response);
    }

    /**
     * Process any untagged responses in the queue.  This will clear out
     * the queue, and send each response to the registered
     * untagged response handlers.
     */
    public void processPendingResponses() throws MessagingException {
        List pendingResponses = null;
        List handlerList = null;

        synchronized(this) {
            if (queuedResponses.isEmpty()) {
                return;
            }
            pendingResponses = queuedResponses;
            queuedResponses = new LinkedList();
            // get a copy of the response handlers so we can
            // release the connection lock before broadcasting
            handlerList = (List)responseHandlers.clone();
        }

        for (int i = 0; i < pendingResponses.size(); i++) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)pendingResponses.get(i);
            for (int j = 0; j < handlerList.size(); j++) {
                // broadcast to each handler.  If a handler returns true, then it
                // handled whatever this message required and we should skip sending
                // it to other handlers.
                IMAPUntaggedResponseHandler h = (IMAPUntaggedResponseHandler)handlerList.get(j);
                if (h.handleResponse(response)) {
                    break;
                }
            }
        }
    }

    /**
     * Extract a single response from the pending queue that
     * match a give keyword type.  All matching responses
     * are removed from the pending queue.
     *
     * @param type   The string name of the keyword.
     *
     * @return A List of all matching queued responses.
     */
    public IMAPUntaggedResponse extractResponse(String type) {
        Iterator i = queuedResponses.iterator();
        while (i.hasNext()) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
            // if this is of the target type, move it to the response set.
            if (response.isKeyword(type)) {
                i.remove();
                return response;
            }
        }
        return null;
    }

    /**
     * Extract all responses from the pending queue that
     * match a give keyword type.  All matching responses
     * are removed from the pending queue.
     *
     * @param type   The string name of the keyword.
     *
     * @return A List of all matching queued responses.
     */
    public List extractResponses(String type) {
        List responses = new ArrayList();

        Iterator i = queuedResponses.iterator();
        while (i.hasNext()) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
            // if this is of the target type, move it to the response set.
            if (response.isKeyword(type)) {
                i.remove();
                responses.add(response);
            }
        }
        return responses;
    }


    /**
     * Extract all responses from the pending queue that
     * are "FETCH" responses for a given message number.  All matching responses
     * are removed from the pending queue.
     *
     * @param type   The string name of the keyword.
     *
     * @return A List of all matching queued responses.
     */
    public List extractFetchResponses(int sequenceNumber) {
        List responses = new ArrayList();

        Iterator i = queuedResponses.iterator();
        while (i.hasNext()) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
            // if this is of the target type, move it to the response set.
            if (response.isKeyword("FETCH")) {
                IMAPFetchResponse fetch = (IMAPFetchResponse)response;
                // a response for the correct message number?
                if (fetch.sequenceNumber == sequenceNumber) {
                    // pluck these from the list and add to the response set.
                    i.remove();
                    responses.add(response);
                }
            }
        }
        return responses;
    }

    /**
     * Extract a fetch response data item from the queued elements.
     *
     * @param sequenceNumber
     *               The message number we're interested in.  Fetch responses for other messages
     *               will be skipped.
     * @param type   The type of body element we need. It is assumed that only one item for
     *               the given message number will exist in the queue.  The located item will
     *               be returned, and that fetch response will be removed from the pending queue.
     *
     * @return The target data item, or null if a match is not found.
     */
    protected IMAPFetchDataItem extractFetchDataItem(long sequenceNumber, int type)
    {
        Iterator i = queuedResponses.iterator();
        while (i.hasNext()) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
            // if this is of the target type, move it to the response set.
            if (response.isKeyword("FETCH")) {
                IMAPFetchResponse fetch = (IMAPFetchResponse)response;
                // a response for the correct message number?
                if (fetch.sequenceNumber == sequenceNumber) {
                    // does this response have the item we're looking for?
                    IMAPFetchDataItem item = fetch.getDataItem(type);
                    if (item != null) {
                        // remove this from the pending queue and return the
                        // located item
                        i.remove();
                        return item;
                    }
                }
            }
        }
        // not located, sorry
        return null;
    }

    /**
     * Extract a all fetch responses that contain a given data item.
     *
     * @param type   The type of body element we need. It is assumed that only one item for
     *               the given message number will exist in the queue.  The located item will
     *               be returned, and that fetch response will be removed from the pending queue.
     *
     * @return A List of all matching Fetch responses.
     */
    protected List extractFetchDataItems(int type)
    {
        Iterator i = queuedResponses.iterator();
        List items = new ArrayList();

        while (i.hasNext()) {
            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next();
            // if this is of the target type, move it to the response set.
            if (response.isKeyword("FETCH")) {
                IMAPFetchResponse fetch = (IMAPFetchResponse)response;
                // does this response have the item we're looking for?
                IMAPFetchDataItem item = fetch.getDataItem(type);
                if (item != null) {
                    // remove this from the pending queue and return the
                    // located item
                    i.remove();
                    // we want the fetch response, not the data item, because
                    // we're going to require the message sequence number information
                    // too.
                    items.add(fetch);
                }
            }
        }
        // return whatever we have.
        return items;
    }

    /**
     * Make sure we have the latest status information available.  We
     * retreive this by sending a NOOP command to the server, and
     * processing any untagged responses we get back.
     */
    public void updateMailboxStatus() throws MessagingException {
        sendSimpleCommand("NOOP");
    }


    /**
     * check to see if this connection is truely alive.
     *
     * @param timeout The timeout value to control how often we ping
     *                the server to see if we're still good.
     *
     * @return true if the server is responding to requests, false for any
     *         connection errors.  This will also update the folder status
     *         by processing returned unsolicited messages.
     */
    public synchronized boolean isAlive(long timeout) {
        long lastUsed = System.currentTimeMillis() - lastAccess;
        if (lastUsed < timeout) {
            return true;
        }

        try {
            sendSimpleCommand("NOOP");
            return true;
        } catch (MessagingException e) {
            // the NOOP command will throw a MessagingException if we get anything
            // other than an OK response back from the server.
        }
        return false;
    }


    /**
     * Issue a fetch command to retrieve the message ENVELOPE structure.
     *
     * @param sequenceNumber The sequence number of the message.
     *
     * @return The IMAPResponse item containing the ENVELOPE information.
     */
    public synchronized List fetchEnvelope(int sequenceNumber) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("ENVELOPE INTERNALDATE RFC822.SIZE");
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        // these are fairly involved sets, so the caller needs to handle these.
        // we just return all of the FETCH results matching the target message number.
        return extractFetchResponses(sequenceNumber);
    }

    /**
     * Issue a FETCH command to retrieve the message BODYSTRUCTURE structure.
     *
     * @param sequenceNumber The sequence number of the message.
     *
     * @return The IMAPBodyStructure item for the message.
     *         All other untagged responses are queued for processing.
     */
    public synchronized IMAPBodyStructure fetchBodyStructure(int sequenceNumber) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("BODYSTRUCTURE");
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        // locate the response from this
        IMAPBodyStructure bodyStructure = (IMAPBodyStructure)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.BODYSTRUCTURE);

        if (bodyStructure == null) {
            throw new MessagingException("No BODYSTRUCTURE information received from IMAP server");
        }
        // and return the body structure directly.
        return bodyStructure;
    }


    /**
     * Issue a FETCH command to retrieve the message RFC822.HEADERS structure containing the message headers (using PEEK).
     *
     * @param sequenceNumber The sequence number of the message.
     *
     * @return The IMAPRFC822Headers item for the message.
     *         All other untagged responses are queued for processing.
     */
    public synchronized InternetHeaders fetchHeaders(int sequenceNumber, String part) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("BODY.PEEK");
        command.appendBodySection(part, "HEADER");
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        IMAPInternetHeader header = (IMAPInternetHeader)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.HEADER);

        if (header == null) {
            throw new MessagingException("No HEADER information received from IMAP server");
        }
        // and return the body structure directly.
        return header.headers;
    }


    /**
     * Issue a FETCH command to retrieve the message text
     *
     * @param sequenceNumber The sequence number of the message.
     *
     * @return The IMAPMessageText item for the message.
     *         All other untagged responses are queued for processing.
     */
    public synchronized IMAPMessageText fetchText(int sequenceNumber) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("BODY.PEEK");
        command.appendBodySection("TEXT");
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        IMAPMessageText text = (IMAPMessageText)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.TEXT);

        if (text == null) {
            throw new MessagingException("No TEXT information received from IMAP server");
        }
        // and return the body structure directly.
        return text;
    }


    /**
     * Issue a FETCH command to retrieve the message text
     *
     * @param sequenceNumber The sequence number of the message.
     *
     * @return The IMAPMessageText item for the message.
     *         All other untagged responses are queued for processing.
     */
    public synchronized IMAPMessageText fetchBodyPartText(int sequenceNumber, String section) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("BODY.PEEK");
        command.appendBodySection(section, "TEXT");
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        IMAPMessageText text = (IMAPMessageText)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.TEXT);

        if (text == null) {
            throw new MessagingException("No TEXT information received from IMAP server");
        }
        // and return the body structure directly.
        return text;
    }


    /**
     * Issue a FETCH command to retrieve the entire message body in one shot.
     * This may also be used to fetch an embedded message part as a unit.
     *
     * @param sequenceNumber
     *                The sequence number of the message.
     * @param section The section number to fetch.  If null, the entire body of the message
     *                is retrieved.
     *
     * @return The IMAPBody item for the message.
     *         All other untagged responses are queued for processing.
     * @exception MessagingException
     */
    public synchronized IMAPBody fetchBody(int sequenceNumber, String section) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.startList();
        command.appendAtom("BODY.PEEK");
        // no part name here, only the section identifier.  This will fetch
        // the entire body, with all of the bits in place.
        command.appendBodySection(section, null);
        command.endList();

        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
        sendCommand(command);
        IMAPBody body = (IMAPBody)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.BODY);

        if (body == null) {
            throw new MessagingException("No BODY information received from IMAP server");
        }
        // and return the body structure directly.
        return body;
    }


    /**
     * Fetch the message content.  This sorts out which method should be used
     * based on the server capability.
     *
     * @param sequenceNumber
     *               The sequence number of the target message.
     *
     * @return The byte[] content information.
     * @exception MessagingException
     */
    public byte[] fetchContent(int sequenceNumber) throws MessagingException {
        // fetch the text item and return the data
        IMAPMessageText text = fetchText(sequenceNumber);
        return text.getContent();
    }


    /**
     * Fetch the message content.  This sorts out which method should be used
     * based on the server capability.
     *
     * @param sequenceNumber
     *               The sequence number of the target message.
     *
     * @return The byte[] content information.
     * @exception MessagingException
     */
    public byte[] fetchContent(int sequenceNumber, String section) throws MessagingException {
        if (section == null) {
            IMAPMessageText text = fetchText(sequenceNumber);
            return text.getContent();
        } else {
            IMAPBody body = fetchBody(sequenceNumber, section);
            return body.getContent();
        }
    }


    /**
     * Send an LIST command to the IMAP server, returning all LIST
     * response information.
     *
     * @param mailbox The reference mailbox name sent on the command.
     * @param pattern The match pattern used on the name.
     *
     * @return A List of all LIST response information sent back from the server.
     */
    public synchronized List list(String mailbox, String pattern) throws MessagingException {
        IMAPCommand command = new IMAPCommand("LIST");

        // construct the command, encoding the tokens as required by the content.
        command.appendEncodedString(mailbox);
        command.appendEncodedString(pattern);

        sendCommand(command);

        // pull out the ones we're interested in
        return extractResponses("LIST");
    }


    /**
     * Send an LSUB command to the IMAP server, returning all LSUB
     * response information.
     *
     * @param mailbox The reference mailbox name sent on the command.
     * @param pattern The match pattern used on the name.
     *
     * @return A List of all LSUB response information sent back from the server.
     */
    public List listSubscribed(String mailbox, String pattern) throws MessagingException {
        IMAPCommand command = new IMAPCommand("LSUB");

        // construct the command, encoding the tokens as required by the content.
        command.appendEncodedString(mailbox);
        command.appendEncodedString(pattern);

        sendCommand(command);
        // pull out the ones we're interested in
        return extractResponses("LSUB");
    }


    /**
     * Subscribe to a give mailbox.
     *
     * @param mailbox The desired mailbox name.
     *
     * @exception MessagingException
     */
    public void subscribe(String mailbox) throws MessagingException {
        IMAPCommand command = new IMAPCommand("SUBSCRIBE");
        // add on the encoded mailbox name, as the appropriate token type.
        command.appendEncodedString(mailbox);

        // send this, and ignore the response.
        sendSimpleCommand(command);
    }


    /**
     * Unsubscribe from a mailbox.
     *
     * @param mailbox The mailbox to remove.
     *
     * @exception MessagingException
     */
    public void unsubscribe(String mailbox) throws MessagingException {
        IMAPCommand command = new IMAPCommand("UNSUBSCRIBE");
        // add on the encoded mailbox name, as the appropriate token type.
        command.appendEncodedString(mailbox);

        // send this, and ignore the response.
        sendSimpleCommand(command);
    }


    /**
     * Create a mailbox.
     *
     * @param mailbox The desired new mailbox name (fully qualified);
     *
     * @exception MessagingException
     */
    public void createMailbox(String mailbox) throws MessagingException {
        IMAPCommand command = new IMAPCommand("CREATE");
        // add on the encoded mailbox name, as the appropriate token type.
        command.appendEncodedString(mailbox);

        // send this, and ignore the response.
        sendSimpleCommand(command);
    }


    /**
     * Delete a mailbox.
     *
     * @param mailbox The target mailbox name (fully qualified);
     *
     * @exception MessagingException
     */
    public void deleteMailbox(String mailbox) throws MessagingException {
        IMAPCommand command = new IMAPCommand("DELETE");
        // add on the encoded mailbox name, as the appropriate token type.
        command.appendEncodedString(mailbox);

        // send this, and ignore the response.
        sendSimpleCommand(command);
    }


    /**
     * Rename a mailbox.
     *
     * @param mailbox The target mailbox name (fully qualified);
     *
     * @exception MessagingException
     */
    public void renameMailbox(String oldName, String newName) throws MessagingException {
        IMAPCommand command = new IMAPCommand("RENAME");
        // add on the encoded mailbox name, as the appropriate token type.
        command.appendEncodedString(oldName);
        command.appendEncodedString(newName);

        // send this, and ignore the response.
        sendSimpleCommand(command);
    }


    /**
     * Retrieve a complete set of status items for a mailbox.
     *
     * @param mailbox The mailbox name.
     *
     * @return An IMAPMailboxStatus item filled in with the STATUS responses.
     * @exception MessagingException
     */
    public synchronized IMAPMailboxStatus getMailboxStatus(String mailbox) throws MessagingException {
        IMAPCommand command = new IMAPCommand("STATUS");

        // construct the command, encoding the tokens as required by the content.
        command.appendEncodedString(mailbox);
        // request all of the status items
        command.append(" (MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)");

        sendCommand(command);

        // now harvest each of the respon
        IMAPMailboxStatus status = new IMAPMailboxStatus();
        status.mergeSizeResponses(extractResponses("EXISTS"));
        status.mergeSizeResponses(extractResponses("RECENT"));
        status.mergeOkResponses(extractResponses("UIDNEXT"));
        status.mergeOkResponses(extractResponses("UIDVALIDITY"));
        status.mergeOkResponses(extractResponses("UNSEEN"));
        status.mergeStatus((IMAPStatusResponse)extractResponse("STATUS"));
        status.mergeStatus((IMAPPermanentFlagsResponse)extractResponse("PERMANENTFLAGS"));

        return status;
    }


    /**
     * Select a mailbox, returning the accumulated status information
     * about the mailbox returned with the response.
     *
     * @param mailbox  The desired mailbox name.
     * @param readOnly The open mode.  If readOnly is true, the mailbox is opened
     *                 using EXAMINE rather than SELECT.
     *
     * @return A status object containing the mailbox particulars.
     * @exception MessagingException
     */
    public synchronized IMAPMailboxStatus openMailbox(String mailbox, boolean readOnly) throws MessagingException {
        IMAPCommand command = new IMAPCommand();

        // if readOnly is required, we use EXAMINE to switch to the mailbox rather than SELECT.
        // This returns the same response information, but the mailbox will not accept update operations.
        if (readOnly) {
            command.appendAtom("EXAMINE");
        }
        else {
            command.appendAtom("SELECT");
        }

        // construct the command, encoding the tokens as required by the content.
        command.appendEncodedString(mailbox);

        // issue the select
        IMAPTaggedResponse response = sendCommand(command);

        IMAPMailboxStatus status = new IMAPMailboxStatus();
        // set the mode to the requested open mode.
        status.mode = readOnly ? Folder.READ_ONLY : Folder.READ_WRITE;

        // the server might disagree on the mode, so check to see if
        // it's telling us READ-ONLY.
        if (response.hasStatus("READ-ONLY")) {
            status.mode = Folder.READ_ONLY;
        }

        // some of these are required, some are optional.
        status.mergeFlags((IMAPFlagsResponse)extractResponse("FLAGS"));
        status.mergeStatus((IMAPSizeResponse)extractResponse("EXISTS"));
        status.mergeStatus((IMAPSizeResponse)extractResponse("RECENT"));
        status.mergeStatus((IMAPOkResponse)extractResponse("UIDVALIDITY"));
        status.mergeStatus((IMAPOkResponse)extractResponse("UNSEEN"));
        status.mergeStatus((IMAPPermanentFlagsResponse)extractResponse("PERMANENTFLAGS"));
        // mine the response for status information about the selected mailbox.
        return status;
    }


    /**
     * Tells the IMAP server to expunge messages marked for deletion.
     * The server will send us an untagged EXPUNGE message back for
     * each deleted message.  For explicit expunges we request, we'll
     * grabbed the untagged responses here, rather than force them to
     * be handled as pending responses.  The caller will handle the
     * updates directly.
     *
     * @exception MessagingException
     */
    public synchronized List expungeMailbox() throws MessagingException {
        // send the message, and make sure we got an OK response
        sendCommand("EXPUNGE");
        // extract all of the expunged responses and return.
        return extractResponses("EXPUNGED");
    }

    public int[] searchMailbox(SearchTerm term) throws MessagingException {
        return searchMailbox("ALL", term);
    }

    /**
     * Send a search to the IMAP server using the specified
     * messages selector and search term.  This figures out what
     * to do with CHARSET on the SEARCH command.
     *
     * @param messages The list of messages (comma-separated numbers or "ALL").
     * @param term     The desired search criteria
     *
     * @return Returns an int[] array of message numbers for all matched messages.
     * @exception MessagingException
     */
    public int[] searchMailbox(String messages, SearchTerm term) throws MessagingException {
        // don't use a charset by default, but we need to look at the data to see if we have a problem.
        String charset = null;

        if (IMAPCommand.checkSearchEncoding(term)) {
            // not sure exactly how to decide what to use here.  Two immediate possibilities come to mind,
            // UTF-8 or the MimeUtility.getDefaultJavaCharset() value.  Running a small test against the
            // Sun impl shows them sending a CHARSET value of UTF-8, so that sounds like the winner.  I don't
            // believe there's anything in the CAPABILITY response that would tell us what to use.
            charset = "UTF-8";
        }

        return searchMailbox(messages, term, charset);
    }

    /**
     * Send a search to the IMAP server using the specified
     * messages selector and search term.
     *
     * @param messages The list of messages (comma-separated numbers or "ALL").
     * @param charset  The charset specifier to send to the server.  If null, then
     *                 the CHARSET keyword is omitted.
     * @param term     The desired search criteria
     *
     * @return Returns an int[] array of message numbers for all matched messages.
     * @exception MessagingException
     */
    public synchronized int[] searchMailbox(String messages, SearchTerm term, String charset) throws MessagingException {
        IMAPCommand command = new IMAPCommand("SEARCH");

        // if we have an explicit charset to use, append that.
        if (charset != null) {
            command.appendAtom("CHARSET");
            command.appendAtom(charset);
        }

        // now go through the process of translating the javamail SearchTerm objects into
        // the IMAP command sequence.  The SearchTerm sequence may be a complex tree of comparison terms,
        // so this is not a simple process.
        command.appendSearchTerm(term, charset);
        // need to append the message set
        command.appendAtom(messages);

        // now issue the composed command.
        sendCommand(command);

        // get the list of search responses
        IMAPSearchResponse hits = (IMAPSearchResponse)extractResponse("SEARCH");
        // and return the message hits
        return hits.messageNumbers;
    }


    /**
     * Append a message to a mailbox, given the direct message data.
     *
     * @param mailbox The target mailbox name.
     * @param messageFlags
     *                The initial flag set for the appended message.
     * @param messageDate
     *                The received date the message is created with,
     * @param messageData
     *                The RFC822 Message data stored on the server.
     *
     * @exception MessagingException
     */
    public void appendMessage(String mailbox, Date messageDate, Flags messageFlags, byte[] messageData) throws MessagingException {
        IMAPCommand command = new IMAPCommand("APPEND");

        // the mailbox is encoded.
        command.appendEncodedString(mailbox);

        if (messageFlags != null) {
            // the flags are pulled from an existing object.  We can set most flag values, but the servers
            // reserve RECENT for themselves.  We need to force that one off.
            messageFlags.remove(Flags.Flag.RECENT);
            // and add the flag list to the commmand.
            command.appendFlags(messageFlags);
        }

        if (messageDate != null) {
            command.appendDate(messageDate);
        }

        // this gets appended as a literal.
        command.appendLiteral(messageData);
        // just send this as a simple command...we don't deal with the response other than to verifiy
        // it was ok.
        sendSimpleCommand(command);
    }

    /**
     * Fetch the flag set for a given message sequence number.
     *
     * @param sequenceNumber
     *               The message sequence number.
     *
     * @return The Flags defined for this message.
     * @exception MessagingException
     */
    public synchronized Flags fetchFlags(int sequenceNumber) throws MessagingException {
        // we want just the flag item here.
        sendCommand("FETCH " + String.valueOf(sequenceNumber) + " (FLAGS)");
        // get the return data item, and get the flags from within it
        IMAPFlags flags = (IMAPFlags)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.FLAGS);
        return flags.flags;
    }


    /**
     * Set the flags for a range of messages.
     *
     * @param messageSet The set of message numbers.
     * @param flags      The new flag settings.
     * @param set        true if the flags should be set, false for a clear operation.
     *
     * @return A list containing all of the responses with the new flag values.
     * @exception MessagingException
     */
    public synchronized List setFlags(String messageSet, Flags flags, boolean set) throws MessagingException {
        IMAPCommand command = new IMAPCommand("STORE");
        command.appendAtom(messageSet);
        // the command varies depending on whether this is a set or clear operation
        if (set) {
            command.appendAtom("+FLAGS");
        }
        else {
            command.appendAtom("-FLAGS");
        }

        // append the flag set
        command.appendFlags(flags);

        // we want just the flag item here.
        sendCommand(command);
        // we should have a FETCH response for each of the updated messages.  Return this
        // response, and update the message numbers.
        return extractFetchDataItems(IMAPFetchDataItem.FLAGS);
    }


    /**
     * Set the flags for a single message.
     *
     * @param sequenceNumber
     *               The sequence number of target message.
     * @param flags  The new flag settings.
     * @param set    true if the flags should be set, false for a clear operation.
     *
     * @exception MessagingException
     */
    public synchronized Flags setFlags(int sequenceNumber, Flags flags, boolean set) throws MessagingException {
        IMAPCommand command = new IMAPCommand("STORE");
        command.appendInteger(sequenceNumber);
        // the command varies depending on whether this is a set or clear operation
        if (set) {
            command.appendAtom("+FLAGS");
        }
        else {
            command.appendAtom("-FLAGS");
        }

        // append the flag set
        command.appendFlags(flags);

        // we want just the flag item here.
        sendCommand(command);
        // get the return data item, and get the flags from within it
        IMAPFlags flagResponse = (IMAPFlags)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.FLAGS);
        return flagResponse.flags;
    }


    /**
     * Copy a range of messages to a target mailbox.
     *
     * @param messageSet The set of message numbers.
     * @param target     The target mailbox name.
     *
     * @exception MessagingException
     */
    public void copyMessages(String messageSet, String target) throws MessagingException {
        IMAPCommand command = new IMAPCommand("COPY");
        // the auth command initiates the handshaking.
        command.appendAtom(messageSet);
        // the mailbox is encoded.
        command.appendEncodedString(target);
        // just send this as a simple command...we don't deal with the response other than to verifiy
        // it was ok.
        sendSimpleCommand(command);
    }


    /**
     * Fetch the message number for a give UID.
     *
     * @param uid    The target UID
     *
     * @return An IMAPUid object containing the mapping information.
     */
    public synchronized IMAPUid getSequenceNumberForUid(long uid) throws MessagingException {
        IMAPCommand command = new IMAPCommand("UID FETCH");
        command.appendLong(uid);
        command.appendAtom("(UID)");

        // this situation is a little strange, so it deserves a little explanation.
        // We need the message sequence number for this message from a UID value.
        // we're going to send a UID FETCH command, requesting the UID value back.
        // That seems strange, but the * nnnn FETCH response for the request will
        // be tagged with the message sequence number.  THAT'S the information we
        // really want, and it will be included in the IMAPUid object.

        sendCommand(command);
        // ok, now we need to search through these looking for a FETCH response with a UID element.
        List responses = extractResponses("FETCH");

        // we're looking for a fetch response with a UID data item with the UID information
        // inside of it.
        for (int i = 0; i < responses.size(); i++) {
            IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
            IMAPUid item = (IMAPUid)response.getDataItem(IMAPFetchDataItem.UID);
            // is this the response we're looking for?  The information we
            // need is the message number returned with the response, which is
            // also contained in the UID item.
            if (item != null && item.uid == uid) {
                return item;
            }
            // not one meant for us, add it back to the pending queue.
            queuePendingResponse(response);
        }
        // didn't find this one
        return null;
    }


    /**
     * Fetch the message numbers for a consequetive range
     * of UIDs.
     *
     * @param start  The start of the range.
     * @param end    The end of the uid range.
     *
     * @return A list of UID objects containing the mappings.
     */
    public synchronized List getSequenceNumbersForUids(long start, long end) throws MessagingException {
        IMAPCommand command = new IMAPCommand("UID FETCH");
        // send the request for the range "start:end" so we can fetch all of the info
        // at once.
        command.appendLong(start);
        command.append(":");
        // not the special range marker?  Just append the
        // number.  The LASTUID value needs to be "*" on the command.
        if (end != UIDFolder.LASTUID) {
            command.appendLong(end);
        }
        else {
            command.append("*");
        }
        command.appendAtom("(UID)");

        // this situation is a little strange, so it deserves a little explanation.
        // We need the message sequence number for this message from a UID value.
        // we're going to send a UID FETCH command, requesting the UID value back.
        // That seems strange, but the * nnnn FETCH response for the request will
        // be tagged with the message sequence number.  THAT'S the information we
        // really want, and it will be included in the IMAPUid object.

        sendCommand(command);
        // ok, now we need to search through these looking for a FETCH response with a UID element.
        List responses = extractResponses("FETCH");

        List uids = new ArrayList((int)(end - start + 1));

        // we're looking for a fetch response with a UID data item with the UID information
        // inside of it.
        for (int i = 0; i < responses.size(); i++) {
            IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
            IMAPUid item = (IMAPUid)response.getDataItem(IMAPFetchDataItem.UID);
            // is this the response we're looking for?  The information we
            // need is the message number returned with the response, which is
            // also contained in the UID item.
            if (item != null) {
                uids.add(item);
            }
            else {
                // not one meant for us, add it back to the pending queue.
                queuePendingResponse(response);
            }
        }
        // return the list of uids we located.
        return uids;
    }


    /**
     * Fetch the UID value for a target message number
     *
     * @param sequenceNumber
     *               The target message number.
     *
     * @return An IMAPUid object containing the mapping information.
     */
    public synchronized IMAPUid getUidForSequenceNumber(int sequenceNumber) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendInteger(sequenceNumber);
        command.appendAtom("(UID)");

        // similar to the other fetches, but without the strange bit.  We're starting
        // with the message number in this case.

        sendCommand(command);

        // ok, now we need to search through these looking for a FETCH response with a UID element.
        return (IMAPUid)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.UID);
    }


    /**
     * Retrieve the user name space info from the server.
     *
     * @return An IMAPNamespace response item with the information.  If the server
     *         doesn't support the namespace extension, an empty one is returned.
     */
    public synchronized IMAPNamespaceResponse getNamespaces() throws MessagingException {
        // if no namespace capability, then return an empty
        // response, which will trigger the default behavior.
        if (!hasCapability("NAMESPACE")) {
            return new IMAPNamespaceResponse();
        }
        // no arguments on this command, so just send an hope it works.
        sendCommand("NAMESPACE");

        // this should be here, since it's a required response when the
        // command worked.  Just extract, and return.
        return (IMAPNamespaceResponse)extractResponse("NAMESPACE");
    }


    /**
     * Prefetch message information based on the request profile.  We'll return
     * all of the fetch information to the requesting Folder, which will sort
     * out what goes where.
     *
     * @param messageSet The set of message numbers we need to fetch.
     * @param profile    The profile of the required information.
     *
     * @return All FETCH responses resulting from the command.
     * @exception MessagingException
     */
    public synchronized List fetch(String messageSet, FetchProfile profile) throws MessagingException {
        IMAPCommand command = new IMAPCommand("FETCH");
        command.appendAtom(messageSet);
        // this is the set of items to append
        command.appendFetchProfile(profile);

        // now send the fetch command, which will likely send back a lot of "FETCH" responses.
        // Suck all of those reponses out of the queue and send them back for processing.
        sendCommand(command);
        // we can have a large number of messages here, so just grab all of the fetches
        // we get back, and let the Folder sort out who gets what.
        return extractResponses("FETCH");
    }


    /**
     * Set the ACL rights for a mailbox.  This replaces
     * any existing ACLs defined.
     *
     * @param mailbox The target mailbox.
     * @param acl     The new ACL to be used for the mailbox.
     *
     * @exception MessagingException
     */
    public synchronized void setACLRights(String mailbox, ACL acl) throws MessagingException {
        IMAPCommand command = new IMAPCommand("SETACL");
        command.appendEncodedString(mailbox);

        command.appendACL(acl);

        sendSimpleCommand(command);
    }


    /**
     * Add a set of ACL rights to a mailbox.
     *
     * @param mailbox The mailbox to alter.
     * @param acl     The ACL to add.
     *
     * @exception MessagingException
     */
    public synchronized void addACLRights(String mailbox, ACL acl) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("SETACL");
        command.appendEncodedString(mailbox);

        command.appendACL(acl, "+");

        sendSimpleCommand(command);
    }


    /**
     * Remove an ACL from a given mailbox.
     *
     * @param mailbox The mailbox to alter.
     * @param acl     The particular ACL to revoke.
     *
     * @exception MessagingException
     */
    public synchronized void removeACLRights(String mailbox, ACL acl) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("SETACL");
        command.appendEncodedString(mailbox);

        command.appendACL(acl, "-");

        sendSimpleCommand(command);
    }


    /**
     * Get the ACL rights assigned to a given mailbox.
     *
     * @param mailbox The target mailbox.
     *
     * @return The an array of ACL items describing the access
     *         rights to the mailbox.
     * @exception MessagingException
     */
    public synchronized ACL[] getACLRights(String mailbox) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("GETACL");
        command.appendEncodedString(mailbox);

        // now send the GETACL command, which will return a single ACL untagged response.
        sendCommand(command);
        // there should be just a single ACL response back from this command.
        IMAPACLResponse response = (IMAPACLResponse)extractResponse("ACL");
        return response.acls;
    }


    /**
     * Get the current user's ACL rights to a given mailbox.
     *
     * @param mailbox The target mailbox.
     *
     * @return The Rights associated with this mailbox.
     * @exception MessagingException
     */
    public synchronized Rights getMyRights(String mailbox) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("MYRIGHTS");
        command.appendEncodedString(mailbox);

        // now send the MYRIGHTS command, which will return a single MYRIGHTS untagged response.
        sendCommand(command);
        // there should be just a single MYRIGHTS response back from this command.
        IMAPMyRightsResponse response = (IMAPMyRightsResponse)extractResponse("MYRIGHTS");
        return response.rights;
    }


    /**
     * List the ACL rights that a particular user has
     * to a mailbox.
     *
     * @param mailbox The target mailbox.
     * @param name    The user we're querying.
     *
     * @return An array of rights the use has to this mailbox.
     * @exception MessagingException
     */
    public synchronized Rights[] listACLRights(String mailbox, String name) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("LISTRIGHTS");
        command.appendEncodedString(mailbox);
        command.appendString(name);

        // now send the GETACL command, which will return a single ACL untagged response.
        sendCommand(command);
        // there should be just a single ACL response back from this command.
        IMAPListRightsResponse response = (IMAPListRightsResponse)extractResponse("LISTRIGHTS");
        return response.rights;
    }


    /**
     * Delete an ACL item for a given user name from
     * a target mailbox.
     *
     * @param mailbox The mailbox we're altering.
     * @param name    The user name.
     *
     * @exception MessagingException
     */
    public synchronized void deleteACL(String mailbox, String name) throws MessagingException {
        if (!hasCapability("ACL")) {
            throw new MethodNotSupportedException("ACL not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("DELETEACL");
        command.appendEncodedString(mailbox);
        command.appendString(name);

        // just send the command.  No response to handle.
        sendSimpleCommand(command);
    }

    /**
     * Fetch the quota root information for a target mailbox.
     *
     * @param mailbox The mailbox of interest.
     *
     * @return An array of quotas describing all of the quota roots
     *         that apply to the target mailbox.
     * @exception MessagingException
     */
    public synchronized Quota[] fetchQuotaRoot(String mailbox) throws MessagingException {
        if (!hasCapability("QUOTA")) {
            throw new MethodNotSupportedException("QUOTA not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("GETQUOTAROOT");
        command.appendEncodedString(mailbox);

        // This will return a single QUOTAROOT response, plust a series of QUOTA responses for
        // each root names in the first response.
        sendCommand(command);
        // we don't really need this, but pull it from the response queue anyway.
        extractResponse("QUOTAROOT");

        // now get the real meat of the matter
        List responses = extractResponses("QUOTA");

        // now copy all of the returned quota items into the response array.
        Quota[] quotas = new Quota[responses.size()];
        for (int i = 0; i < quotas.length; i++) {
            IMAPQuotaResponse q = (IMAPQuotaResponse)responses.get(i);
            quotas[i] = q.quota;
        }

        return quotas;
    }

    /**
     * Fetch QUOTA information from a named QUOTE root.
     *
     * @param root   The target root name.
     *
     * @return An array of Quota items associated with that root name.
     * @exception MessagingException
     */
    public synchronized Quota[] fetchQuota(String root) throws MessagingException {
        if (!hasCapability("QUOTA")) {
            throw new MethodNotSupportedException("QUOTA not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("GETQUOTA");
        command.appendString(root);

        // This will return a single QUOTAROOT response, plust a series of QUOTA responses for
        // each root names in the first response.
        sendCommand(command);

        // now get the real meat of the matter
        List responses = extractResponses("QUOTA");

        // now copy all of the returned quota items into the response array.
        Quota[] quotas = new Quota[responses.size()];
        for (int i = 0; i < quotas.length; i++) {
            IMAPQuotaResponse q = (IMAPQuotaResponse)responses.get(i);
            quotas[i] = q.quota;
        }

        return quotas;
    }

    /**
     * Set a Quota item for the currently accessed
     * userid/folder resource.
     *
     * @param quota  The new QUOTA information.
     *
     * @exception MessagingException
     */
    public synchronized void setQuota(Quota quota) throws MessagingException {
        if (!hasCapability("QUOTA")) {
            throw new MethodNotSupportedException("QUOTA not available from this IMAP server");
        }
        IMAPCommand command = new IMAPCommand("GETQUOTA");
        // this gets appended as a list of resource values
        command.appendQuota(quota);

        // This will return a single QUOTAROOT response, plust a series of QUOTA responses for
        // each root names in the first response.
        sendCommand(command);
        // we don't really need this, but pull it from the response queue anyway.
        extractResponses("QUOTA");
    }


    /**
     * Test if this connection has a given capability.
     *
     * @param capability The capability name.
     *
     * @return true if this capability is in the list, false for a mismatch.
     */
    public boolean hasCapability(String capability) {
        if (capabilities == null) {
            return false;
        }
        return capabilities.containsKey(capability);
    }

    /**
     * Tag this connection as having been closed by the
     * server.  This will not be returned to the
     * connection pool.
     */
    public void setClosed() {
        closed = true;
    }

    /**
     * Test if the connnection has been forcibly closed.
     *
     * @return True if the server disconnected the connection.
     */
    public boolean isClosed() {
        return closed;
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



