You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by jl...@apache.org on 2020/10/07 15:58:41 UTC

svn commit: r1882306 [5/17] - in /geronimo/javamail/trunk/geronimo-javamail_1.6: ./ geronimo-javamail_1.6_mail/ geronimo-javamail_1.6_mail/src/ geronimo-javamail_1.6_mail/src/site/ geronimo-javamail_1.6_provider/ geronimo-javamail_1.6_provider/src/ ger...

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.java Wed Oct  7 15:58:39 2020
@@ -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 = 143;
+    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 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 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; 
+    }
+}

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java Wed Oct  7 15:58:39 2020
@@ -0,0 +1,303 @@
+/**
+ * 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 {
+        /**
+         * Perform a sort comparison to order two Right objects.
+         * The sort is performed using the string value. 
+         * 
+         * @param o1     The left comparator
+         * @param o2     The right comparator.
+         * 
+         * @return 0 if the two items have equal ordering, -1 if the 
+         *         left item is lower, 1 if the left item is greater.
+         */
+        public int compare(Object o1, Object o2) {
+            // compare on the string value 
+            String left = o1.toString(); 
+            return left.compareTo(o2.toString()); 
+        }
+    }
+    
+}

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPACLResponse.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPACLResponse.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPACLResponse.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPACLResponse.java Wed Oct  7 15:58:39 2020
@@ -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); 
+    }
+}

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.java Wed Oct  7 15:58:39 2020
@@ -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.BODY), 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(BODY, 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);
+    }
+}
+

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodySection.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodySection.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodySection.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodySection.java Wed Oct  7 15:58:39 2020
@@ -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); 
+    }
+}
+

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java Wed Oct  7 15:58:39 2020
@@ -0,0 +1,228 @@
+/**
+ * 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;
+
+import org.apache.geronimo.javamail.util.ResponseFormatException;
+
+
+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 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.
+        if (source.peek(true).getType() == '(') {
+            source.checkLeftParen();
+            disposition.setDisposition(source.readString());
+            disposition.setParameterList(source.readParameterList());
+            source.checkRightParen();
+        } else if (source.peek(true) == IMAPResponseTokenizer.NIL) {
+            source.next();
+        } else {
+            throw new ResponseFormatException("Expecting NIL or '(' in response");
+        }
+
+        // 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();
+        }
+
+        // step over the closing paren
+        source.next();
+    }
+
+
+    /**
+     * 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"); 
+    }
+}
+

Added: geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCapabilityResponse.java
URL: http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCapabilityResponse.java?rev=1882306&view=auto
==============================================================================
--- geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCapabilityResponse.java (added)
+++ geronimo/javamail/trunk/geronimo-javamail_1.6/geronimo-javamail_1.6_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCapabilityResponse.java Wed Oct  7 15:58:39 2020
@@ -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;
+    }
+}
+