View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.geronimo.javamail.store.imap.connection;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.io.PrintStream;
24  import java.io.UnsupportedEncodingException;
25  import java.io.Writer;
26  import java.net.InetAddress;
27  import java.net.Socket;
28  import java.net.SocketException;
29  import java.net.UnknownHostException;
30  import java.util.ArrayList;
31  import java.util.Date;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.LinkedList;
35  import java.util.List;
36  import java.util.StringTokenizer;
37  
38  import javax.mail.Address;
39  import javax.mail.AuthenticationFailedException;
40  import javax.mail.FetchProfile; 
41  import javax.mail.Flags;
42  import javax.mail.Folder;
43  import javax.mail.Message;
44  import javax.mail.MessagingException;
45  import javax.mail.MethodNotSupportedException;
46  import javax.mail.Quota;
47  import javax.mail.Session;
48  import javax.mail.UIDFolder;
49  import javax.mail.URLName;
50  
51  import javax.mail.internet.InternetHeaders;
52  
53  import javax.mail.search.SearchTerm;
54  
55  import org.apache.geronimo.javamail.authentication.AuthenticatorFactory; 
56  import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
57  import org.apache.geronimo.javamail.authentication.LoginAuthenticator; 
58  import org.apache.geronimo.javamail.authentication.PlainAuthenticator; 
59  import org.apache.geronimo.javamail.store.imap.ACL;
60  import org.apache.geronimo.javamail.store.imap.Rights; 
61  
62  import org.apache.geronimo.javamail.util.CommandFailedException;      
63  import org.apache.geronimo.javamail.util.InvalidCommandException;      
64  import org.apache.geronimo.javamail.util.MailConnection; 
65  import org.apache.geronimo.javamail.util.ProtocolProperties; 
66  import org.apache.geronimo.javamail.util.TraceInputStream;
67  import org.apache.geronimo.javamail.util.TraceOutputStream;
68  import org.apache.geronimo.mail.util.Base64;
69  
70  /**
71   * Simple implementation of IMAP transport.  Just does plain RFC977-ish
72   * delivery.
73   * <p/>
74   * There is no way to indicate failure for a given recipient (it's possible to have a
75   * recipient address rejected).  The sun impl throws exceptions even if others successful),
76   * but maybe we do a different way...
77   * <p/>
78   *
79   * @version $Rev: 739377 $ $Date: 2009-01-30 13:57:39 -0500 (Fri, 30 Jan 2009) $
80   */
81  public class IMAPConnection extends MailConnection {
82      
83      protected static final String CAPABILITY_LOGIN_DISABLED = "LOGINDISABLED";
84  
85      // The connection pool we're a member of.  This keeps holds most of the 
86      // connnection parameter information for us. 
87      protected IMAPConnectionPool pool; 
88      
89      // special input stream for reading individual response lines.
90      protected IMAPResponseStream reader;
91  
92      // connection pool connections.
93      protected long lastAccess = 0;
94      // our handlers for any untagged responses
95      protected LinkedList responseHandlers = new LinkedList();
96      // the list of queued untagged responses.
97      protected List queuedResponses = new LinkedList();
98      // this is set on if we had a forced disconnect situation from 
99      // the server. 
100     protected boolean closed = false;
101 
102     /**
103      * Normal constructor for an IMAPConnection() object.
104      * 
105      * @param props  The protocol properties abstraction containing our
106      *               property modifiers.
107      * @param pool
108      */
109     public IMAPConnection(ProtocolProperties props, IMAPConnectionPool pool) {
110         super(props);
111         this.pool = pool; 
112     }
113 
114                           
115     /**
116      * Connect to the server and do the initial handshaking.
117      *
118      * @exception MessagingException
119      */
120     public boolean protocolConnect(String host, int port, String authid, String realm, String username, String password) throws MessagingException {
121         this.serverHost = host; 
122         this.serverPort = port; 
123         this.realm = realm; 
124         this.authid = authid; 
125         this.username = username; 
126         this.password = password; 
127         
128         boolean preAuthorized = false; 
129         
130         try {
131             // create socket and connect to server.
132             getConnection();
133 
134             // we need to ask the server what its capabilities are.  This can be done 
135             // before we login.  
136             getCapability();
137             // do a preauthoriziation check. 
138             if (extractResponse("PREAUTH") != null) {
139                 preAuthorized = true; 
140             }
141             
142             // make sure we process these now
143             processPendingResponses(); 
144 
145             // if we're not already using an SSL connection, and we have permission to issue STARTTLS, AND
146             // the server supports this, then switch to TLS mode before continuing.
147             if (!sslConnection && props.getBooleanProperty(MAIL_STARTTLS_ENABLE, false) && hasCapability(CAPABILITY_STARTTLS)) {
148                 // if the server supports TLS, then use it for the connection.
149                 // on our connection.
150                 
151                 // tell the server of our intention to start a TLS session
152                 sendSimpleCommand("STARTTLS");
153                 
154                 // The connection is then handled by the superclass level. 
155                 getConnectedTLSSocket();
156                 
157                 // create the special reader for pulling the responses.
158                 reader = new IMAPResponseStream(inputStream);
159 
160                 // the IMAP spec states that the capability response is independent of login state or   
161                 // user, but I'm not sure I believe that to be the case.  It doesn't hurt to refresh 
162                 // the information again after establishing a secure connection. 
163                 getCapability();
164                 // and we need to repeat this check. 
165                 if (extractResponse("PREAUTH") != null) {
166                     preAuthorized = true; 
167                 }
168             }
169             
170             // damn, no login required.  
171             if (preAuthorized) {
172                 return true; 
173             }
174             
175             // go login with the server 
176             return login(); 
177         } catch (IOException e) {
178             if (debug) {
179                 debugOut("I/O exception establishing connection", e);
180             }
181             throw new MessagingException("Connection error", e);
182         }
183         finally {
184             // make sure the queue is cleared 
185             processPendingResponses(); 
186         }
187     }
188 
189     /**
190      * Update the last access time for the connection.
191      */
192     protected void updateLastAccess() {
193         lastAccess = System.currentTimeMillis();
194     }
195 
196     /**
197      * Test if the connection has been sitting idle for longer than
198      * the set timeout period.
199      *
200      * @param timeout The allowed "freshness" interval.
201      *
202      * @return True if the connection has been active within the required
203      *         interval, false if it has been sitting idle for too long.
204      */
205     public boolean isStale(long timeout) {
206         return (System.currentTimeMillis() - lastAccess) > timeout;
207     }
208 
209 
210     /**
211      * Close the connection.  On completion, we'll be disconnected from
212      * the server and unable to send more data.
213      *
214      * @exception MessagingException
215      */
216     public void close() throws MessagingException {
217         // if we're already closed, get outta here.
218         if (socket == null) {
219             return;
220         }
221         try {
222             // say goodbye
223             logout();   
224         } finally {
225             // and close up the connection.  We do this in a finally block to make sure the connection
226             // is shut down even if quit gets an error.
227             closeServerConnection();
228             // get rid of our response processor too. 
229             reader = null; 
230         }
231     }
232 
233 
234     /**
235      * Create a transport connection object and connect it to the
236      * target server.
237      *
238      * @exception MessagingException
239      */
240     protected void getConnection() throws IOException, MessagingException
241     {
242         // do all of the non-protocol specific set up.  This will get our socket established 
243         // and ready use. 
244         super.getConnection(); 
245         // create the special reader for pulling the responses.
246         reader = new IMAPResponseStream(inputStream);
247 
248         // set the initial access time stamp
249         updateLastAccess();
250     }
251 
252 
253     /**
254      * Process a simple command/response sequence between the
255      * client and the server.  These are commands where the
256      * client is expecting them to "just work", and also will not
257      * directly process the reply information.  Unsolicited untagged
258      * responses are dispatched to handlers, and a MessagingException
259      * will be thrown for any non-OK responses from the server.
260      *
261      * @param data   The command data we're writing out.
262      *
263      * @exception MessagingException
264      */
265     public void sendSimpleCommand(String data) throws MessagingException {
266         // create a command object and issue the command with that. 
267         IMAPCommand command = new IMAPCommand(data); 
268         sendSimpleCommand(command); 
269     }
270 
271 
272     /**
273      * Process a simple command/response sequence between the
274      * client and the server.  These are commands where the
275      * client is expecting them to "just work", and also will not
276      * directly process the reply information.  Unsolicited untagged
277      * responses are dispatched to handlers, and a MessagingException
278      * will be thrown for any non-OK responses from the server.
279      *
280      * @param data   The command data we're writing out.
281      *
282      * @exception MessagingException
283      */
284     public void sendSimpleCommand(IMAPCommand data) throws MessagingException {
285         // the command sending process will raise exceptions for bad responses....
286         // we just need to send the command and forget about it. 
287         sendCommand(data);
288     }
289 
290 
291     /**
292      * Sends a  command down the socket, returning the server response.
293      * 
294      * @param data   The String form of the command.
295      * 
296      * @return The tagged response information that terminates the command interaction.
297      * @exception MessagingException
298      */
299     public IMAPTaggedResponse sendCommand(String data) throws MessagingException {
300         IMAPCommand command = new IMAPCommand(data); 
301         return sendCommand(command); 
302     }
303 
304 
305     /**
306      * Sends a  command down the socket, returning the server response.
307      * 
308      * @param data   An IMAPCommand object with the prepared command information.
309      * 
310      * @return The tagged (or continuation) response information that terminates the 
311      *         command response sequence.
312      * @exception MessagingException
313      */
314     public synchronized IMAPTaggedResponse sendCommand(IMAPCommand data) throws MessagingException {
315         // check first 
316         checkConnected(); 
317         try {
318             // have the command write the command data.  This also prepends a tag. 
319             data.writeTo(outputStream, this);
320             outputStream.flush();
321             // update the activity timestamp
322             updateLastAccess();
323             // get the received response  
324             return receiveResponse(); 
325         } catch (IOException e) {
326             throw new MessagingException(e.toString(), e);
327         }
328     }
329     
330 
331     /**
332      * Sends a  message down the socket and terminates with the
333      * appropriate CRLF
334      * 
335      * @param data   The string data to send.
336      * 
337      * @return An IMAPTaggedResponse item returned from the server.
338      * @exception MessagingException
339      */
340     public IMAPTaggedResponse sendLine(String data) throws MessagingException {
341         return sendLine(data.getBytes()); 
342     }
343     
344 
345     /**
346      * Sends a  message down the socket and terminates with the
347      * appropriate CRLF
348      * 
349      * @param data   The array of data to send to the server.
350      * 
351      * @return The response item returned from the IMAP server.
352      * @exception MessagingException
353      */
354     public IMAPTaggedResponse sendLine(byte[] data) throws MessagingException {
355         return sendLine(data, 0, data.length); 
356     }
357     
358 
359     /**
360      * Sends a  message down the socket and terminates with the
361      * appropriate CRLF
362      * 
363      * @param data   The source data array.
364      * @param offset The offset within the data array.
365      * @param length The length of data to send.
366      * 
367      * @return The response line returned from the IMAP server. 
368      * @exception MessagingException
369      */
370     public synchronized IMAPTaggedResponse sendLine(byte[] data, int offset, int length) throws MessagingException {
371         // check first 
372         checkConnected(); 
373         
374         try {
375             outputStream.write(data, offset, length);
376             outputStream.write(CR);
377             outputStream.write(LF);
378             outputStream.flush();
379             // update the activity timestamp
380             updateLastAccess();
381             return receiveResponse(); 
382         } catch (IOException e) {
383             throw new MessagingException(e.toString(), e);
384         }
385     }
386 
387     
388     /**
389      * Get a reply line for an IMAP command.
390      *
391      * @return An IMAP reply object from the stream.
392      */
393     public IMAPTaggedResponse receiveResponse() throws MessagingException {
394         while (true) {
395             // read and parse a response from the server.
396             IMAPResponse response = reader.readResponse();
397             // The response set is terminated by either a continuation response or a  
398             // tagged response (we only have a single command active at one time). 
399             if (response instanceof IMAPTaggedResponse) {
400                 // update the access time stamp for later timeout processing.
401                 updateLastAccess(); 
402                 IMAPTaggedResponse tagged = (IMAPTaggedResponse)response; 
403                 // we turn these into exceptions here, which means the issuer doesn't have to 
404                 // worry about checking status. 
405                 if (tagged.isBAD()) {
406                     throw new InvalidCommandException("Unexpected command IMAP command error"); 
407                 }
408                 else if (tagged.isNO()) {
409                     throw new CommandFailedException("Unexpected error executing IMAP command"); 
410                 }
411                 return tagged;                       
412             }
413             else {
414                 // all other unsolicited responses are either async status updates or 
415                 // additional elements of a command we just sent.  These will be processed 
416                 // either during processing of the command response, or at the end of the 
417                 // current command processing. 
418                 queuePendingResponse((IMAPUntaggedResponse)response); 
419             }
420         }
421     }
422 
423     
424     /**
425      * Get the servers capabilities from the wire....
426      */
427     public void getCapability() throws MessagingException {
428         sendCommand("CAPABILITY");
429         // get the capabilities from the response.
430         IMAPCapabilityResponse response = (IMAPCapabilityResponse)extractResponse("CAPABILITY"); 
431         capabilities = response.getCapabilities(); 
432         authentications = response.getAuthentications(); 
433     }
434 
435     /**
436      * Logs out from the server.                                     
437      */
438     public void logout() throws MessagingException {
439         // We can just send the command and generally ignore the 
440         // status response. 
441         sendCommand("LOGOUT");
442     }
443 
444     /**
445      * Deselect a mailbox when a folder returns a connection.
446      * 
447      * @exception MessagingException
448      */
449     public void closeMailbox() throws MessagingException {
450         // We can just send the command and generally ignore the 
451         // status response. 
452         sendCommand("CLOSE");
453     }
454 
455     
456     /**
457      * Authenticate with the server, if necessary (or possible).
458      * 
459      * @return true if we were able to authenticate correctly, false for authentication failures.
460      * @exception MessagingException
461      */
462     protected boolean login() throws MessagingException
463     {
464         // if no username or password, fail this immediately. 
465         // the base connect property should resolve a username/password combo for us and 
466         // try again. 
467         if (username == null || password == null) {
468             return false; 
469         }
470         
471         // are we permitted to use SASL mechanisms?
472         if (props.getBooleanProperty(MAIL_SASL_ENABLE, false)) {
473             // we might be enable for SASL, but the client and the server might
474             // not have any supported mechanisms in common.  Try again with another
475             // mechanism.
476             if (processSaslAuthentication()) {
477                 return true;
478             }
479         }
480 
481         // see if we're allowed to try plain.
482         if (!props.getBooleanProperty(MAIL_PLAIN_DISABLE, false) && supportsMechanism(AUTHENTICATION_PLAIN)) {
483             return processPlainAuthentication();
484         }
485 
486         // see if we're allowed to try login.
487         if (!props.getBooleanProperty(MAIL_LOGIN_DISABLE, false) && supportsMechanism(AUTHENTICATION_LOGIN)) {
488             // no authzid capability with this authentication method.
489             return processLoginAuthentication();
490         }
491         
492         // the server can choose to disable the LOGIN command.  If not disabled, try 
493         // using LOGIN rather than AUTHENTICATE. 
494         if (!hasCapability(CAPABILITY_LOGIN_DISABLED)) {
495             return processLogin(); 
496         }
497         
498         throw new MessagingException("No supported LOGIN methods enabled"); 
499     }
500 
501     
502     /**
503      * Process SASL-type authentication.
504      *
505      * @return Returns true if the server support a SASL authentication mechanism and
506      * accepted reponse challenges.
507      * @exception MessagingException
508      */
509     protected boolean processSaslAuthentication() throws MessagingException {
510         // if unable to get an appropriate authenticator, just fail it. 
511         ClientAuthenticator authenticator = getSaslAuthenticator(); 
512         if (authenticator == null) {
513             return false; 
514         }
515         
516         // go process the login.
517         return processLogin(authenticator);
518     }
519     
520     protected ClientAuthenticator getSaslAuthenticator() {
521         return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm); 
522     }
523 
524     /**
525      * Process SASL-type PLAIN authentication.
526      *
527      * @return Returns true if the login is accepted. 
528      * @exception MessagingException
529      */
530     protected boolean processPlainAuthentication() throws MessagingException {
531         // go process the login.
532         return processLogin(new PlainAuthenticator(username, password));
533     }
534 
535 
536     /**
537      * Process SASL-type LOGIN authentication.
538      *
539      * @return Returns true if the login is accepted. 
540      * @exception MessagingException
541      */
542     protected boolean processLoginAuthentication() throws MessagingException {
543         // go process the login.
544         return processLogin(new LoginAuthenticator(username, password));
545     }
546     
547     
548     /**
549      * Process a LOGIN using the LOGIN command instead of AUTHENTICATE. 
550      * 
551      * @return true if the command succeeded, false for any authentication failures. 
552      * @exception MessagingException
553      */
554     protected boolean processLogin() throws MessagingException {
555         // arguments are "LOGIN userid password"
556         IMAPCommand command = new IMAPCommand("LOGIN");
557         command.appendAtom(username); 
558         command.appendAtom(password); 
559         
560         // go issue the command 
561         try {
562             sendCommand(command); 
563         } catch (CommandFailedException e) {
564             // we'll get a NO response for a rejected login
565             return false; 
566         }
567         // seemed to work ok....
568         return true;   
569     }
570 
571 
572     /**
573      * Process a login using the provided authenticator object.
574      * 
575      * NB:  This method is synchronized because we have a multi-step process going on 
576      * here.  No other commands should be sent to the server until we complete. 
577      *
578      * @return Returns true if the server support a SASL authentication mechanism and
579      * accepted reponse challenges.
580      * @exception MessagingException
581      */
582     protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
583         if (debug) {
584             debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
585         }
586 
587         IMAPCommand command = new IMAPCommand("AUTHENTICATE");
588         // and tell the server which mechanism we're using.
589         command.appendAtom(authenticator.getMechanismName());
590         // send the command now
591         
592         try {
593             IMAPTaggedResponse response = sendCommand(command);
594 
595             // now process the challenge sequence.  We get a 235 response back when the server accepts the
596             // authentication, and a 334 indicates we have an additional challenge.
597             while (true) {
598                 // this should be a continuation reply, if things are still good.
599                 if (response.isContinuation()) {
600                     // we're passed back a challenge value, Base64 encoded.
601                     byte[] challenge = response.decodeChallengeResponse();
602 
603                     // have the authenticator evaluate and send back the encoded response.
604                     response = sendLine(Base64.encode(authenticator.evaluateChallenge(challenge)));
605                 }
606                 else {
607                     // there are only two choices here, OK or a continuation.  OK means 
608                     // we've passed muster and are in. 
609                     return true; 
610                 }
611             }
612         } catch (CommandFailedException e ) {
613             // a failure at any point in this process will result in a "NO" response.  
614             // That causes an exception to get thrown, so just fail the login 
615             // if we get one. 
616             return false; 
617         }
618     }
619     
620 
621     /**
622      * Return the server host for this connection.
623      *
624      * @return The String name of the server host.
625      */
626     public String getHost() {
627         return serverHost;
628     }
629 
630     
631     /**
632      * Attach a handler for untagged responses to this connection.
633      *
634      * @param h      The new untagged response handler.
635      */
636     public synchronized void addResponseHandler(IMAPUntaggedResponseHandler h) {
637         responseHandlers.add(h);
638     }
639 
640 
641     /**
642      * Remove a response handler from the connection.
643      *
644      * @param h      The handler to remove.
645      */
646     public synchronized void removeResponseHandler(IMAPUntaggedResponseHandler h) {
647         responseHandlers.remove(h);
648     }
649 
650 
651     /**
652      * Add a response to the pending untagged response queue.
653      *
654      * @param response The response to add.
655      */
656     public synchronized void queuePendingResponse(IMAPUntaggedResponse response) {
657         queuedResponses.add(response);
658     }
659 
660     /**
661      * Process any untagged responses in the queue.  This will clear out
662      * the queue, and send each response to the registered
663      * untagged response handlers.
664      */
665     public void processPendingResponses() throws MessagingException {
666         List pendingResponses = null;
667         List handlerList = null; 
668 
669         synchronized(this) {
670             if (queuedResponses.isEmpty()) {
671                 return;
672             }
673             pendingResponses = queuedResponses;
674             queuedResponses = new LinkedList();
675             // get a copy of the response handlers so we can 
676             // release the connection lock before broadcasting 
677             handlerList = (List)responseHandlers.clone(); 
678         }
679         
680         for (int i = 0; i < pendingResponses.size(); i++) {
681             IMAPUntaggedResponse response = (IMAPUntaggedResponse)pendingResponses.get(i); 
682             for (int j = 0; j < handlerList.size(); j++) {
683                 // broadcast to each handler.  If a handler returns true, then it 
684                 // handled whatever this message required and we should skip sending 
685                 // it to other handlers. 
686                 IMAPUntaggedResponseHandler h = (IMAPUntaggedResponseHandler)handlerList.get(j); 
687                 if (h.handleResponse(response)) { 
688                     break; 
689                 }
690             }
691         }
692     }
693     
694     /**
695      * Extract a single response from the pending queue that 
696      * match a give keyword type.  All matching responses 
697      * are removed from the pending queue. 
698      * 
699      * @param type   The string name of the keyword.
700      * 
701      * @return A List of all matching queued responses. 
702      */
703     public IMAPUntaggedResponse extractResponse(String type) {
704         Iterator i = queuedResponses.iterator(); 
705         while (i.hasNext()) {
706             IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next(); 
707             // if this is of the target type, move it to the response set. 
708             if (response.isKeyword(type)) {
709                 i.remove(); 
710                 return response;
711             }
712         }
713         return null;  
714     }
715     
716     /**
717      * Extract all responses from the pending queue that 
718      * match a give keyword type.  All matching responses 
719      * are removed from the pending queue. 
720      * 
721      * @param type   The string name of the keyword.
722      * 
723      * @return A List of all matching queued responses. 
724      */
725     public List extractResponses(String type) {
726         List responses = new ArrayList(); 
727         
728         Iterator i = queuedResponses.iterator(); 
729         while (i.hasNext()) {
730             IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next(); 
731             // if this is of the target type, move it to the response set. 
732             if (response.isKeyword(type)) {
733                 i.remove(); 
734                 responses.add(response); 
735             }
736         }
737         return responses; 
738     }
739     
740     
741     /**
742      * Extract all responses from the pending queue that 
743      * are "FETCH" responses for a given message number.  All matching responses 
744      * are removed from the pending queue. 
745      * 
746      * @param type   The string name of the keyword.
747      * 
748      * @return A List of all matching queued responses. 
749      */
750     public List extractFetchResponses(int sequenceNumber) {
751         List responses = new ArrayList(); 
752         
753         Iterator i = queuedResponses.iterator(); 
754         while (i.hasNext()) {
755             IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next(); 
756             // if this is of the target type, move it to the response set. 
757             if (response.isKeyword("FETCH")) {
758                 IMAPFetchResponse fetch = (IMAPFetchResponse)response; 
759                 // a response for the correct message number?
760                 if (fetch.sequenceNumber == sequenceNumber) {
761                     // pluck these from the list and add to the response set. 
762                     i.remove(); 
763                     responses.add(response); 
764                 }
765             }
766         }
767         return responses; 
768     }
769     
770     /**
771      * Extract a fetch response data item from the queued elements. 
772      * 
773      * @param sequenceNumber
774      *               The message number we're interested in.  Fetch responses for other messages
775      *               will be skipped.
776      * @param type   The type of body element we need. It is assumed that only one item for
777      *               the given message number will exist in the queue.  The located item will
778      *               be returned, and that fetch response will be removed from the pending queue.
779      * 
780      * @return The target data item, or null if a match is not found.
781      */
782     protected IMAPFetchDataItem extractFetchDataItem(long sequenceNumber, int type) 
783     {
784         Iterator i = queuedResponses.iterator(); 
785         while (i.hasNext()) {
786             IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next(); 
787             // if this is of the target type, move it to the response set. 
788             if (response.isKeyword("FETCH")) {
789                 IMAPFetchResponse fetch = (IMAPFetchResponse)response; 
790                 // a response for the correct message number?
791                 if (fetch.sequenceNumber == sequenceNumber) {
792                     // does this response have the item we're looking for?
793                     IMAPFetchDataItem item = fetch.getDataItem(type); 
794                     if (item != null) {
795                         // remove this from the pending queue and return the 
796                         // located item
797                         i.remove(); 
798                         return item; 
799                     }
800                 }
801             }
802         }
803         // not located, sorry 
804         return null;       
805     }
806     
807     /**
808      * Extract a all fetch responses that contain a given data item.  
809      * 
810      * @param type   The type of body element we need. It is assumed that only one item for
811      *               the given message number will exist in the queue.  The located item will
812      *               be returned, and that fetch response will be removed from the pending queue.
813      * 
814      * @return A List of all matching Fetch responses.                         
815      */
816     protected List extractFetchDataItems(int type) 
817     {
818         Iterator i = queuedResponses.iterator(); 
819         List items = new ArrayList(); 
820         
821         while (i.hasNext()) {
822             IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next(); 
823             // if this is of the target type, move it to the response set. 
824             if (response.isKeyword("FETCH")) {
825                 IMAPFetchResponse fetch = (IMAPFetchResponse)response; 
826                 // does this response have the item we're looking for?
827                 IMAPFetchDataItem item = fetch.getDataItem(type); 
828                 if (item != null) {
829                     // remove this from the pending queue and return the 
830                     // located item
831                     i.remove(); 
832                     // we want the fetch response, not the data item, because 
833                     // we're going to require the message sequence number information 
834                     // too. 
835                     items.add(fetch); 
836                 }
837             }
838         }
839         // return whatever we have. 
840         return items;      
841     }
842 
843     /**
844      * Make sure we have the latest status information available.  We
845      * retreive this by sending a NOOP command to the server, and
846      * processing any untagged responses we get back.
847      */
848     public void updateMailboxStatus() throws MessagingException {
849         sendSimpleCommand("NOOP");
850     }
851 
852 
853     /**
854      * check to see if this connection is truely alive.
855      * 
856      * @param timeout The timeout value to control how often we ping
857      *                the server to see if we're still good.
858      * 
859      * @return true if the server is responding to requests, false for any
860      *         connection errors.  This will also update the folder status
861      *         by processing returned unsolicited messages.
862      */
863     public synchronized boolean isAlive(long timeout) {
864         long lastUsed = System.currentTimeMillis() - lastAccess; 
865         if (lastUsed < timeout) {
866             return true; 
867         }
868         
869         try {
870             sendSimpleCommand("NOOP"); 
871             return true;
872         } catch (MessagingException e) {
873             // the NOOP command will throw a MessagingException if we get anything 
874             // other than an OK response back from the server.  
875         }
876         return false;
877     }
878 
879 
880     /**
881      * Issue a fetch command to retrieve the message ENVELOPE structure.
882      *
883      * @param sequenceNumber The sequence number of the message.
884      *
885      * @return The IMAPResponse item containing the ENVELOPE information.
886      */
887     public synchronized List fetchEnvelope(int sequenceNumber) throws MessagingException {
888         IMAPCommand command = new IMAPCommand("FETCH");
889         command.appendInteger(sequenceNumber);
890         command.startList(); 
891         command.appendAtom("ENVELOPE INTERNALDATE RFC822.SIZE"); 
892         command.endList();   
893         
894         // we want all of the envelope information about the message, which involves multiple FETCH chunks.
895         sendCommand(command);
896         // these are fairly involved sets, so the caller needs to handle these.
897         // we just return all of the FETCH results matching the target message number.  
898         return extractFetchResponses(sequenceNumber); 
899     }
900     
901     /**
902      * Issue a FETCH command to retrieve the message BODYSTRUCTURE structure.
903      *
904      * @param sequenceNumber The sequence number of the message.
905      *
906      * @return The IMAPBodyStructure item for the message.
907      *         All other untagged responses are queued for processing.
908      */
909     public synchronized IMAPBodyStructure fetchBodyStructure(int sequenceNumber) throws MessagingException {
910         IMAPCommand command = new IMAPCommand("FETCH");
911         command.appendInteger(sequenceNumber);
912         command.startList(); 
913         command.appendAtom("BODYSTRUCTURE"); 
914         command.endList();   
915         
916         // we want all of the envelope information about the message, which involves multiple FETCH chunks.
917         sendCommand(command);
918         // locate the response from this 
919         IMAPBodyStructure bodyStructure = (IMAPBodyStructure)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.BODYSTRUCTURE);
920 
921         if (bodyStructure == null) {
922             throw new MessagingException("No BODYSTRUCTURE information received from IMAP server");
923         }
924         // and return the body structure directly.
925         return bodyStructure;
926     }
927 
928 
929     /**
930      * Issue a FETCH command to retrieve the message RFC822.HEADERS structure containing the message headers (using PEEK).
931      *
932      * @param sequenceNumber The sequence number of the message.
933      *
934      * @return The IMAPRFC822Headers item for the message.
935      *         All other untagged responses are queued for processing.
936      */
937     public synchronized InternetHeaders fetchHeaders(int sequenceNumber, String part) throws MessagingException {
938         IMAPCommand command = new IMAPCommand("FETCH");
939         command.appendInteger(sequenceNumber);
940         command.startList(); 
941         command.appendAtom("BODY.PEEK"); 
942         command.appendBodySection(part, "HEADER"); 
943         command.endList();   
944         
945         // we want all of the envelope information about the message, which involves multiple FETCH chunks.
946         sendCommand(command);
947         IMAPInternetHeader header = (IMAPInternetHeader)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.HEADER);
948 
949         if (header == null) {
950             throw new MessagingException("No HEADER information received from IMAP server");
951         }
952         // and return the body structure directly.
953         return header.headers;
954     }
955 
956 
957     /**
958      * Issue a FETCH command to retrieve the message text
959      *
960      * @param sequenceNumber The sequence number of the message.
961      *
962      * @return The IMAPMessageText item for the message.
963      *         All other untagged responses are queued for processing.
964      */
965     public synchronized IMAPMessageText fetchText(int sequenceNumber) throws MessagingException {
966         IMAPCommand command = new IMAPCommand("FETCH");
967         command.appendInteger(sequenceNumber);
968         command.startList(); 
969         command.appendAtom("BODY.PEEK"); 
970         command.appendBodySection("TEXT"); 
971         command.endList();   
972         
973         // we want all of the envelope information about the message, which involves multiple FETCH chunks.
974         sendCommand(command);
975         IMAPMessageText text = (IMAPMessageText)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.TEXT);
976 
977         if (text == null) {
978             throw new MessagingException("No TEXT information received from IMAP server");
979         }
980         // and return the body structure directly.
981         return text;
982     }
983 
984 
985     /**
986      * Issue a FETCH command to retrieve the message text
987      *
988      * @param sequenceNumber The sequence number of the message.
989      *
990      * @return The IMAPMessageText item for the message.
991      *         All other untagged responses are queued for processing.
992      */
993     public synchronized IMAPMessageText fetchBodyPartText(int sequenceNumber, String section) throws MessagingException {
994         IMAPCommand command = new IMAPCommand("FETCH");
995         command.appendInteger(sequenceNumber);
996         command.startList(); 
997         command.appendAtom("BODY.PEEK"); 
998         command.appendBodySection(section, "TEXT"); 
999         command.endList();   
1000         
1001         // we want all of the envelope information about the message, which involves multiple FETCH chunks.
1002         sendCommand(command);
1003         IMAPMessageText text = (IMAPMessageText)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.TEXT);
1004 
1005         if (text == null) {
1006             throw new MessagingException("No TEXT information received from IMAP server");
1007         }
1008         // and return the body structure directly.
1009         return text;
1010     }
1011 
1012 
1013     /**
1014      * Issue a FETCH command to retrieve the entire message body in one shot.
1015      * This may also be used to fetch an embedded message part as a unit.
1016      * 
1017      * @param sequenceNumber
1018      *                The sequence number of the message.
1019      * @param section The section number to fetch.  If null, the entire body of the message
1020      *                is retrieved.
1021      * 
1022      * @return The IMAPBody item for the message.
1023      *         All other untagged responses are queued for processing.
1024      * @exception MessagingException
1025      */
1026     public synchronized IMAPBody fetchBody(int sequenceNumber, String section) throws MessagingException {
1027         IMAPCommand command = new IMAPCommand("FETCH");
1028         command.appendInteger(sequenceNumber);
1029         command.startList(); 
1030         command.appendAtom("BODY.PEEK"); 
1031         // no part name here, only the section identifier.  This will fetch 
1032         // the entire body, with all of the bits in place. 
1033         command.appendBodySection(section, null); 
1034         command.endList();   
1035         
1036         // we want all of the envelope information about the message, which involves multiple FETCH chunks.
1037         sendCommand(command);
1038         IMAPBody body = (IMAPBody)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.BODY);
1039 
1040         if (body == null) {
1041             throw new MessagingException("No BODY information received from IMAP server");
1042         }
1043         // and return the body structure directly.
1044         return body;
1045     }
1046     
1047     
1048     /**
1049      * Fetch the message content.  This sorts out which method should be used 
1050      * based on the server capability.
1051      * 
1052      * @param sequenceNumber
1053      *               The sequence number of the target message.
1054      * 
1055      * @return The byte[] content information.
1056      * @exception MessagingException
1057      */
1058     public byte[] fetchContent(int sequenceNumber) throws MessagingException {
1059         // fetch the text item and return the data 
1060         IMAPMessageText text = fetchText(sequenceNumber);
1061         return text.getContent();
1062     }
1063     
1064     
1065     /**
1066      * Fetch the message content.  This sorts out which method should be used 
1067      * based on the server capability.
1068      * 
1069      * @param sequenceNumber
1070      *               The sequence number of the target message.
1071      * 
1072      * @return The byte[] content information.
1073      * @exception MessagingException
1074      */
1075     public byte[] fetchContent(int sequenceNumber, String section) throws MessagingException {
1076         if (section == null) {
1077             IMAPMessageText text = fetchText(sequenceNumber);
1078             return text.getContent();
1079         } else {
1080             IMAPBody body = fetchBody(sequenceNumber, section);
1081             return body.getContent();
1082         }
1083     }
1084 
1085 
1086     /**
1087      * Send an LIST command to the IMAP server, returning all LIST
1088      * response information.
1089      *
1090      * @param mailbox The reference mailbox name sent on the command.
1091      * @param pattern The match pattern used on the name.
1092      *
1093      * @return A List of all LIST response information sent back from the server.
1094      */
1095     public synchronized List list(String mailbox, String pattern) throws MessagingException {
1096         IMAPCommand command = new IMAPCommand("LIST");
1097 
1098         // construct the command, encoding the tokens as required by the content.
1099         command.appendEncodedString(mailbox);
1100         command.appendEncodedString(pattern);
1101 
1102         sendCommand(command);
1103 
1104         // pull out the ones we're interested in 
1105         return extractResponses("LIST"); 
1106     }
1107 
1108 
1109     /**
1110      * Send an LSUB command to the IMAP server, returning all LSUB
1111      * response information.
1112      *
1113      * @param mailbox The reference mailbox name sent on the command.
1114      * @param pattern The match pattern used on the name.
1115      *
1116      * @return A List of all LSUB response information sent back from the server.
1117      */
1118     public List listSubscribed(String mailbox, String pattern) throws MessagingException {
1119         IMAPCommand command = new IMAPCommand("LSUB");
1120 
1121         // construct the command, encoding the tokens as required by the content.
1122         command.appendEncodedString(mailbox);
1123         command.appendEncodedString(pattern);
1124 
1125         sendCommand(command);
1126         // pull out the ones we're interested in 
1127         return extractResponses("LSUB"); 
1128     }
1129 
1130 
1131     /**
1132      * Subscribe to a give mailbox.
1133      *
1134      * @param mailbox The desired mailbox name.
1135      *
1136      * @exception MessagingException
1137      */
1138     public void subscribe(String mailbox) throws MessagingException {
1139         IMAPCommand command = new IMAPCommand("SUBSCRIBE");
1140         // add on the encoded mailbox name, as the appropriate token type.
1141         command.appendEncodedString(mailbox);
1142 
1143         // send this, and ignore the response.
1144         sendSimpleCommand(command);
1145     }
1146 
1147 
1148     /**
1149      * Unsubscribe from a mailbox.
1150      *
1151      * @param mailbox The mailbox to remove.
1152      *
1153      * @exception MessagingException
1154      */
1155     public void unsubscribe(String mailbox) throws MessagingException {
1156         IMAPCommand command = new IMAPCommand("UNSUBSCRIBE");
1157         // add on the encoded mailbox name, as the appropriate token type.
1158         command.appendEncodedString(mailbox);
1159 
1160         // send this, and ignore the response.
1161         sendSimpleCommand(command);
1162     }
1163 
1164 
1165     /**
1166      * Create a mailbox.
1167      *
1168      * @param mailbox The desired new mailbox name (fully qualified);
1169      *
1170      * @exception MessagingException
1171      */
1172     public void createMailbox(String mailbox) throws MessagingException {
1173         IMAPCommand command = new IMAPCommand("CREATE");
1174         // add on the encoded mailbox name, as the appropriate token type.
1175         command.appendEncodedString(mailbox);
1176 
1177         // send this, and ignore the response.
1178         sendSimpleCommand(command);
1179     }
1180 
1181 
1182     /**
1183      * Delete a mailbox.
1184      *
1185      * @param mailbox The target mailbox name (fully qualified);
1186      *
1187      * @exception MessagingException
1188      */
1189     public void deleteMailbox(String mailbox) throws MessagingException {
1190         IMAPCommand command = new IMAPCommand("DELETE");
1191         // add on the encoded mailbox name, as the appropriate token type.
1192         command.appendEncodedString(mailbox);
1193 
1194         // send this, and ignore the response.
1195         sendSimpleCommand(command);
1196     }
1197 
1198 
1199     /**
1200      * Rename a mailbox.
1201      *
1202      * @param mailbox The target mailbox name (fully qualified);
1203      *
1204      * @exception MessagingException
1205      */
1206     public void renameMailbox(String oldName, String newName) throws MessagingException {
1207         IMAPCommand command = new IMAPCommand("RENAME");
1208         // add on the encoded mailbox name, as the appropriate token type.
1209         command.appendEncodedString(oldName);
1210         command.appendEncodedString(newName);
1211 
1212         // send this, and ignore the response.
1213         sendSimpleCommand(command);
1214     }
1215 
1216 
1217     /**
1218      * Retrieve a complete set of status items for a mailbox.
1219      *
1220      * @param mailbox The mailbox name.
1221      *
1222      * @return An IMAPMailboxStatus item filled in with the STATUS responses.
1223      * @exception MessagingException
1224      */
1225     public synchronized IMAPMailboxStatus getMailboxStatus(String mailbox) throws MessagingException {
1226         IMAPCommand command = new IMAPCommand("STATUS");
1227 
1228         // construct the command, encoding the tokens as required by the content.
1229         command.appendEncodedString(mailbox);
1230         // request all of the status items
1231         command.append(" (MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)");
1232 
1233         sendCommand(command);
1234 
1235         // now harvest each of the respon
1236         IMAPMailboxStatus status = new IMAPMailboxStatus();
1237         status.mergeSizeResponses(extractResponses("EXISTS")); 
1238         status.mergeSizeResponses(extractResponses("RECENT")); 
1239         status.mergeOkResponses(extractResponses("UIDNEXT")); 
1240         status.mergeOkResponses(extractResponses("UIDVALIDITY")); 
1241         status.mergeOkResponses(extractResponses("UNSEEN")); 
1242         status.mergeStatus((IMAPStatusResponse)extractResponse("STATUS")); 
1243         status.mergeStatus((IMAPPermanentFlagsResponse)extractResponse("PERMANENTFLAGS")); 
1244 
1245         return status;
1246     }
1247 
1248 
1249     /**
1250      * Select a mailbox, returning the accumulated status information
1251      * about the mailbox returned with the response.
1252      *
1253      * @param mailbox  The desired mailbox name.
1254      * @param readOnly The open mode.  If readOnly is true, the mailbox is opened
1255      *                 using EXAMINE rather than SELECT.
1256      *
1257      * @return A status object containing the mailbox particulars.
1258      * @exception MessagingException
1259      */
1260     public synchronized IMAPMailboxStatus openMailbox(String mailbox, boolean readOnly) throws MessagingException {
1261         IMAPCommand command = new IMAPCommand();
1262 
1263         // if readOnly is required, we use EXAMINE to switch to the mailbox rather than SELECT.
1264         // This returns the same response information, but the mailbox will not accept update operations.
1265         if (readOnly) {
1266             command.appendAtom("EXAMINE");
1267         }
1268         else {
1269             command.appendAtom("SELECT");
1270         }
1271 
1272         // construct the command, encoding the tokens as required by the content.
1273         command.appendEncodedString(mailbox);
1274 
1275         // issue the select
1276         IMAPTaggedResponse response = sendCommand(command);
1277 
1278         IMAPMailboxStatus status = new IMAPMailboxStatus(); 
1279         // set the mode to the requested open mode. 
1280         status.mode = readOnly ? Folder.READ_ONLY : Folder.READ_WRITE; 
1281         
1282         // the server might disagree on the mode, so check to see if 
1283         // it's telling us READ-ONLY.  
1284         if (response.hasStatus("READ-ONLY")) {
1285             status.mode = Folder.READ_ONLY; 
1286         }
1287         
1288         // some of these are required, some are optional. 
1289         status.mergeFlags((IMAPFlagsResponse)extractResponse("FLAGS")); 
1290         status.mergeStatus((IMAPSizeResponse)extractResponse("EXISTS")); 
1291         status.mergeStatus((IMAPSizeResponse)extractResponse("RECENT")); 
1292         status.mergeStatus((IMAPOkResponse)extractResponse("UIDVALIDITY")); 
1293         status.mergeStatus((IMAPOkResponse)extractResponse("UNSEEN")); 
1294         status.mergeStatus((IMAPPermanentFlagsResponse)extractResponse("PERMANENTFLAGS")); 
1295         // mine the response for status information about the selected mailbox.
1296         return status; 
1297     }
1298 
1299 
1300     /**
1301      * Tells the IMAP server to expunge messages marked for deletion.
1302      * The server will send us an untagged EXPUNGE message back for
1303      * each deleted message.  For explicit expunges we request, we'll
1304      * grabbed the untagged responses here, rather than force them to 
1305      * be handled as pending responses.  The caller will handle the 
1306      * updates directly. 
1307      *
1308      * @exception MessagingException
1309      */
1310     public synchronized List expungeMailbox() throws MessagingException {
1311         // send the message, and make sure we got an OK response 
1312         sendCommand("EXPUNGE");
1313         // extract all of the expunged responses and return. 
1314         return extractResponses("EXPUNGED"); 
1315     }
1316 
1317     public int[] searchMailbox(SearchTerm term) throws MessagingException {
1318         return searchMailbox("ALL", term);
1319     }
1320 
1321     /**
1322      * Send a search to the IMAP server using the specified
1323      * messages selector and search term.  This figures out what
1324      * to do with CHARSET on the SEARCH command.
1325      *
1326      * @param messages The list of messages (comma-separated numbers or "ALL").
1327      * @param term     The desired search criteria
1328      *
1329      * @return Returns an int[] array of message numbers for all matched messages.
1330      * @exception MessagingException
1331      */
1332     public int[] searchMailbox(String messages, SearchTerm term) throws MessagingException {
1333         // don't use a charset by default, but we need to look at the data to see if we have a problem.
1334         String charset = null;
1335 
1336         if (IMAPCommand.checkSearchEncoding(term)) {
1337             // not sure exactly how to decide what to use here.  Two immediate possibilities come to mind,
1338             // UTF-8 or the MimeUtility.getDefaultJavaCharset() value.  Running a small test against the
1339             // Sun impl shows them sending a CHARSET value of UTF-8, so that sounds like the winner.  I don't
1340             // believe there's anything in the CAPABILITY response that would tell us what to use.
1341             charset = "UTF-8";
1342         }
1343 
1344         return searchMailbox(messages, term, charset);
1345     }
1346 
1347     /**
1348      * Send a search to the IMAP server using the specified
1349      * messages selector and search term.
1350      *
1351      * @param messages The list of messages (comma-separated numbers or "ALL").
1352      * @param charset  The charset specifier to send to the server.  If null, then
1353      *                 the CHARSET keyword is omitted.
1354      * @param term     The desired search criteria
1355      *
1356      * @return Returns an int[] array of message numbers for all matched messages.
1357      * @exception MessagingException
1358      */
1359     public synchronized int[] searchMailbox(String messages, SearchTerm term, String charset) throws MessagingException {
1360         IMAPCommand command = new IMAPCommand("SEARCH");
1361 
1362         // if we have an explicit charset to use, append that.
1363         if (charset != null) {
1364             command.appendAtom("CHARSET");
1365             command.appendAtom(charset);
1366         }
1367 
1368         // now go through the process of translating the javamail SearchTerm objects into
1369         // the IMAP command sequence.  The SearchTerm sequence may be a complex tree of comparison terms,
1370         // so this is not a simple process.
1371         command.appendSearchTerm(term, charset);
1372         // need to append the message set 
1373         command.appendAtom(messages); 
1374 
1375         // now issue the composed command.
1376         sendCommand(command);
1377 
1378         // get the list of search responses 
1379         IMAPSearchResponse hits = (IMAPSearchResponse)extractResponse("SEARCH"); 
1380         // and return the message hits 
1381         return hits.messageNumbers; 
1382     }
1383 
1384 
1385     /**
1386      * Append a message to a mailbox, given the direct message data.
1387      *
1388      * @param mailbox The target mailbox name.
1389      * @param messageFlags
1390      *                The initial flag set for the appended message.
1391      * @param messageDate
1392      *                The received date the message is created with,
1393      * @param messageData
1394      *                The RFC822 Message data stored on the server.
1395      *
1396      * @exception MessagingException
1397      */
1398     public void appendMessage(String mailbox, Date messageDate, Flags messageFlags, byte[] messageData) throws MessagingException {
1399         IMAPCommand command = new IMAPCommand("APPEND");
1400 
1401         // the mailbox is encoded.
1402         command.appendEncodedString(mailbox);
1403 
1404         if (messageFlags != null) {
1405             // the flags are pulled from an existing object.  We can set most flag values, but the servers
1406             // reserve RECENT for themselves.  We need to force that one off.
1407             messageFlags.remove(Flags.Flag.RECENT);
1408             // and add the flag list to the commmand.
1409             command.appendFlags(messageFlags);
1410         }
1411 
1412         if (messageDate != null) {
1413             command.appendDate(messageDate);
1414         }
1415 
1416         // this gets appended as a literal.
1417         command.appendLiteral(messageData);
1418         // just send this as a simple command...we don't deal with the response other than to verifiy
1419         // it was ok.
1420         sendSimpleCommand(command);
1421     }
1422 
1423     /**
1424      * Fetch the flag set for a given message sequence number.
1425      * 
1426      * @param sequenceNumber
1427      *               The message sequence number.
1428      * 
1429      * @return The Flags defined for this message.
1430      * @exception MessagingException
1431      */
1432     public synchronized Flags fetchFlags(int sequenceNumber) throws MessagingException { 
1433         // we want just the flag item here.  
1434         sendCommand("FETCH " + String.valueOf(sequenceNumber) + " (FLAGS)");
1435         // get the return data item, and get the flags from within it
1436         IMAPFlags flags = (IMAPFlags)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.FLAGS);
1437         return flags.flags; 
1438     }
1439     
1440 
1441     /**
1442      * Set the flags for a range of messages.
1443      * 
1444      * @param messageSet The set of message numbers.
1445      * @param flags      The new flag settings.
1446      * @param set        true if the flags should be set, false for a clear operation.
1447      * 
1448      * @return A list containing all of the responses with the new flag values.
1449      * @exception MessagingException
1450      */
1451     public synchronized List setFlags(String messageSet, Flags flags, boolean set) throws MessagingException {
1452         IMAPCommand command = new IMAPCommand("STORE");
1453         command.appendAtom(messageSet);
1454         // the command varies depending on whether this is a set or clear operation
1455         if (set) {
1456             command.appendAtom("+FLAGS");
1457         }
1458         else {
1459             command.appendAtom("-FLAGS");
1460         }
1461 
1462         // append the flag set
1463         command.appendFlags(flags);
1464         
1465         // we want just the flag item here.  
1466         sendCommand(command); 
1467         // we should have a FETCH response for each of the updated messages.  Return this 
1468         // response, and update the message numbers. 
1469         return extractFetchDataItems(IMAPFetchDataItem.FLAGS);
1470     }
1471     
1472 
1473     /**
1474      * Set the flags for a single message.
1475      * 
1476      * @param sequenceNumber
1477      *               The sequence number of target message.
1478      * @param flags  The new flag settings.
1479      * @param set    true if the flags should be set, false for a clear operation.
1480      * 
1481      * @exception MessagingException
1482      */
1483     public synchronized Flags setFlags(int sequenceNumber, Flags flags, boolean set) throws MessagingException {
1484         IMAPCommand command = new IMAPCommand("STORE");
1485         command.appendInteger(sequenceNumber); 
1486         // the command varies depending on whether this is a set or clear operation
1487         if (set) {
1488             command.appendAtom("+FLAGS");
1489         }
1490         else {
1491             command.appendAtom("-FLAGS");
1492         }
1493 
1494         // append the flag set
1495         command.appendFlags(flags);
1496         
1497         // we want just the flag item here.  
1498         sendCommand(command); 
1499         // get the return data item, and get the flags from within it
1500         IMAPFlags flagResponse = (IMAPFlags)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.FLAGS);
1501         return flagResponse.flags; 
1502     }
1503 
1504 
1505     /**
1506      * Copy a range of messages to a target mailbox. 
1507      * 
1508      * @param messageSet The set of message numbers.
1509      * @param target     The target mailbox name.
1510      * 
1511      * @exception MessagingException
1512      */
1513     public void copyMessages(String messageSet, String target) throws MessagingException {
1514         IMAPCommand command = new IMAPCommand("COPY");
1515         // the auth command initiates the handshaking.
1516         command.appendAtom(messageSet);
1517         // the mailbox is encoded.
1518         command.appendEncodedString(target);
1519         // just send this as a simple command...we don't deal with the response other than to verifiy
1520         // it was ok.
1521         sendSimpleCommand(command);
1522     }
1523     
1524     
1525     /**
1526      * Fetch the message number for a give UID.
1527      * 
1528      * @param uid    The target UID
1529      * 
1530      * @return An IMAPUid object containing the mapping information.
1531      */
1532     public synchronized IMAPUid getSequenceNumberForUid(long uid) throws MessagingException {
1533         IMAPCommand command = new IMAPCommand("UID FETCH");
1534         command.appendLong(uid);
1535         command.appendAtom("(UID)"); 
1536 
1537         // this situation is a little strange, so it deserves a little explanation.  
1538         // We need the message sequence number for this message from a UID value.  
1539         // we're going to send a UID FETCH command, requesting the UID value back.
1540         // That seems strange, but the * nnnn FETCH response for the request will 
1541         // be tagged with the message sequence number.  THAT'S the information we 
1542         // really want, and it will be included in the IMAPUid object. 
1543 
1544         sendCommand(command);
1545         // ok, now we need to search through these looking for a FETCH response with a UID element.
1546         List responses = extractResponses("FETCH"); 
1547 
1548         // we're looking for a fetch response with a UID data item with the UID information 
1549         // inside of it. 
1550         for (int i = 0; i < responses.size(); i++) {
1551             IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 
1552             IMAPUid item = (IMAPUid)response.getDataItem(IMAPFetchDataItem.UID); 
1553             // is this the response we're looking for?  The information we 
1554             // need is the message number returned with the response, which is 
1555             // also contained in the UID item. 
1556             if (item != null && item.uid == uid) {
1557                 return item; 
1558             }
1559             // not one meant for us, add it back to the pending queue. 
1560             queuePendingResponse(response);
1561         }
1562         // didn't find this one 
1563         return null; 
1564     }
1565     
1566     
1567     /**
1568      * Fetch the message numbers for a consequetive range 
1569      * of UIDs.
1570      * 
1571      * @param start  The start of the range.
1572      * @param end    The end of the uid range.
1573      * 
1574      * @return A list of UID objects containing the mappings.  
1575      */
1576     public synchronized List getSequenceNumbersForUids(long start, long end) throws MessagingException {
1577         IMAPCommand command = new IMAPCommand("UID FETCH");
1578         // send the request for the range "start:end" so we can fetch all of the info 
1579         // at once. 
1580         command.appendLong(start);
1581         command.append(":"); 
1582         // not the special range marker?  Just append the 
1583         // number.  The LASTUID value needs to be "*" on the command. 
1584         if (end != UIDFolder.LASTUID) {
1585             command.appendLong(end);
1586         }
1587         else {
1588             command.append("*");
1589         }
1590         command.appendAtom("(UID)"); 
1591 
1592         // this situation is a little strange, so it deserves a little explanation.  
1593         // We need the message sequence number for this message from a UID value.  
1594         // we're going to send a UID FETCH command, requesting the UID value back.
1595         // That seems strange, but the * nnnn FETCH response for the request will 
1596         // be tagged with the message sequence number.  THAT'S the information we 
1597         // really want, and it will be included in the IMAPUid object. 
1598 
1599         sendCommand(command);
1600         // ok, now we need to search through these looking for a FETCH response with a UID element.
1601         List responses = extractResponses("FETCH"); 
1602 
1603         List uids = new ArrayList((int)(end - start + 1)); 
1604 
1605         // we're looking for a fetch response with a UID data item with the UID information 
1606         // inside of it. 
1607         for (int i = 0; i < responses.size(); i++) {
1608             IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 
1609             IMAPUid item = (IMAPUid)response.getDataItem(IMAPFetchDataItem.UID); 
1610             // is this the response we're looking for?  The information we 
1611             // need is the message number returned with the response, which is 
1612             // also contained in the UID item. 
1613             if (item != null) {
1614                 uids.add(item); 
1615             }
1616             else {
1617                 // not one meant for us, add it back to the pending queue. 
1618                 queuePendingResponse(response);
1619             }
1620         }
1621         // return the list of uids we located. 
1622         return uids; 
1623     }
1624     
1625     
1626     /**
1627      * Fetch the UID value for a target message number
1628      * 
1629      * @param sequenceNumber
1630      *               The target message number.
1631      * 
1632      * @return An IMAPUid object containing the mapping information.
1633      */
1634     public synchronized IMAPUid getUidForSequenceNumber(int sequenceNumber) throws MessagingException {
1635         IMAPCommand command = new IMAPCommand("FETCH");
1636         command.appendInteger(sequenceNumber); 
1637         command.appendAtom("(UID)"); 
1638 
1639         // similar to the other fetches, but without the strange bit.  We're starting 
1640         // with the message number in this case. 
1641 
1642         sendCommand(command);
1643         
1644         // ok, now we need to search through these looking for a FETCH response with a UID element.
1645         return (IMAPUid)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.UID); 
1646     }
1647     
1648     
1649     /**
1650      * Retrieve the user name space info from the server.
1651      * 
1652      * @return An IMAPNamespace response item with the information.  If the server 
1653      *         doesn't support the namespace extension, an empty one is returned.
1654      */
1655     public synchronized IMAPNamespaceResponse getNamespaces() throws MessagingException {
1656         // if no namespace capability, then return an empty 
1657         // response, which will trigger the default behavior. 
1658         if (!hasCapability("NAMESPACE")) {
1659             return new IMAPNamespaceResponse(); 
1660         }
1661         // no arguments on this command, so just send an hope it works. 
1662         sendCommand("NAMESPACE"); 
1663         
1664         // this should be here, since it's a required response when the  
1665         // command worked.  Just extract, and return. 
1666         return (IMAPNamespaceResponse)extractResponse("NAMESPACE"); 
1667     }
1668     
1669     
1670     /**
1671      * Prefetch message information based on the request profile.  We'll return
1672      * all of the fetch information to the requesting Folder, which will sort 
1673      * out what goes where. 
1674      * 
1675      * @param messageSet The set of message numbers we need to fetch.
1676      * @param profile    The profile of the required information.
1677      * 
1678      * @return All FETCH responses resulting from the command. 
1679      * @exception MessagingException
1680      */
1681     public synchronized List fetch(String messageSet, FetchProfile profile) throws MessagingException {
1682         IMAPCommand command = new IMAPCommand("FETCH");
1683         command.appendAtom(messageSet); 
1684         // this is the set of items to append           
1685         command.appendFetchProfile(profile); 
1686     
1687         // now send the fetch command, which will likely send back a lot of "FETCH" responses. 
1688         // Suck all of those reponses out of the queue and send them back for processing. 
1689         sendCommand(command); 
1690         // we can have a large number of messages here, so just grab all of the fetches 
1691         // we get back, and let the Folder sort out who gets what. 
1692         return extractResponses("FETCH"); 
1693     }
1694     
1695     
1696     /**
1697      * Set the ACL rights for a mailbox.  This replaces 
1698      * any existing ACLs defined.
1699      * 
1700      * @param mailbox The target mailbox.
1701      * @param acl     The new ACL to be used for the mailbox.
1702      * 
1703      * @exception MessagingException
1704      */
1705     public synchronized void setACLRights(String mailbox, ACL acl) throws MessagingException {
1706         IMAPCommand command = new IMAPCommand("SETACL");
1707         command.appendEncodedString(mailbox);
1708         
1709         command.appendACL(acl); 
1710         
1711         sendSimpleCommand(command); 
1712     }
1713     
1714     
1715     /**
1716      * Add a set of ACL rights to a mailbox.
1717      * 
1718      * @param mailbox The mailbox to alter.
1719      * @param acl     The ACL to add.
1720      * 
1721      * @exception MessagingException
1722      */
1723     public synchronized void addACLRights(String mailbox, ACL acl) throws MessagingException {
1724         if (!hasCapability("ACL")) {
1725             throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
1726         }
1727         IMAPCommand command = new IMAPCommand("SETACL");
1728         command.appendEncodedString(mailbox);
1729         
1730         command.appendACL(acl, "+"); 
1731         
1732         sendSimpleCommand(command); 
1733     }
1734     
1735     
1736     /**
1737      * Remove an ACL from a given mailbox.
1738      * 
1739      * @param mailbox The mailbox to alter.
1740      * @param acl     The particular ACL to revoke.
1741      * 
1742      * @exception MessagingException
1743      */
1744     public synchronized void removeACLRights(String mailbox, ACL acl) throws MessagingException {
1745         if (!hasCapability("ACL")) {
1746             throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
1747         }
1748         IMAPCommand command = new IMAPCommand("SETACL");
1749         command.appendEncodedString(mailbox);
1750         
1751         command.appendACL(acl, "-"); 
1752         
1753         sendSimpleCommand(command); 
1754     }
1755     
1756     
1757     /**
1758      * Get the ACL rights assigned to a given mailbox.
1759      * 
1760      * @param mailbox The target mailbox.
1761      * 
1762      * @return The an array of ACL items describing the access 
1763      *         rights to the mailbox.
1764      * @exception MessagingException
1765      */
1766     public synchronized ACL[] getACLRights(String mailbox) throws MessagingException {
1767         if (!hasCapability("ACL")) {
1768             throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
1769         }
1770         IMAPCommand command = new IMAPCommand("GETACL");
1771         command.appendEncodedString(mailbox);
1772     
1773         // now send the GETACL command, which will return a single ACL untagged response.      
1774         sendCommand(command); 
1775         // there should be just a single ACL response back from this command. 
1776         IMAPACLResponse response = (IMAPACLResponse)extractResponse("ACL"); 
1777         return response.acls; 
1778     }
1779     
1780     
1781     /**
1782      * Get the current user's ACL rights to a given mailbox. 
1783      * 
1784      * @param mailbox The target mailbox.
1785      * 
1786      * @return The Rights associated with this mailbox. 
1787      * @exception MessagingException
1788      */
1789     public synchronized Rights getMyRights(String mailbox) throws MessagingException {
1790         if (!hasCapability("ACL")) {
1791             throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
1792         }
1793         IMAPCommand command = new IMAPCommand("MYRIGHTS");
1794         command.appendEncodedString(mailbox);
1795     
1796         // now send the MYRIGHTS command, which will return a single MYRIGHTS untagged response.      
1797         sendCommand(command); 
1798         // there should be just a single MYRIGHTS response back from this command. 
1799         IMAPMyRightsResponse response = (IMAPMyRightsResponse)extractResponse("MYRIGHTS"); 
1800         return response.rights; 
1801     }
1802     
1803     
1804     /**
1805      * List the ACL rights that a particular user has 
1806      * to a mailbox.
1807      * 
1808      * @param mailbox The target mailbox.
1809      * @param name    The user we're querying.
1810      * 
1811      * @return An array of rights the use has to this mailbox. 
1812      * @exception MessagingException
1813      */
1814     public synchronized Rights[] listACLRights(String mailbox, String name) throws MessagingException {
1815         if (!hasCapability("ACL")) {
1816             throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
1817         }
1818         IMAPCommand command = new IMAPCommand("LISTRIGHTS");
1819         command.appendEncodedString(mailbox);
1820         command.appendString(name); 
1821     
1822         // now send the GETACL command, which will return a single ACL untagged response.      
1823         sendCommand(command); 
1824         // there should be just a single ACL response back from this command. 
1825         IMAPListRightsResponse response = (IMAPListRightsResponse)extractResponse("LISTRIGHTS"); 
1826         return response.rights; 
1827     }
1828     
1829     
1830     /**
1831      * Delete an ACL item for a given user name from 
1832      * a target mailbox. 
1833      * 
1834      * @param mailbox The mailbox we're altering.
1835      * @param name    The user name.
1836      * 
1837      * @exception MessagingException
1838      */
1839     public synchronized void deleteACL(String mailbox, String name) throws MessagingException {
1840         if (!hasCapability("ACL")) {
1841             throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
1842         }
1843         IMAPCommand command = new IMAPCommand("DELETEACL");
1844         command.appendEncodedString(mailbox);
1845         command.appendString(name); 
1846     
1847         // just send the command.  No response to handle. 
1848         sendSimpleCommand(command); 
1849     }
1850     
1851     /**
1852      * Fetch the quota root information for a target mailbox.
1853      * 
1854      * @param mailbox The mailbox of interest.
1855      * 
1856      * @return An array of quotas describing all of the quota roots
1857      *         that apply to the target mailbox.
1858      * @exception MessagingException
1859      */
1860     public synchronized Quota[] fetchQuotaRoot(String mailbox) throws MessagingException {
1861         if (!hasCapability("QUOTA")) {
1862             throw new MethodNotSupportedException("QUOTA not available from this IMAP server"); 
1863         }
1864         IMAPCommand command = new IMAPCommand("GETQUOTAROOT");
1865         command.appendEncodedString(mailbox);
1866     
1867         // This will return a single QUOTAROOT response, plust a series of QUOTA responses for 
1868         // each root names in the first response.  
1869         sendCommand(command); 
1870         // we don't really need this, but pull it from the response queue anyway. 
1871         extractResponse("QUOTAROOT"); 
1872         
1873         // now get the real meat of the matter 
1874         List responses = extractResponses("QUOTA"); 
1875         
1876         // now copy all of the returned quota items into the response array. 
1877         Quota[] quotas = new Quota[responses.size()]; 
1878         for (int i = 0; i < quotas.length; i++) {
1879             IMAPQuotaResponse q = (IMAPQuotaResponse)responses.get(i); 
1880             quotas[i] = q.quota; 
1881         }
1882         
1883         return quotas; 
1884     }
1885     
1886     /**
1887      * Fetch QUOTA information from a named QUOTE root.
1888      * 
1889      * @param root   The target root name.
1890      * 
1891      * @return An array of Quota items associated with that root name.
1892      * @exception MessagingException
1893      */
1894     public synchronized Quota[] fetchQuota(String root) throws MessagingException {
1895         if (!hasCapability("QUOTA")) {
1896             throw new MethodNotSupportedException("QUOTA not available from this IMAP server"); 
1897         }
1898         IMAPCommand command = new IMAPCommand("GETQUOTA");
1899         command.appendString(root);
1900     
1901         // This will return a single QUOTAROOT response, plust a series of QUOTA responses for 
1902         // each root names in the first response.  
1903         sendCommand(command); 
1904         
1905         // now get the real meat of the matter 
1906         List responses = extractResponses("QUOTA"); 
1907         
1908         // now copy all of the returned quota items into the response array. 
1909         Quota[] quotas = new Quota[responses.size()]; 
1910         for (int i = 0; i < quotas.length; i++) {
1911             IMAPQuotaResponse q = (IMAPQuotaResponse)responses.get(i); 
1912             quotas[i] = q.quota; 
1913         }
1914         
1915         return quotas; 
1916     }
1917     
1918     /**
1919      * Set a Quota item for the currently accessed 
1920      * userid/folder resource. 
1921      * 
1922      * @param quota  The new QUOTA information.
1923      * 
1924      * @exception MessagingException
1925      */
1926     public synchronized void setQuota(Quota quota) throws MessagingException {
1927         if (!hasCapability("QUOTA")) {
1928             throw new MethodNotSupportedException("QUOTA not available from this IMAP server"); 
1929         }
1930         IMAPCommand command = new IMAPCommand("GETQUOTA");
1931         // this gets appended as a list of resource values 
1932         command.appendQuota(quota); 
1933     
1934         // This will return a single QUOTAROOT response, plust a series of QUOTA responses for 
1935         // each root names in the first response.  
1936         sendCommand(command); 
1937         // we don't really need this, but pull it from the response queue anyway. 
1938         extractResponses("QUOTA"); 
1939     }
1940     
1941     
1942     /**
1943      * Test if this connection has a given capability. 
1944      * 
1945      * @param capability The capability name.
1946      * 
1947      * @return true if this capability is in the list, false for a mismatch. 
1948      */
1949     public boolean hasCapability(String capability) {
1950         if (capabilities == null) {
1951             return false; 
1952         }
1953         return capabilities.containsKey(capability); 
1954     }
1955     
1956     /**
1957      * Tag this connection as having been closed by the 
1958      * server.  This will not be returned to the 
1959      * connection pool. 
1960      */
1961     public void setClosed() {
1962         closed = true;
1963     }
1964     
1965     /**
1966      * Test if the connnection has been forcibly closed.
1967      * 
1968      * @return True if the server disconnected the connection.
1969      */
1970     public boolean isClosed() {
1971         return closed; 
1972     }
1973 }
1974