geronimo-scm mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rickmcgu...@apache.org
Subject svn commit: r594520 [6/10] - in /geronimo/javamail/trunk/geronimo-javamail_1.4: ./ geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/ geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handl...
Date Tue, 13 Nov 2007 12:57:53 GMT
Added: geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java?rev=594520&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java Tue Nov 13 04:57:39 2007
@@ -0,0 +1,1978 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.geronimo.javamail.store.imap.connection;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.mail.Address;
+import javax.mail.AuthenticationFailedException;
+import javax.mail.FetchProfile; 
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.MethodNotSupportedException;
+import javax.mail.Quota;
+import javax.mail.Session;
+import javax.mail.UIDFolder;
+import javax.mail.URLName;
+
+import javax.mail.internet.InternetHeaders;
+
+import javax.mail.search.SearchTerm;
+
+import org.apache.geronimo.javamail.authentication.AuthenticatorFactory; 
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
+import org.apache.geronimo.javamail.authentication.LoginAuthenticator; 
+import org.apache.geronimo.javamail.authentication.PlainAuthenticator; 
+import org.apache.geronimo.javamail.store.imap.ACL;
+import org.apache.geronimo.javamail.store.imap.Rights; 
+
+import org.apache.geronimo.javamail.util.CommandFailedException;      
+import org.apache.geronimo.javamail.util.InvalidCommandException;      
+import org.apache.geronimo.javamail.util.MailConnection; 
+import org.apache.geronimo.javamail.util.ProtocolProperties; 
+import org.apache.geronimo.javamail.util.TraceInputStream;
+import org.apache.geronimo.javamail.util.TraceOutputStream;
+import org.apache.geronimo.mail.util.Base64;
+
+/**
+ * Simple implementation of IMAP transport.  Just does plain RFC977-ish
+ * delivery.
+ * <p/>
+ * There is no way to indicate failure for a given recipient (it's possible to have a
+ * recipient address rejected).  The sun impl throws exceptions even if others successful),
+ * but maybe we do a different way...
+ * <p/>
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPConnection extends MailConnection {
+    
+    protected static final String CAPABILITY_LOGIN_DISABLED = "LOGINDISABLED";
+
+    // The connection pool we're a member of.  This keeps holds most of the 
+    // connnection parameter information for us. 
+    protected IMAPConnectionPool pool; 
+    
+    // special input stream for reading individual response lines.
+    protected IMAPResponseStream reader;
+
+    // connection pool connections.
+    protected long lastAccess = 0;
+    // our handlers for any untagged responses
+    protected LinkedList responseHandlers = new LinkedList();
+    // the list of queued untagged responses.
+    protected List queuedResponses = new LinkedList();
+    // this is set on if we had a forced disconnect situation from 
+    // the server. 
+    protected boolean closed = false;
+
+    /**
+     * Normal constructor for an IMAPConnection() object.
+     *
+     * @param store    The store we're associated with (source of parameter values).
+     * @param host     The target host name of the IMAP server.
+     * @param port     The target listening port of the server.  Defaults to 119 if
+     *                 the port is specified as -1.
+     * @param username The login user name (can be null unless authentication is
+     *                 required).
+     * @param password Password associated with the userid account.  Can be null if
+     *                 authentication is not required.
+     * @param sslConnection
+     *                 True if this is targetted as an SSLConnection.
+     * @param debug    The session debug flag.
+     */
+    public IMAPConnection(ProtocolProperties props, IMAPConnectionPool pool) {
+        super(props);
+        this.pool = pool; 
+    }
+
+                          
+    /**
+     * Connect to the server and do the initial handshaking.
+     *
+     * @exception MessagingException
+     */
+    public boolean protocolConnect(String host, int port, String authid, String realm, String username, String password) throws MessagingException {
+        this.serverHost = host; 
+        this.serverPort = port; 
+        this.realm = realm; 
+        this.authid = authid; 
+        this.username = username; 
+        this.password = password; 
+        
+        boolean preAuthorized = false; 
+        
+        try {
+            // create socket and connect to server.
+            getConnection();
+
+            // we need to ask the server what its capabilities are.  This can be done 
+            // before we login.  
+            getCapability();
+            // do a preauthoriziation check. 
+            if (extractResponse("PREAUTH") != null) {
+                preAuthorized = true; 
+            }
+            
+            // make sure we process these now
+            processPendingResponses(); 
+
+            // if we're not already using an SSL connection, and we have permission to issue STARTTLS, AND
+            // the server supports this, then switch to TLS mode before continuing.
+            if (!sslConnection && props.getBooleanProperty(MAIL_STARTTLS_ENABLE, false) && hasCapability(CAPABILITY_STARTTLS)) {
+                // if the server supports TLS, then use it for the connection.
+                // on our connection.
+                
+                // tell the server of our intention to start a TLS session
+                sendSimpleCommand("STARTTLS");
+                
+                // The connection is then handled by the superclass level. 
+                getConnectedTLSSocket();
+                
+                // create the special reader for pulling the responses.
+                reader = new IMAPResponseStream(inputStream);
+
+                // the IMAP spec states that the capability response is independent of login state or   
+                // user, but I'm not sure I believe that to be the case.  It doesn't hurt to refresh 
+                // the information again after establishing a secure connection. 
+                getCapability();
+                // and we need to repeat this check. 
+                if (extractResponse("PREAUTH") != null) {
+                    preAuthorized = true; 
+                }
+            }
+            
+            // damn, no login required.  
+            if (preAuthorized) {
+                return true; 
+            }
+            
+            // go login with the server 
+            return login(); 
+        } catch (IOException e) {
+            if (debug) {
+                debugOut("I/O exception establishing connection", e);
+            }
+            throw new MessagingException("Connection error", e);
+        }
+        finally {
+            // make sure the queue is cleared 
+            processPendingResponses(); 
+        }
+    }
+
+    /**
+     * Update the last access time for the connection.
+     */
+    protected void updateLastAccess() {
+        lastAccess = System.currentTimeMillis();
+    }
+
+    /**
+     * Test if the connection has been sitting idle for longer than
+     * the set timeout period.
+     *
+     * @param timeout The allowed "freshness" interval.
+     *
+     * @return True if the connection has been active within the required
+     *         interval, false if it has been sitting idle for too long.
+     */
+    public boolean isStale(long timeout) {
+        return (System.currentTimeMillis() - lastAccess) > timeout;
+    }
+
+
+    /**
+     * Close the connection.  On completion, we'll be disconnected from
+     * the server and unable to send more data.
+     *
+     * @exception MessagingException
+     */
+    public void close() throws MessagingException {
+        // if we're already closed, get outta here.
+        if (socket == null) {
+            return;
+        }
+        try {
+            // say goodbye
+            logout();   
+        } finally {
+            // and close up the connection.  We do this in a finally block to make sure the connection
+            // is shut down even if quit gets an error.
+            closeServerConnection();
+            // get rid of our response processor too. 
+            reader = null; 
+        }
+    }
+
+
+    /**
+     * Create a transport connection object and connect it to the
+     * target server.
+     *
+     * @exception MessagingException
+     */
+    protected void getConnection() throws IOException, MessagingException
+    {
+        // do all of the non-protocol specific set up.  This will get our socket established 
+        // and ready use. 
+        super.getConnection(); 
+        // create the special reader for pulling the responses.
+        reader = new IMAPResponseStream(inputStream);
+
+        // set the initial access time stamp
+        updateLastAccess();
+    }
+
+
+    /**
+     * Process a simple command/response sequence between the
+     * client and the server.  These are commands where the
+     * client is expecting them to "just work", and also will not
+     * directly process the reply information.  Unsolicited untagged
+     * responses are dispatched to handlers, and a MessagingException
+     * will be thrown for any non-OK responses from the server.
+     *
+     * @param data   The command data we're writing out.
+     *
+     * @exception MessagingException
+     */
+    public void sendSimpleCommand(String data) throws MessagingException {
+        // create a command object and issue the command with that. 
+        IMAPCommand command = new IMAPCommand(data); 
+        sendSimpleCommand(command); 
+    }
+
+
+    /**
+     * Process a simple command/response sequence between the
+     * client and the server.  These are commands where the
+     * client is expecting them to "just work", and also will not
+     * directly process the reply information.  Unsolicited untagged
+     * responses are dispatched to handlers, and a MessagingException
+     * will be thrown for any non-OK responses from the server.
+     *
+     * @param data   The command data we're writing out.
+     *
+     * @exception MessagingException
+     */
+    public void sendSimpleCommand(IMAPCommand data) throws MessagingException {
+        // the command sending process will raise exceptions for bad responses....
+        // we just need to send the command and forget about it. 
+        sendCommand(data);
+    }
+
+
+    /**
+     * Sends a  command down the socket, returning the server response.
+     * 
+     * @param data   The String form of the command.
+     * 
+     * @return The tagged response information that terminates the command interaction.
+     * @exception MessagingException
+     */
+    public IMAPTaggedResponse sendCommand(String data) throws MessagingException {
+        IMAPCommand command = new IMAPCommand(data); 
+        return sendCommand(command); 
+    }
+
+
+    /**
+     * Sends a  command down the socket, returning the server response.
+     * 
+     * @param data   An IMAPCommand object with the prepared command information.
+     * 
+     * @return The tagged (or continuation) response information that terminates the 
+     *         command response sequence.
+     * @exception MessagingException
+     */
+    public synchronized IMAPTaggedResponse sendCommand(IMAPCommand data) throws MessagingException {
+        // check first 
+        checkConnected(); 
+        try {
+            // have the command write the command data.  This also prepends a tag. 
+            data.writeTo(outputStream, this);
+            outputStream.flush();
+            // update the activity timestamp
+            updateLastAccess();
+            // get the received response  
+            return receiveResponse(); 
+        } catch (IOException e) {
+            throw new MessagingException(e.toString(), e);
+        }
+    }
+    
+
+    /**
+     * Sends a  message down the socket and terminates with the
+     * appropriate CRLF
+     * 
+     * @param data   The string data to send.
+     * 
+     * @return An IMAPTaggedResponse item returned from the server.
+     * @exception MessagingException
+     */
+    public IMAPTaggedResponse sendLine(String data) throws MessagingException {
+        return sendLine(data.getBytes()); 
+    }
+    
+
+    /**
+     * Sends a  message down the socket and terminates with the
+     * appropriate CRLF
+     * 
+     * @param data   The array of data to send to the server.
+     * 
+     * @return The response item returned from the IMAP server.
+     * @exception MessagingException
+     */
+    public IMAPTaggedResponse sendLine(byte[] data) throws MessagingException {
+        return sendLine(data, 0, data.length); 
+    }
+    
+
+    /**
+     * Sends a  message down the socket and terminates with the
+     * appropriate CRLF
+     * 
+     * @param data   The source data array.
+     * @param offset The offset within the data array.
+     * @param length The length of data to send.
+     * 
+     * @return The response line returned from the IMAP server. 
+     * @exception MessagingException
+     */
+    public synchronized IMAPTaggedResponse sendLine(byte[] data, int offset, int length) throws MessagingException {
+        // check first 
+        checkConnected(); 
+        
+        try {
+            outputStream.write(data, offset, length);
+            outputStream.write(CR);
+            outputStream.write(LF);
+            outputStream.flush();
+            // update the activity timestamp
+            updateLastAccess();
+            return receiveResponse(); 
+        } catch (IOException e) {
+            throw new MessagingException(e.toString(), e);
+        }
+    }
+
+    
+    /**
+     * Get a reply line for an IMAP command.
+     *
+     * @return An IMAP reply object from the stream.
+     */
+    public IMAPTaggedResponse receiveResponse() throws MessagingException {
+        while (true) {
+            // read and parse a response from the server.
+            IMAPResponse response = reader.readResponse();
+            // The response set is terminated by either a continuation response or a  
+            // tagged response (we only have a single command active at one time). 
+            if (response instanceof IMAPTaggedResponse) {
+                // update the access time stamp for later timeout processing.
+                updateLastAccess(); 
+                IMAPTaggedResponse tagged = (IMAPTaggedResponse)response; 
+                // we turn these into exceptions here, which means the issuer doesn't have to 
+                // worry about checking status. 
+                if (tagged.isBAD()) {
+                    throw new InvalidCommandException("Unexpected command IMAP command error"); 
+                }
+                else if (tagged.isNO()) {
+                    throw new CommandFailedException("Unexpected error executing IMAP command"); 
+                }
+                return tagged;                       
+            }
+            else {
+                // all other unsolicited responses are either async status updates or 
+                // additional elements of a command we just sent.  These will be processed 
+                // either during processing of the command response, or at the end of the 
+                // current command processing. 
+                queuePendingResponse((IMAPUntaggedResponse)response); 
+            }
+        }
+    }
+
+    
+    /**
+     * Get the servers capabilities from the wire....
+     */
+    public void getCapability() throws MessagingException {
+        sendCommand("CAPABILITY");
+        // get the capabilities from the response.
+        IMAPCapabilityResponse response = (IMAPCapabilityResponse)extractResponse("CAPABILITY"); 
+        capabilities = response.getCapabilities(); 
+        authentications = response.getAuthentications(); 
+    }
+
+    /**
+     * Logs out from the server.                                     
+     */
+    public void logout() throws MessagingException {
+        // We can just send the command and generally ignore the 
+        // status response. 
+        sendCommand("LOGOUT");
+    }
+
+    /**
+     * Deselect a mailbox when a folder returns a connection.
+     * 
+     * @exception MessagingException
+     */
+    public void closeMailbox() throws MessagingException {
+        // We can just send the command and generally ignore the 
+        // status response. 
+        sendCommand("CLOSE");
+    }
+
+    
+    /**
+     * Authenticate with the server, if necessary (or possible).
+     * 
+     * @return true if we were able to authenticate correctly, false for authentication failures.
+     * @exception MessagingException
+     */
+    protected boolean login() throws MessagingException
+    {
+        // if no username or password, fail this immediately. 
+        // the base connect property should resolve a username/password combo for us and 
+        // try again. 
+        if (username == null || password == null) {
+            return false; 
+        }
+        
+        // are we permitted to use SASL mechanisms?
+        if (props.getBooleanProperty(MAIL_SASL_ENABLE, false)) {
+            // we might be enable for SASL, but the client and the server might
+            // not have any supported mechanisms in common.  Try again with another
+            // mechanism.
+            if (processSaslAuthentication()) {
+                return true;
+            }
+        }
+
+        // see if we're allowed to try plain.
+        if (!props.getBooleanProperty(MAIL_PLAIN_DISABLE, false) && supportsMechanism(AUTHENTICATION_PLAIN)) {
+            return processPlainAuthentication();
+        }
+
+        // see if we're allowed to try login.
+        if (!props.getBooleanProperty(MAIL_LOGIN_DISABLE, false) && supportsMechanism(AUTHENTICATION_LOGIN)) {
+            // no authzid capability with this authentication method.
+            return processLoginAuthentication();
+        }
+        
+        // the server can choose to disable the LOGIN command.  If not disabled, try 
+        // using LOGIN rather than AUTHENTICATE. 
+        if (!hasCapability(CAPABILITY_LOGIN_DISABLED)) {
+            return processLogin(); 
+        }
+        
+        throw new MessagingException("No supported LOGIN methods enabled"); 
+    }
+
+    
+    /**
+     * Process SASL-type authentication.
+     *
+     * @return Returns true if the server support a SASL authentication mechanism and
+     * accepted reponse challenges.
+     * @exception MessagingException
+     */
+    protected boolean processSaslAuthentication() throws MessagingException {
+        // if unable to get an appropriate authenticator, just fail it. 
+        ClientAuthenticator authenticator = getSaslAuthenticator(); 
+        if (authenticator == null) {
+            return false; 
+        }
+        
+        // go process the login.
+        return processLogin(authenticator);
+    }
+    
+    protected ClientAuthenticator getSaslAuthenticator() {
+        return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm); 
+    }
+
+    /**
+     * Process SASL-type PLAIN authentication.
+     *
+     * @return Returns true if the login is accepted. 
+     * @exception MessagingException
+     */
+    protected boolean processPlainAuthentication() throws MessagingException {
+        // go process the login.
+        return processLogin(new PlainAuthenticator(username, password));
+    }
+
+
+    /**
+     * Process SASL-type LOGIN authentication.
+     *
+     * @return Returns true if the login is accepted. 
+     * @exception MessagingException
+     */
+    protected boolean processLoginAuthentication() throws MessagingException {
+        // go process the login.
+        return processLogin(new LoginAuthenticator(username, password));
+    }
+    
+    
+    /**
+     * Process a LOGIN using the LOGIN command instead of AUTHENTICATE. 
+     * 
+     * @return true if the command succeeded, false for any authentication failures. 
+     * @exception MessagingException
+     */
+    protected boolean processLogin() throws MessagingException {
+        // arguments are "LOGIN userid password"
+        IMAPCommand command = new IMAPCommand("LOGIN");
+        command.appendAtom(username); 
+        command.appendAtom(password); 
+        
+        // go issue the command 
+        try {
+            sendCommand(command); 
+        } catch (CommandFailedException e) {
+            // we'll get a NO response for a rejected login
+            return false; 
+        }
+        // seemed to work ok....
+        return true;   
+    }
+
+
+    /**
+     * Process a login using the provided authenticator object.
+     * 
+     * NB:  This method is synchronized because we have a multi-step process going on 
+     * here.  No other commands should be sent to the server until we complete. 
+     *
+     * @return Returns true if the server support a SASL authentication mechanism and
+     * accepted reponse challenges.
+     * @exception MessagingException
+     */
+    protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
+        if (debug) {
+            debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
+        }
+
+        IMAPCommand command = new IMAPCommand("AUTHENTICATE");
+        // and tell the server which mechanism we're using.
+        command.appendAtom(authenticator.getMechanismName());
+        // send the command now
+        
+        try {
+            IMAPTaggedResponse response = sendCommand(command);
+
+            // now process the challenge sequence.  We get a 235 response back when the server accepts the
+            // authentication, and a 334 indicates we have an additional challenge.
+            while (true) {
+                // this should be a continuation reply, if things are still good.
+                if (response.isContinuation()) {
+                    // we're passed back a challenge value, Base64 encoded.
+                    byte[] challenge = response.decodeChallengeResponse();
+
+                    // have the authenticator evaluate and send back the encoded response.
+                    response = sendLine(Base64.encode(authenticator.evaluateChallenge(challenge)));
+                }
+                else {
+                    // there are only two choices here, OK or a continuation.  OK means 
+                    // we've passed muster and are in. 
+                    return true; 
+                }
+            }
+        } catch (CommandFailedException e ) {
+            // a failure at any point in this process will result in a "NO" response.  
+            // That causes an exception to get thrown, so just fail the login 
+            // if we get one. 
+            return false; 
+        }
+    }
+    
+
+    /**
+     * Return the server host for this connection.
+     *
+     * @return The String name of the server host.
+     */
+    public String getHost() {
+        return serverHost;
+    }
+
+    
+    /**
+     * Attach a handler for untagged responses to this connection.
+     *
+     * @param h      The new untagged response handler.
+     */
+    public synchronized void addResponseHandler(IMAPUntaggedResponseHandler h) {
+        responseHandlers.add(h);
+    }
+
+
+    /**
+     * Remove a response handler from the connection.
+     *
+     * @param h      The handler to remove.
+     */
+    public synchronized void removeResponseHandler(IMAPUntaggedResponseHandler h) {
+        responseHandlers.remove(h);
+    }
+
+
+    /**
+     * Add a response to the pending untagged response queue.
+     *
+     * @param response The response to add.
+     */
+    public synchronized void queuePendingResponse(IMAPUntaggedResponse response) {
+        queuedResponses.add(response);
+    }
+
+    /**
+     * Process any untagged responses in the queue.  This will clear out
+     * the queue, and send each response to the registered
+     * untagged response handlers.
+     */
+    public void processPendingResponses() throws MessagingException {
+        List pendingResponses = null;
+        List handlerList = null; 
+
+        synchronized(this) {
+            if (queuedResponses.isEmpty()) {
+                return;
+            }
+            pendingResponses = queuedResponses;
+            queuedResponses = new LinkedList();
+            // get a copy of the response handlers so we can 
+            // release the connection lock before broadcasting 
+            handlerList = (List)responseHandlers.clone(); 
+        }
+        
+        for (int i = 0; i < pendingResponses.size(); i++) {
+            IMAPUntaggedResponse response = (IMAPUntaggedResponse)pendingResponses.get(i); 
+            for (int j = 0; j < handlerList.size(); j++) {
+                // broadcast to each handler.  If a handler returns true, then it 
+                // handled whatever this message required and we should skip sending 
+                // it to other handlers. 
+                IMAPUntaggedResponseHandler h = (IMAPUntaggedResponseHandler)handlerList.get(j); 
+                if (h.handleResponse(response)) { 
+                    break; 
+                }
+            }
+        }
+    }
+    
+    /**
+     * Extract a single response from the pending queue that 
+     * match a give keyword type.  All matching responses 
+     * are removed from the pending queue. 
+     * 
+     * @param type   The string name of the keyword.
+     * 
+     * @return A List of all matching queued responses. 
+     */
+    public IMAPUntaggedResponse extractResponse(String type) {
+        Iterator i = queuedResponses.iterator(); 
+        while (i.hasNext()) {
+            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next(); 
+            // if this is of the target type, move it to the response set. 
+            if (response.isKeyword(type)) {
+                i.remove(); 
+                return response;
+            }
+        }
+        return null;  
+    }
+    
+    /**
+     * Extract all responses from the pending queue that 
+     * match a give keyword type.  All matching responses 
+     * are removed from the pending queue. 
+     * 
+     * @param type   The string name of the keyword.
+     * 
+     * @return A List of all matching queued responses. 
+     */
+    public List extractResponses(String type) {
+        List responses = new ArrayList(); 
+        
+        Iterator i = queuedResponses.iterator(); 
+        while (i.hasNext()) {
+            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next(); 
+            // if this is of the target type, move it to the response set. 
+            if (response.isKeyword(type)) {
+                i.remove(); 
+                responses.add(response); 
+            }
+        }
+        return responses; 
+    }
+    
+    
+    /**
+     * Extract all responses from the pending queue that 
+     * are "FETCH" responses for a given message number.  All matching responses 
+     * are removed from the pending queue. 
+     * 
+     * @param type   The string name of the keyword.
+     * 
+     * @return A List of all matching queued responses. 
+     */
+    public List extractFetchResponses(int sequenceNumber) {
+        List responses = new ArrayList(); 
+        
+        Iterator i = queuedResponses.iterator(); 
+        while (i.hasNext()) {
+            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next(); 
+            // if this is of the target type, move it to the response set. 
+            if (response.isKeyword("FETCH")) {
+                IMAPFetchResponse fetch = (IMAPFetchResponse)response; 
+                // a response for the correct message number?
+                if (fetch.sequenceNumber == sequenceNumber) {
+                    // pluck these from the list and add to the response set. 
+                    i.remove(); 
+                    responses.add(response); 
+                }
+            }
+        }
+        return responses; 
+    }
+    
+    /**
+     * Extract a fetch response data item from the queued elements. 
+     * 
+     * @param sequenceNumber
+     *               The message number we're interested in.  Fetch responses for other messages
+     *               will be skipped.
+     * @param type   The type of body element we need. It is assumed that only one item for
+     *               the given message number will exist in the queue.  The located item will
+     *               be returned, and that fetch response will be removed from the pending queue.
+     * 
+     * @return The target data item, or null if a match is not found.
+     */
+    protected IMAPFetchDataItem extractFetchDataItem(long sequenceNumber, int type) 
+    {
+        Iterator i = queuedResponses.iterator(); 
+        while (i.hasNext()) {
+            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next(); 
+            // if this is of the target type, move it to the response set. 
+            if (response.isKeyword("FETCH")) {
+                IMAPFetchResponse fetch = (IMAPFetchResponse)response; 
+                // a response for the correct message number?
+                if (fetch.sequenceNumber == sequenceNumber) {
+                    // does this response have the item we're looking for?
+                    IMAPFetchDataItem item = fetch.getDataItem(type); 
+                    if (item != null) {
+                        // remove this from the pending queue and return the 
+                        // located item
+                        i.remove(); 
+                        return item; 
+                    }
+                }
+            }
+        }
+        // not located, sorry 
+        return null;       
+    }
+    
+    /**
+     * Extract a all fetch responses that contain a given data item.  
+     * 
+     * @param type   The type of body element we need. It is assumed that only one item for
+     *               the given message number will exist in the queue.  The located item will
+     *               be returned, and that fetch response will be removed from the pending queue.
+     * 
+     * @return A List of all matching Fetch responses.                         
+     */
+    protected List extractFetchDataItems(int type) 
+    {
+        Iterator i = queuedResponses.iterator(); 
+        List items = new ArrayList(); 
+        
+        while (i.hasNext()) {
+            IMAPUntaggedResponse response = (IMAPUntaggedResponse)i.next(); 
+            // if this is of the target type, move it to the response set. 
+            if (response.isKeyword("FETCH")) {
+                IMAPFetchResponse fetch = (IMAPFetchResponse)response; 
+                // does this response have the item we're looking for?
+                IMAPFetchDataItem item = fetch.getDataItem(type); 
+                if (item != null) {
+                    // remove this from the pending queue and return the 
+                    // located item
+                    i.remove(); 
+                    // we want the fetch response, not the data item, because 
+                    // we're going to require the message sequence number information 
+                    // too. 
+                    items.add(fetch); 
+                }
+            }
+        }
+        // return whatever we have. 
+        return items;      
+    }
+
+    /**
+     * Make sure we have the latest status information available.  We
+     * retreive this by sending a NOOP command to the server, and
+     * processing any untagged responses we get back.
+     */
+    public void updateMailboxStatus() throws MessagingException {
+        sendSimpleCommand("NOOP");
+    }
+
+
+    /**
+     * check to see if this connection is truely alive.
+     * 
+     * @param timeout The timeout value to control how often we ping
+     *                the server to see if we're still good.
+     * 
+     * @return true if the server is responding to requests, false for any
+     *         connection errors.  This will also update the folder status
+     *         by processing returned unsolicited messages.
+     */
+    public synchronized boolean isAlive(long timeout) {
+        long lastUsed = System.currentTimeMillis() - lastAccess; 
+        if (lastUsed < timeout) {
+            return true; 
+        }
+        
+        try {
+            sendSimpleCommand("NOOP"); 
+            return true;
+        } catch (MessagingException e) {
+            // the NOOP command will throw a MessagingException if we get anything 
+            // other than an OK response back from the server.  
+        }
+        return false;
+    }
+
+
+    /**
+     * Issue a fetch command to retrieve the message ENVELOPE structure.
+     *
+     * @param sequenceNumber The sequence number of the message.
+     *
+     * @return The IMAPResponse item containing the ENVELOPE information.
+     */
+    public synchronized List fetchEnvelope(int sequenceNumber) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("FETCH");
+        command.appendInteger(sequenceNumber);
+        command.startList(); 
+        command.appendAtom("ENVELOPE INTERNALDATE RFC822.SIZE"); 
+        command.endList();   
+        
+        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
+        sendCommand(command);
+        // these are fairly involved sets, so the caller needs to handle these.
+        // we just return all of the FETCH results matching the target message number.  
+        return extractFetchResponses(sequenceNumber); 
+    }
+    
+    /**
+     * Issue a FETCH command to retrieve the message BODYSTRUCTURE structure.
+     *
+     * @param sequenceNumber The sequence number of the message.
+     *
+     * @return The IMAPBodyStructure item for the message.
+     *         All other untagged responses are queued for processing.
+     */
+    public synchronized IMAPBodyStructure fetchBodyStructure(int sequenceNumber) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("FETCH");
+        command.appendInteger(sequenceNumber);
+        command.startList(); 
+        command.appendAtom("BODYSTRUCTURE"); 
+        command.endList();   
+        
+        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
+        sendCommand(command);
+        // locate the response from this 
+        IMAPBodyStructure bodyStructure = (IMAPBodyStructure)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.BODYSTRUCTURE);
+
+        if (bodyStructure == null) {
+            throw new MessagingException("No BODYSTRUCTURE information received from IMAP server");
+        }
+        // and return the body structure directly.
+        return bodyStructure;
+    }
+
+
+    /**
+     * Issue a FETCH command to retrieve the message RFC822.HEADERS structure containing the message headers (using PEEK).
+     *
+     * @param sequenceNumber The sequence number of the message.
+     *
+     * @return The IMAPRFC822Headers item for the message.
+     *         All other untagged responses are queued for processing.
+     */
+    public synchronized InternetHeaders fetchHeaders(int sequenceNumber, String part) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("FETCH");
+        command.appendInteger(sequenceNumber);
+        command.startList(); 
+        command.appendAtom("BODY.PEEK"); 
+        command.appendBodySection(part, "HEADER"); 
+        command.endList();   
+        
+        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
+        sendCommand(command);
+        IMAPInternetHeader header = (IMAPInternetHeader)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.HEADER);
+
+        if (header == null) {
+            throw new MessagingException("No HEADER information received from IMAP server");
+        }
+        // and return the body structure directly.
+        return header.headers;
+    }
+
+
+    /**
+     * Issue a FETCH command to retrieve the message text
+     *
+     * @param sequenceNumber The sequence number of the message.
+     *
+     * @return The IMAPMessageText item for the message.
+     *         All other untagged responses are queued for processing.
+     */
+    public synchronized IMAPMessageText fetchText(int sequenceNumber) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("FETCH");
+        command.appendInteger(sequenceNumber);
+        command.startList(); 
+        command.appendAtom("BODY.PEEK"); 
+        command.appendBodySection("TEXT"); 
+        command.endList();   
+        
+        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
+        sendCommand(command);
+        IMAPMessageText text = (IMAPMessageText)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.TEXT);
+
+        if (text == null) {
+            throw new MessagingException("No TEXT information received from IMAP server");
+        }
+        // and return the body structure directly.
+        return text;
+    }
+
+
+    /**
+     * Issue a FETCH command to retrieve the message text
+     *
+     * @param sequenceNumber The sequence number of the message.
+     *
+     * @return The IMAPMessageText item for the message.
+     *         All other untagged responses are queued for processing.
+     */
+    public synchronized IMAPMessageText fetchBodyPartText(int sequenceNumber, String section) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("FETCH");
+        command.appendInteger(sequenceNumber);
+        command.startList(); 
+        command.appendAtom("BODY.PEEK"); 
+        command.appendBodySection(section, "TEXT"); 
+        command.endList();   
+        
+        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
+        sendCommand(command);
+        IMAPMessageText text = (IMAPMessageText)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.TEXT);
+
+        if (text == null) {
+            throw new MessagingException("No TEXT information received from IMAP server");
+        }
+        // and return the body structure directly.
+        return text;
+    }
+
+
+    /**
+     * Issue a FETCH command to retrieve the entire message body in one shot.
+     * This may also be used to fetch an embedded message part as a unit.
+     * 
+     * @param sequenceNumber
+     *                The sequence number of the message.
+     * @param section The section number to fetch.  If null, the entire body of the message
+     *                is retrieved.
+     * 
+     * @return The IMAPBody item for the message.
+     *         All other untagged responses are queued for processing.
+     * @exception MessagingException
+     */
+    public synchronized IMAPBody fetchBody(int sequenceNumber, String section) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("FETCH");
+        command.appendInteger(sequenceNumber);
+        command.startList(); 
+        command.appendAtom("BODY.PEEK"); 
+        // no part name here, only the section identifier.  This will fetch 
+        // the entire body, with all of the bits in place. 
+        command.appendBodySection(section, null); 
+        command.endList();   
+        
+        // we want all of the envelope information about the message, which involves multiple FETCH chunks.
+        sendCommand(command);
+        IMAPBody body = (IMAPBody)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.BODY);
+
+        if (body == null) {
+            throw new MessagingException("No BODY information received from IMAP server");
+        }
+        // and return the body structure directly.
+        return body;
+    }
+    
+    
+    /**
+     * Fetch the message content.  This sorts out which method should be used 
+     * based on the server capability.
+     * 
+     * @param sequenceNumber
+     *               The sequence number of the target message.
+     * 
+     * @return The byte[] content information.
+     * @exception MessagingException
+     */
+    public byte[] fetchContent(int sequenceNumber) throws MessagingException {
+        // fetch the text item and return the data 
+        IMAPMessageText text = fetchText(sequenceNumber);
+        return text.getContent();
+    }
+    
+    
+    /**
+     * Fetch the message content.  This sorts out which method should be used 
+     * based on the server capability.
+     * 
+     * @param sequenceNumber
+     *               The sequence number of the target message.
+     * 
+     * @return The byte[] content information.
+     * @exception MessagingException
+     */
+    public byte[] fetchContent(int sequenceNumber, String section) throws MessagingException {
+        // fetch the text item and return the data 
+        IMAPMessageText text = fetchBodyPartText(sequenceNumber, section);
+        return text.getContent();
+    }
+
+
+    /**
+     * Send an LIST command to the IMAP server, returning all LIST
+     * response information.
+     *
+     * @param mailbox The reference mailbox name sent on the command.
+     * @param pattern The match pattern used on the name.
+     *
+     * @return A List of all LIST response information sent back from the server.
+     */
+    public synchronized List list(String mailbox, String pattern) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("LIST");
+
+        // construct the command, encoding the tokens as required by the content.
+        command.appendEncodedString(mailbox);
+        command.appendEncodedString(pattern);
+
+        sendCommand(command);
+
+        // pull out the ones we're interested in 
+        return extractResponses("LIST"); 
+    }
+
+
+    /**
+     * Send an LSUB command to the IMAP server, returning all LSUB
+     * response information.
+     *
+     * @param mailbox The reference mailbox name sent on the command.
+     * @param pattern The match pattern used on the name.
+     *
+     * @return A List of all LSUB response information sent back from the server.
+     */
+    public List listSubscribed(String mailbox, String pattern) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("LSUB");
+
+        // construct the command, encoding the tokens as required by the content.
+        command.appendEncodedString(mailbox);
+        command.appendEncodedString(pattern);
+
+        sendCommand(command);
+        // pull out the ones we're interested in 
+        return extractResponses("LSUB"); 
+    }
+
+
+    /**
+     * Subscribe to a give mailbox.
+     *
+     * @param mailbox The desired mailbox name.
+     *
+     * @exception MessagingException
+     */
+    public void subscribe(String mailbox) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("SUBSCRIBE");
+        // add on the encoded mailbox name, as the appropriate token type.
+        command.appendEncodedString(mailbox);
+
+        // send this, and ignore the response.
+        sendSimpleCommand(command);
+    }
+
+
+    /**
+     * Unsubscribe from a mailbox.
+     *
+     * @param mailbox The mailbox to remove.
+     *
+     * @exception MessagingException
+     */
+    public void unsubscribe(String mailbox) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("UNSUBSCRIBE");
+        // add on the encoded mailbox name, as the appropriate token type.
+        command.appendEncodedString(mailbox);
+
+        // send this, and ignore the response.
+        sendSimpleCommand(command);
+    }
+
+
+    /**
+     * Create a mailbox.
+     *
+     * @param mailbox The desired new mailbox name (fully qualified);
+     *
+     * @exception MessagingException
+     */
+    public void createMailbox(String mailbox) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("CREATE");
+        // add on the encoded mailbox name, as the appropriate token type.
+        command.appendEncodedString(mailbox);
+
+        // send this, and ignore the response.
+        sendSimpleCommand(command);
+    }
+
+
+    /**
+     * Delete a mailbox.
+     *
+     * @param mailbox The target mailbox name (fully qualified);
+     *
+     * @exception MessagingException
+     */
+    public void deleteMailbox(String mailbox) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("DELETE");
+        // add on the encoded mailbox name, as the appropriate token type.
+        command.appendEncodedString(mailbox);
+
+        // send this, and ignore the response.
+        sendSimpleCommand(command);
+    }
+
+
+    /**
+     * Rename a mailbox.
+     *
+     * @param mailbox The target mailbox name (fully qualified);
+     *
+     * @exception MessagingException
+     */
+    public void renameMailbox(String oldName, String newName) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("RENAME");
+        // add on the encoded mailbox name, as the appropriate token type.
+        command.appendEncodedString(oldName);
+        command.appendEncodedString(newName);
+
+        // send this, and ignore the response.
+        sendSimpleCommand(command);
+    }
+
+
+    /**
+     * Retrieve a complete set of status items for a mailbox.
+     *
+     * @param mailbox The mailbox name.
+     *
+     * @return An IMAPMailboxStatus item filled in with the STATUS responses.
+     * @exception MessagingException
+     */
+    public synchronized IMAPMailboxStatus getMailboxStatus(String mailbox) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("STATUS");
+
+        // construct the command, encoding the tokens as required by the content.
+        command.appendEncodedString(mailbox);
+        // request all of the status items
+        command.append(" (MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)");
+
+        sendCommand(command);
+
+        // now harvest each of the respon
+        IMAPMailboxStatus status = new IMAPMailboxStatus();
+        status.mergeSizeResponses(extractResponses("EXISTS")); 
+        status.mergeSizeResponses(extractResponses("RECENT")); 
+        status.mergeOkResponses(extractResponses("UIDNEXT")); 
+        status.mergeOkResponses(extractResponses("UIDVALIDITY")); 
+        status.mergeOkResponses(extractResponses("UNSEEN")); 
+        status.mergeStatus((IMAPStatusResponse)extractResponse("STATUS")); 
+        status.mergeStatus((IMAPPermanentFlagsResponse)extractResponse("PERMANENTFLAGS")); 
+
+        return status;
+    }
+
+
+    /**
+     * Select a mailbox, returning the accumulated status information
+     * about the mailbox returned with the response.
+     *
+     * @param mailbox  The desired mailbox name.
+     * @param readOnly The open mode.  If readOnly is true, the mailbox is opened
+     *                 using EXAMINE rather than SELECT.
+     *
+     * @return A status object containing the mailbox particulars.
+     * @exception MessagingException
+     */
+    public synchronized IMAPMailboxStatus openMailbox(String mailbox, boolean readOnly) throws MessagingException {
+        IMAPCommand command = new IMAPCommand();
+
+        // if readOnly is required, we use EXAMINE to switch to the mailbox rather than SELECT.
+        // This returns the same response information, but the mailbox will not accept update operations.
+        if (readOnly) {
+            command.appendAtom("EXAMINE");
+        }
+        else {
+            command.appendAtom("SELECT");
+        }
+
+        // construct the command, encoding the tokens as required by the content.
+        command.appendEncodedString(mailbox);
+
+        // issue the select
+        IMAPTaggedResponse response = sendCommand(command);
+
+        IMAPMailboxStatus status = new IMAPMailboxStatus(); 
+        // set the mode to the requested open mode. 
+        status.mode = readOnly ? Folder.READ_ONLY : Folder.READ_WRITE; 
+        
+        // the server might disagree on the mode, so check to see if 
+        // it's telling us READ-ONLY.  
+        if (response.hasStatus("READ-ONLY")) {
+            status.mode = Folder.READ_ONLY; 
+        }
+        
+        // some of these are required, some are optional. 
+        status.mergeFlags((IMAPFlagsResponse)extractResponse("FLAGS")); 
+        status.mergeStatus((IMAPSizeResponse)extractResponse("EXISTS")); 
+        status.mergeStatus((IMAPSizeResponse)extractResponse("RECENT")); 
+        status.mergeStatus((IMAPOkResponse)extractResponse("UIDVALIDITY")); 
+        status.mergeStatus((IMAPOkResponse)extractResponse("UNSEEN")); 
+        status.mergeStatus((IMAPPermanentFlagsResponse)extractResponse("PERMANENTFLAGS")); 
+        // mine the response for status information about the selected mailbox.
+        return status; 
+    }
+
+
+    /**
+     * Tells the IMAP server to expunge messages marked for deletion.
+     * The server will send us an untagged EXPUNGE message back for
+     * each deleted message.  For explicit expunges we request, we'll
+     * grabbed the untagged responses here, rather than force them to 
+     * be handled as pending responses.  The caller will handle the 
+     * updates directly. 
+     *
+     * @exception MessagingException
+     */
+    public synchronized List expungeMailbox() throws MessagingException {
+        // send the message, and make sure we got an OK response 
+        sendCommand("EXPUNGE");
+        // extract all of the expunged responses and return. 
+        return extractResponses("EXPUNGED"); 
+    }
+
+    public int[] searchMailbox(SearchTerm term) throws MessagingException {
+        return searchMailbox("ALL", term);
+    }
+
+    /**
+     * Send a search to the IMAP server using the specified
+     * messages selector and search term.  This figures out what
+     * to do with CHARSET on the SEARCH command.
+     *
+     * @param messages The list of messages (comma-separated numbers or "ALL").
+     * @param term     The desired search criteria
+     *
+     * @return Returns an int[] array of message numbers for all matched messages.
+     * @exception MessagingException
+     */
+    public int[] searchMailbox(String messages, SearchTerm term) throws MessagingException {
+        // don't use a charset by default, but we need to look at the data to see if we have a problem.
+        String charset = null;
+
+        if (IMAPCommand.checkSearchEncoding(term)) {
+            // not sure exactly how to decide what to use here.  Two immediate possibilities come to mind,
+            // UTF-8 or the MimeUtility.getDefaultJavaCharset() value.  Running a small test against the
+            // Sun impl shows them sending a CHARSET value of UTF-8, so that sounds like the winner.  I don't
+            // believe there's anything in the CAPABILITY response that would tell us what to use.
+            charset = "UTF-8";
+        }
+
+        return searchMailbox(messages, term, charset);
+    }
+
+    /**
+     * Send a search to the IMAP server using the specified
+     * messages selector and search term.
+     *
+     * @param messages The list of messages (comma-separated numbers or "ALL").
+     * @param charset  The charset specifier to send to the server.  If null, then
+     *                 the CHARSET keyword is omitted.
+     * @param term     The desired search criteria
+     *
+     * @return Returns an int[] array of message numbers for all matched messages.
+     * @exception MessagingException
+     */
+    public synchronized int[] searchMailbox(String messages, SearchTerm term, String charset) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("SEARCH");
+
+        // if we have an explicit charset to use, append that.
+        if (charset != null) {
+            command.appendAtom("CHARSET");
+            command.appendAtom(charset);
+        }
+
+        // now go through the process of translating the javamail SearchTerm objects into
+        // the IMAP command sequence.  The SearchTerm sequence may be a complex tree of comparison terms,
+        // so this is not a simple process.
+        command.appendSearchTerm(term, charset);
+        // need to append the message set 
+        command.appendAtom(messages); 
+
+        // now issue the composed command.
+        sendCommand(command);
+
+        // get the list of search responses 
+        IMAPSearchResponse hits = (IMAPSearchResponse)extractResponse("SEARCH"); 
+        // and return the message hits 
+        return hits.messageNumbers; 
+    }
+
+
+    /**
+     * Append a message to a mailbox, given the direct message data.
+     *
+     * @param mailbox The target mailbox name.
+     * @param messageFlags
+     *                The initial flag set for the appended message.
+     * @param messageDate
+     *                The received date the message is created with,
+     * @param messageData
+     *                The RFC822 Message data stored on the server.
+     *
+     * @exception MessagingException
+     */
+    public void appendMessage(String mailbox, Date messageDate, Flags messageFlags, byte[] messageData) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("APPEND");
+
+        // the mailbox is encoded.
+        command.appendEncodedString(mailbox);
+
+        if (messageFlags != null) {
+            // the flags are pulled from an existing object.  We can set most flag values, but the servers
+            // reserve RECENT for themselves.  We need to force that one off.
+            messageFlags.remove(Flags.Flag.RECENT);
+            // and add the flag list to the commmand.
+            command.appendFlags(messageFlags);
+        }
+
+        if (messageDate != null) {
+            command.appendDate(messageDate);
+        }
+
+        // this gets appended as a literal.
+        command.appendLiteral(messageData);
+        // just send this as a simple command...we don't deal with the response other than to verifiy
+        // it was ok.
+        sendSimpleCommand(command);
+    }
+
+    /**
+     * Fetch the flag set for a given message sequence number.
+     * 
+     * @param sequenceNumber
+     *               The message sequence number.
+     * 
+     * @return The Flags defined for this message.
+     * @exception MessagingException
+     */
+    public synchronized Flags fetchFlags(int sequenceNumber) throws MessagingException { 
+        // we want just the flag item here.  
+        sendCommand("FETCH " + String.valueOf(sequenceNumber) + " (FLAGS)");
+        // get the return data item, and get the flags from within it
+        IMAPFlags flags = (IMAPFlags)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.FLAGS);
+        return flags.flags; 
+    }
+    
+
+    /**
+     * Set the flags for a range of messages.
+     * 
+     * @param messageSet The set of message numbers.
+     * @param flags      The new flag settings.
+     * @param set        true if the flags should be set, false for a clear operation.
+     * 
+     * @return A list containing all of the responses with the new flag values.
+     * @exception MessagingException
+     */
+    public synchronized List setFlags(String messageSet, Flags flags, boolean set) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("STORE");
+        command.appendAtom(messageSet);
+        // the command varies depending on whether this is a set or clear operation
+        if (set) {
+            command.appendAtom("+FLAGS");
+        }
+        else {
+            command.appendAtom("-FLAGS");
+        }
+
+        // append the flag set
+        command.appendFlags(flags);
+        
+        // we want just the flag item here.  
+        sendCommand(command); 
+        // we should have a FETCH response for each of the updated messages.  Return this 
+        // response, and update the message numbers. 
+        return extractFetchDataItems(IMAPFetchDataItem.FLAGS);
+    }
+    
+
+    /**
+     * Set the flags for a single message.
+     * 
+     * @param sequenceNumber
+     *               The sequence number of target message.
+     * @param flags  The new flag settings.
+     * @param set    true if the flags should be set, false for a clear operation.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized Flags setFlags(int sequenceNumber, Flags flags, boolean set) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("STORE");
+        command.appendInteger(sequenceNumber); 
+        // the command varies depending on whether this is a set or clear operation
+        if (set) {
+            command.appendAtom("+FLAGS");
+        }
+        else {
+            command.appendAtom("-FLAGS");
+        }
+
+        // append the flag set
+        command.appendFlags(flags);
+        
+        // we want just the flag item here.  
+        sendCommand(command); 
+        // get the return data item, and get the flags from within it
+        IMAPFlags flagResponse = (IMAPFlags)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.FLAGS);
+        return flagResponse.flags; 
+    }
+
+
+    /**
+     * Copy a range of messages to a target mailbox. 
+     * 
+     * @param messageSet The set of message numbers.
+     * @param target     The target mailbox name.
+     * 
+     * @exception MessagingException
+     */
+    public void copyMessages(String messageSet, String target) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("COPY");
+        // the auth command initiates the handshaking.
+        command.appendAtom(messageSet);
+        // the mailbox is encoded.
+        command.appendEncodedString(target);
+        // just send this as a simple command...we don't deal with the response other than to verifiy
+        // it was ok.
+        sendSimpleCommand(command);
+    }
+    
+    
+    /**
+     * Fetch the message number for a give UID.
+     * 
+     * @param uid    The target UID
+     * 
+     * @return An IMAPUid object containing the mapping information.
+     */
+    public synchronized IMAPUid getSequenceNumberForUid(long uid) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("UID FETCH");
+        command.appendLong(uid);
+        command.appendAtom("(UID)"); 
+
+        // this situation is a little strange, so it deserves a little explanation.  
+        // We need the message sequence number for this message from a UID value.  
+        // we're going to send a UID FETCH command, requesting the UID value back.
+        // That seems strange, but the * nnnn FETCH response for the request will 
+        // be tagged with the message sequence number.  THAT'S the information we 
+        // really want, and it will be included in the IMAPUid object. 
+
+        sendCommand(command);
+        // ok, now we need to search through these looking for a FETCH response with a UID element.
+        List responses = extractResponses("FETCH"); 
+
+        // we're looking for a fetch response with a UID data item with the UID information 
+        // inside of it. 
+        for (int i = 0; i < responses.size(); i++) {
+            IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 
+            IMAPUid item = (IMAPUid)response.getDataItem(IMAPFetchDataItem.UID); 
+            // is this the response we're looking for?  The information we 
+            // need is the message number returned with the response, which is 
+            // also contained in the UID item. 
+            if (item != null && item.uid == uid) {
+                return item; 
+            }
+            // not one meant for us, add it back to the pending queue. 
+            queuePendingResponse(response);
+        }
+        // didn't find this one 
+        return null; 
+    }
+    
+    
+    /**
+     * Fetch the message numbers for a consequetive range 
+     * of UIDs.
+     * 
+     * @param start  The start of the range.
+     * @param end    The end of the uid range.
+     * 
+     * @return A list of UID objects containing the mappings.  
+     */
+    public synchronized List getSequenceNumbersForUids(long start, long end) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("UID FETCH");
+        // send the request for the range "start:end" so we can fetch all of the info 
+        // at once. 
+        command.appendLong(start);
+        command.append(":"); 
+        // not the special range marker?  Just append the 
+        // number.  The LASTUID value needs to be "*" on the command. 
+        if (end != UIDFolder.LASTUID) {
+            command.appendLong(end);
+        }
+        else {
+            command.append("*");
+        }
+        command.appendAtom("(UID)"); 
+
+        // this situation is a little strange, so it deserves a little explanation.  
+        // We need the message sequence number for this message from a UID value.  
+        // we're going to send a UID FETCH command, requesting the UID value back.
+        // That seems strange, but the * nnnn FETCH response for the request will 
+        // be tagged with the message sequence number.  THAT'S the information we 
+        // really want, and it will be included in the IMAPUid object. 
+
+        sendCommand(command);
+        // ok, now we need to search through these looking for a FETCH response with a UID element.
+        List responses = extractResponses("FETCH"); 
+
+        List uids = new ArrayList((int)(end - start + 1)); 
+
+        // we're looking for a fetch response with a UID data item with the UID information 
+        // inside of it. 
+        for (int i = 0; i < responses.size(); i++) {
+            IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 
+            IMAPUid item = (IMAPUid)response.getDataItem(IMAPFetchDataItem.UID); 
+            // is this the response we're looking for?  The information we 
+            // need is the message number returned with the response, which is 
+            // also contained in the UID item. 
+            if (item != null) {
+                uids.add(item); 
+            }
+            else {
+                // not one meant for us, add it back to the pending queue. 
+                queuePendingResponse(response);
+            }
+        }
+        // return the list of uids we located. 
+        return uids; 
+    }
+    
+    
+    /**
+     * Fetch the UID value for a target message number
+     * 
+     * @param sequenceNumber
+     *               The target message number.
+     * 
+     * @return An IMAPUid object containing the mapping information.
+     */
+    public synchronized IMAPUid getUidForSequenceNumber(int sequenceNumber) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("FETCH");
+        command.appendInteger(sequenceNumber); 
+        command.appendAtom("(UID)"); 
+
+        // similar to the other fetches, but without the strange bit.  We're starting 
+        // with the message number in this case. 
+
+        sendCommand(command);
+        
+        // ok, now we need to search through these looking for a FETCH response with a UID element.
+        return (IMAPUid)extractFetchDataItem(sequenceNumber, IMAPFetchDataItem.UID); 
+    }
+    
+    
+    /**
+     * Retrieve the user name space info from the server.
+     * 
+     * @return An IMAPNamespace response item with the information.  If the server 
+     *         doesn't support the namespace extension, an empty one is returned.
+     */
+    public synchronized IMAPNamespaceResponse getNamespaces() throws MessagingException {
+        // if no namespace capability, then return an empty 
+        // response, which will trigger the default behavior. 
+        if (!hasCapability("NAMESPACE")) {
+            return new IMAPNamespaceResponse(); 
+        }
+        // no arguments on this command, so just send an hope it works. 
+        sendCommand("NAMESPACE"); 
+        
+        // this should be here, since it's a required response when the  
+        // command worked.  Just extract, and return. 
+        return (IMAPNamespaceResponse)extractResponse("NAMESPACE"); 
+    }
+    
+    
+    /**
+     * Prefetch message information based on the request profile.  We'll return
+     * all of the fetch information to the requesting Folder, which will sort 
+     * out what goes where. 
+     * 
+     * @param messageSet The set of message numbers we need to fetch.
+     * @param profile    The profile of the required information.
+     * 
+     * @return All FETCH responses resulting from the command. 
+     * @exception MessagingException
+     */
+    public synchronized List fetch(String messageSet, FetchProfile profile) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("FETCH");
+        command.appendAtom(messageSet); 
+        // this is the set of items to append           
+        command.appendFetchProfile(profile); 
+    
+        // now send the fetch command, which will likely send back a lot of "FETCH" responses. 
+        // Suck all of those reponses out of the queue and send them back for processing. 
+        sendCommand(command); 
+        // we can have a large number of messages here, so just grab all of the fetches 
+        // we get back, and let the Folder sort out who gets what. 
+        return extractResponses("FETCH"); 
+    }
+    
+    
+    /**
+     * Set the ACL rights for a mailbox.  This replaces 
+     * any existing ACLs defined.
+     * 
+     * @param mailbox The target mailbox.
+     * @param acl     The new ACL to be used for the mailbox.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void setACLRights(String mailbox, ACL acl) throws MessagingException {
+        IMAPCommand command = new IMAPCommand("SETACL");
+        command.appendEncodedString(mailbox);
+        
+        command.appendACL(acl); 
+        
+        sendSimpleCommand(command); 
+    }
+    
+    
+    /**
+     * Add a set of ACL rights to a mailbox.
+     * 
+     * @param mailbox The mailbox to alter.
+     * @param acl     The ACL to add.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void addACLRights(String mailbox, ACL acl) throws MessagingException {
+        if (!hasCapability("ACL")) {
+            throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
+        }
+        IMAPCommand command = new IMAPCommand("SETACL");
+        command.appendEncodedString(mailbox);
+        
+        command.appendACL(acl, "+"); 
+        
+        sendSimpleCommand(command); 
+    }
+    
+    
+    /**
+     * Remove an ACL from a given mailbox.
+     * 
+     * @param mailbox The mailbox to alter.
+     * @param acl     The particular ACL to revoke.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void removeACLRights(String mailbox, ACL acl) throws MessagingException {
+        if (!hasCapability("ACL")) {
+            throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
+        }
+        IMAPCommand command = new IMAPCommand("SETACL");
+        command.appendEncodedString(mailbox);
+        
+        command.appendACL(acl, "-"); 
+        
+        sendSimpleCommand(command); 
+    }
+    
+    
+    /**
+     * Get the ACL rights assigned to a given mailbox.
+     * 
+     * @param mailbox The target mailbox.
+     * 
+     * @return The an array of ACL items describing the access 
+     *         rights to the mailbox.
+     * @exception MessagingException
+     */
+    public synchronized ACL[] getACLRights(String mailbox) throws MessagingException {
+        if (!hasCapability("ACL")) {
+            throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
+        }
+        IMAPCommand command = new IMAPCommand("GETACL");
+        command.appendEncodedString(mailbox);
+    
+        // now send the GETACL command, which will return a single ACL untagged response.      
+        sendCommand(command); 
+        // there should be just a single ACL response back from this command. 
+        IMAPACLResponse response = (IMAPACLResponse)extractResponse("ACL"); 
+        return response.acls; 
+    }
+    
+    
+    /**
+     * Get the current user's ACL rights to a given mailbox. 
+     * 
+     * @param mailbox The target mailbox.
+     * 
+     * @return The Rights associated with this mailbox. 
+     * @exception MessagingException
+     */
+    public synchronized Rights getMyRights(String mailbox) throws MessagingException {
+        if (!hasCapability("ACL")) {
+            throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
+        }
+        IMAPCommand command = new IMAPCommand("MYRIGHTS");
+        command.appendEncodedString(mailbox);
+    
+        // now send the MYRIGHTS command, which will return a single MYRIGHTS untagged response.      
+        sendCommand(command); 
+        // there should be just a single MYRIGHTS response back from this command. 
+        IMAPMyRightsResponse response = (IMAPMyRightsResponse)extractResponse("MYRIGHTS"); 
+        return response.rights; 
+    }
+    
+    
+    /**
+     * List the ACL rights that a particular user has 
+     * to a mailbox.
+     * 
+     * @param mailbox The target mailbox.
+     * @param name    The user we're querying.
+     * 
+     * @return An array of rights the use has to this mailbox. 
+     * @exception MessagingException
+     */
+    public synchronized Rights[] listACLRights(String mailbox, String name) throws MessagingException {
+        if (!hasCapability("ACL")) {
+            throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
+        }
+        IMAPCommand command = new IMAPCommand("LISTRIGHTS");
+        command.appendEncodedString(mailbox);
+        command.appendString(name); 
+    
+        // now send the GETACL command, which will return a single ACL untagged response.      
+        sendCommand(command); 
+        // there should be just a single ACL response back from this command. 
+        IMAPListRightsResponse response = (IMAPListRightsResponse)extractResponse("LISTRIGHTS"); 
+        return response.rights; 
+    }
+    
+    
+    /**
+     * Delete an ACL item for a given user name from 
+     * a target mailbox. 
+     * 
+     * @param mailbox The mailbox we're altering.
+     * @param name    The user name.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void deleteACL(String mailbox, String name) throws MessagingException {
+        if (!hasCapability("ACL")) {
+            throw new MethodNotSupportedException("ACL not available from this IMAP server"); 
+        }
+        IMAPCommand command = new IMAPCommand("DELETEACL");
+        command.appendEncodedString(mailbox);
+        command.appendString(name); 
+    
+        // just send the command.  No response to handle. 
+        sendSimpleCommand(command); 
+    }
+    
+    /**
+     * Fetch the quota root information for a target mailbox.
+     * 
+     * @param mailbox The mailbox of interest.
+     * 
+     * @return An array of quotas describing all of the quota roots
+     *         that apply to the target mailbox.
+     * @exception MessagingException
+     */
+    public synchronized Quota[] fetchQuotaRoot(String mailbox) throws MessagingException {
+        if (!hasCapability("QUOTA")) {
+            throw new MethodNotSupportedException("QUOTA not available from this IMAP server"); 
+        }
+        IMAPCommand command = new IMAPCommand("GETQUOTAROOT");
+        command.appendEncodedString(mailbox);
+    
+        // This will return a single QUOTAROOT response, plust a series of QUOTA responses for 
+        // each root names in the first response.  
+        sendCommand(command); 
+        // we don't really need this, but pull it from the response queue anyway. 
+        extractResponse("QUOTAROOT"); 
+        
+        // now get the real meat of the matter 
+        List responses = extractResponses("QUOTA"); 
+        
+        // now copy all of the returned quota items into the response array. 
+        Quota[] quotas = new Quota[responses.size()]; 
+        for (int i = 0; i < quotas.length; i++) {
+            IMAPQuotaResponse q = (IMAPQuotaResponse)responses.get(i); 
+            quotas[i] = q.quota; 
+        }
+        
+        return quotas; 
+    }
+    
+    /**
+     * Fetch QUOTA information from a named QUOTE root.
+     * 
+     * @param root   The target root name.
+     * 
+     * @return An array of Quota items associated with that root name.
+     * @exception MessagingException
+     */
+    public synchronized Quota[] fetchQuota(String root) throws MessagingException {
+        if (!hasCapability("QUOTA")) {
+            throw new MethodNotSupportedException("QUOTA not available from this IMAP server"); 
+        }
+        IMAPCommand command = new IMAPCommand("GETQUOTA");
+        command.appendString(root);
+    
+        // This will return a single QUOTAROOT response, plust a series of QUOTA responses for 
+        // each root names in the first response.  
+        sendCommand(command); 
+        
+        // now get the real meat of the matter 
+        List responses = extractResponses("QUOTA"); 
+        
+        // now copy all of the returned quota items into the response array. 
+        Quota[] quotas = new Quota[responses.size()]; 
+        for (int i = 0; i < quotas.length; i++) {
+            IMAPQuotaResponse q = (IMAPQuotaResponse)responses.get(i); 
+            quotas[i] = q.quota; 
+        }
+        
+        return quotas; 
+    }
+    
+    /**
+     * Set a Quota item for the currently accessed 
+     * userid/folder resource. 
+     * 
+     * @param quota  The new QUOTA information.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void setQuota(Quota quota) throws MessagingException {
+        if (!hasCapability("QUOTA")) {
+            throw new MethodNotSupportedException("QUOTA not available from this IMAP server"); 
+        }
+        IMAPCommand command = new IMAPCommand("GETQUOTA");
+        // this gets appended as a list of resource values 
+        command.appendQuota(quota); 
+    
+        // This will return a single QUOTAROOT response, plust a series of QUOTA responses for 
+        // each root names in the first response.  
+        sendCommand(command); 
+        // we don't really need this, but pull it from the response queue anyway. 
+        extractResponses("QUOTA"); 
+    }
+    
+    
+    /**
+     * Test if this connection has a given capability. 
+     * 
+     * @param capability The capability name.
+     * 
+     * @return true if this capability is in the list, false for a mismatch. 
+     */
+    public boolean hasCapability(String capability) {
+        if (capabilities == null) {
+            return false; 
+        }
+        return capabilities.containsKey(capability); 
+    }
+    
+    /**
+     * Tag this connection as having been closed by the 
+     * server.  This will not be returned to the 
+     * connection pool. 
+     */
+    public void setClosed() {
+        closed = true;
+    }
+    
+    /**
+     * Test if the connnection has been forcibly closed.
+     * 
+     * @return True if the server disconnected the connection.
+     */
+    public boolean isClosed() {
+        return closed; 
+    }
+}
+

Propchange: geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: geronimo/javamail/trunk/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message