GERONIMO-3600 Add imap and imaps protocol support to the Geronimo javamail providers.



git-svn-id: https://svn.apache.org/repos/asf/geronimo/javamail/trunk@594520 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/AuthenticatorFactory.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/AuthenticatorFactory.java
new file mode 100644
index 0000000..57db7f5
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/AuthenticatorFactory.java
@@ -0,0 +1,88 @@
+/**
+ * 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.authentication;
+
+import java.lang.reflect.Constructor;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.geronimo.javamail.util.ProtocolProperties; 
+
+public class AuthenticatorFactory {
+    // the list of authentication mechanisms we have direct support for.  Others come from 
+    // SASL, if it's available. 
+    
+    public static final String AUTHENTICATION_PLAIN = "PLAIN";
+    public static final String AUTHENTICATION_LOGIN = "LOGIN";
+    public static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5";
+    public static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5";
+     
+    static public ClientAuthenticator getAuthenticator(ProtocolProperties props, List mechanisms, String host, String username, String password, String authId, String realm)
+    {
+        // if the authorization id isn't given, then this is the same as the logged in user name. 
+        if (authId == null) {
+            authId = username; 
+        }
+        
+        // if SASL is enabled, try getting a SASL authenticator first 
+        if (props.getBooleanProperty("sasl.enable", false)) {
+            // we need to convert the mechanisms map into an array of strings for SASL. 
+            String [] mechs = (String [])mechanisms.toArray(new String[mechanisms.size()]); 
+            
+            try {
+                // need to try to load this using reflection since it has references to 
+                // the SASL API.  That's only available with 1.5 or later. 
+                Class authenticatorClass = Class.forName("org.apache.geronimo.javamal.authentication.SASLAuthenticator"); 
+                Constructor c = authenticatorClass.getConstructor(new Class[] {
+                    (new String[0]).getClass(), 
+                    Properties.class, 
+                    String.class, 
+                    String.class, 
+                    String.class, 
+                    String.class, 
+                    String.class, 
+                    String.class
+                }); 
+                
+                Object[] args = { mechs, props.getProperties(), props.getProtocol(), host, realm, authId, username, password };
+                
+                return (ClientAuthenticator)c.newInstance(args); 
+            } catch (Throwable e) {
+                // Any exception is likely because we're running on 1.4 and can't use the Sasl API.  
+                // just ignore and use our fallback implementations. 
+            }
+        }
+
+        // now go through the progression of mechanisms we support, from the
+        // most secure to the least secure.
+
+        if (mechanisms.contains(AUTHENTICATION_DIGESTMD5)) {
+            return new DigestMD5Authenticator(host, username, password, realm);
+        } else if (mechanisms.contains(AUTHENTICATION_CRAMMD5)) {
+            return new CramMD5Authenticator(username, password);
+        } else if (mechanisms.contains(AUTHENTICATION_LOGIN)) {
+            return new LoginAuthenticator(username, password);
+        } else if (mechanisms.contains(AUTHENTICATION_PLAIN)) {
+            return new PlainAuthenticator(username, password);
+        } else {
+            // can't find a mechanism we support in common
+            return null;
+        }
+    }
+}
+     
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/SASLAuthenticator.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/SASLAuthenticator.java
new file mode 100644
index 0000000..9b22442
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/SASLAuthenticator.java
@@ -0,0 +1,174 @@
+/*
+ * 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.authentication;
+
+import java.io.UnsupportedEncodingException ;
+import java.util.Map;        
+import java.util.Properties; 
+
+import javax.mail.MessagingException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.sasl.Sasl; 
+import javax.security.sasl.SaslClient; 
+import javax.security.sasl.SaslException; 
+import javax.security.sasl.RealmCallback; 
+import javax.security.sasl.RealmChoiceCallback; 
+
+public class SASLAuthenticator implements ClientAuthenticator, CallbackHandler {
+    // The realm we're authenticating within 
+    protected String realm; 
+    // the user we're authenticating
+    protected String username;
+    // the user's password (the "shared secret")
+    protected String password;
+    // the authenticator we're proxying 
+    protected SaslClient authenticator; 
+
+    protected boolean complete = false;
+
+    /**
+     * Main constructor.
+     * 
+     * @param username
+     *            The login user name.
+     * @param password
+     *            The login password.
+     */
+    public SASLAuthenticator(String[] mechanisms, Properties properties, String protocol, String host, String realm, 
+            String authorizationID, String username, String password) throws MessagingException {
+        this.realm = realm; 
+        this.username = username;
+        this.password = password;
+        try {
+            authenticator = Sasl.createSaslClient(mechanisms, authorizationID, protocol, host, (Map)properties, 
+                this); 
+        } catch (SaslException e) {
+        } 
+    }
+    
+    
+    /**
+     * Respond to the hasInitialResponse query. We defer this to the Sasl client.  
+     * 
+     * @return The SaslClient response to the same query. 
+     */
+    public boolean hasInitialResponse() {
+        return authenticator.hasInitialResponse(); 
+    }
+
+    /**
+     * Indicate whether the challenge/response process is complete.
+     * 
+     * @return True if the last challenge has been processed, false otherwise.
+     */
+    public boolean isComplete() {
+        return authenticator.hasInitialResponse(); 
+    }
+
+    /**
+     * Retrieve the authenticator mechanism name.
+     * 
+     * @return Always returns the string "PLAIN"
+     */
+    public String getMechanismName() {
+        // the authenticator selects this for us. 
+        return authenticator.getMechanismName(); 
+    }
+
+    /**
+     * Evaluate a login challenge, returning the a result string that
+     * should satisfy the clallenge.  This is forwarded to the 
+     * SaslClient, which will use the CallBackHandler to retrieve the 
+     * information it needs for the given protocol. 
+     * 
+     * @param challenge
+     *            The decoded challenge data, as byte array.
+     * 
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
+        // for an initial response challenge, there's no challenge date.  The SASL 
+        // client still expects a byte array argument. 
+        if (challenge == null) {
+            challenge = new byte[0];
+        }
+        
+        try {
+            return authenticator.evaluateChallenge(challenge);
+        } catch (SaslException e) {
+            // got an error, fail this
+            throw new MessagingException("Error performing SASL validation", e);
+        }
+    }
+    
+    public void handle(Callback[] callBacks) {
+        for (int i = 0; i < callBacks.length; i++) {
+            Callback callBack = callBacks[i]; 
+            // requesting the user name 
+            if (callBack instanceof NameCallback) {
+                ((NameCallback)callBack).setName(username); 
+            }
+            // need the password 
+            else if (callBack instanceof PasswordCallback) {
+                ((PasswordCallback)callBack).setPassword(password.toCharArray()); 
+            }
+            // direct request for the realm information 
+            else if (callBack instanceof RealmCallback) {
+                RealmCallback realmCallback = (RealmCallback)callBack; 
+                // we might not have a realm, so use the default from the 
+                // callback item 
+                if (realm == null) {
+                    realmCallback.setText(realmCallback.getDefaultText()); 
+                }
+                else { 
+                    realmCallback.setText(realm); 
+                }
+            }
+            // asked to select the realm information from a list 
+            else if (callBack instanceof RealmChoiceCallback) {
+                RealmChoiceCallback realmCallback = (RealmChoiceCallback)callBack; 
+                // if we don't have a realm, just tell it to use the default 
+                if (realm == null) {
+                    realmCallback.setSelectedIndex(realmCallback.getDefaultChoice()); 
+                }
+                else {
+                    // locate our configured one in the list 
+                    String[] choices = realmCallback.getChoices(); 
+
+                    for (int j = 0; j < choices.length; j++) {
+                        // set the index to any match and get out of here. 
+                        if (choices[j].equals(realm)) {
+                            realmCallback.setSelectedIndex(j); 
+                            break; 
+                        }
+                    }
+                    // NB:  If there was no match, we don't set anything.  
+                    // this should cause an authentication failure. 
+                }
+            }
+        }
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTextHandler.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTextHandler.java
index 35e81a5..91716c3 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTextHandler.java
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTextHandler.java
@@ -19,10 +19,12 @@
 import java.awt.datatransfer.DataFlavor;
 import java.awt.datatransfer.UnsupportedFlavorException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Reader;
+
 import javax.activation.DataContentHandler;
 import javax.activation.DataSource;
 
@@ -45,12 +47,12 @@
     }
 
     public Object getContent(DataSource ds) throws IOException {
-        // todo handle encoding
-        Reader reader = new InputStreamReader(ds.getInputStream());
+        InputStream is = ds.getInputStream(); 
+        Reader reader = new InputStreamReader(is);
         StringBuffer result = new StringBuffer(1024);
         char[] buffer = new char[32768];
         int count;
-        while ((count = reader.read(buffer)) != -1) {
+        while ((count = reader.read(buffer)) > 0) {
             result.append(buffer, 0, count);
         }
         return result.toString();
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/ACL.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/ACL.java
new file mode 100644
index 0000000..382ef48
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/ACL.java
@@ -0,0 +1,146 @@
+/**
+ * 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;
+
+/**
+ * A named access control list for IMAP resources.  
+ */
+public class ACL implements Cloneable {
+    /**
+     * The name of the resource this ACL applies to.
+     */
+    private String name; 
+    /**
+     * The rights associated with this resource.
+     */
+    private Rights rights;
+    
+    /**
+     * Create an ACL for a resource.  The ACL will have an empty Rights set.
+     * 
+     * @param name   The name of the resource.
+     */
+    public ACL(String name) {
+        this.name = name; 
+        this.rights = new Rights(); 
+    }
+    
+    /**
+     * Create a named ACL instance with an initial Rights set.
+     * 
+     * @param name   The name of the resouce this ACL applies to.
+     * @param rights The Rights associated with this resource.
+     */
+    public ACL(String name, Rights rights) {
+        this.name = name; 
+        this.rights = rights;  
+    }
+    
+    /**
+     * Get the ACL name.
+     * 
+     * @return The string name of the ACL.
+     */
+    public String getName() {
+        return name; 
+    }
+    
+    /**
+     * Get the Rights associated with this ACL.
+     * 
+     * @return The Rights set supported for this resource.
+     */
+    public Rights getRights() {
+        return rights; 
+    }
+    
+    /**
+     * Set a new set of Rights for this ACL instance.
+     * 
+     * @param rights The new Rights set.
+     */
+    public void setRights(Rights rights) {
+        this.rights = rights;         
+    }
+    
+    
+    
+    /**
+     * Creates and returns a copy of this object.  The precise meaning
+     * of "copy" may depend on the class of the object. The general
+     * intent is that, for any object <tt>x</tt>, the expression:
+     * <blockquote>
+     * <pre>
+     * x.clone() != x</pre></blockquote>
+     * will be true, and that the expression:
+     * <blockquote>
+     * <pre>
+     * x.clone().getClass() == x.getClass()</pre></blockquote>
+     * will be <tt>true</tt>, but these are not absolute requirements.
+     * While it is typically the case that:
+     * <blockquote>
+     * <pre>
+     * x.clone().equals(x)</pre></blockquote>
+     * will be <tt>true</tt>, this is not an absolute requirement.
+     * <p>
+     * By convention, the returned object should be obtained by calling
+     * <tt>super.clone</tt>.  If a class and all of its superclasses (except
+     * <tt>Object</tt>) obey this convention, it will be the case that
+     * <tt>x.clone().getClass() == x.getClass()</tt>.
+     * <p>
+     * By convention, the object returned by this method should be independent
+     * of this object (which is being cloned).  To achieve this independence,
+     * it may be necessary to modify one or more fields of the object returned
+     * by <tt>super.clone</tt> before returning it.  Typically, this means
+     * copying any mutable objects that comprise the internal "deep structure"
+     * of the object being cloned and replacing the references to these
+     * objects with references to the copies.  If a class contains only
+     * primitive fields or references to immutable objects, then it is usually
+     * the case that no fields in the object returned by <tt>super.clone</tt>
+     * need to be modified.
+     * <p>
+     * The method <tt>clone</tt> for class <tt>Object</tt> performs a
+     * specific cloning operation. First, if the class of this object does
+     * not implement the interface <tt>Cloneable</tt>, then a
+     * <tt>CloneNotSupportedException</tt> is thrown. Note that all arrays
+     * are considered to implement the interface <tt>Cloneable</tt>.
+     * Otherwise, this method creates a new instance of the class of this
+     * object and initializes all its fields with exactly the contents of
+     * the corresponding fields of this object, as if by assignment; the
+     * contents of the fields are not themselves cloned. Thus, this method
+     * performs a "shallow copy" of this object, not a "deep copy" operation.
+     * <p>
+     * The class <tt>Object</tt> does not itself implement the interface
+     * <tt>Cloneable</tt>, so calling the <tt>clone</tt> method on an object
+     * whose class is <tt>Object</tt> will result in throwing an
+     * exception at run time.
+     * 
+     * @return a clone of this instance.
+     * @exception CloneNotSupportedException
+     *                   if the object's class does not
+     *                   support the <code>Cloneable</code> interface. Subclasses
+     *                   that override the <code>clone</code> method can also
+     *                   throw this exception to indicate that an instance cannot
+     *                   be cloned.
+     * @see java.lang.Cloneable
+     */
+    protected Object clone() throws CloneNotSupportedException {
+        return new ACL(name, new Rights(rights)); 
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPAttachedMessage.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPAttachedMessage.java
new file mode 100644
index 0000000..03fc82e
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPAttachedMessage.java
@@ -0,0 +1,125 @@
+/**
+ * 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;
+
+import javax.activation.DataHandler;
+
+import javax.mail.Flags;
+import javax.mail.MessagingException;
+import javax.mail.MethodNotSupportedException;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPEnvelope;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure;
+
+/**
+ * A nested message attachement inside of another 
+ * IMAP message.  This is a less-functional version 
+ * of the top-level message.
+ */
+public class IMAPAttachedMessage extends IMAPMessage {
+    // the parent enclosing message.
+    protected IMAPMessage parent;
+
+    /**
+     * Constructor for an attached message part.
+     * 
+     * @param parent   The parent message (outer-most message).
+     * @param section  The section identifier for this embedded part
+     *                 in IMAP section format.  This will identify
+     *                 the part hierarchy used to locate this part within
+     *                 the message.
+     * @param envelope The Envelope that describes this part.
+     * @param bodyStructure
+     *                 The Body structure element that describes this part.
+     */
+    public IMAPAttachedMessage(IMAPMessage parent, String section, IMAPEnvelope envelope, IMAPBodyStructure bodyStructure) {
+        super((IMAPFolder)parent.getFolder(), parent.store, parent.getMessageNumber(), parent.sequenceNumber);
+        this.parent = parent;
+        // sets the subset we're looking for 
+        this.section = section;
+        // the envelope and body structure are loaded from the server by the parent 
+        this.envelope = envelope;
+        this.bodyStructure = bodyStructure;
+    }
+
+    /**
+     * Check if this message is still valid.  This is 
+     * delegated to the outer-most message.
+     * 
+     * @exception MessagingException
+     */
+    protected void checkValidity() throws MessagingException {
+        parent.checkValidity();
+    }
+
+    /**
+     * Check if the outer-most message has been expunged.
+     * 
+     * @return true if the message has been expunged.
+     */
+    public boolean isExpunged() {
+        return parent.isExpunged();
+    }
+
+    /**
+     * Get the size of this message part.
+     * 
+     * @return The estimate size of this message part, in bytes.
+     */
+    public int getSize() {
+        return bodyStructure.bodySize;
+    }
+
+    
+    /**
+     * Return a copy the flags associated with this message.
+     *
+     * @return a copy of the flags for this message
+     * @throws MessagingException if there was a problem accessing the Store
+     */
+    public Flags getFlags() throws MessagingException {
+        return parent.getFlags(); 
+    }
+
+
+    /**
+     * Check whether the supplied flag is set.
+     * The default implementation checks the flags returned by {@link #getFlags()}.
+     *
+     * @param flag the flags to check for
+     * @return true if the flags is set
+     * @throws MessagingException if there was a problem accessing the Store
+     */
+    public boolean isSet(Flags.Flag flag) throws MessagingException {
+        // load the flags, if needed 
+        return parent.isSet(flag); 
+    }
+
+    /**
+     * Set or clear a flag value.
+     *
+     * @param flags  The set of flags to effect.
+     * @param set    The value to set the flag to (true or false).
+     *
+     * @exception MessagingException
+     */
+    public void setFlags(Flags flag, boolean set) throws MessagingException {
+        throw new MethodNotSupportedException("Flags cannot be set on message attachements"); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java
new file mode 100644
index 0000000..7c143d6
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java
@@ -0,0 +1,2386 @@
+/**
+ * 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;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+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.Map;
+import java.util.NoSuchElementException;
+import java.util.Vector;
+
+import javax.mail.*;
+import javax.mail.event.ConnectionEvent;
+import javax.mail.event.FolderEvent;
+import javax.mail.event.MessageChangedEvent;
+import javax.mail.search.FlagTerm;
+import javax.mail.search.SearchTerm;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchDataItem;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchResponse;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPFlags;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPListResponse;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPMailboxStatus;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPSizeResponse;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPUid;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponse;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponseHandler;
+
+/**
+ * The base IMAP implementation of the javax.mail.Folder
+ * This is a base class for both the Root IMAP server and each IMAP group folder.
+ * @see javax.mail.Folder
+ *
+ * @version $Rev$
+ */
+public class IMAPFolder extends Folder implements UIDFolder, IMAPUntaggedResponseHandler {
+
+    /**
+     * Special profile item used for fetching SIZE and HEADER information.
+     * These items are extensions that Sun has added to their IMAPFolder immplementation. 
+     * We're supporting the same set. 
+     */
+    public static class FetchProfileItem extends FetchProfile.Item {
+        public static final FetchProfileItem HEADERS = new FetchProfileItem("HEADERS");
+        public static final FetchProfileItem SIZE = new FetchProfileItem("SIZE");
+
+        protected FetchProfileItem(String name) {
+            super(name);
+        }
+    }
+    
+    // marker that we don't know the separator yet for this folder. 
+    // This occurs when we obtain a folder reference from the 
+    // default folder.  At that point, we've not queried the 
+    // server for specifics yet. 
+    static final protected char UNDETERMINED = 0; 
+    
+    // our attached session
+    protected Session session;
+    // retrieved messages, mapped by sequence number.
+    protected LinkedList messages;
+    // mappings of UIDs to retrieved messages.
+    protected Map uidCache;
+
+    // the separator the server indicates is used as the hierarchy separator
+    protected char separator;
+    // the "full" name of the folder.  This is the fully qualified path name for the folder returned by
+    // the IMAP server.  Elements of the hierarchy are delimited by "separator" characters.
+    protected String fullname;
+    // the name of this folder.  The is the last element of the fully qualified name.
+    protected String name;
+    // the folder open state
+	protected boolean folderOpen = false;
+    // the type information on what the folder can hold
+    protected int folderType;
+    // the subscription status
+    protected boolean subscribed = false;
+
+    // the message identifier ticker, used to assign message numbers. 
+    protected int nextMessageID = 1; 
+    // the current count of messages in our cache. 
+    protected int maxSequenceNumber = 0;  
+    // the reported count of new messages (updated as a result of untagged message resposes)
+    protected int recentMessages = -1;
+    // the reported count of unseen messages
+    protected int unseenMessages = 0;
+    // the uidValidity value reported back from the server
+    protected long uidValidity = 0;
+    // the uidNext value reported back from the server
+    protected long uidNext = 0;
+    // the persistent flags we save in the store
+    protected Flags permanentFlags;
+    // the settable flags the server reports back to us
+    protected Flags availableFlags;
+    // Our cached status information.  We will only hold this for the timeout interval.
+    protected IMAPMailboxStatus cachedStatus;
+    // Folder information retrieved from the server.  Good info here indicates the 
+    // folder exists. 
+    protected IMAPListResponse listInfo; 
+    // the configured status cache timeout value.
+    protected long statusCacheTimeout;
+    // the last time we took a status snap shot.
+    protected long lastStatusTimeStamp;
+    // Our current connection.  We get one of these when opened, and release it when closed. 
+    // We do this because for any folder (and message) operations, the folder must be selected on 
+    // the connection.  
+    // Note, however, that there are operations which will require us to borrow a connection 
+    // temporarily because we need to touch the server when the folder is not open.  In those 
+    // cases, we grab a connection, then immediately return it to the pool. 
+    protected IMAPConnection currentConnection; 
+    
+    
+
+    /**
+     * Super class constructor the base IMAPFolder class.
+     * 
+     * @param store     The javamail store this folder is attached to.
+     * @param fullname  The fully qualified name of this folder.
+     * @param separator The separtor character used to delimit the different
+     *                  levels of the folder hierarchy.  This is used to
+     *                  decompose the full name into smaller parts and
+     *                  create the names of subfolders.
+     */
+	protected IMAPFolder(IMAPStore store, String fullname, char separator) {
+		super(store);
+		this.session = store.getSession();
+        this.fullname = fullname;
+        this.separator = separator;
+        // get the status timeout value from the folder. 
+        statusCacheTimeout = store.statusCacheTimeout; 
+	}
+
+    /**
+     * Retrieve the folder name.  This is the simple folder
+     * name at the its hiearchy level.  This can be invoked when the folder is closed.
+     * 
+     * @return The folder's name.
+     */
+	public String getName() {
+        // At the time we create the folder, we might not know the separator character yet. 
+        // Because of this we need to delay creating the name element until 
+        // it's required.  
+        if (name == null) {
+            // extract the name from the full name
+            int lastLevel = -1; 
+            try {
+                lastLevel = fullname.lastIndexOf(getSeparator());
+            } catch (MessagingException e) {
+                // not likely to occur, but the link could go down before we 
+                // get this.  Just assume a failure to locate the character 
+                // occurred. 
+            }
+            if (lastLevel == -1) {
+                name = fullname;
+            }
+            else {
+                name = fullname.substring(lastLevel + 1);
+            }
+        }
+        return name;
+	}
+
+    /**
+     * Retrieve the folder's full name (including hierarchy information).
+     * This can be invoked when the folder is closed.
+     *
+     * @return The full name value.
+     */
+	public String getFullName() {
+        return fullname;
+	}
+
+
+
+    /**
+     * Return the parent for this folder; if the folder is at the root of a heirarchy
+     * this returns null.
+     * This can be invoked when the folder is closed.
+     *
+     * @return this folder's parent
+     * @throws MessagingException
+     */
+	public Folder getParent() throws MessagingException {
+        // NB:  We need to use the method form because the separator 
+        // might not have been retrieved from the server yet. 
+        char separator = getSeparator(); 
+        // we don't hold a reference to the parent folder, as that would pin the instance in memory 
+        // as long as any any leaf item in the hierarchy is still open.  
+        int lastLevel = fullname.lastIndexOf(separator);
+        // no parent folder?  Get the root one from the Store.
+        if (lastLevel == -1) {
+            return ((IMAPStore)store).getDefaultFolder();
+        }
+        else {
+            // create a folder for the parent.
+            return new IMAPFolder((IMAPStore)store, fullname.substring(0, lastLevel), separator);
+        }
+	}
+
+
+    /**
+     * Check to see if this folder physically exists in the store.
+     * This can be invoked when the folder is closed.
+     *
+     * @return true if the folder really exists
+     * @throws MessagingException if there was a problem accessing the store
+     */
+    public synchronized boolean exists() throws MessagingException {
+        IMAPConnection connection = getConnection(); 
+        try {
+            return checkExistance(connection); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+    
+    /**
+     * Internal routine for checking existance using an 
+     * already obtained connection.  Used for situations
+     * where the list information needs updating but 
+     * we'd end up acquiring a new connection because 
+     * the folder isn't open yet. 
+     * 
+     * @param connection The connection to use.
+     * 
+     * @return true if the folder exists, false for non-existence.
+     * @exception MessagingException
+     */
+    private boolean checkExistance(IMAPConnection connection) throws MessagingException {
+        // get the list response for this folder.
+        List responses = connection.list("", fullname);
+        // NB, this grabs the latest information and updates 
+        // the type information also.  Note also that we need to  
+        // use the mailbox name, not the full name.  This is so 
+        // the namespace folders will return the correct response. 
+        listInfo = findListResponse(responses, getMailBoxName());
+
+        if (listInfo == null) {
+            return false;
+        }
+
+        // update the type information from the status.
+        folderType = 0;
+        if (!listInfo.noinferiors) {
+            folderType |= HOLDS_FOLDERS;
+        }
+        if (!listInfo.noselect) {
+            folderType |= HOLDS_MESSAGES;
+        }
+
+        // also update the separator information.  This will allow 
+        // use to skip a call later 
+        separator = listInfo.separator;
+        // this can be omitted in the response, so assume a default 
+        if (separator == '\0') {
+            separator = '/';
+        }
+
+        // updated ok, so it must be there.
+        return true;
+    }
+    
+
+
+    /**
+     * Return a list of folders from this Folder's namespace that match the supplied pattern.
+     * Patterns may contain the following wildcards:
+     * <ul><li>'%' which matches any characater except hierarchy delimiters</li>
+     * <li>'*' which matches any character including hierarchy delimiters</li>
+     * </ul>
+     * This can be invoked when the folder is closed.
+     * 
+     * @param pattern the pattern to search for
+     * 
+     * @return a possibly empty array containing Folders that matched the pattern
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+    public synchronized Folder[] list(String pattern) throws MessagingException {
+        // go filter the folders based on the pattern.  The server does most of the
+        // heavy lifting on the pattern matching.
+        return filterFolders(pattern, false);
+    }
+
+
+    /**
+     * Return a list of folders to which the user is subscribed and which match the supplied pattern.
+     * If the store does not support the concept of subscription then this should match against
+     * all folders; the default implementation of this method achieves this by defaulting to the
+     * {@link #list(String)} method.
+     * 
+     * @param pattern the pattern to search for
+     * 
+     * @return a possibly empty array containing subscribed Folders that matched the pattern
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+    public synchronized Folder[] listSubscribed(String pattern) throws MessagingException {
+        // go filter the folders based on the pattern.  The server does most of the
+        // heavy lifting on the pattern matching.
+        return filterFolders(pattern, true);
+    }
+
+
+    /**
+     * Return the character used by this folder's Store to separate path components.
+     *
+     * @return the name separater character
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public synchronized char getSeparator() throws MessagingException {
+        // not determined yet, we need to ask the server for the information 
+        if (separator == UNDETERMINED) {
+            IMAPConnection connection = getConnection(); 
+            try {
+                List responses = connection.list("", fullname);
+                IMAPListResponse info = findListResponse(responses, fullname);
+
+                // if we didn't get any hits, then we just assume a reasonable default. 
+                if (info == null) {
+                    separator = '/';
+                }
+                else {
+                    separator = info.separator;
+                    // this can be omitted in the response, so assume a default 
+                    if (separator == '\0') {
+                        separator = '/';
+                    }
+                }
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+        return separator;
+	}
+
+
+    /**
+     * Return whether this folder can hold just messages or also
+     * subfolders.  
+     *
+     * @return The combination of Folder.HOLDS_MESSAGES and Folder.HOLDS_FOLDERS, depending 
+     * on the folder capabilities. 
+     * @exception MessagingException
+     */
+	public int getType() throws MessagingException {
+        // checking the validity will update the type information 
+        // if it succeeds. 
+        checkFolderValidity();
+		return folderType;
+	}
+    
+
+    /**
+     * Create a new folder capable of containing subfolder and/or messages as
+     * determined by the type parameter. Any hierarchy defined by the folder
+     * name will be recursively created.
+     * If the folder was sucessfully created, a {@link FolderEvent#CREATED CREATED FolderEvent}
+     * is sent to all FolderListeners registered with this Folder or with the Store.
+     * 
+     * @param newType the type, indicating if this folder should contain subfolders, messages or both
+     * 
+     * @return true if the folder was sucessfully created
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+	public synchronized boolean create(int newType) throws MessagingException {
+        IMAPConnection connection = getConnection(); 
+        try {
+
+            // by default, just create using the fullname.  
+            String newPath = fullname;
+
+            // if this folder is expected to only hold additional folders, we need to
+            // add a separator on to the end when we create this.
+            if ((newType & HOLDS_MESSAGES) == 0) {
+                newPath = fullname + separator;
+            }
+            try {
+                // go create this
+                connection.createMailbox(newPath);
+                // verify this exists...also updates some of the status 
+                boolean reallyCreated = checkExistance(connection); 
+                // broadcast a creation event.
+                notifyFolderListeners(FolderEvent.CREATED);
+                return reallyCreated; 
+            } catch (MessagingException e) {
+                //TODO add folder level debug logging.
+            }
+            // we have a failure
+            return false;
+        } finally {
+            releaseConnection(connection); 
+        }
+	}
+    
+
+    /**
+     * Return the subscription status of this folder.
+     *
+     * @return true if the folder is marked as subscribed, false for
+     *         unsubscribed.
+     */
+    public synchronized boolean isSubscribed() {
+        try {
+            IMAPConnection connection = getConnection(); 
+            try {
+                // get the lsub response for this folder.
+                List responses = connection.listSubscribed("", fullname);
+
+                IMAPListResponse response = findListResponse(responses, fullname);
+                if (response == null) {
+                    return false;
+                }
+                else {
+                    // a NOSELECT flag response indicates the mailbox is no longer 
+                    // selectable, so it's also no longer subscribed to. 
+                    return !response.noselect; 
+                }
+            } finally {
+                releaseConnection(connection); 
+            }
+        } catch (MessagingException e) {
+            // Can't override to throw a MessagingException on this method, so 
+            // just swallow any exceptions and assume false is the answer. 
+        }
+        return false; 
+    }
+
+
+    /**
+     * Set or clear the subscription status of a file.
+     *
+     * @param flag
+     *            The new subscription state.
+     */
+    public synchronized void setSubscribed(boolean flag) throws MessagingException {
+        IMAPConnection connection = getConnection(); 
+        try {             
+            if (flag) {
+                connection.subscribe(fullname);
+            }
+            else {
+                connection.unsubscribe(fullname);
+            }
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+
+    /**
+     * Check to see if this Folder conatins messages with the {@link Flag.RECENT} flag set.
+     * This can be used when the folder is closed to perform a light-weight check for new mail;
+     * to perform an incremental check for new mail the folder must be opened.
+     *
+     * @return true if the Store has recent messages
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public synchronized boolean hasNewMessages() throws MessagingException {
+        // the folder must exist for this to work.
+        checkFolderValidity();
+        
+        // get the freshest status information.
+        refreshStatus(true);
+        // return the indicator from the message state.
+        return recentMessages > 0;
+	}
+
+    /**
+     * Get the Folder determined by the supplied name; if the name is relative
+     * then it is interpreted relative to this folder. This does not check that
+     * the named folder actually exists.
+     *
+     * @param name the name of the folder to return
+     * @return the named folder
+     * @throws MessagingException if there was a problem accessing the store
+     */
+    public Folder getFolder(String name) throws MessagingException {
+        // this must be a real, valid folder to hold a subfolder
+        checkFolderValidity(); 
+        if (!holdsFolders()) {
+            throw new MessagingException("Folder " + fullname + " cannot hold subfolders"); 
+        }
+        // our separator does not get determined until we ping the server for it.  We 
+        // might need to do that now, so we need to use the getSeparator() method to retrieve this. 
+        char separator = getSeparator(); 
+        
+        return new IMAPFolder((IMAPStore)store, fullname + separator + name, separator);
+    }
+    
+
+    /**
+     * Delete this folder and possibly any subfolders. This operation can only be
+     * performed on a closed folder.
+     * If recurse is true, then all subfolders are deleted first, then any messages in
+     * this folder are removed and it is finally deleted; {@link FolderEvent#DELETED}
+     * events are sent as appropriate.
+     * If recurse is false, then the behaviour depends on the folder type and store
+     * implementation as followd:
+     * <ul>
+     * <li>If the folder can only conrain messages, then all messages are removed and
+     * then the folder is deleted; a {@link FolderEvent#DELETED} event is sent.</li>
+     * <li>If the folder can onlu contain subfolders, then if it is empty it will be
+     * deleted and a {@link FolderEvent#DELETED} event is sent; if the folder is not
+     * empty then the delete fails and this method returns false.</li>
+     * <li>If the folder can contain both subfolders and messages, then if the folder
+     * does not contain any subfolders, any messages are deleted, the folder itself
+     * is deleted and a {@link FolderEvent#DELETED} event is sent; if the folder does
+     * contain subfolders then the implementation may choose from the following three
+     * behaviors:
+     * <ol>
+     * <li>it may return false indicting the operation failed</li>
+     * <li>it may remove all messages within the folder, send a {@link FolderEvent#DELETED}
+     * event, and then return true to indicate the delete was performed. Note this does
+     * not delete the folder itself and the {@link #exists()} operation for this folder
+     * will return true</li>
+     * <li>it may remove all messages within the folder as per the previous option; in
+     * addition it may change the type of the Folder to only HOLDS_FOLDERS indictaing
+     * that messages may no longer be added</li>
+     * </li>
+     * </ul>
+     * FolderEvents are sent to all listeners registered with this folder or
+     * with the Store.
+     *
+     * @param recurse whether subfolders should be recursively deleted as well
+     * @return true if the delete operation succeeds
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public synchronized boolean delete(boolean recurse) throws MessagingException {
+        // we must be in the closed state.
+        checkClosed();
+
+        // if recursive, get the list of subfolders and delete them first.
+        if (recurse) {
+
+            Folder[] subfolders = list();
+            for (int i = 0; i < subfolders.length; i++) {
+                // this is a recursive delete also
+                subfolders[i].delete(true);
+            }
+        }
+
+        IMAPConnection connection = getConnection(); 
+        try {
+            // delete this one now.
+            connection.deleteMailbox(fullname);
+            // this folder no longer exists on the server.
+            listInfo = null;
+
+            // notify interested parties about the deletion.
+            notifyFolderListeners(FolderEvent.DELETED);
+            return true;
+
+        } catch (MessagingException e) {
+            // ignored
+        } finally {
+            releaseConnection(connection); 
+        }
+        return false;
+	}
+
+
+    /**
+     * Rename this folder; the folder must be closed.
+     * If the rename is successfull, a {@link FolderEvent#RENAMED} event is sent to
+     * all listeners registered with this folder or with the store.
+     *
+     * @param newName the new name for this folder
+     * @return true if the rename succeeded
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public synchronized boolean renameTo(Folder f) throws MessagingException {
+        // we must be in the closed state.
+        checkClosed();
+        // but we must also exist
+        checkFolderValidity();
+
+        IMAPConnection connection = getConnection(); 
+        try {
+            // delete this one now.
+            connection.renameMailbox(fullname, f.getFullName());
+            // we renamed, so get a fresh set of status 
+            refreshStatus(false); 
+
+            // notify interested parties about the deletion.
+            notifyFolderRenamedListeners(f);
+            return true;
+        } catch (MessagingException e) {
+            // ignored
+        } finally {
+            releaseConnection(connection); 
+        }
+        return false;
+	}
+
+
+    /**
+     * Open this folder; the folder must be able to contain messages and
+     * must currently be closed. If the folder is opened successfully then
+     * a {@link ConnectionEvent#OPENED} event is sent to listeners registered
+     * with this Folder.
+     * <p/>
+     * Whether the Store allows multiple connections or if it allows multiple
+     * writers is implementation defined.
+     *
+     * @param mode READ_ONLY or READ_WRITE
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public synchronized void open(int mode) throws MessagingException {
+
+        // we use a synchronized block rather than use a synchronized method so that we
+        // can notify the event listeners while not holding the lock.
+        synchronized(this) {
+            // can only be performed on a closed folder
+            checkClosed();
+            // ask the store to kindly hook us up with a connection.
+            // We're going to hang on to this until we're closed, so store it in 
+            // the Folder field.  We need to make sure our mailbox is selected while 
+            // we're working things. 
+            currentConnection = ((IMAPStore)store).getFolderConnection(this); 
+            // we need to make ourselves a handler of unsolicited responses 
+            currentConnection.addResponseHandler(this); 
+            // record our open mode
+            this.mode = mode;
+
+
+            try {
+                // try to open, which gives us a lot of initial mailbox state.
+                IMAPMailboxStatus status = currentConnection.openMailbox(fullname, mode == Folder.READ_ONLY);
+
+                // not available in the requested mode?
+                if (status.mode != mode) {
+                    // trying to open READ_WRITE and this isn't available?
+                    if (mode == READ_WRITE) {
+                        throw new ReadOnlyFolderException(this, "Cannot open READ_ONLY folder in READ_WRITE mode");
+                    }
+                }
+                
+                // save this status and when we got it for later updating. 
+                cachedStatus = status; 
+                // mark when we got this
+                lastStatusTimeStamp = System.currentTimeMillis();
+
+                // now copy the status information over and flip over the open sign.
+                this.mode = status.mode;
+                maxSequenceNumber = status.messages;
+                recentMessages = status.recentMessages;
+                uidValidity = status.uidValidity;
+                uidNext = status.uidNext;
+
+                availableFlags = status.availableFlags;
+                permanentFlags = status.permanentFlags;
+
+                // create a our caches
+                messages = new LinkedList(); 
+                uidCache = new HashMap();
+                // this is a real pain, but because we need to track updates 
+                // to message sequence numbers while the folder is open, the 
+                // messages list needs to be populated with Message objects 
+                // to keep track of things.  The IMAPMessage objects will not 
+                // retrieve information from the server until required, so they're
+                // relatively lightweight at this point. 
+                populateMessageCache(); 
+
+                // we're open for business folks!
+                folderOpen = true;
+                notifyConnectionListeners(ConnectionEvent.OPENED);
+            } finally {
+                // NB:  this doesn't really release this, but it does drive 
+                // the processing of any unsolicited responses. 
+                releaseConnection(currentConnection); 
+            }
+        }
+	}
+    
+    
+    /**
+     * Populate the message cache with dummy messages, ensuring we're filled 
+     * up to the server-reported number of messages. 
+     * 
+     * @exception MessagingException
+     */
+    protected void populateMessageCache() {
+        // spin through the server-reported number of messages and add a dummy one to 
+        // the cache.  The message number is assigned from the id counter, the 
+        // sequence number is the cache position + 1. 
+        for (int i = messages.size(); i < maxSequenceNumber; i++) {
+            messages.add(new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, i+1));  
+        }
+    }
+    
+
+    /**
+     * Close this folder; it must already be open.
+     * A  @link ConnectionEvent#CLOSED} event is sent to all listeners registered
+     {* 
+     * with this folder.
+     *
+     * @param expunge whether to expunge all deleted messages
+     * @throws MessagingException if there was a problem accessing the store; the folder is still closed
+     */
+	public synchronized void close(boolean expunge) throws MessagingException {
+		// Can only be performed on an open folder
+		checkOpen();
+        cleanupFolder(expunge, false); 
+	}
+    
+    
+    /**
+     * Do folder cleanup.  This is used both for normal
+     * close operations, and adnormal closes where the
+     * server has sent us a BYE message.
+     * 
+     * @param expunge Indicates whether open messages should be expunged.
+     * @param disconnected
+     *                The disconnected flag.  If true, the server has cut
+     *                us off, which means our connection can not be returned
+     *                to the connection pool.
+     * 
+     * @exception MessagingException
+     */
+    protected void cleanupFolder(boolean expunge, boolean disconnected) throws MessagingException {
+		folderOpen = false;
+        uidCache = null; 
+        messages = null; 
+        // if we have a connection active at the moment
+        if (currentConnection != null) {
+            // was this a forced disconnect by the server?
+            if (disconnected) {
+                currentConnection.setClosed(); 
+            }
+            else {
+                // The CLOSE operation depends on what mode was used to select the mailbox.  
+                // If we're open in READ-WRITE mode, we used a SELECT operation.  When CLOSE 
+                // is issued, any deleted messages will be expunged.  If we've been asked not 
+                // to expunge the messages, we have a problem.  The solution is to reselect the 
+                // mailbox using EXAMINE, which will not expunge messages when closed.  
+                if (mode == READ_WRITE && !expunge) {
+                    // we can ignore the result...we're just switching modes. 
+                    currentConnection.openMailbox(fullname, true);
+                }
+                
+                // have this close the selected mailbox 
+                currentConnection.closeMailbox(); 
+            }
+            currentConnection.removeResponseHandler(this); 
+            // we need to release the connection to the Store once we're closed 
+            ((IMAPStore)store).releaseFolderConnection(this, currentConnection); 
+            currentConnection = null; 
+        }
+		notifyConnectionListeners(ConnectionEvent.CLOSED);
+    }
+
+    
+    /**
+     * Tests the open status of the folder.
+     *
+     * @return true if the folder is open, false otherwise.
+     */
+	public boolean isOpen() {
+		return folderOpen;
+	}
+
+    /**
+     * Get the permanentFlags
+     *
+     * @return The set of permanent flags we support (only SEEN).
+     */
+	public synchronized Flags getPermanentFlags() {
+        if (permanentFlags != null) {
+            // we need a copy of our master set.
+            return new Flags(permanentFlags);
+        }
+        else {
+            // a null return is expected if not there. 
+            return null; 
+        }
+	}
+
+
+    /**
+     * Return the number of messages this folder contains.
+     * If this operation is invoked on a closed folder, the implementation
+     * may choose to return -1 to avoid the expense of opening the folder.
+     *
+     * @return the number of messages, or -1 if unknown
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public synchronized int getMessageCount() throws MessagingException {
+        checkFolderValidity();
+
+        // if we haven't opened the folder yet, we might not have good status information.
+        // go request some, which updates the folder fields also.
+        refreshStatus(false);
+		return maxSequenceNumber;
+	}
+
+    /**
+     * Return the numbew of messages in this folder that have the {@link Flag.RECENT} flag set.
+     * If this operation is invoked on a closed folder, the implementation
+     * may choose to return -1 to avoid the expense of opening the folder.
+     * The default implmentation of this method iterates over all messages
+     * in the folder; subclasses should override if possible to provide a more
+     * efficient implementation.
+     * 
+     * NB:  This is an override of the default Folder implementation, which 
+     * examines each of the messages in the folder.  IMAP has more efficient 
+     * mechanisms for grabbing the information. 
+     *
+     * @return the number of new messages, or -1 if unknown
+     * @throws MessagingException if there was a problem accessing the store
+     */
+    public synchronized int getNewMessageCount() throws MessagingException {
+        // the folder must be a real one for this to work. 
+        checkFolderValidity(); 
+        // now get current status from the folder 
+        refreshStatus(false); 
+        // this should be current now. 
+        return recentMessages; 
+    }
+
+
+
+    /**
+     * Return the number of messages in this folder that do not have the {@link Flag.SEEN} flag set.
+     * If this operation is invoked on a closed folder, the implementation
+     * may choose to return -1 to avoid the expense of opening the folder.
+     * The default implmentation of this method iterates over all messages
+     * in the folder; subclasses should override if possible to provide a more
+     * efficient implementation.
+     * 
+     * NB:  This is an override of the default Folder implementation, which 
+     * examines each of the messages in the folder.  IMAP has more efficient 
+     * mechanisms for grabbing the information. 
+     *
+     * @return the number of new messages, or -1 if unknown
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public synchronized int getUnreadMessageCount() throws MessagingException {
+        checkFolderValidity();
+        // if we haven't opened the folder yet, we might not have good status information.
+        // go request some, which updates the folder fields also.
+        if (!folderOpen) {
+            refreshStatus(false);
+        }
+        else {
+            // if we have an open connection, then search the folder for any messages
+            // marked UNSEEN.
+
+            // UNSEEN is a false test on SEEN using the search criteria.
+            SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
+            
+            // ask the store to kindly hook us up with a connection.
+            IMAPConnection connection = getConnection(); 
+            try {
+                // search using the connection directly rather than calling our search() method so we don't
+                // need to instantiate each of the matched messages.  We're really only interested in the count
+                // right now.
+                int[] matches = connection.searchMailbox(criteria);
+                // update the unseen count.
+                unseenMessages = matches == null ? 0 : matches.length;
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+        // return our current message count.
+		return unseenMessages;
+	}
+
+
+
+    /**
+     * Return the number of messages in this folder that have the {@link Flag.DELETED} flag set.
+     * If this operation is invoked on a closed folder, the implementation
+     * may choose to return -1 to avoid the expense of opening the folder.
+     * The default implmentation of this method iterates over all messages
+     * in the folder; subclasses should override if possible to provide a more
+     * efficient implementation.
+     *
+     * @return the number of new messages, or -1 if unknown
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public synchronized int getDeletedMessageCount() throws MessagingException {
+        checkFolderValidity();
+
+        // if we haven't opened the folder yet, we might not have good status information.
+        // go request some, which updates the folder fields also.
+        if (!folderOpen) {
+            // the status update doesn't return deleted messages.  These can only be obtained by
+            // searching an open folder.  Just return a bail-out response
+            return -1;
+        }
+        else {
+            // if we have an open connection, then search the folder for any messages
+            // marked DELETED.
+
+            // UNSEEN is a false test on SEEN using the search criteria.
+            SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.DELETED), true);
+            
+            // ask the store to kindly hook us up with a connection.
+            IMAPConnection connection = getConnection(); 
+            try {
+                // search using the connection directly rather than calling our search() method so we don't
+                // need to instantiate each of the matched messages.  We're really only interested in the count
+                // right now.
+                int[] matches = connection.searchMailbox(criteria);
+                return matches == null ? 0 : matches.length;
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+	}
+
+
+    /**
+     * Retrieve the message with the specified index in this Folder;
+     * messages indices start at 1 not zero.
+     * Clients should note that the index for a specific message may change
+     * if the folder is expunged; {@link Message} objects should be used as
+     * references instead.
+     * 
+     * @param msgNum The message sequence number of the target message.
+     * 
+     * @return the message
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+    public synchronized Message getMessage(int msgNum) throws MessagingException {
+        // Can only be performed on an Open folder
+        checkOpen();
+        // Check the validity of the message number.  This may require pinging the server to
+        // see if there are new messages in the folder.
+        checkMessageValidity(msgNum);
+        // ok, if the message number is within range, we should have this in the 
+        // messages list.  Just return the element. 
+        return (Message)messages.get(msgNum - 1); 
+    }
+
+
+    /**
+     * Retrieve a range of messages for this folder.
+     * messages indices start at 1 not zero.
+     * 
+     * @param start  Index of the first message to fetch, inclusive.
+     * @param end    Index of the last message to fetch, inclusive.
+     * 
+     * @return An array of the fetched messages.
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+    public synchronized Message[] getMessages(int start, int end) throws MessagingException {
+        // Can only be performed on an Open folder
+        checkOpen();
+        Message[] messageRange = new Message[end - start + 1];
+        
+        for (int i = 0; i < messageRange.length; i++) {
+            // NB:  getMessage() requires values that are origin 1, so there's 
+            // no need to adjust the value by other than the start position. 
+            messageRange[i] = getMessage(start + i);
+        }
+        return messageRange; 
+    }
+
+
+    /**
+     * Append the supplied messages to this folder. A {@link MessageCountEvent} is sent
+     * to all listeners registered with this folder when all messages have been appended.
+     * If the array contains a previously expunged message, it must be re-appended to the Store
+     * and implementations must not abort this operation.
+     * 
+     * @param msgs   The array of messages to append to the folder.
+     * 
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+	public synchronized void appendMessages(Message[] msgs) throws MessagingException {
+        checkFolderValidity(); 
+        for (int i = 0; i < msgs.length; i++) {
+            Message msg = msgs[i]; 
+            
+            appendMessage(msg); 
+        }
+	}
+
+    /**
+     * Hint to the store to prefetch information on the supplied messages.
+     * Subclasses should override this method to provide an efficient implementation;
+     * the default implementation in this class simply returns.
+     *
+     * @param messages messages for which information should be fetched
+     * @param profile  the information to fetch
+     * @throws MessagingException if there was a problem accessing the store
+     * @see FetchProfile
+     */
+    public void fetch(Message[] messages, FetchProfile profile) throws MessagingException {
+        
+        // we might already have the information being requested, so ask each of the 
+        // messages in the list to evaluate itself against the profile.  We'll only ask 
+        // the server to send information that's required. 
+        List fetchSet = new ArrayList(); 
+        
+        for (int i = 0; i < messages.length; i++) {
+            Message msg = messages[i]; 
+            // the message is missing some of the information still.  Keep this in the list. 
+            // even if the message is only missing one piece of information, we still fetch everything. 
+            if (((IMAPMessage)msg).evaluateFetch(profile)) {
+                fetchSet.add(msg); 
+            }
+        }
+        
+        // we've got everything already, no sense bothering the server 
+        if (fetchSet.isEmpty()) {
+            return;
+        }
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+        try {
+            // ok, from this point onward, we don't want any threads messing with the 
+            // message cache.  A single processed EXPUNGE could make for a very bad day 
+            synchronized(this) {
+                // get the message set for this 
+                String messageSet = generateMessageSet(fetchSet); 
+                // fetch all of the responses 
+                List responses = connection.fetch(messageSet, profile); 
+                
+                // IMPORTANT:  We must do our updates while synchronized to keep the 
+                // cache from getting updated underneath us.   This includes 
+                // not releasing the connection until we're done to delay processing any 
+                // pending expunge responses.  
+                for (int i = 0; i < responses.size(); i++) {
+                    IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 
+                    Message msg = getMessage(response.getSequenceNumber()); 
+                    // Belt and Braces.  This should never be false. 
+                    if (msg != null) {
+                        // have the message apply this to itself. 
+                        ((IMAPMessage)msg).updateMessageInformation(response); 
+                    }
+                }
+            }
+        } finally {
+            releaseConnection(connection); 
+        }
+        return;
+    }
+
+    /**
+     * Set flags on the messages to the supplied value; all messages must belong to this folder.
+     * This method may be overridden by subclasses that can optimize the setting
+     * of flags on multiple messages at once; the default implementation simply calls
+     * {@link Message#setFlags(Flags, boolean)} for each supplied messages.
+     * 
+     * @param messages whose flags should be set
+     * @param flags    the set of flags to modify
+     * @param set      Indicates whether the flags should be set or cleared.
+     * 
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+    public void setFlags(Message[] messages, Flags flags, boolean set) throws MessagingException {
+        // this is a list of messages for the change broadcast after the update 
+        List updatedMessages = new ArrayList(); 
+        
+        synchronized(this) {
+            // the folder must be open and writeable.
+            checkOpenReadWrite();
+
+            // now make sure these are settable flags.
+            if (!availableFlags.contains(flags))
+            {
+                throw new MessagingException("The IMAP server does not support changing of this flag set");
+            }
+
+            // turn this into a set of message numbers
+            String messageSet = generateMessageSet(messages);
+            // if all of the messages have been expunged, nothing to do.
+            if (messageSet == null) {
+                return;
+            }
+            // ask the store to kindly hook us up with a connection.
+            IMAPConnection connection = getConnection(); 
+
+            try {
+                // and have the connection set this
+                List responses = connection.setFlags(messageSet, flags, set);
+                // retrieve each of the messages from our cache, and do the flag update.
+                // we need to keep the list so we can broadcast a change update event 
+                // when we're finished. 
+                for (int i = 0; i < responses.size(); i++) {
+                    IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i); 
+
+                    // get the updated message and update the internal state. 
+                    Message message = getMessage(response.sequenceNumber); 
+                    // this shouldn't happen, but it might have been expunged too. 
+                    if (message != null) {
+                        ((IMAPMessage)message).updateMessageInformation(response); 
+                        updatedMessages.add(message); 
+                    }     
+                }
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+        
+        // ok, we're no longer holding the lock.  Now go broadcast the update for each 
+        // of the affected messages. 
+        for (int i = 0; i < updatedMessages.size(); i++) {
+            Message message = (Message)updatedMessages.get(i); 
+            notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
+        }
+    }
+
+    
+    /**
+     * Set flags on a range of messages to the supplied value.
+     * This method may be overridden by subclasses that can optimize the setting
+     * of flags on multiple messages at once; the default implementation simply
+     * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
+     * 
+     * @param start  first message end set
+     * @param end    last message end set
+     * @param flags  the set of flags end modify
+     * @param value  Indicates whether the flags should be set or cleared.
+     * 
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+    public synchronized void setFlags(int start, int end, Flags flags, boolean value) throws MessagingException {
+        Message[] msgs = new Message[end - start + 1]; 
+        
+        for (int i = start; i <= end; i++) {
+            msgs[i] = getMessage(i);
+        }
+        // go do a bulk set operation on these messages 
+        setFlags(msgs, flags, value); 
+    }
+
+    /**
+     * Set flags on a set of messages to the supplied value.
+     * This method may be overridden by subclasses that can optimize the setting
+     * of flags on multiple messages at once; the default implementation simply
+     * gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
+     * 
+     * @param ids    the indexes of the messages to set
+     * @param flags  the set of flags end modify
+     * @param value  Indicates whether the flags should be set or cleared.
+     * 
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+    public synchronized void setFlags(int ids[], Flags flags, boolean value) throws MessagingException {
+        Message[] msgs = new Message[ids.length]; 
+        
+        for (int i = 0; i <ids.length; i++) {
+            msgs[i] = getMessage(ids[i]);
+        }
+        // go do a bulk set operation on these messages 
+        setFlags(msgs, flags, value); 
+    }
+
+    
+    /**
+     * Copy the specified messages to another folder.
+     * The default implementation simply appends the supplied messages to the
+     * target folder using {@link #appendMessages(Message[])}.
+     * @param messages the messages to copy
+     * @param folder the folder to copy to
+     * @throws MessagingException if there was a problem accessing the store
+     */
+    public synchronized void copyMessages(Message[] messages, Folder folder) throws MessagingException {
+        // the default implementation just appends the messages to the target.  If 
+        // we're copying between two folders of the same store, we can get the server to 
+        // do most of the work for us without needing to fetch all of the message data.  
+        // If we're dealing with two different Store instances, we need to do this the 
+        // hardway.
+        if (getStore() != folder.getStore()) {
+            super.copyMessages(messages, folder); 
+            return; 
+        }
+
+        // turn this into a set of message numbers
+        String messageSet = generateMessageSet(messages);
+        // if all of the messages have been expunged, nothing to do.
+        if (messageSet == null) {
+            return;
+        }
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // ask the server to copy this information over to the other mailbox. 
+            connection.copyMessages(messageSet, folder.getFullName()); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+
+    
+
+    /**
+     * Permanently delete all supplied messages that have the DELETED flag set from the Store.
+     * The original message indices of all messages actually deleted are returned and a
+     * {@link MessageCountEvent} event is sent to all listeners with this folder. The expunge
+     * may cause the indices of all messaged that remain in the folder to change.
+     *
+     * @return the original indices of messages that were actually deleted
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public synchronized Message[] expunge() throws MessagingException {
+        // must be open to do this.
+        checkOpen();
+        // and changes need to be allowed
+        checkReadWrite();
+
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+        List expunges = null;
+
+        try {
+            // send the expunge notification.  This operation results in "nn EXPUNGE" responses getting returned
+            // for each expunged messages.  These will be dispatched to our response handler, which will process
+            // the expunge operation.  We could process this directly, but we may have received asynchronous
+            // expunge messages that also marked messages as expunged.
+            expunges = connection.expungeMailbox();
+        } finally {
+            releaseConnection(connection); 
+        }
+
+        // we get one EXPUNGE message for each message that's expunged.  They MUST be processed in 
+        // order, as the message sequence numbers represent a relative position that takes into account 
+        // previous expunge operations.  For example, if message sequence numbers 5, 6, and 7 are 
+        // expunged, we receive 3 expunge messages, all indicating that message 5 has been expunged. 
+        Message[] messages = new Message[expunges.size()]; 
+
+        // now we need to protect the internal structures
+        synchronized (this) {
+            // expunge all of the messages from the message cache.  This keeps the sequence 
+            // numbers up to-date. 
+            for (int i = 0; i < expunges.size(); i++) {
+                IMAPSizeResponse response = (IMAPSizeResponse)expunges.get(i); 
+                messages[i] = expungeMessage(response.getSize()); 
+            }
+        }
+        // if we have messages that have been removed, broadcast the notification.
+        if (messages.length > 0) {
+            notifyMessageRemovedListeners(true, messages);
+        }
+
+        // note, we're expected to return an array in all cases, even if the expunged count was zero.
+        return messages;
+	}
+
+
+
+    /**
+     * Search the supplied messages for those that match the supplied criteria;
+     * messages must belong to this folder.
+     * The default implementation iterates through the messages, returning those
+     * whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true;
+     * subclasses may provide a more efficient implementation.
+     *
+     * @param term the search criteria
+     * @param messages the messages to search
+     * @return an array containing messages that match the criteria
+     * @throws MessagingException if there was a problem accessing the store
+     */
+    public synchronized Message[] search(SearchTerm term) throws MessagingException {
+        // only allowed on open folders
+        checkOpen();
+
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // just search everything
+            int[] messageNumbers = connection.searchMailbox(term);
+            return resolveMessages(messageNumbers);
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+
+
+
+    /**
+     * Search the supplied messages for those that match the supplied criteria;
+     * messages must belong to this folder.
+     * The default implementation iterates through the messages, returning those
+     * whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true;
+     * subclasses may provide a more efficient implementation.
+     *
+     * @param term the search criteria
+     * @param messages the messages to search
+     * @return an array containing messages that match the criteria
+     * @throws MessagingException if there was a problem accessing the store
+     */
+    public synchronized Message[] search(SearchTerm term, Message[] messages) throws MessagingException {
+        // only allowed on open folders
+        checkOpen();
+
+        // turn this into a string specifier for these messages.  We'll weed out the expunged messages first.
+        String messageSet = generateMessageSet(messages);
+
+        // If we have no "live" messages to search, just return now.  We're required to return a non-null
+        // value, so give an empy array back.
+        if (messageSet == null) {
+            return new Message[0];
+        }
+
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+
+            // now go do the search.
+            int[] messageNumbers = connection.searchMailbox(messageSet, term);
+            return resolveMessages(messageNumbers);
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+
+    /**
+     * Get the UID validity value for this Folder.
+     * 
+     * @return The current UID validity value, as a long. 
+     * @exception MessagingException
+     */
+    public synchronized long getUIDValidity() throws MessagingException
+    {
+        // get the latest status to make sure we have the  
+        // most current. 
+        refreshStatus(true); 
+        return uidValidity; 
+    }
+
+    /**
+     * Retrieve a message using the UID rather than the 
+     * message sequence number.  Returns null if the message
+     * doesn't exist.
+     * 
+     * @param uid    The target UID.
+     * 
+     * @return the Message object.  Returns null if the message does
+     *         not exist.
+     * @exception MessagingException
+     */
+    public synchronized Message getMessageByUID(long uid) throws MessagingException
+    {
+        // only allowed on open folders
+        checkOpen();
+        
+        Long key = new Long(uid); 
+        // first check to see if we have a cached value for this 
+        synchronized(messages) {
+            Message msg = (Message)uidCache.get(key); 
+            if (msg != null) {
+                return msg; 
+            }
+        }
+
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // locate the message identifier 
+            IMAPUid imapuid = connection.getSequenceNumberForUid(uid);
+            // if nothing is returned, the message doesn't exist 
+            if (imapuid == null) {
+                return null; 
+            }
+            
+            
+            // retrieve the actual message object and place this in the UID cache
+            return retrieveMessageByUid(key, imapuid.messageNumber); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+
+    /**
+     * Get a series of messages using a UID range.  The 
+     * special value LASTUID can be used to mark the 
+     * last available message.
+     * 
+     * @param start  The start of the UID range.
+     * @param end    The end of the UID range.  The special value
+     *               LASTUID can be used to request all messages up
+     *               to the last UID.
+     * 
+     * @return An array containing all of the messages in the 
+     *         range.
+     * @exception MessagingException
+     */
+    public synchronized Message[] getMessagesByUID(long start, long end) throws MessagingException
+    {
+        // only allowed on open folders
+        checkOpen();
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // locate the message identifier 
+            List uids = connection.getSequenceNumbersForUids(start, end);
+            Message[] msgs = new Message[uids.size()];
+            
+            // fill in each of the messages based on the returned value 
+            for (int i = 0; i < msgs.length; i++) {
+                IMAPUid uid = (IMAPUid)uids.get(i); 
+                msgs[i] = retrieveMessageByUid(new Long(uid.uid), uid.messageNumber);
+            }
+            
+            return msgs; 
+        } finally {
+            releaseConnection(connection); 
+        }
+        
+        
+    }
+
+    /**
+     * Retrieve a set of messages by explicit UIDs.  If 
+     * any message in the list does not exist, null 
+     * will be returned for the corresponding item.
+     * 
+     * @param ids    An array of UID values to be retrieved.
+     * 
+     * @return An array of Message items the same size as the ids
+     *         argument array.  This array will contain null
+     *         entries for any UIDs that do not exist.
+     * @exception MessagingException
+     */
+    public synchronized Message[] getMessagesByUID(long[] ids) throws MessagingException
+    {
+        // only allowed on open folders
+        checkOpen();
+        
+        Message[] msgs = new Message[ids.length];
+        
+        for (int i = 0; i < msgs.length; i++) {
+            msgs[i] = getMessageByUID(ids[i]); 
+        }
+        
+        return msgs; 
+    }
+
+    /**
+     * Retrieve the UID for a message from this Folder.
+     * The argument Message MUST belong to this Folder
+     * instance, otherwise a NoSuchElementException will 
+     * be thrown.
+     * 
+     * @param message The target message.
+     * 
+     * @return The UID associated with this message.
+     * @exception MessagingException
+     */
+    public synchronized long getUID(Message message) throws MessagingException
+    {
+        // verify this actually is in this folder. 
+        checkMessageFolder(message); 
+        IMAPMessage msg = (IMAPMessage)message; 
+        
+        // we might already know this bit of information 
+        if (msg.getUID() != -1) {
+            return msg.getUID(); 
+        }
+
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // locate the message identifier 
+            IMAPUid imapuid = connection.getUidForSequenceNumber(msg.getMessageNumber());
+            // if nothing is returned, the message doesn't exist 
+            if (imapuid == null) {
+                return -1;
+            }
+            // cache this information now that we've gotten it. 
+            addToUidCache(new Long(imapuid.uid), getMessage(imapuid.messageNumber));
+            // return the UID information. 
+            return imapuid.uid; 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+    
+    /**
+     * Retrieve a message from a UID/message mapping.
+     * 
+     * @param key       The UID key used for the mapping.
+     * @param msgNumber The message sequence number.
+     * 
+     * @return The Message object corresponding to the message.
+     * @exception MessagingException
+     */
+    protected synchronized Message retrieveMessageByUid(Long key, int msgNumber) throws MessagingException
+    {
+        synchronized (messages) {
+            // first check the cache...this might have already been added. 
+            Message msg = (Message)uidCache.get(key); 
+            if (msg != null) {
+                return msg; 
+            }
+            
+            // retrieve the message by sequence number 
+            msg = getMessage(msgNumber); 
+            // add this to our UID mapping cache. 
+            addToUidCache(key, msg); 
+            return msg; 
+        }
+    }
+    
+    
+    /**
+     * Add a message to the UID mapping cache, ensuring that 
+     * the UID value is updated.
+     * 
+     * @param key    The UID key.
+     * @param msg    The message to add.
+     */
+    protected void addToUidCache(Long key, Message msg) {  
+        synchronized (messages) {
+            ((IMAPMessage)msg).setUID(key.longValue());  
+            uidCache.put(key, msg); 
+        }
+    }
+    
+    
+    /**
+     * Append a single message to the IMAP Folder.
+     * 
+     * @param msg    The message to append.
+     * 
+     * @exception MessagingException
+     */
+    protected synchronized void appendMessage(Message msg) throws MessagingException 
+    {
+        // sort out the dates.  If no received date, use the sent date. 
+        Date date = msg.getReceivedDate(); 
+        if (date == null) {
+            date = msg.getSentDate(); 
+        }
+        
+        Flags flags = msg.getFlags(); 
+        
+        // convert the message into an array of bytes we can attach as a literal. 
+        ByteArrayOutputStream out = new ByteArrayOutputStream(); 
+        
+        try {
+            msg.writeTo(out); 
+        } catch (IOException e) {
+        }
+        
+        // now issue the append command 
+        IMAPConnection connection = getConnection(); 
+        try {
+            connection.appendMessage(getFullName(), date, flags, out.toByteArray()); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+
+    
+    /**
+     * Retrieve the list of matching groups from the IMAP server using the LIST
+     * or LSUB command. The server does the wildcard matching for us.
+     *
+     * @param pattern
+     *            The pattern string (in wildmat format) used to match.
+     *
+     * @return An array of folders for the matching groups.
+     */
+    protected synchronized Folder[] filterFolders(String pattern, boolean subscribed) throws MessagingException {
+        IMAPConnection connection = getConnection(); 
+        // this is used to filter out our own folder from the search 
+        String root = fullname + getSeparator();
+        
+        List responses = null;
+        try {             
+
+
+            if (subscribed) {
+                // get the lsub response for this folder.
+                responses = connection.listSubscribed(root, pattern);
+            }
+            else {
+                // grab using the LIST command.
+                responses = connection.list(root, pattern);
+            }
+        } finally {
+            releaseConnection(connection); 
+        }
+
+        List folders = new ArrayList();
+
+        for (int i = 0; i < responses.size(); i++) {
+            IMAPListResponse response = (IMAPListResponse)responses.get(i);
+            // if a full wildcard is specified, the root folder can be returned too.  Make sure we
+            // filter that one out.
+            if (!response.mailboxName.equals(root)) {
+                IMAPFolder folder = new IMAPFolder((IMAPStore)store, response.mailboxName, response.separator);
+                folders.add(folder);
+            }
+        }
+
+        // convert into an array and return
+        return (Folder[])folders.toArray(new Folder[folders.size()]);
+    }
+
+
+    /**
+     * Test if a folder can hold sub folders.
+     *
+     * @return True if the folder is allowed to have subfolders.
+     */
+    protected synchronized boolean holdsFolders() throws MessagingException {
+        checkFolderValidity();
+        return (folderType & HOLDS_FOLDERS) != 0;
+    }
+
+
+    /**
+     * Validate that a target message number is considered valid
+     * by the IMAP server.  If outside of the range we currently
+     * are a ware of, we'll ping the IMAP server to see if there
+     * have been any updates.
+     *
+     * @param messageNumber
+     *               The message number we're checking.
+     *
+     * @exception MessagingException
+     */
+    protected void checkMessageValidity(int messageNumber) throws MessagingException {
+        // lower range for a message is 1.
+        if (messageNumber < 1) {
+            throw new MessagingException("Invalid message number for IMAP folder: " + messageNumber);
+        }
+        // if within our current known range, we'll accept this
+        if (messageNumber <= maxSequenceNumber) {
+            return;
+        }
+        
+        IMAPConnection connection = getConnection(); 
+
+        synchronized (this) {
+            try {
+                // ping the server to see if there's any updates to process.  The updates are handled
+                // by the response handlers.
+                connection.updateMailboxStatus();
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+
+        // still out of range?
+        if (messageNumber > maxSequenceNumber) {
+            throw new MessagingException("Message " + messageNumber + " does not exist on server");
+        }
+    }
+
+
+	/**
+	 * Below is a list of convenience methods that avoid repeated checking for a
+	 * value and throwing an exception
+	 */
+
+    /**
+     * Ensure the folder is open.  Throws a MessagingException
+     * if not in the correct state for the operation.
+     * 
+     * @exception IllegalStateException
+     */
+    protected void checkOpen() throws IllegalStateException {
+		if (!folderOpen){
+		    throw new IllegalStateException("Folder is not Open");
+		}
+    }
+
+    /**
+     * Ensure the folder is not open for operations
+     * that require the folder to be closed.
+     * 
+     * @exception IllegalStateException
+     */
+    protected void checkClosed() throws IllegalStateException {
+		if (folderOpen){
+		    throw new IllegalStateException("Folder is Open");
+		}
+    }
+
+    /**
+     * Ensure that the folder is open for read/write mode before doing
+     * an operation that would make a change.
+     *
+     * @exception IllegalStateException
+     */
+    protected void checkReadWrite() throws IllegalStateException {
+        if (mode != READ_WRITE) {
+		    throw new IllegalStateException("Folder is opened READY_ONLY");
+        }
+    }
+
+
+    /**
+     * Check that the folder is open and in read/write mode.
+     *
+     * @exception IllegalStateException
+     */
+    protected void checkOpenReadWrite() throws IllegalStateException {
+        checkOpen();
+        checkReadWrite();
+    }
+
+
+
+    /**
+     * Notify the message changed listeners that a 
+     * message contained in the folder has been updated.
+     * 
+     * @param type   The type of update made to the message.
+     * @param m      The message that was updated.
+     * 
+     * @see javax.mail.Folder#notifyMessageChangedListeners(int, javax.mail.Message)
+     */
+    public void notifyMessageChangedListeners(int type, Message m) {
+    	super.notifyMessageChangedListeners(type, m);
+    }
+
+    
+    /**
+     * Retrieve the connection attached to this folder.  Throws an
+     * exception if we don't have an active connection.
+     *
+     * @return The current connection object.
+     * @exception MessagingException
+     */
+    protected synchronized IMAPConnection getConnection() throws MessagingException {
+        // don't have an open connection yet?  Just request a pool connection. 
+        if (currentConnection == null) {
+            // request a connection from the central store. 
+            IMAPConnection connection = ((IMAPStore)store).getFolderConnection(this);  
+            // we need to make ourselves a handler of unsolicited responses 
+            connection.addResponseHandler(this); 
+            return connection;
+        }
+        // we have a connection for our use.  Just return it. 
+        return currentConnection; 
+    }
+    
+    
+    /**
+     * Release our connection back to the Store.
+     * 
+     * @param connection The connection to release.
+     * 
+     * @exception MessagingException
+     */
+    protected void releaseConnection(IMAPConnection connection) throws MessagingException {
+        // This is a bit of a pain.  We need to delay processing of the 
+        // unsolicited responses until after each user of the connection has 
+        // finished processing the expected responses.  We need to do this because 
+        // the unsolicited responses may include EXPUNGED messages.  The EXPUNGED 
+        // messages will alter the message sequence numbers for the messages in the 
+        // cache.  Processing the EXPUNGED messages too early will result in 
+        // updates getting applied to the wrong message instances.  So, as a result, 
+        // we delay that stage of the processing until all expected responses have 
+        // been handled.  
+        
+        // process any pending messages before returning. 
+        connection.processPendingResponses(); 
+        // if no cached connection or this is somehow different from the cached one, just 
+        // return it. 
+        if (currentConnection == null || connection != currentConnection) {
+            connection.removeResponseHandler(this); 
+            ((IMAPStore)store).releaseFolderConnection(this, connection);  
+        }
+        // if we're open, then we don't have to worry about returning this connection 
+        // to the Store.  This is set up perfectly for our use right now. 
+    }
+    
+    
+    /**
+     * Obtain a connection object for a Message attached to this Folder.  This 
+     * will be the Folder's connection, which is only available if the Folder 
+     * is currently open.
+     * 
+     * @return The connection object for the Message instance to use. 
+     * @exception MessagingException
+     */
+    synchronized IMAPConnection getMessageConnection() throws MessagingException {
+        // if we're not open, the messages can't communicate either
+        if (currentConnection == null) {
+            throw new FolderClosedException(this, "No Folder connections available"); 
+        }
+        // return the current Folder connection.  At this point, we'll be sharing the 
+        // connection between the Folder and the Message (and potentially, other messages).  The 
+        // command operations on the connection are synchronized so only a single command can be 
+        // issued at one time. 
+        return currentConnection; 
+    }
+    
+    
+    /**
+     * Release the connection object back to the Folder instance.  
+     * 
+     * @param connection The connection being released.
+     * 
+     * @exception MessagingException
+     */
+    void releaseMessageConnection(IMAPConnection connection) throws MessagingException {
+        // release it back to ourselves...this will drive unsolicited message processing. 
+        releaseConnection(connection); 
+    }
+
+
+    /**
+     * Refresh the status information on this folder.
+     * 
+     * @param force  Force a status refresh always.
+     * 
+     * @exception MessagingException
+     */
+    protected void refreshStatus(boolean force) throws MessagingException {
+        // first check that any cached status we've received has gotten a little moldy.
+        if (cachedStatus != null) {
+            // if not forcing, check the time out. 
+            if (!force) {
+                if (statusCacheTimeout > 0) {
+                    long age = System.currentTimeMillis() - lastStatusTimeStamp;
+                    if (age < statusCacheTimeout) {
+                        return;                 
+                    }
+                }
+            }
+            // make sure the stale information is cleared out.
+            cachedStatus = null;
+        }
+        
+        IMAPConnection connection = getConnection(); 
+        try {
+            // ping the server for the list information for this folder
+            cachedStatus = connection.getMailboxStatus(fullname);
+            // mark when we got this
+            lastStatusTimeStamp = System.currentTimeMillis();
+        } finally {
+            releaseConnection(connection); 
+        }
+
+        // refresh the internal state from the message information
+        maxSequenceNumber = cachedStatus.messages;
+        recentMessages = cachedStatus.recentMessages;
+        unseenMessages = cachedStatus.unseenMessages;
+        uidValidity = cachedStatus.uidValidity;
+    }
+
+
+    /**
+     * Process an EXPUNGE response for a message, removing the
+     * message from the message cache.
+     * 
+     * @param sequenceNumber
+     *               The sequence number for the expunged message.
+     * 
+     * @return The Message object corresponding to this expunged
+     *         message.
+     * @exception MessagingException
+     */
+    protected synchronized Message expungeMessage(int sequenceNumber) throws MessagingException {
+        // we need to remove this from our message list.  Deletion of a message by 
+        // sequence number shifts the sequence numbers of every message after 
+        // the target.  So we, need to remove the message, update its status, then 
+        // update the sequence numbers of every message after that.  This is a serious 
+        // pain!
+        
+        Message expungedMessage = (Message)messages.remove(sequenceNumber - 1); 
+        // mark the message as expunged. 
+        ((IMAPMessage)expungedMessage).setExpunged(true); 
+        
+        // update the message sequence numbers for every message after the 
+        // expunged one.  NB.  The sequence number is the cache index + 1
+        for (int i = sequenceNumber - 1; i < messages.size(); i++) {
+            IMAPMessage message = (IMAPMessage)messages.get(i); 
+            message.setSequenceNumber(i + 1); 
+        }
+        
+        // have we retrieved a UID for this message?  If we have, then it's in the UID cache and
+        // needs removal from there also
+        long uid = ((IMAPMessage)expungedMessage).getUID();
+        if (uid >= 0) {
+            uidCache.remove(new Long(uid));
+        }
+
+        // adjust the message count downward
+        maxSequenceNumber--;
+        return expungedMessage; 
+    }
+
+
+    /**
+     * Resolve an array of message numbers into an array of the
+     * referenced messages.
+     *
+     * @param messageNumbers
+     *               The array of message numbers (can be null).
+     *
+     * @return An array of Message[] containing the resolved messages from
+     *         the list.  Returns a zero-length array if there are no
+     *         messages to resolve.
+     * @exception MessagingException
+     */
+    protected Message[] resolveMessages(int[] messageNumbers) throws MessagingException {
+        // the connection search returns a null pointer if nothing was found, just convert this into a
+        // null array.
+        if (messageNumbers == null) {
+            return new Message[0];
+        }
+
+        Message[] messages = new Message[messageNumbers.length];
+
+        // retrieve each of the message numbers in turn.
+        for (int i = 0; i < messageNumbers.length; i++) {
+            messages[i] = getMessage(messageNumbers[i]);
+        }
+
+        return messages;
+    }
+    
+    /**
+     * Generate a message set string from a List of messages rather than an 
+     * array.
+     * 
+     * @param messages The List of messages.
+     * 
+     * @return The evaluated message set string. 
+     * @exception MessagingException
+     */
+    protected String generateMessageSet(List messages) throws MessagingException {
+        Message[] msgs = (Message[])messages.toArray(new Message[messages.size()]); 
+        return generateMessageSet(msgs); 
+    }
+
+
+    /**
+     * Take an array of messages and generate a String <message set>
+     * argument as specified by RFC 2060.  The message set argument
+     * is a comma-separated list of message number ranges.  A
+     * single element range is just one number.  A longer range is
+     * a pair of numbers separated by a ":".  The generated string
+     * should not have any blanks.  This will attempt to locate
+     * consequetive ranges of message numbers, but will only do this
+     * for messages that are already ordered in the array (i.e., we
+     * don't try to sort).  Expunged messages are excluded from the
+     * search, since they don't exist anymore.  A valid search string
+     * will look something like this:
+     *
+     *    "3,6:10,15,21:35"
+     *
+     * @param messages The array of messages we generate from.
+     *
+     * @return A string formatted version of these message identifiers that
+     *         can be used on an IMAP command.
+     */
+    protected String generateMessageSet(Message[] messages) throws MessagingException {
+        StringBuffer set = new StringBuffer();
+
+        for (int i = 0; i < messages.length; i++) {
+            // first scan the list looking for a "live" message.
+            IMAPMessage start = (IMAPMessage)messages[i];
+            if (!start.isExpunged()) {
+
+                // we can go ahead and add this to the list now.  If we find this is the start of a
+                // range, we'll tack on the ":end" bit once we find the last message in the range.
+                if (set.length() != 0) {
+                    // only append the comma if not the first element of the list
+                    set.append(',');
+                }
+
+                // append the first number.  NOTE:  We append this directly rather than 
+                // use appendInteger(), which appends it using atom rules. 
+                set.append(Integer.toString(start.getSequenceNumber()));
+
+                // ok, we have a live one.  Now scan the list from here looking for the end of
+                // a range of consequetive messages.
+                int endIndex = -1; ;
+                // get the number we're checking against.
+                int previousSequence = start.getSequenceNumber();
+                for (int j = i + 1; j < messages.length; j++) {
+                    IMAPMessage message = (IMAPMessage)messages[j];
+                    if (!message.isExpunged()) {
+                        // still consequetive?
+                        if (message.getSequenceNumber() == previousSequence + 1) {
+                            // step this for the next check.
+                            previousSequence++;
+                            // record this as the current end of the range.
+                            endIndex = j;
+                        }
+                        else {
+                            // found a non-consequetive one, stop here
+                            break;
+                        }
+                    }
+                }
+
+                // have a range end point?  Add the range specifier and step the loop index point
+                // to skip over this
+                if (endIndex != -1) {
+                    // pick up the scan at the next location
+                    i = endIndex;
+
+                    set.append(':');
+                    set.append(Integer.toString(((IMAPMessage)messages[endIndex]).getSequenceNumber()));
+                }
+            }
+        }
+
+        // return null for an empty list. This is possible because either an empty array has been handed to
+        // us or all of the messages in the array have been expunged.
+        if (set.length() == 0) {
+            return null;
+        }
+        return set.toString();
+    }
+    
+    /**
+     * Verify that this folder exists on the server before 
+     * performning an operation that requires a valid 
+     * Folder instance. 
+     * 
+     * @exception MessagingException
+     */
+    protected void checkFolderValidity() throws MessagingException {
+        // if we are holding a current listinfo response, then 
+        // we have chached existance information.  In that case, 
+        // all of our status is presumed up-to-date and we can go 
+        // with that.  If we don't have the information, then we 
+        // ping the server for it. 
+        if (listInfo == null && !exists()) {
+            throw new FolderNotFoundException(this, "Folder " + fullname + " not found on server"); 
+        }
+    }
+    
+    
+    /**
+     * Check if a Message is properly within the target 
+     * folder. 
+     * 
+     * @param msg    The message we're checking.
+     * 
+     * @exception MessagingException
+     */
+    protected void checkMessageFolder(Message msg) throws MessagingException {
+        if (msg.getFolder() != this) {
+            throw new NoSuchElementException("Message is not within the target Folder"); 
+        }
+    }
+
+
+    /**
+     * Search a list of LIST responses for one containing information
+     * for a particular mailbox name.
+     *
+     * @param responses The list of responses.
+     * @param name      The desired mailbox name.
+     *
+     * @return The IMAPListResponse information for the requested name.
+     */
+    protected IMAPListResponse findListResponse(List responses, String name) {
+        for (int i = 0; i < responses.size(); i++) {
+            IMAPListResponse response = (IMAPListResponse)responses.get(i);
+            if (response.mailboxName.equals(name)) {
+                return response;
+            }
+        }
+        return null;
+    }
+    
+    
+    /**
+     * Protected class intended for subclass overrides.  For normal folders, 
+     * the mailbox name is fullname.  For Namespace root folders, the mailbox
+     * name is the prefix + separator.  
+     * 
+     * @return The string name to use as the mailbox name for exists() and issubscribed() 
+     *         calls.
+     */
+    protected String getMailBoxName() {
+        return fullname; 
+    }
+    
+    /**
+     * Handle an unsolicited response from the server.  Most unsolicited responses 
+     * are replies to specific commands sent to the server.  The remainder must 
+     * be handled by the Store or the Folder using the connection.  These are 
+     * critical to handle, as events such as expunged messages will alter the 
+     * sequence numbers of the live messages.  We need to keep things in sync.
+     * 
+     * @param response The UntaggedResponse to process.
+     * 
+     * @return true if we handled this response and no further handling is required.  false
+     *         means this one wasn't one of ours.
+     */
+    public boolean handleResponse(IMAPUntaggedResponse response) {
+        // "you've got mail".  The message count has been updated.  There 
+        // are two posibilities.  Either there really are new messages, or 
+        // this is an update following an expunge.  If there are new messages, 
+        // we need to update the message cache and broadcast the change to 
+        // any listeners.
+        if (response.isKeyword("EXISTS")) {
+            // we need to update our cache, and also retrieve the new messages and 
+            // send them out in a broadcast update. 
+            int oldCount = maxSequenceNumber; 
+            maxSequenceNumber = ((IMAPSizeResponse)response).getSize(); 
+            populateMessageCache();  
+            // has the size grown?  We have to send the "you've got mail" announcement. 
+            if (oldCount < maxSequenceNumber) {
+                try {
+                    Message[] messages = getMessages(oldCount + 1, maxSequenceNumber); 
+                    notifyMessageAddedListeners(messages); 
+                } catch (MessagingException e) {
+                    // should never happen in this context 
+                }
+            }
+            return true; 
+        }
+        // "you had mail".  A message was expunged from the server.  This MUST 
+        // be processed immediately, as any subsequent expunge messages will 
+        // shift the message numbers as a result of previous messages getting 
+        // removed.  We need to keep our internal cache in sync with the server. 
+        else if (response.isKeyword("EXPUNGE")) {
+            int messageNumber = ((IMAPSizeResponse)response).getSize(); 
+            try {
+                Message message = expungeMessage(messageNumber); 
+
+                // broadcast the message update. 
+                notifyMessageRemovedListeners(false, new Message[] {message}); 
+            } catch (MessagingException e) {
+            }
+            // we handled this one. 
+            return true; 
+        }
+        // just an update of recently arrived stuff?  Just update the field. 
+        else if (response.isKeyword("RECENT")) {
+            recentMessages = ((IMAPSizeResponse)response).getSize();  
+            return true; 
+        }
+        // The spec is not particularly clear what types of unsolicited 
+        // FETCH response can be sent.  The only one that is specifically 
+        // spelled out is flag updates.  If this is one of those, then 
+        // handle it. 
+        else if (response.isKeyword("FETCH")) {
+            IMAPFetchResponse fetch = (IMAPFetchResponse)response;            
+            IMAPFlags flags = (IMAPFlags)fetch.getDataItem(IMAPFetchDataItem.FLAGS); 
+            // if this is a flags response, get the message and update 
+            if (flags != null) {
+                try {
+                    // get the updated message and update the internal state. 
+                    IMAPMessage message = (IMAPMessage)getMessage(fetch.sequenceNumber); 
+                    // this shouldn't happen, but it might have been expunged too. 
+                    if (message != null) {
+                        message.updateMessageInformation(fetch); 
+                    }
+                    notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
+                } catch (MessagingException e) {
+                }
+                return true; 
+            }
+        }
+        // this is a BYE response on our connection.  This forces us to close, but 
+        // when we return the connection, the pool needs to get rid of it. 
+        else if (response.isKeyword("BYE")) {
+            // this is essentially a close event.  We need to clean everything up 
+            // and make sure our connection is not returned to the general pool. 
+            try {
+                cleanupFolder(false, true); 
+            } catch (MessagingException e) {
+            }
+            return true; 
+        }
+        
+        // not a response the folder knows how to deal with. 
+        return false; 
+    }
+    
+// The following set of methods are extensions that exist in the Sun implementation.  They     
+// match the Sun version in intent, but are not 100% compatible because the Sun implementation     
+// uses com.sun.* class instances as opposed to the org.apache.geronimo.* classes.     
+    
+    
+    
+    /**
+     *   Remove an entry from the access control list for this folder.
+     * 
+     * @param acl    The ACL element to remove.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void removeACL(ACL acl) throws MessagingException {
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // the connection does the heavy lifting 
+            connection.removeACLRights(fullname, acl); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+    
+    
+    /**
+     *   Add an entry to the access control list for this folder.
+     * 
+     * @param acl    The new ACL to add.
+     */
+    public synchronized void addACL(ACL acl) throws MessagingException {
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // the connection does the heavy lifting 
+            connection.setACLRights(fullname, acl); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+    
+    
+    /**
+     * Add Rights to a given ACL entry.
+     * 
+     * @param acl    The target ACL to update.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void addRights(ACL acl) throws MessagingException {
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // the connection does the heavy lifting 
+            connection.addACLRights(fullname, acl); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+    
+    
+    /**
+     * Remove ACL Rights from a folder.
+     * 
+     * @param acl    The ACL describing the Rights to remove.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void removeRights(ACL acl) throws MessagingException {
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // the connection does the heavy lifting 
+            connection.removeACLRights(fullname, acl); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+    
+    
+    /**
+     *   List the rights associated with a given name.
+     * 
+     * @param name   The user name for the Rights.
+     * 
+     * @return The set of Rights associated with the user name.
+     * @exception MessagingException
+     */
+    public synchronized Rights[] listRights(String name) throws MessagingException {
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // the connection does the heavy lifting 
+            return connection.listACLRights(fullname, name); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+    
+    
+    /**
+     *   List the rights for the currently authenticated user.
+     * 
+     * @return The set of Rights for the current user.
+     * @exception MessagingException
+     */
+    public synchronized Rights myRights() throws MessagingException {
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // the connection does the heavy lifting 
+            return connection.getMyRights(fullname); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+    
+    /**
+     * Get the quota values assigned to the current folder.
+     * 
+     * @return The Quota information for the folder.
+     * @exception MessagingException
+     */
+    public synchronized Quota[] getQuota() throws MessagingException {
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // the connection does the heavy lifting 
+            return connection.fetchQuotaRoot(fullname); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+    
+    /**
+     * Set the quota value for a quota root
+     * 
+     * @param quota  The new quota information to set.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void setQuota(Quota quota) throws MessagingException {
+        // ask the store to kindly hook us up with a connection.
+        IMAPConnection connection = getConnection(); 
+
+        try {
+            // the connection does the heavy lifting 
+            connection.setQuota(quota); 
+        } finally {
+            releaseConnection(connection); 
+        }
+    }
+    
+    /**
+     * Get the set of attributes defined for the folder
+     * as the set of capabilities returned when the folder 
+     * was opened.
+     * 
+     * @return The set of attributes associated with the folder.
+     * @exception MessagingException
+     */
+    public synchronized String[] getAttributes() throws MessagingException {
+        // if we don't have the LIST command information for this folder yet, 
+        // call exists() to force this to be updated so we can return. 
+        if (listInfo == null) {
+            // return a null reference if this is not valid. 
+            if (!exists()) {
+                return null; 
+            }
+        }
+        // return a copy of the attributes array. 
+        return (String[])listInfo.attributes.clone(); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessage.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessage.java
new file mode 100644
index 0000000..b425dbc
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessage.java
@@ -0,0 +1,1298 @@
+/**
+ * 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;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.activation.DataHandler;
+
+import javax.mail.Address; 
+import javax.mail.FetchProfile;
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.Header;
+import javax.mail.IllegalWriteException;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.MessageRemovedException;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.UIDFolder;
+import javax.mail.event.MessageChangedEvent;
+
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MailDateFormat;     
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeUtility;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPBody; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPEnvelope;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchDataItem;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchResponse;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPInternalDate;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPInternetHeader;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPMessageSize;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPUid;
+
+/**
+ * IMAP implementation of javax.mail.internet.MimeMessage
+ *
+ * Only the most basic information is given and
+ * Message objects created here is a light-weight reference to the actual Message
+ * As per the JavaMail spec items from the actual message will get filled up on demand
+ *
+ * If some other items are obtained from the server as a result of one call, then the other
+ * details are also processed and filled in. For ex if RETR is called then header information
+ * will also be processed in addition to the content
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPMessage extends MimeMessage {
+    // the Store we're stored in (which manages the connection and other stuff).
+    protected IMAPStore store;
+
+    // the IMAP server sequence number (potentially updated during the life of this message object).
+    protected int sequenceNumber;
+    // the IMAP uid value;
+    protected long uid = -1;
+    // the section identifier.  This is only really used for nested messages.  The toplevel version  
+    // will be null, and each nested message will set the appropriate part identifier 
+    protected String section; 
+    // the loaded message envelope (delayed until needed)
+    protected IMAPEnvelope envelope;
+    // the body structure information (also lazy loaded).
+    protected IMAPBodyStructure bodyStructure;
+    // the IMAP INTERNALDATE value.
+    protected Date receivedDate;
+    // the size item, which is maintained separately from the body structure 
+    // as it can be retrieved without getting the body structure
+    protected int size; 
+    // turned on once we've requested the entire header set. 
+    protected boolean allHeadersRetrieved = false; 
+    // singleton date formatter for this class.
+    static protected MailDateFormat dateFormat = new MailDateFormat();
+
+
+    /**
+     * Contruct an IMAPMessage instance.
+     * 
+     * @param folder   The hosting folder for the message.
+     * @param store    The Store owning the article (and folder).
+     * @param msgnum   The article message number.  This is assigned by the Folder, and is unique
+     *                 for each message in the folder.  The message numbers are only valid
+     *                 as long as the Folder is open.
+     * @param sequenceNumber The IMAP server manages messages by sequence number, which is subject to
+     *                 change whenever messages are expunged.  This is the server retrieval number
+     *                 of the message, which needs to be synchronized with status updates
+     *                 sent from the server.
+     * 
+     * @exception MessagingException
+     */
+	IMAPMessage(IMAPFolder folder, IMAPStore store, int msgnum, int sequenceNumber) {
+		super(folder, msgnum);
+        this.sequenceNumber = sequenceNumber;
+		this.store = store;
+        // The default constructor creates an empty Flags item.  We need to clear this out so we 
+        // know if the flags need to be fetched from the server when requested.  
+        flags = null;
+        // make sure this is a totally fresh set of headers.  We'll fill things in as we retrieve them.
+        headers = new InternetHeaders();
+	}
+
+
+    /**
+     * Override for the Message class setExpunged() method to allow
+     * us to do additional cleanup for expunged messages.
+     *
+     * @param value  The new expunge setting.
+     */
+    public void setExpunged(boolean value) {
+        // super class handles most of the details
+        super.setExpunged(value);
+        // if we're now expunged, this removes us from the server message sequencing scheme, so
+        // we need to invalidate the sequence number.
+        if (isExpunged()) {
+            sequenceNumber = -1;
+        }
+    }
+    
+
+    /**
+     * Return a copy the flags associated with this message.
+     *
+     * @return a copy of the flags for this message
+     * @throws MessagingException if there was a problem accessing the Store
+     */
+    public synchronized Flags getFlags() throws MessagingException {
+        // load the flags, if needed 
+        loadFlags(); 
+        return super.getFlags(); 
+    }
+
+
+    /**
+     * Check whether the supplied flag is set.
+     * The default implementation checks the flags returned by {@link #getFlags()}.
+     *
+     * @param flag the flags to check for
+     * @return true if the flags is set
+     * @throws MessagingException if there was a problem accessing the Store
+     */
+    public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
+        // load the flags, if needed 
+        loadFlags(); 
+        return super.isSet(flag); 
+    }
+
+    /**
+     * Set or clear a flag value.
+     *
+     * @param flags  The set of flags to effect.
+     * @param set    The value to set the flag to (true or false).
+     *
+     * @exception MessagingException
+     */
+    public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
+        // make sure this is in a valid state.
+        checkValidity();
+        
+        // we need to ensure that we're the only ones with access to the folder's 
+        // message cache any time we need to talk to the server.  This needs to be 
+        // held until after we release the connection so that any pending EXPUNGE 
+        // untagged responses are processed before the next time the folder connection is 
+        // used. 
+        synchronized (folder) {
+            IMAPConnection connection = getConnection();
+
+            try {
+                // set the flags for this item and update the 
+                // internal state with the new values returned from the 
+                // server. 
+                flags = connection.setFlags(sequenceNumber, flag, set); 
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+    }
+
+
+    /**
+     * Return an InputStream instance for accessing the 
+     * message content.
+     * 
+     * @return An InputStream instance for accessing the content
+     *         (body) of the message.
+     * @exception MessagingException
+     * @see javax.mail.internet.MimeMessage#getContentStream()
+     */
+	protected InputStream getContentStream() throws MessagingException {
+
+        // no content loaded yet?
+        if (content == null) {
+            // make sure we're still valid
+            checkValidity();
+            // make sure the content is fully loaded
+            loadContent();
+        }
+
+        // allow the super class to handle creating it from the loaded content.
+        return super.getContentStream();
+	}
+
+
+    /**
+     * Write out the byte data to the provided output stream.
+     *
+     * @param out    The target stream.
+     *
+     * @exception IOException
+     * @exception MessagingException
+     */
+    public void writeTo(OutputStream out) throws IOException, MessagingException {
+        // TODO:  The connection is shared on a folder basis, so some of these operations
+        // will need to use a common locking object.
+        // make sure we're still good
+        checkValidity();
+        
+        // we need to ensure that we're the only ones with access to the folder's 
+        // message cache any time we need to talk to the server.  This needs to be 
+        // held until after we release the connection so that any pending EXPUNGE 
+        // untagged responses are processed before the next time the folder connection is 
+        // used. 
+        synchronized (folder) {
+            IMAPConnection connection = getConnection();
+            try {
+                IMAPBody body = connection.fetchBody(sequenceNumber, section);
+                byte[] outData = body.getContent();
+
+                // write out the data.
+                out.write(outData);
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+    }
+	/******************************************************************
+	 * Following is a set of methods that deal with information in the 
+	 * envelope.  These methods ensure the enveloper is loaded and   
+     * retrieve the information.                         
+	 ********************************************************************/
+     
+
+    /**
+     * Get the message "From" addresses.  This looks first at the
+     * "From" headers, and no "From" header is found, the "Sender"
+     * header is checked.  Returns null if not found.
+     *
+     * @return An array of addresses identifying the message from target.  Returns
+     *         null if this is not resolveable from the headers.
+     * @exception MessagingException
+     */
+    public Address[] getFrom() throws MessagingException {
+        // make sure we've retrieved the envelope information.
+        loadEnvelope();
+        // make sure we return a copy of the array so this can't be changed. 
+        Address[] addresses = envelope.from; 
+        if (addresses == null) {
+            return null;
+        }
+        return (Address[])addresses.clone(); 
+    }
+    
+
+    /**
+     * Return the "Sender" header as an address.
+     *
+     * @return the "Sender" header as an address, or null if not present
+     * @throws MessagingException if there was a problem parsing the header
+     */
+    public Address getSender() throws MessagingException {
+        // make sure we've retrieved the envelope information.
+        loadEnvelope();
+        // make sure we return a copy of the array so this can't be changed. 
+        Address[] addresses = envelope.sender; 
+        if (addresses == null) {
+            return null;
+        }
+        // There's only a single sender, despite IMAP potentially returning a list
+        return addresses[0]; 
+    }
+
+    /**
+     * Gets the recipients by type.  Returns null if there are no
+     * headers of the specified type.  Acceptable RecipientTypes are:
+     *
+     *   javax.mail.Message.RecipientType.TO
+     *   javax.mail.Message.RecipientType.CC
+     *   javax.mail.Message.RecipientType.BCC
+     *   javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
+     *
+     * @param type   The message RecipientType identifier.
+     *
+     * @return The array of addresses for the specified recipient types.
+     * @exception MessagingException
+     */
+    public Address[] getRecipients(Message.RecipientType type) throws MessagingException {
+        // make sure we've retrieved the envelope information.
+        loadEnvelope();
+        Address[] addresses = null; 
+        
+        if (type == Message.RecipientType.TO) {
+            addresses = envelope.to; 
+        }
+        else if (type == Message.RecipientType.CC) {
+            addresses = envelope.cc; 
+        }
+        else if (type == Message.RecipientType.BCC) {
+            addresses = envelope.bcc; 
+        }
+        else {
+            // this could be a newsgroup type, which will tickle the message headers. 
+            return super.getRecipients(type);
+        }
+        // make sure we return a copy of the array so this can't be changed. 
+        if (addresses == null) {
+            return null;
+        }
+        return (Address[])addresses.clone(); 
+    }
+
+    /**
+     * Get the ReplyTo address information.  The headers are parsed
+     * using the "mail.mime.address.strict" setting.  If the "Reply-To" header does
+     * not have any addresses, then the value of the "From" field is used.
+     *
+     * @return An array of addresses obtained from parsing the header.
+     * @exception MessagingException
+     */
+    public Address[] getReplyTo() throws MessagingException {
+        // make sure we've retrieved the envelope information.
+        loadEnvelope();
+        // make sure we return a copy of the array so this can't be changed. 
+        Address[] addresses = envelope.replyTo; 
+        if (addresses == null) {
+            return null;
+        }
+        return (Address[])addresses.clone(); 
+    }
+
+    /**
+     * Returns the value of the "Subject" header.  If the subject
+     * is encoded as an RFC 2047 value, the value is decoded before
+     * return.  If decoding fails, the raw string value is
+     * returned.
+     *
+     * @return The String value of the subject field.
+     * @exception MessagingException
+     */
+    public String getSubject() throws MessagingException {
+        // make sure we've retrieved the envelope information.
+        loadEnvelope();
+        
+        if (envelope.subject == null) {
+            return null; 
+        }
+        // the subject could be encoded.  If there is a decoding error, 
+        // return the raw subject string. 
+        try {
+            return MimeUtility.decodeText(envelope.subject); 
+        } catch (UnsupportedEncodingException e) {
+            return envelope.subject; 
+        }
+    }
+
+    /**
+     * Get the value of the "Date" header field.  Returns null if
+     * if the field is absent or the date is not in a parseable format.
+     *
+     * @return A Date object parsed according to RFC 822.
+     * @exception MessagingException
+     */
+    public Date getSentDate() throws MessagingException {
+        // make sure we've retrieved the envelope information.
+        loadEnvelope();
+        // just return that directly 
+        return envelope.date; 
+    }
+
+
+    /**
+     * Get the message received date.
+     *
+     * @return Always returns the formatted INTERNALDATE, if available.
+     * @exception MessagingException
+     */
+    public Date getReceivedDate() throws MessagingException {
+        loadEnvelope();
+        return receivedDate; 
+    }
+
+
+    /**
+     * Retrieve the size of the message content.  The content will
+     * be retrieved from the server, if necessary.
+     *
+     * @return The size of the content.
+     * @exception MessagingException
+     */
+	public int getSize() throws MessagingException {
+        // make sure we've retrieved the envelope information.  We load the 
+        // size when we retrieve that. 
+        loadEnvelope();
+        return size;                          
+	}
+    
+
+    /**
+     * Get a line count for the IMAP message.  This is potentially
+     * stored in the Lines article header.  If not there, we return
+     * a default of -1.
+     *
+     * @return The header line count estimate, or -1 if not retrieveable.
+     * @exception MessagingException
+     */
+    public int getLineCount() throws MessagingException {
+        loadBodyStructure();
+        return bodyStructure.lines; 
+    }
+
+    /**
+     * Return the IMAP in reply to information (retrieved with the
+     * ENVELOPE).
+     *
+     * @return The in reply to String value, if available.
+     * @exception MessagingException
+     */
+    public String getInReplyTo() throws MessagingException {
+        loadEnvelope();
+        return envelope.inReplyTo;
+    }
+
+    /**
+     * Returns the current content type (defined in the "Content-Type"
+     * header.  If not available, "text/plain" is the default.
+     *
+     * @return The String name of the message content type.
+     * @exception MessagingException
+     */
+    public String getContentType() throws MessagingException {
+        loadBodyStructure();
+        return bodyStructure.mimeType.toString(); 
+    }
+
+
+    /**
+     * Tests to see if this message has a mime-type match with the
+     * given type name.
+     *
+     * @param type   The tested type name.
+     *
+     * @return If this is a type match on the primary and secondare portion of the types.
+     * @exception MessagingException
+     */
+    public boolean isMimeType(String type) throws MessagingException {
+        loadBodyStructure();
+        return bodyStructure.mimeType.match(type); 
+    }
+
+    /**
+     * Retrieve the message "Content-Disposition" header field.
+     * This value represents how the part should be represented to
+     * the user.
+     *
+     * @return The string value of the Content-Disposition field.
+     * @exception MessagingException
+     */
+    public String getDisposition() throws MessagingException {
+        loadBodyStructure();
+        if (bodyStructure.disposition != null) {
+            return bodyStructure.disposition.getDisposition(); 
+        }
+        return null; 
+    }
+
+    /**
+     * Decode the Content-Transfer-Encoding header to determine
+     * the transfer encoding type.
+     *
+     * @return The string name of the required encoding.
+     * @exception MessagingException
+     */
+    public String getEncoding() throws MessagingException {
+        loadBodyStructure();
+        return bodyStructure.transferEncoding; 
+    }
+
+    /**
+     * Retrieve the value of the "Content-ID" header.  Returns null
+     * if the header does not exist.
+     *
+     * @return The current header value or null.
+     * @exception MessagingException
+     */
+    public String getContentID() throws MessagingException {
+        loadBodyStructure();
+        return bodyStructure.contentID;         
+    }
+
+    public String getContentMD5() throws MessagingException {
+        loadBodyStructure();
+        return bodyStructure.md5Hash;         
+    }
+    
+    
+    public String getDescription() throws MessagingException {
+        loadBodyStructure();
+        
+        if (bodyStructure.contentDescription == null) {
+            return null; 
+        }
+        // the subject could be encoded.  If there is a decoding error, 
+        // return the raw subject string. 
+        try {
+            return MimeUtility.decodeText(bodyStructure.contentDescription); 
+        } catch (UnsupportedEncodingException e) {
+            return bodyStructure.contentDescription;
+        }
+    }
+
+    /**
+     * Return the content languages associated with this 
+     * message.
+     * 
+     * @return 
+     * @exception MessagingException
+     */
+    public String[] getContentLanguage() throws MessagingException {
+        loadBodyStructure();
+        
+        if (!bodyStructure.languages.isEmpty()) {
+            return (String[])bodyStructure.languages.toArray(new String[bodyStructure.languages.size()]); 
+        }
+        return null; 
+    }
+
+    public String getMessageID() throws MessagingException {
+        loadEnvelope();
+        return envelope.messageID; 
+    }
+    
+    public void setFrom(Address address) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void addFrom(Address[] address) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void setSender(Address address) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void setReplyTo(Address[] address) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void setSubject(String subject) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void setSubject(String subject, String charset) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void setSentDate(Date sent) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void setDisposition(String disposition) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void setContentID(String cid) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+    public void setContentMD5(String md5) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+    public void setDescription(String description) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+    public void setDescription(String description, String charset) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+    public void setContentLanguage(String[] languages) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+    
+
+	/******************************************************************
+	 * Following is a set of methods that deal with headers
+	 * These methods are just overrides on the superclass methods to
+     * allow lazy loading of the header information.
+	 ********************************************************************/
+
+	public String[] getHeader(String name) throws MessagingException {
+        loadHeaders();
+		return headers.getHeader(name);
+	}
+
+	public String getHeader(String name, String delimiter) throws MessagingException {
+        loadHeaders();
+		return headers.getHeader(name, delimiter);
+	}
+
+	public Enumeration getAllHeaders() throws MessagingException {
+        loadHeaders();
+		return headers.getAllHeaders();
+	}
+
+	public Enumeration getMatchingHeaders(String[] names)  throws MessagingException {
+        loadHeaders();
+		return headers.getMatchingHeaders(names);
+	}
+
+	public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
+        loadHeaders();
+		return headers.getNonMatchingHeaders(names);
+	}
+
+	public Enumeration getAllHeaderLines() throws MessagingException {
+        loadHeaders();
+		return headers.getAllHeaderLines();
+	}
+
+	public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
+        loadHeaders();
+		return headers.getMatchingHeaderLines(names);
+	}
+
+	public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
+        loadHeaders();
+		return headers.getNonMatchingHeaderLines(names);
+	}
+
+    // the following are overrides for header modification methods.  These messages are read only,
+    // so the headers cannot be modified.
+    public void addHeader(String name, String value) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+    public void setHeader(String name, String value) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+
+    public void removeHeader(String name) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+    public void addHeaderLine(String line) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+	/**
+	 * We cannot modify these messages
+	 */
+	public void saveChanges() throws MessagingException {
+		throw new IllegalWriteException("IMAP messages are read-only");
+	}
+
+
+    /**
+     * Utility method for synchronizing IMAP envelope information and
+     * the message headers.
+     *
+     * @param header    The target header name.
+     * @param addresses The update addresses.
+     */
+    protected void updateHeader(String header, InternetAddress[] addresses) throws MessagingException {
+        if (addresses != null) {
+            headers.addHeader(header, InternetAddress.toString(envelope.from));
+        }
+    }
+
+    /**
+     * Utility method for synchronizing IMAP envelope information and
+     * the message headers.
+     *
+     * @param header    The target header name.
+     * @param address   The update address.
+     */
+    protected void updateHeader(String header, Address address) throws MessagingException {
+        if (address != null) {
+            headers.setHeader(header, address.toString());
+        }
+    }
+
+    /**
+     * Utility method for synchronizing IMAP envelope information and
+     * the message headers.
+     *
+     * @param header    The target header name.
+     * @param value     The update value.
+     */
+    protected void updateHeader(String header, String value) throws MessagingException {
+        if (value != null) {
+            headers.setHeader(header, value);
+        }
+    }
+
+
+    /**
+     * Create the DataHandler object for this message.
+     *
+     * @return The DataHandler object that processes the content set for this
+     *         message.
+     * @exception MessagingException
+     */
+    public synchronized DataHandler getDataHandler() throws MessagingException {
+        // check the validity and make sure we have the body structure information. 
+        checkValidity();
+        loadBodyStructure();
+        if (dh == null) {
+            // are we working with a multipart message here?
+            if (bodyStructure.isMultipart()) {
+                dh = new DataHandler(new IMAPMultipartDataSource(this, this, section, bodyStructure));
+                return dh;
+            }
+            else if (bodyStructure.isAttachedMessage()) {
+                dh = new DataHandler(new IMAPAttachedMessage(this, section, bodyStructure.nestedEnvelope, bodyStructure.nestedBody), 
+                     bodyStructure.mimeType.toString()); 
+                return dh;
+            }
+        }
+
+        // single part messages get handled the normal way.
+        return super.getDataHandler();
+    }
+
+    public void setDataHandler(DataHandler content) throws MessagingException {
+        throw new IllegalWriteException("IMAP body parts are read-only");
+    }
+
+    /**
+     * Update the message headers from an input stream.
+     *
+     * @param in     The InputStream source for the header information.
+     *
+     * @exception MessagingException
+     */
+    public void updateHeaders(InputStream in) throws MessagingException {
+        // wrap a stream around the reply data and read as headers.
+        headers = new InternetHeaders(in);
+        allHeadersRetrieved = true;
+    }
+    
+    /**
+     * Load the flag set for this message from the server. 
+     * 
+     * @exception MessagingeException
+     */
+    public void loadFlags() throws MessagingException {
+        // make sure this is in a valid state.
+        checkValidity();
+        // if the flags are already loaded, nothing to do 
+        if (flags != null) {
+            return; 
+        }
+        // we need to ensure that we're the only ones with access to the folder's 
+        // message cache any time we need to talk to the server.  This needs to be 
+        // held until after we release the connection so that any pending EXPUNGE 
+        // untagged responses are processed before the next time the folder connection is 
+        // used. 
+        synchronized (folder) {
+            IMAPConnection connection = getConnection();
+
+            try {
+                // fetch the flags for this item. 
+                flags = connection.fetchFlags(sequenceNumber); 
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+    }
+
+
+    /**
+     * Retrieve the message raw message headers from the IMAP server, synchronizing with the existing header set.
+     *
+     * @exception MessagingException
+     */
+    protected synchronized void loadHeaders() throws MessagingException {
+        // don't retrieve if already loaded.
+        if (allHeadersRetrieved) {
+            return;
+        }
+
+        // make sure this is in a valid state.
+        checkValidity();
+        // we need to ensure that we're the only ones with access to the folder's 
+        // message cache any time we need to talk to the server.  This needs to be 
+        // held until after we release the connection so that any pending EXPUNGE 
+        // untagged responses are processed before the next time the folder connection is 
+        // used. 
+        synchronized (folder) {
+            IMAPConnection connection = getConnection();
+
+            try {
+                // get the headers and set 
+                headers = connection.fetchHeaders(sequenceNumber, section);
+                // we have the entire header set, not just a subset. 
+                allHeadersRetrieved = true;                                               
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+    }
+
+
+    /**
+     * Retrieve the message envelope from the IMAP server, synchronizing the headers with the
+     * information.
+     *
+     * @exception MessagingException
+     */                                
+    protected synchronized void loadEnvelope() throws MessagingException {
+        // don't retrieve if already loaded.
+        if (envelope != null) {
+            return;
+        }
+
+        // make sure this is in a valid state.
+        checkValidity();
+        // we need to ensure that we're the only ones with access to the folder's 
+        // message cache any time we need to talk to the server.  This needs to be 
+        // held until after we release the connection so that any pending EXPUNGE 
+        // untagged responses are processed before the next time the folder connection is 
+        // used. 
+        synchronized (folder) {
+            IMAPConnection connection = getConnection();
+            try {
+                // fetch the envelope information for this
+                List fetches = connection.fetchEnvelope(sequenceNumber);
+                // now process all of the fetch responses before releasing the folder lock.  
+                // it's possible that an unsolicited update on another thread might try to 
+                // make an update, causing a potential deadlock. 
+                for (int i = 0; i < fetches.size(); i++) {
+                    // get the returned data items from each of the fetch responses
+                    // and process. 
+                    IMAPFetchResponse fetch = (IMAPFetchResponse)fetches.get(i);
+                    // update the internal info 
+                    updateMessageInformation(fetch); 
+                }
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+    }
+
+
+    /**
+     * Retrieve the message envelope from the IMAP server, synchronizing the headers with the
+     * information.
+     *
+     * @exception MessagingException
+     */
+    protected synchronized void updateEnvelope(IMAPEnvelope envelope) throws MessagingException {
+        // set the envelope item 
+        this.envelope = envelope; 
+
+        // copy header type information from the envelope into the headers.
+        updateHeader("From", envelope.from);
+        if (envelope.sender != null) {
+            // we can only have a single sender, even though the envelope theoretically supports more.
+            updateHeader("Sender", envelope.sender[0]);
+        }
+        updateHeader("To", envelope.to);
+        updateHeader("Cc", envelope.cc);
+        updateHeader("Bcc", envelope.bcc);
+        updateHeader("Reply-To", envelope.replyTo);
+        // NB:  This is already in encoded form, if needed.
+        updateHeader("Subject", envelope.subject);
+        updateHeader("Message-ID", envelope.messageID);
+    }
+
+
+    /**
+     * Retrieve the BODYSTRUCTURE information from the IMAP server.
+     *
+     * @exception MessagingException
+     */
+    protected synchronized void loadBodyStructure() throws MessagingException {
+        // don't retrieve if already loaded.
+        if (bodyStructure != null) {
+            return;
+        }
+
+        // make sure this is in a valid state.
+        checkValidity();
+        // we need to ensure that we're the only ones with access to the folder's 
+        // message cache any time we need to talk to the server.  This needs to be 
+        // held until after we release the connection so that any pending EXPUNGE 
+        // untagged responses are processed before the next time the folder connection is 
+        // used. 
+        synchronized (folder) {
+            IMAPConnection connection = getConnection();
+            try {
+                // fetch the envelope information for this
+                bodyStructure = connection.fetchBodyStructure(sequenceNumber);
+                // go update all of the information 
+            } finally {
+                releaseConnection(connection); 
+            }
+            
+            // update this before we release the folder lock so we can avoid 
+            // deadlock. 
+            updateBodyStructure(bodyStructure); 
+        }
+    }
+
+
+    /**
+     * Update the BODYSTRUCTURE information from the IMAP server.
+     *
+     * @exception MessagingException
+     */
+    protected synchronized void updateBodyStructure(IMAPBodyStructure structure) throws MessagingException {
+        // save the reference. 
+        bodyStructure = structure; 
+        // now update various headers with the information from the body structure 
+
+        // now update header information with the body structure data.
+        if (bodyStructure.lines != -1) {
+            updateHeader("Lines", Integer.toString(bodyStructure.lines));
+        }
+
+        // languages are a little more complicated
+        if (bodyStructure.languages != null) {
+            // this is a duplicate of what happens in the super class, but 
+            // the superclass methods call setHeader(), which we override and 
+            // throw an exception for.  We need to set the headers ourselves. 
+            if (bodyStructure.languages.size() == 1) {
+                updateHeader("Content-Language", (String)bodyStructure.languages.get(0));
+            }
+            else {
+                StringBuffer buf = new StringBuffer(bodyStructure.languages.size() * 20);
+                buf.append(bodyStructure.languages.get(0));
+                for (int i = 1; i < bodyStructure.languages.size(); i++) {
+                    buf.append(',').append(bodyStructure.languages.get(i));
+                }
+                updateHeader("Content-Language", buf.toString());
+            }
+        }
+
+        updateHeader("Content-Type", bodyStructure.mimeType.toString());
+        if (bodyStructure.disposition != null) {
+            updateHeader("Content-Disposition", bodyStructure.disposition.toString());
+        }
+
+        updateHeader("Content-Transfer-Encoding", bodyStructure.transferEncoding);
+        updateHeader("Content-ID", bodyStructure.contentID);
+        // NB:  This is already in encoded form, if needed.
+        updateHeader("Content-Description", bodyStructure.contentDescription);
+    }
+
+
+    /**
+     * Load the message content into the Message object.
+     *
+     * @exception MessagingException
+     */
+    protected void loadContent() throws MessagingException {
+        // if we've loaded this already, just return
+        if (content != null) {
+            return;
+        }
+        
+        // we need to ensure that we're the only ones with access to the folder's 
+        // message cache any time we need to talk to the server.  This needs to be 
+        // held until after we release the connection so that any pending EXPUNGE 
+        // untagged responses are processed before the next time the folder connection is 
+        // used. 
+        synchronized (folder) {
+            IMAPConnection connection = getConnection();
+            try {
+                // load the content from the server. 
+                content = connection.fetchContent(getSequenceNumber(), section); 
+            } finally {
+                releaseConnection(connection); 
+            }
+        }
+    }
+
+    
+    /**
+     * Retrieve the sequence number assigned to this message.
+     *
+     * @return The messages assigned sequence number.  This maps back to the server's assigned number for
+     * this message.
+     */
+    int getSequenceNumber() {
+        return sequenceNumber;
+    }
+    
+    /**
+     * Set the sequence number for the message.  This 
+     * is updated whenever messages get expunged from 
+     * the folder. 
+     * 
+     * @param s      The new sequence number.
+     */
+    void setSequenceNumber(int s) {
+        sequenceNumber = s; 
+    }
+
+
+    /**
+     * Retrieve the message UID value.
+     *
+     * @return The assigned UID value, if we have the information.
+     */
+    long getUID() {
+        return uid;
+    }
+
+    /**
+     * Set the message UID value.
+     *
+     * @param uid    The new UID value.
+     */
+    void setUID(long uid) {
+        this.uid = uid;
+    }
+
+    
+    /**
+     * get the current connection pool attached to the folder.  We need
+     * to do this dynamically, to A) ensure we're only accessing an
+     * currently open folder, and B) to make sure we're using the
+     * correct connection attached to the folder.
+     *
+     * @return A connection attached to the hosting folder.
+     */
+    protected IMAPConnection getConnection() throws MessagingException {
+        // the folder owns everything.
+        return ((IMAPFolder)folder).getMessageConnection();
+    }
+    
+    /**
+     * Release the connection back to the Folder after performing an operation 
+     * that requires a connection.
+     * 
+     * @param connection The previously acquired connection.
+     */
+    protected void releaseConnection(IMAPConnection connection) throws MessagingException {
+        // the folder owns everything.
+        ((IMAPFolder)folder).releaseMessageConnection(connection);
+    }
+
+
+    /**
+     * Check the validity of the current message.  This ensures that
+     * A) the folder is currently open, B) that the message has not
+     * been expunged (after getting the latest status from the server).
+     *
+     * @exception MessagingException
+     */
+    protected void checkValidity() throws MessagingException {
+        checkValidity(false); 
+    }
+
+
+    /**
+     * Check the validity of the current message.  This ensures that
+     * A) the folder is currently open, B) that the message has not
+     * been expunged (after getting the latest status from the server).
+     *
+     * @exception MessagingException
+     */
+    protected void checkValidity(boolean update) throws MessagingException {
+        // we need to ensure that we're the only ones with access to the folder's 
+        // message cache any time we need to talk to the server.  This needs to be 
+        // held until after we release the connection so that any pending EXPUNGE 
+        // untagged responses are processed before the next time the folder connection is 
+        // used. 
+        if (update) {
+            synchronized (folder) {
+                // have the connection update the folder status.  This might result in this message
+                // changing its state to expunged.  It might also result in an exception if the
+                // folder has been closed.
+                IMAPConnection connection = getConnection(); 
+
+                try {
+                    connection.updateMailboxStatus();
+                } finally {
+                    // this will force any expunged messages to be processed before we release 
+                    // the lock. 
+                    releaseConnection(connection); 
+                }
+            }
+        }
+
+        // now see if we've been expunged, this is a bad op on the message.
+        if (isExpunged()) {
+            throw new MessageRemovedException("Illegal opertion on a deleted message");
+        }
+    }
+    
+    
+    /**
+     * Evaluate whether this message requires any of the information 
+     * in a FetchProfile to be fetched from the server.  If the messages 
+     * already contains the information in the profile, it returns false.
+     * This allows IMAPFolder to optimize fetch() requests to just the 
+     * messages that are missing any of the requested information.
+     * 
+     * NOTE:  If any of the items in the profile are missing, then this 
+     * message will be updated with ALL of the items.  
+     * 
+     * @param profile The FetchProfile indicating the information that should be prefetched.
+     * 
+     * @return true if any of the profile information requires fetching.  false if this 
+     *         message already contains the given information.
+     */
+    protected boolean evaluateFetch(FetchProfile profile) {
+        // the fetch profile can contain a number of different item types.  Validate
+        // whether we need any of these and return true on the first mismatch. 
+        
+        // the UID is a common fetch request, put it first. 
+        if (profile.contains(UIDFolder.FetchProfileItem.UID) && uid == -1) {
+            return true; 
+        }
+        if (profile.contains(FetchProfile.Item.ENVELOPE) && envelope == null) {
+            return true; 
+        }
+        if (profile.contains(FetchProfile.Item.FLAGS) && flags == null) {
+            return true; 
+        }
+        if (profile.contains(FetchProfile.Item.CONTENT_INFO) && bodyStructure == null) {
+            return true; 
+        }
+        // The following profile items are our implementation of items that the 
+        // Sun IMAPFolder implementation supports.  
+        if (profile.contains(IMAPFolder.FetchProfileItem.HEADERS) && !allHeadersRetrieved) {
+            return true; 
+        }
+        if (profile.contains(IMAPFolder.FetchProfileItem.SIZE) && bodyStructure.bodySize < 0) {
+            return true; 
+        }
+        // last bit after checking each of the information types is to see if 
+        // particular headers have been requested and whether those are on the 
+        // set we do have loaded. 
+        String [] requestedHeaders = profile.getHeaderNames(); 
+         
+        // ok, any missing header in the list is enough to force us to request the 
+        // information. 
+        for (int i = 0; i < requestedHeaders.length; i++) {
+            if (headers.getHeader(requestedHeaders[i]) == null) {
+                return true; 
+            }
+        }
+        // this message, at least, does not need anything fetched. 
+        return false; 
+    }
+    
+    /**
+     * Update a message instance with information retrieved via an IMAP FETCH 
+     * command.  The command response for this message may contain multiple pieces
+     * that we need to process.
+     * 
+     * @param response The response line, which may contain multiple data items.
+     * 
+     * @exception MessagingException
+     */
+    void updateMessageInformation(IMAPFetchResponse response) throws MessagingException {
+        // get the list of data items associated with this response.  We can have 
+        // a large number of items returned in a single update. 
+        List items = response.getDataItems(); 
+        
+        for (int i = 0; i < items.size(); i++) {
+            IMAPFetchDataItem item = (IMAPFetchDataItem)items.get(i); 
+            
+            switch (item.getType()) {
+                // if the envelope has been requested, we'll end up with all of these items. 
+                case IMAPFetchDataItem.ENVELOPE:
+                    // update the envelope and map the envelope items into the headers. 
+                    updateEnvelope((IMAPEnvelope)item);
+                    break;
+                case IMAPFetchDataItem.INTERNALDATE:
+                    receivedDate = ((IMAPInternalDate)item).getDate();;
+                    break;
+                case IMAPFetchDataItem.SIZE:
+                    size = ((IMAPMessageSize)item).size;      
+                    break;
+                case IMAPFetchDataItem.UID:
+                    uid = ((IMAPUid)item).uid;       
+                    // make sure the folder knows about the UID update. 
+                    ((IMAPFolder)folder).addToUidCache(new Long(uid), this); 
+                    break; 
+                case IMAPFetchDataItem.BODYSTRUCTURE: 
+                    updateBodyStructure((IMAPBodyStructure)item); 
+                    break; 
+                    // a partial or full header update 
+                case IMAPFetchDataItem.HEADER:
+                {
+                    // if we've fetched the complete set, then replace what we have 
+                    IMAPInternetHeader h = (IMAPInternetHeader)item; 
+                    if (h.isComplete()) {
+                        // we've got a complete header set now. 
+                        this.headers = h.headers;     
+                        allHeadersRetrieved = true; 
+                    }
+                    else {
+                        // need to merge the requested headers in with 
+                        // our existing set.  We need to be careful, since we 
+                        // don't want to add duplicates. 
+                        mergeHeaders(h.headers); 
+                    }
+                }
+                default:
+            }
+        }
+    }
+    
+    
+    /**
+     * Merge a subset of the requested headers with our existing partial set. 
+     * The new set will contain all headers requested from the server, plus 
+     * any of our existing headers that were not included in the retrieved set.
+     * 
+     * @param newHeaders The retrieved set of headers.
+     */
+    protected synchronized void mergeHeaders(InternetHeaders newHeaders) {
+        // This is sort of tricky to manage.  The input headers object is a fresh set  
+        // retrieved from the server, but it's a subset of the headers.  Our existing set
+        // might not be complete, but it may contain duplicates of information in the 
+        // retrieved set, plus headers that are not in the retrieved set.  To keep from 
+        // adding duplicates, we'll only add headers that are not in the retrieved set to 
+        // that set.   
+        
+        // start by running through the list of headers 
+        Enumeration e = headers.getAllHeaders(); 
+        
+        while (e.hasMoreElements()) {
+            Header header = (Header)e.nextElement(); 
+            // if there are no headers with this name in the new set, then 
+            // we can add this.  Note that to add the header, we need to 
+            // retrieve all instances by this name and add them as a unit.  
+            // When we hit one of the duplicates again with the enumeration, 
+            // we'll skip it then because the merge target will have everything. 
+            if (newHeaders.getHeader(header.getName()) == null) {
+                // get all occurrences of this name and stuff them into the 
+                // new list 
+                String name = header.getName(); 
+                String[] a = headers.getHeader(name); 
+                for (int i = 0; i < a.length; i++) {
+                    newHeaders.addHeader(name, a[i]); 
+                }
+            }
+        }
+        // and replace the current header set
+        headers = newHeaders; 
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMimeBodyPart.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMimeBodyPart.java
new file mode 100644
index 0000000..4a0859f
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMimeBodyPart.java
@@ -0,0 +1,350 @@
+/**
+ * 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;
+
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Enumeration; 
+
+import javax.activation.DataHandler;   
+
+import javax.mail.IllegalWriteException; 
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeUtility;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection;
+
+
+public class IMAPMimeBodyPart extends MimeBodyPart {
+    // the message we're part of
+    protected IMAPMessage message;
+    // the retrieved BODYSTRUCTURE information for this part.
+    protected IMAPBodyStructure bodyStructure;
+    // the section identifier.  This will be in a format such as 1.2.3, which 
+    // would refer to the "third part contained in the second part of the first part"...
+    // got all that?  There will be a quiz at the end of class :-)
+    protected String section;
+    // flag to indicate whether the body part headers have been loaded.
+    boolean headersLoaded = false;
+
+    /**
+     * Create an instance of a MimeBodyPart within an 
+     * IMAP message.
+     * 
+     * @param message The parent Message instance containing this part.
+     * @param bodyStructure
+     *                The IMAPBodyStructure information describing the part.
+     * @param section The numeric section identifier string for this part.
+     *                This is a hierarchical set of numbers describing
+     *                how to navigate to the message part on the IMAP
+     *                server.  For example, "2.1.3" would be the third
+     *                subpart of the first subpart of the second main
+     *                message part.
+     */
+    public IMAPMimeBodyPart(IMAPMessage message, IMAPBodyStructure bodyStructure, String section) {
+        super();
+        this.message = message;
+        this.bodyStructure = bodyStructure;
+        this.section = section;
+    }
+
+
+    /**
+     * Get the size of the message part.
+     * 
+     * @return The size information returned in the IMAP body structure.
+     * @exception MessagingException
+     */
+    public int getSize() throws MessagingException {
+        return bodyStructure.bodySize;
+    }
+
+    /**
+     * Get the estimated line count for the body part.
+     * 
+     * @return The line count information returned by the IMAP 
+     *         server.
+     * @exception MessagingException
+     */
+    public int getLineCount() throws MessagingException {
+        return bodyStructure.lines;
+    }
+
+    /**
+     * Get the content type for the body part.
+     * 
+     * @return The mimetype for the body part, in string format.
+     * @exception MessagingException
+     */
+    public String getContentType() throws MessagingException {
+        return bodyStructure.mimeType.toString();
+    }
+
+    /**
+     * Test if the body part is of a particular MIME type.
+     * 
+     * @param type   The string MIME-type name.  A wild card * can be
+     *               specified for the subpart type.
+     * 
+     * @return true if the body part matches the give MIME-type.
+     * @exception MessagingException
+     */
+    public boolean isMimeType(String type) throws MessagingException {
+        return bodyStructure.mimeType.match(type);
+    }
+
+    /**
+     * Retrieve the disposition information about this 
+     * body part.
+     * 
+     * @return The disposition information, as a string value.
+     * @exception MessagingException
+     */
+    public String getDisposition() throws MessagingException {
+        return bodyStructure.disposition.getDisposition();
+    }
+
+    /**
+     * Set the disposition information.  The IMAP message 
+     * is read-only, so this is an error.
+     * 
+     * @param disposition
+     *               The disposition string.
+     * 
+     * @exception MessagingException
+     */
+    public void setDisposition(String disposition) throws MessagingException {
+        throw new IllegalWriteException("IMAP message parts are read-only");
+    }
+
+    public String getEncoding() throws MessagingException {
+        return bodyStructure.transferEncoding;
+    }
+
+    public String getContentID() throws MessagingException {
+        return bodyStructure.contentID;
+    }
+
+    public void setContentID(String id) throws MessagingException {
+        throw new IllegalWriteException("IMAP message parts are read-only");
+    }
+
+    public String getContentMD5() throws MessagingException {
+        return bodyStructure.md5Hash;
+    }
+
+    public void setContentMD5(String id) throws MessagingException {
+        throw new IllegalWriteException("IMAP message parts are read-only");
+    }
+
+    public String getDescription() throws MessagingException {
+        String description = bodyStructure.contentDescription;
+        if (description != null) {
+            try {
+                // this could be both folded and encoded.  Return this to usable form.
+                return MimeUtility.decodeText(MimeUtility.unfold(description));
+            } catch (UnsupportedEncodingException e) {
+                // ignore
+            }
+        }
+        // return the raw version for any errors (this might be null also)
+        return description;
+    }
+
+    public void setDescription(String d, String charset) throws MessagingException {
+        throw new IllegalWriteException("IMAP message parts are read-only");
+    }
+
+    public String getFileName() throws MessagingException {
+        String filename = bodyStructure.disposition.getParameter("filename");
+        if (filename == null) {
+            filename = bodyStructure.mimeType.getParameter("name");
+        }
+        return filename;
+    }
+
+    public void setFileName(String name) throws MessagingException {
+        throw new IllegalWriteException("IMAP message parts are read-only");
+    }
+
+    protected InputStream getContentStream() throws MessagingException {
+
+        // no content loaded yet?
+        if (content == null) {
+            // make sure we're still valid
+            message.checkValidity();
+            // make sure the content is fully loaded
+            loadContent();
+        }
+
+        // allow the super class to handle creating it from the loaded content.
+        return super.getContentStream();
+    }
+
+
+    /**
+     * Create the DataHandler object for this message.
+     *
+     * @return The DataHandler object that processes the content set for this
+     *         message.
+     * @exception MessagingException
+     */
+    public synchronized DataHandler getDataHandler() throws MessagingException {
+        if (dh == null) {                                                
+            // are we working with a multipart message here?
+            if (bodyStructure.isMultipart()) {
+                dh = new DataHandler(new IMAPMultipartDataSource(message, this, section, bodyStructure));
+                return dh;
+            }
+            else if (bodyStructure.isAttachedMessage()) {
+                dh = new DataHandler(new IMAPAttachedMessage(message, section, bodyStructure.nestedEnvelope, 
+                    bodyStructure.nestedBody), bodyStructure.mimeType.toString());
+                return dh;
+            }
+        }
+
+        // single part messages get handled the normal way.
+        return super.getDataHandler();
+    }
+
+    public void setDataHandler(DataHandler content) throws MessagingException {
+        throw new IllegalWriteException("IMAP body parts are read-only");
+    }
+
+    public void setContent(Object o, String type) throws MessagingException {
+        throw new IllegalWriteException("IMAP body parts are read-only");
+    }
+
+    public void setContent(Multipart mp) throws MessagingException {
+        throw new IllegalWriteException("IMAP body parts are read-only");
+    }
+
+
+	/******************************************************************
+	 * Following is a set of methods that deal with headers
+	 * These methods are just overrides on the superclass methods to
+     * allow lazy loading of the header information.
+	 ********************************************************************/
+
+	public String[] getHeader(String name) throws MessagingException {
+        loadHeaders();
+		return headers.getHeader(name);
+	}
+
+	public String getHeader(String name, String delimiter) throws MessagingException {
+        loadHeaders();
+		return headers.getHeader(name, delimiter);
+	}
+
+	public Enumeration getAllHeaders() throws MessagingException {
+        loadHeaders();
+		return headers.getAllHeaders();
+	}
+
+	public Enumeration getMatchingHeaders(String[] names)  throws MessagingException {
+        loadHeaders();
+		return headers.getMatchingHeaders(names);
+	}
+
+	public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
+        loadHeaders();
+		return headers.getNonMatchingHeaders(names);
+	}
+
+	public Enumeration getAllHeaderLines() throws MessagingException {
+        loadHeaders();
+		return headers.getAllHeaderLines();
+	}
+
+	public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
+        loadHeaders();
+		return headers.getMatchingHeaderLines(names);
+	}
+
+	public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
+        loadHeaders();
+		return headers.getNonMatchingHeaderLines(names);
+	}
+
+    // the following are overrides for header modification methods.  These messages are read only,
+    // so the headers cannot be modified.
+    public void addHeader(String name, String value) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+    public void setHeader(String name, String value) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+
+    public void removeHeader(String name) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+    public void addHeaderLine(String line) throws MessagingException {
+        throw new IllegalWriteException("IMAP messages are read-only");
+    }
+
+
+    /**
+     * Load the mime part headers into this body part.
+     *
+     * @exception MessagingException
+     */
+    protected synchronized void loadHeaders() throws MessagingException {
+        // have them already?  Super..
+        if (headers != null) {
+            return;
+        }
+                           
+        IMAPConnection connection = message.getConnection();
+        try {
+            // this asks for the MIME subsection of the given section piece.
+            headers = connection.fetchHeaders(message.getSequenceNumber(), section);
+        } finally {    
+            message.releaseConnection(connection);
+        }
+
+    }
+
+
+    /**
+     * Load the message content into the BodyPart object.
+     *
+     * @exception MessagingException
+     */
+    protected void loadContent() throws MessagingException {
+        // if we've loaded this already, just return
+        if (content != null) {
+            return;
+        }
+        
+        IMAPConnection connection = message.getConnection();
+        try {
+            // load the content from the server. 
+            content = connection.fetchContent(message.getSequenceNumber(), section); 
+        } finally {
+            message.releaseConnection(connection); 
+        }
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMultipartDataSource.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMultipartDataSource.java
new file mode 100644
index 0000000..30c2515
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMultipartDataSource.java
@@ -0,0 +1,56 @@
+/**
+ * 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;
+
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.MultipartDataSource;
+
+import javax.mail.internet.MimePart;
+import javax.mail.internet.MimePartDataSource;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure;
+
+public class IMAPMultipartDataSource extends MimePartDataSource implements MultipartDataSource {
+    // the list of parts
+    protected BodyPart[] parts;
+
+    IMAPMultipartDataSource(IMAPMessage message, MimePart parent, String section, IMAPBodyStructure bodyStructure) {
+        super(parent);
+
+        parts = new BodyPart[bodyStructure.parts.length];
+        
+        // We're either created from the parent message, in which case we're the top level 
+        // of the hierarchy, or we're created from a nested message, so we need to apply the 
+        // parent numbering prefix. 
+        String sectionBase = section == null ? "" : section + "."; 
+
+        for (int i = 0; i < parts.length; i++) {
+            // create a section id.  This is either the count (origin zero) or a subpart of the previous section.
+            parts[i] = new IMAPMimeBodyPart(message, (IMAPBodyStructure)bodyStructure.parts[i], sectionBase + (i + 1));
+        }
+    }
+
+    public int getCount() {
+        return parts.length;
+    }
+
+    public BodyPart getBodyPart(int index) throws MessagingException {
+        return parts[index];
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPNamespaceFolder.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPNamespaceFolder.java
new file mode 100644
index 0000000..2d51b7c
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPNamespaceFolder.java
@@ -0,0 +1,51 @@
+/**
+ * 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;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPNamespace;
+
+/**
+ * An override of the base IMAPFolder class for folders representing namespace roots. 
+ * @see javax.mail.Folder
+ *
+ * @version $Rev$
+ */
+public class IMAPNamespaceFolder extends IMAPFolder {
+    
+    IMAPNamespaceFolder(IMAPStore store, IMAPNamespace namespace) {
+        // initialize with the namespace information 
+        super(store, namespace.prefix, namespace.separator); 
+    }
+    
+    
+    /**
+     * Override of the default IMAPFolder method to provide the mailbox name 
+     * as the prefix + delimiter. 
+     * 
+     * @return The string name to use as the mailbox name for exists() and issubscribed() 
+     *         calls.
+     */
+    protected String getMailBoxName() {
+        // no delimiter is a possibility, so 
+        // we need to check.  
+        if (separator == '\0') {
+            return fullname;
+        }
+        return fullname + separator; 
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPRootFolder.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPRootFolder.java
new file mode 100644
index 0000000..dce9420
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPRootFolder.java
@@ -0,0 +1,131 @@
+/**
+ * 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;
+
+import javax.mail.Folder; 
+import javax.mail.Message; 
+import javax.mail.MessagingException; 
+import javax.mail.MethodNotSupportedException;
+import javax.mail.Store; 
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPEnvelope; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure; 
+
+/**
+ * An IMAP folder instance for the root of IMAP folder tree.  This has 
+ * some of the folder operations disabled. 
+ */
+public class IMAPRootFolder extends IMAPFolder {
+    
+    /**
+     * Create a default IMAPRootFolder attached to a specific Store instance.
+     * 
+     * @param store  The Store instance this is the root for.
+     */
+    public IMAPRootFolder(IMAPStore store) {
+        // create a folder with a null string name and the default separator. 
+        super(store, "", '/'); 
+        // this only holds folders 
+        folderType = HOLDS_FOLDERS; 
+    }
+
+    /**
+     * Get the Folder determined by the supplied name; if the name is relative
+     * then it is interpreted relative to this folder. This does not check that
+     * the named folder actually exists.
+     *
+     * @param name the name of the folder to return
+     * @return the named folder
+     * @throws MessagingException if there was a problem accessing the store
+     */
+    public Folder getFolder(String name) throws MessagingException {
+        // The root folder is a dummy one.  Any getFolder() request starting 
+        // at the root will use the request name for the full name.  The separator 
+        // used in that folder's namespace will be determined when the folder is 
+        // first opened. 
+        return new IMAPFolder((IMAPStore)store, name, UNDETERMINED);
+    }
+    
+    
+    public Folder getParent() {
+        // we never have a parent folder 
+        return null; 
+    }
+    
+    
+    public boolean exists() throws MessagingException {
+        // this always exists 
+        return true; 
+    }
+    
+    public boolean hasNewMessages() {
+        // we don't really exist, so the answer is always false. 
+        return false; 
+    }
+    
+    
+    public int getMessagesCount() {
+        // we don't really exist, so the answer is always 0; 
+        return 0; 
+    }
+    
+    
+    public int getNewMessagesCount() {
+        // we don't really exist, so the answer is always 0; 
+        return 0; 
+    }
+    
+    
+    public int getUnreadMessagesCount() {
+        // we don't really exist, so the answer is always 0; 
+        return 0; 
+    }
+    
+    
+    public int getDeletedMessagesCount() {
+        // we don't really exist, so the answer is always 0; 
+        return 0; 
+    }
+    
+    
+	public boolean create(int newType) throws MessagingException {
+        throw new MethodNotSupportedException("Default IMAP folder cannot be created"); 
+    }
+    
+    public boolean delete(boolean recurse) throws MessagingException {
+        throw new MethodNotSupportedException("Default IMAP folder cannot be deleted"); 
+    }
+    
+    
+    public boolean rename(boolean recurse) throws MessagingException {
+        throw new MethodNotSupportedException("Default IMAP folder cannot be renamed"); 
+    }
+    
+    
+    public void appendMessages(Message[] msgs) throws MessagingException {
+        throw new MethodNotSupportedException("Messages cannot be appended to Default IMAP folder"); 
+    }
+    
+    
+    public Message[] expunge() throws MessagingException {
+        throw new MethodNotSupportedException("Messages cannot be expunged from Default IMAP folder"); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.java
new file mode 100644
index 0000000..fa33a43
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.java
@@ -0,0 +1,44 @@
+/**
+ * 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;
+
+import javax.mail.Session;
+import javax.mail.URLName;
+
+/**
+ * IMAP implementation of javax.mail.Store
+ * POP protocol spec is implemented in
+ * org.apache.geronimo.javamail.store.pop3.IMAPConnection
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPSSLStore extends IMAPStore {
+    /**
+     * Construct an IMAPSSLStore item.
+     *
+     * @param session The owning javamail Session.
+     * @param urlName The Store urlName, which can contain server target information.
+     */
+	public IMAPSSLStore(Session session, URLName urlName) {
+        // we're the imaps protocol, our default connection port is 993, and we must use
+        // an SSL connection for the initial hookup 
+		super(session, urlName, "imaps", true, DEFAULT_IMAP_SSL_PORT);
+	}
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.java
new file mode 100644
index 0000000..bf171a3
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.java
@@ -0,0 +1,614 @@
+/**
+ * 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.mail.AuthenticationFailedException;
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+import javax.mail.Quota;
+import javax.mail.QuotaAwareStore;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.URLName;
+import javax.mail.event.StoreEvent; 
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPConnectionPool; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPOkResponse; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPNamespaceResponse; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPNamespace; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPServerStatusResponse; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponse; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPUntaggedResponseHandler; 
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+
+/**
+ * IMAP implementation of javax.mail.Store
+ * POP protocol spec is implemented in
+ * org.apache.geronimo.javamail.store.pop3.IMAPConnection
+ *
+ * @version $Rev$ $Date$
+ */
+
+public class IMAPStore extends Store implements QuotaAwareStore, IMAPUntaggedResponseHandler {
+    // the default connection ports for secure and non-secure variations
+    protected static final int DEFAULT_IMAP_PORT = 119;
+    protected static final int DEFAULT_IMAP_SSL_PORT = 993;
+    
+    protected static final String MAIL_STATUS_TIMEOUT = "statuscacheimeout";
+    protected static final int DEFAULT_STATUS_TIMEOUT = 1000; 
+    
+    // our accessor for protocol properties and the holder of 
+    // protocol-specific information 
+    protected ProtocolProperties props; 
+
+    // the connection pool we use for access 
+	protected IMAPConnectionPool connectionPool;
+
+    // the root folder
+    protected IMAPRootFolder root;
+
+    // the list of open folders (which also represents an open connection).
+    protected List openFolders = new LinkedList();
+
+    // our session provided debug output stream.
+    protected PrintStream debugStream;
+    // the debug flag 
+    protected boolean debug; 
+    // until we're connected, we're closed 
+    boolean closedForBusiness = true; 
+    // The timeout value for our status cache 
+    long statusCacheTimeout = 0; 
+
+    /**
+     * Construct an IMAPStore item.
+     *
+     * @param session The owning javamail Session.
+     * @param urlName The Store urlName, which can contain server target information.
+     */
+	public IMAPStore(Session session, URLName urlName) {
+        // we're the imap protocol, our default connection port is 119, and don't use 
+        // an SSL connection for the initial hookup 
+		this(session, urlName, "imap", false, DEFAULT_IMAP_PORT);
+	}
+                                                          
+    /**
+     * Protected common constructor used by both the IMAPStore and the IMAPSSLStore
+     * to initialize the Store instance. 
+     * 
+     * @param session  The Session we're attached to.
+     * @param urlName  The urlName.
+     * @param protocol The protocol name.
+     * @param sslConnection
+     *                 The sslConnection flag.
+     * @param defaultPort
+     *                 The default connection port.
+     */
+    protected IMAPStore(Session session, URLName urlName, String protocol, boolean sslConnection, int defaultPort) {
+        super(session, urlName); 
+        // create the protocol property holder.  This gives an abstraction over the different 
+        // flavors of the protocol. 
+        props = new ProtocolProperties(session, protocol, sslConnection, defaultPort); 
+        
+        // get the status timeout value for the folders. 
+        statusCacheTimeout = props.getIntProperty(MAIL_STATUS_TIMEOUT, DEFAULT_STATUS_TIMEOUT);
+
+        // get our debug settings
+        debugStream = session.getDebugOut();
+        debug = session.getDebug(); 
+        
+        // create a connection pool we can retrieve connections from 
+        connectionPool = new IMAPConnectionPool(this, props); 
+    }
+    
+    
+    /**
+     * Attempt the protocol-specific connection; subclasses should override this to establish
+     * a connection in the appropriate manner.
+     * 
+     * This method should return true if the connection was established.
+     * It may return false to cause the {@link #connect(String, int, String, String)} method to
+     * reattempt the connection after trying to obtain user and password information from the user.
+     * Alternatively it may throw a AuthenticatedFailedException to abandon the conection attempt.
+     * 
+     * @param host     The target host name of the service.
+     * @param port     The connection port for the service.
+     * @param user     The user name used for the connection.
+     * @param password The password used for the connection.
+     * 
+     * @return true if a connection was established, false if there was authentication 
+     *         error with the connection.
+     * @throws AuthenticationFailedException
+     *                if authentication fails
+     * @throws MessagingException
+     *                for other failures
+     */
+	protected synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
+        if (debug) {
+            debugOut("Connecting to server " + host + ":" + port + " for user " + username);
+        }
+
+        // the connection pool handles all of the details here. 
+        if (connectionPool.protocolConnect(host, port, username, password)) 
+        {
+            // the store is now open 
+            closedForBusiness = false; 
+            return true; 
+        }
+        return false; 
+	}
+
+
+    /**
+     * Close this service and terminate its physical connection.
+     * The default implementation simply calls setConnected(false) and then
+     * sends a CLOSED event to all registered ConnectionListeners.
+     * Subclasses overriding this method should still ensure it is closed; they should
+     * also ensure that it is called if the connection is closed automatically, for
+     * for example in a finalizer.
+     *
+     *@throws MessagingException if there were errors closing; the connection is still closed
+     */
+	public synchronized void close() throws MessagingException{
+        // if already closed, nothing to do. 
+        if (closedForBusiness) {
+            return; 
+        }
+        
+        // close the folders first, then shut down the Store. 
+        closeOpenFolders();
+        
+        connectionPool.close(); 
+        connectionPool = null; 
+
+		// make sure we do the superclass close operation first so 
+        // notification events get broadcast properly. 
+		super.close();
+	}
+
+
+    /**
+     * Return a Folder object that represents the root of the namespace for the current user.
+     *
+     * Note that in some store configurations (such as IMAP4) the root folder might
+     * not be the INBOX folder.
+     *
+     * @return the root Folder
+     * @throws MessagingException if there was a problem accessing the store
+     */
+	public Folder getDefaultFolder() throws MessagingException {
+		checkConnectionStatus();
+        // if no root yet, create a root folder instance. 
+        if (root == null) {
+            return new IMAPRootFolder(this);
+        }
+        return root;
+	}
+
+    /**
+     * Return the Folder corresponding to the given name.
+     * The folder might not physically exist; the {@link Folder#exists()} method can be used
+     * to determine if it is real.
+     * 
+     * @param name   the name of the Folder to return
+     * 
+     * @return the corresponding folder
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+	public Folder getFolder(String name) throws MessagingException {
+        return getDefaultFolder().getFolder(name);
+	}
+
+    
+    /**
+     * Return the folder identified by the URLName; the URLName must refer to this Store.
+     * Implementations may use the {@link URLName#getFile()} method to determined the folder name.
+     * 
+     * @param url
+     * 
+     * @return the corresponding folder
+     * @throws MessagingException
+     *                if there was a problem accessing the store
+     */
+	public Folder getFolder(URLName url) throws MessagingException {
+        return getDefaultFolder().getFolder(url.getFile());
+	}
+
+    
+    /**
+     * Return the root folders of the personal namespace belonging to the current user.
+     *
+     * The default implementation simply returns an array containing the folder returned by {@link #getDefaultFolder()}.
+     * @return the root folders of the user's peronal namespaces
+     * @throws MessagingException if there was a problem accessing the store
+     */
+    public Folder[] getPersonalNamespaces() throws MessagingException {
+        IMAPNamespaceResponse namespaces = getNamespaces(); 
+        
+        // if nothing is returned, then use the API-defined default for this 
+        if (namespaces.personalNamespaces.size() == 0) {
+            return super.getPersonalNamespaces(); 
+        }
+        
+        // convert the list into an array of Folders. 
+        return getNamespaceFolders(namespaces.personalNamespaces); 
+    }
+    
+    
+    /**
+     * Return the root folders of the personal namespaces belonging to the supplied user.
+     *
+     * The default implementation simply returns an empty array.
+     *
+     * @param user the user whose namespaces should be returned
+     * @return the root folders of the given user's peronal namespaces
+     * @throws MessagingException if there was a problem accessing the store
+     */
+    public Folder[] getUserNamespaces(String user) throws MessagingException {
+        IMAPNamespaceResponse namespaces = getNamespaces(); 
+        
+        // if nothing is returned, then use the API-defined default for this 
+        if (namespaces.otherUserNamespaces == null || namespaces.otherUserNamespaces.isEmpty()) {
+            return super.getUserNamespaces(user); 
+        }
+        
+        // convert the list into an array of Folders. 
+        return getNamespaceFolders(namespaces.otherUserNamespaces); 
+    }
+
+    
+    /**
+     * Return the root folders of namespaces that are intended to be shared between users.
+     *
+     * The default implementation simply returns an empty array.
+     * @return the root folders of all shared namespaces
+     * @throws MessagingException if there was a problem accessing the store
+     */
+    public Folder[] getSharedNamespaces() throws MessagingException {
+        IMAPNamespaceResponse namespaces = getNamespaces(); 
+        
+        // if nothing is returned, then use the API-defined default for this 
+        if (namespaces.sharedNamespaces == null || namespaces.sharedNamespaces.isEmpty()) {
+            return super.getSharedNamespaces(); 
+        }
+        
+        // convert the list into an array of Folders. 
+        return getNamespaceFolders(namespaces.sharedNamespaces); 
+    }
+    
+    
+    /**
+     * Get the quotas for the specified root element.
+     *
+     * @param root   The root name for the quota information.
+     *
+     * @return An array of Quota objects defined for the root.
+     * @throws MessagingException if the quotas cannot be retrieved
+     */
+    public Quota[] getQuota(String root) throws javax.mail.MessagingException {
+        // get our private connection for access 
+        IMAPConnection connection = getStoreConnection(); 
+        try {
+            // request the namespace information from the server 
+            return connection.fetchQuota(root); 
+        } finally {
+            releaseStoreConnection(connection); 
+        }
+    }
+
+    /**
+     * Set a quota item.  The root contained in the Quota item identifies
+     * the quota target.
+     *
+     * @param quota  The source quota item.
+     * @throws MessagingException if the quota cannot be set
+     */
+    public void setQuota(Quota quota) throws javax.mail.MessagingException {
+        // get our private connection for access 
+        IMAPConnection connection = getStoreConnection(); 
+        try {
+            // request the namespace information from the server 
+            connection.setQuota(quota); 
+        } finally {
+            releaseStoreConnection(connection); 
+        }
+    }
+
+    /**
+     * Verify that the server is in a connected state before 
+     * performing operations that required that status. 
+     * 
+     * @exception MessagingException
+     */
+	private void checkConnectionStatus() throws MessagingException {
+        // we just check the connection status with the superclass.  This 
+        // tells us we've gotten a connection.  We don't want to do the 
+        // complete connection checks that require pinging the server. 
+		if (!super.isConnected()){
+		    throw new MessagingException("Not connected ");
+	    }
+	}
+
+
+    /**
+     * Test to see if we're still connected.  This will ping the server
+     * to see if we're still alive.
+     *
+     * @return true if we have a live, active culture, false otherwise.
+     */
+    public synchronized boolean isConnected() {
+        // check if we're in a presumed connected state.  If not, we don't really have a connection
+        // to check on.
+        if (!super.isConnected()) {
+            return false;
+        }
+        
+        try {
+            IMAPConnection connection = getStoreConnection(); 
+            try {
+                // check with the connecition to see if it's still alive. 
+                // we use a zero timeout value to force it to check. 
+                return connection.isAlive(0);
+            } finally {
+                releaseStoreConnection(connection); 
+            }
+        } catch (MessagingException e) {
+            return false; 
+        }
+        
+    }
+
+    /**
+     * Internal debug output routine.
+     *
+     * @param value  The string value to output.
+     */
+    void debugOut(String message) {
+        debugStream.println("IMAPStore DEBUG: " + message);
+    }
+
+    /**
+     * Internal debugging routine for reporting exceptions.
+     *
+     * @param message A message associated with the exception context.
+     * @param e       The received exception.
+     */
+    void debugOut(String message, Throwable e) {
+        debugOut("Received exception -> " + message);
+        debugOut("Exception message -> " + e.getMessage());
+        e.printStackTrace(debugStream);
+    }
+
+
+    /**
+     * Retrieve the server connection created by this store.
+     *
+     * @return The active connection object.
+     */
+    protected IMAPConnection getStoreConnection() throws MessagingException {
+        return connectionPool.getStoreConnection(); 
+    }
+    
+    protected void releaseStoreConnection(IMAPConnection connection) throws MessagingException {
+        // This is a bit of a pain.  We need to delay processing of the 
+        // unsolicited responses until after each user of the connection has 
+        // finished processing the expected responses.  We need to do this because 
+        // the unsolicited responses may include EXPUNGED messages.  The EXPUNGED 
+        // messages will alter the message sequence numbers for the messages in the 
+        // cache.  Processing the EXPUNGED messages too early will result in 
+        // updates getting applied to the wrong message instances.  So, as a result, 
+        // we delay that stage of the processing until all expected responses have 
+        // been handled.  
+        
+        // process any pending messages before returning. 
+        connection.processPendingResponses(); 
+        // return this to the connectin pool 
+        connectionPool.releaseStoreConnection(connection); 
+    }
+    
+    synchronized IMAPConnection getFolderConnection(IMAPFolder folder) throws MessagingException {
+        IMAPConnection connection = connectionPool.getFolderConnection(); 
+        openFolders.add(folder);
+        return connection; 
+    }
+    
+    
+    synchronized void releaseFolderConnection(IMAPFolder folder, IMAPConnection connection) throws MessagingException {
+        openFolders.remove(folder); 
+        // return this to the connectin pool 
+        // NB:  It is assumed that the Folder has already triggered handling of 
+        // unsolicited responses on this connection before returning it. 
+        connectionPool.releaseFolderConnection(connection); 
+    }
+
+
+    /**
+     * Retrieve the Session object this Store is operating under.
+     *
+     * @return The attached Session instance.
+     */
+    Session getSession() {
+        return session;
+    }
+    
+    /**
+     * Close all open folders.  We have a small problem here with a race condition.  There's no safe, single
+     * synchronization point for us to block creation of new folders while we're closing.  So we make a copy of
+     * the folders list, close all of those folders, and keep repeating until we're done.
+     */
+    protected void closeOpenFolders() {
+        // we're no longer accepting additional opens.  Any folders that open after this point will get an
+        // exception trying to get a connection.
+        closedForBusiness = true;
+
+        while (true) {
+            List folders = null;
+
+            // grab our lock, copy the open folders reference, and null this out.  Once we see a null
+            // open folders ref, we're done closing.
+            synchronized(connectionPool) {
+                folders = openFolders;
+                openFolders = new LinkedList();
+            }
+
+            // null folder, we're done
+            if (folders.isEmpty()) {
+                return;
+            }
+            // now close each of the open folders.
+            for (int i = 0; i < folders.size(); i++) {
+                IMAPFolder folder = (IMAPFolder)folders.get(i);
+                try {
+                    folder.close(false);
+                } catch (MessagingException e) {
+                }
+            }
+        }
+    }
+    
+    /**
+     * Get the namespace information from the IMAP server.
+     * 
+     * @return An IMAPNamespaceResponse with the namespace information. 
+     * @exception MessagingException
+     */
+    protected IMAPNamespaceResponse getNamespaces() throws MessagingException {
+        // get our private connection for access 
+        IMAPConnection connection = getStoreConnection(); 
+        try {
+            // request the namespace information from the server 
+            return connection.getNamespaces(); 
+        } finally {
+            releaseStoreConnection(connection); 
+        }
+    }
+    
+    
+    /**
+     * Convert a List of IMAPNamespace definitions into an array of Folder 
+     * instances. 
+     * 
+     * @param namespaces The namespace List
+     * 
+     * @return An array of the same size as the namespace list containing a Folder 
+     *         instance for each defined namespace.
+     * @exception MessagingException
+     */
+    protected Folder[] getNamespaceFolders(List namespaces) throws MessagingException {
+        Folder[] folders = new Folder[namespaces.size()]; 
+        
+        // convert each of these to a Folder instance. 
+        for (int i = 0; i < namespaces.size(); i++) {
+            IMAPNamespace namespace = (IMAPNamespace)namespaces.get(i); 
+            folders[i] = new IMAPNamespaceFolder(this, namespace); 
+        }
+        return folders; 
+    }
+    
+    
+    /**
+     * 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) {
+        return connectionPool.hasCapability(capability); 
+    }
+    
+    
+    /**
+     * Handle an unsolicited response from the server.  Most unsolicited responses 
+     * are replies to specific commands sent to the server.  The remainder must 
+     * be handled by the Store or the Folder using the connection.  These are 
+     * critical to handle, as events such as expunged messages will alter the 
+     * sequence numbers of the live messages.  We need to keep things in sync.
+     * 
+     * @param response The UntaggedResponse to process.
+     * 
+     * @return true if we handled this response and no further handling is required.  false
+     *         means this one wasn't one of ours.
+     */
+    public boolean handleResponse(IMAPUntaggedResponse response) {
+        // Some sort of ALERT response from the server?
+        // we need to broadcast this to any of the listeners 
+        if (response.isKeyword("ALERT")) {
+            notifyStoreListeners(StoreEvent.ALERT, ((IMAPOkResponse)response).getMessage()); 
+            return true; 
+        }
+        // potentially some sort of unsolicited OK notice.  This is also an event. 
+        else if (response.isKeyword("OK")) {
+            String message = ((IMAPOkResponse)response).getMessage(); 
+            if (message.length() > 0) {
+                notifyStoreListeners(StoreEvent.NOTICE, message); 
+            }
+            return true; 
+        }
+        // potentially some sort of unsolicited notice.  This is also an event. 
+        else if (response.isKeyword("BAD") || response.isKeyword("NO")) {
+            String message = ((IMAPServerStatusResponse)response).getMessage(); 
+            if (message.length() > 0) {
+                notifyStoreListeners(StoreEvent.NOTICE, message); 
+            }
+            return true; 
+        }
+        // this is a BYE response on our connection.  Folders should be handling the 
+        // BYE events on their connections, so we should only be seeing this if 
+        // it's on the store connection.  
+        else if (response.isKeyword("BYE")) {
+            // this is essentially a close event.  We need to clean everything up 
+            try {
+                close();                
+            } catch (MessagingException e) {
+            }
+            return true; 
+        }
+        return false; 
+    }
+    
+    /**
+     * Finalizer to perform IMAPStore() cleanup when 
+     * no longer in use. 
+     * 
+     * @exception Throwable
+     */
+    protected void finalize() throws Throwable {
+        super.finalize(); 
+        close(); 
+    }
+    
+    /**
+     * Retrieve the protocol properties for the Store. 
+     * 
+     * @return The protocol properties bundle. 
+     */
+    ProtocolProperties getProperties() {
+        return props; 
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java
new file mode 100644
index 0000000..5445020
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java
@@ -0,0 +1,325 @@
+/**
+ * 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;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Represents a set of rights associated with a user to manipulate the 
+ * IMAP Store.
+ */
+public class Rights implements Cloneable {
+    
+    /**
+     * An individual right for IMAP Store manipulation.
+     */
+    public static final class Right {
+        // The set of created stores.  The getInstance() method ensures 
+        // that each right is a singleton object. 
+        static private Map rights = new HashMap();
+        
+        /**
+         * lookup (mailbox is visible to LIST/LSUB commands)
+         */
+        public static final Right LOOKUP = getInstance('l'); 
+        /**
+         * read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL,
+         *        SEARCH, COPY from mailbox)
+         */
+        public static final Right READ = getInstance('r'); 
+        /**
+         * keep seen/unseen information across sessions (STORE SEEN flag)
+         */
+        public static final Right KEEP_SEEN = getInstance('s'); 
+        /**
+         * write (STORE flags other than SEEN and DELETED)
+         */
+        public static final Right WRITE = getInstance('w'); 
+        /**
+         * insert (perform APPEND, COPY into mailbox)
+         */
+        public static final Right INSERT = getInstance('i'); 
+        /**
+         * post (send mail to submission address for mailbox,
+         *        not enforced by IMAP4 itself)
+         */
+        public static final Right POST = getInstance('p'); 
+        /**
+         * create (CREATE new sub-mailboxes in any implementation-defined
+         *        hierarchy)
+         */
+        public static final Right CREATE = getInstance('c'); 
+        /**
+         * delete (STORE DELETED flag, perform EXPUNGE)
+         */
+        public static final Right DELETE = getInstance('d'); 
+        /**
+         * administer (perform SETACL)
+         */
+        public static final Right ADMINISTER = getInstance('a'); 
+        
+        // the actual right definition 
+        String right; 
+        
+        /**
+         * Private constructor for an individual Right.  Used by getInstance().
+         * 
+         * @param right  The String name of the right (a single character).
+         */
+        private Right(String right) {
+            this.right = right; 
+        }
+        
+        /**
+         * Get an instance for a right from the single character right value.  The
+         * returned instance will be a singleton for that character value.
+         * 
+         * @param right  The right character value.
+         * 
+         * @return A Right instance that's the mapping for the character value.
+         */
+        public static synchronized Right getInstance(char right) {
+            String name = String.valueOf(right); 
+            Right instance = (Right)rights.get(name); 
+            if (instance == null) {
+                instance = new Right(name); 
+                rights.put(name, instance); 
+            }
+            return instance; 
+        }
+        
+        /**
+         * Return the string value of the Right.  The string value is the character 
+         * used to create the Right with newInstance().
+         * 
+         * @return The string representation of the Right.
+         */
+        public String toString() {
+            return right; 
+        }
+    }
+    
+    /**
+     * The set of Rights contained in this instance.  This is a TreeSet so that
+     * we can create the string value more consistently.
+     */
+    private SortedSet rights = new TreeSet(new RightComparator()); 
+    
+    /**
+     * Construct an empty set of Rights.
+     */
+    public Rights() {
+    }
+    
+    /**
+     * Construct a Rights set from a single Right instance.
+     * 
+     * @param right  The source Right.
+     */
+    public Rights(Right right) {
+        rights.add(right); 
+    }
+    
+    /**
+     * Construct a set of rights from an existing Rights set.  This will copy 
+     * the rights values.
+     * 
+     * @param list   The source Rights instance.
+     */
+    public Rights(Rights list) {
+        add(list); 
+        Rights[] otherRights = list.getRights(); 
+        for (int i = 0; i < otherRights.length; i++) {
+            rights.add(otherRights[i]); 
+        }
+    }
+    
+    /**
+     * Construct a Rights et from a character string.  Each character in the
+     * string represents an individual Right.
+     * 
+     * @param list   The source set of rights.
+     */
+    public Rights(String list) {
+        for (int i = 0; i < list.length(); i++) {
+            rights.add(Right.getInstance(list.charAt(i))); 
+        }
+    }
+    
+    /**
+     * Add a single Right to the set.
+     * 
+     * @param right  The new Right.  If the Rigtht is already part of the Set, this is a nop.
+     */
+    public void add(Right right) {
+        rights.add(right); 
+    }
+    
+    /**
+     * Merge a Rights set with this set.  Duplicates are eliminated.
+     * 
+     * @param list   The source for the added Rights.
+     */
+    public void add(Rights list) {
+        Rights[] otherRights = list.getRights(); 
+        for (int i = 0; i < otherRights.length; i++) {
+            rights.add(otherRights[i]); 
+        }
+    }
+    
+    /**
+     * Clone a set of Rights.
+     */
+    public Object clone() {
+        return new Rights(this); 
+    }
+    
+    /**
+     * Test if a Rights set contains a given Right.
+     * 
+     * @param right  The Right instance to test.
+     * 
+     * @return true if the Right exists in the Set, false otherwise.
+     */
+    public boolean contains(Right right) {
+        return rights.contains(right); 
+    }
+    
+    /**
+     * Test if this Rights set contains all of the Rights contained in another
+     * set.
+     * 
+     * @param list   The source Rights set for the test.
+     * 
+     * @return true if all of the Rights in the source set exist in the target set.
+     */
+    public boolean contains(Rights list) {
+        return rights.containsAll(list.rights); 
+    }
+    
+    /**
+     * Test if two Rights sets are equivalent.
+     * 
+     * @param list   The source rights set.
+     * 
+     * @return true if both Rigths sets contain the same Rights values.
+     */
+    public boolean equals(Rights list) {
+        return rights.equals(list.rights); 
+    }
+    
+    /**
+     * Get an array of Rights contained in the set.
+     * 
+     * @return An array of Rights[] values.
+     */
+    public Rights[] getRights() {
+        Rights[] list = new Rights[rights.size()]; 
+        return (Rights[])rights.toArray(list); 
+    }
+    
+    /**
+     * Compute a hashCode for the Rights set.
+     * 
+     * @return The computed hashCode.
+     */
+    public int hashCode() {
+        return rights.hashCode(); 
+    }
+    
+    /**
+     * Remove a Right from the set.
+     * 
+     * @param right  The single Right to remove.
+     */
+    public void remove(Right right) {
+        rights.remove(right); 
+    }
+    
+    /**
+     * Remove a set of rights from the set.
+     * 
+     * @param list   The list of rights to be removed.
+     */
+    public void remove(Rights list) {
+        rights.removeAll(list.rights); 
+    }
+    
+    /**
+     * Return a string value for the Rights set.  The string value is the 
+     * concatenation of the single-character Rights names. 
+     * 
+     * @return The string representation of this Rights set. 
+     */
+    public String toString() {
+        StringBuffer buff = new StringBuffer(); 
+        Iterator i = rights.iterator(); 
+        while (i.hasNext()) {
+            buff.append(i.next().toString()); 
+        }
+        return buff.toString(); 
+    }
+    
+    class RightComparator implements Comparator {
+        /**
+         * Compares its two arguments for order.  Returns a negative integer,
+         * zero, or a positive integer as the first argument is less than, equal
+         * to, or greater than the second.<p>
+         *
+         * The implementor must ensure that <tt>sgn(compare(x, y)) ==
+         * -sgn(compare(y, x))</tt> for all <tt>x</tt> and <tt>y</tt>.  (This
+         * implies that <tt>compare(x, y)</tt> must throw an exception if and only
+         * if <tt>compare(y, x)</tt> throws an exception.)<p>
+         *
+         * The implementor must also ensure that the relation is transitive:
+         * <tt>((compare(x, y)&gt;0) &amp;&amp; (compare(y, z)&gt;0))</tt> implies
+         * <tt>compare(x, z)&gt;0</tt>.<p>
+         *
+         * Finally, the implementer must ensure that <tt>compare(x, y)==0</tt>
+         * implies that <tt>sgn(compare(x, z))==sgn(compare(y, z))</tt> for all
+         * <tt>z</tt>.<p>
+         *
+         * It is generally the case, but <i>not</i> strictly required that
+         * <tt>(compare(x, y)==0) == (x.equals(y))</tt>.  Generally speaking,
+         * any comparator that violates this condition should clearly indicate
+         * this fact.  The recommended language is "Note: this comparator
+         * imposes orderings that are inconsistent with equals."
+         *
+         * @param o1 the first object to be compared.
+         * @param o2 the second object to be compared.
+         * @return a negative integer, zero, or a positive integer as the
+         * 	       first argument is less than, equal to, or greater than the
+         *	       second.
+         * @throws ClassCastException if the arguments' types prevent them from
+         * 	       being compared by this Comparator.
+         */
+        public int compare(Object o1, Object o2) {
+            // compare on the string value 
+            String left = o1.toString(); 
+            return left.compareTo(o2.toString()); 
+        }
+    }
+    
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPACLResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPACLResponse.java
new file mode 100644
index 0000000..46ed792
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPACLResponse.java
@@ -0,0 +1,52 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.imap.ACL;
+import org.apache.geronimo.javamail.store.imap.Rights;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token; 
+
+/**
+ * Utility class to aggregate status responses for a mailbox.
+ */
+public class IMAPACLResponse extends IMAPUntaggedResponse {
+    public String mailbox; 
+    public ACL[] acls; 
+    
+    public IMAPACLResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super("ACL",  data); 
+        
+        mailbox = source.readEncodedString();
+        List temp = new ArrayList(); 
+        
+        while (source.hasMore()) {
+            String name = source.readString(); 
+            String rights = source.readString(); 
+            temp.add(new ACL(name, new Rights(rights))); 
+        }
+        
+        acls = new ACL[temp.size()]; 
+        acls = (ACL[])temp.toArray(acls); 
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.java
new file mode 100644
index 0000000..25fc021
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.java
@@ -0,0 +1,78 @@
+/**
+ * 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.ByteArrayInputStream;
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+
+
+/**
+ * The full body content of a message.
+ */
+public class IMAPBody extends IMAPFetchBodyPart {
+    // the body content data
+    byte[] content = null;
+
+    /**
+     * Construct a top-level MessageText data item. 
+     * 
+     * @param data   The data for the Message Text     
+     * 
+     * @exception MessagingException
+     */
+    public IMAPBody(byte[] data) throws MessagingException {
+        this(new IMAPBodySection(IMAPBodySection.TEXT), data);
+    }
+    
+    /**
+     * Create a Message Text instance. 
+     * 
+     * @param section The section information.  This may include substring information if this
+     *                was just a partical fetch.
+     * @param data    The message content data.
+     * 
+     * @exception MessagingException
+     */
+    public IMAPBody(IMAPBodySection section, byte[] data) throws MessagingException {
+        super(TEXT, section);
+        // save the content 
+        content = data; 
+    }
+
+
+    /**
+     * Get the part content as a byte array.
+     *
+     * @return The part content as a byte array.
+     */
+    public byte[] getContent() {
+        return content;
+    }
+
+    /**
+     * Get an input stream for reading the part content.
+     *
+     * @return An ByteArrayInputStream sourced to the part content.
+     */
+    public InputStream getInputStream() {
+        return new ByteArrayInputStream(content);
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodySection.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodySection.java
new file mode 100644
index 0000000..241ea4d
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodySection.java
@@ -0,0 +1,273 @@
+/**
+ * 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.util.List;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.util.ResponseFormatException; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token; 
+
+/**
+ * Class to represent a FETCH response BODY segment qualifier.  The qualifier is 
+ * of the form "BODY[<section>]<<partial>>".  The optional section qualifier is 
+ * a "." separated part specifiers.  A part specifier is either a number, or 
+ * one of the tokens HEADER, HEADER.FIELD, HEADER.FIELD.NOT, MIME, and TEXT.  
+ * The partial specification is in the form "<start.length>". 
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPBodySection {
+    // the section type qualifiers 
+    static public final int BODY = 0; 
+    static public final int HEADERS = 1; 
+    static public final int HEADERSUBSET = 2; 
+    static public final int MIME = 3;
+    static public final int TEXT = 4; 
+    
+    // the optional part number 
+    public String partNumber = "1"; 
+    // the string name of the section 
+    public String sectionName = ""; 
+    // the section qualifier 
+    public int section; 
+    // the starting substring position 
+    public int start = -1; 
+    // the substring length (requested)
+    public int length = -1; 
+    // the list of any explicit header names 
+    public List headers = null; 
+    
+    /**
+     * Construct a simple-toplevel BodySection tag.
+     * 
+     * @param section The section identifier.
+     */
+    public IMAPBodySection(int section) {
+        this.section = section; 
+        partNumber = "1"; 
+        start = -1; 
+        length = -1; 
+    }
+    
+    /**
+     * construct a BodySegment descriptor from the FETCH returned name. 
+     * 
+     * @param name   The name code, which may be encoded with a section identifier and
+     *               substring qualifiers.
+     * 
+     * @exception MessagingException
+     */
+    public IMAPBodySection(IMAPResponseTokenizer source) throws MessagingException {
+        
+        // this could be just "BODY" alone.  
+        if (!source.peek(false, true).isType('[')) {
+            // complete body, all other fields take default  
+            section = BODY;             
+            return; 
+        }
+        
+        // now we need to scan along this, building up the pieces as we go. 
+        // NOTE:  The section identifiers use "[", "]", "." as delimiters, which 
+        // are normally acceptable in ATOM names.  We need to use the expanded 
+        // delimiter set to parse these tokens off. 
+        Token token = source.next(false, true); 
+        // the first token was the "[", now step to the next token in line. 
+        token = source.next(false, true); 
+        
+        if (token.isType(Token.NUMERIC)) {
+            token = parsePartNumber(token, source); 
+        }
+        
+        // have a potential name here?
+        if (token.isType(Token.ATOM)) {
+            token = parseSectionName(token, source); 
+        }
+        
+        // the HEADER.FIELD and HEADER.FIELD.NOT section types 
+        // are followed by a list of header names. 
+        if (token.isType('(')) {
+            token = parseHeaderList(source); 
+        }
+        
+        // ok, in theory, our current token should be a ']'
+        if (!token.isType(']')) {
+            throw new ResponseFormatException("Invalid section identifier on FETCH response"); 
+        }
+        
+        // do we have a substring qualifier?
+        // that needs to be stripped off too 
+        parseSubstringValues(source); 
+        
+        // now fill in the type information 
+        if (sectionName.equals("")) {
+            section = BODY; 
+        }
+        else if (sectionName.equals("HEADER")) {
+            section = HEADERS; 
+        }
+        else if (sectionName.equals("HEADER.FIELDS")) {
+            section = HEADERSUBSET; 
+        }
+        else if (sectionName.equals("HEADER.FIELDS.NOT")) {
+            section = HEADERSUBSET; 
+        }
+        else if (sectionName.equals("TEXT")) {
+            section = TEXT; 
+        }
+        else if (sectionName.equals("MIME")) {
+            section = MIME; 
+        }
+    }
+    
+    
+    /**
+     * Strip the part number off of a BODY section identifier.  The part number 
+     * is a series of "." separated tokens.  So "BODY[3.2.1]" would be the BODY for 
+     * section 3.2.1 of a multipart message.  The section may also have a qualifier
+     * name on the end.  "BODY[3.2.1.HEADER}" would be the HEADERS for that 
+     * body section.  The return value is the name of the section, which can 
+     * be a "" or the the section qualifier (e.g., "HEADER"). 
+     * 
+     * @param name   The section name.
+     * 
+     * @return The remainder of the section name after the numeric part number has 
+     *         been removed.
+     */
+    private Token parsePartNumber(Token token, IMAPResponseTokenizer source) throws MessagingException {
+        StringBuffer part = new StringBuffer(token.getValue()); 
+        // NB:  We're still parsing with the expanded delimiter set 
+        token = source.next(false, true); 
+        
+        while (true) {
+            // Not a period?  We've reached the end of the section number, 
+            // finalize the part number and let the caller figure out what 
+            // to do from here.  
+            if (!token.isType('.')) {
+                partNumber = part.toString(); 
+                return token; 
+            }
+            // might have another number section 
+            else {
+                // step to the next token 
+                token = source.next(false, true); 
+                // another section number piece?
+                if (token.isType(Token.NUMERIC)) {
+                    // add this to the collection, and continue 
+                    part.append('.'); 
+                    part.append(token.getValue()); 
+                    token = source.next(false, true); 
+                }
+                else  {
+                    partNumber = part.toString(); 
+                    // this is likely the start of the section name 
+                    return token; 
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * Parse the section name, if any, in a BODY section qualifier.  The 
+     * section name may stand alone within the body section (e.g., 
+     * "BODY[HEADERS]" or follow the section number (e.g., 
+     * "BODY[1.2.3.HEADERS.FIELDS.NOT]".  
+     * 
+     * @param token  The first token of the name sequence.
+     * @param source The source tokenizer.
+     * 
+     * @return The first non-name token in the response. 
+     */
+    private Token parseSectionName(Token token, IMAPResponseTokenizer source) throws MessagingException {
+        StringBuffer part = new StringBuffer(token.getValue()); 
+        // NB:  We're still parsing with the expanded delimiter set 
+        token = source.next(false, true); 
+        
+        while (true) {
+            // Not a period?  We've reached the end of the section number, 
+            // finalize the part number and let the caller figure out what 
+            // to do from here.  
+            if (!token.isType('.')) {
+                sectionName = part.toString(); 
+                return token; 
+            }
+            // might have another number section 
+            else {
+                // add this to the collection, and continue 
+                part.append('.'); 
+                part.append(source.readString()); 
+                token = source.next(false, true); 
+            }
+        }
+    }
+    
+    
+    /**
+     * Parse a header list that may follow the HEADER.FIELD or HEADER.FIELD.NOT
+     * name qualifier.  This is a list of string values enclosed in parens.
+     * 
+     * @param source The source tokenizer.
+     * 
+     * @return The next token in the response (which should be the section terminator, ']')
+     * @exception MessagingException
+     */
+    private Token parseHeaderList(IMAPResponseTokenizer source) throws MessagingException {
+        headers = new ArrayList();
+        
+        // normal parsing rules going on here 
+        while (source.notListEnd()) {
+            String value = source.readString();
+            headers.add(value);
+        }
+        // step over the closing paren 
+        source.next(); 
+        // NB, back to the expanded token rules again 
+        return source.next(false, true); 
+    }
+    
+    
+    /**
+     * Parse off the substring values following the section identifier, if 
+     * any.  If present, they will be in the format "<start.len>".  
+     * 
+     * @param source The source tokenizer.
+     * 
+     * @exception MessagingException
+     */
+    private void parseSubstringValues(IMAPResponseTokenizer source) throws MessagingException {
+        // We rarely have one of these, so it's a quick out 
+        if (!source.peek(false, true).isType('<')) {
+            return; 
+        }
+        // step over the angle bracket. 
+        source.next(false, true); 
+        // pull out the start information 
+        start = source.next(false, true).getInteger(); 
+        // step over the period 
+        source.next(false, true);         
+        // now the length bit                  
+        length = source.next(false, true).getInteger(); 
+        // and consume the closing angle bracket 
+        source.next(false, true); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java
new file mode 100644
index 0000000..ea20ec8
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java
@@ -0,0 +1,219 @@
+/**
+ * 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.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.ContentDisposition;
+import javax.mail.internet.ContentType;
+
+
+public class IMAPBodyStructure extends IMAPFetchDataItem {
+
+    // the MIME type information
+    public ContentType mimeType = new ContentType();
+    // the content disposition info
+    public ContentDisposition disposition = null;
+    // the message ID
+    public String contentID;
+    public String contentDescription;
+    public String transferEncoding;
+    // size of the message 
+    public int bodySize;
+    // number of lines, which only applies to text types.
+    public int lines = -1;
+
+    // "parts is parts".  If this is a multipart message, we have a body structure item for each subpart.
+    public IMAPBodyStructure[] parts;
+    // optional dispostiion parameters
+    public Map dispositionParameters;
+    // language parameters
+    public List languages;
+    // the MD5 hash
+    public String md5Hash;
+
+    // references to nested message information.
+    public IMAPEnvelope nestedEnvelope;
+    public IMAPBodyStructure nestedBody;
+
+
+    public IMAPBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
+        super(BODYSTRUCTURE);
+        parseBodyStructure(source);
+    }
+
+
+    protected void parseBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
+        // the body structure needs to start with a left paren
+        source.checkLeftParen();
+
+        // if we start with a parentized item, we have a multipart content type.  We need to
+        // recurse on each of those as appropriate
+        if (source.peek().getType() == '(') {
+            parseMultipartBodyStructure(source);
+        }
+        else {
+            parseSinglepartBodyStructure(source);
+        }
+    }
+
+
+    protected void parseMultipartBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
+        mimeType.setPrimaryType("multipart");
+        ArrayList partList = new ArrayList();
+
+        do {
+            // parse the subpiece (which might also be a multipart).
+            IMAPBodyStructure part = new IMAPBodyStructure(source);
+            partList.add(part);
+            // we keep doing this as long as we seen parenthized items.
+        } while (source.peek().getType() == '(');
+        
+        parts = (IMAPBodyStructure[])partList.toArray(new IMAPBodyStructure[partList.size()]); 
+
+        // get the subtype (required)
+        mimeType.setSubType(source.readString());
+
+        if (source.checkListEnd()) {
+            return;
+        }
+        // if the next token is the list terminator, we're done.  Otherwise, we need to read extension
+        // data.
+        if (source.checkListEnd()) {
+            return;
+        }
+        // read the content parameter information and copy into the ContentType.
+        mimeType.setParameterList(source.readParameterList());
+
+        // more optional stuff
+        if (source.checkListEnd()) {
+            return;
+        }
+
+        // go parse the extensions that are common to both single- and multi-part messages.
+        parseMessageExtensions(source);
+    }
+
+
+    protected void parseSinglepartBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
+        // get the primary and secondary types.
+        mimeType.setPrimaryType(source.readString());
+        mimeType.setSubType(source.readString());
+
+        // read the parameters associated with the content type.
+        mimeType.setParameterList(source.readParameterList());
+
+        // now a bunch of string value parameters
+        contentID = source.readStringOrNil();
+        contentDescription = source.readStringOrNil();
+        transferEncoding = source.readStringOrNil();
+        bodySize = source.readInteger();
+
+        // is this an embedded message type?  Embedded messages include envelope and body structure
+        // information for the embedded message next.
+        if (mimeType.match("message/rfc822")) {
+            // parse the nested information
+            nestedEnvelope = new IMAPEnvelope(source);
+            nestedBody = new IMAPBodyStructure(source);
+            lines = source.readInteger();
+        }
+        // text types include a line count
+        else if (mimeType.match("text/*")) {
+            lines = source.readInteger();
+        }
+
+        // now the optional extension data.  All of these are optional, but must be in the specified order.
+        if (source.checkListEnd()) {
+            return;
+        }
+
+        md5Hash = source.readString();
+
+        // go parse the extensions that are common to both single- and multi-part messages.
+        parseMessageExtensions(source);
+    }
+
+    /**
+     * Parse common message extension information shared between
+     * single part and multi part messages.
+     *
+     * @param source The source tokenizer..
+     */
+    protected void parseMessageExtensions(IMAPResponseTokenizer source) throws MessagingException {
+
+        // now the optional extension data.  All of these are optional, but must be in the specified order.
+        if (source.checkListEnd()) {
+            return;
+        }
+
+        disposition = new ContentDisposition();
+        // now the dispostion.  This is a string, followed by a parameter list.
+        disposition.setDisposition(source.readString());
+        disposition.setParameterList(source.readParameterList());
+
+        // once more
+        if (source.checkListEnd()) {
+            return;
+        }
+        // read the language info.
+        languages = source.readStringList();
+        // next is the body location information.  The Javamail APIs don't really expose that, so
+        // we'll just skip over that.
+
+        // once more
+        if (source.checkListEnd()) {
+            return;
+        }
+        // read the location info.
+        source.readStringList();
+
+
+        // we don't recognize any other forms of extension, so just skip over these.
+        while (source.notListEnd()) {
+            source.skipExtensionItem();
+        }
+    }
+
+
+    /**
+     * Tests if a body structure is for a multipart body.
+     *
+     * @return true if this is a multipart body part, false for a single part.
+     */
+    public boolean isMultipart() {
+        return parts != null;
+    }
+    
+    
+    /**
+     * Test if this body structure represents an attached message.  If it's a
+     * message, this will be a single part of MIME type message/rfc822. 
+     * 
+     * @return True if this is a nested message type, false for either a multipart or 
+     *         a single part of another type.
+     */
+    public boolean isAttachedMessage() {
+        return !isMultipart() && mimeType.match("message/rfc822"); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCapabilityResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCapabilityResponse.java
new file mode 100644
index 0000000..577893e
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCapabilityResponse.java
@@ -0,0 +1,90 @@
+/**
+ * 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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
+
+/**
+ * Util class to represent a CAPABILITY response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPCapabilityResponse extends IMAPUntaggedResponse {
+    // the advertised capabilities 
+    protected Map capabilities = new HashMap(); 
+    // the authentication mechanisms.  The order is important with 
+    // the authentications, as we a) want to process these in the 
+    // order presented, and b) need to convert them into String arrays 
+    // for Sasl API calls. 
+    protected List authentications = new ArrayList(); 
+
+    /**
+     * Create a reply object from a server response line (normally, untagged).  This includes
+     * doing the parsing of the response line.
+     *
+     * @param response The response line used to create the reply object.
+     */
+    public IMAPCapabilityResponse(IMAPResponseTokenizer source, byte [] response) throws MessagingException {
+        super("CAPABILITY", response); 
+        
+        // parse each of the capability tokens.  We're using the default RFC822 parsing rules,
+        // which does not consider "=" to be a delimiter token, so all "AUTH=" capabilities will
+        // come through as a single token.
+        while (source.hasMore()) {
+            // the capabilities are always ATOMs. 
+            String value = source.readAtom().toUpperCase(); 
+            // is this an authentication option?
+            if (value.startsWith("AUTH=")) {
+                // parse off the mechanism that fillows the "=", and add this to the supported list.
+                String mechanism = value.substring(5);
+                authentications.add(mechanism);
+            }
+            else {
+                // just add this to the capabilities map.
+                capabilities.put(value, value);
+            }
+        }
+    }
+    
+
+    /**
+     * Return the capability map for the server.
+     * 
+     * @return A map of the capability items.
+     */
+    public Map getCapabilities() {
+        return capabilities;
+    }
+    
+    /**
+     * Retrieve the map of the server-supported authentication
+     * mechanisms.
+     * 
+     * @return 
+     */
+    public List getAuthentications() {
+        return authentications;
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java
new file mode 100644
index 0000000..705dedf
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java
@@ -0,0 +1,1462 @@
+/**
+ * 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.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Vector;
+
+import javax.mail.FetchProfile; 
+import javax.mail.Flags;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Quota;
+import javax.mail.UIDFolder;
+
+import javax.mail.search.AddressTerm;
+import javax.mail.search.AndTerm;
+import javax.mail.search.BodyTerm;
+import javax.mail.search.ComparisonTerm;
+import javax.mail.search.DateTerm;
+import javax.mail.search.FlagTerm;
+import javax.mail.search.FromTerm;
+import javax.mail.search.FromStringTerm;
+import javax.mail.search.HeaderTerm;
+import javax.mail.search.MessageIDTerm;
+import javax.mail.search.MessageNumberTerm;
+import javax.mail.search.NotTerm;
+import javax.mail.search.OrTerm;
+import javax.mail.search.ReceivedDateTerm;
+import javax.mail.search.RecipientTerm;
+import javax.mail.search.RecipientStringTerm;
+import javax.mail.search.SearchException;
+import javax.mail.search.SearchTerm;
+import javax.mail.search.SentDateTerm;
+import javax.mail.search.SizeTerm;
+import javax.mail.search.StringTerm;
+import javax.mail.search.SubjectTerm;
+
+import org.apache.geronimo.javamail.store.imap.ACL; 
+import org.apache.geronimo.javamail.store.imap.IMAPFolder;
+import org.apache.geronimo.javamail.store.imap.Rights; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token; 
+
+import org.apache.geronimo.javamail.util.CommandFailedException;
+
+
+/**
+ * Utility class for building up what might be complex arguments
+ * to a command.  This includes the ability to directly write out
+ * binary arrays of data and have them constructed as IMAP
+ * literals.
+ */
+public class IMAPCommand {
+
+    // digits table for encoding IMAP modified Base64.  Note that this differs
+    // from "normal" base 64 by using ',' instead of '/' for the last digit.
+    public static final char[] encodingTable = {
+        'A', 'B', 'C', 'D', 'E', 'F', 'G',
+        'H', 'I', 'J', 'K', 'L', 'M', 'N',
+        'O', 'P', 'Q', 'R', 'S', 'T', 'U',
+        'V', 'W', 'X', 'Y', 'Z',
+        'a', 'b', 'c', 'd', 'e', 'f', 'g',
+        'h', 'i', 'j', 'k', 'l', 'm', 'n',
+        'o', 'p', 'q', 'r', 's', 't', 'u',
+        'v', 'w', 'x', 'y', 'z',
+        '0', '1', '2', '3', '4', '5', '6',
+        '7', '8', '9',
+        '+', ','
+    };
+    
+    protected boolean needWhiteSpace = false;
+
+    // our utility writer stream
+    protected DataOutputStream out;
+    // the real output target
+    protected ByteArrayOutputStream sink;
+    // our command segment set.  If the command contains literals, then the literal     
+    // data must be sent after receiving an continue response back from the server. 
+    protected List segments = null; 
+    // the append tag for the response 
+    protected String tag; 
+    
+    // our counter used to generate command tags.
+    static protected int tagCounter = 0;
+
+    /**
+     * Create an empty command. 
+     */
+    public IMAPCommand() {
+        try {
+            sink = new ByteArrayOutputStream();
+            out = new DataOutputStream(sink);
+
+            // write the tag data at the beginning of the command. 
+            out.writeBytes(getTag()); 
+            // need a blank separator 
+            out.write(' '); 
+        } catch (IOException e ) {
+        }
+    }
+
+    /**
+     * Create a command with an initial command string.
+     * 
+     * @param command The command string used to start this command.
+     */
+    public IMAPCommand(String command) {
+        this(); 
+        append(command); 
+    }
+    
+    public String getTag() {
+        if (tag == null) {
+            // the tag needs to be non-numeric, so tack a convenient alpha character on the front.
+            tag = "a" + tagCounter++;
+        }
+        return tag; 
+    }
+    
+    
+    /**
+     * Save the current segment of the command we've accumulated.  This 
+     * generally occurs because we have a literal element in the command 
+     * that's going to require a continuation response from the server before 
+     * we can send it. 
+     */
+    private void saveCurrentSegment() 
+    {
+        try {
+            out.flush();     // make sure everything is written 
+                             // get the data so far and reset the sink 
+            byte[] segment = sink.toByteArray(); 
+            sink.reset(); 
+            // most commands don't have segments, so don't create the list until we do. 
+            if (segments == null) {
+                segments = new ArrayList(); 
+            }
+            // ok, we need to issue this command as a conversation. 
+            segments.add(segment);
+        } catch (IOException e) {
+        }
+    }
+    
+    
+    /**
+     * Write all of the command data to the stream.  This includes the 
+     * leading tag data. 
+     * 
+     * @param outStream
+     * @param connection
+     * 
+     * @exception IOException
+     * @exception MessagingException
+     */
+    public void writeTo(OutputStream outStream, IMAPConnection connection) throws IOException, MessagingException
+    {
+        
+        // just a simple, single string-encoded command?
+        if (segments == null) {
+            // make sure the output stream is flushed
+            out.flush(); 
+            // just copy the command data to the output stream 
+            sink.writeTo(outStream); 
+            // we need to end the command with a CRLF sequence. 
+            outStream.write('\r');
+            outStream.write('\n');
+        }
+        // multiple-segment mode, which means we need to deal with continuation responses at 
+        // each of the literal boundaries. 
+        else { 
+            // at this point, we have a list of command pieces that must be written out, then a 
+            // continuation response checked for after each write.  Once each of these pieces is 
+            // written out, we still have command stuff pending in the out stream, which we'll tack  
+            // on to the end. 
+            for (int i = 0; i < segments.size(); i++) {
+                outStream.write((byte [])segments.get(i)); 
+                // now wait for a response from the connection.  We should be getting a  
+                // continuation response back (and might have also received some asynchronous 
+                // replies, which we'll leave in the queue for now.  If we get some status back 
+                // other than than a continue, we've got an error in our command somewhere. 
+                IMAPTaggedResponse response = connection.receiveResponse(); 
+                if (!response.isContinuation()) {
+                    throw new CommandFailedException("Error response received on a IMAP continued command:  " + response); 
+                }
+            }
+            out.flush();
+            // all leading segments written with the appropriate continuation received in reply. 
+            // just copy the command data to the output stream 
+            sink.writeTo(outStream); 
+            // we need to end the command with a CRLF sequence. 
+            outStream.write('\r');
+            outStream.write('\n');
+        }
+    }
+
+
+    /**
+     * Directly append a value to the buffer without attempting
+     * to insert whitespace or figure out any format encodings.
+     *
+     * @param value  The value to append.
+     */
+    public void append(String value) {
+        try {
+            // add the bytes direcly 
+            out.writeBytes(value);
+            // assume we're needing whitespace after this (pretty much unknown).
+            needWhiteSpace = true;
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append a string value to a command buffer.  This sorts out
+     * what form the string needs to be appended in (LITERAL, QUOTEDSTRING,
+     * or ATOM).
+     *
+     * @param target The target buffer for appending the string.
+     * @param value  The value to append.
+     */
+    public void appendString(String value) {
+        // work off the byte values
+        appendString(value.getBytes());
+    }
+
+
+    /**
+     * Append a string value to a command buffer.  This always appends as
+     * a QUOTEDSTRING
+     * 
+     * @param value  The value to append.
+     */
+    public void appendQuotedString(String value) {
+        // work off the byte values
+        appendQuotedString(value.getBytes());
+    }
+
+
+    /**
+     * Append a string value to a command buffer, with encoding.  This sorts out
+     * what form the string needs to be appended in (LITERAL, QUOTEDSTRING,
+     * or ATOM).
+     *
+     * @param target The target buffer for appending the string.
+     * @param value  The value to append.
+     */
+    public void appendEncodedString(String value) {
+        // encode first.
+        value = encode(value);
+        // work off the byte values
+        appendString(value.getBytes());
+    }
+
+    
+    /**
+     * Encode a string using the modified UTF-7 encoding.
+     *
+     * @param original The original string.
+     *
+     * @return The original string encoded with modified UTF-7 encoding.
+     */
+    public String encode(String original) {
+
+        // buffer for encoding sections of data
+        byte[] buffer = new byte[4];
+        int bufferCount = 0;
+
+        StringBuffer result = new StringBuffer();
+
+        // state flag for the type of section we're in.
+        boolean encoding = false;
+
+        for (int i = 0; i < original.length(); i++) {
+            char ch = original.charAt(i);
+
+            // processing an encoded section?
+            if (encoding) {
+                // is this a printable character?
+                if (ch > 31 && ch < 127) {
+                    // encode anything in the buffer
+                    encode(buffer, bufferCount, result);
+                    // add the section terminator char
+                    result.append('-');
+                    encoding = false;
+                    // we now fall through to the printable character section.
+                }
+                // still an unprintable
+                else {
+                    // add this char to the working buffer?
+                    buffer[++bufferCount] = (byte)(ch >> 8);
+                    buffer[++bufferCount] = (byte)(ch & 0xff);
+                    // if we have enough to encode something, do it now.
+                    if (bufferCount >= 3) {
+                        bufferCount = encode(buffer, bufferCount, result);
+                    }
+                    // go back to the top of the loop.
+                    continue;
+                }
+            }
+            // is this the special printable?
+            if (ch == '&') {
+                // this is the special null escape sequence
+                result.append('&');
+                result.append('-');
+            }
+            // is this a printable character?
+            else if (ch > 31 && ch < 127) {
+                // just add to the result
+                result.append(ch);
+            }
+            else {
+                // write the escape character
+                result.append('&');
+
+                // non-printable ASCII character, we need to switch modes
+                // both bytes of this character need to be encoded.  Each
+                // encoded digit will basically be a "character-and-a-half".
+                buffer[0] = (byte)(ch >> 8);
+                buffer[1] = (byte)(ch & 0xff);
+                bufferCount = 2;
+                encoding = true;
+            }
+        }
+        // were we in a non-printable section at the end?
+        if (encoding) {
+            // take care of any remaining characters
+            encode(buffer, bufferCount, result);
+            // add the section terminator char
+            result.append('-');
+        }
+        // convert the encoded string.
+        return result.toString();
+    }
+    
+
+    /**
+     * Encode a single buffer of characters.  This buffer will have
+     * between 0 and 4 bytes to encode.
+     *
+     * @param buffer The buffer to encode.
+     * @param count  The number of characters in the buffer.
+     * @param result The accumulator for appending the result.
+     *
+     * @return The remaining number of bytes remaining in the buffer (return 0
+     *         unless the count was 4 at the beginning).
+     */
+    protected static int encode(byte[] buffer, int count, StringBuffer result) {
+        byte b1 = 0;
+        byte b2 = 0;
+        byte b3 = 0;
+
+        // different processing based on how much we have in the buffer
+        switch (count) {
+            // ended at a boundary.  This is cool, not much to do.
+            case 0:
+                // no residual in the buffer
+                return 0;
+
+            // just a single left over byte from the last encoding op.
+            case 1:
+                b1 = buffer[0];
+                result.append(encodingTable[(b1 >>> 2) & 0x3f]);
+                result.append(encodingTable[(b1 << 4) & 0x30]);
+                return 0;
+
+            // one complete char to encode
+            case 2:
+                b1 = buffer[0];
+                b2 = buffer[1];
+                result.append(encodingTable[(b1 >>> 2) & 0x3f]);
+                result.append(encodingTable[((b1 << 4) & 0x30) + ((b2 >>>4) & 0x0f)]);
+                result.append(encodingTable[((b2 << 2) & (0x3c))]);
+                return 0;
+
+            // at least a full triplet of bytes to encode
+            case 3:
+            case 4:
+                b1 = buffer[0];
+                b2 = buffer[1];
+                b3 = buffer[2];
+                result.append(encodingTable[(b1 >>> 2) & 0x3f]);
+                result.append(encodingTable[((b1 << 4) & 0x30) + ((b2 >>>4) & 0x0f)]);
+                result.append(encodingTable[((b2 << 2) & 0x3c) + ((b3 >>> 6) & 0x03)]);
+                result.append(encodingTable[b3 & 0x3f]);
+
+                // if we have more than the triplet, we need to move the extra one into the first
+                // position and return the residual indicator
+                if (count == 4) {
+                    buffer[0] = buffer[4];
+                    return 1;
+                }
+                return 0;
+        }
+        return 0;
+    }
+
+
+    /**
+     * Append a string value to a command buffer.  This sorts out
+     * what form the string needs to be appended in (LITERAL, QUOTEDSTRING,
+     * or ATOM).
+     *
+     * @param target The target buffer for appending the string.
+     * @param value  The value to append.
+     */
+    public void appendString(String value, String charset) throws MessagingException {
+        if (charset == null) {
+            // work off the byte values
+            appendString(value.getBytes());
+        }
+        else {
+            try {
+                // use the charset to extract the bytes
+                appendString(value.getBytes(charset));
+            } catch (UnsupportedEncodingException e) {
+                throw new MessagingException("Invalid text encoding");
+            }
+        }
+    }
+
+
+    /**
+     * Append a value in a byte array to a command buffer.  This sorts out
+     * what form the string needs to be appended in (LITERAL, QUOTEDSTRING,
+     * or ATOM).
+     *
+     * @param target The target buffer for appending the string.
+     * @param value  The value to append.
+     */
+    public void appendString(byte[] value) {
+        // sort out how we need to append this
+        switch (IMAPResponseTokenizer.getEncoding(value)) {
+            case Token.LITERAL:
+                appendLiteral(value);
+                break;
+            case Token.QUOTEDSTRING:
+                appendQuotedString(value);
+                break;
+            case Token.ATOM:
+                appendAtom(value);
+                break;
+        }
+    }
+
+
+    /**
+     * Append an integer value to the command, converting 
+     * the integer into string form.
+     * 
+     * @param value  The value to append.
+     */
+    public void appendInteger(int value) {
+        appendAtom(Integer.toString(value));
+    }
+
+    
+    /**
+     * Append a long value to the command, converting 
+     * the integer into string form.
+     * 
+     * @param value  The value to append.
+     */
+    public void appendLong(long value) {
+        appendAtom(Long.toString(value));
+    }
+
+
+    /**
+     * Append an atom value to the command.  Atoms are directly 
+     * appended without using literal encodings. 
+     * 
+     * @param value  The value to append.
+     */
+    public void appendAtom(String value) {
+        appendAtom(value.getBytes());
+    }
+
+
+
+    /**
+     * Append an atom to the command buffer.  Atoms are directly 
+     * appended without using literal encodings.  White space is  
+     * accounted for with the append operation. 
+     * 
+     * @param value  The value to append.
+     */
+    public void appendAtom(byte[] value) {
+        try {
+            // give a token separator
+            conditionalWhitespace();
+            // ATOMs are easy
+            out.write(value);
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append an IMAP literal values to the command.  
+     * literals are written using a header with the length 
+     * specified, followed by a CRLF sequence, followed 
+     * by the literal data. 
+     * 
+     * @param value  The literal data to write.
+     */
+    public void appendLiteral(byte[] value) {
+        try {
+            appendLiteralHeader(value.length);
+            out.write(value);
+        } catch (IOException e) {
+        }
+    }
+
+    /**
+     * Add a literal header to the buffer.  The literal 
+     * header is the literal length enclosed in a 
+     * "{n}" pair, followed by a CRLF sequence.
+     * 
+     * @param size   The size of the literal value.
+     */
+    protected void appendLiteralHeader(int size) {
+        try {
+            conditionalWhitespace();
+            out.writeByte('{');
+            out.writeBytes(Integer.toString(size));
+            out.writeBytes("}\r\n");
+            // the IMAP client is required to send literal data to the server by 
+            // writing the command up to the header, then waiting for a continuation 
+            // response to send the rest. 
+            saveCurrentSegment(); 
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append literal data to the command where the 
+     * literal sourcd is a ByteArrayOutputStream.
+     * 
+     * @param value  The source of the literal data.
+     */
+    public void appendLiteral(ByteArrayOutputStream value) {
+        try {
+            appendLiteralHeader(value.size());
+            // have this output stream write directly into our stream
+            value.writeTo(out);
+        } catch (IOException e) {
+        }
+    }
+
+    /**
+     * Write out a string of literal data, taking into 
+     * account the need to escape both '"' and '\' 
+     * characters.
+     * 
+     * @param value  The bytes of the string to write.
+     */
+    public void appendQuotedString(byte[] value) {
+        try {
+            conditionalWhitespace();
+            out.writeByte('"');
+
+            // look for chars requiring escaping
+            for (int i = 0; i < value.length; i++) {
+                byte ch = value[i];
+
+                if (ch == '"' || ch == '\\') {
+                    out.writeByte('\\');
+                }
+                out.writeByte(ch);
+            }
+
+            out.writeByte('"');
+        } catch (IOException e) {
+        }
+    }
+
+    /**
+     * Mark the start of a list value being written to 
+     * the command.  A list is a sequences of different 
+     * tokens enclosed in "(" ")" pairs.  Lists can 
+     * be nested. 
+     */
+    public void startList() {
+        try {
+            conditionalWhitespace();
+            out.writeByte('(');
+            needWhiteSpace = false;
+        } catch (IOException e) {
+        }
+    }
+
+    /**
+     * Write out the end of the list. 
+     */
+    public void endList() {
+        try {
+            out.writeByte(')');
+            needWhiteSpace = true;
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Add a whitespace character to the command if the 
+     * previous token was a type that required a 
+     * white space character to mark the boundary. 
+     */
+    protected void conditionalWhitespace() {
+        try {
+            if (needWhiteSpace) {
+                out.writeByte(' ');
+            }
+            // all callers of this are writing a token that will need white space following, so turn this on
+            // every time we're called.
+            needWhiteSpace = true;
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append a body section specification to a command string.  Body
+     * section specifications are of the form "[section]<start.count>".
+     * 
+     * @param section  The section numeric identifier.
+     * @param partName The name of the body section we want (e.g. "TEST", "HEADERS").
+     */
+    public void appendBodySection(String section, String partName) {
+        try {
+            // we sometimes get called from the top level 
+            if (section == null) {
+                appendBodySection(partName); 
+                return;
+            }
+
+            out.writeByte('[');
+            out.writeBytes(section);
+            if (partName != null) {
+                out.writeByte('.');
+                out.writeBytes(partName);
+            }
+            out.writeByte(']');
+            needWhiteSpace = true;
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append a body section specification to a command string.  Body
+     * section specifications are of the form "[section]".
+     * 
+     * @param partName The partname we require.
+     */
+    public void appendBodySection(String partName) {
+        try {
+            out.writeByte('[');
+            out.writeBytes(partName);
+            out.writeByte(']');
+            needWhiteSpace = true;
+        } catch (IOException e) {
+        }
+    }
+
+
+    /**
+     * Append a set of flags to a command buffer.
+     *
+     * @param flags  The flag set to append.
+     */
+    public void appendFlags(Flags flags) {
+        startList();
+
+        Flags.Flag[] systemFlags = flags.getSystemFlags();
+
+        // process each of the system flag names
+        for (int i = 0; i < systemFlags.length; i++) {
+            Flags.Flag flag = systemFlags[i];
+
+            if (flag == Flags.Flag.ANSWERED) {
+                appendAtom("\\Answered");
+            }
+            else if (flag == Flags.Flag.DELETED) {
+                appendAtom("\\Deleted");
+            }
+            else if (flag == Flags.Flag.DRAFT) {
+                appendAtom("\\Draft");
+            }
+            else if (flag == Flags.Flag.FLAGGED) {
+                appendAtom("\\Flagged");
+            }
+            else if (flag == Flags.Flag.RECENT) {
+                appendAtom("\\Recent");
+            }
+            else if (flag == Flags.Flag.SEEN) {
+                appendAtom("\\Seen");
+            }
+        }
+
+        // now process the user flags, which just get appended as is.
+        String[] userFlags = flags.getUserFlags();
+
+        for (int i = 0; i < userFlags.length; i++) {
+            appendAtom(userFlags[i]);
+        }
+
+        // close the list off
+        endList();
+    }
+
+
+    /**
+     * Format a date into the form required for IMAP commands.
+     *
+     * @param d      The source Date.
+     */
+    public void appendDate(Date d) {
+        // get a formatter to create IMAP dates.  Use the US locale, as the dates are not localized.
+        IMAPDateFormat formatter = new IMAPDateFormat();
+        // date_time strings need to be done as quoted strings because they contain blanks.
+        appendString(formatter.format(d));
+    }
+
+
+    /**
+     * Format a date into the form required for IMAP search commands.
+     *
+     * @param d      The source Date.
+     */
+    public void appendSearchDate(Date d) {
+        // get a formatter to create IMAP dates.  Use the US locale, as the dates are not localized.
+        IMAPSearchDateFormat formatter = new IMAPSearchDateFormat();
+        // date_time strings need to be done as quoted strings because they contain blanks.
+        appendString(formatter.format(d));
+    }
+    
+    
+    /**
+     * append an IMAP search sequence from a SearchTerm.  SearchTerms
+     * terms can be complex sets of terms in a tree form, so this
+     * may involve some recursion to completely translate.
+     *
+     * @param term    The search term we're processing.
+     * @param charset The charset we need to use when generating the sequence.
+     *
+     * @exception MessagingException
+     */
+    public void appendSearchTerm(SearchTerm term, String charset) throws MessagingException {
+        // we need to do this manually, by inspecting the term object against the various SearchTerm types
+        // defined by the javamail spec.
+
+        // Flag searches are used internally by other operations, so this is a good one to check first.
+        if (term instanceof FlagTerm) {
+            appendFlag((FlagTerm)term, charset);
+        }
+        // after that, I'm not sure there's any optimal order to these.  Let's start with the conditional
+        // modifiers (AND, OR, NOT), then just hit each of the header types
+        else if (term instanceof AndTerm) {
+            appendAnd((AndTerm)term, charset);
+        }
+        else if (term instanceof OrTerm) {
+            appendOr((OrTerm)term, charset);
+        }
+        else if (term instanceof NotTerm) {
+            appendNot((NotTerm)term, charset);
+        }
+        // multiple forms of From: search
+        else if (term instanceof FromTerm) {
+            appendFrom((FromTerm)term, charset);
+        }
+        else if (term instanceof FromStringTerm) {
+            appendFrom((FromStringTerm)term, charset);
+        }
+        else if (term instanceof HeaderTerm) {
+            appendHeader((HeaderTerm)term, charset);
+        }
+        else if (term instanceof RecipientTerm) {
+            appendRecipient((RecipientTerm)term, charset);
+        }
+        else if (term instanceof RecipientStringTerm) {
+            appendRecipient((RecipientStringTerm)term, charset);
+        }
+        else if (term instanceof SubjectTerm) {
+            appendSubject((SubjectTerm)term, charset);
+        }
+        else if (term instanceof BodyTerm) {
+            appendBody((BodyTerm)term, charset);
+        }
+        else if (term instanceof SizeTerm) {
+            appendSize((SizeTerm)term, charset);
+        }
+        else if (term instanceof SentDateTerm) {
+            appendSentDate((SentDateTerm)term, charset);
+        }
+        else if (term instanceof ReceivedDateTerm) {
+            appendReceivedDate((ReceivedDateTerm)term, charset);
+        }
+        else if (term instanceof MessageIDTerm) {
+            appendMessageID((MessageIDTerm)term, charset);
+        }
+        else {
+            // don't know what this is
+            throw new SearchException("Unsupported search type");
+        }
+    }
+
+    /**
+     * append IMAP search term information from a FlagTerm item.
+     *
+     * @param term    The source FlagTerm
+     * @param charset target charset for the search information (can be null).
+     * @param out     The target command buffer.
+     */
+    protected void appendFlag(FlagTerm term, String charset) {
+        // decide which one we need to test for
+        boolean set = term.getTestSet();
+
+        Flags flags = term.getFlags();
+        Flags.Flag[] systemFlags = flags.getSystemFlags();
+
+        String[] userFlags = flags.getUserFlags();
+
+        // empty search term?  not sure if this is an error.  The default search implementation would
+        // not consider this an error, so we'll just ignore this.
+        if (systemFlags.length == 0 && userFlags.length == 0) {
+            return;
+        }
+
+        if (set) {
+            for (int i = 0; i < systemFlags.length; i++) {
+                Flags.Flag flag = systemFlags[i];
+
+                if (flag == Flags.Flag.ANSWERED) {
+                    appendAtom("ANSWERED");
+                }
+                else if (flag == Flags.Flag.DELETED) {
+                    appendAtom("DELETED");
+                }
+                else if (flag == Flags.Flag.DRAFT) {
+                    appendAtom("DRAFT");
+                }
+                else if (flag == Flags.Flag.FLAGGED) {
+                    appendAtom("FLAGGED");
+                }
+                else if (flag == Flags.Flag.RECENT) {
+                    appendAtom("RECENT");
+                }
+                else if (flag == Flags.Flag.SEEN) {
+                    appendAtom("SEEN");
+                }
+            }
+        }
+        else {
+            for (int i = 0; i < systemFlags.length; i++) {
+                Flags.Flag flag = systemFlags[i];
+
+                if (flag == Flags.Flag.ANSWERED) {
+                    appendAtom("UNANSWERED");
+                }
+                else if (flag == Flags.Flag.DELETED) {
+                    appendAtom("UNDELETED");
+                }
+                else if (flag == Flags.Flag.DRAFT) {
+                    appendAtom("UNDRAFT");
+                }
+                else if (flag == Flags.Flag.FLAGGED) {
+                    appendAtom("UNFLAGGED");
+                }
+                else if (flag == Flags.Flag.RECENT) {
+                    // not UNRECENT?
+                    appendAtom("OLD");
+                }
+                else if (flag == Flags.Flag.SEEN) {
+                    appendAtom("UNSEEN");
+                }
+            }
+        }
+
+
+        // User flags are done as either "KEYWORD name" or "UNKEYWORD name"
+        for (int i = 0; i < userFlags.length; i++) {
+            appendAtom(set ? "KEYWORD" : "UNKEYWORD");
+            appendAtom(userFlags[i]);
+        }
+    }
+
+
+    /**
+     * append IMAP search term information from an AndTerm item.
+     *
+     * @param term    The source AndTerm
+     * @param charset target charset for the search information (can be null).
+     * @param out     The target command buffer.
+     */
+    protected void appendAnd(AndTerm term, String charset) throws MessagingException {
+        // ANDs are pretty easy.  Just append all of the terms directly to the
+        // command as is.
+
+        SearchTerm[] terms = term.getTerms();
+
+        for (int i = 0; i < terms.length; i++) {
+            appendSearchTerm(terms[i], charset);
+        }
+    }
+
+
+    /**
+     * append IMAP search term information from an OrTerm item.
+     *
+     * @param term    The source OrTerm
+     * @param charset target charset for the search information (can be null).
+     * @param out     The target command buffer.
+     */
+    protected void appendOr(OrTerm term, String charset) throws MessagingException {
+        SearchTerm[] terms = term.getTerms();
+
+        // OrTerms are a bit of a pain to translate to IMAP semantics.  The IMAP OR operation only allows 2
+        // search keys, while OrTerms can have n keys (including, it appears, just one!  If we have more than
+        // 2, it's easiest to convert this into a tree of OR keys and let things generate that way.  The
+        // resulting IMAP operation would be OR (key1) (OR (key2) (key3))
+
+        // silly rabbit...somebody doesn't know how to use OR
+        if (terms.length == 1) {
+            // just append the singleton in place without the OR operation.
+            appendSearchTerm(terms[0], charset);
+            return;
+        }
+
+        // is this a more complex operation?
+        if (terms.length > 2) {
+            // have to chain these together (shazbat).
+            SearchTerm current = terms[0];
+
+            for (int i = 1; i < terms.length; i++) {
+                current = new OrTerm(current, terms[i]);
+            }
+
+            // replace the term array with the newly generated top array
+            terms = ((OrTerm)current).getTerms();
+        }
+
+        // we're going to generate this with parenthetical search keys, even if it is just a simple term.
+        appendAtom("OR");
+        startList(); 
+        // generated OR argument 1
+        appendSearchTerm(terms[0], charset);
+        endList(); 
+        startList(); 
+        // generated OR argument 2
+        appendSearchTerm(terms[0], charset);
+        // and the closing parens
+        endList(); 
+    }
+
+
+    /**
+     * append IMAP search term information from a NotTerm item.
+     *
+     * @param term    The source NotTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendNot(NotTerm term, String charset) throws MessagingException {
+        // we're goint to generate this with parenthetical search keys, even if it is just a simple term.
+        appendAtom("NOT");
+        startList(); 
+        // generated the NOT expression
+        appendSearchTerm(term.getTerm(), charset);
+        // and the closing parens
+        endList(); 
+    }
+
+
+    /**
+     * append IMAP search term information from a FromTerm item.
+     *
+     * @param term    The source FromTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendFrom(FromTerm term, String charset) throws MessagingException {
+        appendAtom("FROM");
+        // this may require encoding
+        appendString(term.getAddress().toString(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a FromStringTerm item.
+     *
+     * @param term    The source FromStringTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendFrom(FromStringTerm term, String charset) throws MessagingException {
+        appendAtom("FROM");
+        // this may require encoding
+        appendString(term.getPattern(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a RecipientTerm item.
+     *
+     * @param term    The source RecipientTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendRecipient(RecipientTerm term, String charset) throws MessagingException {
+        appendAtom(recipientType(term.getRecipientType()));
+        // this may require encoding
+        appendString(term.getAddress().toString(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a RecipientStringTerm item.
+     *
+     * @param term    The source RecipientStringTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendRecipient(RecipientStringTerm term, String charset) throws MessagingException {
+        appendAtom(recipientType(term.getRecipientType()));
+        // this may require encoding
+        appendString(term.getPattern(), charset);
+    }
+
+
+    /**
+     * Translate a recipient type into it's string name equivalent.
+     *
+     * @param type   The source recipient type
+     *
+     * @return A string name matching the recipient type.
+     */
+    protected String recipientType(Message.RecipientType type) throws MessagingException {
+        if (type == Message.RecipientType.TO) {
+            return "TO";
+        }
+        if (type == Message.RecipientType.CC) {
+            return "CC";
+        }
+        if (type == Message.RecipientType.BCC) {
+            return "BCC";
+        }
+
+        throw new SearchException("Unsupported RecipientType");
+    }
+
+
+    /**
+     * append IMAP search term information from a HeaderTerm item.
+     *
+     * @param term    The source HeaderTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendHeader(HeaderTerm term, String charset) throws MessagingException {
+        appendAtom("HEADER");
+        appendString(term.getHeaderName());
+        appendString(term.getPattern(), charset);
+    }
+
+
+
+    /**
+     * append IMAP search term information from a SubjectTerm item.
+     *
+     * @param term    The source SubjectTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendSubject(SubjectTerm term, String charset) throws MessagingException {
+        appendAtom("SUBJECT");
+        appendString(term.getPattern(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a BodyTerm item.
+     *
+     * @param term    The source BodyTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendBody(BodyTerm term, String charset) throws MessagingException {
+        appendAtom("BODY");
+        appendString(term.getPattern(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a SizeTerm item.
+     *
+     * @param term    The source SizeTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendSize(SizeTerm term, String charset) throws MessagingException {
+
+        // these comparisons can be a real pain.  IMAP only supports LARGER and SMALLER.  So comparisons
+        // other than GT and LT have to be composed of complex sequences of these.  For example, an EQ
+        // comparison becomes NOT LARGER size NOT SMALLER size
+
+        if (term.getComparison() == ComparisonTerm.GT) {
+            appendAtom("LARGER");
+            appendInteger(term.getNumber());
+        }
+        else if (term.getComparison() == ComparisonTerm.LT) {
+            appendAtom("SMALLER");
+            appendInteger(term.getNumber());
+        }
+        else if (term.getComparison() == ComparisonTerm.EQ) {
+            appendAtom("NOT");
+            appendAtom("LARGER");
+            appendInteger(term.getNumber());
+
+            appendAtom("NOT");
+            appendAtom("SMALLER");
+            // it's just right <g>
+            appendInteger(term.getNumber());
+        }
+        else if (term.getComparison() == ComparisonTerm.NE) {
+            // this needs to be an OR comparison
+            appendAtom("OR");
+            appendAtom("LARGER");
+            appendInteger(term.getNumber());
+
+            appendAtom("SMALLER");
+            appendInteger(term.getNumber());
+        }
+        else if (term.getComparison() == ComparisonTerm.LE) {
+            // just the inverse of LARGER
+            appendAtom("NOT");
+            appendAtom("LARGER");
+            appendInteger(term.getNumber());
+        }
+        else if (term.getComparison() == ComparisonTerm.GE) {
+            // and the reverse.
+            appendAtom("NOT");
+            appendAtom("SMALLER");
+            appendInteger(term.getNumber());
+        }
+    }
+
+
+    /**
+     * append IMAP search term information from a MessageIDTerm item.
+     *
+     * @param term    The source MessageIDTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendMessageID(MessageIDTerm term, String charset) throws MessagingException {
+
+        // not directly supported by IMAP, but we can compare on the header information.
+        appendAtom("HEADER");
+        appendString("Message-ID");
+        appendString(term.getPattern(), charset);
+    }
+
+
+    /**
+     * append IMAP search term information from a SendDateTerm item.
+     *
+     * @param term    The source SendDateTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendSentDate(SentDateTerm term, String charset) throws MessagingException {
+        Date date = term.getDate();
+
+        switch (term.getComparison()) {
+            case ComparisonTerm.EQ:
+                appendAtom("SENTON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.LT:
+                appendAtom("SENTBEFORE");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.GT:
+                appendAtom("SENTSINCE");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.GE:
+                appendAtom("OR");
+                appendAtom("SENTSINCE");
+                appendSearchDate(date);
+                appendAtom("SENTON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.LE:
+                appendAtom("OR");
+                appendAtom("SENTBEFORE");
+                appendSearchDate(date);
+                appendAtom("SENTON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.NE:
+                appendAtom("NOT");
+                appendAtom("SENTON");
+                appendSearchDate(date);
+                break;
+            default:
+                throw new SearchException("Unsupported date comparison type");
+        }
+    }
+
+
+    /**
+     * append IMAP search term information from a ReceivedDateTerm item.
+     *
+     * @param term    The source ReceivedDateTerm
+     * @param charset target charset for the search information (can be null).
+     */
+    protected void appendReceivedDate(ReceivedDateTerm term, String charset) throws MessagingException {
+        Date date = term.getDate();
+
+        switch (term.getComparison()) {
+            case ComparisonTerm.EQ:
+                appendAtom("ON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.LT:
+                appendAtom("BEFORE");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.GT:
+                appendAtom("SINCE");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.GE:
+                appendAtom("OR");
+                appendAtom("SINCE");
+                appendSearchDate(date);
+                appendAtom("ON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.LE:
+                appendAtom("OR");
+                appendAtom("BEFORE");
+                appendSearchDate(date);
+                appendAtom("ON");
+                appendSearchDate(date);
+                break;
+            case ComparisonTerm.NE:
+                appendAtom("NOT");
+                appendAtom("ON");
+                appendSearchDate(date);
+                break;
+            default:
+                throw new SearchException("Unsupported date comparison type");
+        }
+    }
+
+
+    /**
+     * Run the tree of search terms, checking for problems with
+     * the terms that may require specifying a CHARSET modifier
+     * on a SEARCH command sent to the server.
+     *
+     * @param term   The term to check.
+     *
+     * @return True if there are 7-bit problems, false if the terms contain
+     *         only 7-bit ASCII characters.
+     */
+    static public boolean checkSearchEncoding(SearchTerm term) {
+        // StringTerm is the basis of most of the string-valued terms, and are most important ones to check.
+        if (term instanceof StringTerm) {
+            return checkStringEncoding(((StringTerm)term).getPattern());
+        }
+        // Address terms are basically string terms also, but we need to check the string value of the
+        // addresses, since that's what we're sending along.  This covers a lot of the TO/FROM, etc. searches.
+        else if (term instanceof AddressTerm) {
+            return checkStringEncoding(((AddressTerm)term).getAddress().toString());
+        }
+        // the NOT contains a term itself, so recurse on that.  The NOT does not directly have string values
+        // to check.
+        else if (term instanceof NotTerm) {
+            return checkSearchEncoding(((NotTerm)term).getTerm());
+        }
+        // AND terms and OR terms have lists of subterms that must be checked.
+        else if (term instanceof AndTerm) {
+            return checkSearchEncoding(((AndTerm)term).getTerms());
+        }
+        else if (term instanceof OrTerm) {
+            return checkSearchEncoding(((OrTerm)term).getTerms());
+        }
+
+        // non of the other term types (FlagTerm, SentDateTerm, etc.) pose a problem, so we'll give them
+        // a free pass.
+        return false;
+    }
+
+
+    /**
+     * Run an array of search term items to check each one for ASCII
+     * encoding problems.
+     *
+     * @param terms  The array of terms to check.
+     *
+     * @return True if any of the search terms contains a 7-bit ASCII problem,
+     *         false otherwise.
+     */
+    static public boolean checkSearchEncoding(SearchTerm[] terms) {
+        for (int i = 0; i < terms.length; i++) {
+            if (checkSearchEncoding(terms[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Check a string to see if this can be processed using just
+     * 7-bit ASCII.
+     *
+     * @param s      The string to check
+     *
+     * @return true if the string contains characters outside the 7-bit ascii range,
+     *         false otherwise.
+     */
+    static public boolean checkStringEncoding(String s) {
+        for (int i = 0; i < s.length(); i++) {
+            // any value greater that 0x7f is a problem char.  We're not worried about
+            // lower ctl chars (chars < 32) since those are still expressible in 7-bit.
+            if (s.charAt(i) > 127) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+    
+    
+    /**
+     * Append a FetchProfile information to an IMAPCommand
+     * that's to be issued.
+     * 
+     * @param profile The fetch profile we're using.
+     * 
+     * @exception MessagingException
+     */
+    public void appendFetchProfile(FetchProfile profile) throws MessagingException {
+        // the fetch profile items are a parenthtical list passed on a 
+        // FETCH command. 
+        startList(); 
+        if (profile.contains(UIDFolder.FetchProfileItem.UID)) {
+            appendAtom("UID"); 
+        }
+        if (profile.contains(FetchProfile.Item.ENVELOPE)) {
+            // fetching the envelope involves several items 
+            appendAtom("ENVELOPE"); 
+            appendAtom("INTERNALDATE"); 
+            appendAtom("RFC822.SIZE"); 
+        }
+        if (profile.contains(FetchProfile.Item.FLAGS)) {
+            appendAtom("FLAGS"); 
+        }
+        if (profile.contains(FetchProfile.Item.CONTENT_INFO)) {
+            appendAtom("BODYSTRUCTURE"); 
+        }
+        if (profile.contains(IMAPFolder.FetchProfileItem.SIZE)) {
+            appendAtom("RFC822.SIZE"); 
+        }
+        // There are two choices here, that are sort of redundant.  
+        // if all headers have been requested, there's no point in 
+        // adding any specifically requested one. 
+        if (profile.contains(IMAPFolder.FetchProfileItem.HEADERS)) {
+            appendAtom("BODY.PEEK[HEADER]"); 
+        }
+        else {
+            String[] headers = profile.getHeaderNames(); 
+            // have an actual list to retrieve?  need to craft this as a sublist
+            // of identified fields. 
+            if (headers.length > 0) {
+                appendAtom("BODY.PEEK[HEADER.FIELDS]"); 
+                startList(); 
+                for (int i = 0; i < headers.length; i++) {
+                    appendAtom(headers[i]); 
+                }
+                endList(); 
+            }
+        }
+        // end the list.  
+        endList(); 
+    }
+    
+    
+    /**
+     * Append an ACL value to a command.  The ACL is the writes string name, 
+     * followed by the rights value.  This version uses no +/- modifier.
+     * 
+     * @param acl    The ACL to append.
+     */
+    public void appendACL(ACL acl) {
+        appendACL(acl, null); 
+    }
+    
+    /**
+     * Append an ACL value to a command.  The ACL is the writes string name,
+     * followed by the rights value.  A +/- modifier can be added to the 
+     * // result. 
+     * 
+     * @param acl      The ACL to append.
+     * @param modifier The modifer string (can be null).
+     */
+    public void appendACL(ACL acl, String modifier) {
+        appendString(acl.getName()); 
+        String rights = acl.getRights().toString(); 
+        
+        if (modifier != null) {
+            rights = modifier + rights; 
+        }
+        appendString(rights); 
+    }
+    
+    
+    /**
+     * Append a quota specification to an IMAP command. 
+     * 
+     * @param quota  The quota value to append.
+     */
+    public void appendQuota(Quota quota) {
+        appendString(quota.quotaRoot); 
+        startList(); 
+        for (int i = 0; i < quota.resources.length; i++) {
+            appendQuotaResource(quota.resources[i]); 
+        }
+        endList(); 
+    }
+    
+    /**
+     * Append a Quota.Resource element to an IMAP command.  This converts as 
+     * the resoure name, the usage value and limit value). 
+     * 
+     * @param resource The resource element we're appending.
+     */
+    public void appendQuotaResource(Quota.Resource resource) {
+        appendAtom(resource.name); 
+        // NB:  For command purposes, only the limit is used. 
+        appendLong(resource.limit);
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
new file mode 100644
index 0000000..5a0f179
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
@@ -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; 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java
new file mode 100644
index 0000000..05002ec
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java
@@ -0,0 +1,596 @@
+/**
+ * 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.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.mail.MessagingException; 
+import javax.mail.Session;
+import javax.mail.Store;
+
+import javax.mail.StoreClosedException;
+
+import org.apache.geronimo.javamail.store.imap.IMAPStore; 
+import org.apache.geronimo.javamail.util.ProtocolProperties; 
+
+public class IMAPConnectionPool {
+
+    protected static final String MAIL_PORT = "port";
+    protected static final String MAIL_POOL_SIZE = "connectionpoolsize";
+    protected static final String MAIL_POOL_TIMEOUT = "connectionpooltimeout";
+    protected static final String MAIL_SEPARATE_STORE_CONNECTION = "separatestoreconnection";
+    
+    protected static final String MAIL_SASL_REALM = "sasl.realm"; 
+    protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid"; 
+
+    // 45 seconds, by default.
+    protected static final int DEFAULT_POOL_TIMEOUT = 45000;
+    protected static final String DEFAULT_MAIL_HOST = "localhost";
+    
+    protected static final int MAX_CONNECTION_RETRIES = 3; 
+    protected static final int MAX_POOL_WAIT = 500; 
+
+
+    // Our hosting Store instance
+    protected IMAPStore store;
+    // our Protocol abstraction 
+    protected ProtocolProperties props; 
+    // our list of created connections
+    protected List poolConnections = new ArrayList();
+    // our list of available connections 
+    protected List availableConnections = new ArrayList();
+    
+    // the dedicated Store connection (if we're configured that way)
+    protected IMAPConnection storeConnection = null;
+    
+    // our dedicated Store connection attribute
+    protected boolean dedicatedStoreConnection;
+    // the size of our connection pool (by default, we only keep a single connection in the pool)
+    protected int poolSize = 1;
+    // the connection timeout property
+    protected long poolTimeout;
+    // our debug flag
+    protected boolean debug;
+
+    // the target host
+    protected String host;
+    // the target server port.
+    protected int port;
+    // the username we connect with
+    protected String username;
+    // the authentication password.
+    protected String password;
+    // the SASL realm name 
+    protected String realm; 
+    // the authorization id.  With IMAP, it's possible to 
+    // log on with another's authorization. 
+    protected String authid; 
+    // Turned on when the store is closed for business. 
+    protected boolean closed = false; 
+    // the connection capabilities map
+    protected Map capabilities; 
+
+    /**
+     * Create a connection pool associated with a give IMAPStore instance.  The 
+     * connection pool manages handing out connections for both the Store and 
+     * Folder and Message usage.  
+     * 
+     * Depending on the session properties, the Store may be given a dedicated 
+     * connection, or will share connections with the Folders.  Connections may 
+     * be requested from either the Store or Folders.  Messages must request 
+     * their connections from their hosting Folder, and only one connection is 
+     * allowed per folder. 
+     * 
+     * @param store   The Store we're creating the pool for.
+     * @param session The Session this Store is created under.  This contains the properties
+     *                we used to tailor behavior.
+     * @param sslConnection
+     *                Indicates whether we need to start connections using an SSL connection.
+     * @param defaultPort
+     *                The default port.  Used if we receive a -1 port value on the initial
+     *                connection request.
+     * @param debug   The debug flag.  Tells us whether to wrapper connections with debug
+     *                capture streams.
+     */
+    public IMAPConnectionPool(IMAPStore store, ProtocolProperties props) {
+        this.store = store;
+        this.props = props; 
+
+        // get the pool size.  By default, we just use a single connection that's 
+        // shared among Store and all of the Folders.  Since most apps that use 
+        // javamail tend to be single-threaded, this generally poses no great hardship. 
+        poolSize = props.getIntProperty(MAIL_POOL_SIZE, 1);
+        // get the timeout property.  Default is 45 seconds.
+        poolTimeout = props.getIntProperty(MAIL_POOL_TIMEOUT, DEFAULT_POOL_TIMEOUT);
+        // we can create a dedicated connection over and above the pool set that's 
+        // reserved for the Store instance to use. 
+        dedicatedStoreConnection = props.getBooleanProperty(MAIL_SEPARATE_STORE_CONNECTION, false);
+        // if we have a dedicated pool connection, we allocated that from the pool.  Add this to 
+        // the total pool size so we don't find ourselves stuck if the pool size is 1. 
+        if (dedicatedStoreConnection) {
+            poolSize++; 
+        }
+    }
+
+
+    /**
+     * Manage the initial connection to the IMAP server.  This is the first 
+     * point where we obtain the information needed to make an actual server 
+     * connection.  Like the Store protocolConnect method, we return false 
+     * if there's any sort of authentication difficulties. 
+     * 
+     * @param host     The host of the IMAP server.
+     * @param port     The IMAP server connection port.
+     * @param user     The connection user name.
+     * @param password The connection password.
+     * 
+     * @return True if we were able to connect and authenticate correctly. 
+     * @exception MessagingException
+     */
+    public synchronized boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
+        // NOTE:  We don't check for the username/password being null at this point.  It's possible that 
+        // the server will send back a PREAUTH response, which means we don't need to go through login 
+        // processing.  We'll need to check the capabilities response after we make the connection to decide 
+        // if logging in is necesssary. 
+        
+        // save this for subsequent connections.  All pool connections will use this info.
+        // if the port is defaulted, then see if we have something configured in the session.
+        // if not configured, we just use the default default.
+        if (port == -1) {
+            // check for a property and fall back on the default if it's not set.
+            port = props.getIntProperty(MAIL_PORT, props.getDefaultPort());
+            // it's possible that -1 might have been explicitly set, so one last check. 
+            if (port == -1) {
+                port = props.getDefaultPort(); 
+            }
+        }
+    	
+    	// Before we do anything, let's make sure that we succesfully received a host
+    	if ( host == null ) {
+    		host = DEFAULT_MAIL_HOST;
+    	}
+        
+        this.host = host;
+        this.port = port;
+        this.username = username;
+        this.password = password;
+        
+        // make sure we have the realm information 
+        realm = props.getProperty(MAIL_SASL_REALM); 
+        // get an authzid value, if we have one.  The default is to use the username.
+        authid = props.getProperty(MAIL_AUTHORIZATIONID, username);
+
+        // go create a connection and just add it to the pool.  If there is an authenticaton error, 
+        // return the connect failure, and we may end up trying again. 
+        IMAPConnection connection = createPoolConnection(); 
+        if (connection == null) {
+            return false; 
+        }
+        // save the capabilities map from the first connection. 
+        capabilities = connection.getCapabilities(); 
+        // if we're using a dedicated store connection, remove this from the pool and
+        // reserve it for the store.
+        if (dedicatedStoreConnection)  
+        {  
+            storeConnection = connection;
+            // make sure this is hooked up to the store. 
+            connection.addResponseHandler(store); 
+        }
+        else {
+            // just put this back in the pool.  It's ready for anybody to use now. 
+            synchronized(this) {
+                availableConnections.add(connection); 
+            }
+        }
+        // we're connection, authenticated, and ready to go. 
+        return true; 
+    }
+
+    /**
+     * Creates an authenticated pool connection and adds it to
+     * the connection pool.  If there is an existing connection
+     * already in the pool, this returns without creating a new
+     * connection.
+     *
+     * @exception MessagingException
+     */
+    protected IMAPConnection createPoolConnection() throws MessagingException {
+        IMAPConnection connection = new IMAPConnection(props, this);
+        if (!connection.protocolConnect(host, port, authid, realm, username, password)) {
+            // we only add live connections to the pool.  Sever the connections and 
+            // allow it to go free. 
+            connection.closeServerConnection(); 
+            return null; 
+        }
+        
+        // add this to the master list.  We do NOT add this to the 
+        // available queue because we're handing this out. 
+        synchronized(this) {
+            // uh oh, we closed up shop while we were doing this...clean it up a 
+            // get out of here 
+            if (closed) {
+                connection.close(); 
+                throw new StoreClosedException(store, "No Store connections available"); 
+            }
+            
+            poolConnections.add(connection);
+        }
+        // return that connection 
+        return connection; 
+    }
+
+
+    /**
+     * Get a connection from the pool.  We try to retrieve a live
+     * connection, but we test the connection's liveness before
+     * returning one.  If we don't have a viable connection in
+     * the pool, we'll create a new one.  The returned connection
+     * will be in the authenticated state already.
+     *
+     * @return An IMAPConnection object that is connected to the server.
+     */
+    protected IMAPConnection getConnection() throws MessagingException {
+        int retryCount = 0; 
+        
+        // To keep us from falling into a futile failure loop, we'll only allow 
+        // a set number of connection failures. 
+        while (retryCount < MAX_CONNECTION_RETRIES) {
+            // first try for an already created one.  If this returns 
+            // null, then we'll probably have to make a new one. 
+            IMAPConnection connection = getPoolConnection(); 
+            // cool, we got one, the hard part is done.  
+            if (connection != null) {
+                return connection; 
+            }
+            // ok, create a new one.  This *should* work, but the server might 
+            // have gone down, or other problem may occur. If we have a problem, 
+            // retry the entire process...but only for a bit.  No sense 
+            // being stubborn about it. 
+            connection = createPoolConnection(); 
+            if (connection != null) {
+                return connection; 
+            }
+            // step the retry count 
+            retryCount++; 
+        }
+        
+        throw new MessagingException("Unable to get connection to IMAP server"); 
+    }
+    
+    /**
+     * Obtain a connection from the existing connection pool.  If none are 
+     * available, and we've reached the connection pool limit, we'll wait for 
+     * some other thread to return one.  It generally doesn't take too long, as 
+     * they're usually only held for the time required to execute a single 
+     * command.   If we're not at the pool limit, return null, which will signal 
+     * the caller to go ahead and create a new connection outside of the 
+     * lock. 
+     * 
+     * @return Either an active connection instance, or null if the caller should go 
+     *         ahead and try to create a new connection.
+     * @exception MessagingException
+     */
+    protected synchronized IMAPConnection getPoolConnection() throws MessagingException {
+        // if the pool is closed, we can't process this 
+        if (closed) {
+            throw new StoreClosedException(store, "No Store connections available"); 
+        }
+        
+        // we'll retry this a few times if the connection pool is full, but 
+        // after that, we'll just create a new connection. 
+        for (int i = 0; i < MAX_CONNECTION_RETRIES; i++) {
+            Iterator it = availableConnections.iterator(); 
+            while (it.hasNext()) {
+                IMAPConnection connection = (IMAPConnection)it.next(); 
+                // live or dead, we're going to remove this from the 
+                // available list. 
+                it.remove(); 
+                if (connection.isAlive(poolTimeout)) {
+                    // return the connection to the requestor 
+                    return connection; 
+                }
+                else {
+                    // remove this from the pool...it's toast. 
+                    poolConnections.remove(connection); 
+                    // make sure this cleans up after itself. 
+                    connection.closeServerConnection(); 
+                }
+            }
+
+            // we've not found something usable in the pool.  Now see if 
+            // we're allowed to add another connection, or must just wait for 
+            // someone else to return one. 
+
+            if (poolConnections.size() >= poolSize) {
+                // check to see if we've been told to shutdown before waiting
+                if (closed) {
+                    throw new StoreClosedException(store, "No Store connections available"); 
+                }
+                // we need to wait for somebody to return a connection 
+                // once woken up, we'll spin around and try to snag one from 
+                // the pool again.
+                try {
+                    wait(MAX_POOL_WAIT);
+                } catch (InterruptedException e) {
+                }
+                
+                // check to see if we've been told to shutdown while we waited
+                if (closed) {
+                    throw new StoreClosedException(store, "No Store connections available"); 
+                }
+            }
+            else {
+                // exit out and create a new connection.  Since 
+                // we're going to be outside the synchronized block, it's possible 
+                // we'll go over our pool limit.  We'll take care of that when connections start 
+                // getting returned. 
+                return null; 
+            }
+        }
+        // we've hit the maximum number of retries...just create a new connection. 
+        return null; 
+    }
+    
+    /**
+     * Return a connection to the connection pool.
+     * 
+     * @param connection The connection getting returned.
+     * 
+     * @exception MessagingException
+     */
+    protected void returnPoolConnection(IMAPConnection connection) throws MessagingException
+    {
+        synchronized(this) {
+            // If we're still within the bounds of our connection pool, 
+            // just add this to the active list and send out a notification 
+            // in case somebody else is waiting for the connection. 
+            if (availableConnections.size() < poolSize) {
+                availableConnections.add(connection); 
+                notify(); 
+                return; 
+            }
+            // remove this from the connection pool...we have too many. 
+            poolConnections.remove(connection); 
+        }
+        // the additional cleanup occurs outside the synchronized block 
+        connection.close(); 
+    }
+    
+    /**
+     * Release a closed connection.
+     * 
+     * @param connection The connection getting released.
+     * 
+     * @exception MessagingException
+     */
+    protected void releasePoolConnection(IMAPConnection connection) throws MessagingException
+    {
+        synchronized(this) {
+            // remove this from the connection pool...it's no longer usable. 
+            poolConnections.remove(connection); 
+        }
+        // the additional cleanup occurs outside the synchronized block 
+        connection.close(); 
+    }
+
+
+    /**
+     * Get a connection for the Store.  This will be either a
+     * dedicated connection object, or one from the pool, depending
+     * on the mail.imap.separatestoreconnection property.
+     *
+     * @return An authenticated connection object.
+     */
+    public synchronized IMAPConnection getStoreConnection() throws MessagingException {  
+        if (closed) {
+            throw new StoreClosedException(store, "No Store connections available"); 
+        }
+        // if we have a dedicated connection created, return it.
+        if (storeConnection != null) {
+            return storeConnection;
+        }
+        else {
+            IMAPConnection connection = getConnection();
+            // add the store as a response handler while it has it. 
+            connection.addResponseHandler(store); 
+            return connection; 
+        }
+    }
+
+
+    /**
+     * Return the Store connection to the connection pool.  If we have a dedicated
+     * store connection, this is simple.  Otherwise, the connection goes back 
+     * into the general connection pool.
+     * 
+     * @param connection The connection getting returned.
+     */
+    public synchronized void releaseStoreConnection(IMAPConnection connection) throws MessagingException {
+        // have a server disconnect situation?
+        if (connection.isClosed()) {
+            // we no longer have a dedicated store connection.  
+            // we need to return to the pool from now on. 
+            storeConnection = null; 
+            // throw this away. 
+            releasePoolConnection(connection); 
+        }
+        else {
+            // if we have a dedicated connection, nothing to do really.  Otherwise, 
+            // return this connection to the pool. 
+            if (storeConnection == null) {
+                // unhook the store from the connection. 
+                connection.removeResponseHandler(store); 
+                returnPoolConnection(connection); 
+            }
+        }
+    }
+
+
+    /**
+     * Get a connection for Folder.  
+     *
+     * @return An authenticated connection object.
+     */
+    public IMAPConnection getFolderConnection() throws MessagingException {  
+        // just get a connection from the pool 
+        return getConnection(); 
+    }
+
+
+    /**
+     * Return a Folder connection to the connection pool.  
+     * 
+     * @param connection The connection getting returned.
+     */
+    public void releaseFolderConnection(IMAPConnection connection) throws MessagingException {
+        // potentially, the server may have decided to shut us down.  
+        // In that case, the connection is no longer usable, so we need 
+        // to remove it from the list of available ones. 
+        if (!connection.isClosed()) {
+            // back into the pool with yee, matey....arrggghhh
+            returnPoolConnection(connection); 
+        }
+        else {
+            // can't return this one to the pool.  It's been stomped on 
+            releasePoolConnection(connection); 
+        }
+    }
+    
+    
+    /**
+     * Close the entire connection pool. 
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void close() throws MessagingException {
+        // first close each of the connections.  This also closes the 
+        // store connection. 
+        for (int i = 0; i < poolConnections.size(); i++) {
+            IMAPConnection connection = (IMAPConnection)poolConnections.get(i);
+            connection.close(); 
+        }
+        // clear the pool 
+        poolConnections.clear(); 
+        availableConnections.clear(); 
+        storeConnection = null; 
+        // turn out the lights, hang the closed sign on the wall. 
+        closed = true; 
+    }
+
+
+    /**
+     * Flush any connections from the pool that have not been used
+     * for at least the connection pool timeout interval.
+     */
+    protected synchronized void closeStaleConnections() {
+        Iterator i = poolConnections.iterator();
+
+        while (i.hasNext()) {
+            IMAPConnection connection = (IMAPConnection)i.next();
+            // if this connection is a stale one, remove it from the pool
+            // and close it out.
+            if (connection.isStale(poolTimeout)) {
+                i.remove();
+                try {
+                    connection.close();
+                } catch (MessagingException e) {
+                    // ignored.  we're just closing connections that are probably timed out anyway, so errors
+                    // on those shouldn't have an effect on the real operation we're dealing with.
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Return a connection back to the connection pool.  If we're not
+     * over our limit, the connection is kept around.  Otherwise, it's
+     * given a nice burial.
+     *
+     * @param connection The returned connection.
+     */
+    protected synchronized void releaseConnection(IMAPConnection connection) {
+        // before adding this to the pool, close any stale connections we may
+        // have.  The connection we're adding is quite likely to be a fresh one,
+        // so we should cache that one if we can.
+        closeStaleConnections();
+        // still over the limit?
+        if (poolConnections.size() + 1 > poolSize) {
+            try {
+                // close this out and forget we ever saw it.
+                connection.close();
+            } catch (MessagingException e) {
+                // ignore....this is a non-critical problem if this fails now.
+            }
+        }
+        else {
+            // listen to alerts on this connection, and put it back in the pool.
+            poolConnections.add(connection);
+        }
+    }
+
+    /**
+     * Cleanup time.  Sever and cleanup all of the pool connection
+     * objects, including the special Store connection, if we have one.
+     */
+    protected synchronized void freeAllConnections() {
+        for (int i = 0; i < poolConnections.size(); i++) {
+            IMAPConnection connection = (IMAPConnection)poolConnections.get(i);
+            try {
+                // close this out and forget we ever saw it.
+                connection.close();
+            } catch (MessagingException e) {
+                // ignore....this is a non-critical problem if this fails now.
+            }
+        }
+        // everybody, out of the pool!
+        poolConnections.clear();
+
+        // don't forget the special store connection, if we have one.
+        if (storeConnection != null) {
+            try {
+                // close this out and forget we ever saw it.
+                storeConnection.close();
+            } catch (MessagingException e) {
+                // ignore....this is a non-critical problem if this fails now.
+            }
+            storeConnection = null;
+        }
+    }
+    
+    
+    /**
+     * 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); 
+    }
+}
+
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPContinuationResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPContinuationResponse.java
new file mode 100644
index 0000000..96e3827
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPContinuationResponse.java
@@ -0,0 +1,42 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+/**
+ * Util class to represent a continuation response from an IMAP server. 
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPContinuationResponse extends IMAPTaggedResponse {
+    /**
+     * Create a continuation object from a server response line (normally, untagged).  This includes
+     * doing the parsing of the response line.
+     *
+     * @param response The response line used to create the reply object.
+     */
+    protected IMAPContinuationResponse(byte [] response) {
+        super(response); 
+    }
+}
+
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPDateFormat.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPDateFormat.java
new file mode 100644
index 0000000..884b9a4
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPDateFormat.java
@@ -0,0 +1,72 @@
+/**
+ * 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.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Formats ths date as specified by
+ * draft-ietf-drums-msg-fmt-08 dated January 26, 2000
+ * which supercedes RFC822.
+ * <p/>
+ * <p/>
+ * The format used is <code>EEE, d MMM yyyy HH:mm:ss Z</code> and
+ * locale is always US-ASCII.
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPDateFormat extends SimpleDateFormat {
+    public IMAPDateFormat() {
+        super("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
+    }
+    public StringBuffer format(Date date, StringBuffer buffer, FieldPosition position) {
+        StringBuffer result = super.format(date, buffer, position);
+        // The RFC 2060 requires that the day in the date be formatted with either 2 digits
+        // or one digit.  Our format specifies 2 digits, which pads with leading
+        // zeros.  We need to check for this and whack it if it's there
+        if (result.charAt(0) == '0') {
+            result.deleteCharAt(0); 
+        }
+        return result;
+    }
+
+    /**
+     * The calendar cannot be set
+     * @param calendar
+     * @throws UnsupportedOperationException
+     */
+    public void setCalendar(Calendar calendar) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * The format cannot be set
+     * @param format
+     * @throws UnsupportedOperationException
+     */
+    public void setNumberFormat(NumberFormat format) {
+        throw new UnsupportedOperationException();
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPEnvelope.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPEnvelope.java
new file mode 100644
index 0000000..37b8a3f
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPEnvelope.java
@@ -0,0 +1,70 @@
+/**
+ * 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.util.Date;
+
+import javax.mail.MessagingException;
+
+import javax.mail.internet.InternetAddress;
+
+
+public class IMAPEnvelope extends IMAPFetchDataItem {
+    // the following are various fields from the FETCH ENVELOPE structure.  These
+    // should be self-explanitory.
+    public Date date;
+    public String subject;
+    public InternetAddress[] from;
+    public InternetAddress[] sender;
+    public InternetAddress[] replyTo;
+    public InternetAddress[] to;
+    public InternetAddress[] cc;
+    public InternetAddress[] bcc;
+
+    public String inReplyTo;
+    public String messageID;
+
+
+    /**
+     * Parse an IMAP FETCH ENVELOPE response into the component pieces.
+     * 
+     * @param source The tokenizer for the response we're processing.
+     */
+    public IMAPEnvelope(IMAPResponseTokenizer source) throws MessagingException {
+        super(ENVELOPE);
+
+        // these should all be a parenthetical list 
+        source.checkLeftParen(); 
+        // the following fields are all positional
+        // The envelope date is defined in the spec as being an "nstring" value, which 
+        // means it is either a string value or NIL.  
+        date = source.readDateOrNil(); 
+        subject = source.readStringOrNil();
+        from = source.readAddressList();
+        sender = source.readAddressList();
+        replyTo = source.readAddressList();
+        to = source.readAddressList();
+        cc = source.readAddressList();
+        bcc = source.readAddressList();
+        inReplyTo = source.readStringOrNil();
+        messageID = source.readStringOrNil();
+
+        // make sure we have a correct close on the field.
+        source.checkRightParen();
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchBodyPart.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchBodyPart.java
new file mode 100644
index 0000000..ec17870
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchBodyPart.java
@@ -0,0 +1,76 @@
+/**
+ * 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;
+
+public class IMAPFetchBodyPart extends IMAPFetchDataItem {
+    // the parse body section information. 
+    protected IMAPBodySection section; 
+
+    /**
+     * Construct a base BODY section subpiece.
+     * 
+     * @param type    The fundamental type of the body section.  This will be either BODY, TEXT,
+     *                or HEADER, depending on the subclass.
+     * @param section The section information.  This will contain the section numbering information,
+     *                the section name, and and substring information if this was a partial fetch
+     *                request.
+     */
+    public IMAPFetchBodyPart(int type, IMAPBodySection section) {
+        super(type); 
+        this.section = section; 
+    }
+    
+    /**
+     * Get the part number information associated with this request.
+     * 
+     * @return The string form of the part number. 
+     */
+    public String getPartNumber() {
+        return section.partNumber;
+    }
+    
+    /**
+     * Get the section type information.  This is the qualifier that appears
+     * within the "[]" of the body sections.
+     * 
+     * @return The numeric identifier for the type from the IMAPBodySection.
+     */
+    public int getSectionType() {
+        return section.section; 
+    }
+    
+    /**
+     * Get the substring start location.  
+     * 
+     * @return The start location for the substring.  Returns -1 if this is not a partial 
+     *         fetch.
+     */
+    public int getSubstringStart() {
+        return section.start; 
+    }
+    
+    /**
+     * Returns the length of the substring section.  
+     * 
+     * @return The length of the substring section.  Returns -1 if this was not a partial 
+     *         fetch.
+     */
+    public int getSubstringLength() {
+        return section.length; 
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchDataItem.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchDataItem.java
new file mode 100644
index 0000000..6d15af3
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchDataItem.java
@@ -0,0 +1,61 @@
+/**
+ * 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 javax.mail.internet.MailDateFormat;
+
+public class IMAPFetchDataItem {
+    public static final int FETCH = 0;
+    public static final int ENVELOPE = 1;
+    public static final int BODY = 2;
+    public static final int BODYSTRUCTURE = 3;
+    public static final int INTERNALDATE = 4;
+    public static final int SIZE = 5;
+    public static final int UID = 6;
+    public static final int TEXT = 7;
+    public static final int HEADER = 8;
+    public static final int FLAGS = 9;
+
+    // the type of the FETCH response item.
+    protected int type;
+
+    public IMAPFetchDataItem(int type) {
+        this.type = type;
+    }
+
+    /**
+     * Get the type of the FetchResponse.
+     *
+     * @return The type indicator.
+     */
+    public int getType() {
+        return type;
+    }
+
+    /**
+     * Test if this fetch response is of the correct type.
+     *
+     * @param t      The type to test against.
+     *
+     * @return True if the Fetch response contains the requested type information.
+     */
+    public boolean isType(int t) {
+        return type == t;
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchResponse.java
new file mode 100644
index 0000000..f29af6a
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchResponse.java
@@ -0,0 +1,161 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+/**
+ * Util class to represent a composite FETCH response from an IMAP server.  The
+ * response may have information about multiple message dataItems.
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPFetchResponse extends IMAPUntaggedResponse {
+    // parsed sections within the FETCH response structure 
+    protected List dataItems = new ArrayList();
+    // the message number to which this applies 
+    public int sequenceNumber; 
+
+    public IMAPFetchResponse(int sequenceNumber, byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super("FETCH", data);
+        
+        this.sequenceNumber = sequenceNumber; 
+
+        // fetch responses are a list, even if there is just a single member.
+        source.checkLeftParen();
+
+        // loop until we find the list end.
+        while (source.notListEnd()) {
+            // the response names are coded as ATOMS.  The BODY one's use a special 
+            // syntax, so we need to use the expanded delimiter set to pull this out. 
+            String itemName = source.readAtom(true).toUpperCase();
+
+            if (itemName.equals("ENVELOPE")) {
+                dataItems.add(new IMAPEnvelope(source));
+            }
+            else if (itemName.equals("BODYSTRUCTURE")) {
+                dataItems.add(new IMAPBodyStructure(source));
+            }
+            else if (itemName.equals("FLAGS")) {
+                dataItems.add(new IMAPFlags(source));
+            }
+            else if (itemName.equals("INTERNALDATE")) {
+                dataItems.add(new IMAPInternalDate(source));
+            }
+            else if (itemName.equals("UID")) {
+                dataItems.add(new IMAPUid(sequenceNumber, source));
+            }
+            else if (itemName.equals("RFC822")) {
+                // all of the RFC822 items are of form 
+                // "RFC822.name".  We used the expanded parse above because 
+                // the BODY names include some complicated bits.  If we got one 
+                // of the RFC822 sections, then parse the rest of the name using 
+                // the old rules, which will pull in the rest of the name from the period. 
+                itemName = source.readAtom(false).toUpperCase();
+                if (itemName.equals(".SIZE")) {
+                    dataItems.add(new IMAPMessageSize(source));
+                }
+                else if (itemName.equals(".HEADER")) {
+                    dataItems.add(new IMAPInternetHeader(source.readByteArray()));
+                }
+                else if (itemName.equals(".TEXT")) {
+                    dataItems.add(new IMAPMessageText(source.readByteArray()));
+                }
+            }
+            // this is just the body alone. Specific body segments 
+            // have a more complex naming structure.  Believe it or  
+            // not, 
+            else if (itemName.equals("BODY")) {
+                // time to go parse out the section information from the 
+                // name.  
+                IMAPBodySection section = new IMAPBodySection(source); 
+                
+                switch (section.section) {
+                    case IMAPBodySection.BODY:
+                        // a "full body cast".  Just grab the binary data 
+                        dataItems.add(new IMAPBody(section, source.readByteArray())); 
+                        break; 
+                        
+                    case IMAPBodySection.HEADERS:
+                    case IMAPBodySection.HEADERSUBSET:
+                    case IMAPBodySection.MIME:
+                        // these 3 are all variations of a header request
+                        dataItems.add(new IMAPInternetHeader(section, source.readByteArray())); 
+                        break; 
+                        
+                    case IMAPBodySection.TEXT:
+                        // just the text portion of the body 
+                        // a "full body cast".  Just grab the binary data 
+                        dataItems.add(new IMAPMessageText(section, source.readByteArray())); 
+                        break; 
+                }
+            }
+        }
+        // swallow the terminating right paren
+        source.checkRightParen(); 
+    }
+    
+    /**
+     * Retrieve the sequence number for the FETCH item. 
+     * 
+     * @return The message sequence number this FETCH applies to. 
+     */
+    public int getSequenceNumber() {
+        return sequenceNumber; 
+    }
+
+    /**
+     * Get the section count.
+     *
+     * @return The number of sections in the response.
+     */
+    public int getCount() {
+        return dataItems.size();
+    }
+
+    /**
+     * Get the complete set of response dataItems.
+     *
+     * @return The List of IMAPFetchResponse values.
+     */
+    public List getDataItems() {
+        return dataItems;
+    }
+
+
+    /**
+     * Fetch a particular response type from the response dataItems.
+     *
+     * @param type   The target FETCH type.
+     *
+     * @return The first IMAPFetchDataItem item that matches the response type.
+     */
+    public IMAPFetchDataItem getDataItem(int type) {
+        for (int i = 0; i < dataItems.size(); i ++) {
+            IMAPFetchDataItem item = (IMAPFetchDataItem)dataItems.get(i);
+            if (item.isType(type)) {
+                return item;
+            }
+        }
+        return null;
+    }
+    
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlags.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlags.java
new file mode 100644
index 0000000..21f025a
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlags.java
@@ -0,0 +1,40 @@
+/**
+ * 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.util.List;
+
+import javax.mail.MessagingException;
+import javax.mail.Flags;
+
+
+/**
+ * A fetched FLAGS value returned on a FETCH response.
+ */
+public class IMAPFlags extends IMAPFetchDataItem {
+
+    public Flags flags;
+
+    public IMAPFlags(IMAPResponseTokenizer source) throws MessagingException {
+        super(FLAGS);
+
+        // parse the list of flag values and merge each one into the flag set
+        flags = source.readFlagList(); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlagsResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlagsResponse.java
new file mode 100644
index 0000000..ce55057
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlagsResponse.java
@@ -0,0 +1,48 @@
+/**
+ * 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 javax.mail.MessagingException;
+import javax.mail.Flags;
+
+import java.util.List;
+
+/**
+ * A parsed FLAGS untagged response.
+ */
+public class IMAPFlagsResponse extends IMAPUntaggedResponse {
+
+    protected Flags flags = new Flags();
+
+    public IMAPFlagsResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super("FLAGS", data);
+        
+        // read the flags from the response tokenizer. 
+        flags = source.readFlagList(); 
+    }
+
+    /**
+     * Get the parsed flags value.
+     *
+     * @return The accumulated flags setting.
+     */
+    public Flags getFlags() {
+        return flags;
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternalDate.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternalDate.java
new file mode 100644
index 0000000..ce0e088
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternalDate.java
@@ -0,0 +1,45 @@
+/**
+ * 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.util.Date;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.util.ResponseFormatException; 
+
+public class IMAPInternalDate extends IMAPFetchDataItem {
+    // the parsed date.
+    protected Date date;
+
+    public IMAPInternalDate(IMAPResponseTokenizer source) throws MessagingException {
+        super(INTERNALDATE);
+        // read the date from the stream 
+        date = source.readDate(); 
+    }
+
+    /**
+     * Retrieved the parsed internal date object.
+     *
+     * @return The parsed Date object.
+     */
+    public Date getDate() {
+        return date;
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternetHeader.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternetHeader.java
new file mode 100644
index 0000000..09fde92
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternetHeader.java
@@ -0,0 +1,66 @@
+/**
+ * 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.ByteArrayInputStream;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetHeaders; 
+
+public class IMAPInternetHeader extends IMAPFetchBodyPart {
+    // the parsed headers
+    public InternetHeaders headers; 
+    
+    /**
+     * Construct a top-level HEADER data item. 
+     * 
+     * @param data   The data for the InternetHeaders.
+     * 
+     * @exception MessagingException
+     */
+    public IMAPInternetHeader(byte[] data) throws MessagingException {
+        this(new IMAPBodySection(IMAPBodySection.HEADERS), data);
+    }
+    
+
+    /**
+     * Construct a HEADER request data item.
+     * 
+     * @param section  The Section identifier information.
+     * @param data     The raw data for the internet headers.
+     * 
+     * @exception MessagingException
+     */
+    public IMAPInternetHeader(IMAPBodySection section, byte[] data) throws MessagingException {
+        super(HEADER, section);
+        
+        // and convert these into real headers 
+        ByteArrayInputStream in = new ByteArrayInputStream(data); 
+        headers = new InternetHeaders(in); 
+    }
+    
+    /**
+     * Test if this is a complete header fetch, or just a partial list fetch.
+     * 
+     * @return 
+     */
+    public boolean isComplete() {
+        return section.section == IMAPBodySection.HEADERS;
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListResponse.java
new file mode 100644
index 0000000..57678b3
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListResponse.java
@@ -0,0 +1,92 @@
+/**
+ * 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.util.List;
+
+import javax.mail.MessagingException;
+
+/**
+ * Util class to represent a list response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+
+public class IMAPListResponse extends IMAPUntaggedResponse {
+    // parsed flag responses
+    public boolean noinferiors = false;
+    public boolean noselect = false;
+    public boolean marked = false;
+    public boolean unmarked = false;
+
+    // the name separator character
+    public char separator;
+    // the mail box name
+    public String mailboxName;
+    // this is for support of the get attributes command
+    public String[] attributes;
+
+    /**
+     * Construct a LIST response item.  This can be either 
+     * a response from a LIST command or an LSUB command, 
+     * and will be tagged accordingly.
+     * 
+     * @param type   The type of resonse (LIST or LSUB).
+     * @param data   The raw response data.
+     * @param source The tokenizer source.
+     * 
+     * @exception MessagingException
+     */
+    public IMAPListResponse(String type, byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super(type, data); 
+
+        // parse the list of flag values
+        List flags = source.readSystemNameList(); 
+        
+        // copy this into the attributes array. 
+        attributes = new String[flags.size()]; 
+        attributes = (String[])flags.toArray(attributes); 
+
+        for (int i = 0; i < flags.size(); i++) {
+            String flag = ((String)flags.get(i));
+
+            if (flag.equalsIgnoreCase("\\Marked")) {
+                marked = true;
+            }
+            else if (flag.equalsIgnoreCase("\\Unmarked")) {
+                unmarked = true;
+            }
+            else if (flag.equalsIgnoreCase("\\Noselect")) {
+                noselect = true;
+            }
+            else if (flag.equalsIgnoreCase("\\Noinferiors")) {
+                noinferiors = true;
+            }
+        }
+
+        // set a default sep value 
+        separator = '\0';    
+        // get the separator and name tokens
+        String separatorString = source.readQuotedStringOrNil();
+        if (separatorString != null && separatorString.length() == 1) {
+            separator = separatorString.charAt(0); 
+        }
+        mailboxName = source.readEncodedString();
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListRightsResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListRightsResponse.java
new file mode 100644
index 0000000..27fb4d7
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListRightsResponse.java
@@ -0,0 +1,51 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException; 
+
+import org.apache.geronimo.javamail.store.imap.Rights; 
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token; 
+
+/**
+ * Utility class to aggregate status responses for a mailbox.
+ */
+public class IMAPListRightsResponse extends IMAPUntaggedResponse {
+    public String mailbox; 
+    public String name; 
+    public Rights[] rights; 
+    
+    public IMAPListRightsResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super("LISTRIGHTS",  data); 
+        
+        mailbox = source.readEncodedString();
+        name = source.readString(); 
+        List acls = new ArrayList(); 
+        
+        while (source.hasMore()) {
+            acls.add(new Rights(source.readString())); 
+        }
+        
+        rights = new Rights[acls.size()]; 
+        acls.toArray(rights); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxResponse.java
new file mode 100644
index 0000000..106b1fb
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxResponse.java
@@ -0,0 +1,37 @@
+/**
+ * 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;
+
+/**
+ * Util class to represent a status response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+
+public class IMAPMailboxResponse {
+    // count/message number parameter from the response.
+    public int count;
+    // the name of the status code
+    public String name;
+
+    public IMAPMailboxResponse(int count, String name) {
+        this.count = count;
+        this.name = name;
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxStatus.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxStatus.java
new file mode 100644
index 0000000..bf1b712
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxStatus.java
@@ -0,0 +1,188 @@
+/**
+ * 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.util.List;
+
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
+
+
+/**
+ * Utility class to aggregate status responses for a mailbox.
+ */
+public class IMAPMailboxStatus {
+    // the set of available flag values for this mailbox
+    public Flags availableFlags = null;
+    // the permanent flags for this mailbox.
+    public Flags permanentFlags = null;
+    // the open mode flags
+    public int mode = Folder.READ_WRITE;
+
+    // number of messages in the box
+    public int messages = -1;
+    // the number of newly added messages
+    public int recentMessages = -1;
+    // the number of unseen messages
+    public int unseenMessages = -1;
+
+    // the next UID for this mailbox
+    public long uidNext = -1L;
+    // the UID validity item
+    public long uidValidity = -1L;
+
+    public IMAPMailboxStatus() {
+    }
+
+
+    /**
+     * Merge information from a server status message.  These
+     * messages are in the form "* NAME args".  We only handle
+     * STATUS and FLAGS messages here.
+     *
+     * @param source The parsed status message.
+     *
+     * @exception MessagingException
+     */
+    public void mergeStatus(IMAPStatusResponse source) throws MessagingException {
+        // update any of the values that have changed since the last. 
+        if (source.messages != -1) {
+            messages = source.messages; 
+        }
+        if (source.uidNext != -1L) {
+            uidNext = source.uidNext; 
+        }
+        if (source.uidValidity != -1L) {
+            uidValidity = source.uidValidity; 
+        }
+        if (source.recentMessages != -1) {
+            recentMessages = source.recentMessages; 
+        }
+        if (source.unseenMessages != -1) {
+            unseenMessages = source.unseenMessages; 
+        }
+    }
+    
+    /**
+     * Merge in the FLAGS response from an EXAMINE or 
+     * SELECT mailbox command.
+     * 
+     * @param response The returned FLAGS item.
+     * 
+     * @exception MessagingException
+     */
+    public void mergeFlags(IMAPFlagsResponse response) throws MessagingException {
+        if (response != null) {
+            availableFlags = response.getFlags(); 
+        }
+    }
+    
+    
+    public void mergeSizeResponses(List responses) throws MessagingException  
+      {  
+        for (int i = 0; i < responses.size(); i++) {
+            mergeStatus((IMAPSizeResponse)responses.get(i)); 
+        }
+    }
+    
+    
+    public void mergeOkResponses(List responses) throws MessagingException {
+        for (int i = 0; i < responses.size(); i++) {
+            mergeStatus((IMAPOkResponse)responses.get(i)); 
+        }
+    }
+
+    
+    /**
+     * Gather mailbox status information from mailbox status
+     * messages.  These messages come in as untagged messages in the
+     * form "* nnn NAME".
+     *
+     * @param source The parse message information.
+     *
+     * @exception MessagingException
+     */
+    public void mergeStatus(IMAPSizeResponse source) throws MessagingException {
+        if (source != null) {
+            String name = source.getKeyword(); 
+
+            // untagged exists response
+            if (source.isKeyword("EXISTS")) {
+                messages = source.getSize();
+            }
+            // untagged resent response
+            else if (source.isKeyword("RECENT")) {
+                recentMessages = source.getSize();
+            }
+        }
+    }
+
+    
+
+    
+    /**
+     * Gather mailbox status information from mailbox status
+     * messages.  These messages come in as untagged messages in the
+     * form "* OK [NAME args]".
+     *
+     * @param source The parse message information.
+     *
+     * @exception MessagingException
+     */
+    public void mergeStatus(IMAPOkResponse source) throws MessagingException {
+        if (source != null) {
+            String name = source.getKeyword(); 
+
+            // untagged UIDVALIDITY response 
+            if (source.isKeyword("UIDVALIDITY")) {
+                List arguments = source.getStatus(); 
+                uidValidity = ((Token)arguments.get(0)).getLong(); 
+            }
+            // untagged UIDNEXT response 
+            if (source.isKeyword("UIDNEXT")) {
+                List arguments = source.getStatus(); 
+                uidNext = ((Token)arguments.get(0)).getLong(); 
+            }
+            // untagged unseen response
+            else if (source.isKeyword("UNSEEN")) {
+                List arguments = source.getStatus(); 
+                uidValidity = ((Token)arguments.get(0)).getInteger(); 
+            }
+        }
+    }
+
+    
+    /**
+     * Gather mailbox status information from mailbox status
+     * messages.  These messages come in as untagged messages in the
+     * form "* OK [NAME args]".
+     *
+     * @param source The parse message information.
+     *
+     * @exception MessagingException
+     */
+    public void mergeStatus(IMAPPermanentFlagsResponse source) throws MessagingException {
+        if (source != null) {
+            // this is already parsed.          
+            permanentFlags = source.flags; 
+        }
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageSize.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageSize.java
new file mode 100644
index 0000000..5d3cf81
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageSize.java
@@ -0,0 +1,33 @@
+/**
+ * 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 javax.mail.MessagingException;
+
+public class IMAPMessageSize extends IMAPFetchDataItem {
+    // the size information 
+    public int size;         
+
+    public IMAPMessageSize(IMAPResponseTokenizer source) throws MessagingException {
+        super(SIZE);
+
+        // the size is just a single integer 
+        size = source.readInteger();  
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageText.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageText.java
new file mode 100644
index 0000000..f72bdb0
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageText.java
@@ -0,0 +1,52 @@
+/**
+ * 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 javax.mail.MessagingException;
+
+public class IMAPMessageText extends IMAPFetchBodyPart {
+    // the header data
+    protected byte[] data;
+
+    /**
+     * Construct a top-level TEXT data item. 
+     * 
+     * @param data   The data for the message text.
+     * 
+     * @exception MessagingException
+     */
+    public IMAPMessageText(byte[] data) throws MessagingException {
+        this(new IMAPBodySection(IMAPBodySection.TEXT), data);
+    }
+    
+    
+    public IMAPMessageText(IMAPBodySection section, byte[] data) throws MessagingException {
+        super(TEXT, section);
+        this.data = data; 
+    }
+    
+    /**
+     * Retrieved the header data.
+     *
+     * @return The header data.
+     */
+    public byte[] getContent() {
+        return data;
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMyRightsResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMyRightsResponse.java
new file mode 100644
index 0000000..fdb3209
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMyRightsResponse.java
@@ -0,0 +1,38 @@
+/**
+ * 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 javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.imap.Rights;
+
+/**
+ * Utility class to aggregate status responses for a mailbox.
+ */
+public class IMAPMyRightsResponse extends IMAPUntaggedResponse {
+    public String mailbox; 
+    public Rights rights; 
+    
+    public IMAPMyRightsResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super("MYRIGHTS", data); 
+        
+        mailbox = source.readEncodedString();
+        rights = new Rights(source.readString());
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespace.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespace.java
new file mode 100644
index 0000000..c5e0f15
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespace.java
@@ -0,0 +1,49 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+/**
+ * Util class to represent a NAMESPACE response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+
+public class IMAPNamespace {
+    // the namespace prefix 
+    public String prefix; 
+    // the namespace hierarchy delimiter
+    public char separator = '\0'; 
+    
+    public IMAPNamespace(IMAPResponseTokenizer source) throws MessagingException {
+        source.checkLeftParen(); 
+        // read the two that make up the response and ...
+        prefix = source.readString(); 
+        String delim = source.readString(); 
+        // if the delimiter is not a null string, grab the first character. 
+        if (delim.length() != 0) {
+            separator = delim.charAt(0); 
+        }
+        source.checkRightParen(); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespaceResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespaceResponse.java
new file mode 100644
index 0000000..a760157
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespaceResponse.java
@@ -0,0 +1,98 @@
+/**
+ * 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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token; 
+import org.apache.geronimo.javamail.util.ResponseFormatException; 
+
+/**
+ * Util class to represent a NAMESPACE response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+
+public class IMAPNamespaceResponse extends IMAPUntaggedResponse {
+    // the personal namespaces defined 
+    public List personalNamespaces; 
+    // the other use name spaces this user has access to. 
+    public List otherUserNamespaces; 
+    // the list of shared namespaces 
+    public List sharedNamespaces; 
+    
+    // construct a default IMAPNamespace response for return when the server doesn't support this. 
+    public IMAPNamespaceResponse() 
+    {
+        super("NAMESPACE", null); 
+        // fill in default lists to simplify processing 
+        personalNamespaces = Collections.EMPTY_LIST; 
+        otherUserNamespaces = Collections.EMPTY_LIST; 
+        sharedNamespaces = Collections.EMPTY_LIST; 
+    }
+
+    /**
+     * Construct a LIST response item.  This can be either 
+     * a response from a LIST command or an LSUB command, 
+     * and will be tagged accordingly.
+     * 
+     * @param type   The type of resonse (LIST or LSUB).
+     * @param data   The raw response data.
+     * @param source The tokenizer source.
+     * 
+     * @exception MessagingException
+     */
+    public IMAPNamespaceResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super("NAMESPACE", data); 
+        // the namespace response is a set of 3 items, which will be either NIL or a "list of lists".  
+        // if the item exists, then there will be a set of list parens, with 1 or more subitems inside. 
+        // Each of the subitems will consist of a namespace prefix and the hierarchy delimiter for that 
+        // particular namespace. 
+        personalNamespaces = parseNamespace(source); 
+        otherUserNamespaces = parseNamespace(source); 
+        sharedNamespaces = parseNamespace(source); 
+    }
+    
+    private List parseNamespace(IMAPResponseTokenizer source) throws MessagingException {
+        Token token = source.next(true); 
+        // is this token the NIL token?
+        if (token.getType() == Token.NIL) {
+            // no items at this position. 
+            return null; 
+        }
+        if (token.getType() != '(') {
+            throw new ResponseFormatException("Missing '(' in response");
+        }
+        
+        // ok, we're processing a namespace list.  Create a list and populate it with IMAPNamespace 
+        // items. 
+        
+        List namespaces = new ArrayList(); 
+        
+        while (source.notListEnd()) {
+            namespaces.add(new IMAPNamespace(source)); 
+        }
+        // this should always pass, since it terminated the loop 
+        source.checkRightParen(); 
+        return namespaces; 
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPOkResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPOkResponse.java
new file mode 100644
index 0000000..647753b
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPOkResponse.java
@@ -0,0 +1,78 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+/**
+ * Util class to represent an untagged response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPOkResponse extends IMAPUntaggedResponse {
+    // the response status value 
+    protected List status; 
+    // any message following the response 
+    protected String message; 
+
+    /**
+     * Create a reply object from a server response line (normally, untagged).  This includes
+     * doing the parsing of the response line.
+     *
+     * @param response The response line used to create the reply object.
+     */
+    public IMAPOkResponse(String keyword, List status, String message, byte [] response) {
+        super(keyword, response); 
+        this.status = status; 
+        this.message = message; 
+    }
+    
+    /**
+     * Get the response code included with the OK 
+     * response. 
+     * 
+     * @return The string name of the response code.
+     */
+    public String getResponseCode() {
+        return getKeyword(); 
+    }
+
+    /**
+     * Return the status argument values associated with
+     * this status response.
+     * 
+     * @return The status value information, as a list of tokens.
+     */
+    public List getStatus() {
+        return status; 
+    }
+    
+    /**
+     * Get any trailing message associated with this 
+     * status response. 
+     * 
+     * @return 
+     */
+    public String getMessage() {
+        return message; 
+    }
+}
+
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPPermanentFlagsResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPPermanentFlagsResponse.java
new file mode 100644
index 0000000..53b74df
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPPermanentFlagsResponse.java
@@ -0,0 +1,42 @@
+/**
+ * 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 javax.mail.Flags;
+import javax.mail.MessagingException;
+
+/**
+ * Util class to represent an untagged response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPPermanentFlagsResponse extends IMAPUntaggedResponse {
+    // the response flags value  
+    public Flags flags;     
+    /**
+     * Create a reply object from a server response line (normally, untagged).  This includes
+     * doing the parsing of the response line.
+     *
+     * @param response The response line used to create the reply object.
+     */
+    public IMAPPermanentFlagsResponse(byte [] response, IMAPResponseTokenizer source) throws MessagingException {
+        super("PERMANENTFLAGS", response); 
+        flags = source.readFlagList(); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaResponse.java
new file mode 100644
index 0000000..f8f9f11
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaResponse.java
@@ -0,0 +1,70 @@
+/**
+ * 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.util.ArrayList; 
+import java.util.List;
+
+import javax.mail.MessagingException;
+import javax.mail.Quota;
+
+/**
+ * Util class to represent a list response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+
+public class IMAPQuotaResponse extends IMAPUntaggedResponse {
+    // the returned quota item 
+    public Quota quota; 
+
+    /**
+     * Construct a LIST response item.  This can be either 
+     * a response from a LIST command or an LSUB command, 
+     * and will be tagged accordingly.
+     * 
+     * @param type   The type of resonse (LIST or LSUB).
+     * @param data   The raw response data.
+     * @param source The tokenizer source.
+     * 
+     * @exception MessagingException
+     */
+    public IMAPQuotaResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super("QUOTA", data); 
+
+        // first token is the root name, which can be either an atom or a string. 
+        String tokenName = source.readString(); 
+        
+        // create a quota item for this 
+        quota = new Quota(tokenName); 
+        
+        source.checkLeftParen(); 
+        
+        List resources = new ArrayList(); 
+        
+        while (source.notListEnd()) {
+            // quotas are returned as a set of triplets.  The first element is the 
+            // resource name, followed by the current usage and the limit value. 
+            Quota.Resource resource = new Quota.Resource(source.readAtom(), source.readLong(), source.readLong()); 
+            resources.add(resource); 
+        }
+        
+        quota.resources = (Quota.Resource[])resources.toArray(new Quota.Resource[resources.size()]); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaRootResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaRootResponse.java
new file mode 100644
index 0000000..802d129
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaRootResponse.java
@@ -0,0 +1,57 @@
+/**
+ * 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.util.List;
+
+import javax.mail.MessagingException;
+
+/**
+ * Util class to represent a list response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+
+public class IMAPQuotaRootResponse extends IMAPUntaggedResponse {
+    // the mailbox this applies to 
+    public String mailbox; 
+    // The list of quota roots 
+    public List roots; 
+    
+
+    /**
+     * Construct a LIST response item.  This can be either 
+     * a response from a LIST command or an LSUB command, 
+     * and will be tagged accordingly.
+     * 
+     * @param type   The type of resonse (LIST or LSUB).
+     * @param data   The raw response data.
+     * @param source The tokenizer source.
+     * 
+     * @exception MessagingException
+     */
+    public IMAPQuotaRootResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super("QUOTAROOT", data); 
+
+        // first token is the mailbox 
+        mailbox = source.readEncodedString(); 
+        // get the root name list as the remainder of the command. 
+        roots = source.readStrings(); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponse.java
new file mode 100644
index 0000000..9fbbb24
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponse.java
@@ -0,0 +1,66 @@
+/**
+ * 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 javax.mail.MessagingException;
+
+/**
+ * Base class for all response messages.                      
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPResponse {
+    // The original (raw) response data
+    protected byte[] response;
+
+    /**
+     * Create a response object from a server response line (normally, untagged).  This includes
+     * doing the parsing of the response line.
+     *
+     * @param response The response line used to create the reply object.
+     */
+    protected IMAPResponse(byte [] response) {
+        // set this as the current message and parse.
+        this.response = response;
+    }
+    
+    /**
+     * Retrieve the raw response line data for this 
+     * response message.  Normally, this will be a complete
+     * single line response, unless there are quoted 
+     * literals in the response data containing octet
+     * data. 
+     * 
+     * @return The byte array containing the response information.
+     */
+    public byte[] getResponseData() {
+        return response; 
+    }
+
+    /**
+     * Return the response message as a string value.  
+     * This is intended for debugging purposes only.  The 
+     * response data might contain octet data that 
+     * might not convert to character data appropriately. 
+     * 
+     * @return The string version of the response. 
+     */
+    public String toString() {
+        return new String(response);
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseBuffer.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseBuffer.java
new file mode 100644
index 0000000..0c2e458
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseBuffer.java
@@ -0,0 +1,134 @@
+/**
+ * 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.ByteArrayOutputStream;
+
+/**
+ * Simple extension to the ByteArrayOutputStream to allow inspection
+ * of the data while it is being accumulated.
+ */
+public class IMAPResponseBuffer extends ByteArrayOutputStream {
+
+    public IMAPResponseBuffer() {
+        super();
+    }
+
+
+    /**
+     * Read a character from the byte array output stream buffer
+     * at the give position.
+     *
+     * @param index  The requested index.
+     *
+     * @return The byte at the target index, or -1 if the index is out of
+     *         bounds.
+     */
+    public int read(int index) {
+        if (index >= size()) {
+            return -1;
+        }
+        return buf[index];
+    }
+
+    /**
+     * Read a buffer of data from the output stream's accumulator
+     * buffer.  This will copy the data into a target byte arrain.
+     *
+     * @param buffer The target byte array for returning the data.
+     * @param offset The offset of the source data within the output stream buffer.
+     * @param length The desired length.
+     *
+     * @return The count of bytes transferred into the buffer.
+     */
+    public int read(byte[] buffer, int offset, int length) {
+
+        int available = size() - offset;
+        length = Math.min(length, available);
+        // nothing to return?   quit now.
+        if (length <= 0) {
+            return 0;
+        }
+        System.arraycopy(buf, offset, buffer, 0, length);
+        return length;
+    }
+
+    /**
+     * Search backwards through the buffer for a given byte.
+     *
+     * @param target The search character.
+     *
+     * @return The index relative to the buffer start of the given byte.
+     *         Returns -1 if not found.
+     */
+    public int lastIndex(byte target) {
+        for (int i = size() - 1; i > 0; i--) {
+            if (buf[i] == target) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+
+    /**
+     * Return the last byte written to the output stream.  Returns
+     * -1 if the stream is empty.
+     *
+     * @return The last byte written (or -1 if the stream is empty).
+     */
+    public int lastByte() {
+        if (size() > 0) {
+            return buf[size() - 1];
+        }
+        return -1;
+    }
+
+
+    /**
+     * Retrieve an IMAP literal length value from the buffer.  We
+     * have a literal length value IFF the last characters written
+     * to the buffer have the form "{nnnn}".  This returns the
+     * integer value of the info inside the curly braces.  Returns -1
+     * if a valid literal length is not found.
+     *
+     * @return A literal length value, or -1 if we don't have a literal
+     *         signature at the end.
+     */
+    public int getLiteralLength() {
+        // was the last byte before the line break the close of the literal length?
+        if (lastByte() == '}') {
+            // locate the length start
+            int literalStart = lastIndex((byte)'{');
+            // no matching start, this can't be a literal.
+            if (literalStart == -1) {
+                return -1;
+            }
+
+            String lenString = new String(buf, literalStart + 1, size() - (literalStart + 2));
+            try {
+                return Integer.parseInt(lenString);
+            } catch (NumberFormatException e) {
+                e.printStackTrace(); 
+            }
+        }
+        // not a literal
+        return -1;
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseStream.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseStream.java
new file mode 100644
index 0000000..9c8e365
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseStream.java
@@ -0,0 +1,392 @@
+/**
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.mail.MessagingException; 
+import javax.mail.event.FolderEvent; 
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token;
+import org.apache.geronimo.javamail.util.ConnectionException;
+
+public class IMAPResponseStream {
+    protected final int BUFFER_SIZE = 1024;   
+    
+    // our source input stream
+    protected InputStream in;
+    // The response buffer 
+    IMAPResponseBuffer out; 
+    // the buffer array 
+    protected byte[] buffer = new byte[BUFFER_SIZE]; 
+    // the current buffer position 
+    int position; 
+    // the current buffer read length 
+    int length; 
+
+    public IMAPResponseStream(InputStream in) {
+        this.in = in;
+        out = new IMAPResponseBuffer();
+    }
+    
+    public int read() throws IOException {
+        // if we can't read any more, that's an EOF condition. 
+        if (!fillBufferIfNeeded()) {
+            return -1; 
+        }
+        // just grab the next character 
+        return buffer[position++]; 
+    }
+    
+    protected boolean fillBufferIfNeeded() throws IOException {
+        // used up all of the data in the buffer?
+        if (position >= length) {
+            int readLength = 0; 
+            // a read from a network connection can return 0 bytes, 
+            // so we need to be prepared to handle a spin loop.  
+            while (readLength == 0) {
+                readLength = in.read(buffer, 0, buffer.length); 
+            }
+            // we may have hit the EOF.  Indicate the read failure   
+            if (readLength == -1) {
+                return false; 
+            }
+            // set our new buffer positions. 
+            position = 0; 
+            length = readLength; 
+        }
+        return true; 
+    }
+
+
+    /**
+     * Read a single response line from the input stream, returning
+     * a parsed and processed response line.
+     *
+     * @return A parsed IMAPResponse item using the response data.
+     * @exception MessagingException
+     */
+    public IMAPResponse readResponse() throws MessagingException  
+      {  
+        // reset our accumulator 
+        out.reset(); 
+        // now read a buffer of data
+        byte[] data = readData();
+        
+        // and create a tokenizer for parsing this down.
+        IMAPResponseTokenizer tokenizer = new IMAPResponseTokenizer(data);
+        // get the first token.
+        Token token = tokenizer.next();
+        
+        int type = token.getType(); 
+        
+        // a continuation response.  This will terminate a response set. 
+        if (type == Token.CONTINUATION) {
+            return new IMAPContinuationResponse(data); 
+        }
+        // unsolicited response.  There are multiple forms of these, which might actually be 
+        // part of the response for the last issued command. 
+        else if (type == Token.UNTAGGED) {
+            // step to the next token, which will give us the type
+            token = tokenizer.next(); 
+            // if the token is numeric, then this is a size response in the 
+            // form "* nn type"
+            if (token.isType(Token.NUMERIC)) {
+                int size = token.getInteger(); 
+                
+                token = tokenizer.next(); 
+                
+                String keyword = token.getValue(); 
+                
+                // FETCH responses require fairly complicated parsing.  Other 
+                // size/message updates are fairly generic. 
+                if (keyword.equals("FETCH")) {
+                    return new IMAPFetchResponse(size, data, tokenizer); 
+                }
+                return new IMAPSizeResponse(keyword, size, data); 
+            }
+            
+            // this needs to be an ATOM type, which will tell us what format this untagged 
+            // response is in.  There are many different untagged formats, some general, some 
+            // specific to particular command types. 
+            if (token.getType() != Token.ATOM) {
+                throw new MessagingException("Unknown server response: " + new String(data)); 
+            }
+            
+            String keyword = token.getValue(); 
+            // many response are in the form "* OK [keyword value] message". 
+            if (keyword.equals("OK")) {
+                return parseUntaggedOkResponse(data, tokenizer); 
+            }
+            // preauth status response 
+            else if (keyword.equals("PREAUTH")) {
+                return new IMAPServerStatusResponse("PREAUTH", tokenizer.getRemainder(), data); 
+            }
+            // preauth status response 
+            else if (keyword.equals("BYE")) {
+                return new IMAPServerStatusResponse("BYE", tokenizer.getRemainder(), data); 
+            }
+            else if (keyword.equals("BAD")) {
+                // these are generally ignored. 
+                return new IMAPServerStatusResponse("BAD", tokenizer.getRemainder(), data); 
+            }
+            else if (keyword.equals("NO")) {
+                // these are generally ignored. 
+                return new IMAPServerStatusResponse("NO", tokenizer.getRemainder(), data); 
+            }
+            // a complex CAPABILITY response 
+            else if (keyword.equals("CAPABILITY")) {
+                return new IMAPCapabilityResponse(tokenizer, data); 
+            }
+            // a complex LIST response 
+            else if (keyword.equals("LIST")) {
+                return new IMAPListResponse("LIST", data, tokenizer); 
+            }
+            // a complex FLAGS response 
+            else if (keyword.equals("FLAGS")) {
+                // parse this into a flags set. 
+                return new IMAPFlagsResponse(data, tokenizer); 
+            }
+            // a complex LSUB response (identical in format to LIST)
+            else if (keyword.equals("LSUB")) {
+                return new IMAPListResponse("LSUB", data, tokenizer); 
+            }
+            // a STATUS response, which will contain a list of elements 
+            else if (keyword.equals("STATUS")) {
+                return new IMAPStatusResponse(data, tokenizer); 
+            }
+            // SEARCH requests return an variable length list of message matches. 
+            else if (keyword.equals("SEARCH")) {
+                return new IMAPSearchResponse(data, tokenizer); 
+            }
+            // ACL requests return an variable length list of ACL values . 
+            else if (keyword.equals("ACL")) {
+                return new IMAPACLResponse(data, tokenizer); 
+            }
+            // LISTRIGHTS requests return a variable length list of RIGHTS values . 
+            else if (keyword.equals("LISTRIGHTS")) {
+                return new IMAPListRightsResponse(data, tokenizer); 
+            }
+            // MYRIGHTS requests return a list of user rights for a mailbox name. 
+            else if (keyword.equals("MYRIGHTS")) {
+                return new IMAPMyRightsResponse(data, tokenizer); 
+            }
+            // QUOTAROOT requests return a list of mailbox quota root names  
+            else if (keyword.equals("QUOTAROOT")) {
+                return new IMAPQuotaRootResponse(data, tokenizer); 
+            }
+            // QUOTA requests return a list of quota values for a root name  
+            else if (keyword.equals("QUOTA")) {
+                return new IMAPQuotaResponse(data, tokenizer); 
+            }
+            else if (keyword.equals("NAMESPACE")) {
+                return new IMAPNamespaceResponse(data, tokenizer); 
+            }
+        }
+        // begins with a word, this should be the tagged response from the last command. 
+        else if (type == Token.ATOM) {
+            String tag = token.getValue(); 
+            token = tokenizer.next(); 
+            String status = token.getValue(); 
+            // primary information in one of these is the status field, which hopefully 
+            // is 'OK'
+            return new IMAPTaggedResponse(tag, status, tokenizer.getRemainder(), data); 
+        }
+        throw new MessagingException("Unknown server response: " + new String(data)); 
+    }
+    
+    /**
+     * Parse an unsolicited OK status response.  These 
+     * responses are of the form:
+     * 
+     * * OK [keyword arguments ...] message
+     * 
+     * The part in the brackets are optional, but 
+     * most OK messages will have some sort of update.
+     * 
+     * @param data      The raw message data
+     * @param tokenizer The tokenizer being used for this message.
+     * 
+     * @return An IMAPResponse instance for this message. 
+     */
+    private IMAPResponse parseUntaggedOkResponse(byte [] data, IMAPResponseTokenizer tokenizer) throws MessagingException {
+        Token token = tokenizer.peek(); 
+        // we might have an optional value here 
+        if (token.getType() != '[') {
+            // this has no tagging item, so there's nothing to be processed 
+            // later. 
+            return new IMAPOkResponse("OK", null, tokenizer.getRemainder(), data); 
+        }
+        // skip over the "[" token
+        tokenizer.next(); 
+        token = tokenizer.next(); 
+        String keyword = token.getValue(); 
+        
+        // Permanent flags gets special handling 
+        if (keyword.equals("PERMANENTFLAGS")) {
+            return new IMAPPermanentFlagsResponse(data, tokenizer); 
+        }
+        
+        ArrayList arguments = new ArrayList(); 
+        
+        // strip off all of the argument tokens until the "]" list terminator. 
+        token = tokenizer.next(); 
+        while (token.getType() != ']') {
+            arguments.add(token); 
+            token = tokenizer.next(); 
+        }
+        // this has a tagged keyword and arguments that will be processed later. 
+        return new IMAPOkResponse(keyword, arguments, tokenizer.getRemainder(), data); 
+    }
+
+    
+    /**
+     * Read a "line" of server response data.  An individual line
+     * may span multiple line breaks, depending on syntax implications.
+     *
+     * @return
+     * @exception MessagingException
+     */
+    public byte[] readData() throws MessagingException {
+        // reset out buffer accumulator
+        out.reset();
+        // read until the end of the response into our buffer.
+        readBuffer();
+        // get the accumulated data.
+        return out.toByteArray();
+    }
+
+    /**
+     * Read a buffer of data.  This accumulates the data into a
+     * ByteArrayOutputStream, terminating the processing at a line
+     * break.  This also handles line breaks that are the result
+     * of literal continuations in the stream.
+     *
+     * @exception MessagingException
+     * @exception IOException
+     */
+    public void readBuffer() throws MessagingException {
+        while (true) {
+            int ch = nextByte();
+            // potential end of line?  Check the next character, and if it is an end of line,
+            // we need to do literal processing.
+            if (ch == '\r') {
+                int next = nextByte();
+                if (next == '\n') {
+                    // had a line break, which might be part of a literal marker.  Check for the signature,
+                    // and if we found it, continue with the next line.  In any case, we're done with processing here.
+                    checkLiteral();
+                    return;
+                }
+            }
+            // write this to the buffer.
+            out.write(ch);
+        }
+    }
+
+
+    /**
+     * Check the line just read to see if we're processing a line
+     * with a literal value.  Literals are encoded as "{length}\r\n",
+     * so if we've read up to the line break, we can check to see
+     * if we need to continue reading.
+     *
+     * If a literal marker is found, we read that many characters
+     * from the reader without looking for line breaks.  Once we've
+     * read the literal data, we just read the rest of the line
+     * as normal (which might also end with a literal marker).
+     *
+     * @exception MessagingException
+     */
+    public void checkLiteral() throws MessagingException {
+        try {
+            // see if we have a literal length signature at the end.
+            int length = out.getLiteralLength();
+            
+            // -1 means no literal length, so we're done reading this particular response.
+            if (length == -1) {
+                return;
+            }
+
+            // we need to write out the literal line break marker.
+            out.write('\r');
+            out.write('\n');
+
+            // have something we're supposed to read for the literal?
+            if (length > 0) {
+                byte[] bytes = new byte[length];
+
+                int offset = 0;
+
+                // The InputStream can return less than the requested length if it needs to block.
+                // This may take a couple iterations to get everything, particularly if it's long.
+                while (length > 0) {
+                    int read = -1; 
+                    try {
+                        read = in.read(bytes, offset, length);
+                    } catch (IOException e) {
+                        throw new MessagingException("Unexpected read error on server connection", e); 
+                    }
+                    // premature EOF we can't ignore.
+                    if (read == -1) {
+                        throw new MessagingException("Unexpected end of stream");
+                    }
+                    length -= read;
+                    offset += read;
+                }
+
+                // write this out to the output stream.
+                out.write(bytes);
+            }
+            // Now that we have the literal data, we need to read the rest of the response line (which might contain
+            // additional literals).  Just recurse on the line reading logic.
+            readBuffer();
+        } catch (IOException e) {
+            e.printStackTrace(); 
+            // this is a byte array output stream...should never happen
+        }
+    }
+
+
+    /**
+     * Get the next byte from the input stream, handling read errors
+     * and EOF conditions as MessagingExceptions.
+     *
+     * @return The next byte read from the stream.
+     * @exception MessagingException
+     */
+    protected int nextByte() throws MessagingException {
+        try {
+            int next = in.read();
+            if (next == -1) {
+                throw new MessagingException("Read error on IMAP server connection");
+            }
+            return next;
+        } catch (IOException e) {
+            throw new MessagingException("Unexpected error on server stream", e);
+        }
+    }
+
+
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseTokenizer.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseTokenizer.java
new file mode 100644
index 0000000..90355f1
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseTokenizer.java
@@ -0,0 +1,1441 @@
+/**
+ * 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.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList; 
+import java.util.Date; 
+import java.util.List; 
+
+import javax.mail.Flags;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MailDateFormat;
+import javax.mail.internet.ParameterList;
+
+import org.apache.geronimo.javamail.util.ResponseFormatException; 
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class IMAPResponseTokenizer {
+    /*
+     * set up the decoding table.
+     */
+    protected static final byte[] decodingTable = new byte[256];
+
+    protected static void initializeDecodingTable()
+    {
+        for (int i = 0; i < IMAPCommand.encodingTable.length; i++)
+        {
+            decodingTable[IMAPCommand.encodingTable[i]] = (byte)i;
+        }
+    }
+
+
+    static {
+        initializeDecodingTable();
+    }
+    
+    // a singleton formatter for header dates.
+    protected static MailDateFormat dateParser = new MailDateFormat();
+    
+    
+    public static class Token {
+        // Constant values from J2SE 1.4 API Docs (Constant values)
+        public static final int ATOM = -1;
+        public static final int QUOTEDSTRING = -2;
+        public static final int LITERAL = -3;
+        public static final int NUMERIC = -4;
+        public static final int EOF = -5;
+        public static final int NIL = -6;
+        // special single character markers     
+        public static final int CONTINUATION = '-';
+        public static final int UNTAGGED = '*';
+            
+        /**
+         * The type indicator.  This will be either a specific type, represented by 
+         * a negative number, or the actual character value. 
+         */
+        private int type;
+        /**
+         * The String value associated with this token.  All tokens have a String value, 
+         * except for the EOF and NIL tokens. 
+         */
+        private String value;
+
+        public Token(int type, String value) {
+            this.type = type;
+            this.value = value;
+        }
+
+        public int getType() {
+            return type;
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        public boolean isType(int type) {
+            return this.type == type;
+        }
+
+        /**
+         * Return the token as an integer value.  If this can't convert, an exception is 
+         * thrown. 
+         * 
+         * @return The integer value of the token. 
+         * @exception ResponseFormatException
+         */
+        public int getInteger() throws MessagingException {
+            if (value != null) {
+                try {
+                    return Integer.parseInt(value);
+                } catch (NumberFormatException e) {
+                }
+            }
+
+            throw new ResponseFormatException("Number value expected in response; fount: " + value);
+        }
+
+        /**
+         * Return the token as a long value.  If it can't convert, an exception is 
+         * thrown. 
+         * 
+         * @return The token as a long value. 
+         * @exception ResponseFormatException
+         */
+        public long getLong() throws MessagingException {
+            if (value != null) {
+                try {
+                    return Long.parseLong(value);
+                } catch (NumberFormatException e) {
+                }
+            }
+            throw new ResponseFormatException("Number value expected in response; fount: " + value);
+        }
+        
+        /**
+         * Handy debugging toString() method for token. 
+         * 
+         * @return The string value of the token. 
+         */
+        public String toString() {
+            if (type == NIL) {
+                return "NIL"; 
+            }
+            else if (type == EOF) {
+                return "EOF";
+            }
+            
+            if (value == null) {
+                return ""; 
+            }
+            return value; 
+        }
+    }
+
+    public static final Token EOF = new Token(Token.EOF, null);
+    public static final Token NIL = new Token(Token.NIL, null);
+
+    private static final String WHITE = " \t\n\r";
+    // The list of delimiter characters we process when    
+    // handling parsing of ATOMs.  
+    private static final String atomDelimiters = "(){}%*\"\\" + WHITE;
+    // this set of tokens is a slighly expanded set used for 
+    // specific response parsing.  When dealing with Body 
+    // section names, there are sub pieces to the name delimited 
+    // by "[", "]", ".", "<", ">" and SPACE, so reading these using 
+    // a superset of the ATOM processing makes for easier parsing. 
+    private static final String tokenDelimiters = "<>[].(){}%*\"\\" + WHITE;
+
+    // the response data read from the connection
+    private byte[] response;
+    // current parsing position
+    private int pos;
+
+    public IMAPResponseTokenizer(byte [] response) {
+        this.response = response;
+    }
+
+    /**
+     * Get the remainder of the response as a string.
+     *
+     * @return A string representing the remainder of the response.
+     */
+    public String getRemainder() {
+        // make sure we're still in range
+        if (pos >= response.length) {
+            return "";
+        }
+
+        return new String(response, pos, response.length - pos);
+    }
+    
+    
+    public Token next() throws MessagingException {
+        return next(false);
+    }
+
+    public Token next(boolean nilAllowed) throws MessagingException {
+        return readToken(nilAllowed, false);
+    }
+
+    public Token next(boolean nilAllowed, boolean expandedDelimiters) throws MessagingException {
+        return readToken(nilAllowed, expandedDelimiters);
+    }
+
+    public Token peek() throws MessagingException {
+        return peek(false, false);
+    }
+
+    public Token peek(boolean nilAllowed) throws MessagingException {
+        return peek(nilAllowed, false);
+    }
+
+    public Token peek(boolean nilAllowed, boolean expandedDelimiters) throws MessagingException {
+        int start = pos;
+        try {
+            return readToken(nilAllowed, expandedDelimiters);
+        } finally {
+            pos = start;
+        }
+    }
+
+    /**
+     * Read an ATOM token from the parsed response.
+     *
+     * @return A token containing the value of the atom token.
+     */
+    private Token readAtomicToken(String delimiters) {
+        // skip to next delimiter
+        int start = pos;
+        while (++pos < response.length) {
+            // break on the first non-atom character.
+            byte ch = response[pos];
+            if (delimiters.indexOf(response[pos]) != -1 || ch < 32 || ch >= 127) {
+                break;
+            }
+        }
+        
+        // Numeric tokens we store as a different type.  
+        String value = new String(response, start, pos - start); 
+        try {
+            int intValue = Integer.parseInt(value); 
+            return new Token(Token.NUMERIC, value);
+        } catch (NumberFormatException e) {
+        }
+        return new Token(Token.ATOM, value);
+    }
+
+    /**
+     * Read the next token from the response.
+     *
+     * @return The next token from the response.  White space is skipped, and comment
+     *         tokens are also skipped if indicated.
+     * @exception ResponseFormatException
+     */
+    private Token readToken(boolean nilAllowed, boolean expandedDelimiters) throws MessagingException {
+        String delimiters = expandedDelimiters ? tokenDelimiters : atomDelimiters; 
+        
+        if (pos >= response.length) {
+            return EOF;
+        } else {
+            byte ch = response[pos];
+            if (ch == '\"') {
+                return readQuotedString();
+            // beginning of a length-specified literal?
+            } else if (ch == '{') {
+                return readLiteral();
+            // white space, eat this and find a real token.
+            } else if (WHITE.indexOf(ch) != -1) {
+                eatWhiteSpace();
+                return readToken(nilAllowed, expandedDelimiters);
+            // either a CTL or special.  These characters have a self-defining token type.
+            } else if (ch < 32 || ch >= 127 || delimiters.indexOf(ch) != -1) {
+                pos++;
+                return new Token((int)ch, String.valueOf((char)ch));
+            } else {
+                // start of an atom, parse it off.
+                Token token = readAtomicToken(delimiters);
+                // now, if we've been asked to look at NIL tokens, check to see if it is one,
+                // and return that instead of the ATOM.
+                if (nilAllowed) {
+                    if (token.getValue().equalsIgnoreCase("NIL")) {
+                        return NIL;
+                    }
+                }
+                return token;
+            }
+        }
+    }
+
+    /**
+     * Read the next token from the response, returning it as a byte array value.
+     *
+     * @return The next token from the response.  White space is skipped, and comment
+     *         tokens are also skipped if indicated.
+     * @exception ResponseFormatException
+     */
+    private byte[] readData(boolean nilAllowed) throws MessagingException {
+        if (pos >= response.length) {
+            return null;
+        } else {
+            byte ch = response[pos];
+            if (ch == '\"') {
+                return readQuotedStringData();
+            // beginning of a length-specified literal?
+            } else if (ch == '{') {
+                return readLiteralData();
+            // white space, eat this and find a real token.
+            } else if (WHITE.indexOf(ch) != -1) {
+                eatWhiteSpace();
+                return readData(nilAllowed);
+            // either a CTL or special.  These characters have a self-defining token type.
+            } else if (ch < 32 || ch >= 127 || atomDelimiters.indexOf(ch) != -1) {
+                throw new ResponseFormatException("Invalid string value: " + ch);
+            } else {
+                // only process this if we're allowing NIL as an option.
+                if (nilAllowed) {
+                    // start of an atom, parse it off.
+                    Token token = next(true);
+                    if (token.isType(Token.NIL)) {
+                        return null;
+                    }
+                    // invalid token type.
+                    throw new ResponseFormatException("Invalid string value: " + token.getValue());
+                }
+                // invalid token type.
+                throw new ResponseFormatException("Invalid string value: " + ch);
+            }
+        }
+    }
+
+    /**
+     * Extract a substring from the response string and apply any
+     * escaping/folding rules to the string.
+     *
+     * @param start  The starting offset in the response.
+     * @param end    The response end offset + 1.
+     *
+     * @return The processed string value.
+     * @exception ResponseFormatException
+     */
+    private byte[] getEscapedValue(int start, int end) throws MessagingException {
+        ByteArrayOutputStream value = new ByteArrayOutputStream();
+
+        for (int i = start; i < end; i++) {
+            byte ch = response[i];
+            // is this an escape character?
+            if (ch == '\\') {
+                i++;
+                if (i == end) {
+                    throw new ResponseFormatException("Invalid escape character");
+                }
+                value.write(response[i]);
+            }
+            // line breaks are ignored, except for naked '\n' characters, which are consider
+            // parts of linear whitespace.
+            else if (ch == '\r') {
+                // see if this is a CRLF sequence, and skip the second if it is.
+                if (i < end - 1 && response[i + 1] == '\n') {
+                    i++;
+                }
+            }
+            else {
+                // just append the ch value.
+                value.write(ch);
+            }
+        }
+        return value.toByteArray();
+    }
+
+    /**
+     * Parse out a quoted string from the response, applying escaping
+     * rules to the value.
+     *
+     * @return The QUOTEDSTRING token with the value.
+     * @exception ResponseFormatException
+     */
+    private Token readQuotedString() throws MessagingException {
+
+        String value = new String(readQuotedStringData());
+        return new Token(Token.QUOTEDSTRING, value);
+    }
+
+    /**
+     * Parse out a quoted string from the response, applying escaping
+     * rules to the value.
+     *
+     * @return The byte array with the resulting string bytes.
+     * @exception ResponseFormatException
+     */
+    private byte[] readQuotedStringData() throws MessagingException {
+        int start = pos + 1;
+        boolean requiresEscaping = false;
+
+        // skip to end of comment/string
+        while (++pos < response.length) {
+            byte ch = response[pos];
+            if (ch == '"') {
+                byte[] value;
+                if (requiresEscaping) {
+                    value = getEscapedValue(start, pos);
+                }
+                else {
+                    value = subarray(start, pos);
+                }
+                // step over the delimiter for all cases.
+                pos++;
+                return value;
+            }
+            else if (ch == '\\') {
+                pos++;
+                requiresEscaping = true;
+            }
+            // we need to process line breaks also
+            else if (ch == '\r') {
+                requiresEscaping = true;
+            }
+        }
+
+        throw new ResponseFormatException("Missing '\"'");
+    }
+
+
+    /**
+     * Parse out a literal string from the response, using the length
+     * encoded before the listeral.
+     *
+     * @return The LITERAL token with the value.
+     * @exception ResponseFormatException
+     */
+    protected Token readLiteral() throws MessagingException {
+        String value = new String(readLiteralData());
+        return new Token(Token.LITERAL, value);
+    }
+
+
+    /**
+     * Parse out a literal string from the response, using the length
+     * encoded before the listeral.
+     *
+     * @return The byte[] array with the value.
+     * @exception ResponseFormatException
+     */
+    protected byte[] readLiteralData() throws MessagingException {
+        int lengthStart = pos + 1;
+
+        // see if we have a close marker.
+        int lengthEnd = indexOf("}\r\n", lengthStart);
+        if (lengthEnd == -1) {
+            throw new ResponseFormatException("Missing terminator on literal length");
+        }
+
+        int count = 0;
+        try {
+            count = Integer.parseInt(substring(lengthStart, lengthEnd));
+        } catch (NumberFormatException e) {
+            throw new ResponseFormatException("Invalid literal length " + substring(lengthStart, lengthEnd));
+        }
+        
+        // step over the length
+        pos = lengthEnd + 3;
+
+        // too long?
+        if (pos + count > response.length) {
+            throw new ResponseFormatException("Invalid literal length: " + count);
+        }
+
+        byte[] value = subarray(pos, pos + count);
+        pos += count;
+        
+        return value;
+    }
+
+
+    /**
+     * Extract a substring from the response buffer.
+     *
+     * @param start  The starting offset.
+     * @param end    The end offset (+ 1).
+     *
+     * @return A String extracted from the buffer.
+     */
+    protected String substring(int start, int end ) {
+        return new String(response, start, end - start);
+    }
+
+
+    /**
+     * Extract a subarray from the response buffer.
+     *
+     * @param start  The starting offset.
+     * @param end    The end offset (+ 1).
+     *
+     * @return A byte array string extracted rom the buffer.
+     */
+    protected byte[] subarray(int start, int end ) {
+        byte[] result = new byte[end - start];
+        System.arraycopy(response, start, result, 0, end - start);
+        return result;
+    }
+
+
+    /**
+     * Test if the bytes in the response buffer match a given
+     * string value.
+     *
+     * @param position The compare position.
+     * @param needle   The needle string we're testing for.
+     *
+     * @return True if the bytes match the needle value, false for any
+     *         mismatch.
+     */
+    public boolean match(int position, String needle) {
+        int length = needle.length();
+
+        if (response.length - position < length) {
+            return false;
+        }
+
+        for (int i = 0; i < length; i++) {
+            if (response[position + i ] != needle.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    /**
+     * Search for a given string starting from the current position
+     * cursor.
+     *
+     * @param needle The search string.
+     *
+     * @return The index of a match (in absolute byte position in the
+     *         response buffer).
+     */
+    public int indexOf(String needle) {
+        return indexOf(needle, pos);
+    }
+
+    /**
+     * Search for a string in the response buffer starting from the
+     * indicated position.
+     *
+     * @param needle   The search string.
+     * @param position The starting buffer position.
+     *
+     * @return The index of the match position.  Returns -1 for no match.
+     */
+    public int indexOf(String needle, int position) {
+        // get the last possible match position
+        int last = response.length - needle.length();
+        // no match possible
+        if (last < position) {
+            return -1;
+        }
+
+        for (int i = position; i <= last; i++) {
+            if (match(i, needle)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+
+
+    /**
+     * Skip white space in the token string.
+     */
+    private void eatWhiteSpace() {
+        // skip to end of whitespace
+        while (++pos < response.length
+                && WHITE.indexOf(response[pos]) != -1)
+            ;
+    }
+    
+    
+    /**
+     * Ensure that the next token in the parsed response is a
+     * '(' character.
+     *
+     * @exception ResponseFormatException
+     */
+    public void checkLeftParen() throws MessagingException {
+        Token token = next();
+        if (token.getType() != '(') {
+            throw new ResponseFormatException("Missing '(' in response");
+        }
+    }
+
+
+    /**
+     * Ensure that the next token in the parsed response is a
+     * ')' character.
+     *
+     * @exception ResponseFormatException
+     */
+    public void checkRightParen() throws MessagingException {
+        Token token = next();
+        if (token.getType() != ')') {
+            throw new ResponseFormatException("Missing ')' in response");
+        }
+    }
+
+
+    /**
+     * Read a string-valued token from the response.  A string
+     * valued token can be either a quoted string, a literal value,
+     * or an atom.  Any other token type is an error.
+     *
+     * @return The string value of the source token.
+     * @exception ResponseFormatException
+     */
+    public String readString() throws MessagingException {
+        Token token = next();
+        int type = token.getType();
+
+        if (type != Token.ATOM && type != Token.QUOTEDSTRING && type != Token.LITERAL && type != Token.NUMERIC) {
+            throw new ResponseFormatException("String token expected in response: " + token.getValue());
+        }
+        return token.getValue();
+    }
+    
+
+    /**
+     * Read an encoded string-valued token from the response.  A string
+     * valued token can be either a quoted string, a literal value,
+     * or an atom.  Any other token type is an error.
+     *
+     * @return The string value of the source token.
+     * @exception ResponseFormatException
+     */
+    public String readEncodedString() throws MessagingException {
+        String value = readString(); 
+        return decode(value); 
+    }
+
+
+    /**
+     * Decode a Base 64 encoded string value.
+     * 
+     * @param original The original encoded string.
+     * 
+     * @return The decoded string. 
+     * @exception MessagingException
+     */
+    public String decode(String original) throws MessagingException {
+        StringBuffer result = new StringBuffer();
+
+        for (int i = 0; i < original.length(); i++) {
+            char ch = original.charAt(i);
+
+            if (ch == '&') {
+                i = decode(original, i, result);
+            }
+            else {
+                result.append(ch);
+            }
+        }
+
+        return result.toString();
+    }
+
+
+    /**
+     * Decode a section of an encoded string value. 
+     * 
+     * @param original The original source string.
+     * @param index    The current working index.
+     * @param result   The StringBuffer used for the decoded result.
+     * 
+     * @return The new index for the decoding operation. 
+     * @exception MessagingException
+     */
+    public static int decode(String original, int index, StringBuffer result) throws MessagingException {
+        // look for the section terminator
+        int terminator = original.indexOf('-', index);
+
+        // unmatched?
+        if (terminator == -1) {
+            throw new MessagingException("Invalid UTF-7 encoded string");
+        }
+
+        // is this just an escaped "&"?
+        if (terminator == index + 1) {
+            // append and skip over this.
+            result.append('&');
+            return index + 2;
+        }
+
+        // step over the starting char
+        index++;
+
+        int chars = terminator - index;
+        int quads = chars / 4;
+        int residual = chars % 4;
+
+        // buffer for decoded characters
+        byte[] buffer = new byte[4];
+        int bufferCount = 0;
+
+        // process each of the full triplet pairs
+        for (int i = 0; i < quads; i++) {
+            byte b1 = decodingTable[original.charAt(index++) & 0xff];
+            byte b2 = decodingTable[original.charAt(index++) & 0xff];
+            byte b3 = decodingTable[original.charAt(index++) & 0xff];
+            byte b4 = decodingTable[original.charAt(index++) & 0xff];
+
+            buffer[bufferCount++] = (byte)((b1 << 2) | (b2 >> 4));
+            buffer[bufferCount++] = (byte)((b2 << 4) | (b3 >> 2));
+            buffer[bufferCount++] = (byte)((b3 << 6) | b4);
+
+            // we've written 3 bytes to the buffer, but we might have a residual from a previous
+            // iteration to deal with.
+            if (bufferCount == 4) {
+                // two complete chars here
+                b1 = buffer[0];
+                b2 = buffer[1];
+                result.append((char)((b1 << 8) + (b2 & 0xff)));
+                b1 = buffer[2];
+                b2 = buffer[3];
+                result.append((char)((b1 << 8) + (b2 & 0xff)));
+                bufferCount = 0;
+            }
+            else {
+                // we need to save the 3rd byte for the next go around
+                b1 = buffer[0];
+                b2 = buffer[1];
+                result.append((char)((b1 << 8) + (b2 & 0xff)));
+                buffer[0] = buffer[2];
+                bufferCount = 1;
+            }
+        }
+
+        // properly encoded, we should have an even number of bytes left.
+
+        switch (residual) {
+            // no residual...so we better not have an extra in the buffer
+            case 0:
+                // this is invalid...we have an odd number of bytes so far,
+                if (bufferCount == 1) {
+                    throw new MessagingException("Invalid UTF-7 encoded string");
+                }
+            // one byte left.  This shouldn't be valid.  We need at least 2 bytes to
+            // encode one unprintable char.
+            case 1:
+                throw new MessagingException("Invalid UTF-7 encoded string");
+
+            // ok, we have two bytes left, which can only encode a single byte.  We must have
+            // a dangling unhandled char.
+            case 2:
+            {
+                if (bufferCount != 1) {
+                    throw new MessagingException("Invalid UTF-7 encoded string");
+                }
+                byte b1 = decodingTable[original.charAt(index++) & 0xff];
+                byte b2 = decodingTable[original.charAt(index++) & 0xff];
+                buffer[bufferCount++] = (byte)((b1 << 2) | (b2 >> 4));
+
+                b1 = buffer[0];
+                b2 = buffer[1];
+                result.append((char)((b1 << 8) + (b2 & 0xff)));
+                break;
+            }
+
+            // we have 2 encoded chars.  In this situation, we can't have a leftover.
+            case 3:
+            {
+                // this is invalid...we have an odd number of bytes so far,
+                if (bufferCount == 1) {
+                    throw new MessagingException("Invalid UTF-7 encoded string");
+                }
+                byte b1 = decodingTable[original.charAt(index++) & 0xff];
+                byte b2 = decodingTable[original.charAt(index++) & 0xff];
+                byte b3 = decodingTable[original.charAt(index++) & 0xff];
+
+                buffer[bufferCount++] = (byte)((b1 << 2) | (b2 >> 4));
+                buffer[bufferCount++] = (byte)((b2 << 4) | (b3 >> 2));
+
+                b1 = buffer[0];
+                b2 = buffer[1];
+                result.append((char)((b1 << 8) + (b2 & 0xff)));
+                break;
+            }
+        }
+
+        // return the new scan location
+        return terminator + 1;
+    }
+
+    /**
+     * Read a string-valued token from the response, verifying this is an ATOM token.
+     *
+     * @return The string value of the source token.
+     * @exception ResponseFormatException
+     */
+    public String readAtom() throws MessagingException {
+        return readAtom(false); 
+    }
+    
+
+    /**
+     * Read a string-valued token from the response, verifying this is an ATOM token.
+     *
+     * @return The string value of the source token.
+     * @exception ResponseFormatException
+     */
+    public String readAtom(boolean expandedDelimiters) throws MessagingException {
+        Token token = next(false, expandedDelimiters);
+        int type = token.getType();
+
+        if (type != Token.ATOM) {
+            Thread.currentThread().dumpStack(); 
+            throw new ResponseFormatException("ATOM token expected in response: " + token.getValue());
+        }
+        return token.getValue();
+    }
+
+
+    /**
+     * Read a number-valued token from the response.  This must be an ATOM
+     * token.
+     *
+     * @return The integer value of the source token.
+     * @exception ResponseFormatException
+     */
+    public int readInteger() throws MessagingException {
+        Token token = next();
+        return token.getInteger(); 
+    }
+
+
+    /**
+     * Read a number-valued token from the response.  This must be an ATOM
+     * token.
+     *
+     * @return The long value of the source token.
+     * @exception ResponseFormatException
+     */
+    public int readLong() throws MessagingException {
+        Token token = next();
+        return token.getInteger(); 
+    }
+
+
+    /**
+     * Read a string-valued token from the response.  A string
+     * valued token can be either a quoted string, a literal value,
+     * or an atom.  Any other token type is an error.
+     *
+     * @return The string value of the source token.
+     * @exception ResponseFormatException
+     */
+    public String readStringOrNil() throws MessagingException {
+        // we need to recognize the NIL token.
+        Token token = next(true);
+        int type = token.getType();
+
+        if (type != Token.ATOM && type != Token.QUOTEDSTRING && type != Token.LITERAL && type != Token.NIL) {
+            throw new ResponseFormatException("String token or NIL expected in response: " + token.getValue());
+        }
+        // this returns null if the token is the NIL token.
+        return token.getValue();
+    }
+
+
+    /**
+     * Read a quoted string-valued token from the response.
+     * Any other token type other than NIL is an error.
+     *
+     * @return The string value of the source token.
+     * @exception ResponseFormatException
+     */
+    protected String readQuotedStringOrNil() throws MessagingException {
+        // we need to recognize the NIL token.
+        Token token = next(true);
+        int type = token.getType();
+
+        if (type != Token.QUOTEDSTRING && type != Token.NIL) {
+            throw new ResponseFormatException("String token or NIL expected in response");
+        }
+        // this returns null if the token is the NIL token.
+        return token.getValue();
+    }
+
+
+    /**
+     * Read a date from a response string.  This is expected to be in
+     * Internet Date format, but there's a lot of variation implemented
+     * out there.  If we're unable to format this correctly, we'll
+     * just return null.
+     *
+     * @return A Date object created from the source date.
+     */
+    public Date readDate() throws MessagingException {
+        String value = readString();
+
+        try {
+            return dateParser.parse(value);
+        } catch (Exception e) {
+            // we're just skipping over this, so return null
+            return null;
+        }
+    }
+
+
+    /**
+     * Read a date from a response string.  This is expected to be in
+     * Internet Date format, but there's a lot of variation implemented
+     * out there.  If we're unable to format this correctly, we'll
+     * just return null.
+     *
+     * @return A Date object created from the source date.
+     */
+    public Date readDateOrNil() throws MessagingException {
+        String value = readStringOrNil();
+        // this might be optional
+        if (value == null) {
+            return null; 
+        }
+
+        try {
+            return dateParser.parse(value);
+        } catch (Exception e) {
+            // we're just skipping over this, so return null
+            return null;
+        }
+    }
+
+    /**
+     * Read an internet address from a Fetch response.  The
+     * addresses are returned as a set of string tokens in the
+     * order "personal list mailbox host".  Any of these tokens
+     * can be NIL.
+     *
+     * The address may also be the start of a group list, which
+     * is indicated by the host being NIL.  If we have found the
+     * start of a group, then we need to parse multiple elements
+     * until we find the group end marker (indicated by both the
+     * mailbox and the host being NIL), and create a group
+     * InternetAddress instance from this.
+     *
+     * @return An InternetAddress instance parsed from the
+     *         element.
+     * @exception ResponseFormatException
+     */
+    public InternetAddress readAddress() throws MessagingException {
+        // we recurse, expecting a null response back for sublists.  
+        if (peek().getType() != '(') {
+            return null; 
+        }
+        
+        // must start with a paren
+        checkLeftParen(); 
+
+        // personal information
+        String personal = readStringOrNil();
+        // the domain routine information.
+        String routing = readStringOrNil();
+        // the target mailbox
+        String mailbox = readStringOrNil();
+        // and finally the host
+        String host = readStringOrNil();
+        // and validate the closing paren
+        checkRightParen();
+
+        // if this is a real address, we need to compose
+        if (host != null) {
+            StringBuffer address = new StringBuffer();
+            if (routing != null) {
+                address.append(routing);
+                address.append(':');
+            }
+            address.append(mailbox);
+            address.append('@');
+            address.append(host);
+
+            try {
+                return new InternetAddress(address.toString(), personal);
+            } catch (UnsupportedEncodingException e) {
+                throw new ResponseFormatException("Invalid Internet address format");
+            }
+        }
+        else {
+            // we're going to recurse on this.  If the mailbox is null (the group name), this is the group item
+            // terminator.
+            if (mailbox == null) {
+                return null;
+            }
+
+            StringBuffer groupAddress = new StringBuffer();
+
+            groupAddress.append(mailbox);
+            groupAddress.append(':');
+            int count = 0;
+
+            while (true) {
+                // now recurse until we hit the end of the list
+                InternetAddress member = readAddress();
+                if (member == null) {
+                    groupAddress.append(';');
+
+                    try {
+                        return new InternetAddress(groupAddress.toString(), personal);
+                    } catch (UnsupportedEncodingException e) {
+                        throw new ResponseFormatException("Invalid Internet address format");
+                    }
+                }
+                else {
+                    if (count != 0) {
+                        groupAddress.append(',');
+                    }
+                    groupAddress.append(member.toString());
+                    count++;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Parse out a list of addresses.  This list of addresses is
+     * surrounded by parentheses, and each address is also
+     * parenthized (SP?).
+     *
+     * @return An array of the parsed addresses.
+     * @exception ResponseFormatException
+     */
+    public InternetAddress[] readAddressList() throws MessagingException {
+        // must start with a paren, but can be NIL also.
+        Token token = next(true);
+        int type = token.getType();
+
+        // either of these results in a null address.  The caller determines based on
+        // context whether this was optional or not.
+        if (type == Token.NIL) {
+            return null;
+        }
+        // non-nil address and no paren.  This is a syntax error.
+        else if (type != '(') {
+            throw new ResponseFormatException("Missing '(' in response");
+        }
+
+        List addresses = new ArrayList();
+
+        // we have a list, now parse it.
+        while (notListEnd()) {
+            // go read the next address.  If we had an address, add to the list.
+            // an address ITEM cannot be NIL inside the parens. 
+            InternetAddress address = readAddress();
+            addresses.add(address);
+        }
+        // we need to skip over the peeked token.
+        checkRightParen(); 
+        return (InternetAddress[])addresses.toArray(new InternetAddress[addresses.size()]);
+    }
+
+
+    /**
+     * Check to see if we're at the end of a parenthized list
+     * without advancing the parsing pointer.  If we are at the
+     * end, then this will step over the closing paren.
+     *
+     * @return True if the next token is a closing list paren, false otherwise.
+     * @exception ResponseFormatException
+     */
+    public boolean checkListEnd() throws MessagingException {
+        Token token = peek(true);
+        if (token.getType() == ')') {
+            // step over this token.
+            next();
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Reads a string item which can be encoded either as a single
+     * string-valued token or a parenthized list of string tokens.
+     *
+     * @return A List containing all of the strings.
+     * @exception ResponseFormatException
+     */
+    public List readStringList() throws MessagingException {
+        Token token = peek();
+
+        List list = new ArrayList();
+
+        if (token.getType() == '(') {
+            next();
+
+            while (notListEnd()) {
+                String value = readString();
+                // this can be NIL, technically
+                if (value != null) {
+                    list.add(value);
+                }
+            }
+            // step over the closing paren 
+            next(); 
+        }
+        else {
+            // just a single string value.
+            String value = readString();
+            // this can be NIL, technically
+            if (value != null) {
+                list.add(value);
+            }
+        }
+        return list;
+    }
+
+
+    /**
+     * Reads all remaining tokens and returns them as a list of strings. 
+     * NIL values are not supported. 
+     *
+     * @return A List containing all of the strings.
+     * @exception ResponseFormatException
+     */
+    public List readStrings() throws MessagingException {
+        List list = new ArrayList();
+        
+        while (hasMore()) {
+            String value = readString();
+            list.add(value);
+        }
+        return list; 
+    }
+
+
+    /**
+     * Skip over an extension item.  This may be either a string
+     * token or a parenthized item (with potential nesting).
+     *
+     * At the point where this is called, we're looking for a closing
+     * ')', but we know it is not that.  An EOF is an error, however,
+     */
+    public void skipExtensionItem() throws MessagingException {
+        Token token = next();
+        int type = token.getType();
+
+        // list form?  Scan to find the correct list closure.
+        if (type == '(') {
+            skipNestedValue();
+        }
+        // found an EOF?  Big problem
+        else if (type == Token.EOF) {
+            throw new ResponseFormatException("Missing ')'");
+        }
+    }
+
+    /**
+     * Skip over a parenthized value that we're not interested in.
+     * These lists may contain nested sublists, so we need to
+     * handle the nesting properly.
+     */
+    public void skipNestedValue() throws MessagingException {
+        Token token = next();
+
+        while (true) {
+            int type = token.getType();
+            // list terminator?
+            if (type == ')') {
+                return;
+            }
+            // unexpected end of the tokens.
+            else if (type == Token.EOF) {
+                throw new ResponseFormatException("Missing ')'");
+            }
+            // encountered a nested list?
+            else if (type == '(') {
+                // recurse and finish this list.
+                skipNestedValue();
+            }
+            // we're just skipping the token.
+            token = next();
+        }
+    }
+
+    /**
+     * Get the next token and verify that it's of the expected type
+     * for the context.
+     *
+     * @param type   The type of token we're expecting.
+     */
+    public void checkToken(int type) throws MessagingException {
+        Token token = next();
+        if (token.getType() != type) {
+            throw new ResponseFormatException("Unexpected token: " + token.getValue());
+        }
+    }
+
+
+    /**
+     * Read the next token as binary data.  The next token can be a literal, a quoted string, or
+     * the token NIL (which returns a null result).  Any other token throws a ResponseFormatException.
+     *
+     * @return A byte array representing the rest of the response data.
+     */
+    public byte[] readByteArray() throws MessagingException {
+        return readData(true);
+    }
+    
+    
+    /**
+     * Determine what type of token encoding needs to be 
+     * used for a string value.
+     * 
+     * @param value  The string to test.
+     * 
+     * @return Either Token.ATOM, Token.QUOTEDSTRING, or 
+     *         Token.LITERAL, depending on the characters contained
+     *         in the value.
+     */
+    static public int getEncoding(byte[] value) {
+        
+        // a null string always needs to be represented as a quoted literal. 
+        if (value.length == 0) {
+            return Token.QUOTEDSTRING; 
+        }
+        
+        for (int i = 0; i < value.length; i++) {
+            int ch = value[i]; 
+            // make sure the sign extension is eliminated 
+            ch = ch & 0xff;
+            // check first for any characters that would 
+            // disqualify a quoted string 
+            // NULL
+            if (ch == 0x00) {
+                return Token.LITERAL; 
+            }
+            // non-7bit ASCII
+            if (ch > 0x7F) {
+                return Token.LITERAL; 
+            }
+            // carriage return
+            if (ch == '\r') {
+                return Token.LITERAL; 
+            }
+            // linefeed 
+            if (ch == '\n') {
+                return Token.LITERAL; 
+            }
+            // now check for ATOM disqualifiers 
+            if (atomDelimiters.indexOf(ch) != -1) {
+                return Token.QUOTEDSTRING; 
+            }
+            // CTL character.  We've already eliminated the high characters 
+            if (ch < 0x20) {
+                return Token.QUOTEDSTRING; 
+            }
+        }
+        // this can be an ATOM token 
+        return Token.ATOM;
+    }
+    
+    
+    /**
+     * Read a ContentType or ContentDisposition parameter 
+     * list from an IMAP command response.
+     * 
+     * @return A ParameterList instance containing the parameters. 
+     * @exception MessagingException
+     */
+    public ParameterList readParameterList() throws MessagingException {
+        ParameterList params = new ParameterList(); 
+        
+        // read the tokens, taking NIL into account. 
+        Token token = next(true, false); 
+        
+        // just return an empty list if this is NIL 
+        if (token.isType(token.NIL)) {
+            return params; 
+        }
+        
+        // these are pairs of strings for each parameter value 
+        while (notListEnd()) {
+            String name = readString(); 
+            String value = readString(); 
+            params.set(name, value); 
+        }
+        // we need to consume the list terminator 
+        checkRightParen(); 
+        return params; 
+    }
+    
+    
+    /**
+     * Test if we have more data in the response buffer.
+     * 
+     * @return true if there are more tokens to process.  false if 
+     *         we've reached the end of the stream.
+     */
+    public boolean hasMore() throws MessagingException {
+        // we need to eat any white space that might be in the stream.  
+        eatWhiteSpace();
+        return pos < response.length; 
+    }
+    
+    
+    /**
+     * Tests if we've reached the end of a parenthetical
+     * list in our parsing stream.
+     * 
+     * @return true if the next token will be a ')'.  false if the 
+     *         next token is anything else.
+     * @exception MessagingException
+     */
+    public boolean notListEnd() throws MessagingException {
+        return peek().getType() != ')';
+    }
+    
+    /**
+     * Read a list of Flag values from an IMAP response, 
+     * returning a Flags instance containing the appropriate 
+     * pieces. 
+     * 
+     * @return A Flags instance with the flag values. 
+     * @exception MessagingException
+     */
+    public Flags readFlagList() throws MessagingException {
+        Flags flags = new Flags();
+        
+        // this should be a list here 
+        checkLeftParen(); 
+        
+        // run through the flag list 
+        while (notListEnd()) {
+            // the flags are a bit of a pain.  The flag names include "\" in the name, which 
+            // is not a character allowed in an atom.  This requires a bit of customized parsing 
+            // to handle this. 
+            Token token = next(); 
+            // flags can be specified as just atom tokens, so allow this as a user flag. 
+            if (token.isType(token.ATOM)) {
+                // append the atom as a raw name 
+                flags.add(token.getValue()); 
+            }
+            // all of the system flags start with a '\' followed by 
+            // an atom.  They also can be extension flags.  IMAP has a special 
+            // case of "\*" that we need to check for. 
+            else if (token.isType('\\')) {
+                token = next(); 
+                // the next token is the real bit we need to process. 
+                if (token.isType('*')) {
+                    // this indicates USER flags are allowed. 
+                    flags.add(Flags.Flag.USER); 
+                }
+                // if this is an atom name, handle as a system flag 
+                else if (token.isType(Token.ATOM)) {
+                    String name = token.getValue(); 
+                    if (name.equalsIgnoreCase("Seen")) {
+                        flags.add(Flags.Flag.SEEN);
+                    }
+                    else if (name.equalsIgnoreCase("RECENT")) {
+                        flags.add(Flags.Flag.RECENT);
+                    }
+                    else if (name.equalsIgnoreCase("DELETED")) {
+                        flags.add(Flags.Flag.DELETED);
+                    }
+                    else if (name.equalsIgnoreCase("ANSWERED")) {
+                        flags.add(Flags.Flag.ANSWERED);
+                    }
+                    else if (name.equalsIgnoreCase("DRAFT")) {
+                        flags.add(Flags.Flag.DRAFT);
+                    }
+                    else if (name.equalsIgnoreCase("FLAGGED")) {
+                        flags.add(Flags.Flag.FLAGGED);
+                    }
+                    else {
+                        // this is a server defined flag....just add the name with the 
+                        // flag thingy prepended. 
+                        flags.add("\\" + name); 
+                    }
+                }
+                else {
+                    throw new MessagingException("Invalid Flag: " + token.getValue()); 
+                }
+            }
+            else {
+                throw new MessagingException("Invalid Flag: " + token.getValue()); 
+            }
+        }
+        
+        // step over this for good practice. 
+        checkRightParen(); 
+        
+        return flags; 
+    }
+    
+    
+    /**
+     * Read a list of Flag values from an IMAP response, 
+     * returning a Flags instance containing the appropriate 
+     * pieces. 
+     * 
+     * @return A Flags instance with the flag values. 
+     * @exception MessagingException
+     */
+    public List readSystemNameList() throws MessagingException {
+        List flags = new ArrayList(); 
+        
+        // this should be a list here 
+        checkLeftParen(); 
+        
+        // run through the flag list 
+        while (notListEnd()) {
+            // the flags are a bit of a pain.  The flag names include "\" in the name, which 
+            // is not a character allowed in an atom.  This requires a bit of customized parsing 
+            // to handle this. 
+            Token token = next(); 
+            // all of the system flags start with a '\' followed by 
+            // an atom.  They also can be extension flags.  IMAP has a special 
+            // case of "\*" that we need to check for. 
+            if (token.isType('\\')) {
+                token = next(); 
+                // if this is an atom name, handle as a system flag 
+                if (token.isType(Token.ATOM)) {
+                    // add the token value to the list WITH the 
+                    // flag indicator included.  The attributes method returns 
+                    // these flag indicators, so we need to include it. 
+                    flags.add("\\" + token.getValue()); 
+                }
+                else {
+                    throw new MessagingException("Invalid Flag: " + token.getValue()); 
+                }
+            }
+            else {
+                throw new MessagingException("Invalid Flag: " + token.getValue()); 
+            }
+        }
+        
+        // step over this for good practice. 
+        checkRightParen(); 
+        
+        return flags; 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchDateFormat.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchDateFormat.java
new file mode 100644
index 0000000..b284a57
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchDateFormat.java
@@ -0,0 +1,69 @@
+/**
+ * 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.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Formats ths date in the form used by the javamail IMAP SEARCH command, 
+ * <p/>
+ * The format used is <code>d MMM yyyy</code> and  locale is always US-ASCII.
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPSearchDateFormat extends SimpleDateFormat {
+    public IMAPSearchDateFormat() {
+        super("dd-MMM-yyyy", Locale.US);
+    }
+    public StringBuffer format(Date date, StringBuffer buffer, FieldPosition position) {
+        StringBuffer result = super.format(date, buffer, position);
+        // The RFC 2060 requires that the day in the date be formatted with either 2 digits
+        // or one digit.  Our format specifies 2 digits, which pads with leading
+        // zeros.  We need to check for this and whack it if it's there
+        if (result.charAt(0) == '0') {
+            result.deleteCharAt(0); 
+        }
+        return result;
+    }
+
+    /**
+     * The calendar cannot be set
+     * @param calendar
+     * @throws UnsupportedOperationException
+     */
+    public void setCalendar(Calendar calendar) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * The format cannot be set
+     * @param format
+     * @throws UnsupportedOperationException
+     */
+    public void setNumberFormat(NumberFormat format) {
+        throw new UnsupportedOperationException();
+    }
+}
+
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchResponse.java
new file mode 100644
index 0000000..1afeb84
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchResponse.java
@@ -0,0 +1,53 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseTokenizer.Token; 
+
+/**
+ * Utility class to aggregate status responses for a mailbox.
+ */
+public class IMAPSearchResponse extends IMAPUntaggedResponse {
+    public int[] messageNumbers; 
+    
+    public IMAPSearchResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super("SEARCH",  data); 
+        
+        Token token = source.next(); 
+        List tokens = new ArrayList();
+        
+        // just accumulate the list of tokens first 
+        while (token.getType() != Token.EOF) {
+            tokens.add(token); 
+            token = source.next(); 
+        }
+        
+        messageNumbers = new int[tokens.size()]; 
+        
+        // now parse these into numbers 
+        for (int i = 0; i < messageNumbers.length; i++) {
+            token = (Token)tokens.get(i); 
+            messageNumbers[i] = token.getInteger(); 
+        }
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPServerStatusResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPServerStatusResponse.java
new file mode 100644
index 0000000..b8932d6
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPServerStatusResponse.java
@@ -0,0 +1,50 @@
+/**
+ * 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;
+
+/**
+ * Util class to represent an untagged response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPServerStatusResponse extends IMAPUntaggedResponse {
+    // any message following the response 
+    protected String message; 
+
+    /**
+     * Create a reply object from a server response line (normally, untagged).  This includes
+     * doing the parsing of the response line.
+     *
+     * @param response The response line used to create the reply object.
+     */
+    public IMAPServerStatusResponse(String keyword, String message, byte [] response) {
+        super(keyword, response); 
+        this.message = message; 
+    }
+    
+    /**
+     * Get any trailing message associated with this 
+     * status response. 
+     * 
+     * @return 
+     */
+    public String getMessage() {
+        return message; 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSizeResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSizeResponse.java
new file mode 100644
index 0000000..11d7a4b
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSizeResponse.java
@@ -0,0 +1,50 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.mail.MessagingException;
+
+/**
+ * Util class to represent a server size response.
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPSizeResponse extends IMAPUntaggedResponse {
+    // the associated size 
+    protected int size; 
+
+    /**
+     * Create a size response item.
+     * 
+     * @param keyword  The KEYWORD item associated with the size.
+     * @param size     The size value.
+     * @param response The raw response data.
+     */
+    public IMAPSizeResponse(String keyword, int size, byte [] response) {
+        super(keyword, response); 
+        this.size = size; 
+    }
+    
+    public int getSize() {
+        return size; 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPStatusResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPStatusResponse.java
new file mode 100644
index 0000000..6db932e
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPStatusResponse.java
@@ -0,0 +1,79 @@
+/**
+ * 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.util.List;
+
+import javax.mail.MessagingException;
+
+/**
+ * Utility class to aggregate status responses for a mailbox.
+ */
+public class IMAPStatusResponse extends IMAPUntaggedResponse {
+    // the mail box name 
+    public String mailbox; 
+    // number of messages in the box
+    public int messages = -1;
+    // number of recent messages 
+    public int recentMessages = -1; 
+    // the number of unseen messages
+    public int unseenMessages = -1;
+    // the next UID for this mailbox
+    public long uidNext = -1L;
+    // the UID validity item
+    public long uidValidity = -1L;
+
+    public IMAPStatusResponse(byte[] data, IMAPResponseTokenizer source) throws MessagingException {
+        super("STATUS",  data); 
+        
+        // the mail box name is supposed to be encoded, so decode it now.
+        mailbox = source.readEncodedString();
+
+        // parse the list of flag values
+        List flags = source.readStringList();
+
+        for (int i = 0; i < flags.size(); i += 2) {
+            String field = ((String)flags.get(i)).toUpperCase();
+            String stringValue = ((String)flags.get(i + 1)); 
+            long value; 
+            try {
+                value = Long.parseLong(stringValue); 
+            } catch (NumberFormatException e) {
+                throw new MessagingException("Invalid IMAP Status response", e); 
+            }
+                
+
+            if (field.equals("MESSAGES")) {
+                messages = (int)value; 
+            }
+            else if (field.equals("RECENT")) {
+                recentMessages = (int)value;
+            }
+            else if (field.equals("UIDNEXT")) {
+                uidNext = value;
+            }
+            else if (field.equals("UIDVALIDITY")) {
+                uidValidity = value; 
+            }
+            else if (field.equals("UNSEEN")) {
+                unseenMessages = (int)value; 
+            }
+        }
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPTaggedResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPTaggedResponse.java
new file mode 100644
index 0000000..b1de093
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPTaggedResponse.java
@@ -0,0 +1,155 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.mail.util.Base64;
+
+/**
+ * Util class to represent a response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPTaggedResponse extends IMAPResponse {
+
+    // the reply state
+    protected String status;
+    // the tag associated with a reply.
+    protected String tag;
+    // the message associated with the completion response 
+    protected String message;
+
+    /**
+     * Create a command completion response for a 
+     * submitted command.  The tag prefix identifies 
+     * the command this response is for. 
+     * 
+     * @param tag      The command tag value.
+     * @param status   The Status response (OK, BAD, or NO).
+     * @param message  The remainder of the response, as a string.
+     * @param response The response data used to create the reply object.
+     */
+    public IMAPTaggedResponse(String tag, String status, String message, byte [] response) {
+        super(response); 
+        this.tag = tag; 
+        this.status = status;
+        this.message = message; 
+    }
+
+
+    /**
+     * Create a continuation response for a 
+     * submitted command.  
+     * 
+     * @param response The response data used to create the reply object.
+     */
+    public IMAPTaggedResponse(byte [] response) {
+        super(response); 
+        this.tag = "";  
+        this.status = "CONTINUATION";
+        this.message = message; 
+    }
+
+    /**
+     * Test if the response code was "OK".
+     *
+     * @return True if the response status was OK, false for any other status.
+     */
+    public boolean isOK() {
+        return status.equals("OK");
+    }
+
+    /**
+     * Test for an error return from a command.
+     *
+     * @return True if the response status was BAD.
+     */
+    public boolean isBAD() {
+        return status.equals("BAD"); 
+    }
+
+    /**
+     * Test for an error return from a command.
+     *
+     * @return True if the response status was NO.
+     */
+    public boolean isNO() {
+        return status.equals("NO"); 
+    }
+    
+    /**
+     * Get the message included on the tagged response. 
+     * 
+     * @return The String message data. 
+     */
+    public String getMessage() {
+        return message; 
+    }
+    
+    /**
+     * Decode the message portion of a continuation challenge response.
+     * 
+     * @return The byte array containing the decoded data. 
+     */
+    public byte[] decodeChallengeResponse() 
+    {
+        // we're passed back a challenge value, Base64 encoded.  Decode that portion of the 
+        // response data. 
+        return Base64.decode(response, 2, response.length - 2);
+    }
+    
+    
+    /**
+     * Test if this is a continuation response. 
+     * 
+     * @return True if this a continuation.  false for a normal tagged response. 
+     */
+    public boolean isContinuation() {
+        return status.equals("CONTINUATION"); 
+    }
+    
+    
+    /**
+     * Test if the untagged response includes a given 
+     * status indicator.  Mostly used for checking 
+     * READ-ONLY or READ-WRITE status after selecting a 
+     * mail box.
+     * 
+     * @param name   The status name to check.
+     * 
+     * @return true if this is found in the "[" "]" delimited
+     *         section of the response message.
+     */
+    public boolean hasStatus(String name) {
+        // see if we have the status bits at all 
+        int statusStart = message.indexOf('['); 
+        if (statusStart == -1) {
+            return false; 
+        }
+        
+        int statusEnd = message.indexOf(']'); 
+        String statusString = message.substring(statusStart, statusEnd).toUpperCase(); 
+        // just search for the status token. 
+        return statusString.indexOf(name) != -1; 
+    }
+}
+
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUid.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUid.java
new file mode 100644
index 0000000..45137ee
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUid.java
@@ -0,0 +1,36 @@
+/**
+ * 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 javax.mail.MessagingException;
+
+public class IMAPUid extends IMAPFetchDataItem {
+    // the returned uid
+    public long uid;
+    // the returned sequence number for the message 
+    public int messageNumber; 
+
+    public IMAPUid(int messageNumber, IMAPResponseTokenizer source) throws MessagingException {
+        super(UID);
+        // just read the number pairs 
+        this.messageNumber = messageNumber;
+        uid = source.readLong();
+    }
+}
+
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponse.java
new file mode 100644
index 0000000..bbf20d5
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponse.java
@@ -0,0 +1,67 @@
+/**
+ * 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.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+/**
+ * Util class to represent an untagged response from a IMAP server
+ *
+ * @version $Rev$ $Date$
+ */
+public class IMAPUntaggedResponse extends IMAPResponse {
+    // the response key word 
+    protected String keyword; 
+
+    /**
+     * Create a reply object from a server response line (normally, untagged).  This includes
+     * doing the parsing of the response line.
+     *
+     * @param response The response line used to create the reply object.
+     */
+    public IMAPUntaggedResponse(String keyword, byte [] response) {
+        super(response); 
+        this.keyword = keyword; 
+    }
+
+    /**
+     * Return the KEYWORD that identifies the type 
+     * of this untagged response.
+     * 
+     * @return The identifying keyword.
+     */
+    public String getKeyword() {
+        return keyword; 
+    }
+    
+    
+    /**
+     * Test if an untagged response is of a given 
+     * keyword type.
+     * 
+     * @param keyword The test keyword.
+     * 
+     * @return True if this is a type match, false for mismatches.
+     */
+    public boolean isKeyword(String keyword) {
+        return this.keyword.equals(keyword); 
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponseHandler.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponseHandler.java
new file mode 100644
index 0000000..1a6812a
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponseHandler.java
@@ -0,0 +1,34 @@
+/**
+ * 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;
+
+public interface IMAPUntaggedResponseHandler {
+    /**
+     * Handle an unsolicited untagged response receive back from a command.  This 
+     * will be any responses left over after the command has cherry picked the 
+     * bits that are relevent to the command just issued.  It is important 
+     * that the unsolicited response be reacted to in order to keep the message 
+     * caches in sync. 
+     * 
+     * @param response The response to handle.
+     * 
+     * @return true if the handle took care of the response and it should not be sent 
+     *         to additional handlers.  false if broadcasting of the response should continue.
+     */
+    public boolean handleResponse(IMAPUntaggedResponse response);
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
index 9e953eb..8942944 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
@@ -195,7 +195,6 @@
         // instances.
         for (int i = 0; i < addresses.length; i++) {
             if (!(addresses[i] instanceof NewsAddress)) {
-                System.out.println("Illegal address is of class " + addresses[i].getClass());
                 throw new MessagingException("Illegal NewsAddress " + addresses[i]);
             }
         }
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
index 206f4ad..14e91a4 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
@@ -1697,7 +1697,6 @@
             throw new MessagingException("no connection");
         }
         try {
-            System.out.println(">>>>>Sending data " + data + "<<<<<<");
             outputStream.write(data.getBytes());
             outputStream.write(CR);
             outputStream.write(LF);
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/CommandFailedException.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/CommandFailedException.java
new file mode 100644
index 0000000..2c12b70
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/CommandFailedException.java
@@ -0,0 +1,35 @@
+/**
+ * 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.util;
+
+import javax.mail.MessagingException; 
+
+public class CommandFailedException extends MessagingException {
+    public CommandFailedException() {
+        super();
+    }
+
+    public CommandFailedException(String message) {
+        super(message);
+    }
+
+    public CommandFailedException(String message, Exception cause) {
+        super(message, cause);
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ConnectionException.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ConnectionException.java
new file mode 100644
index 0000000..ece163d
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ConnectionException.java
@@ -0,0 +1,35 @@
+/**
+ * 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.util;
+
+import javax.mail.MessagingException; 
+
+public class ConnectionException extends MessagingException {
+    public ConnectionException() {
+        super();
+    }
+
+    public ConnectionException(String message) {
+        super(message);
+    }
+
+    public ConnectionException(String message, Exception cause) {
+        super(message, cause);
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/InvalidCommandException.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/InvalidCommandException.java
new file mode 100644
index 0000000..4273cbb
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/InvalidCommandException.java
@@ -0,0 +1,35 @@
+/**
+ * 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.util;
+
+import javax.mail.MessagingException; 
+
+public class InvalidCommandException extends MessagingException {
+    public InvalidCommandException() {
+        super();
+    }
+
+    public InvalidCommandException(String message) {
+        super(message);
+    }
+
+    public InvalidCommandException(String message, Exception cause) {
+        super(message, cause);
+    }
+}
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java
new file mode 100644
index 0000000..71e4ead
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java
@@ -0,0 +1,712 @@
+/**
+ * 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.util;
+
+import java.io.IOException; 
+import java.io.InputStream; 
+import java.io.OutputStream; 
+import java.io.PrintStream; 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.Socket; 
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer; 
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.net.ssl.SSLSocket;
+
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator; 
+import org.apache.geronimo.javamail.authentication.CramMD5Authenticator; 
+import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator; 
+import org.apache.geronimo.javamail.authentication.LoginAuthenticator; 
+import org.apache.geronimo.javamail.authentication.PlainAuthenticator; 
+import org.apache.geronimo.javamail.authentication.SASLAuthenticator; 
+import org.apache.geronimo.javamail.util.CommandFailedException;      
+import org.apache.geronimo.javamail.util.InvalidCommandException;      
+
+/**
+ * Base class for all mail Store/Transport connection.  Centralizes management
+ * of a lot of common connection handling.  Actual protcol-specific 
+ * functions are handled at the subclass level. 
+ */
+public class MailConnection {
+    /**
+     * constants for EOL termination
+     */
+    protected static final char CR = '\r';
+    protected static final char LF = '\n';
+
+    /**
+     * property keys for protocol properties.
+     */
+    protected static final String MAIL_AUTH = "auth";
+    protected static final String MAIL_PORT = "port";
+    protected static final String MAIL_LOCALHOST = "localhost";
+    protected static final String MAIL_STARTTLS_ENABLE = "starttls.enable";
+    protected static final String MAIL_SSL_ENABLE = "ssl.enable";
+    protected static final String MAIL_TIMEOUT = "timeout";
+    protected static final String MAIL_SASL_ENABLE = "sasl.enable";
+    protected static final String MAIL_SASL_REALM = "sasl.realm";
+    protected static final String MAIL_SASL_MECHANISMS = "sasl.mechanisms";
+    protected static final String MAIL_PLAIN_DISABLE = "auth.plain.disable";
+    protected static final String MAIL_LOGIN_DISABLE = "auth.login.disable";
+    protected static final String MAIL_FACTORY_CLASS = "socketFactory.class";
+    protected static final String MAIL_FACTORY_FALLBACK = "socketFactory.fallback";
+    protected static final String MAIL_FACTORY_PORT = "socketFactory.port";
+    protected static final String MAIL_SSL_FACTORY_CLASS = "SSLsocketFactory.class";
+    protected static final String MAIL_SSL_FACTORY_FALLBACK = "SSLsocketFactory.fallback";
+    protected static final String MAIL_SSL_FACTORY_PORT = "SSLsocketFactory.port";
+    protected static final String MAIL_LOCALADDRESS = "localaddress";
+    protected static final String MAIL_LOCALPORT = "localport";
+    protected static final String MAIL_ENCODE_TRACE = "encodetrace";
+
+    protected static final int MIN_MILLIS = 1000 * 60;
+    protected static final int TIMEOUT = MIN_MILLIS * 5;
+    protected static final String DEFAULT_MAIL_HOST = "localhost";
+
+    protected static final String CAPABILITY_STARTTLS = "STARTTLS";
+
+    protected static final String AUTHENTICATION_PLAIN = "PLAIN";
+    protected static final String AUTHENTICATION_LOGIN = "LOGIN";
+    protected static final String AUTHENTICATION_CRAMMD5 = "CRAM-MD5";
+    protected static final String AUTHENTICATION_DIGESTMD5 = "DIGEST-MD5";
+    
+    // The mail Session we're associated with
+    protected Session session; 
+    // The protocol we're implementing 
+    protected String protocol; 
+    // There are usually SSL and non-SSL versions of these protocols.  This 
+    // indicates which version we're using.
+    protected boolean sslConnection; 
+    // This is the default port we should be using for making a connection.  Each 
+    // protocol (and each ssl version of the protocol) normally has a different default that 
+    // should be used. 
+    protected int defaultPort; 
+    
+    // a wrapper around our session to provide easier lookup of protocol 
+    // specific property values 
+    protected ProtocolProperties props; 
+    
+    // The target server host 
+    protected String serverHost;
+    // The target server port 
+    protected int serverPort; 
+    
+    // the connection socket...can be a plain socket or SSLSocket, if TLS is being used.
+    protected Socket socket;
+    
+    // our local host name
+    protected InetAddress localAddress;
+    // our local port value 
+    protected int localPort; 
+    // our timeout value 
+    protected int timeout; 
+    
+    // our login username 
+    protected String username; 
+    // our login password 
+    protected String password; 
+    // our SASL security realm 
+    protected String realm; 
+    // our authorization id 
+    protected String authid; 
+    
+    // input stream used to read data.  If Sasl is in use, this might be other than the
+    // direct access to the socket input stream.
+    protected InputStream inputStream;
+    // the other end of the connection pipeline.
+    protected OutputStream outputStream;
+
+    // our session provided debug output stream.
+    protected PrintStream debugStream;
+    // our debug flag (passed from the hosting transport)
+    protected boolean debug;
+
+    // list of authentication mechanisms supported by the server
+    protected List authentications;
+    // map of server extension arguments
+    protected Map capabilities;        
+    // property list of authentication mechanisms
+    protected List mechanisms; 
+    
+
+    
+    protected MailConnection(ProtocolProperties props) 
+    {
+        // this is our properties retriever utility, which will look up 
+        // properties based on the appropriate "mail.protocol." prefix. 
+        // this also holds other information we might need for access, such as 
+        // the protocol name and the Session; 
+        this.props = props; 
+        this.protocol = props.getProtocol(); 
+        this.session = props.getSession(); 
+        this.sslConnection = props.getSSLConnection(); 
+        this.defaultPort = props.getDefaultPort(); 
+        
+        // initialize our debug settings from the session 
+        debug = session.getDebug(); 
+        debugStream = session.getDebugOut();
+    }
+    
+    
+    /**
+     * Create a transport connection object and connect it to the
+     * target server.
+     *
+     * @exception MessagingException
+     */
+    protected void getConnection() throws IOException, MessagingException
+    {
+        // We might have been passed a socket to connect with...if not, we need to create one of the correct type.
+        if (socket == null) {
+            // get the connection properties that control how we set this up. 
+            getConnectionProperties(); 
+            // if this is the SSL version of the protocol, we start with an SSLSocket
+            if (sslConnection) {
+                getConnectedSSLSocket();
+            }
+            else
+            {
+                getConnectedSocket();
+            }
+        }
+        // if we already have a socket, get some information from it and override what we've been passed.
+        else {
+            localPort = socket.getPort();
+            localAddress = socket.getInetAddress();
+        }
+        
+        // now set up the input/output streams.
+        getConnectionStreams(); 
+    }
+
+    /**
+     * Get common connection properties before creating a connection socket. 
+     */
+    protected void getConnectionProperties() {
+
+        // there are several protocol properties that can be set to tune the created socket.  We need to
+        // retrieve those bits before creating the socket.
+        timeout = props.getIntProperty(MAIL_TIMEOUT, -1);
+        localAddress = null;
+        // see if we have a local address override.
+        String localAddrProp = props.getProperty(MAIL_LOCALADDRESS);
+        if (localAddrProp != null) {
+            try {
+                localAddress = InetAddress.getByName(localAddrProp);
+            } catch (UnknownHostException e) {
+                // not much we can do if this fails. 
+            }
+        }
+
+        // check for a local port...default is to allow socket to choose.
+        localPort = props.getIntProperty(MAIL_LOCALPORT, 0);
+    }
+    
+
+    /**
+     * Creates a connected socket
+     *
+     * @exception MessagingException
+     */
+    protected void getConnectedSocket() throws IOException {
+        if (debug) {
+            debugOut("Attempting plain socket connection to server " + serverHost + ":" + serverPort);
+        }
+
+        // check the properties that control how we connect. 
+        getConnectionProperties(); 
+
+        // the socket factory can be specified via a session property.  By default, we just directly
+        // instantiate a socket without using a factory.
+        String socketFactory = props.getProperty(MAIL_FACTORY_CLASS);
+
+        // make sure the socket is nulled out to start 
+        socket = null;
+
+        // if there is no socket factory defined (normal), we just create a socket directly.
+        if (socketFactory == null) {
+            socket = new Socket(serverHost, serverPort, localAddress, localPort);
+        }
+
+        else {
+            try {
+                int socketFactoryPort = props.getIntProperty(MAIL_FACTORY_PORT, -1);
+
+                // we choose the port used by the socket based on overrides.
+                Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort);
+
+                // use the current context loader to resolve this.
+                ClassLoader loader = Thread.currentThread().getContextClassLoader();
+                Class factoryClass = loader.loadClass(socketFactory);
+
+                // done indirectly, we need to invoke the method using reflection.
+                // This retrieves a factory instance.
+                Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
+                Object defFactory = getDefault.invoke(new Object(), new Object[0]);
+
+                // now that we have the factory, there are two different createSocket() calls we use,
+                // depending on whether we have a localAddress override.
+
+                if (localAddress != null) {
+                    // retrieve the createSocket(String, int, InetAddress, int) method.
+                    Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
+                    Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+                    Object[] createSocketArgs = new Object[] { serverHost, portArg, localAddress, new Integer(localPort) };
+                    socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
+                }
+                else {
+                    // retrieve the createSocket(String, int) method.
+                    Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
+                    Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+                    Object[] createSocketArgs = new Object[] { serverHost, portArg };
+                    socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
+                }
+            } catch (Throwable e) {
+                // if a socket factor is specified, then we may need to fall back to a default.  This behavior
+                // is controlled by (surprise) more session properties.
+                if (props.getBooleanProperty(MAIL_FACTORY_FALLBACK, false)) {
+                    if (debug) {
+                        debugOut("First plain socket attempt failed, falling back to default factory", e);
+                    }
+                    socket = new Socket(serverHost, serverPort, localAddress, localPort);
+                }
+                // we have an exception.  We're going to throw an IOException, which may require unwrapping
+                // or rewrapping the exception.
+                else {
+                    // we have an exception from the reflection, so unwrap the base exception
+                    if (e instanceof InvocationTargetException) {
+                        e = ((InvocationTargetException)e).getTargetException();
+                    }
+
+                    if (debug) {
+                        debugOut("Plain socket creation failure", e);
+                    }
+
+                    // throw this as an IOException, with the original exception attached.
+                    IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort);
+                    ioe.initCause(e);
+                    throw ioe;
+                }
+            }
+        }
+        // if we have a timeout value, set that before returning 
+        if (timeout >= 0) {
+            socket.setSoTimeout(timeout);
+        }
+    }
+
+
+    /**
+     * Creates a connected SSL socket for an initial SSL connection.
+     *
+     * @exception MessagingException
+     */
+    protected void getConnectedSSLSocket() throws IOException {
+        if (debug) {
+            debugOut("Attempting SSL socket connection to server " + serverHost + ":" + serverPort);
+        }
+        // the socket factory can be specified via a protocol property, a session property, and if all else
+        // fails (which it usually does), we fall back to the standard factory class.
+        String socketFactory = props.getProperty(MAIL_SSL_FACTORY_CLASS, props.getSessionProperty(MAIL_SSL_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory"));
+
+        // make sure this is null 
+        socket = null;
+
+        // we'll try this with potentially two different factories if we're allowed to fall back.
+        boolean fallback = props.getBooleanProperty(MAIL_SSL_FACTORY_FALLBACK, false);
+
+        while (true) {
+            try {
+                if (debug) {
+                    debugOut("Creating SSL socket using factory " + socketFactory);
+                }
+
+                int socketFactoryPort = props.getIntProperty(MAIL_SSL_FACTORY_PORT, -1);
+
+                // we choose the port used by the socket based on overrides.
+                Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort);
+
+                // use the current context loader to resolve this.
+                ClassLoader loader = Thread.currentThread().getContextClassLoader();
+                Class factoryClass = loader.loadClass(socketFactory);
+
+                // done indirectly, we need to invoke the method using reflection.
+                // This retrieves a factory instance.
+                Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
+                Object defFactory = getDefault.invoke(new Object(), new Object[0]);
+
+                // now that we have the factory, there are two different createSocket() calls we use,
+                // depending on whether we have a localAddress override.
+
+                if (localAddress != null) {
+                    // retrieve the createSocket(String, int, InetAddress, int) method.
+                    Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
+                    Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+                    Object[] createSocketArgs = new Object[] { serverHost, portArg, localAddress, new Integer(localPort) };
+                    socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
+                    break; 
+                }
+                else {
+                    // retrieve the createSocket(String, int) method.
+                    Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
+                    Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+                    Object[] createSocketArgs = new Object[] { serverHost, portArg };
+                    socket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
+                    break; 
+                }
+            } catch (Throwable e) {
+                // if we're allowed to fallback, then use the default factory and try this again.  We only
+                // allow this to happen once.
+                if (fallback) {
+                    if (debug) {
+                        debugOut("First attempt at creating SSL socket failed, falling back to default factory");
+                    }
+                    socketFactory = "javax.net.ssl.SSLSocketFactory";
+                    fallback = false;
+                    continue;
+                }
+                // we have an exception.  We're going to throw an IOException, which may require unwrapping
+                // or rewrapping the exception.
+                else {
+                    // we have an exception from the reflection, so unwrap the base exception
+                    if (e instanceof InvocationTargetException) {
+                        e = ((InvocationTargetException)e).getTargetException();
+                    }
+
+                    if (debug) {
+                        debugOut("Failure creating SSL socket", e);
+                    }
+
+                    // throw this as an IOException, with the original exception attached.
+                    IOException ioe = new IOException("Error connecting to " + serverHost + ", " + serverPort);
+                    ioe.initCause(e);
+                    throw ioe;
+                }
+            }
+        }
+        // and set the timeout value 
+        if (timeout >= 0) {
+            socket.setSoTimeout(timeout);
+        }
+    }
+
+
+    /**
+     * Switch the connection to using TLS level security,
+     * switching to an SSL socket.
+     */
+    protected void getConnectedTLSSocket() throws MessagingException {
+        if (debug) {
+            debugOut("Attempting to negotiate STARTTLS with server " + serverHost);
+        }
+     	// it worked, now switch the socket into TLS mode
+     	try {
+
+            // we use the same target and port as the current connection.
+            String host = socket.getInetAddress().getHostName();
+            int port = socket.getPort();
+
+            // the socket factory can be specified via a session property.  By default, we use
+            // the native SSL factory.
+            String socketFactory = props.getProperty(MAIL_SSL_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
+
+            // use the current context loader to resolve this.
+            ClassLoader loader = Thread.currentThread().getContextClassLoader();
+            Class factoryClass = loader.loadClass(socketFactory);
+
+            // done indirectly, we need to invoke the method using reflection.
+            // This retrieves a factory instance.
+            Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
+            Object defFactory = getDefault.invoke(new Object(), new Object[0]);
+
+            // now we need to invoke createSocket()
+            Class[] createSocketSig = new Class[] { Socket.class, String.class, Integer.TYPE, Boolean.TYPE };
+            Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+            Object[] createSocketArgs = new Object[] { socket, host, new Integer(port), Boolean.TRUE };
+
+            // and finally create the socket
+            Socket sslSocket = (Socket)createSocket.invoke(defFactory, createSocketArgs);
+
+            // if this is an instance of SSLSocket (very common), try setting the protocol to be
+            // "TLSv1".  If this is some other class because of a factory override, we'll just have to
+            // accept that things will work.
+            if (sslSocket instanceof SSLSocket) {
+                ((SSLSocket)sslSocket).setEnabledProtocols(new String[] {"TLSv1"} );
+                ((SSLSocket)sslSocket).setUseClientMode(true);
+                ((SSLSocket)sslSocket).startHandshake();
+            }
+
+
+            // this is our active socket now
+            socket = sslSocket;
+            getConnectionStreams(); 
+     	}
+        catch (Exception e) {
+            if (debug) {
+                debugOut("Failure attempting to convert connection to TLS", e);
+            }
+     	    throw new MessagingException("Unable to convert connection to SSL", e);
+     	}
+    }
+    
+    
+    /**
+     * Set up the input and output streams for server communications once the 
+     * socket connection has been made. 
+     * 
+     * @exception MessagingException
+     */
+    protected void getConnectionStreams() throws MessagingException, IOException {
+        // and finally, as a last step, replace our input streams with the secure ones.
+        // now set up the input/output streams.
+        inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug, props.getBooleanProperty(
+                MAIL_ENCODE_TRACE, false));
+        outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug, props.getBooleanProperty(
+                MAIL_ENCODE_TRACE, false));
+    }
+    
+
+    /**
+     * Close the server connection at termination.
+     */
+    public void closeServerConnection()
+    {
+        try {
+            socket.close();
+        } catch (IOException ignored) {
+        }
+
+        socket = null;
+        inputStream = null;
+        outputStream = null;
+    }
+    
+    
+    /**
+     * Verify that we have a good connection before 
+     * attempting to send a command. 
+     * 
+     * @exception MessagingException
+     */
+    protected void checkConnected() throws MessagingException {
+        if (socket == null || !socket.isConnected()) {
+            throw new MessagingException("no connection");
+        }
+    }
+
+
+    /**
+     * Retrieve the SASL realm used for DIGEST-MD5 authentication.
+     * This will either be explicitly set, or retrieved using the
+     * mail.imap.sasl.realm session property.
+     *
+     * @return The current realm information (which can be null).
+     */
+    public String getSASLRealm() {
+        // if the realm is null, retrieve it using the realm session property.
+        if (realm == null) {
+            realm = props.getProperty(MAIL_SASL_REALM);
+        }
+        return realm;
+    }
+
+
+    /**
+     * Explicitly set the SASL realm used for DIGEST-MD5 authenticaiton.
+     *
+     * @param name   The new realm name.
+     */
+    public void setSASLRealm(String name) {
+        realm = name;
+    }
+
+
+    /**
+     * Get a list of the SASL mechanisms we're configured to accept.
+     *
+     * @return A list of mechanisms we're allowed to use.
+     */
+    protected List getSaslMechanisms() {
+        if (mechanisms == null) {
+            mechanisms = new ArrayList();
+            String mechList = props.getProperty(MAIL_SASL_MECHANISMS);
+            if (mechList != null) {
+                // the mechanisms are a blank or comma-separated list
+                StringTokenizer tokenizer = new StringTokenizer(mechList, " ,");
+
+                while (tokenizer.hasMoreTokens()) {
+                    String mech = tokenizer.nextToken().toUpperCase();
+                    mechanisms.add(mech);
+                }
+            }
+        }
+        return mechanisms;
+    }
+    
+    
+    /**
+     * Get the list of authentication mechanisms the server
+     * is supposed to support. 
+     * 
+     * @return A list of the server supported authentication 
+     *         mechanisms.
+     */
+    protected List getServerMechanisms() {
+        return authentications; 
+    }
+    
+    
+    /**
+     * Merge the configured SASL mechanisms with the capabilities that the 
+     * server has indicated it supports, returning a merged list that can 
+     * be used for selecting a mechanism. 
+     * 
+     * @return A List representing the intersection of the configured list and the 
+     *         capabilities list.
+     */
+    protected List selectSaslMechanisms() {
+        List configured = getSaslMechanisms(); 
+        List supported = getServerMechanisms(); 
+        
+        // if not restricted, then we'll select from anything supported. 
+        if (configured.isEmpty()) {
+            return supported; 
+        }
+        
+        List merged = new ArrayList(); 
+        
+        // we might need a subset of the supported ones 
+        for (int i = 0; i < configured.size(); i++) {
+            // if this is in both lists, add to the merged one. 
+            String mech = (String)configured.get(i); 
+            if (supported.contains(mech)) {
+                merged.add(mech); 
+            }
+        }
+        return merged; 
+    }
+
+
+    /**
+     * Process SASL-type authentication.
+     *
+     * @return An authenticator to process the login challenge/response handling.    
+     * @exception MessagingException
+     */
+    protected ClientAuthenticator getLoginAuthenticator() throws MessagingException {
+        
+        // get the list of mechanisms we're allowed to use. 
+        List mechs = selectSaslMechanisms(); 
+
+        try {
+            String[] mechArray = (String[])mechs.toArray(new String[0]); 
+            // create a SASLAuthenticator, if we can.  A failure likely indicates we're not 
+            // running on a Java 5 VM, and the Sasl API isn't available. 
+            return new SASLAuthenticator(mechArray, session.getProperties(), protocol, serverHost, getSASLRealm(), authid, username, password); 
+        } catch (Throwable e) {
+        }
+        
+
+        // now go through the progression of mechanisms we support, from the most secure to the
+        // least secure.
+
+        if (mechs.contains(AUTHENTICATION_DIGESTMD5)) {
+            return new DigestMD5Authenticator(serverHost, username, password, getSASLRealm());
+        }
+        else if (mechs.contains(AUTHENTICATION_CRAMMD5)) {
+            return new CramMD5Authenticator(username, password);
+        }
+        else if (mechs.contains(AUTHENTICATION_LOGIN)) {
+            return new LoginAuthenticator(username, password);
+        }
+        else if (mechs.contains(AUTHENTICATION_PLAIN)) {
+            return new PlainAuthenticator(username, password);
+        }
+        else {
+            // can't find a mechanism we support in common
+            return null;  
+        }
+    }
+    
+    
+    /**
+     * Internal debug output routine.
+     *
+     * @param value  The string value to output.
+     */
+    protected void debugOut(String message) {
+        debugStream.println("IMAPStore DEBUG: " + message);
+    }
+
+    /**
+     * Internal debugging routine for reporting exceptions.
+     *
+     * @param message A message associated with the exception context.
+     * @param e       The received exception.
+     */
+    protected void debugOut(String message, Throwable e) {
+        debugOut("Received exception -> " + message);
+        debugOut("Exception message -> " + e.getMessage());
+        e.printStackTrace(debugStream);
+    }
+    
+    
+    /**
+     * 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) {
+        return capabilities.containsKey(capability); 
+    }
+    
+    /**
+     * Get the capabilities map. 
+     * 
+     * @return The capabilities map for the connection. 
+     */
+    public Map getCapabilities() {
+        return capabilities; 
+    }
+    
+    
+    /**
+     * Test if the server supports a given mechanism. 
+     * 
+     * @param mech   The mechanism name.
+     * 
+     * @return true if the server has asserted support for the named 
+     *         mechanism.
+     */
+    public boolean supportsMechanism(String mech) {
+        return authentications.contains(mech); 
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ProtocolProperties.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ProtocolProperties.java
new file mode 100644
index 0000000..841681f
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ProtocolProperties.java
@@ -0,0 +1,276 @@
+/**
+ * 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.util;
+ 
+import java.util.Properties;
+
+import javax.mail.Session;
+
+/**
+ * Interface for providing access to protocol specific properties to 
+ * utility classes. 
+ */
+public class ProtocolProperties {
+    // the protocol we're working with. 
+    protected String protocol; 
+    // a preconstructed prefix string to reduce concatenation operations.  
+    protected String protocolPrefix; 
+    // the Session that's the source of all of the properties 
+    protected Session session; 
+    // the sslConnection property.  This indicates this protocol is to use SSL for 
+    // all communications with the server. 
+    protected boolean sslConnection;
+    // the default port property.  The default port differs with the protocol 
+    // and the sslConnection property. 
+    protected int defaultPort; 
+    
+    
+    public ProtocolProperties(Session session, String protocol, boolean sslConnection, int defaultPort) {
+        this.session = session; 
+        this.protocol = protocol; 
+        this.sslConnection = sslConnection; 
+        this.defaultPort = defaultPort; 
+        // this helps avoid a lot of concatentates when retrieving properties. 
+        protocolPrefix = "mail." + protocol + ".";
+    }
+    
+    
+    /**
+     * Retrieve the Session associated with this property bundle instance.
+     * 
+     * @return A Session object that's the source of the accessed properties. 
+     */
+    public Session getSession() {
+        return session; 
+    }
+    
+    
+    /**
+     * Retrieve the name of the protocol used to access properties.
+     * 
+     * @return The protocol String name. 
+     */
+    public String getProtocol() {
+        return protocol; 
+    }
+    
+    
+    /**
+     * Retrieve the SSL Connection flag for this protocol; 
+     * 
+     * @return true if an SSL connection is required, false otherwise. 
+     */
+    public boolean getSSLConnection() {
+        return sslConnection; 
+    }
+    
+    
+    /**
+     * Return the default port to use with this connection.
+     * 
+     * @return The default port value. 
+     */
+    public int getDefaultPort() {
+        return defaultPort; 
+    }
+    
+    
+    /**
+     * Get a property associated with this mail protocol.
+     *
+     * @param name   The name of the property.
+     *
+     * @return The property value (returns null if the property has not been set).
+     */
+    public String getProperty(String name) {
+        // the name we're given is the least qualified part of the name.  
+        // We construct the full property name
+        // using the protocol
+        String fullName = protocolPrefix + name;
+        return session.getProperty(fullName);
+    }
+
+    /**
+     * Get a property associated with this mail session.  Returns
+     * the provided default if it doesn't exist.
+     *
+     * @param name   The name of the property.
+     * @param defaultValue
+     *               The default value to return if the property doesn't exist.
+     *
+     * @return The property value (returns defaultValue if the property has not been set).
+     */
+    public String getProperty(String name, String defaultValue) {
+        // the name we're given is the least qualified part of the name.  
+        // We construct the full property name
+        // using the protocol
+        String fullName = protocolPrefix + name;
+        String value = session.getProperty(fullName);
+        if (value == null) {
+            value = defaultValue; 
+        }
+        return value; 
+    }
+
+
+    /**
+     * Get a property associated with this mail session as an integer value.  Returns
+     * the default value if the property doesn't exist or it doesn't have a valid int value.
+     *
+     * @param name   The name of the property.
+     * @param defaultValue
+     *               The default value to return if the property doesn't exist.
+     *
+     * @return The property value converted to an int.
+     */
+    public int getIntProperty(String name, int defaultValue)
+    {
+        // retrieve the property 
+        String value = getProperty(name); 
+        // return the default value if not set. 
+        if (value == null) {
+            return defaultValue; 
+        }
+        return Integer.parseInt(value); 
+    }
+    
+
+    /**
+     * Get a property associated with this mail session as an boolean value.  Returns
+     * the default value if the property doesn't exist or it doesn't have a valid int value.
+     *
+     * @param name   The name of the property.
+     * @param defaultValue
+     *               The default value to return if the property doesn't exist.
+     *
+     * @return The property value converted to a boolean
+     */
+    public boolean getBooleanProperty(String name, boolean defaultValue)
+    {
+        // retrieve the property 
+        String value = getProperty(name); 
+        // return the default value if not set. 
+        if (value == null) {
+            return defaultValue; 
+        }
+        // just do a single test for true. 
+        if ("true".equals(value)) {
+            return true; 
+        }
+        // return false for anything other than true
+        return false; 
+    }
+    
+    
+    /**
+     * Get a property associated with this mail session.  Session 
+     * properties all begin with "mail."
+     *
+     * @param name   The name of the property.
+     *
+     * @return The property value (returns null if the property has not been set).
+     */
+    public String getSessionProperty(String name) {
+        // the name we're given is the least qualified part of the name.  
+        // We construct the full property name
+        // using the protocol
+        String fullName = "mail." + name;
+        return session.getProperty(fullName);
+    }
+
+    /**
+     * Get a property associated with this mail session.  Returns
+     * the provided default if it doesn't exist.
+     *
+     * @param name   The name of the property.
+     * @param defaultValue
+     *               The default value to return if the property doesn't exist.
+     *
+     * @return The property value (returns defaultValue if the property has not been set).
+     */
+    public String getSessionProperty(String name, String defaultValue) {
+        // the name we're given is the least qualified part of the name.  
+        // We construct the full property name
+        // using the protocol
+        String fullName = "mail." + name;
+        String value = session.getProperty(fullName);
+        if (value == null) {
+            value = defaultValue; 
+        }
+        return value; 
+    }
+
+
+    /**
+     * Get a property associated with this mail session as an integer value.  Returns
+     * the default value if the property doesn't exist or it doesn't have a valid int value.
+     *
+     * @param name   The name of the property.
+     * @param defaultValue
+     *               The default value to return if the property doesn't exist.
+     *
+     * @return The property value converted to an int.
+     */
+    public int getIntSessionProperty(String name, int defaultValue)
+    {
+        // retrieve the property 
+        String value = getSessionProperty(name); 
+        // return the default value if not set. 
+        if (value == null) {
+            return defaultValue; 
+        }
+        return Integer.parseInt(value); 
+    }
+    
+
+    /**
+     * Get a property associated with this mail session as an boolean value.  Returns
+     * the default value if the property doesn't exist or it doesn't have a valid int value.
+     *
+     * @param name   The name of the property.
+     * @param defaultValue
+     *               The default value to return if the property doesn't exist.
+     *
+     * @return The property value converted to a boolean
+     */
+    public boolean getBooleanSessionProperty(String name, boolean defaultValue)
+    {
+        // retrieve the property 
+        String value = getSessionProperty(name); 
+        // return the default value if not set. 
+        if (value == null) {
+            return defaultValue; 
+        }
+        // just do a single test for true. 
+        if ("true".equals(value)) {
+            return true; 
+        }
+        // return false for anything other than true
+        return false; 
+    }
+    
+    /**
+     * Get the complete set of properties associated with this Session.
+     * 
+     * @return The Session properties bundle. 
+     */
+    public Properties getProperties() {
+        return session.getProperties(); 
+    }
+    
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ResponseFormatException.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ResponseFormatException.java
new file mode 100644
index 0000000..e533313
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ResponseFormatException.java
@@ -0,0 +1,34 @@
+/**
+ * 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.util;
+
+import javax.mail.MessagingException; 
+
+public class ResponseFormatException extends MessagingException {
+    public ResponseFormatException() {
+        super();
+    }
+
+    public ResponseFormatException(String message) {
+        super(message);
+    }
+
+    public ResponseFormatException(String message, Exception cause) {
+        super(message, cause);
+    }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers
index fb8ee77..15287a7 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers
@@ -35,4 +35,6 @@
 protocol=nntp-post; type=transport; class=org.apache.geronimo.javamail.transport.nntp.NNTPTransport; vendor=Apache Software Foundation; version=1.0

 protocol=nntp; type=store; class=org.apache.geronimo.javamail.store.nntp.NNTPStore; vendor=Apache Software Foundation; version=1.0

 protocol=pop3; type=store; class=org.apache.geronimo.javamail.store.pop3.POP3Store; vendor=Apache Software Foundation; version=1.0

+protocol=imap; type=store; class=org.apache.geronimo.javamail.store.imap.IMAPStore; vendor=Apache Software Foundation; version=1.0

+protocol=imaps; type=store; class=org.apache.geronimo.javamail.store.imap.IMAPSSLStore; vendor=Apache Software Foundation; version=1.0

 

diff --git a/geronimo-javamail_1.4/pom.xml b/geronimo-javamail_1.4/pom.xml
index e9093b8..4788f7b 100644
--- a/geronimo-javamail_1.4/pom.xml
+++ b/geronimo-javamail_1.4/pom.xml
@@ -59,7 +59,7 @@
             <dependency>
                 <groupId>org.apache.geronimo.specs</groupId>
                 <artifactId>geronimo-javamail_1.4_spec</artifactId>
-                <version>1.1</version>
+                <version>1.2-SNAPSHOT</version>
             </dependency>
 
         </dependencies>
@@ -69,6 +69,31 @@
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>enforce</goal>
+                        </goals>
+                        <configuration>
+                            <rules>
+                                <!-- Allow any Java >= 1.5, but not 1.6 or above -->
+                                <requireJavaVersion>
+                                    <version>[1.5,1.6)</version>
+                                </requireJavaVersion>
+                                
+                                <!-- Allow any Maven >= 2.0.5 -->
+                                <requireMavenVersion>
+                                    <version>[2.0.5,)</version>
+                                </requireMavenVersion>
+                            </rules>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
                     <source>1.4</source>