Open Source Repository

Home /mail/mail-1.4.3 | Repository Home



com/sun/mail/imap/protocol/IMAPProtocol.java
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.mail.imap.protocol;

import java.io.*;
import java.util.*;
import java.text.*;
import java.lang.reflect.*;

import javax.mail.*;
import javax.mail.internet.*;
import javax.mail.search.*;

import com.sun.mail.util.*;
import com.sun.mail.iap.*;
import com.sun.mail.auth.Ntlm;

import com.sun.mail.imap.ACL;
import com.sun.mail.imap.Rights;
import com.sun.mail.imap.AppendUID;

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

public class IMAPProtocol extends Protocol {
    
    private boolean connected = false;    // did constructor succeed?
    private boolean rev1 = false;    // REV1 server ?
    private boolean authenticated;    // authenticated?
    // WARNING: authenticated may be set to true in superclass
    //    constructor, don't initialize it here.

    private Map capabilities;
    // WARNING: capabilities may be initialized as a result of superclass
    //    constructor, don't initialize it here.
    private List authmechs;
    // WARNING: authmechs may be initialized as a result of superclass
    //    constructor, don't initialize it here.
    private String[] searchCharsets;   // array of search charsets

    private String name;
    private SaslAuthenticator saslAuthenticator;  // if SASL is being used

    private ByteArray ba;    // a buffer for fetchBody

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

    /**
     * Constructor.
     * Opens a connection to the given host at given port.
     *
     @param host  host to connect to
     @param port  portnumber to connect to
     @param debug     debug mode
     @param props     Properties object used by this protocol
     */
    public IMAPProtocol(String name, String host, int port, 
      boolean debug, PrintStream out, Properties props,
      boolean isSSLthrows IOException, ProtocolException {
  super(host, port, debug, out, props, "mail." + name, isSSL);

  try {
      this.name = name;

      if (capabilities == null)
    capability();

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

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

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

    /**
     * CAPABILITY command.
     *
     @see "RFC2060, section 6.1.1"
     */
    public void capability() throws ProtocolException {
  // Check CAPABILITY
  Response[] r = command("CAPABILITY"null);

  if (!r[r.length-1].isOK())
      throw new ProtocolException(r[r.length-1].toString());

  capabilities = new HashMap(10);
  authmechs = new ArrayList(5);
  for (int i = 0, len = r.length; i < len; i++) {
      if (!(r[iinstanceof IMAPResponse))
    continue;

      IMAPResponse ir = (IMAPResponse)r[i];

      // Handle *all* untagged CAPABILITY responses.
      //   Though the spec seemingly states that only
      // one CAPABILITY response string is allowed (6.1.1),
      // some server vendors claim otherwise.
      if (ir.keyEquals("CAPABILITY"))
    parseCapabilities(ir);
  }
    }

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

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

    /**
     * Check the greeting when first connecting; look for PREAUTH response.
     */
    protected void processGreeting(Response rthrows ProtocolException {
  super.processGreeting(r);  // check if it's BAD
  if (r.isOK()) {      // check if it's OK
      setCapabilities(r);
      return;
  }
  // only other choice is PREAUTH
  IMAPResponse ir = (IMAPResponse)r;
  if (ir.keyEquals("PREAUTH")) {
      authenticated = true;
      setCapabilities(r);
  else
      throw new ConnectionException(this, r);
    }

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

    /**
     * Returns <code>true</code> if this is a IMAP4rev1 server
     */
    public boolean isREV1() {
  return rev1;
    }

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

    /**
     * Read a response from the server.
     */
    public Response readResponse() throws IOException, ProtocolException {
  // assert Thread.holdsLock(this);
  // can't assert because it's called from constructor
  return IMAPResponse.readResponse(this);
    }

    /**
     * Check whether the given capability is supported by
     * this server. Returns <code>true</code> if so, otherwise
     * returns false.
     */
    public boolean hasCapability(String c) {
  return capabilities.containsKey(c.toUpperCase(Locale.ENGLISH));
    }

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

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

    /**
     * The NOOP command.
     *
     @see "RFC2060, section 6.1.2"
     */
    public void noop() throws ProtocolException {
  if (debug)
      out.println("IMAP DEBUG: IMAPProtocol noop");
  simpleCommand("NOOP"null);
    }

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

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

    /**
     * LOGIN Command.
     
     @see "RFC2060, section 6.2.2"
     */
    public void login(String u, String pthrows ProtocolException {
  Argument args = new Argument();
  args.writeString(u);
  args.writeString(p);

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

  // dispatch untagged responses
  notifyResponseHandlers(r);

  // Handle result of this command
  handleResult(r[r.length-1]);
  // If the response includes a CAPABILITY response code, process it
  setCapabilities(r[r.length-1]);
  // if we get this far without an exception, we're authenticated
  authenticated = true;
    }

    /**
     * The AUTHENTICATE command with AUTH=LOGIN authenticate scheme
     *
     @see "RFC2060, section 6.2.1"
     */
    public synchronized void authlogin(String u, String p)
        throws ProtocolException {
  Vector v = new Vector();
  String tag = null;
  Response r = null;
  boolean done = false;

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

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

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

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

  while (!done) { // loop till we are done
      try {
    r = readResponse();
        if (r.isContinuation()) {
        // Server challenge ..
        String s;
        if (first) { // Send encoded username
      s = u;
      first = false;
        else   // Send encoded password
      s = p;
        
        // obtain b64 encoded bytes
        b64os.write(ASCIIUtility.getBytes(s));
        b64os.flush();   // complete the encoding

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

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

  // Handle the final OK, NO, BAD or BYE response
  handleResult(r);
  // If the response includes a CAPABILITY response code, process it
  setCapabilities(r);
  // if we get this far without an exception, we're authenticated
  authenticated = true;
    }


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

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

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

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

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

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

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

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

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

  // Handle the final OK, NO, BAD or BYE response
  handleResult(r);
  // If the response includes a CAPABILITY response code, process it
  setCapabilities(r);
  // if we get this far without an exception, we're authenticated
  authenticated = true;
    }

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

  /*
   * Generate the first response right away because Ntlm will
   * return null if the NTLM authentication support isn't
   * available and then we can fail cleanly without invoking
   * the AUTHENTICATE command.
   */
  Ntlm ntlm = new Ntlm(debug ? out : null);
  int flags = 0;
  String domain = null;
  String type1Msg = null;
  try {
      flags = PropUtil.getIntProperty(props,
    "mail." + name + ".auth.ntlm.flags"0);
      boolean useUnicode = PropUtil.getBooleanProperty(props,
    "mail." + name + ".auth.ntlm.unicode"true);
      domain = props.getProperty(
    "mail." + name + ".auth.ntlm.domain""");
      type1Msg = ntlm.generateType1Msg(useUnicode, flags,
    domain, getLocalHost());
      if (type1Msg == null) {
    if (debug)
        out.println("IMAP DEBUG: Can't load NTLM authenticator");
    throw new ProtocolException("Can't load NTLM authenticator");
      }
  catch (IOException ex) {
      throw new ProtocolException("Error generating NTLM response", ex);
  }

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

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

  while (!done) { // loop till we are done
      try {
    r = readResponse();
        if (r.isContinuation()) {
        // Server challenge ..
        String s;
        if (first) {
      s = type1Msg;
      first = false;
        else {
      int lmCompatibility = PropUtil.getIntProperty(props,
          "mail." + name + ".auth.ntlm.lmcompat"3);
      s = ntlm.generateType3Msg(u, p,
          domain, getLocalHost(),
          r.getRest(),
          flags,
          lmCompatibility);
        }
 
        os.write(ASCIIUtility.getBytes(s));
        os.write(CRLF);   // CRLF termination
        os.flush();   // flush the stream
    else if (r.isTagged() && r.getTag().equals(tag))
        // Ah, our tagged response
        done = true;
    else if (r.isBYE()) // outta here
        done = true;
    else // hmm .. unsolicited response here ?!
        v.addElement(r);
      catch (Exception ioex) {
    // convert this into a BYE response
    r = Response.byeResponse(ioex);
    done = true;
      }
  }

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

  // Handle the final OK, NO, BAD or BYE response
  handleResult(r);
  // If the response includes a CAPABILITY response code, process it
  setCapabilities(r);
  // if we get this far without an exception, we're authenticated
  authenticated = true;
    }

    /**
     * SASL-based login.
     */
    public void sasllogin(String[] allowed, String realm, String authzid,
        String u, String pthrows ProtocolException {
  if (saslAuthenticator == null) {
      try {
    Class sac = Class.forName(
        "com.sun.mail.imap.protocol.IMAPSaslAuthenticator");
    Constructor c = sac.getConstructor(new Class[] {
          IMAPProtocol.class,
          String.class,
          Properties.class,
          Boolean.TYPE,
          PrintStream.class,
          String.class
          });
    saslAuthenticator = (SaslAuthenticator)c.newInstance(
          new Object[] {
          this,
          name,
          props,
          debug ? Boolean.TRUE : Boolean.FALSE,
          out,
          host
          });
      catch (Exception ex) {
    if (debug)
        out.println("IMAP DEBUG: Can't load SASL authenticator: " +
                ex);
    // probably because we're running on a system without SASL
    return;  // not authenticated, try without SASL
      }
  }

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

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

    /**
     * PROXYAUTH Command.
     
     @see "Netscape/iPlanet/SunONE Messaging Server extension"
     */
    public void proxyauth(String uthrows ProtocolException {
  Argument args = new Argument();
  args.writeString(u);

  simpleCommand("PROXYAUTH", args);
    }

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

    /**
     * SELECT Command.
     *
     @see "RFC2060, section 6.3.1"
     */
    public MailboxInfo select(String mboxthrows ProtocolException {
  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);

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

  // Note that MailboxInfo also removes those responses 
  // it knows about
  MailboxInfo minfo = new MailboxInfo(r);
  
  // dispatch any remaining untagged responses
  notifyResponseHandlers(r);

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

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

    /**
     * EXAMINE Command.
     *
     @see "RFC2060, section 6.3.2"
     */
    public MailboxInfo examine(String mboxthrows ProtocolException {
  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);

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

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

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

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

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

  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);

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

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

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

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

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

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

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

    /**
     * CREATE Command.
     *
     @see "RFC2060, section 6.3.3"
     */
    public void create(String mboxthrows ProtocolException {
  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);

  simpleCommand("CREATE", args);
    }

    /**
     * DELETE Command.
     *
     @see "RFC2060, section 6.3.4"
     */
    public void delete(String mboxthrows ProtocolException {
  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);

  simpleCommand("DELETE", args);
    }

    /**
     * RENAME Command.
     *
     @see "RFC2060, section 6.3.5"
     */
    public void rename(String o, String nthrows ProtocolException {
  // encode the mbox as per RFC2060
  o = BASE64MailboxEncoder.encode(o);
  n = BASE64MailboxEncoder.encode(n);

  Argument args = new Argument();  
  args.writeString(o);
  args.writeString(n);

  simpleCommand("RENAME", args);
    }

    /**
     * SUBSCRIBE Command.
     *
     @see "RFC2060, section 6.3.6"
     */
    public void subscribe(String mboxthrows ProtocolException {
  Argument args = new Argument();  
  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);
  args.writeString(mbox);

  simpleCommand("SUBSCRIBE", args);
    }

    /**
     * UNSUBSCRIBE Command.
     *
     @see "RFC2060, section 6.3.7"
     */
    public void unsubscribe(String mboxthrows ProtocolException {
  Argument args = new Argument();  
  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);
  args.writeString(mbox);

  simpleCommand("UNSUBSCRIBE", args);
    }

    /**
     * LIST Command.
     *
     @see "RFC2060, section 6.3.8"
     */
    public ListInfo[] list(String ref, String pattern
      throws ProtocolException {
  return doList("LIST", ref, pattern);
    }

    /**
     * LSUB Command.
     *
     @see "RFC2060, section 6.3.9"
     */
    public ListInfo[] lsub(String ref, String pattern
      throws ProtocolException {
  return doList("LSUB", ref, pattern);
    }

    private ListInfo[] doList(String cmd, String ref, String pat)
      throws ProtocolException {
  // encode the mbox as per RFC2060
  ref = BASE64MailboxEncoder.encode(ref);
  pat = BASE64MailboxEncoder.encode(pat);

  Argument args = new Argument();  
  args.writeString(ref);
  args.writeString(pat);

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

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

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

    IMAPResponse ir = (IMAPResponse)r[i];
    if (ir.keyEquals(cmd)) {
        v.addElement(new ListInfo(ir));
        r[inull;
    }
      }
      if (v.size() 0) {
    linfo = new ListInfo[v.size()];
    v.copyInto(linfo);
      }
  }
  
  // Dispatch remaining untagged responses
  notifyResponseHandlers(r);
  handleResult(response);
  return linfo;
    }
    
    /**
     * APPEND Command.
     *
     @see "RFC2060, section 6.3.11"
     */
    public void append(String mbox, Flags f, Date d,
      Literal datathrows ProtocolException {
  appenduid(mbox, f, d, data, false);  // ignore return value
    }

    /**
     * APPEND Command, return uid from APPENDUID response code.
     *
     @see "RFC2060, section 6.3.11"
     */
    public AppendUID appenduid(String mbox, Flags f, Date d,
      Literal datathrows ProtocolException {
  return appenduid(mbox, f, d, data, true);
    }

    public AppendUID appenduid(String mbox, Flags f, Date d,
      Literal data, boolean uidthrows ProtocolException {
  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);

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

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

  args.writeBytes(data);

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

  // dispatch untagged responses
  notifyResponseHandlers(r);

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

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

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

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

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

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

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

    /**
     * UID EXPUNGE Command.
     *
     @see "RFC2359, section 4.1"
     */
    public void uidexpunge(UIDSet[] setthrows ProtocolException {
  if (!hasCapability("UIDPLUS")) 
      throw new BadCommandException("UID EXPUNGE not supported");
  simpleCommand("UID EXPUNGE " + UIDSet.toString(set)null);
    }

    /**
     * Fetch the BODYSTRUCTURE of the specified message.
     */
    public BODYSTRUCTURE fetchBodyStructure(int msgno
      throws ProtocolException {
  Response[] r = fetch(msgno, "BODYSTRUCTURE");
  notifyResponseHandlers(r);

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

    /**
     * Fetch given BODY section, without marking the message
     * as SEEN.
     */
    public BODY peekBody(int msgno, String section)
      throws ProtocolException {
  return fetchBody(msgno, section, true);
    }

    /**
     * Fetch given BODY section.
     */
    public BODY fetchBody(int msgno, String section)
      throws ProtocolException {
  return fetchBody(msgno, section, false);
    }

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

  if (peek)
      r = fetch(msgno,
         "BODY.PEEK[" (section == null "]" : section + "]"));
  else
      r = fetch(msgno,
         "BODY[" (section == null "]" : section + "]"));

  notifyResponseHandlers(r);

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

    /**
     * Partial FETCH of given BODY section, without setting SEEN flag.
     */
    public BODY peekBody(int msgno, String section, int start, int size)
      throws ProtocolException {
  return fetchBody(msgno, section, start, size, true, null);
    }

    /**
     * Partial FETCH of given BODY section.
     */
    public BODY fetchBody(int msgno, String section, int start, int size)
      throws ProtocolException {
  return fetchBody(msgno, section, start, size, false, null);
    }

    /**
     * Partial FETCH of given BODY section, without setting SEEN flag.
     */
    public BODY peekBody(int msgno, String section, int start, int size,
        ByteArray bathrows ProtocolException {
  return fetchBody(msgno, section, start, size, true, ba);
    }

    /**
     * Partial FETCH of given BODY section.
     */
    public BODY fetchBody(int msgno, String section, int start, int size,
        ByteArray bathrows ProtocolException {
  return fetchBody(msgno, section, start, size, false, ba);
    }

    protected BODY fetchBody(int msgno, String section, int start, int size,
      boolean peek, ByteArray bathrows ProtocolException {
  this.ba = ba;  // save for later use by getResponseBuffer
  Response[] r = fetch(
      msgno, (peek ? "BODY.PEEK[" "BODY[" +
      (section == null "]<" (section +"]<")) +
      String.valueOf(start"." +
      String.valueOf(size">"
           );

  notifyResponseHandlers(r);

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

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

    /**
     * Fetch the specified RFC822 Data item. 'what' names
     * the item to be fetched. 'what' can be <code>null</code>
     * to fetch the whole message.
     */
    public RFC822DATA fetchRFC822(int msgno, String what)
      throws ProtocolException {
  Response[] r = fetch(msgno,
           what == null "RFC822" "RFC822." + what
          );

  // dispatch untagged responses
  notifyResponseHandlers(r);

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

    /**
     * Fetch the FLAGS for the given message.
     */
    public Flags fetchFlags(int msgnothrows ProtocolException {
  Flags flags = null;
  Response[] r = fetch(msgno, "FLAGS");

  // Search for our FLAGS response
  for (int i = 0, len = r.length; i < len; i++) {
      if (r[i== null ||
    !(r[iinstanceof FetchResponse||
    ((FetchResponse)r[i]).getNumber() != msgno)
    continue;    
      
      FetchResponse fr = (FetchResponse)r[i];
      if ((flags = (Flags)fr.getItem(Flags.class)) != null) {
    r[inull// remove this response
    break;
      }
  }

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

    /**
     * Fetch the IMAP UID for the given message.
     */
    public UID fetchUID(int msgnothrows ProtocolException {
  Response[] r = fetch(msgno, "UID");

  // dispatch untagged responses
  notifyResponseHandlers(r);

  Response response = r[r.length-1]
  if (response.isOK())
      return (UID)FetchResponse.getItem(r, msgno, UID.class);
  else if (response.isNO()) // XXX: Issue NOOP ?
      return null;
  else {
      handleResult(response);
      return null// NOTREACHED
  }
    }
    
    /**
     * Get the sequence number for the given UID. A UID object
     * containing the sequence number is returned. If the given UID
     * is invalid, <code>null</code> is returned.
     */
    public UID fetchSequenceNumber(long uidthrows ProtocolException {
  UID u = null;
  Response[] r = fetch(String.valueOf(uid)"UID"true);  

  for (int i = 0, len = r.length; i < len; i++) {
      if (r[i== null || !(r[iinstanceof FetchResponse))
    continue;
      
      FetchResponse fr = (FetchResponse)r[i];
      if ((u = (UID)fr.getItem(UID.class)) != null) {
    if (u.uid == uid// this is the one we want
        break;
    else
        u = null;
      }
  }
    
  notifyResponseHandlers(r);
  handleResult(r[r.length-1]);
  return u;
    }

    /**
     * Get the sequence numbers for UIDs ranging from start till end.
     * UID objects that contain the sequence numbers are returned.
     * If no UIDs in the given range are found, an empty array is returned.
     */
    public UID[] fetchSequenceNumbers(long start, long end)
      throws ProtocolException {
  Response[] r = fetch(String.valueOf(start":" 
        (end == UIDFolder.LASTUID ? "*" 
        String.valueOf(end)),
           "UID"true);  

  UID u;
  Vector v = new Vector();
  for (int i = 0, len = r.length; i < len; i++) {
      if (r[i== null || !(r[iinstanceof FetchResponse))
    continue;
      
      FetchResponse fr = (FetchResponse)r[i];
      if ((u = (UID)fr.getItem(UID.class)) != null)
    v.addElement(u);
  }
    
  notifyResponseHandlers(r);
  handleResult(r[r.length-1]);

  UID[] ua = new UID[v.size()];
  v.copyInto(ua);
  return ua;
    }

    /**
     * Get the sequence numbers for UIDs ranging from start till end.
     * UID objects that contain the sequence numbers are returned.
     * If no UIDs in the given range are found, an empty array is returned.
     */
    public UID[] fetchSequenceNumbers(long[] uidsthrows ProtocolException {
  StringBuffer sb = new StringBuffer();
  for (int i = 0; i < uids.length; i++) {
      if (i > 0)
    sb.append(",");
      sb.append(String.valueOf(uids[i]));
  }

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

  UID u;
  Vector v = new Vector();
  for (int i = 0, len = r.length; i < len; i++) {
      if (r[i== null || !(r[iinstanceof FetchResponse))
    continue;
      
      FetchResponse fr = (FetchResponse)r[i];
      if ((u = (UID)fr.getItem(UID.class)) != null)
    v.addElement(u);
  }
    
  notifyResponseHandlers(r);
  handleResult(r[r.length-1]);

  UID[] ua = new UID[v.size()];
  v.copyInto(ua);
  return ua;
    }

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

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

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

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

    /**
     * COPY command.
     */
    public void copy(MessageSet[] msgsets, String mbox)
      throws ProtocolException {
  copy(MessageSet.toString(msgsets), mbox);
    }

    public void copy(int start, int end, String mbox)
      throws ProtocolException {
  copy(String.valueOf(start":" + String.valueOf(end),
        mbox);
    }

    private void copy(String msgSequence, String mbox)
      throws ProtocolException {
  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeAtom(msgSequence);
  args.writeString(mbox);

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

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

    /**
     * Set the specified flags on this message. <p>
     */
    public void storeFlags(int msg, Flags flags, boolean set)
      throws ProtocolException 
  storeFlags(String.valueOf(msg), flags, set);
    }

    private void storeFlags(String msgset, Flags flags, boolean set)
      throws ProtocolException {
  Response[] r;
  if (set)
      r = command("STORE " + msgset + " +FLAGS " 
       createFlagList(flags)null);
  else
      r = command("STORE " + msgset + " -FLAGS " 
      createFlagList(flags)null);
  
  // Dispatch untagged responses
  notifyResponseHandlers(r);
  handleResult(r[r.length-1]);
    }

    /**
     * Creates an IMAP flag_list from the given Flags object.
     */
    private String createFlagList(Flags flags) {
  StringBuffer sb = new StringBuffer();
  sb.append("(")// start of flag_list

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

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

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

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

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

    /* Apply the given SearchTerm on the specified sequence.
     * Returns array of matching sequence numbers. Note that an empty
     * array is returned for no matches.
     */
    private int[] search(String msgSequence, SearchTerm term)
      throws ProtocolException, SearchException {
  // Check if the search "text" terms contain only ASCII chars
  if (SearchSequence.isAscii(term)) {
      try {
    return issueSearch(msgSequence, term, null);
      catch (IOException ioex) { /* will not happen */ }
  }
  
  /* The search "text" terms do contain non-ASCII chars. We need to
   * use SEARCH CHARSET <charset> ...
   *  The charsets we try to use are UTF-8 and the locale's
   * default charset. If the server supports UTF-8, great, 
   * always use it. Else we try to use the default charset.
   */

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

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

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

    /* Apply the given SearchTerm on the specified sequence, using the
     * given charset. <p>
     * Returns array of matching sequence numbers. Note that an empty
     * array is returned for no matches.
     */
    private int[] issueSearch(String msgSequence, SearchTerm term,
                  String charset
       throws ProtocolException, SearchException, IOException {

  // Generate a search-sequence with the given charset
  Argument args = SearchSequence.generateSequence(term, 
        charset == null null 
              MimeUtility.javaCharset(charset)
      );
  args.writeAtom(msgSequence);

  Response[] r;

  if (charset == null// text is all US-ASCII
      r = command("SEARCH", args);
  else
      r = command("SEARCH CHARSET " + charset, args);

  Response response = r[r.length-1];
  int[] matches = null;

  // Grab all SEARCH responses
  if (response.isOK()) { // command succesful
      Vector v = new Vector();
      int num;
      for (int i = 0, len = r.length; i < len; i++) {
    if (!(r[iinstanceof IMAPResponse))
        continue;

    IMAPResponse ir = (IMAPResponse)r[i];
    // There *will* be one SEARCH response.
    if (ir.keyEquals("SEARCH")) {
        while ((num = ir.readNumber()) != -1)
      v.addElement(new Integer(num));
        r[inull;
    }
      }

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

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

    /**
     * NAMESPACE Command.
     *
     @see "RFC2342"
     */
    public Namespaces namespace() throws ProtocolException {
  if (!hasCapability("NAMESPACE")) 
      throw new BadCommandException("NAMESPACE not supported");

  Response[] r = command("NAMESPACE"null);

  Namespaces namespace = null;
  Response response = r[r.length-1];

  // Grab NAMESPACE response
  if (response.isOK()) { // command succesful 
      for (int i = 0, len = r.length; i < len; i++) {
    if (!(r[iinstanceof IMAPResponse))
        continue;

    IMAPResponse ir = (IMAPResponse)r[i];
    if (ir.keyEquals("NAMESPACE")) {
        if (namespace == null)
      namespace = new Namespaces(ir);
        r[inull;
    }
      }
  }

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

    /**
     * GETQUOTAROOT Command.
     *
     * Returns an array of Quota objects, representing the quotas
     * for this mailbox and, indirectly, the quotaroots for this
     * mailbox.
     *
     @see "RFC2087"
     */
    public Quota[] getQuotaRoot(String mboxthrows ProtocolException {
  if (!hasCapability("QUOTA")) 
      throw new BadCommandException("GETQUOTAROOT not supported");

  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);

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

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

  Hashtable tab = new Hashtable();

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

    IMAPResponse ir = (IMAPResponse)r[i];
    if (ir.keyEquals("QUOTAROOT")) {
        // quotaroot_response
        //           ::= "QUOTAROOT" SP astring *(SP astring)

        // read name of mailbox and throw away
        ir.readAtomString();
        // for each quotaroot add a placeholder quota
        String root = null;
        while ((root = ir.readAtomString()) != null)
      tab.put(root, new Quota(root));
        r[inull;
    else if (ir.keyEquals("QUOTA")) {
        Quota quota = parseQuota(ir);
        Quota q = (Quota)tab.get(quota.quotaRoot);
        if (q != null && q.resources != null) {
      // XXX - should merge resources
        }
        tab.put(quota.quotaRoot, quota);
        r[inull;
    }
      }
  }

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

  Quota[] qa = new Quota[tab.size()];
  Enumeration e = tab.elements();
  for (int i = 0; e.hasMoreElements(); i++)
      qa[i(Quota)e.nextElement();
  return qa;
    }

    /**
     * GETQUOTA Command.
     *
     * Returns an array of Quota objects, representing the quotas
     * for this quotaroot.
     *
     @see "RFC2087"
     */
    public Quota[] getQuota(String rootthrows ProtocolException {
  if (!hasCapability("QUOTA")) 
      throw new BadCommandException("QUOTA not supported");

  Argument args = new Argument();  
  args.writeString(root);

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

  Quota quota = null;
  Vector v = new Vector();
  Response response = r[r.length-1];

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

    IMAPResponse ir = (IMAPResponse)r[i];
    if (ir.keyEquals("QUOTA")) {
        quota = parseQuota(ir);
        v.addElement(quota);
        r[inull;
    }
      }
  }

  // dispatch remaining untagged responses
  notifyResponseHandlers(r);
  handleResult(response);
  Quota[] qa = new Quota[v.size()];
  v.copyInto(qa);
  return qa;
    }

    /**
     * SETQUOTA Command.
     *
     * Set the indicated quota on the corresponding quotaroot.
     *
     @see "RFC2087"
     */
    public void setQuota(Quota quotathrows ProtocolException {
  if (!hasCapability("QUOTA")) 
      throw new BadCommandException("QUOTA not supported");

  Argument args = new Argument();  
  args.writeString(quota.quotaRoot);
  Argument qargs = new Argument();  
  if (quota.resources != null) {
      for (int i = 0; i < quota.resources.length; i++) {
    qargs.writeAtom(quota.resources[i].name);
    qargs.writeNumber(quota.resources[i].limit);
      }
  }
  args.writeArgument(qargs);

  Response[] r = command("SETQUOTA", args);
  Response response = r[r.length-1];

  // XXX - It's not clear from the RFC whether the SETQUOTA command
  // will provoke untagged QUOTA responses.  If it does, perhaps
  // we should grab them here and return them?

  /*
  Quota quota = null;
  Vector v = new Vector();

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

    IMAPResponse ir = (IMAPResponse)r[i];
    if (ir.keyEquals("QUOTA")) {
        quota = parseQuota(ir);
        v.addElement(quota);
        r[i] = null;
    }
      }
  }
  */

  // dispatch remaining untagged responses
  notifyResponseHandlers(r);
  handleResult(response);
  /*
  Quota[] qa = new Quota[v.size()];
  v.copyInto(qa);
  return qa;
  */
    }

    /**
     * Parse a QUOTA response.
     */
    private Quota parseQuota(Response rthrows ParsingException {
  // quota_response ::= "QUOTA" SP astring SP quota_list
  String quotaRoot = r.readAtomString();  // quotaroot ::= astring
  Quota q = new Quota(quotaRoot);
  r.skipSpaces();
  // quota_list ::= "(" #quota_resource ")"
  if (r.readByte() != '(')
      throw new ParsingException("parse error in QUOTA");

  Vector v = new Vector();
  while (r.peekByte() != ')') {
      // quota_resource ::= atom SP number SP number
      String name = r.readAtom();
      if (name != null) {
    long usage = r.readLong();
    long limit = r.readLong();
    Quota.Resource res = new Quota.Resource(name, usage, limit);
    v.addElement(res);
      }
  }
  r.readByte();
  q.resources = new Quota.Resource[v.size()];
  v.copyInto(q.resources);
  return q;
    }


    /**
     * SETACL Command.
     *
     @see "RFC2086"
     */
    public void setACL(String mbox, char modifier, ACL acl)
        throws ProtocolException {
  if (!hasCapability("ACL")) 
      throw new BadCommandException("ACL not supported");

  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);
  args.writeString(acl.getName());
  String rights = acl.getRights().toString();
  if (modifier == '+' || modifier == '-')
      rights = modifier + rights;
  args.writeString(rights);

  Response[] r = command("SETACL", args);
  Response response = r[r.length-1];

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

    /**
     * DELETEACL Command.
     *
     @see "RFC2086"
     */
    public void deleteACL(String mbox, String userthrows ProtocolException {
  if (!hasCapability("ACL")) 
      throw new BadCommandException("ACL not supported");

  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);
  args.writeString(user);

  Response[] r = command("DELETEACL", args);
  Response response = r[r.length-1];

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

    /**
     * GETACL Command.
     *
     @see "RFC2086"
     */
    public ACL[] getACL(String mboxthrows ProtocolException {
  if (!hasCapability("ACL")) 
      throw new BadCommandException("ACL not supported");

  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);

  Response[] r = command("GETACL", args);
  Response response = r[r.length-1];

  // Grab all ACL responses
  Vector v = new Vector();
  if (response.isOK()) { // command succesful 
      for (int i = 0, len = r.length; i < len; i++) {
    if (!(r[iinstanceof IMAPResponse))
        continue;

    IMAPResponse ir = (IMAPResponse)r[i];
    if (ir.keyEquals("ACL")) {
        // acl_data ::= "ACL" SPACE mailbox
        //    *(SPACE identifier SPACE rights)
        // read name of mailbox and throw away
        ir.readAtomString();
        String name = null;
        while ((name = ir.readAtomString()) != null) {
      String rights = ir.readAtomString();
      if (rights == null)
          break;
      ACL acl = new ACL(name, new Rights(rights));
      v.addElement(acl);
        }
        r[inull;
    }
      }
  }

  // dispatch remaining untagged responses
  notifyResponseHandlers(r);
  handleResult(response);
  ACL[] aa = new ACL[v.size()];
  v.copyInto(aa);
  return aa;
    }

    /**
     * LISTRIGHTS Command.
     *
     @see "RFC2086"
     */
    public Rights[] listRights(String mbox, String user)
        throws ProtocolException {
  if (!hasCapability("ACL")) 
      throw new BadCommandException("ACL not supported");

  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);
  args.writeString(user);

  Response[] r = command("LISTRIGHTS", args);
  Response response = r[r.length-1];

  // Grab LISTRIGHTS response
  Vector v = new Vector();
  if (response.isOK()) { // command succesful 
      for (int i = 0, len = r.length; i < len; i++) {
    if (!(r[iinstanceof IMAPResponse))
        continue;

    IMAPResponse ir = (IMAPResponse)r[i];
    if (ir.keyEquals("LISTRIGHTS")) {
        // listrights_data ::= "LISTRIGHTS" SPACE mailbox
        //    SPACE identifier SPACE rights *(SPACE rights)
        // read name of mailbox and throw away
        ir.readAtomString();
        // read identifier and throw away
        ir.readAtomString();
        String rights;
        while ((rights = ir.readAtomString()) != null)
      v.addElement(new Rights(rights));
        r[inull;
    }
      }
  }

  // dispatch remaining untagged responses
  notifyResponseHandlers(r);
  handleResult(response);
  Rights[] ra = new Rights[v.size()];
  v.copyInto(ra);
  return ra;
    }

    /**
     * MYRIGHTS Command.
     *
     @see "RFC2086"
     */
    public Rights myRights(String mboxthrows ProtocolException {
  if (!hasCapability("ACL")) 
      throw new BadCommandException("ACL not supported");

  // encode the mbox as per RFC2060
  mbox = BASE64MailboxEncoder.encode(mbox);

  Argument args = new Argument();  
  args.writeString(mbox);

  Response[] r = command("MYRIGHTS", args);
  Response response = r[r.length-1];

  // Grab MYRIGHTS response
  Rights rights = null;
  if (response.isOK()) { // command succesful 
      for (int i = 0, len = r.length; i < len; i++) {
    if (!(r[iinstanceof IMAPResponse))
        continue;

    IMAPResponse ir = (IMAPResponse)r[i];
    if (ir.keyEquals("MYRIGHTS")) {
        // myrights_data ::= "MYRIGHTS" SPACE mailbox SPACE rights
        // read name of mailbox and throw away
        ir.readAtomString();
        String rs = ir.readAtomString();
        if (rights == null)
      rights = new Rights(rs);
        r[inull;
    }
      }
  }

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

    /*
     * The tag used on the IDLE command.  Set by idleStart() and
     * used in processIdleResponse() to determine if the response
     * is the matching end tag.
     */
    private String idleTag;

    /**
     * IDLE Command. <p>
     *
     * If the server supports the IDLE command extension, the IDLE
     * command is issued and this method blocks until a response has
     * been received.  Once the first response has been received, the
     * IDLE command is terminated and all responses are collected and
     * handled and this method returns. <p>
     *
     * Note that while this method is blocked waiting for a response,
     * no other threads may issue any commands to the server that would
     * use this same connection.
     *
     @see "RFC2177"
     @since  JavaMail 1.4.1
     */
    public synchronized void idleStart() throws ProtocolException {
  if (!hasCapability("IDLE")) 
      throw new BadCommandException("IDLE not supported");

  Response r;
  // write the command
  try {
      idleTag = writeCommand("IDLE"null);
      r = readResponse();
  catch (LiteralException lex) {
      r = lex.getResponse();
  catch (Exception ex) {
      // Convert this into a BYE response
      r = Response.byeResponse(ex);
  }

  if (!r.isContinuation())
      handleResult(r);
    }

    /**
     * While an IDLE command is in progress, read a response
     * sent from the server.  The response is read with no locks
     * held so that when the read blocks waiting for the response
     * from the server it's not holding locks that would prevent
     * other threads from interrupting the IDLE command.
     *
     @since  JavaMail 1.4.1
     */
    public synchronized Response readIdleResponse() {
  if (idleTag == null)
      return null;  // IDLE not in progress
  Response r = null;
  while (r == null) {
      try {
    r = readResponse();
      catch (InterruptedIOException iioex) {
    /*
     * If a socket timeout was set, the read will timeout
     * before the IDLE times out.  In that case, just go
     * back and read some more.  After all, the point of
     * IDLE is to sit here and wait until something happens.
     */
    if (iioex.bytesTransferred == 0)
        r = null;  // keep trying
    else
        // convert this into a BYE response
        r = Response.byeResponse(iioex);
      catch (IOException ioex) {
    // convert this into a BYE response
    r = Response.byeResponse(ioex);
      catch (ProtocolException pex) {
    // convert this into a BYE response
    r = Response.byeResponse(pex);
      }
  }
  return r;
    }

    /**
     * Process a response returned by readIdleResponse().
     * This method will be called with appropriate locks
     * held so that the processing of the response is safe.
     *
     @since  JavaMail 1.4.1
     */
    public boolean processIdleResponse(Response rthrows ProtocolException {
  Response[] responses = new Response[1];
  responses[0= r;
  boolean done = false;    // done reading responses?
  notifyResponseHandlers(responses);

  if (r.isBYE()) // shouldn't wait for command completion response
      done = true;

  // If this is a matching command completion response, we are done
  if (r.isTagged() && r.getTag().equals(idleTag))
      done = true;

  if (done)
      idleTag = null;  // no longer in IDLE

  handleResult(r);
  return !done;
    }

    // the DONE command to break out of IDLE
    private static final byte[] DONE = 'D''O''N''E''\r''\n' };

    /**
     * Abort an IDLE command.  While one thread is blocked in
     * readIdleResponse(), another thread will use this method
     * to abort the IDLE command, which will cause the server
     * to send the closing tag for the IDLE command, which
     * readIdleResponse() and processIdleResponse() will see
     * and terminate the IDLE state.
     *
     @since  JavaMail 1.4.1
     */
    public void idleAbort() throws ProtocolException {
  OutputStream os = getOutputStream();
  try {
      os.write(DONE);
      os.flush();
  catch (IOException ex) {
      // nothing to do, hope to detect it again later
  }
    }
}