KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > mail > imap > protocol > IMAPProtocol


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the "License"). You may not use this file except
5  * in compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * glassfish/bootstrap/legal/CDDLv1.0.txt or
9  * https://glassfish.dev.java.net/public/CDDLv1.0.html.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * HEADER in each file and include the License file at
15  * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
16  * add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your
18  * own identifying information: Portions Copyright [yyyy]
19  * [name of copyright owner]
20  */

21
22 /*
23  * @(#)IMAPProtocol.java 1.53 05/11/17
24  *
25  * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
26  */

27
28 package com.sun.mail.imap.protocol;
29
30 import java.io.*;
31 import java.util.*;
32 import java.text.*;
33 import java.lang.reflect.*;
34
35 import javax.mail.*;
36 import javax.mail.internet.*;
37 import javax.mail.search.*;
38
39 import com.sun.mail.util.*;
40 import com.sun.mail.iap.*;
41
42 import com.sun.mail.imap.ACL;
43 import com.sun.mail.imap.Rights;
44 import com.sun.mail.imap.AppendUID;
45
46 /**
47  * This class extends the iap.Protocol object and implements IMAP
48  * semantics. In general, there is a method corresponding to each
49  * IMAP protocol command. The typical implementation issues the
50  * appropriate protocol command, collects all responses, processes
51  * those responses that are specific to this command and then
52  * dispatches the rest (the unsolicited ones) to the dispatcher
53  * using the <code>notifyResponseHandlers(r)</code>.
54  *
55  * @version 1.53, 05/11/17
56  * @author John Mani
57  * @author Bill Shannon
58  */

59
60 public class IMAPProtocol extends Protocol {
61     
62     private boolean rev1 = false; // REV1 server ?
63
private boolean authenticated; // authenticated?
64
// WARNING: authenticated may be set to true in superclass
65
// constructor, don't initialize it here.
66

67     private Hashtable capabilities = null;
68     private Vector authmechs = null;
69     private String JavaDoc[] searchCharsets; // array of search charsets
70

71     private String JavaDoc name;
72     private Properties props;
73     private SaslAuthenticator saslAuthenticator; // if SASL is being used
74

75     /**
76      * Constructor.
77      * Opens a connection to the given host at given port.
78      *
79      * @param host host to connect to
80      * @param port portnumber to connect to
81      * @param debug debug mode
82      * @param props Properties object used by this protocol
83      */

84     public IMAPProtocol(String JavaDoc name, String JavaDoc host, int port,
85             boolean debug, PrintStream out, Properties props,
86             boolean isSSL) throws IOException, ProtocolException {
87     super(host, port, debug, out, props, "mail." + name, isSSL);
88
89     this.name = name;
90     this.props = props;
91
92     if (capabilities == null)
93         capability();
94
95     if (hasCapability("IMAP4rev1"))
96         rev1 = true;
97
98     searchCharsets = new String JavaDoc[2]; // 2, for now.
99
searchCharsets[0] = "UTF-8";
100     searchCharsets[1] = MimeUtility.mimeCharset(
101                 MimeUtility.getDefaultJavaCharset()
102                 );
103     }
104
105     /**
106      * CAPABILITY command.
107      *
108      * @see "RFC2060, section 6.1.1"
109      */

110     public void capability() throws ProtocolException {
111     // Check CAPABILITY
112
Response[] r = command("CAPABILITY", null);
113
114     if (!r[r.length-1].isOK())
115         throw new ProtocolException(r[r.length-1].toString());
116
117     capabilities = new Hashtable(10);
118     authmechs = new Vector(5);
119     for (int i = 0, len = r.length; i < len; i++) {
120         if (!(r[i] instanceof IMAPResponse))
121         continue;
122
123         IMAPResponse ir = (IMAPResponse)r[i];
124
125         // Handle *all* untagged CAPABILITY responses.
126
// Though the spec seemingly states that only
127
// one CAPABILITY response string is allowed (6.1.1),
128
// some server vendors claim otherwise.
129
if (ir.keyEquals("CAPABILITY"))
130         parseCapabilities(ir);
131     }
132     }
133
134     /**
135      * If the response contains a CAPABILITY response code, extract
136      * it and save the capabilities.
137      */

138     protected void setCapabilities(Response r) {
139     byte b;
140     while ((b = r.readByte()) > 0 && b != (byte)'[')
141         ;
142     if (b == 0)
143         return;
144     String JavaDoc s;
145     s = r.readAtom();
146     if (!s.equalsIgnoreCase("CAPABILITY"))
147         return;
148     capabilities = new Hashtable(10);
149     authmechs = new Vector(5);
150     parseCapabilities(r);
151     }
152
153     /**
154      * Parse the capabilities from a CAPABILITY response or from
155      * a CAPABILITY response code attached to (e.g.) an OK response.
156      */

157     protected void parseCapabilities(Response r) {
158     String JavaDoc s;
159     while ((s = r.readAtom(']')) != null) {
160         if (s.length() == 0) {
161         if (r.peekByte() == (byte)']')
162             break;
163         /*
164          * Probably found something here that's not an atom.
165          * Rather than loop forever or fail completely, we'll
166          * try to skip this bogus capability. This is known
167          * to happen with:
168          * Netscape Messaging Server 4.03 (built Apr 27 1999)
169          * that returns:
170          * * CAPABILITY * CAPABILITY IMAP4 IMAP4rev1 ...
171          * The "*" in the middle of the capability list causes
172          * us to loop forever here.
173          */

174         r.skipToken();
175         } else {
176         capabilities.put(s.toUpperCase(), s);
177         if (s.regionMatches(true, 0, "AUTH=", 0, 5)) {
178             authmechs.addElement(s.substring(5));
179             if (debug)
180             out.println("IMAP DEBUG: AUTH: " + s.substring(5));
181         }
182         }
183     }
184     }
185
186     /**
187      * Check the greeting when first connecting; look for PREAUTH response.
188      */

189     protected void processGreeting(Response r) throws ProtocolException {
190     super.processGreeting(r); // check if it's BAD
191
if (r.isOK()) { // check if it's OK
192
setCapabilities(r);
193         return;
194     }
195     // only other choice is PREAUTH
196
IMAPResponse ir = (IMAPResponse)r;
197     if (ir.keyEquals("PREAUTH")) {
198         authenticated = true;
199         setCapabilities(r);
200     } else
201         throw new ConnectionException(this, r);
202     }
203
204     /**
205      * Returns <code>true</code> if the connection has been authenticated,
206      * either due to a successful login, or due to a PREAUTH greeting response.
207      */

208     public boolean isAuthenticated() {
209     return authenticated;
210     }
211
212     /**
213      * Returns <code>true</code> if this is a IMAP4rev1 server
214      */

215     public boolean isREV1() {
216     return rev1;
217     }
218
219     /**
220      * Returns whether this Protocol supports non-synchronizing literals.
221      */

222     protected boolean supportsNonSyncLiterals() {
223     return hasCapability("LITERAL+");
224     }
225
226     /**
227      * Read a response from the server.
228      */

229     public Response readResponse() throws IOException, ProtocolException {
230     return IMAPResponse.readResponse(this);
231     }
232
233     /**
234      * Check whether the given capability is supported by
235      * this server. Returns <code>true</code> if so, otherwise
236      * returns false.
237      */

238     public boolean hasCapability(String JavaDoc c) {
239     return capabilities.containsKey(c.toUpperCase());
240     }
241
242     /**
243      * Close socket connection.
244      *
245      * This method just makes the Protocol.disconnect() method
246      * public.
247      */

248     public void disconnect() {
249     super.disconnect();
250     authenticated = false; // just in case
251
}
252
253     /**
254      * The NOOP command.
255      *
256      * @see "RFC2060, section 6.1.2"
257      */

258     public void noop() throws ProtocolException {
259     if (debug)
260         out.println("IMAP DEBUG: IMAPProtocol noop");
261     simpleCommand("NOOP", null);
262     }
263
264     /**
265      * LOGOUT Command.
266      *
267      * @see "RFC2060, section 6.1.3"
268      */

269     public void logout() throws ProtocolException {
270     // XXX - what happens if exception is thrown?
271
Response[] r = command("LOGOUT", null);
272
273     authenticated = false;
274     // dispatch any unsolicited responses.
275
// NOTE that the BYE response is dispatched here as well
276
notifyResponseHandlers(r);
277     disconnect();
278     }
279
280     /**
281      * LOGIN Command.
282      *
283      * @see "RFC2060, section 6.2.2"
284      */

285     public void login(String JavaDoc u, String JavaDoc p) throws ProtocolException {
286     Argument args = new Argument();
287     args.writeString(u);
288     args.writeString(p);
289
290     simpleCommand("LOGIN", args);
291     // if we get this far without an exception, we're authenticated
292
authenticated = true;
293     }
294
295     /**
296      * The AUTHENTICATE command with AUTH=LOGIN authenticate scheme
297      *
298      * @see "RFC2060, section 6.2.1"
299      */

300     public void authlogin(String JavaDoc u, String JavaDoc p) throws ProtocolException {
301     Vector v = new Vector();
302     String JavaDoc tag = null;
303     Response r = null;
304     boolean done = false;
305
306     try {
307         tag = writeCommand("AUTHENTICATE LOGIN", null);
308     } catch (Exception JavaDoc ex) {
309         // Convert this into a BYE response
310
r = Response.byeResponse(ex);
311         done = true;
312     }
313
314     OutputStream os = getOutputStream(); // stream to IMAP server
315

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

331
332     ByteArrayOutputStream bos = new ByteArrayOutputStream();
333     OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
334     byte[] CRLF = { (byte)'\r', (byte)'\n'};
335     boolean first = true;
336
337     while (!done) { // loop till we are done
338
try {
339         r = readResponse();
340             if (r.isContinuation()) {
341             // Server challenge ..
342
String JavaDoc s;
343             if (first) { // Send encoded username
344
s = u;
345             first = false;
346             } else // Send encoded password
347
s = p;
348             
349             // obtain b64 encoded bytes
350
b64os.write(ASCIIUtility.getBytes(s));
351             b64os.flush(); // complete the encoding
352

353             bos.write(CRLF); // CRLF termination
354
os.write(bos.toByteArray()); // write out line
355
os.flush(); // flush the stream
356
bos.reset(); // reset buffer
357
} else if (r.isTagged() && r.getTag().equals(tag))
358             // Ah, our tagged response
359
done = true;
360         else if (r.isBYE()) // outta here
361
done = true;
362         else // hmm .. unsolicited response here ?!
363
v.addElement(r);
364         } catch (Exception JavaDoc ioex) {
365         // convert this into a BYE response
366
r = Response.byeResponse(ioex);
367         done = true;
368         }
369     }
370
371     /* Dispatch untagged responses.
372      * NOTE: in our current upper level IMAP classes, we add the
373      * responseHandler to the Protocol object only *after* the
374      * connection has been authenticated. So, for now, the below
375      * code really ends up being just a no-op.
376      */

377     Response[] responses = new Response[v.size()];
378     v.copyInto(responses);
379     notifyResponseHandlers(responses);
380
381     // Handle the final OK, NO, BAD or BYE response
382
handleResult(r);
383     // If the response includes a CAPABILITY response code, process it
384
if (r.isOK())
385         setCapabilities(r);
386     // if we get this far without an exception, we're authenticated
387
authenticated = true;
388     }
389
390
391     /**
392      * The AUTHENTICATE command with AUTH=PLAIN authentication scheme.
393      * This is based heavly on the {@link #authlogin} method.
394      *
395      * @param authzid the authorization id
396      * @param u the username
397      * @param p the password
398      * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
399      * @see "RFC3501, section 6.2.2"
400      * @see "RFC2595, section 6"
401      * @since JavaMail 1.3.2
402      */

403     public void authplain(String JavaDoc authzid, String JavaDoc u, String JavaDoc p)
404                 throws ProtocolException {
405     Vector v = new Vector();
406     String JavaDoc tag = null;
407     Response r = null;
408     boolean done = false;
409
410     try {
411         tag = writeCommand("AUTHENTICATE PLAIN", null);
412     } catch (Exception JavaDoc ex) {
413         // Convert this into a BYE response
414
r = Response.byeResponse(ex);
415         done = true;
416     }
417
418     OutputStream os = getOutputStream(); // stream to IMAP server
419

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

435
436     ByteArrayOutputStream bos = new ByteArrayOutputStream();
437     OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
438
439     while (!done) { // loop till we are done
440
try {
441         r = readResponse();
442         if (r.isContinuation()) {
443             // Server challenge ..
444
final String JavaDoc nullByte = "\0";
445             String JavaDoc s = authzid + nullByte + u + nullByte + p;
446
447             // obtain b64 encoded bytes
448
b64os.write(ASCIIUtility.getBytes(s));
449             b64os.flush(); // complete the encoding
450

451             bos.write(CRLF); // CRLF termination
452
os.write(bos.toByteArray()); // write out line
453
os.flush(); // flush the stream
454
bos.reset(); // reset buffer
455
} else if (r.isTagged() && r.getTag().equals(tag))
456             // Ah, our tagged response
457
done = true;
458         else if (r.isBYE()) // outta here
459
done = true;
460         else // hmm .. unsolicited response here ?!
461
v.addElement(r);
462         } catch (Exception JavaDoc ioex) {
463         // convert this into a BYE response
464
r = Response.byeResponse(ioex);
465         done = true;
466         }
467     }
468
469     /* Dispatch untagged responses.
470      * NOTE: in our current upper level IMAP classes, we add the
471      * responseHandler to the Protocol object only *after* the
472      * connection has been authenticated. So, for now, the below
473      * code really ends up being just a no-op.
474      */

475     Response[] responses = new Response[v.size()];
476     v.copyInto(responses);
477     notifyResponseHandlers(responses);
478
479     // Handle the final OK, NO, BAD or BYE response
480
handleResult(r);
481     // If the response includes a CAPABILITY response code, process it
482
if (r.isOK())
483         setCapabilities(r);
484     // if we get this far without an exception, we're authenticated
485
authenticated = true;
486     }
487
488     /**
489      * SASL-based login.
490      */

491     public void sasllogin(String JavaDoc[] allowed, String JavaDoc realm, String JavaDoc authzid,
492                 String JavaDoc u, String JavaDoc p) throws ProtocolException {
493     if (saslAuthenticator == null) {
494         try {
495         Class JavaDoc sac = Class.forName(
496             "com.sun.mail.imap.protocol.IMAPSaslAuthenticator");
497         Constructor c = sac.getConstructor(new Class JavaDoc[] {
498                     IMAPProtocol.class,
499                     String JavaDoc.class,
500                     Properties.class,
501                     Boolean.TYPE,
502                     PrintStream.class,
503                     String JavaDoc.class
504                     });
505         saslAuthenticator = (SaslAuthenticator)c.newInstance(
506                     new Object JavaDoc[] {
507                     this,
508                     name,
509                     props,
510                     debug ? Boolean.TRUE : Boolean.FALSE,
511                     out,
512                     host
513                     });
514         } catch (Exception JavaDoc ex) {
515         if (debug)
516             out.println("IMAP DEBUG: Can't load SASL authenticator: " +
517                                 ex);
518         // probably because we're running on a system without SASL
519
return; // not authenticated, try without SASL
520
}
521     }
522
523     // were any allowed mechanisms specified?
524
Vector v;
525     if (allowed != null && allowed.length > 0) {
526         // remove anything not supported by the server
527
v = new Vector(allowed.length);
528         for (int i = 0; i < allowed.length; i++)
529         if (authmechs.contains(allowed[i])) // XXX - case must match
530
v.addElement(allowed[i]);
531     } else {
532         // everything is allowed
533
v = authmechs;
534     }
535     String JavaDoc[] mechs = new String JavaDoc[v.size()];
536     v.copyInto(mechs);
537     if (saslAuthenticator.authenticate(mechs, realm, authzid, u, p))
538         authenticated = true;
539     }
540
541     // XXX - for IMAPSaslAuthenticator access to protected method
542
OutputStream getIMAPOutputStream() {
543     return getOutputStream();
544     }
545
546     /**
547      * PROXYAUTH Command.
548      *
549      * @see "Netscape/iPlanet/SunONE Messaging Server extension"
550      */

551     public void proxyauth(String JavaDoc u) throws ProtocolException {
552     Argument args = new Argument();
553     args.writeString(u);
554
555     simpleCommand("PROXYAUTH", args);
556     }
557
558     /**
559      * STARTTLS Command.
560      *
561      * @see "RFC3501, section 6.2.1"
562      */

563     public void startTLS() throws ProtocolException {
564     try {
565         super.startTLS("STARTTLS");
566     } catch (ProtocolException pex) {
567         // ProtocolException just means the command wasn't recognized,
568
// or failed. This should never happen if we check the
569
// CAPABILITY first.
570
throw pex;
571     } catch (Exception JavaDoc ex) {
572         // any other exception means we have to shut down the connection
573
// generate an artificial BYE response and disconnect
574
Response[] r = { Response.byeResponse(ex) };
575         notifyResponseHandlers(r);
576         disconnect();
577     }
578     }
579
580     /**
581      * SELECT Command.
582      *
583      * @see "RFC2060, section 6.3.1"
584      */

585     public MailboxInfo select(String JavaDoc mbox) throws ProtocolException {
586     // encode the mbox as per RFC2060
587
mbox = BASE64MailboxEncoder.encode(mbox);
588
589     Argument args = new Argument();
590     args.writeString(mbox);
591
592     Response[] r = command("SELECT", args);
593
594     // Note that MailboxInfo also removes those responses
595
// it knows about
596
MailboxInfo minfo = new MailboxInfo(r);
597     
598     // dispatch any remaining untagged responses
599
notifyResponseHandlers(r);
600
601     Response response = r[r.length-1];
602
603     if (response.isOK()) { // command succesful
604
if (response.toString().indexOf("READ-ONLY") != -1)
605         minfo.mode = Folder.READ_ONLY;
606         else
607         minfo.mode = Folder.READ_WRITE;
608     }
609     
610     handleResult(response);
611     return minfo;
612     }
613
614     /**
615      * EXAMINE Command.
616      *
617      * @see "RFC2060, section 6.3.2"
618      */

619     public MailboxInfo examine(String JavaDoc mbox) throws ProtocolException {
620     // encode the mbox as per RFC2060
621
mbox = BASE64MailboxEncoder.encode(mbox);
622
623     Argument args = new Argument();
624     args.writeString(mbox);
625
626     Response[] r = command("EXAMINE", args);
627
628     // Note that MailboxInfo also removes those responses
629
// it knows about
630
MailboxInfo minfo = new MailboxInfo(r);
631     minfo.mode = Folder.READ_ONLY; // Obviously
632

633     // dispatch any remaining untagged responses
634
notifyResponseHandlers(r);
635
636     handleResult(r[r.length-1]);
637     return minfo;
638     }
639
640     /**
641      * STATUS Command.
642      *
643      * @see "RFC2060, section 6.3.10"
644      */

645     public Status status(String JavaDoc mbox, String JavaDoc[] items)
646         throws ProtocolException {
647     if (!isREV1() && !hasCapability("IMAP4SUNVERSION"))
648         // STATUS is rev1 only, however the non-rev1 SIMS2.0
649
// does support this.
650
throw new BadCommandException("STATUS not supported");
651
652     // encode the mbox as per RFC2060
653
mbox = BASE64MailboxEncoder.encode(mbox);
654
655     Argument args = new Argument();
656     args.writeString(mbox);
657
658     Argument itemArgs = new Argument();
659     if (items == null)
660         items = Status.standardItems;
661
662     for (int i = 0, len = items.length; i < len; i++)
663         itemArgs.writeAtom(items[i]);
664     args.writeArgument(itemArgs);
665
666     Response[] r = command("STATUS", args);
667
668     Status status = null;
669     Response response = r[r.length-1];
670
671     // Grab all STATUS responses
672
if (response.isOK()) { // command succesful
673
for (int i = 0, len = r.length; i < len; i++) {
674         if (!(r[i] instanceof IMAPResponse))
675             continue;
676
677         IMAPResponse ir = (IMAPResponse)r[i];
678         if (ir.keyEquals("STATUS")) {
679             if (status == null)
680             status = new Status(ir);
681             else // collect 'em all
682
Status.add(status, new Status(ir));
683             r[i] = null;
684         }
685         }
686     }
687
688     // dispatch remaining untagged responses
689
notifyResponseHandlers(r);
690     handleResult(response);
691     return status;
692     }
693
694     /**
695      * CREATE Command.
696      *
697      * @see "RFC2060, section 6.3.3"
698      */

699     public void create(String JavaDoc mbox) throws ProtocolException {
700     // encode the mbox as per RFC2060
701
mbox = BASE64MailboxEncoder.encode(mbox);
702
703     Argument args = new Argument();
704     args.writeString(mbox);
705
706     simpleCommand("CREATE", args);
707     }
708
709     /**
710      * DELETE Command.
711      *
712      * @see "RFC2060, section 6.3.4"
713      */

714     public void delete(String JavaDoc mbox) throws ProtocolException {
715     // encode the mbox as per RFC2060
716
mbox = BASE64MailboxEncoder.encode(mbox);
717
718     Argument args = new Argument();
719     args.writeString(mbox);
720
721     simpleCommand("DELETE", args);
722     }
723
724     /**
725      * RENAME Command.
726      *
727      * @see "RFC2060, section 6.3.5"
728      */

729     public void rename(String JavaDoc o, String JavaDoc n) throws ProtocolException {
730     // encode the mbox as per RFC2060
731
o = BASE64MailboxEncoder.encode(o);
732     n = BASE64MailboxEncoder.encode(n);
733
734     Argument args = new Argument();
735     args.writeString(o);
736     args.writeString(n);
737
738     simpleCommand("RENAME", args);
739     }
740
741     /**
742      * SUBSCRIBE Command.
743      *
744      * @see "RFC2060, section 6.3.6"
745      */

746     public void subscribe(String JavaDoc mbox) throws ProtocolException {
747     Argument args = new Argument();
748     // encode the mbox as per RFC2060
749
mbox = BASE64MailboxEncoder.encode(mbox);
750     args.writeString(mbox);
751
752     simpleCommand("SUBSCRIBE", args);
753     }
754
755     /**
756      * UNSUBSCRIBE Command.
757      *
758      * @see "RFC2060, section 6.3.7"
759      */

760     public void unsubscribe(String JavaDoc mbox) throws ProtocolException {
761     Argument args = new Argument();
762     // encode the mbox as per RFC2060
763
mbox = BASE64MailboxEncoder.encode(mbox);
764     args.writeString(mbox);
765
766     simpleCommand("UNSUBSCRIBE", args);
767     }
768
769     /**
770      * LIST Command.
771      *
772      * @see "RFC2060, section 6.3.8"
773      */

774     public ListInfo[] list(String JavaDoc ref, String JavaDoc pattern)
775             throws ProtocolException {
776     return doList("LIST", ref, pattern);
777     }
778
779     /**
780      * LSUB Command.
781      *
782      * @see "RFC2060, section 6.3.9"
783      */

784     public ListInfo[] lsub(String JavaDoc ref, String JavaDoc pattern)
785             throws ProtocolException {
786     return doList("LSUB", ref, pattern);
787     }
788
789     private ListInfo[] doList(String JavaDoc cmd, String JavaDoc ref, String JavaDoc pat)
790             throws ProtocolException {
791     // encode the mbox as per RFC2060
792
ref = BASE64MailboxEncoder.encode(ref);
793     pat = BASE64MailboxEncoder.encode(pat);
794
795     Argument args = new Argument();
796     args.writeString(ref);
797     args.writeString(pat);
798
799     Response[] r = command(cmd, args);
800
801     ListInfo[] linfo = null;
802     Response response = r[r.length-1];
803
804     if (response.isOK()) { // command succesful
805
Vector v = new Vector(1);
806         for (int i = 0, len = r.length; i < len; i++) {
807         if (!(r[i] instanceof IMAPResponse))
808             continue;
809
810         IMAPResponse ir = (IMAPResponse)r[i];
811         if (ir.keyEquals(cmd)) {
812             v.addElement(new ListInfo(ir));
813             r[i] = null;
814         }
815         }
816         if (v.size() > 0) {
817         linfo = new ListInfo[v.size()];
818         v.copyInto(linfo);
819         }
820     }
821     
822     // Dispatch remaining untagged responses
823
notifyResponseHandlers(r);
824     handleResult(response);
825     return linfo;
826     }
827         
828     /**
829      * APPEND Command.
830      *
831      * @see "RFC2060, section 6.3.11"
832      */

833     public void append(String JavaDoc mbox, Flags f, Date d,
834             Literal data) throws ProtocolException {
835     appenduid(mbox, f, d, data, false); // ignore return value
836
}
837
838     /**
839      * APPEND Command, return uid from APPENDUID response code.
840      *
841      * @see "RFC2060, section 6.3.11"
842      */

843     public AppendUID appenduid(String JavaDoc mbox, Flags f, Date d,
844             Literal data) throws ProtocolException {
845     return appenduid(mbox, f, d, data, true);
846     }
847
848     public AppendUID appenduid(String JavaDoc mbox, Flags f, Date d,
849             Literal data, boolean uid) throws ProtocolException {
850     // encode the mbox as per RFC2060
851
mbox = BASE64MailboxEncoder.encode(mbox);
852
853     Argument args = new Argument();
854     args.writeString(mbox);
855
856     if (f != null) // set Flags in appended message
857
// can't set the \Recent flag in APPEND
858
f.remove(Flags.Flag.RECENT);
859         /*
860          * HACK ALERT: We want the flag_list to be written out
861          * without any checking/processing of the bytes in it. If
862          * I use writeString(), the flag_list will end up being
863          * quoted since it contains "illegal" characters. So I
864          * am depending on implementation knowledge that writeAtom()
865          * does not do any checking/processing - it just writes out
866          * the bytes. What we really need is a writeFoo() that just
867          * dumps out its argument.
868          */

869         args.writeAtom(createFlagList(f));
870     if (d != null) // set INTERNALDATE in appended message
871
args.writeString(INTERNALDATE.format(d));
872
873     args.writeBytes(data);
874
875     Response[] r = command("APPEND", args);
876
877     // dispatch untagged responses
878
notifyResponseHandlers(r);
879
880     // Handle result of this command
881
handleResult(r[r.length-1]);
882
883     if (uid)
884         return getAppendUID(r[r.length-1]);
885     else
886         return null;
887     }
888
889     /**
890      * If the response contains an APPENDUID response code, extract
891      * it and return an AppendUID object with the information.
892      */

893     private AppendUID getAppendUID(Response r) {
894     if (!r.isOK())
895         return null;
896     byte b;
897     while ((b = r.readByte()) > 0 && b != (byte)'[')
898         ;
899     if (b == 0)
900         return null;
901     String JavaDoc s;
902     s = r.readAtom();
903     if (!s.equalsIgnoreCase("APPENDUID"))
904         return null;
905
906     long uidvalidity = r.readLong();
907     long uid = r.readLong();
908     return new AppendUID(uidvalidity, uid);
909     }
910
911     /**
912      * CHECK Command.
913      *
914      * @see "RFC2060, section 6.4.1"
915      */

916     public void check() throws ProtocolException {
917     simpleCommand("CHECK", null);
918     }
919
920     /**
921      * CLOSE Command.
922      *
923      * @see "RFC2060, section 6.4.2"
924      */

925     public void close() throws ProtocolException {
926     simpleCommand("CLOSE", null);
927     }
928
929     /**
930      * EXPUNGE Command.
931      *
932      * @see "RFC2060, section 6.4.3"
933      */

934     public void expunge() throws ProtocolException {
935     simpleCommand("EXPUNGE", null);
936     }
937
938     /**
939      * UID EXPUNGE Command.
940      *
941      * @see "RFC2359, section 4.1"
942      */

943     public void uidexpunge(UIDSet[] set) throws ProtocolException {
944     if (!hasCapability("UIDPLUS"))
945         throw new BadCommandException("UID EXPUNGE not supported");
946     simpleCommand("UID EXPUNGE " + UIDSet.toString(set), null);
947     }
948
949     /**
950      * Fetch the BODYSTRUCTURE of the specified message.
951      */

952     public BODYSTRUCTURE fetchBodyStructure(int msgno)
953             throws ProtocolException {
954     Response[] r = fetch(msgno, "BODYSTRUCTURE");
955     notifyResponseHandlers(r);
956
957     Response response = r[r.length-1];
958     if (response.isOK())
959         return (BODYSTRUCTURE)FetchResponse.getItem(r, msgno,
960                     BODYSTRUCTURE.class);
961     else if (response.isNO())
962         return null;
963     else {
964         handleResult(response);
965         return null;
966     }
967     }
968
969     /**
970      * Fetch given BODY section, without marking the message
971      * as SEEN.
972      */

973     public BODY peekBody(int msgno, String JavaDoc section)
974             throws ProtocolException {
975     return fetchBody(msgno, section, true);
976     }
977
978     /**
979      * Fetch given BODY section.
980      */

981     public BODY fetchBody(int msgno, String JavaDoc section)
982             throws ProtocolException {
983     return fetchBody(msgno, section, false);
984     }
985
986     private BODY fetchBody(int msgno, String JavaDoc section, boolean peek)
987             throws ProtocolException {
988     Response[] r;
989
990     if (peek)
991         r = fetch(msgno,
992              "BODY.PEEK[" + (section == null ? "]" : section + "]"));
993     else
994         r = fetch(msgno,
995              "BODY[" + (section == null ? "]" : section + "]"));
996
997     notifyResponseHandlers(r);
998
999     Response response = r[r.length-1];
1000    if (response.isOK())
1001        return (BODY)FetchResponse.getItem(r, msgno, BODY.class);
1002    else if (response.isNO())
1003        return null;
1004    else {
1005        handleResult(response);
1006        return null;
1007    }
1008    }
1009
1010    /**
1011     * Partial FETCH of given BODY section, without setting SEEN flag.
1012     */

1013    public BODY peekBody(int msgno, String JavaDoc section, int start, int size)
1014            throws ProtocolException {
1015    return fetchBody(msgno, section, start, size, true);
1016    }
1017
1018    /**
1019     * Partial FETCH of given BODY section.
1020     */

1021    public BODY fetchBody(int msgno, String JavaDoc section, int start, int size)
1022            throws ProtocolException {
1023    return fetchBody(msgno, section, start, size, false);
1024    }
1025
1026    private BODY fetchBody(int msgno, String JavaDoc section, int start, int size,
1027            boolean peek) throws ProtocolException {
1028    Response[] r = fetch(
1029            msgno, (peek ? "BODY.PEEK[" : "BODY[" ) +
1030            (section == null ? "]<" : (section +"]<")) +
1031            String.valueOf(start) + "." +
1032            String.valueOf(size) + ">"
1033               );
1034
1035    notifyResponseHandlers(r);
1036
1037    Response response = r[r.length-1];
1038    if (response.isOK())
1039        return (BODY)FetchResponse.getItem(r, msgno, BODY.class);
1040    else if (response.isNO())
1041        return null;
1042    else {
1043        handleResult(response);
1044        return null;
1045    }
1046    }
1047
1048    /**
1049     * Fetch the specified RFC822 Data item. 'what' names
1050     * the item to be fetched. 'what' can be <code>null</code>
1051     * to fetch the whole message.
1052     */

1053    public RFC822DATA fetchRFC822(int msgno, String JavaDoc what)
1054            throws ProtocolException {
1055    Response[] r = fetch(msgno,
1056                 what == null ? "RFC822" : "RFC822." + what
1057                );
1058
1059    // dispatch untagged responses
1060
notifyResponseHandlers(r);
1061
1062    Response response = r[r.length-1];
1063    if (response.isOK())
1064        return (RFC822DATA)FetchResponse.getItem(r, msgno,
1065                    RFC822DATA.class);
1066    else if (response.isNO())
1067        return null;
1068    else {
1069        handleResult(response);
1070        return null;
1071    }
1072    }
1073
1074    /**
1075     * Fetch the FLAGS for the given message.
1076     */

1077    public Flags fetchFlags(int msgno) throws ProtocolException {
1078    Flags flags = null;
1079    Response[] r = fetch(msgno, "FLAGS");
1080
1081    // Search for our FLAGS response
1082
for (int i = 0, len = r.length; i < len; i++) {
1083        if (r[i] == null ||
1084        !(r[i] instanceof FetchResponse) ||
1085        ((FetchResponse)r[i]).getNumber() != msgno)
1086        continue;
1087        
1088        FetchResponse fr = (FetchResponse)r[i];
1089        if ((flags = (Flags)fr.getItem(Flags.class)) != null) {
1090        r[i] = null; // remove this response
1091
break;
1092        }
1093    }
1094
1095    // dispatch untagged responses
1096
notifyResponseHandlers(r);
1097    handleResult(r[r.length-1]);
1098    return flags;
1099    }
1100
1101    /**
1102     * Fetch the IMAP UID for the given message.
1103     */

1104    public UID fetchUID(int msgno) throws ProtocolException {
1105    Response[] r = fetch(msgno, "UID");
1106
1107    // dispatch untagged responses
1108
notifyResponseHandlers(r);
1109
1110    Response response = r[r.length-1];
1111    if (response.isOK())
1112        return (UID)FetchResponse.getItem(r, msgno, UID.class);
1113    else if (response.isNO()) // XXX: Issue NOOP ?
1114
return null;
1115    else {
1116        handleResult(response);
1117        return null; // NOTREACHED
1118
}
1119    }
1120        
1121    /**
1122     * Get the sequence number for the given UID. A UID object
1123     * containing the sequence number is returned. If the given UID
1124     * is invalid, <code>null</code> is returned.
1125     */

1126    public UID fetchSequenceNumber(long uid) throws ProtocolException {
1127    UID u = null;
1128    Response[] r = fetch(String.valueOf(uid), "UID", true);
1129
1130    for (int i = 0, len = r.length; i < len; i++) {
1131        if (r[i] == null || !(r[i] instanceof FetchResponse))
1132        continue;
1133        
1134        FetchResponse fr = (FetchResponse)r[i];
1135        if ((u = (UID)fr.getItem(UID.class)) != null) {
1136        if (u.uid == uid) // this is the one we want
1137
break;
1138        else
1139            u = null;
1140        }
1141    }
1142        
1143    notifyResponseHandlers(r);
1144    handleResult(r[r.length-1]);
1145    return u;
1146    }
1147
1148    /**
1149     * Get the sequence numbers for UIDs ranging from start till end.
1150     * UID objects that contain the sequence numbers are returned.
1151     * If no UIDs in the given range are found, an empty array is returned.
1152     */

1153    public UID[] fetchSequenceNumbers(long start, long end)
1154            throws ProtocolException {
1155    Response[] r = fetch(String.valueOf(start) + ":" +
1156                (end == UIDFolder.LASTUID ? "*" :
1157                String.valueOf(end)),
1158                 "UID", true);
1159
1160    UID u;
1161    Vector v = new Vector();
1162    for (int i = 0, len = r.length; i < len; i++) {
1163        if (r[i] == null || !(r[i] instanceof FetchResponse))
1164        continue;
1165        
1166        FetchResponse fr = (FetchResponse)r[i];
1167        if ((u = (UID)fr.getItem(UID.class)) != null)
1168        v.addElement(u);
1169    }
1170        
1171    notifyResponseHandlers(r);
1172    handleResult(r[r.length-1]);
1173
1174    UID[] ua = new UID[v.size()];
1175    v.copyInto(ua);
1176    return ua;
1177    }
1178
1179    /**
1180     * Get the sequence numbers for UIDs ranging from start till end.
1181     * UID objects that contain the sequence numbers are returned.
1182     * If no UIDs in the given range are found, an empty array is returned.
1183     */

1184    public UID[] fetchSequenceNumbers(long[] uids) throws ProtocolException {
1185    StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1186    for (int i = 0; i < uids.length; i++) {
1187        if (i > 0)
1188        sb.append(",");
1189        sb.append(String.valueOf(uids[i]));
1190    }
1191
1192    Response[] r = fetch(sb.toString(), "UID", true);
1193
1194    UID u;
1195    Vector v = new Vector();
1196    for (int i = 0, len = r.length; i < len; i++) {
1197        if (r[i] == null || !(r[i] instanceof FetchResponse))
1198        continue;
1199        
1200        FetchResponse fr = (FetchResponse)r[i];
1201        if ((u = (UID)fr.getItem(UID.class)) != null)
1202        v.addElement(u);
1203    }
1204        
1205    notifyResponseHandlers(r);
1206    handleResult(r[r.length-1]);
1207
1208    UID[] ua = new UID[v.size()];
1209    v.copyInto(ua);
1210    return ua;
1211    }
1212
1213    public Response[] fetch(MessageSet[] msgsets, String JavaDoc what)
1214            throws ProtocolException {
1215    return fetch(MessageSet.toString(msgsets), what, false);
1216    }
1217
1218    public Response[] fetch(int start, int end, String JavaDoc what)
1219            throws ProtocolException {
1220    return fetch(String.valueOf(start) + ":" + String.valueOf(end),
1221             what, false);
1222    }
1223
1224    public Response[] fetch(int msg, String JavaDoc what)
1225            throws ProtocolException {
1226    return fetch(String.valueOf(msg), what, false);
1227    }
1228
1229    private Response[] fetch(String JavaDoc msgSequence, String JavaDoc what, boolean uid)
1230            throws ProtocolException {
1231    if (uid)
1232        return command("UID FETCH " + msgSequence +" (" + what + ")",null);
1233    else
1234        return command("FETCH " + msgSequence + " (" + what + ")", null);
1235    }
1236
1237    /**
1238     * COPY command.
1239     */

1240    public void copy(MessageSet[] msgsets, String JavaDoc mbox)
1241            throws ProtocolException {
1242    copy(MessageSet.toString(msgsets), mbox);
1243    }
1244
1245    public void copy(int start, int end, String JavaDoc mbox)
1246            throws ProtocolException {
1247    copy(String.valueOf(start) + ":" + String.valueOf(end),
1248            mbox);
1249    }
1250
1251    private void copy(String JavaDoc msgSequence, String JavaDoc mbox)
1252            throws ProtocolException {
1253    // encode the mbox as per RFC2060
1254
mbox = BASE64MailboxEncoder.encode(mbox);
1255
1256    Argument args = new Argument();
1257    args.writeAtom(msgSequence);
1258    args.writeString(mbox);
1259
1260    simpleCommand("COPY", args);
1261    }
1262            
1263    public void storeFlags(MessageSet[] msgsets, Flags flags, boolean set)
1264            throws ProtocolException {
1265    storeFlags(MessageSet.toString(msgsets), flags, set);
1266    }
1267
1268    public void storeFlags(int start, int end, Flags flags, boolean set)
1269            throws ProtocolException {
1270    storeFlags(String.valueOf(start) + ":" + String.valueOf(end),
1271           flags, set);
1272    }
1273
1274    /**
1275     * Set the specified flags on this message. <p>
1276     */

1277    public void storeFlags(int msg, Flags flags, boolean set)
1278            throws ProtocolException {
1279    storeFlags(String.valueOf(msg), flags, set);
1280    }
1281
1282    private void storeFlags(String JavaDoc msgset, Flags flags, boolean set)
1283            throws ProtocolException {
1284    Response[] r;
1285    if (set)
1286        r = command("STORE " + msgset + " +FLAGS " +
1287             createFlagList(flags), null);
1288    else
1289        r = command("STORE " + msgset + " -FLAGS " +
1290            createFlagList(flags), null);
1291    
1292    // Dispatch untagged responses
1293
notifyResponseHandlers(r);
1294    handleResult(r[r.length-1]);
1295    }
1296
1297    /**
1298     * Creates an IMAP flag_list from the given Flags object.
1299     */

1300    private String JavaDoc createFlagList(Flags flags) {
1301    StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1302    sb.append("("); // start of flag_list
1303

1304    Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags
1305
boolean first = true;
1306    for (int i = 0; i < sf.length; i++) {
1307        String JavaDoc s;
1308        Flags.Flag f = sf[i];
1309        if (f == Flags.Flag.ANSWERED)
1310        s = "\\Answered";
1311        else if (f == Flags.Flag.DELETED)
1312        s = "\\Deleted";
1313        else if (f == Flags.Flag.DRAFT)
1314        s = "\\Draft";
1315        else if (f == Flags.Flag.FLAGGED)
1316        s = "\\Flagged";
1317        else if (f == Flags.Flag.RECENT)
1318        s = "\\Recent";
1319        else if (f == Flags.Flag.SEEN)
1320        s = "\\Seen";
1321        else
1322        continue; // skip it
1323
if (first)
1324        first = false;
1325        else
1326        sb.append(' ');
1327        sb.append(s);
1328    }
1329
1330    String JavaDoc[] uf = flags.getUserFlags(); // get the user flag strings
1331
for (int i = 0; i < uf.length; i++) {
1332        if (first)
1333        first = false;
1334        else
1335        sb.append(' ');
1336        sb.append(uf[i]);
1337    }
1338
1339    sb.append(")"); // terminate flag_list
1340
return sb.toString();
1341    }
1342
1343    /**
1344     * Issue the given search criterion on the specified message sets.
1345     * Returns array of matching sequence numbers. An empty array
1346     * is returned if no matches are found.
1347     *
1348     * @param msgsets array of MessageSets
1349     * @param term SearchTerm
1350     * @return array of matching sequence numbers.
1351     */

1352    public int[] search(MessageSet[] msgsets, SearchTerm term)
1353            throws ProtocolException, SearchException {
1354    return search(MessageSet.toString(msgsets), term);
1355    }
1356
1357    /**
1358     * Issue the given search criterion on all messages in this folder.
1359     * Returns array of matching sequence numbers. An empty array
1360     * is returned if no matches are found.
1361     *
1362     * @param term SearchTerm
1363     * @return array of matching sequence numbers.
1364     */

1365    public int[] search(SearchTerm term)
1366            throws ProtocolException, SearchException {
1367    return search("ALL", term);
1368    }
1369
1370    /* Apply the given SearchTerm on the specified sequence.
1371     * Returns array of matching sequence numbers. Note that an empty
1372     * array is returned for no matches.
1373     */

1374    private int[] search(String JavaDoc msgSequence, SearchTerm term)
1375            throws ProtocolException, SearchException {
1376    // Check if the search "text" terms contain only ASCII chars
1377
if (SearchSequence.isAscii(term)) {
1378        try {
1379        return issueSearch(msgSequence, term, null);
1380        } catch (IOException ioex) { /* will not happen */ }
1381    }
1382    
1383    /* The search "text" terms do contain non-ASCII chars. We need to
1384     * use SEARCH CHARSET <charset> ...
1385     * The charsets we try to use are UTF-8 and the locale's
1386     * default charset. If the server supports UTF-8, great,
1387     * always use it. Else we try to use the default charset.
1388     */

1389
1390    // Cycle thru the list of charsets
1391
for (int i = 0; i < searchCharsets.length; i++) {
1392        if (searchCharsets[i] == null)
1393        continue;
1394
1395        try {
1396        return issueSearch(msgSequence, term, searchCharsets[i]);
1397        } catch (CommandFailedException cfx) {
1398        /* Server returned NO. For now, I'll just assume that
1399         * this indicates that this charset is unsupported.
1400         * We can check the BADCHARSET response code once
1401         * that's spec'd into the IMAP RFC ..
1402         */

1403        searchCharsets[i] = null;
1404        continue;
1405        } catch (IOException ioex) {
1406        /* Charset conversion failed. Try the next one */
1407        continue;
1408        } catch (ProtocolException pex) {
1409        throw pex;
1410        } catch (SearchException sex) {
1411        throw sex;
1412        }
1413    }
1414
1415    // No luck.
1416
throw new SearchException("Search failed");
1417    }
1418
1419    /* Apply the given SearchTerm on the specified sequence, using the
1420     * given charset. <p>
1421     * Returns array of matching sequence numbers. Note that an empty
1422     * array is returned for no matches.
1423     */

1424    private int[] issueSearch(String JavaDoc msgSequence, SearchTerm term,
1425                      String JavaDoc charset)
1426         throws ProtocolException, SearchException, IOException {
1427
1428    // Generate a search-sequence with the given charset
1429
Argument args = SearchSequence.generateSequence(term,
1430              charset == null ? null :
1431                        MimeUtility.javaCharset(charset)
1432            );
1433    args.writeAtom(msgSequence);
1434
1435    Response[] r;
1436
1437    if (charset == null) // text is all US-ASCII
1438
r = command("SEARCH", args);
1439    else
1440        r = command("SEARCH CHARSET " + charset, args);
1441
1442    Response response = r[r.length-1];
1443    int[] matches = null;
1444
1445    // Grab all SEARCH responses
1446
if (response.isOK()) { // command succesful
1447
Vector v = new Vector();
1448        int num;
1449        for (int i = 0, len = r.length; i < len; i++) {
1450        if (!(r[i] instanceof IMAPResponse))
1451            continue;
1452
1453        IMAPResponse ir = (IMAPResponse)r[i];
1454        // There *will* be one SEARCH response.
1455
if (ir.keyEquals("SEARCH")) {
1456            while ((num = ir.readNumber()) != -1)
1457            v.addElement(new Integer JavaDoc(num));
1458            r[i] = null;
1459        }
1460        }
1461
1462        // Copy the vector into 'matches'
1463
int vsize = v.size();
1464        matches = new int[vsize];
1465        for (int i = 0; i < vsize; i++)
1466        matches[i] = ((Integer JavaDoc)v.elementAt(i)).intValue();
1467    }
1468
1469    // dispatch remaining untagged responses
1470
notifyResponseHandlers(r);
1471    handleResult(response);
1472    return matches;
1473    }
1474
1475    /**
1476     * NAMESPACE Command.
1477     *
1478     * @see "RFC2342"
1479     */

1480    public Namespaces namespace() throws ProtocolException {
1481    if (!hasCapability("NAMESPACE"))
1482        throw new BadCommandException("NAMESPACE not supported");
1483
1484    Response[] r = command("NAMESPACE", null);
1485
1486    Namespaces namespace = null;
1487    Response response = r[r.length-1];
1488
1489    // Grab NAMESPACE response
1490
if (response.isOK()) { // command succesful
1491
for (int i = 0, len = r.length; i < len; i++) {
1492        if (!(r[i] instanceof IMAPResponse))
1493            continue;
1494
1495        IMAPResponse ir = (IMAPResponse)r[i];
1496        if (ir.keyEquals("NAMESPACE")) {
1497            if (namespace == null)
1498            namespace = new Namespaces(ir);
1499            r[i] = null;
1500        }
1501        }
1502    }
1503
1504    // dispatch remaining untagged responses
1505
notifyResponseHandlers(r);
1506    handleResult(response);
1507    return namespace;
1508    }
1509
1510    /**
1511     * GETQUOTAROOT Command.
1512     *
1513     * Returns an array of Quota objects, representing the quotas
1514     * for this mailbox and, indirectly, the quotaroots for this
1515     * mailbox.
1516     *
1517     * @see "RFC2087"
1518     */

1519    public Quota[] getQuotaRoot(String JavaDoc mbox) throws ProtocolException {
1520    if (!hasCapability("QUOTA"))
1521        throw new BadCommandException("GETQUOTAROOT not supported");
1522
1523    // encode the mbox as per RFC2060
1524
mbox = BASE64MailboxEncoder.encode(mbox);
1525
1526    Argument args = new Argument();
1527    args.writeString(mbox);
1528
1529    Response[] r = command("GETQUOTAROOT", args);
1530
1531    Response response = r[r.length-1];
1532
1533    Hashtable tab = new Hashtable();
1534
1535    // Grab all QUOTAROOT and QUOTA responses
1536
if (response.isOK()) { // command succesful
1537
for (int i = 0, len = r.length; i < len; i++) {
1538        if (!(r[i] instanceof IMAPResponse))
1539            continue;
1540
1541        IMAPResponse ir = (IMAPResponse)r[i];
1542        if (ir.keyEquals("QUOTAROOT")) {
1543            // quotaroot_response
1544
// ::= "QUOTAROOT" SP astring *(SP astring)
1545

1546            // read name of mailbox and throw away
1547
ir.readAtomString();
1548            // for each quotaroot add a placeholder quota
1549
String JavaDoc root = null;
1550            while ((root = ir.readAtomString()) != null)
1551            tab.put(root, new Quota(root));
1552            r[i] = null;
1553        } else if (ir.keyEquals("QUOTA")) {
1554            Quota quota = parseQuota(ir);
1555            Quota q = (Quota)tab.get(quota.quotaRoot);
1556            if (q != null && q.resources != null) {
1557            // XXX - should merge resources
1558
}
1559            tab.put(quota.quotaRoot, quota);
1560            r[i] = null;
1561        }
1562        }
1563    }
1564
1565    // dispatch remaining untagged responses
1566
notifyResponseHandlers(r);
1567    handleResult(response);
1568
1569    Quota[] qa = new Quota[tab.size()];
1570    Enumeration e = tab.elements();
1571    for (int i = 0; e.hasMoreElements(); i++)
1572        qa[i] = (Quota)e.nextElement();
1573    return qa;
1574    }
1575
1576    /**
1577     * GETQUOTA Command.
1578     *
1579     * Returns an array of Quota objects, representing the quotas
1580     * for this quotaroot.
1581     *
1582     * @see "RFC2087"
1583     */

1584    public Quota[] getQuota(String JavaDoc root) throws ProtocolException {
1585    if (!hasCapability("QUOTA"))
1586        throw new BadCommandException("QUOTA not supported");
1587
1588    Argument args = new Argument();
1589    args.writeString(root);
1590
1591    Response[] r = command("GETQUOTA", args);
1592
1593    Quota quota = null;
1594    Vector v = new Vector();
1595    Response response = r[r.length-1];
1596
1597    // Grab all QUOTA responses
1598
if (response.isOK()) { // command succesful
1599
for (int i = 0, len = r.length; i < len; i++) {
1600        if (!(r[i] instanceof IMAPResponse))
1601            continue;
1602
1603        IMAPResponse ir = (IMAPResponse)r[i];
1604        if (ir.keyEquals("QUOTA")) {
1605            quota = parseQuota(ir);
1606            v.addElement(quota);
1607            r[i] = null;
1608        }
1609        }
1610    }
1611
1612    // dispatch remaining untagged responses
1613
notifyResponseHandlers(r);
1614    handleResult(response);
1615    Quota[] qa = new Quota[v.size()];
1616    v.copyInto(qa);
1617    return qa;
1618    }
1619
1620    /**
1621     * SETQUOTA Command.
1622     *
1623     * Set the indicated quota on the corresponding quotaroot.
1624     *
1625     * @see "RFC2087"
1626     */

1627    public void setQuota(Quota quota) throws ProtocolException {
1628    if (!hasCapability("QUOTA"))
1629        throw new BadCommandException("QUOTA not supported");
1630
1631    Argument args = new Argument();
1632    args.writeString(quota.quotaRoot);
1633    Argument qargs = new Argument();
1634    if (quota.resources != null) {
1635        for (int i = 0; i < quota.resources.length; i++) {
1636        qargs.writeAtom(quota.resources[i].name);
1637        qargs.writeNumber(quota.resources[i].limit);
1638        }
1639    }
1640    args.writeArgument(qargs);
1641
1642    Response[] r = command("SETQUOTA", args);
1643    Response response = r[r.length-1];
1644
1645    // XXX - It's not clear from the RFC whether the SETQUOTA command
1646
// will provoke untagged QUOTA responses. If it does, perhaps
1647
// we should grab them here and return them?
1648

1649    /*
1650    Quota quota = null;
1651    Vector v = new Vector();
1652
1653    // Grab all QUOTA responses
1654    if (response.isOK()) { // command succesful
1655        for (int i = 0, len = r.length; i < len; i++) {
1656        if (!(r[i] instanceof IMAPResponse))
1657            continue;
1658
1659        IMAPResponse ir = (IMAPResponse)r[i];
1660        if (ir.keyEquals("QUOTA")) {
1661            quota = parseQuota(ir);
1662            v.addElement(quota);
1663            r[i] = null;
1664        }
1665        }
1666    }
1667    */

1668
1669    // dispatch remaining untagged responses
1670
notifyResponseHandlers(r);
1671    handleResult(response);
1672    /*
1673    Quota[] qa = new Quota[v.size()];
1674    v.copyInto(qa);
1675    return qa;
1676    */

1677    }
1678
1679    /**
1680     * Parse a QUOTA response.
1681     */

1682    private Quota parseQuota(Response r) throws ParsingException {
1683    // quota_response ::= "QUOTA" SP astring SP quota_list
1684
String JavaDoc quotaRoot = r.readAtomString(); // quotaroot ::= astring
1685
Quota q = new Quota(quotaRoot);
1686    r.skipSpaces();
1687    // quota_list ::= "(" #quota_resource ")"
1688
if (r.readByte() != '(')
1689        throw new ParsingException("parse error in QUOTA");
1690
1691    Vector v = new Vector();
1692    while (r.peekByte() != ')') {
1693        // quota_resource ::= atom SP number SP number
1694
String JavaDoc name = r.readAtom();
1695        if (name != null) {
1696        long usage = r.readLong();
1697        long limit = r.readLong();
1698        Quota.Resource res = new Quota.Resource(name, usage, limit);
1699        v.addElement(res);
1700        }
1701    }
1702    r.readByte();
1703    q.resources = new Quota.Resource[v.size()];
1704    v.copyInto(q.resources);
1705    return q;
1706    }
1707
1708
1709    /**
1710     * SETACL Command.
1711     *
1712     * @see "RFC2086"
1713     */

1714    public void setACL(String JavaDoc mbox, char modifier, ACL acl)
1715                throws ProtocolException {
1716    if (!hasCapability("ACL"))
1717        throw new BadCommandException("ACL not supported");
1718
1719    // encode the mbox as per RFC2060
1720
mbox = BASE64MailboxEncoder.encode(mbox);
1721
1722    Argument args = new Argument();
1723    args.writeString(mbox);
1724    args.writeString(acl.getName());
1725    String JavaDoc rights = acl.getRights().toString();
1726    if (modifier == '+' || modifier == '-')
1727        rights = modifier + rights;
1728    args.writeString(rights);
1729
1730    Response[] r = command("SETACL", args);
1731    Response response = r[r.length-1];
1732
1733    // dispatch untagged responses
1734
notifyResponseHandlers(r);
1735    handleResult(response);
1736    }
1737
1738    /**
1739     * DELETEACL Command.
1740     *
1741     * @see "RFC2086"
1742     */

1743    public void deleteACL(String JavaDoc mbox, String JavaDoc user) throws ProtocolException {
1744    if (!hasCapability("ACL"))
1745        throw new BadCommandException("ACL not supported");
1746
1747    // encode the mbox as per RFC2060
1748
mbox = BASE64MailboxEncoder.encode(mbox);
1749
1750    Argument args = new Argument();
1751    args.writeString(mbox);
1752    args.writeString(user);
1753
1754    Response[] r = command("DELETEACL", args);
1755    Response response = r[r.length-1];
1756
1757    // dispatch untagged responses
1758
notifyResponseHandlers(r);
1759    handleResult(response);
1760    }
1761
1762    /**
1763     * GETACL Command.
1764     *
1765     * @see "RFC2086"
1766     */

1767    public ACL[] getACL(String JavaDoc mbox) throws ProtocolException {
1768    if (!hasCapability("ACL"))
1769        throw new BadCommandException("ACL not supported");
1770
1771    // encode the mbox as per RFC2060
1772
mbox = BASE64MailboxEncoder.encode(mbox);
1773
1774    Argument args = new Argument();
1775    args.writeString(mbox);
1776
1777    Response[] r = command("GETACL", args);
1778    Response response = r[r.length-1];
1779
1780    // Grab all ACL responses
1781
Vector v = new Vector();
1782    if (response.isOK()) { // command succesful
1783
for (int i = 0, len = r.length; i < len; i++) {
1784        if (!(r[i] instanceof IMAPResponse))
1785            continue;
1786
1787        IMAPResponse ir = (IMAPResponse)r[i];
1788        if (ir.keyEquals("ACL")) {
1789            // acl_data ::= "ACL" SPACE mailbox
1790
// *(SPACE identifier SPACE rights)
1791
// read name of mailbox and throw away
1792
ir.readAtomString();
1793            String JavaDoc name = null;
1794            while ((name = ir.readAtomString()) != null) {
1795            String JavaDoc rights = ir.readAtomString();
1796            if (rights == null)
1797                break;
1798            ACL acl = new ACL(name, new Rights(rights));
1799            v.addElement(acl);
1800            }
1801            r[i] = null;
1802        }
1803        }
1804    }
1805
1806    // dispatch remaining untagged responses
1807
notifyResponseHandlers(r);
1808    handleResult(response);
1809    ACL[] aa = new ACL[v.size()];
1810    v.copyInto(aa);
1811    return aa;
1812    }
1813
1814    /**
1815     * LISTRIGHTS Command.
1816     *
1817     * @see "RFC2086"
1818     */

1819    public Rights[] listRights(String JavaDoc mbox, String JavaDoc user)
1820                throws ProtocolException {
1821    if (!hasCapability("ACL"))
1822        throw new BadCommandException("ACL not supported");
1823
1824    // encode the mbox as per RFC2060
1825
mbox = BASE64MailboxEncoder.encode(mbox);
1826
1827    Argument args = new Argument();
1828    args.writeString(mbox);
1829    args.writeString(user);
1830
1831    Response[] r = command("LISTRIGHTS", args);
1832    Response response = r[r.length-1];
1833
1834    // Grab LISTRIGHTS response
1835
Vector v = new Vector();
1836    if (response.isOK()) { // command succesful
1837
for (int i = 0, len = r.length; i < len; i++) {
1838        if (!(r[i] instanceof IMAPResponse))
1839            continue;
1840
1841        IMAPResponse ir = (IMAPResponse)r[i];
1842        if (ir.keyEquals("LISTRIGHTS")) {
1843            // listrights_data ::= "LISTRIGHTS" SPACE mailbox
1844
// SPACE identifier SPACE rights *(SPACE rights)
1845
// read name of mailbox and throw away
1846
ir.readAtomString();
1847            // read identifier and throw away
1848
ir.readAtomString();
1849            String JavaDoc rights;
1850            while ((rights = ir.readAtomString()) != null)
1851            v.addElement(new Rights(rights));
1852            r[i] = null;
1853        }
1854        }
1855    }
1856
1857    // dispatch remaining untagged responses
1858
notifyResponseHandlers(r);
1859    handleResult(response);
1860    Rights[] ra = new Rights[v.size()];
1861    v.copyInto(ra);
1862    return ra;
1863    }
1864
1865    /**
1866     * MYRIGHTS Command.
1867     *
1868     * @see "RFC2086"
1869     */

1870    public Rights myRights(String JavaDoc mbox) throws ProtocolException {
1871    if (!hasCapability("ACL"))
1872        throw new BadCommandException("ACL not supported");
1873
1874    // encode the mbox as per RFC2060
1875
mbox = BASE64MailboxEncoder.encode(mbox);
1876
1877    Argument args = new Argument();
1878    args.writeString(mbox);
1879
1880    Response[] r = command("MYRIGHTS", args);
1881    Response response = r[r.length-1];
1882
1883    // Grab MYRIGHTS response
1884
Rights rights = null;
1885    if (response.isOK()) { // command succesful
1886
for (int i = 0, len = r.length; i < len; i++) {
1887        if (!(r[i] instanceof IMAPResponse))
1888            continue;
1889
1890        IMAPResponse ir = (IMAPResponse)r[i];
1891        if (ir.keyEquals("MYRIGHTS")) {
1892            // myrights_data ::= "MYRIGHTS" SPACE mailbox SPACE rights
1893
// read name of mailbox and throw away
1894
ir.readAtomString();
1895            String JavaDoc rs = ir.readAtomString();
1896            if (rights == null)
1897            rights = new Rights(rs);
1898            r[i] = null;
1899        }
1900        }
1901    }
1902
1903    // dispatch remaining untagged responses
1904
notifyResponseHandlers(r);
1905    handleResult(response);
1906    return rights;
1907    }
1908}
1909
Popular Tags