Source for gnu.inet.imap.IMAPConnection

   1: /*
   2:  * IMAPConnection.java
   3:  * Copyright (C) 2003,2004 The Free Software Foundation
   4:  * 
   5:  * This file is part of GNU inetlib, a library.
   6:  * 
   7:  * GNU inetlib is free software; you can redistribute it and/or modify
   8:  * it under the terms of the GNU General Public License as published by
   9:  * the Free Software Foundation; either version 2 of the License, or
  10:  * (at your option) any later version.
  11:  * 
  12:  * GNU inetlib is distributed in the hope that it will be useful,
  13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15:  * GNU General Public License for more details.
  16:  * 
  17:  * You should have received a copy of the GNU General Public License
  18:  * along with this library; if not, write to the Free Software
  19:  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20:  * 
  21:  * Linking this library statically or dynamically with other modules is
  22:  * making a combined work based on this library.  Thus, the terms and
  23:  * conditions of the GNU General Public License cover the whole
  24:  * combination.
  25:  *
  26:  * As a special exception, the copyright holders of this library give you
  27:  * permission to link this library with independent modules to produce an
  28:  * executable, regardless of the license terms of these independent
  29:  * modules, and to copy and distribute the resulting executable under
  30:  * terms of your choice, provided that you also meet, for each linked
  31:  * independent module, the terms and conditions of the license of that
  32:  * module.  An independent module is a module which is not derived from
  33:  * or based on this library.  If you modify this library, you may extend
  34:  * this exception to your version of the library, but you are not
  35:  * obliged to do so.  If you do not wish to do so, delete this
  36:  * exception statement from your version.
  37:  */
  38: 
  39: package gnu.inet.imap;
  40: 
  41: import java.io.BufferedInputStream;
  42: import java.io.BufferedOutputStream;
  43: import java.io.InputStream;
  44: import java.io.IOException;
  45: import java.io.OutputStream;
  46: import java.net.InetSocketAddress;
  47: import java.net.ProtocolException;
  48: import java.net.Socket;
  49: import java.net.UnknownHostException;
  50: import java.security.GeneralSecurityException;
  51: import java.security.KeyStore;
  52: import java.security.MessageDigest;
  53: import java.security.NoSuchAlgorithmException;
  54: import java.util.ArrayList;
  55: import java.util.Collections;
  56: import java.util.HashMap;
  57: import java.util.Iterator;
  58: import java.util.List;
  59: import java.util.Map;
  60: import java.util.Properties;
  61: import java.util.TreeMap;
  62: 
  63: import javax.net.ssl.SSLContext;
  64: import javax.net.ssl.SSLSocket;
  65: import javax.net.ssl.SSLSocketFactory;
  66: import javax.net.ssl.TrustManager;
  67: 
  68: import javax.security.auth.callback.CallbackHandler;
  69: import javax.security.sasl.Sasl;
  70: import javax.security.sasl.SaslClient;
  71: import javax.security.sasl.SaslException;
  72: 
  73: import gnu.inet.util.BASE64;
  74: import gnu.inet.util.CRLFOutputStream;
  75: import gnu.inet.util.EmptyX509TrustManager;
  76: import gnu.inet.util.Logger;
  77: import gnu.inet.util.SaslCallbackHandler;
  78: import gnu.inet.util.SaslCramMD5;
  79: import gnu.inet.util.SaslInputStream;
  80: import gnu.inet.util.SaslLogin;
  81: import gnu.inet.util.SaslOutputStream;
  82: import gnu.inet.util.SaslPlain;
  83: 
  84: /**
  85:  * The protocol class implementing IMAP4rev1.
  86:  *
  87:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  88:  */
  89: public class IMAPConnection
  90:   implements IMAPConstants
  91: {
  92: 
  93:   /**
  94:    * Prefix for tags.
  95:    */
  96:   protected static final String TAG_PREFIX = "A";
  97: 
  98:   /**
  99:    * The encoding used to create strings for IMAP commands.
 100:    */
 101:   protected static final String US_ASCII = "US-ASCII";
 102: 
 103:   /**
 104:    * The default IMAP port.
 105:    */
 106:   protected static final int DEFAULT_PORT = 143;
 107: 
 108:   /**
 109:    * The default IMAP-SSL port.
 110:    */
 111:   protected static final int DEFAULT_SSL_PORT = 993;
 112: 
 113:   /**
 114:    * The socket used for communication with the server.
 115:    */
 116:   protected Socket socket;
 117: 
 118:   /**
 119:    * The tokenizer used to read IMAP responses from.
 120:    */
 121:   protected IMAPResponseTokenizer in;
 122: 
 123:   /**
 124:    * The output stream.
 125:    */
 126:   protected CRLFOutputStream out;
 127: 
 128:   /**
 129:    * List of responses received asynchronously.
 130:    */
 131:   protected List asyncResponses;
 132: 
 133:   /**
 134:    * List of alert strings.
 135:    */
 136:   private List alerts;
 137: 
 138:   /*
 139:    * Used to generate new tags for tagged commands.
 140:    */
 141:   private int tagIndex = 0;
 142: 
 143:   /*
 144:    * Print debugging output to stderr.
 145:    */
 146:   private boolean debug;
 147: 
 148:   /*
 149:    * Print debugging output using ANSI colour escape sequences.
 150:    */
 151:   private boolean ansiDebug = false;
 152: 
 153:   /**
 154:    * Creates a new connection to the default IMAP port.
 155:    * @param host the name of the host to connect to
 156:    */
 157:   public IMAPConnection(String host)
 158:     throws UnknownHostException, IOException
 159:   {
 160:     this(host, -1, 0, 0, false, null, false);
 161:   }
 162:   
 163:   /**
 164:    * Creates a new connection.
 165:    * @param host the name of the host to connect to
 166:    * @param port the port to connect to, or -1 for the default
 167:    */
 168:   public IMAPConnection(String host, int port)
 169:     throws UnknownHostException, IOException
 170:   {
 171:     this(host, port, 0, 0, false, null, false);
 172:   }
 173:   
 174:   /**
 175:    * Creates a new connection.
 176:    * @param host the name of the host to connect to
 177:    * @param port the port to connect to, or -1 for the default
 178:    * @param connectionTimeout the socket connection timeout
 179:    * @param timeout the socket timeout
 180:    * @param debug log debugging information
 181:    */
 182:   public IMAPConnection(String host, int port,
 183:                         int connectionTimeout, int timeout,
 184:                         boolean debug)
 185:     throws UnknownHostException, IOException
 186:   {
 187:     this(host, port, connectionTimeout, timeout, false, null, debug);
 188:   }
 189:   
 190:   /**
 191:    * Creates a new secure connection using the specified trust manager.
 192:    * @param host the name of the host to connect to
 193:    * @param port the port to connect to, or -1 for the default
 194:    * @param tm a trust manager used to check SSL certificates, or null to
 195:    * use the default
 196:    */
 197:   public IMAPConnection(String host, int port, TrustManager tm)
 198:     throws UnknownHostException, IOException
 199:   {
 200:     this(host, port, 0, 0, true, tm, false);
 201:   }
 202:   
 203:   /**
 204:    * Creates a new connection.
 205:    * @param host the name of the host to connect to
 206:    * @param port the port to connect to, or -1 for the default
 207:    * @param connectionTimeout the socket connection timeout
 208:    * @param timeout the socket timeout
 209:    * @param secure if an IMAP-SSL connection should be made
 210:    * @param tm a trust manager used to check SSL certificates, or null to
 211:    * use the default
 212:    * @param debug log debugging information
 213:    */
 214:   public IMAPConnection(String host, int port,
 215:                         int connectionTimeout, int timeout,
 216:                         boolean secure, TrustManager tm,
 217:                         boolean debug)
 218:     throws UnknownHostException, IOException
 219:   {
 220:     this.debug = debug;
 221:     if (port < 0)
 222:       {
 223:         port = secure ? DEFAULT_SSL_PORT : DEFAULT_PORT;
 224:       }
 225:     
 226:     // Set up socket
 227:     try
 228:       {
 229:         socket = new Socket();
 230:         InetSocketAddress address = new InetSocketAddress(host, port);
 231:         if (connectionTimeout > 0)
 232:           {
 233:             socket.connect(address, connectionTimeout);
 234:           }
 235:         else
 236:           {
 237:             socket.connect(address);
 238:           }
 239:         if (timeout > 0)
 240:           {
 241:             socket.setSoTimeout(timeout);
 242:           }
 243:         if (secure)
 244:           {
 245:             SSLSocketFactory factory = getSSLSocketFactory(tm);
 246:             SSLSocket ss =
 247:               (SSLSocket) factory.createSocket(socket, host, port, true);
 248:             String[] protocols = { "TLSv1", "SSLv3" };
 249:             ss.setEnabledProtocols(protocols);
 250:             ss.setUseClientMode(true);
 251:             ss.startHandshake();
 252:             socket = ss;
 253:           }
 254:       }
 255:     catch (GeneralSecurityException e)
 256:       {
 257:         e.printStackTrace();
 258:         throw new IOException(e.getMessage());
 259:       }
 260:     
 261:     InputStream in = socket.getInputStream();
 262:     in = new BufferedInputStream(in);
 263:     this.in = new IMAPResponseTokenizer(in);
 264:     OutputStream out = socket.getOutputStream();
 265:     out = new BufferedOutputStream(out);
 266:     this.out = new CRLFOutputStream(out);
 267:     
 268:     asyncResponses = new ArrayList();
 269:     alerts = new ArrayList();
 270:   }
 271:   
 272:   /**
 273:    * Sets whether debugging output should use ANSI colour escape sequences.
 274:    */
 275:   public void setAnsiDebug(boolean flag)
 276:   {
 277:     ansiDebug = flag;
 278:   }
 279:   
 280:   /**
 281:    * Returns a new tag for a command.
 282:    */
 283:   protected String newTag()
 284:   {
 285:     return TAG_PREFIX + (++tagIndex);
 286:   }
 287:   
 288:   /**
 289:    * Sends the specified IMAP tagged command to the server.
 290:    */
 291:   protected void sendCommand(String tag, String command)
 292:     throws IOException
 293:   {
 294:     if (debug)
 295:       {
 296:         Logger logger = Logger.getInstance();
 297:         logger.log("imap", "> " + tag + " " + command);
 298:       }
 299:     out.write(tag + ' ' + command);
 300:     out.writeln();
 301:     out.flush();
 302:   }
 303:   
 304:   /**
 305:    * Sends the specified IMAP command.
 306:    * @param command the command
 307:    * @return true if OK was received, or false if NO was received
 308:    * @exception IOException if BAD was received or an I/O error occurred
 309:    */
 310:   protected boolean invokeSimpleCommand(String command)
 311:     throws IOException
 312:   {
 313:     String tag = newTag();
 314:     sendCommand(tag, command);
 315:     while (true)
 316:       {
 317:         IMAPResponse response = readResponse();
 318:         String id = response.getID();
 319:         if (tag.equals(response.getTag()))
 320:           {
 321:             processAlerts(response);
 322:             if (id == OK)
 323:               {
 324:                 return true;
 325:               }
 326:             else if (id == NO)
 327:               {
 328:                 return false;
 329:               }
 330:             else
 331:               {
 332:                 throw new IMAPException(id, response.getText());
 333:               }
 334:           }
 335:         else if (response.isUntagged())
 336:           {
 337:             asyncResponses.add(response);
 338:           }
 339:         else
 340:           {
 341:             throw new IMAPException(id, response.getText());
 342:           }
 343:       }
 344:   }
 345:   
 346:   
 347:   /**
 348:    * Reads an IMAP response from the server.
 349:    * The response will consist of <i>either</i>:
 350:    * <ul>
 351:    * <li>A tagged response corresponding to a pending command</li>
 352:    * <li>An untagged error response</li>
 353:    * <li>A continuation response</li>
 354:    */
 355:   protected IMAPResponse readResponse()
 356:     throws IOException
 357:   {
 358:     IMAPResponse response = in.next();
 359:     if (debug)
 360:       {
 361:         Logger logger = Logger.getInstance();
 362:         if (response == null)
 363:           {
 364:             logger.log("imap", "<EOF");
 365:           }
 366:         else if (ansiDebug)
 367:           {
 368:             logger.log("imap", "< " + response.toANSIString());
 369:           }
 370:         else
 371:           {
 372:             logger.log("imap", "< " + response.toString());
 373:           }
 374:       }
 375:     if (response == null)
 376:       {
 377:         throw new IOException("EOF");
 378:       }
 379:     return response;
 380:   }
 381:   
 382:   // -- Alert notifications --
 383:   
 384:   private void processAlerts(IMAPResponse response)
 385:   {
 386:     List code = response.getResponseCode();
 387:     if (code != null && code.contains(ALERT))
 388:       {
 389:         alerts.add(response.getText());
 390:       }
 391:   }
 392:   
 393:   /**
 394:    * Indicates if there are alerts pending for the user-agent.
 395:    */
 396:   public boolean alertsPending()
 397:   {
 398:     return (alerts.size() > 0);
 399:   }
 400:   
 401:   /**
 402:    * Returns the pending alerts for the user-agent as an array.
 403:    */
 404:   public String[] getAlerts()
 405:   {
 406:     String[] a = new String[alerts.size()];
 407:     alerts.toArray(a);
 408:     alerts.clear();             // flush
 409:     return a;
 410:   }
 411:   
 412:   // -- IMAP commands --
 413:   
 414:   /**
 415:    * Returns a list of the capabilities of the IMAP server.
 416:    */
 417:   public List capability()
 418:     throws IOException
 419:   {
 420:     String tag = newTag();
 421:     sendCommand(tag, CAPABILITY);
 422:     List capabilities = new ArrayList();
 423:     while (true)
 424:       {
 425:         IMAPResponse response = readResponse();
 426:         String id = response.getID();
 427:         if (tag.equals(response.getTag()))
 428:           {
 429:             processAlerts(response);
 430:             if (id == OK)
 431:               {
 432:                 if (capabilities.size() == 0)
 433:                   {
 434:                     // The capability list may be contained in the
 435:                     // response text.
 436:                     addTokens(capabilities, response.getText());
 437:                   }
 438:                 return capabilities;
 439:               }
 440:             else
 441:               {
 442:                 throw new IMAPException(id, response.getText());
 443:               }
 444:           }
 445:         else if (response.isUntagged())
 446:           {
 447:             if (id == CAPABILITY)
 448:               {
 449:                 // The capability list may be contained in the
 450:                 // response text.
 451:                 addTokens(capabilities, response.getText());
 452:               }
 453:             else if (id == OK)
 454:               {
 455:                 // The capability list may be contained in the
 456:                 // response code.
 457:                 List code = response.getResponseCode();
 458:                 int len = (code == null) ? 0 : code.size();
 459:                 if (len > 0 && CAPABILITY.equals(code.get(0)))
 460:                   {
 461:                     for (int i = 1; i < len; i++)
 462:                       {
 463:                         String token = (String) code.get(i);
 464:                         if (!capabilities.contains(token))
 465:                           {
 466:                             capabilities.add(token);
 467:                           }
 468:                       }
 469:                   }
 470:                 else
 471:                   {
 472:                     asyncResponses.add(response);
 473:                   }
 474:               }
 475:             else
 476:               {
 477:                 asyncResponses.add(response);
 478:               }
 479:           }
 480:         else
 481:           {
 482:             throw new IMAPException(id, response.getText());
 483:           }
 484:       }
 485:   }
 486:   
 487:   private void addTokens(List list, String text)
 488:   {
 489:     int start = 0;
 490:     int end = text.indexOf(' ');
 491:     String token;
 492:     while (end != -1)
 493:       {
 494:         token = text.substring(start, end);
 495:         if (!list.contains(token))
 496:           {
 497:             list.add(token);
 498:           }
 499:         start = end + 1;
 500:         end = text.indexOf(' ', start);
 501:       }
 502:     token = text.substring(start);
 503:     if (token.length() > 0 && !list.contains(token))
 504:       {
 505:         list.add(token);
 506:       }
 507:   }
 508:   
 509:   /**
 510:    * Ping the server.
 511:    * If a change in mailbox state is detected, a new mailbox status is
 512:    * returned, otherwise this method returns null.
 513:    */
 514:   public MailboxStatus noop()
 515:     throws IOException
 516:   {
 517:     String tag = newTag();
 518:     sendCommand(tag, NOOP);
 519:     boolean changed = false;
 520:     MailboxStatus ms = new MailboxStatus();
 521:     Iterator asyncIterator = asyncResponses.iterator();
 522:     while (true)
 523:       {
 524:         IMAPResponse response;
 525:         // Process any asynchronous responses first
 526:         if (asyncIterator.hasNext())
 527:           {
 528:             response = (IMAPResponse) asyncIterator.next();
 529:             asyncIterator.remove();
 530:           }
 531:         else
 532:           {
 533:             response = readResponse();
 534:           }
 535:         String id = response.getID();
 536:         if (response.isUntagged())
 537:           {
 538:             changed = changed || updateMailboxStatus(ms, id, response);
 539:           }
 540:         else if (tag.equals(response.getTag()))
 541:           {
 542:             processAlerts(response);
 543:             if (id == OK)
 544:               {
 545:                 return changed ? ms : null;
 546:               }
 547:             else
 548:               {
 549:                 throw new IMAPException(id, response.getText());
 550:               }
 551:           }
 552:         else
 553:           {
 554:             throw new IMAPException(id, response.getText());
 555:           }
 556:       }
 557:   }
 558: 
 559:   /**
 560:    * Returns a configured SSLSocketFactory to use in creating new SSL
 561:    * sockets.
 562:    * @param tm an optional trust manager to use
 563:    */
 564:   protected SSLSocketFactory getSSLSocketFactory(TrustManager tm)
 565:     throws GeneralSecurityException
 566:   {
 567:     if (tm == null)
 568:       {
 569:         tm = new EmptyX509TrustManager();
 570:       }
 571:     SSLContext context = SSLContext.getInstance("TLS");
 572:     TrustManager[] trust = new TrustManager[] { tm };
 573:     context.init(null, trust, null);
 574:     return context.getSocketFactory();
 575:   }
 576:   
 577:   /**
 578:    * Attempts to start TLS on the specified connection.
 579:    * See RFC 2595 for details.
 580:    * @return true if successful, false otherwise
 581:    */
 582:   public boolean starttls()
 583:     throws IOException
 584:   {
 585:     return starttls(new EmptyX509TrustManager());
 586:   }
 587:   
 588:   /**
 589:    * Attempts to start TLS on the specified connection.
 590:    * See RFC 2595 for details.
 591:    * @param tm the custom trust manager to use
 592:    * @return true if successful, false otherwise
 593:    */
 594:   public boolean starttls(TrustManager tm)
 595:     throws IOException
 596:   {
 597:     try
 598:       {
 599:         SSLSocketFactory factory = getSSLSocketFactory(tm);
 600:         String hostname = socket.getInetAddress().getHostName();
 601:         int port = socket.getPort();
 602:         
 603:         String tag = newTag();
 604:         sendCommand(tag, STARTTLS);
 605:         while (true)
 606:           {
 607:             IMAPResponse response = readResponse();
 608:             if (response.isTagged() && tag.equals(response.getTag()))
 609:               {
 610:                 processAlerts(response);
 611:                 String id = response.getID();
 612:                 if (id == OK)
 613:                   {
 614:                     break;              // negotiate TLS
 615:                   }
 616:                 else if (id == BAD)
 617:                   {
 618:                     return false;
 619:                   }
 620:               }
 621:             else
 622:               {
 623:                 asyncResponses.add(response);
 624:               }
 625:           }
 626:         
 627:         SSLSocket ss =
 628:           (SSLSocket) factory.createSocket(socket, hostname, port, true);
 629:         String[] protocols = { "TLSv1", "SSLv3" };
 630:         ss.setEnabledProtocols(protocols);
 631:         ss.setUseClientMode(true);
 632:         ss.startHandshake();
 633:         
 634:         InputStream in = ss.getInputStream();
 635:         in = new BufferedInputStream(in);
 636:         this.in = new IMAPResponseTokenizer(in);
 637:         OutputStream out = ss.getOutputStream();
 638:         out = new BufferedOutputStream(out);
 639:         this.out = new CRLFOutputStream(out);
 640:         return true;
 641:       }
 642:     catch (GeneralSecurityException e)
 643:       {
 644:         e.printStackTrace();
 645:         return false;
 646:       }
 647:   }
 648:   
 649:   /**
 650:    * Login to the connection using the username and password method.
 651:    * @param username the authentication principal
 652:    * @param password the authentication credentials
 653:    * @return true if authentication was successful, false otherwise
 654:    */
 655:   public boolean login(String username, String password)
 656:     throws IOException
 657:   {
 658:     return invokeSimpleCommand(LOGIN + ' ' + quote(username) +
 659:                                ' ' + quote(password));
 660:   }
 661:   
 662:   /**
 663:    * Authenticates the connection using the specified SASL mechanism,
 664:    * username, and password.
 665:    * @param mechanism a SASL authentication mechanism, e.g. LOGIN, PLAIN,
 666:    * CRAM-MD5, GSSAPI
 667:    * @param username the authentication principal
 668:    * @param password the authentication credentials
 669:    * @return true if authentication was successful, false otherwise
 670:    */
 671:   public boolean authenticate(String mechanism, String username,
 672:                               String password)
 673:     throws IOException
 674:   {
 675:     try
 676:       {
 677:         String[] m = new String[] { mechanism };
 678:         CallbackHandler ch = new SaslCallbackHandler(username, password);
 679:         // Avoid lengthy callback procedure for GNU Crypto
 680:         Properties p = new Properties();
 681:         p.put("gnu.crypto.sasl.username", username);
 682:         p.put("gnu.crypto.sasl.password", password);
 683:         SaslClient sasl = Sasl.createSaslClient(m, null, "imap",
 684:                                                 socket.getInetAddress().
 685:                                                 getHostName(), p, ch);
 686:         if (sasl == null)
 687:           {
 688:             // Fall back to home-grown SASL clients
 689:             if ("LOGIN".equalsIgnoreCase(mechanism))
 690:               {
 691:                 sasl = new SaslLogin(username, password);
 692:               }
 693:             else if ("PLAIN".equalsIgnoreCase(mechanism))
 694:               {
 695:                 sasl = new SaslPlain(username, password);
 696:               }
 697:             else if ("CRAM-MD5".equalsIgnoreCase(mechanism))
 698:               {
 699:                 sasl = new SaslCramMD5(username, password);
 700:               }
 701:             else
 702:               {
 703:                 if (debug)
 704:                   {
 705:                     Logger logger = Logger.getInstance();
 706:                     logger.log("imap", mechanism + " not available");
 707:                   }
 708:                 return false;
 709:               }
 710:           }
 711:         
 712:         StringBuffer cmd = new StringBuffer(AUTHENTICATE);
 713:         cmd.append(' ');
 714:         cmd.append(mechanism);
 715:         String tag = newTag();
 716:         sendCommand(tag, cmd.toString());
 717:         while (true)
 718:           {
 719:             IMAPResponse response = readResponse();
 720:             if (tag.equals(response.getTag()))
 721:               {
 722:                 processAlerts(response);
 723:                 String id = response.getID();
 724:                 if (id == OK)
 725:                   {
 726:                     String qop =
 727:                      (String) sasl.getNegotiatedProperty(Sasl.QOP);
 728:                     if ("auth-int".equalsIgnoreCase(qop)
 729:                         || "auth-conf".equalsIgnoreCase(qop))
 730:                       {
 731:                         InputStream in = socket.getInputStream();
 732:                         in = new BufferedInputStream(in);
 733:                         in = new SaslInputStream(sasl, in);
 734:                         this.in = new IMAPResponseTokenizer(in);
 735:                         OutputStream out = socket.getOutputStream();
 736:                         out = new BufferedOutputStream(out);
 737:                         out = new SaslOutputStream(sasl, out);
 738:                         this.out = new CRLFOutputStream(out);
 739:                       }
 740:                     return true;
 741:                   }
 742:                 else if (id == NO)
 743:                   {
 744:                     return false;
 745:                   }
 746:                 else if (id == BAD)
 747:                   {
 748:                     throw new IMAPException(id, response.getText());
 749:                   }
 750:               }
 751:             else if (response.isContinuation())
 752:               {
 753:                 try
 754:                   {
 755:                     byte[] c0 = response.getText().getBytes(US_ASCII);
 756:                     byte[] c1 = BASE64.decode(c0);       // challenge
 757:                     byte[] r0 = sasl.evaluateChallenge(c1);
 758:                     byte[] r1 = BASE64.encode(r0);       // response
 759:                     out.write(r1);
 760:                     out.writeln();
 761:                     out.flush();
 762:                     if (debug)
 763:                       {
 764:                         Logger logger = Logger.getInstance();
 765:                         logger.log("imap", "> " + new String(r1, US_ASCII));
 766:                       }
 767:                   }
 768:                 catch (SaslException e)
 769:                   {
 770:                     // Error in SASL challenge evaluation - cancel exchange
 771:                     out.write(0x2a);
 772:                     out.writeln();
 773:                     out.flush();
 774:                     if (debug)
 775:                       {
 776:                         Logger logger = Logger.getInstance();
 777:                         logger.log("imap", "> *");
 778:                       }
 779:                   }
 780:               }
 781:             else
 782:               {
 783:                 asyncResponses.add(response);
 784:               }
 785:           }
 786:       }
 787:     catch (SaslException e)
 788:       {
 789:         if (debug)
 790:           {
 791:             Logger logger = Logger.getInstance();
 792:             logger.error("imap", e);
 793:           }
 794:         return false;             // No provider for mechanism
 795:       }
 796:     catch (RuntimeException e)
 797:       {
 798:         if (debug)
 799:           {
 800:             Logger logger = Logger.getInstance();
 801:             logger.error("imap", e);
 802:           }
 803:         return false;             // No javax.security.sasl classes
 804:       }
 805:   }
 806:   
 807:   /**
 808:    * Logout this connection.
 809:    * Underlying network resources will be freed.
 810:    */
 811:   public void logout()
 812:     throws IOException
 813:   {
 814:     String tag = newTag();
 815:     sendCommand(tag, LOGOUT);
 816:     while (true)
 817:       {
 818:         IMAPResponse response = readResponse();
 819:         if (response.isTagged() && tag.equals(response.getTag()))
 820:           {
 821:             processAlerts(response);
 822:             String id = response.getID();
 823:             if (id == OK)
 824:               {
 825:                 socket.close();
 826:                 return;
 827:               }
 828:             else
 829:               {
 830:                 throw new IMAPException(id, response.getText());
 831:               }
 832:           }
 833:         else
 834:           {
 835:             asyncResponses.add(response);
 836:           }
 837:       }
 838:   }
 839:   
 840:   /**
 841:    * Selects the specified mailbox.
 842:    * The mailbox is identified as read-write if writes are permitted.
 843:    * @param mailbox the mailbox name
 844:    * @return a MailboxStatus containing the state of the selected mailbox
 845:    */
 846:   public MailboxStatus select(String mailbox)
 847:     throws IOException
 848:   {
 849:     return selectImpl(mailbox, SELECT);
 850:   }
 851:   
 852:   /**
 853:    * Selects the specified mailbox.
 854:    * The mailbox is identified as read-only.
 855:    * @param mailbox the mailbox name
 856:    * @return a MailboxStatus containing the state of the selected mailbox
 857:    */
 858:   public MailboxStatus examine(String mailbox)
 859:     throws IOException
 860:   {
 861:     return selectImpl(mailbox, EXAMINE);
 862:   }
 863: 
 864:   protected MailboxStatus selectImpl(String mailbox, String command)
 865:     throws IOException
 866:   {
 867:     String tag = newTag();
 868:     sendCommand(tag, command + ' ' + quote(UTF7imap.encode(mailbox)));
 869:     MailboxStatus ms = new MailboxStatus();
 870:     while (true)
 871:       {
 872:         IMAPResponse response = readResponse();
 873:         String id = response.getID();
 874:         if (response.isUntagged())
 875:           {
 876:             if (!updateMailboxStatus(ms, id, response))
 877:               {
 878:                 asyncResponses.add(response);
 879:               }
 880:           }
 881:         else if (tag.equals(response.getTag()))
 882:           {
 883:             processAlerts(response);
 884:             if (id == OK)
 885:               {
 886:                 List rc = response.getResponseCode();
 887:                 if (rc != null && rc.size() > 0 && rc.get(0) == READ_WRITE)
 888:                   {
 889:                     ms.readWrite = true;
 890:                   }
 891:                 return ms;
 892:               }
 893:             else
 894:               {
 895:                 throw new IMAPException(id, response.getText());
 896:               }
 897:           }
 898:         else
 899:           {
 900:             throw new IMAPException(id, response.getText());
 901:           }
 902:       }
 903:   }
 904:   
 905:   protected boolean updateMailboxStatus(MailboxStatus ms, String id,
 906:                                         IMAPResponse response)
 907:     throws IOException
 908:   {
 909:     if (id == OK)
 910:       {
 911:         boolean changed = false;
 912:         List rc = response.getResponseCode();
 913:         int len = (rc == null) ? 0 : rc.size();
 914:         for (int i = 0; i < len; i++)
 915:           {
 916:             Object ocmd = rc.get(i);
 917:             if (ocmd instanceof String)
 918:               {
 919:                 String cmd = (String) ocmd;
 920:                 if (i + 1 < len)
 921:                   {
 922:                     Object oparam = rc.get(i + 1);
 923:                     if (oparam instanceof String)
 924:                       {
 925:                         String param = (String) oparam;
 926:                         try
 927:                           {
 928:                             if (cmd == UNSEEN)
 929:                               {
 930:                                 ms.firstUnreadMessage =
 931:                                   Integer.parseInt(param);
 932:                                 i++;
 933:                                 changed = true;
 934:                               }
 935:                             else if (cmd == UIDVALIDITY)
 936:                               {
 937:                                 ms.uidValidity = Integer.parseInt(param);
 938:                                 i++;
 939:                                 changed = true;
 940:                               }
 941:                           }
 942:                         catch (NumberFormatException e)
 943:                           {
 944:                             throw new ProtocolException("Illegal " + cmd +
 945:                                                         " value: " + param);
 946:                           }
 947:                       }
 948:                     else if (oparam instanceof List)
 949:                       {
 950:                         if (cmd == PERMANENTFLAGS)
 951:                           {
 952:                             ms.permanentFlags = (List) oparam;
 953:                             i++;
 954:                             changed = true;
 955:                           }
 956:                       }
 957:                   }
 958:               }
 959:           }
 960:         return changed;
 961:       }
 962:     else if (id == EXISTS)
 963:       {
 964:         ms.messageCount = response.getCount();
 965:         return true;
 966:       }
 967:     else if (id == RECENT)
 968:       {
 969:         ms.newMessageCount = response.getCount();
 970:         return true;
 971:       }
 972:     else if (id == FLAGS)
 973:       {
 974:         ms.flags = response.getResponseCode();
 975:         return true;
 976:       }
 977:     else
 978:       {
 979:         return false;
 980:       }
 981:   }
 982:   
 983:   /**
 984:    * Creates a mailbox with the specified name.
 985:    * @param mailbox the mailbox name
 986:    * @return true if the mailbox was successfully created, false otherwise
 987:    */
 988:   public boolean create(String mailbox)
 989:     throws IOException
 990:   {
 991:     return invokeSimpleCommand(CREATE + ' ' + quote(UTF7imap.encode(mailbox)));
 992:   }
 993:   
 994:   /**
 995:    * Deletes the mailbox with the specified name.
 996:    * @param mailbox the mailbox name
 997:    * @return true if the mailbox was successfully deleted, false otherwise
 998:    */
 999:   public boolean delete(String mailbox)
1000:     throws IOException
1001:   {
1002:     return invokeSimpleCommand(DELETE + ' ' + quote(UTF7imap.encode(mailbox)));
1003:   }
1004:   
1005:   /**
1006:    * Renames the source mailbox to the specified name.
1007:    * @param source the source mailbox name
1008:    * @param target the target mailbox name
1009:    * @return true if the mailbox was successfully renamed, false otherwise
1010:    */
1011:   public boolean rename(String source, String target)
1012:     throws IOException
1013:   {
1014:     return invokeSimpleCommand(RENAME + ' ' + quote(UTF7imap.encode(source)) +
1015:                                ' ' + quote(UTF7imap.encode(target)));
1016:   }
1017:   
1018:   /**
1019:    * Adds the specified mailbox to the set of subscribed mailboxes as
1020:    * returned by the LSUB command.
1021:    * @param mailbox the mailbox name
1022:    * @return true if the mailbox was successfully subscribed, false otherwise
1023:    */
1024:   public boolean subscribe(String mailbox)
1025:     throws IOException
1026:   {
1027:     return invokeSimpleCommand(SUBSCRIBE + ' ' +
1028:                                quote(UTF7imap.encode(mailbox)));
1029:   }
1030:   
1031:   /**
1032:    * Removes the specified mailbox from the set of subscribed mailboxes as
1033:    * returned by the LSUB command.
1034:    * @param mailbox the mailbox name
1035:    * @return true if the mailbox was successfully unsubscribed, false otherwise
1036:    */
1037:   public boolean unsubscribe(String mailbox)
1038:     throws IOException
1039:   {
1040:     return invokeSimpleCommand(UNSUBSCRIBE + ' ' +
1041:                                quote(UTF7imap.encode(mailbox)));
1042:   }
1043:   
1044:   /**
1045:    * Returns a subset of names from the compete set of names available to
1046:    * the client.
1047:    * @param reference the context relative to which mailbox names are
1048:    * defined
1049:    * @param mailbox a mailbox name, possibly including IMAP wildcards
1050:    */
1051:   public ListEntry[] list(String reference, String mailbox)
1052:     throws IOException
1053:   {
1054:     return listImpl(LIST, reference, mailbox);
1055:   }
1056:   
1057:   /**
1058:    * Returns a subset of subscribed names.
1059:    * @see #list
1060:    */
1061:   public ListEntry[] lsub(String reference, String mailbox)
1062:     throws IOException
1063:   {
1064:     return listImpl(LSUB, reference, mailbox);
1065:   }
1066:   
1067:   protected ListEntry[] listImpl(String command, String reference,
1068:                                  String mailbox)
1069:     throws IOException
1070:   {
1071:     if (reference == null)
1072:       {
1073:         reference = "";
1074:       }
1075:     if (mailbox == null)
1076:       {
1077:         mailbox = "";
1078:       }
1079:     String tag = newTag();
1080:     sendCommand(tag, command + ' ' +
1081:                 quote(UTF7imap.encode(reference)) + ' ' +
1082:                 quote(UTF7imap.encode(mailbox)));
1083:     List acc = new ArrayList();
1084:     while (true)
1085:       {
1086:         IMAPResponse response = readResponse();
1087:         String id = response.getID();
1088:         if (response.isUntagged())
1089:           {
1090:             if (id.equals(command))
1091:               {
1092:                 List code = response.getResponseCode();
1093:                 String text = response.getText();
1094:                 
1095:                 // Populate entry attributes with the interned versions
1096:                 // of the response code.
1097:                 // NB IMAP servers do not necessarily pay attention to case.
1098:                 int alen = (code == null) ? 0 : code.size();
1099:                 boolean noinferiors = false;
1100:                 boolean noselect = false;
1101:                 boolean marked = false;
1102:                 boolean unmarked = false;
1103:                 for (int i = 0; i < alen; i++)
1104:                   {
1105:                     String attribute = (String) code.get(i);
1106:                     if (attribute.equalsIgnoreCase(LIST_NOINFERIORS))
1107:                       {
1108:                         noinferiors = true;
1109:                       }
1110:                     else if (attribute.equalsIgnoreCase(LIST_NOSELECT))
1111:                       {
1112:                         noselect = true;
1113:                       }
1114:                     else if (attribute.equalsIgnoreCase(LIST_MARKED))
1115:                       {
1116:                         marked = true;
1117:                       }
1118:                     else if (attribute.equalsIgnoreCase(LIST_UNMARKED))
1119:                       {
1120:                         unmarked = true;
1121:                       }
1122:                   }
1123:                 int si = text.indexOf(' ');
1124:                 char delimiter = '\u0000';
1125:                 String d = text.substring(0, si);
1126:                 if (!d.equalsIgnoreCase(NIL))
1127:                   {
1128:                     delimiter = stripQuotes(d).charAt(0);
1129:                   }
1130:                 String mbox = stripQuotes(text.substring(si + 1));
1131:                 mbox = UTF7imap.decode(mbox);
1132:                 ListEntry entry = new ListEntry(mbox, delimiter, noinferiors,
1133:                                                 noselect, marked, unmarked);
1134:                 acc.add(entry);
1135:               }
1136:             else
1137:               {
1138:                 asyncResponses.add(response);
1139:               }
1140:           }
1141:         else if (tag.equals(response.getTag()))
1142:           {
1143:             processAlerts(response);
1144:             if (id == OK)
1145:               {
1146:                 ListEntry[] entries = new ListEntry[acc.size()];
1147:                 acc.toArray(entries);
1148:                 return entries;
1149:               }
1150:             else
1151:               {
1152:                 throw new IMAPException(id, response.getText());
1153:               }
1154:           }
1155:         else
1156:           {
1157:             throw new IMAPException(id, response.getText());
1158:           }
1159:       }
1160:   }
1161:   
1162:   /**
1163:    * Requests the status of the specified mailbox.
1164:    */
1165:   public MailboxStatus status(String mailbox, String[] statusNames)
1166:     throws IOException
1167:   {
1168:     String tag = newTag();
1169:     StringBuffer buffer = new StringBuffer(STATUS)
1170:       .append(' ')
1171:       .append(quote(UTF7imap.encode(mailbox)))
1172:       .append(' ')
1173:       .append('(');
1174:     for (int i = 0; i < statusNames.length; i++)
1175:       {
1176:         if (i > 0)
1177:           {
1178:             buffer.append(' ');
1179:           }
1180:         buffer.append(statusNames[i]);
1181:       }
1182:     buffer.append(')');
1183:     sendCommand(tag, buffer.toString());
1184:     MailboxStatus ms = new MailboxStatus();
1185:     while (true)
1186:       {
1187:         IMAPResponse response = readResponse();
1188:         String id = response.getID();
1189:         if (response.isUntagged())
1190:           {
1191:             if (id == STATUS)
1192:               {
1193:                 List code = response.getResponseCode();
1194:                 int last = (code == null) ? 0 : code.size() - 1;
1195:                 for (int i = 0; i < last; i += 2)
1196:                   {
1197:                     try
1198:                       {
1199:                         String statusName = ((String) code.get(i)).intern();
1200:                         int value = Integer.parseInt((String) code.get(i + 1));
1201:                         if (statusName == MESSAGES)
1202:                           {
1203:                             ms.messageCount = value;
1204:                           }
1205:                         else if (statusName == RECENT)
1206:                           {
1207:                             ms.newMessageCount = value;
1208:                           }
1209:                         else if (statusName == UIDNEXT)
1210:                           {
1211:                             ms.uidNext = value;
1212:                           }
1213:                         else if (statusName == UIDVALIDITY)
1214:                           {
1215:                             ms.uidValidity = value;
1216:                           }
1217:                         else if (statusName == UNSEEN)
1218:                           {
1219:                             ms.firstUnreadMessage = value;
1220:                           }
1221:                       }
1222:                     catch (NumberFormatException e)
1223:                       {
1224:                         throw new IMAPException(id, "Invalid code: " + code);
1225:                       }
1226:                   }
1227:               }
1228:             else
1229:               {
1230:                 asyncResponses.add(response);
1231:               }
1232:           }
1233:         else if (tag.equals(response.getTag()))
1234:           {
1235:             processAlerts(response);
1236:             if (id == OK)
1237:               {
1238:                 return ms;
1239:               }
1240:             else
1241:               {
1242:                 throw new IMAPException(id, response.getText());
1243:               }
1244:           }
1245:         else
1246:           {
1247:             throw new IMAPException(id, response.getText());
1248:           }
1249:       }
1250:   }
1251:   
1252:   /**
1253:    * Append a message to the specified mailbox.
1254:    * This method returns an OutputStream to which the message should be
1255:    * written and then closed.
1256:    * @param mailbox the mailbox name
1257:    * @param flags optional list of flags to specify for the message
1258:    * @param content the message body(including headers)
1259:    * @return true if successful, false if error in flags/text
1260:    */
1261:   public boolean append(String mailbox, String[] flags, byte[] content)
1262:     throws IOException
1263:   {
1264:     String tag = newTag();
1265:     StringBuffer buffer = new StringBuffer(APPEND)
1266:       .append(' ')
1267:       .append(quote(UTF7imap.encode(mailbox)))
1268:       .append(' ');
1269:     if (flags != null)
1270:       {
1271:         buffer.append('(');
1272:         for (int i = 0; i < flags.length; i++)
1273:           {
1274:             if (i > 0)
1275:               {
1276:                 buffer.append(' ');
1277:               }
1278:             buffer.append(flags[i]);
1279:           }
1280:         buffer.append(')');
1281:         buffer.append(' ');
1282:       }
1283:     buffer.append('{');
1284:     buffer.append(content.length);
1285:     buffer.append('}');
1286:     sendCommand(tag, buffer.toString());
1287:     IMAPResponse response = readResponse();
1288:     if (!response.isContinuation())
1289:       {
1290:         throw new IMAPException(response.getID(), response.getText());
1291:       }
1292:     out.write(content);         // write the message body
1293:     out.writeln();
1294:     out.flush();
1295:     while (true)
1296:       {
1297:         response = readResponse();
1298:         String id = response.getID();
1299:         if (tag.equals(response.getTag()))
1300:           {
1301:             processAlerts(response);
1302:             if (id == OK)
1303:               {
1304:                 return true;
1305:               }
1306:             else if (id == NO)
1307:               {
1308:                 return false;
1309:               }
1310:             else
1311:               {
1312:                 throw new IMAPException(id, response.getText());
1313:               }
1314:           }
1315:         else if (response.isUntagged())
1316:           {
1317:             asyncResponses.add(response);
1318:           }
1319:         else
1320:           {
1321:             throw new IMAPException(id, response.getText());
1322:           }
1323:       }
1324:   }
1325:   
1326:   /**
1327:    * Request a checkpoint of the currently selected mailbox.
1328:    */
1329:   public void check()
1330:     throws IOException
1331:   {
1332:     invokeSimpleCommand(CHECK);
1333:   }
1334:   
1335:   /**
1336:    * Permanently remove all messages that have the \Deleted flags set,
1337:    * and close the mailbox.
1338:    * @return true if successful, false if no mailbox was selected
1339:    */
1340:   public boolean close()
1341:     throws IOException
1342:   {
1343:     return invokeSimpleCommand(CLOSE);
1344:   }
1345:   
1346:   /**
1347:    * Permanently removes all messages that have the \Delete flag set.
1348:    * @return the numbers of the messages expunged
1349:    */
1350:   public int[] expunge()
1351:     throws IOException
1352:   {
1353:     String tag = newTag();
1354:     sendCommand(tag, EXPUNGE);
1355:     List numbers = new ArrayList();
1356:     while (true)
1357:       {
1358:         IMAPResponse response = readResponse();
1359:         String id = response.getID();
1360:         if (response.isUntagged())
1361:           {
1362:             if (id == EXPUNGE)
1363:               {
1364:                 numbers.add(new Integer(response.getCount()));
1365:               }
1366:             else
1367:               {
1368:                 asyncResponses.add(response);
1369:               }
1370:           }
1371:         else if (tag.equals(response.getTag()))
1372:           {
1373:             processAlerts(response);
1374:             if (id == OK)
1375:               {
1376:                 int len = numbers.size();
1377:                 int[] mn = new int[len];
1378:                 for (int i = 0; i < len; i++)
1379:                   {
1380:                     mn[i] = ((Integer) numbers.get(i)).intValue();
1381:                   }
1382:                 return mn;
1383:               }
1384:             else
1385:               {
1386:                 throw new IMAPException(id, response.getText());
1387:               }
1388:           }
1389:         else
1390:           {
1391:             throw new IMAPException(id, response.getText());
1392:           }
1393:       }
1394:   }
1395:   
1396:   /**
1397:    * Searches the currently selected mailbox for messages matching the
1398:    * specified criteria.
1399:    */
1400:   public int[] search(String charset, String[] criteria)
1401:     throws IOException
1402:   {
1403:     String tag = newTag();
1404:     StringBuffer buffer = new StringBuffer(SEARCH);
1405:     buffer.append(' ');
1406:     if (charset != null)
1407:       {
1408:         buffer.append(charset);
1409:         buffer.append(' ');
1410:       }
1411:     for (int i = 0; i < criteria.length; i++)
1412:       {
1413:         if (i > 0)
1414:           {
1415:             buffer.append(' ');
1416:           }
1417:         buffer.append(criteria[i]);
1418:       }
1419:     sendCommand(tag, buffer.toString());
1420:     List list = new ArrayList();
1421:     while (true)
1422:       {
1423:         IMAPResponse response = readResponse();
1424:         String id = response.getID();
1425:         if (response.isUntagged())
1426:           {
1427:             if (id == SEARCH)
1428:               {
1429:                 String text = response.getText();
1430:                 if (text == null)
1431:                   {
1432:                     continue;
1433:                   }
1434:                 try
1435:                   {
1436:                     int si = text.indexOf(' ');
1437:                     while (si != -1)
1438:                       {
1439:                         list.add(new Integer(text.substring(0, si)));
1440:                         text = text.substring(si + 1);
1441:                         si = text.indexOf(' ');
1442:                       }
1443:                     list.add(new Integer(text));
1444:                   }
1445:                 catch (NumberFormatException e)
1446:                   {
1447:                     throw new IMAPException(id, "Expecting number: " + text);
1448:                   }
1449:               }
1450:             else
1451:               {
1452:                 asyncResponses.add(response);
1453:               }
1454:           }
1455:         else if (tag.equals(response.getTag()))
1456:           {
1457:             processAlerts(response);
1458:             if (id == OK)
1459:               {
1460:                 int len = list.size();
1461:                 int[] mn = new int[len];
1462:                 for (int i = 0; i < len; i++)
1463:                   {
1464:                     mn[i] = ((Integer) list.get(i)).intValue();
1465:                   }
1466:                 return mn;
1467:               }
1468:             else
1469:               {
1470:                 throw new IMAPException(id, response.getText());
1471:               }
1472:           }
1473:         else
1474:           {
1475:             throw new IMAPException(id, response.getText());
1476:           }
1477:       }
1478:   }
1479:   
1480:   /**
1481:    * Retrieves data associated with the specified message in the mailbox.
1482:    * @param message the message number
1483:    * @param fetchCommands the fetch commands, e.g. FLAGS
1484:    */
1485:   public MessageStatus fetch(int message, String[] fetchCommands)
1486:     throws IOException
1487:   {
1488:     String ids = (message == -1) ? "*" : Integer.toString(message);
1489:     return fetchImpl(FETCH, ids, fetchCommands)[0];
1490:   }
1491: 
1492:   /**
1493:    * Retrieves data associated with the specified range of messages in
1494:    * the mailbox.
1495:    * @param start the message number of the first message
1496:    * @param end the message number of the last message
1497:    * @param fetchCommands the fetch commands, e.g. FLAGS
1498:    */
1499:   public MessageStatus[] fetch(int start, int end, String[] fetchCommands)
1500:     throws IOException
1501:   {
1502:     StringBuffer ids = new StringBuffer();
1503:     ids.append((start == -1) ? '*' : start);
1504:     ids.append(':');
1505:     ids.append((end == -1) ? '*' : end);
1506:     return fetchImpl(FETCH, ids.toString(), fetchCommands);
1507:   }
1508: 
1509:   /**
1510:    * Retrieves data associated with messages in the mailbox.
1511:    * @param messages the message numbers
1512:    * @param fetchCommands the fetch commands, e.g. FLAGS
1513:    */
1514:   public MessageStatus[] fetch(int[] messages, String[] fetchCommands)
1515:     throws IOException
1516:   {
1517:     StringBuffer ids = new StringBuffer();
1518:     for (int i = 0; i < messages.length; i++)
1519:       {
1520:         if (i > 0)
1521:           {
1522:             ids.append(',');
1523:           }
1524:         ids.append(messages[i]);
1525:       }
1526:     return fetchImpl(FETCH, ids.toString(), fetchCommands);
1527:   }
1528: 
1529:   /**
1530:    * Retrieves data associated with the specified message in the mailbox.
1531:    * @param uid the message UID
1532:    * @param fetchCommands the fetch commands, e.g. FLAGS
1533:    */
1534:   public MessageStatus uidFetch(long uid, String[] fetchCommands)
1535:     throws IOException
1536:   {
1537:     String ids = (uid == -1L) ? "*" : Long.toString(uid);
1538:     return fetchImpl(UID + ' ' + FETCH, ids, fetchCommands)[0];
1539:   }
1540:   
1541:   /**
1542:    * Retrieves data associated with the specified range of messages in
1543:    * the mailbox.
1544:    * @param start the message number of the first message
1545:    * @param end the message number of the last message
1546:    * @param fetchCommands the fetch commands, e.g. FLAGS
1547:    */
1548:   public MessageStatus[] uidFetch(long start, long end,
1549:                                    String[] fetchCommands)
1550:     throws IOException
1551:   {
1552:     StringBuffer ids = new StringBuffer();
1553:     ids.append((start == -1L) ? '*' : start);
1554:     ids.append(':');
1555:     ids.append((end == -1L) ? '*' : end);
1556:     return fetchImpl(UID + ' ' + FETCH, ids.toString(), fetchCommands);
1557:   }
1558:   
1559:   /**
1560:    * Retrieves data associated with messages in the mailbox.
1561:    * @param uids the message UIDs
1562:    * @param fetchCommands the fetch commands, e.g. FLAGS
1563:    */
1564:   public MessageStatus[] uidFetch(long[] uids, String[] fetchCommands)
1565:     throws IOException
1566:   {
1567:     StringBuffer ids = new StringBuffer();
1568:     for (int i = 0; i < uids.length; i++)
1569:       {
1570:         if (i > 0)
1571:           {
1572:             ids.append(',');
1573:           }
1574:         ids.append(uids[i]);
1575:       }
1576:     return fetchImpl(UID + ' ' + FETCH, ids.toString(), fetchCommands);
1577:   }
1578:   
1579:   private MessageStatus[] fetchImpl(String cmd, String ids,
1580:                                     String[] fetchCommands)
1581:     throws IOException
1582:   {
1583:     String tag = newTag();
1584:     StringBuffer buffer = new StringBuffer(cmd);
1585:     buffer.append(' ');
1586:     buffer.append(ids);
1587:     buffer.append(' ');
1588:     buffer.append('(');
1589:     for (int i = 0; i < fetchCommands.length; i++)
1590:       {
1591:         if (i > 0)
1592:           {
1593:             buffer.append(' ');
1594:           }
1595:         buffer.append(fetchCommands[i]);
1596:       }
1597:     buffer.append(')');
1598:     sendCommand(tag, buffer.toString());
1599:     List list = new ArrayList();
1600:     while (true)
1601:       {
1602:         IMAPResponse response = readResponse();
1603:         String id = response.getID();
1604:         if (response.isUntagged())
1605:           {
1606:             if (id == FETCH)
1607:               {
1608:                 int msgnum = response.getCount();
1609:                 List code = response.getResponseCode();
1610:                 MessageStatus status = new MessageStatus(msgnum, code);
1611:                 list.add(status);
1612:               }
1613:             else
1614:               {
1615:                 asyncResponses.add(response);
1616:               }
1617:           }
1618:         else if (tag.equals(response.getTag()))
1619:           {
1620:             processAlerts(response);
1621:             if (id == OK)
1622:               {
1623:                 MessageStatus[] statuses = new MessageStatus[list.size()];
1624:                 list.toArray(statuses);
1625:                 return statuses;
1626:               }
1627:             else
1628:               {
1629:                 throw new IMAPException(id, response.getText());
1630:               }
1631:           }
1632:         else
1633:           {
1634:             throw new IMAPException(id, response.getText());
1635:           }
1636:       }
1637:   }
1638: 
1639:   /**
1640:    * Alters data associated with the specified message in the mailbox.
1641:    * @param message the message number
1642:    * @param flagCommand FLAGS, +FLAGS, -FLAGS(or .SILENT versions)
1643:    * @param flags message flags to set
1644:    * @return the message status
1645:    */
1646:   public MessageStatus store(int message, String flagCommand,
1647:                              String[] flags)
1648:     throws IOException
1649:   {
1650:     String ids = (message == -1) ? "*" : Integer.toString(message);
1651:     return storeImpl(STORE, ids, flagCommand, flags)[0];
1652:   }
1653: 
1654:   /**
1655:    * Alters data associated with the specified range of messages in the
1656:    * mailbox.
1657:    * @param start the message number of the first message
1658:    * @param end the message number of the last message
1659:    * @param flagCommand FLAGS, +FLAGS, -FLAGS(or .SILENT versions)
1660:    * @param flags message flags to set
1661:    * @return a list of message-number to current flags
1662:    */
1663:   public MessageStatus[] store(int start, int end, String flagCommand,
1664:                                String[] flags)
1665:     throws IOException
1666:   {
1667:     StringBuffer ids = new StringBuffer();
1668:     ids.append((start == -1) ? '*' : start);
1669:     ids.append(':');
1670:     ids.append((end == -1) ? '*' : end);
1671:     return storeImpl(STORE, ids.toString(), flagCommand, flags);
1672:   }
1673: 
1674:   /**
1675:    * Alters data associated with messages in the mailbox.
1676:    * @param messages the message numbers
1677:    * @param flagCommand FLAGS, +FLAGS, -FLAGS(or .SILENT versions)
1678:    * @param flags message flags to set
1679:    * @return a list of message-number to current flags
1680:    */
1681:   public MessageStatus[] store(int[] messages, String flagCommand,
1682:                                String[] flags)
1683:     throws IOException
1684:   {
1685:     StringBuffer ids = new StringBuffer();
1686:     for (int i = 0; i < messages.length; i++)
1687:       {
1688:         if (i > 0)
1689:           {
1690:             ids.append(',');
1691:           }
1692:         ids.append(messages[i]);
1693:       }
1694:     return storeImpl(STORE, ids.toString(), flagCommand, flags);
1695:   }
1696:   
1697:   /**
1698:    * Alters data associated with the specified message in the mailbox.
1699:    * @param uid the message UID
1700:    * @param flagCommand FLAGS, +FLAGS, -FLAGS(or .SILENT versions)
1701:    * @param flags message flags to set
1702:    * @return the message status
1703:    */
1704:   public MessageStatus uidStore(long uid, String flagCommand,
1705:                                 String[] flags)
1706:     throws IOException
1707:   {
1708:     String ids = (uid == -1L) ? "*" : Long.toString(uid);
1709:     return storeImpl(UID + ' ' + STORE, ids, flagCommand, flags)[0];
1710:   }
1711: 
1712:   /**
1713:    * Alters data associated with the specified range of messages in the
1714:    * mailbox.
1715:    * @param start the UID of the first message
1716:    * @param end the UID of the last message
1717:    * @param flagCommand FLAGS, +FLAGS, -FLAGS(or .SILENT versions)
1718:    * @param flags message flags to set
1719:    * @return a list of message-number to current flags
1720:    */
1721:   public MessageStatus[] uidStore(long start, long end, String flagCommand,
1722:                                   String[] flags)
1723:     throws IOException
1724:   {
1725:     StringBuffer ids = new StringBuffer();
1726:     ids.append((start == -1L) ? '*' : start);
1727:     ids.append(':');
1728:     ids.append((end == -1L) ? '*' : end);
1729:     return storeImpl(UID + ' ' + STORE, ids.toString(), flagCommand, flags);
1730:   }
1731: 
1732:   /**
1733:    * Alters data associated with messages in the mailbox.
1734:    * @param uids the message UIDs
1735:    * @param flagCommand FLAGS, +FLAGS, -FLAGS(or .SILENT versions)
1736:    * @param flags message flags to set
1737:    * @return a list of message-number to current flags
1738:    */
1739:   public MessageStatus[] uidStore(long[] uids, String flagCommand,
1740:                                   String[] flags)
1741:     throws IOException
1742:   {
1743:     StringBuffer ids = new StringBuffer();
1744:     for (int i = 0; i < uids.length; i++)
1745:       {
1746:         if (i > 0)
1747:           {
1748:             ids.append(',');
1749:           }
1750:         ids.append(uids[i]);
1751:       }
1752:     return storeImpl(UID + ' ' + STORE, ids.toString(), flagCommand, flags);
1753:   }
1754:   
1755:   private MessageStatus[] storeImpl(String cmd, String ids,
1756:                                     String flagCommand, String[] flags)
1757:     throws IOException
1758:   {
1759:     String tag = newTag();
1760:     StringBuffer buffer = new StringBuffer(cmd);
1761:     buffer.append(' ');
1762:     buffer.append(ids);
1763:     buffer.append(' ');
1764:     buffer.append(flagCommand);
1765:     buffer.append(' ');
1766:     buffer.append('(');
1767:     for (int i = 0; i < flags.length; i++)
1768:       {
1769:         if (i > 0)
1770:           {
1771:             buffer.append(' ');
1772:           }
1773:         buffer.append(flags[i]);
1774:       }
1775:     buffer.append(')');
1776:     sendCommand(tag, buffer.toString());
1777:     List list = new ArrayList();
1778:     while (true)
1779:       {
1780:         IMAPResponse response = readResponse();
1781:         String id = response.getID();
1782:         if (response.isUntagged())
1783:           {
1784:             int msgnum = response.getCount();
1785:             List code = response.getResponseCode();
1786:             // 2 different styles returned by server: FETCH or FETCH FLAGS
1787:             if (id == FETCH)
1788:               {
1789:                 MessageStatus mf = new MessageStatus(msgnum, code);
1790:                 list.add(mf);
1791:               }
1792:             else if (id == FETCH_FLAGS)
1793:               {
1794:                 List base = new ArrayList();
1795:                 base.add(FLAGS);
1796:                 base.add(code);
1797:                 MessageStatus mf = new MessageStatus(msgnum, base);
1798:                 list.add(mf);
1799:               }
1800:             else
1801:               {
1802:                 asyncResponses.add(response);
1803:               }
1804:           }
1805:         else if (tag.equals(response.getTag()))
1806:           {
1807:             processAlerts(response);
1808:             if (id == OK)
1809:               {
1810:                 MessageStatus[] mf = new MessageStatus[list.size()];
1811:                 list.toArray(mf);
1812:                 return mf;
1813:               }
1814:             else
1815:               {
1816:                 throw new IMAPException(id, response.getText());
1817:               }
1818:           }
1819:         else
1820:           {
1821:             throw new IMAPException(id, response.getText());
1822:           }
1823:       }
1824:   }
1825:   
1826:   /**
1827:    * Copies the specified messages to the end of the destination mailbox.
1828:    * @param messages the message numbers
1829:    * @param mailbox the destination mailbox
1830:    */
1831:   public boolean copy(int[] messages, String mailbox)
1832:     throws IOException
1833:   {
1834:     if (messages == null || messages.length < 1)
1835:       {
1836:         return true;
1837:       }
1838:     StringBuffer buffer = new StringBuffer(COPY)
1839:       .append(' ');
1840:     for (int i = 0; i < messages.length; i++)
1841:       {
1842:         if (i > 0)
1843:           {
1844:             buffer.append(',');
1845:           }
1846:         buffer.append(messages[i]);
1847:       }
1848:     buffer.append(' ').append(quote(UTF7imap.encode(mailbox)));
1849:     return invokeSimpleCommand(buffer.toString());
1850:   }
1851: 
1852:   /**
1853:    * Returns the namespaces available on the server.
1854:    * See RFC 2342 for details.
1855:    */
1856:   public Namespaces namespace()
1857:     throws IOException
1858:   {
1859:     String tag = newTag();
1860:     sendCommand(tag, NAMESPACE);
1861:     Namespaces namespaces = null;
1862:     while (true)
1863:       {
1864:         IMAPResponse response = readResponse();
1865:         String id = response.getID();
1866:         if (tag.equals(response.getTag()))
1867:           {
1868:             processAlerts(response);
1869:             if (id == OK)
1870:               {
1871:                 return namespaces;
1872:               }
1873:             else
1874:               {
1875:                 throw new IMAPException(id, response.getText());
1876:               }
1877:           }
1878:         else if (response.isUntagged())
1879:           {
1880:             if (NAMESPACE.equals(response.getID()))
1881:               {
1882:                 namespaces = new Namespaces(response.getText());
1883:               }
1884:             else
1885:               {
1886:                 asyncResponses.add(response);
1887:               }
1888:           }
1889:         else
1890:           {
1891:             throw new IMAPException(id, response.getText());
1892:           }
1893:       }
1894:   }
1895: 
1896:   /**
1897:    * Changes the access rights on the specified mailbox such that the
1898:    * authentication principal is granted the specified permissions.
1899:    * @param mailbox the mailbox name
1900:    * @param principal the authentication identifier
1901:    * @param rights the rights to assign
1902:    */
1903:   public boolean setacl(String mailbox, String principal, int rights)
1904:     throws IOException
1905:   {
1906:     String command = SETACL + ' ' + quote(UTF7imap.encode(mailbox)) +
1907:       ' ' + UTF7imap.encode(principal) + ' ' + rightsToString(rights);
1908:     return invokeSimpleCommand(command);
1909:   }
1910: 
1911:   /**
1912:    * Removes any access rights for the given authentication principal on the
1913:    * specified mailbox.
1914:    * @param mailbox the mailbox name
1915:    * @param principal the authentication identifier
1916:    */
1917:   public boolean deleteacl(String mailbox, String principal)
1918:     throws IOException
1919:   {
1920:     String command = DELETEACL + ' ' + quote(UTF7imap.encode(mailbox)) +
1921:       ' ' + UTF7imap.encode(principal);
1922:     return invokeSimpleCommand(command);
1923:   }
1924: 
1925:   /**
1926:    * Returns the access control list for the specified mailbox.
1927:    * The returned rights are a logical OR of RIGHTS_* bits.
1928:    * @param mailbox the mailbox name
1929:    * @return a map of principal names to Integer rights
1930:    */
1931:   public Map getacl(String mailbox)
1932:     throws IOException
1933:   {
1934:     String tag = newTag();
1935:     sendCommand(tag, GETACL + ' ' + quote(UTF7imap.encode(mailbox)));
1936:     Map ret = new TreeMap();
1937:     while (true)
1938:       {
1939:         IMAPResponse response = readResponse();
1940:         String id = response.getID();
1941:         if (tag.equals(response.getTag()))
1942:           {
1943:             processAlerts(response);
1944:             if (id == OK)
1945:               {
1946:                 return ret;
1947:               }
1948:             else if (id == NO)
1949:               {
1950:                 return null;
1951:               }
1952:             else
1953:               {
1954:                 throw new IMAPException(id, response.getText());
1955:               }
1956:           }
1957:         else if (response.isUntagged())
1958:           {
1959:             if (ACL.equals(response.getID()))
1960:               {
1961:                 String text = response.getText();
1962:                 List args = parseACL(text, 1);
1963:                 String rights = (String) args.get(2);
1964:                 ret.put(args.get(1), new Integer(stringToRights(rights)));
1965:               }
1966:             else
1967:               {
1968:                 asyncResponses.add(response);
1969:               }
1970:           }
1971:         else
1972:           {
1973:             throw new IMAPException(id, response.getText());
1974:           }
1975:       }
1976:   }
1977: 
1978:   /**
1979:    * Returns the rights for the given principal for the specified mailbox.
1980:    * The returned rights are a logical OR of RIGHTS_* bits.
1981:    * @param mailbox the mailbox name
1982:    * @param principal the authentication identity
1983:    */
1984:   public int listrights(String mailbox, String principal)
1985:     throws IOException
1986:   {
1987:     String tag = newTag();
1988:     String command = LISTRIGHTS + ' ' + quote(UTF7imap.encode(mailbox)) +
1989:       ' ' + UTF7imap.encode(principal);
1990:     sendCommand(tag, command);
1991:     int ret = -1;
1992:     while (true)
1993:       {
1994:         IMAPResponse response = readResponse();
1995:         String id = response.getID();
1996:         if (tag.equals(response.getTag()))
1997:           {
1998:             processAlerts(response);
1999:             if (id == OK)
2000:               {
2001:                 return ret;
2002:               }
2003:             else if (id == NO)
2004:               {
2005:                 return -1;
2006:               }
2007:             else
2008:               {
2009:                 throw new IMAPException(id, response.getText());
2010:               }
2011:           }
2012:         else if (response.isUntagged())
2013:           {
2014:             if (LISTRIGHTS.equals(response.getID()))
2015:               {
2016:                 String text = response.getText();
2017:                 List args = parseACL(text, 1);
2018:                 ret = stringToRights((String) args.get(2));
2019:               }
2020:             else
2021:               {
2022:                 asyncResponses.add(response);
2023:               }
2024:           }
2025:         else
2026:           {
2027:             throw new IMAPException(id, response.getText());
2028:           }
2029:       }
2030:   }
2031: 
2032:   /**
2033:    * Returns the rights for the current principal for the specified mailbox.
2034:    * The returned rights are a logical OR of RIGHTS_* bits.
2035:    * @param mailbox the mailbox name
2036:    */
2037:   public int myrights(String mailbox)
2038:     throws IOException
2039:   {
2040:     String tag = newTag();
2041:     String command = MYRIGHTS + ' ' + quote(UTF7imap.encode(mailbox));
2042:     sendCommand(tag, command);
2043:     int ret = -1;
2044:     while (true)
2045:       {
2046:         IMAPResponse response = readResponse();
2047:         String id = response.getID();
2048:         if (tag.equals(response.getTag()))
2049:           {
2050:             processAlerts(response);
2051:             if (id == OK)
2052:               {
2053:                 return ret;
2054:               }
2055:             else if (id == NO)
2056:               {
2057:                 return -1;
2058:               }
2059:             else
2060:               {
2061:                 throw new IMAPException(id, response.getText());
2062:               }
2063:           }
2064:         else if (response.isUntagged())
2065:           {
2066:             if (MYRIGHTS.equals(response.getID()))
2067:               {
2068:                 String text = response.getText();
2069:                 List args = parseACL(text, 0);
2070:                 ret = stringToRights((String) args.get(2));
2071:               }
2072:             else
2073:               {
2074:                 asyncResponses.add(response);
2075:               }
2076:           }
2077:         else
2078:           {
2079:             throw new IMAPException(id, response.getText());
2080:           }
2081:       }
2082:   }
2083: 
2084:   private String rightsToString(int rights)
2085:   {
2086:     StringBuffer buf = new StringBuffer();
2087:     if ((rights & RIGHTS_LOOKUP) != 0)
2088:       {
2089:         buf.append('l');
2090:       }
2091:     if ((rights & RIGHTS_READ) != 0)
2092:       {
2093:         buf.append('r');
2094:       }
2095:     if ((rights & RIGHTS_SEEN) != 0)
2096:       {
2097:         buf.append('s');
2098:       }
2099:     if ((rights & RIGHTS_WRITE) != 0)
2100:       {
2101:         buf.append('w');
2102:       }
2103:     if ((rights & RIGHTS_INSERT) != 0)
2104:       {
2105:         buf.append('i');
2106:       }
2107:     if ((rights & RIGHTS_POST) != 0)
2108:       {
2109:         buf.append('p');
2110:       }
2111:     if ((rights & RIGHTS_CREATE) != 0)
2112:       {
2113:         buf.append('c');
2114:       }
2115:     if ((rights & RIGHTS_DELETE) != 0)
2116:       {
2117:         buf.append('d');
2118:       }
2119:     if ((rights & RIGHTS_ADMIN) != 0)
2120:       {
2121:         buf.append('a');
2122:       }
2123:     return buf.toString();
2124:   }
2125: 
2126:   private int stringToRights(String text)
2127:   {
2128:     int ret = 0;
2129:     int len = text.length();
2130:     for (int i = 0; i < len; i++)
2131:       {
2132:         switch (text.charAt(i))
2133:           {
2134:           case 'l':
2135:             ret |= RIGHTS_LOOKUP;
2136:             break;
2137:           case 'r':
2138:             ret |= RIGHTS_READ;
2139:             break;
2140:           case 's':
2141:             ret |= RIGHTS_SEEN;
2142:             break;
2143:           case 'w':
2144:             ret |= RIGHTS_WRITE;
2145:             break;
2146:           case 'i':
2147:             ret |= RIGHTS_INSERT;
2148:             break;
2149:           case 'p':
2150:             ret |= RIGHTS_POST;
2151:             break;
2152:           case 'c':
2153:             ret |= RIGHTS_CREATE;
2154:             break;
2155:           case 'd':
2156:             ret |= RIGHTS_DELETE;
2157:             break;
2158:           case 'a':
2159:             ret |= RIGHTS_ADMIN;
2160:             break;
2161:           }
2162:       }
2163:     return ret;
2164:   }
2165: 
2166:   /*
2167:    * Parse an ACL entry into a list of 2 or 3 components: mailbox name,
2168:    * optional principal, and rights.
2169:    */
2170:   private List parseACL(String text, int prolog)
2171:   {
2172:     int len = text.length();
2173:     boolean inQuotes = false;
2174:     List ret = new ArrayList();
2175:     StringBuffer buf = new StringBuffer();
2176:     for (int i = 0; i < len; i++)
2177:       {
2178:         char c = text.charAt(i);
2179:         switch (c)
2180:           {
2181:           case '"':
2182:             inQuotes = !inQuotes;
2183:             break;
2184:           case ' ':
2185:             if (inQuotes || ret.size() > prolog)
2186:               {
2187:                 buf.append(c);
2188:               }
2189:             else
2190:               {
2191:                 ret.add(UTF7imap.decode(buf.toString()));
2192:                 buf.setLength(0);
2193:               }
2194:             break;
2195:           default:
2196:             buf.append(c);
2197:           }
2198:       }
2199:     ret.add(buf.toString());
2200:     return ret;
2201:   }
2202: 
2203:   /**
2204:    * Sets the quota for the specified quota root.
2205:    * @param quotaRoot the quota root
2206:    * @param resources the list of resources and associated limits to set
2207:    * @return the new quota, or <code>null</code> if the operation failed
2208:    */
2209:   public Quota setquota(String quotaRoot, Quota.Resource[] resources)
2210:     throws IOException
2211:   {
2212:     // Create resource limits list
2213:     StringBuffer resourceLimits = new StringBuffer();
2214:     if (resources != null)
2215:       {
2216:         for (int i = 0; i < resources.length; i++)
2217:           {
2218:             if (i > 0)
2219:               {
2220:                 resourceLimits.append(' ');
2221:               }
2222:             resourceLimits.append(resources[i].toString());
2223:           }
2224:       }
2225:     String tag = newTag();
2226:     String command = SETQUOTA + ' ' + quote(UTF7imap.encode(quotaRoot)) +
2227:       ' ' + resourceLimits.toString();
2228:     sendCommand(tag, command);
2229:     Quota ret = null;
2230:     while (true)
2231:       {
2232:         IMAPResponse response = readResponse();
2233:         String id = response.getID();
2234:         if (tag.equals(response.getTag()))
2235:           {
2236:             processAlerts(response);
2237:             if (id == OK)
2238:               {
2239:                 return ret;
2240:               }
2241:             else if (id == NO)
2242:               {
2243:                 return null;
2244:               }
2245:             else
2246:               {
2247:                 throw new IMAPException(id, response.getText());
2248:               }
2249:           }
2250:         else if (response.isUntagged())
2251:           {
2252:             if (QUOTA.equals(response.getID()))
2253:               {
2254:                 ret = new Quota(response.getText());
2255:               }
2256:             else
2257:               {
2258:                 asyncResponses.add(response);
2259:               }
2260:           }
2261:         else
2262:           {
2263:             throw new IMAPException(id, response.getText());
2264:           }
2265:       }
2266:   }
2267: 
2268:   /**
2269:    * Returns the specified quota root's resource usage and limits.
2270:    * @param quotaRoot the quota root
2271:    */
2272:   public Quota getquota(String quotaRoot)
2273:     throws IOException
2274:   {
2275:     String tag = newTag();
2276:     String command = GETQUOTA + ' ' + quote(UTF7imap.encode(quotaRoot));
2277:     sendCommand(tag, command);
2278:     Quota ret = null;
2279:     while (true)
2280:       {
2281:         IMAPResponse response = readResponse();
2282:         String id = response.getID();
2283:         if (tag.equals(response.getTag()))
2284:           {
2285:             processAlerts(response);
2286:             if (id == OK)
2287:               {
2288:                 return ret;
2289:               }
2290:             else if (id == NO)
2291:               {
2292:                 return null;
2293:               }
2294:             else
2295:               {
2296:                 throw new IMAPException(id, response.getText());
2297:               }
2298:           }
2299:         else if (response.isUntagged())
2300:           {
2301:             if (QUOTA.equals(response.getID()))
2302:               {
2303:                 ret = new Quota(response.getText());
2304:               }
2305:             else
2306:               {
2307:                 asyncResponses.add(response);
2308:               }
2309:           }
2310:         else
2311:           {
2312:             throw new IMAPException(id, response.getText());
2313:           }
2314:       }
2315:   }
2316:   
2317:   /**
2318:    * Returns the quotas for the given mailbox.
2319:    * @param mailbox the mailbox name
2320:    */
2321:   public Quota[] getquotaroot(String mailbox)
2322:     throws IOException
2323:   {
2324:     String tag = newTag();
2325:     String command = GETQUOTAROOT + ' ' + quote(UTF7imap.encode(mailbox));
2326:     sendCommand(tag, command);
2327:     List acc = new ArrayList();
2328:     while (true)
2329:       {
2330:         IMAPResponse response = readResponse();
2331:         String id = response.getID();
2332:         if (tag.equals(response.getTag()))
2333:           {
2334:             processAlerts(response);
2335:             if (id == OK)
2336:               {
2337:                 Quota[] ret = new Quota[acc.size()];
2338:                 acc.toArray(ret);
2339:                 return ret;
2340:               }
2341:             else if (id == NO)
2342:               {
2343:                 return null;
2344:               }
2345:             else
2346:               {
2347:                 throw new IMAPException(id, response.getText());
2348:               }
2349:           }
2350:         else if (response.isUntagged())
2351:           {
2352:             if (QUOTA.equals(response.getID()))
2353:               {
2354:                 acc.add(new Quota(response.getText()));
2355:               }
2356:             else
2357:               {
2358:                 asyncResponses.add(response);
2359:               }
2360:           }
2361:         else
2362:           {
2363:             throw new IMAPException(id, response.getText());
2364:           }
2365:       }
2366:   }
2367:   
2368:   // -- Utility methods --
2369:   
2370:   /**
2371:    * Remove the quotes from each end of a string literal.
2372:    */
2373:   static String stripQuotes(String text)
2374:   {
2375:     if (text.charAt(0) == '"')
2376:       {
2377:         int len = text.length();
2378:         if (text.charAt(len - 1) == '"')
2379:           {
2380:             return text.substring(1, len - 1);
2381:           }
2382:       }
2383:     return text;
2384:   }
2385:   
2386:   /**
2387:    * Quote the specified text if necessary.
2388:    */
2389:   static String quote(String text)
2390:   {
2391:     if (text.length() == 0 || text.indexOf(' ') != -1)
2392:       {
2393:         StringBuffer buffer = new StringBuffer();
2394:         buffer.append('"');
2395:         buffer.append(text);
2396:         buffer.append('"');
2397:         return buffer.toString();
2398:       }
2399:     return text;
2400:   }
2401:   
2402: }