001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.geronimo.javamail.store.imap.connection;
019    
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.io.OutputStream;
023    import java.io.PrintStream;
024    import java.io.UnsupportedEncodingException;
025    import java.io.Writer;
026    import java.net.InetAddress;
027    import java.net.Socket;
028    import java.net.SocketException;
029    import java.net.UnknownHostException;
030    import java.util.ArrayList;
031    import java.util.Date;
032    import java.util.HashMap;
033    import java.util.Iterator;
034    import java.util.LinkedList;
035    import java.util.List;
036    import java.util.StringTokenizer;
037    
038    import javax.mail.Address;
039    import javax.mail.AuthenticationFailedException;
040    import javax.mail.FetchProfile; 
041    import javax.mail.Flags;
042    import javax.mail.Folder;
043    import javax.mail.Message;
044    import javax.mail.MessagingException;
045    import javax.mail.MethodNotSupportedException;
046    import javax.mail.Quota;
047    import javax.mail.Session;
048    import javax.mail.UIDFolder;
049    import javax.mail.URLName;
050    
051    import javax.mail.internet.InternetHeaders;
052    
053    import javax.mail.search.SearchTerm;
054    
055    import org.apache.geronimo.javamail.authentication.AuthenticatorFactory; 
056    import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
057    import org.apache.geronimo.javamail.authentication.LoginAuthenticator; 
058    import org.apache.geronimo.javamail.authentication.PlainAuthenticator; 
059    import org.apache.geronimo.javamail.store.imap.ACL;
060    import org.apache.geronimo.javamail.store.imap.Rights; 
061    
062    import org.apache.geronimo.javamail.util.CommandFailedException;      
063    import org.apache.geronimo.javamail.util.InvalidCommandException;      
064    import org.apache.geronimo.javamail.util.MailConnection; 
065    import org.apache.geronimo.javamail.util.ProtocolProperties; 
066    import org.apache.geronimo.javamail.util.TraceInputStream;
067    import org.apache.geronimo.javamail.util.TraceOutputStream;
068    import org.apache.geronimo.mail.util.Base64;
069    
070    /**
071     * Simple implementation of IMAP transport.  Just does plain RFC977-ish
072     * delivery.
073     * <p/>
074     * There is no way to indicate failure for a given recipient (it's possible to have a
075     * recipient address rejected).  The sun impl throws exceptions even if others successful),
076     * but maybe we do a different way...
077     * <p/>
078     *
079     * @version $Rev: 739377 $ $Date: 2009-01-30 13:57:39 -0500 (Fri, 30 Jan 2009) $
080     */
081    public class IMAPConnection extends MailConnection {
082        
083        protected static final String CAPABILITY_LOGIN_DISABLED = "LOGINDISABLED";
084    
085        // The connection pool we're a member of.  This keeps holds most of the 
086        // connnection parameter information for us. 
087        protected IMAPConnectionPool pool; 
088        
089        // special input stream for reading individual response lines.
090        protected IMAPResponseStream reader;
091    
092        // connection pool connections.
093        protected long lastAccess = 0;
094        // our handlers for any untagged responses
095        protected LinkedList responseHandlers = new LinkedList();
096        // the list of queued untagged responses.
097        protected List queuedResponses = new LinkedList();
098        // this is set on if we had a forced disconnect situation from 
099        // 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