Add seed code for javamail 1.5

git-svn-id: https://svn.apache.org/repos/asf/geronimo/javamail/trunk@1743402 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/geronimo-javamail_1.5/.travis.yml b/geronimo-javamail_1.5/.travis.yml
new file mode 100644
index 0000000..ca8fc8c
--- /dev/null
+++ b/geronimo-javamail_1.5/.travis.yml
@@ -0,0 +1,8 @@
+language: java
+
+jdk:
+  - openjdk6
+  - openjdk7
+  - oraclejdk7
+  - oraclejdk8
+  
diff --git a/geronimo-javamail_1.5/LICENSE b/geronimo-javamail_1.5/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/geronimo-javamail_1.5/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/geronimo-javamail_1.5/NOTICE b/geronimo-javamail_1.5/NOTICE
new file mode 100644
index 0000000..0505639
--- /dev/null
+++ b/geronimo-javamail_1.5/NOTICE
@@ -0,0 +1,8 @@
+
+Geronimo JavaMail 1.4
+Copyright 2003-2011 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+
diff --git a/geronimo-javamail_1.5/README.txt b/geronimo-javamail_1.5/README.txt
new file mode 100644
index 0000000..cad0219
--- /dev/null
+++ b/geronimo-javamail_1.5/README.txt
@@ -0,0 +1,19 @@
+
+Building
+========
+
+To build you will need:
+
+ * J2SE SDK 1.5+ (http://java.sun.com/j2se/1.5/)
+ * Maven 2.0.7+ (http://maven.apache.org)
+
+To build all changes incrementally:
+
+    mvn install
+
+To perform clean builds, which are sometimes needed after some changes to the
+source tree:
+
+    mvn clean install
+
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_mail/pom.xml b/geronimo-javamail_1.5/geronimo-javamail_1.4_mail/pom.xml
new file mode 100644
index 0000000..c011a09
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_mail/pom.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+
+<!-- $Rev$ $Date$ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.geronimo.javamail</groupId>
+        <artifactId>geronimo-javamail_1.4</artifactId>
+        <version>1.9.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>geronimo-javamail_1.4_mail</artifactId>
+    <packaging>bundle</packaging>
+    <name>Geronimo JavaMail 1.4 :: Mail</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.geronimo.javamail</groupId>
+            <artifactId>geronimo-javamail_1.4_provider</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-javamail_1.4_spec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-activation_1.1_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>4.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <version>4.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-osgi-locator</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <!-- NOTE:  The shade build step is an important step in getting the bundle
+                     built correctly.  This step copies all of the OSGI-INF/services and META-INF/*
+                     resources into the local build target, and then the bundle plugin merges those
+                     resources with the class files pulled from the dependency jars to create the
+                     final result.  Without this extra step, only the class files make it into the
+                     final bundle.-->
+
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <artifactSet>
+                                <includes>
+                                    <include>org.apache.geronimo.specs:geronimo-javamail_1.4_spec</include>
+                                    <include>org.apache.geronimo.javamail:geronimo-javamail_1.4_provider</include>
+                                    <include>${project.groupId}:${project.artifactId}</include>
+                                </includes>
+                            </artifactSet>
+                            <filters>
+                                <filter>
+                                    <artifact>org.apache.geronimo.specs:geronimo-javamail_1.4_spec</artifact>
+                                    <!-- All of the class files need to be excluded to avoid bnd errors about split packages -->
+                                    <excludes>
+                                        <exclude>javax/**</exclude>
+                                        <exclude>org/apache/geronimo/mail/*.class</exclude>
+                                        <exclude>org/apache/geronimo/osgi/**</exclude>
+                                    </excludes>
+                                </filter>
+                                <filter>
+                                    <artifact>org.apache.geronimo.javamail:geronimo-javamail_1.4_provider</artifact>
+                                    <excludes>
+                                        <exclude>org/apache/geronimo/javamail/**</exclude>
+                                        <exclude>org/apache/geronimo/osgi/**</exclude>
+                                    </excludes>
+                                </filter>
+                            </filters>
+                            <promoteTransitiveDependencies>true</promoteTransitiveDependencies>
+                            <createDependencyReducedPom>true</createDependencyReducedPom>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>${project.groupId}.${project.artifactId};singleton=true</Bundle-SymbolicName>
+                        <Specification-Title>JSR-919 Javamail API 1.4 merged bundle</Specification-Title>
+                        <Specification-Vendor>Sun Microsystems, Inc.</Specification-Vendor>
+                        <Specification-Version>1.4</Specification-Version>
+                        <Private-Package>
+                            org.apache.geronimo.osgi.locator,
+                            org.apache.geronimo.mail,
+                            org.apache.geronimo.mail.util,
+                            org.apache.geronimo.javamail.util,
+                            org.apache.geronimo.javamail.authentication
+                        </Private-Package>
+                        <Export-Package>
+                            javax.mail*;version=1.4,
+                            org.apache.geronimo.javamail.handlers*;version=1.4,
+                            org.apache.geronimo.javamail.store*;version=1.4,
+                            org.apache.geronimo.javamail.transport*;version=1.4,
+                            org.apache.geronimo.mail.handlers*;version=1.4,
+                        </Export-Package>
+                        <Import-Package>
+                            javax.activation,
+                            javax.net,
+                            javax.mail*,
+                            javax.imageio*;resolution:="optional",
+                            javax.net.ssl*;resolution:="optional",
+                            javax.security.sasl*;resolution:="optional",
+                            javax.security.auth.callback*;resolution:="optional",
+                            org.apache.geronimo.osgi.registry.api;resolution:="optional",
+                            *
+                        </Import-Package>
+                        <Bundle-Activator>org.apache.geronimo.mail.Activator</Bundle-Activator>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_mail/src/site/site.xml b/geronimo-javamail_1.5/geronimo-javamail_1.4_mail/src/site/site.xml
new file mode 100644
index 0000000..80f99dd
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_mail/src/site/site.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+
+<!-- $Rev$ $Date$ -->
+
+<project name="${project.name}">
+    
+    <body>
+        
+        ${parentProject}
+        
+        ${modules}
+        
+        ${reports}
+        
+    </body>
+
+</project>
+
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/pom.xml b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/pom.xml
new file mode 100644
index 0000000..d0abd1e
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/pom.xml
@@ -0,0 +1,345 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+
+<!-- $Rev$ $Date: 2014-07-20 09:36:35 +0200 (So, 20. Jul 2014) 
+    $ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.geronimo.javamail</groupId>
+        <artifactId>geronimo-javamail_1.4</artifactId>
+        <version>1.9.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>geronimo-javamail_1.4_provider</artifactId>
+    <packaging>bundle</packaging>
+    <name>Geronimo JavaMail 1.4 :: Provider</name>
+
+    <properties>
+        <james.version>3.0-beta4</james.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-activation_1.1_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-javamail_1.4_spec</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>apache-mime4j-core</artifactId>
+                    <groupId>org.apache.james</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-james-imap-processor</artifactId>
+            <version>0.3</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-protocols-pop3</artifactId>
+            <version>${james.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-protocols-imap4</artifactId>
+            <version>${james.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-protocols-smtp</artifactId>
+            <version>${james.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-james-mailbox-memory</artifactId>
+            <version>0.5</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-james-mailbox-memory</artifactId>
+            <version>0.5</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-protocols-library</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <version>${james.version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-protocols-library</artifactId>
+            <scope>test</scope>
+            <version>${james.version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-filesystem-api</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <version>${james.version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-data-library</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <version>${james.version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-data-library</artifactId>
+            <scope>test</scope>
+            <version>${james.version}</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-data-file</artifactId>
+            <version>${james.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-queue-file</artifactId>
+            <version>${james.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-data-file</artifactId>
+            <version>${james.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-queue-file</artifactId>
+            <version>${james.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-queue-api</artifactId>
+            <version>${james.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-data-api</artifactId>
+            <version>${james.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>mail</artifactId>
+                    <groupId>javax.mail</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.2</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.7.2</version>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> 
+            <version>1.3.1</version> <scope>test</scope> </dependency> -->
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>${project.groupId}.${project.artifactId};singleton=true</Bundle-SymbolicName>
+                        <Specification-Title>JSR-919 Javamail API 1.4
+                            provider bundle</Specification-Title>
+                        <Specification-Vendor>Sun Microsystems, Inc.</Specification-Vendor>
+                        <Specification-Version>1.4</Specification-Version>
+                        <Private-Package>
+                            org.apache.geronimo.javamail.util,
+                            org.apache.geronimo.javamail.authentication
+                        </Private-Package>
+                        <Export-Package>
+                            org.apache.geronimo.javamail.store*;version=1.4,
+                            org.apache.geronimo.javamail.transport*;version=1.4,
+                            org.apache.geronimo.javamail.handlers*;version=1.4
+                        </Export-Package>
+                        <Import-Package>
+                            javax.activation,
+                            javax.net,
+                            javax.mail*,
+                            org.apache.geronimo.mail.util,
+                            javax.imageio*;resolution:="optional",
+                            javax.net.ssl*;resolution:="optional",
+                            javax.security.sasl*;resolution:="optional",
+                            javax.security.auth.callback*;resolution:="optional",
+                            org.apache.geronimo.mail.james.mime4j.codec
+                        </Import-Package>
+                    </instructions>
+                    <unpackBundle>true</unpackBundle>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/AuthenticatorFactory.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/AuthenticatorFactory.java
new file mode 100644
index 0000000..c4341c7
--- /dev/null
+++ b/geronimo-javamail_1.5/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.javamail.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(authId, username, password);
+        } else {
+            // can't find a mechanism we support in common
+            return null;
+        }
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.java
new file mode 100644
index 0000000..0404d36
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.java
@@ -0,0 +1,86 @@
+/*
+ * 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 javax.mail.MessagingException;
+
+/**
+ * Simplified version of the Java 5 SaslClient interface. This is used to
+ * implement a javamail authentication framework that mimics the Sasl framework
+ * on a 1.4.2 JVM. Only the methods required by the Javamail code are
+ * implemented here, but it should be a simple migration to the fuller SASL
+ * interface.
+ */
+public interface ClientAuthenticator {
+    /**
+     * Evaluate a challenge and return a response that can be sent back to the
+     * server. Bot the challenge information and the response information are
+     * "raw data", minus any special encodings used by the transport. For
+     * example, SMTP DIGEST-MD5 authentication protocol passes information as
+     * Base64 encoded strings. That encoding must be removed before calling
+     * evaluateChallenge() and the resulting respose must be Base64 encoced
+     * before transmission to the server.
+     * 
+     * It is the authenticator's responsibility to keep track of the state of
+     * the evaluations. That is, if the authentication process requires multiple
+     * challenge/response cycles, then the authenticator needs to keep track of
+     * context of the challenges.
+     * 
+     * @param challenge
+     *            The challenge data.
+     * 
+     * @return An appropriate response for the challenge data.
+     */
+
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException;
+
+    /**
+     * Indicates that the authenticator has data that should be sent when the
+     * authentication process is initiated. For example, the SMTP PLAIN
+     * authentication sends userid/password without waiting for a challenge
+     * response.
+     * 
+     * If this method returns true, then the initial response is retrieved using
+     * evaluateChallenge() passing null for the challenge information.
+     * 
+     * @return True if the challenge/response process starts with an initial
+     *         response on the client side.
+     */
+    public boolean hasInitialResponse();
+
+    /**
+     * Indicates whether the client believes the challenge/response sequence is
+     * now complete.
+     * 
+     * @return true if the client has evaluated what it believes to be the last
+     *         challenge, false if there are additional stages to evaluate.
+     */
+
+    public boolean isComplete();
+
+    /**
+     * Return the mechanism name implemented by this authenticator.
+     * 
+     * @return The string name of the authentication mechanism. This name should
+     *         match the names commonly used by the mail servers (e.g., "PLAIN",
+     *         "LOGIN", "DIGEST-MD5", etc.).
+     */
+    public String getMechanismName();
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java
new file mode 100644
index 0000000..91a6425
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java
@@ -0,0 +1,175 @@
+/*
+ * 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.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.mail.util.Hex;
+
+public class CramMD5Authenticator implements ClientAuthenticator {
+
+    // the user we're authenticating
+    protected String username;
+
+    // the user's password (the "shared secret")
+    protected String password;
+
+    // indicates whether we've gone through the entire challenge process.
+    protected boolean complete = false;
+
+    /**
+     * Main constructor.
+     *
+     * @param username
+     *            The login user name.
+     * @param password
+     *            The login password.
+     */
+    public CramMD5Authenticator(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    /**
+     * Respond to the hasInitialResponse query. This mechanism does not have an
+     * initial response.
+     *
+     * @return Always returns false.
+     */
+    public boolean hasInitialResponse() {
+        return false;
+    }
+
+    /**
+     * Indicate whether the challenge/response process is complete.
+     *
+     * @return True if the last challenge has been processed, false otherwise.
+     */
+    public boolean isComplete() {
+        return complete;
+    }
+
+    /**
+     * Retrieve the authenticator mechanism name.
+     *
+     * @return Always returns the string "CRAM-MD5"
+     */
+    public String getMechanismName() {
+        return "CRAM-MD5";
+    }
+
+    /**
+     * Evaluate a CRAM-MD5 login challenge, returning the a result string that
+     * should satisfy the clallenge.
+     *
+     * @param challenge
+     *            The decoded challenge data, as a byte array.
+     *
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
+        // we create the challenge from the userid and password information (the
+        // "shared secret").
+        byte[] passBytes;
+
+        try {
+            // get the password in an UTF-8 encoding to create the token
+            passBytes = password.getBytes("UTF-8");
+            // compute the password digest using the key
+            byte[] digest = computeCramDigest(passBytes, challenge);
+
+            // create a unified string using the user name and the hex encoded
+            // digest
+            String responseString = username + " " + new String(Hex.encode(digest), "ISO8859-1");
+            complete = true;
+            return responseString.getBytes("ISO8859-1");
+        } catch (UnsupportedEncodingException e) {
+            // got an error, fail this
+            throw new MessagingException("Invalid character encodings");
+        }
+
+    }
+
+    /**
+     * Compute a CRAM digest using the hmac_md5 algorithm. See the description
+     * of RFC 2104 for algorithm details.
+     *
+     * @param key
+     *            The key (K) for the calculation.
+     * @param input
+     *            The encrypted text value.
+     *
+     * @return The computed digest, as a byte array value.
+     * @exception NoSuchAlgorithmException
+     */
+    protected byte[] computeCramDigest(byte[] key, byte[] input) throws MessagingException {
+        // CRAM digests are computed using the MD5 algorithm.
+        MessageDigest digest;
+        try {
+            digest = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            throw new MessagingException("Unable to access MD5 message digest", e);
+        }
+
+        // if the key is longer than 64 bytes, then we get a digest of the key
+        // and use that instead.
+        // this is required by RFC 2104.
+        if (key.length > 64) {
+            digest.update(key);
+            key = digest.digest();
+        }
+
+        // now we create two 64 bit padding keys, initialized with the key
+        // information.
+        byte[] ipad = new byte[64];
+        byte[] opad = new byte[64];
+
+        System.arraycopy(key, 0, ipad, 0, key.length);
+        System.arraycopy(key, 0, opad, 0, key.length);
+
+        // and these versions are munged by XORing with "magic" values.
+
+        for (int i = 0; i < 64; i++) {
+            ipad[i] ^= 0x36;
+            opad[i] ^= 0x5c;
+        }
+
+        // now there are a pair of MD5 operations performed, and inner and an
+        // outer. The spec defines this as
+        // H(K XOR opad, H(K XOR ipad, text)), where H is the MD5 operation.
+
+        // inner operation
+        digest.reset();
+        digest.update(ipad);
+        digest.update(input); // this appends the text to the pad
+        byte[] md5digest = digest.digest();
+
+        // outer operation
+        digest.reset();
+        digest.update(opad);
+        digest.update(md5digest);
+        return digest.digest(); // final result
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java
new file mode 100644
index 0000000..0272b24
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java
@@ -0,0 +1,628 @@
+/*
+ * 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.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+
+import javax.mail.AuthenticationFailedException;
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.Hex;
+
+/**
+ * Process a DIGEST-MD5 authentication, using the challenge/response mechanisms.
+ */
+public class DigestMD5Authenticator implements ClientAuthenticator {
+
+    protected static final int AUTHENTICATE_CLIENT = 0;
+
+    protected static final int AUTHENTICATE_SERVER = 1;
+
+    protected static final int AUTHENTICATION_COMPLETE = 2;
+
+    // the host server name
+    protected String host;
+
+    // the user we're authenticating
+    protected String username;
+
+    // the user's password (the "shared secret")
+    protected String password;
+
+    // the target login realm
+    protected String realm;
+
+    // our message digest for processing the challenges.
+    MessageDigest digest;
+
+    // the string we send to the server on the first challenge.
+    protected String clientResponse;
+
+    // the response back from an authentication challenge.
+    protected String authenticationResponse = null;
+
+    // our list of realms received from the server (normally just one).
+    protected ArrayList realms;
+
+    // the nonce value sent from the server
+    protected String nonce;
+
+    // indicates whether we've gone through the entire challenge process.
+    protected int stage = AUTHENTICATE_CLIENT;
+
+    /**
+     * Main constructor.
+     *
+     * @param host
+     *            The server host name.
+     * @param username
+     *            The login user name.
+     * @param password
+     *            The login password.
+     * @param realm
+     *            The target login realm (can be null).
+     */
+    public DigestMD5Authenticator(String host, String username, String password, String realm) {
+        this.host = host;
+        this.username = username;
+        this.password = password;
+        this.realm = realm;
+    }
+
+    /**
+     * Respond to the hasInitialResponse query. This mechanism does not have an
+     * initial response.
+     *
+     * @return Always returns false.
+     */
+    public boolean hasInitialResponse() {
+        return false;
+    }
+
+    /**
+     * Indicate whether the challenge/response process is complete.
+     *
+     * @return True if the last challenge has been processed, false otherwise.
+     */
+    public boolean isComplete() {
+        return stage == AUTHENTICATION_COMPLETE;
+    }
+
+    /**
+     * Retrieve the authenticator mechanism name.
+     *
+     * @return Always returns the string "DIGEST-MD5"
+     */
+    public String getMechanismName() {
+        return "DIGEST-MD5";
+    }
+
+    /**
+     * Evaluate a DIGEST-MD5 login challenge, returning the a result string that
+     * should satisfy the clallenge.
+     *
+     * @param challenge
+     *            The decoded challenge data, as a string.
+     *
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
+
+        // DIGEST-MD5 authentication goes in two stages. First state involves us
+        // validating with the
+        // server, the second stage is the server validating with us, using the
+        // shared secret.
+        switch (stage) {
+        // stage one of the process.
+        case AUTHENTICATE_CLIENT: {
+            // get the response and advance the processing stage.
+            byte[] response = authenticateClient(challenge);
+            stage = AUTHENTICATE_SERVER;
+            return response;
+        }
+
+        // stage two of the process.
+        case AUTHENTICATE_SERVER: {
+            // get the response and advance the processing stage to completed.
+            byte[] response = authenticateServer(challenge);
+            stage = AUTHENTICATION_COMPLETE;
+            return response;
+        }
+
+        // should never happen.
+        default:
+            throw new MessagingException("Invalid LOGIN challenge");
+        }
+    }
+
+    /**
+     * Evaluate a DIGEST-MD5 login server authentication challenge, returning
+     * the a result string that should satisfy the clallenge.
+     *
+     * @param challenge
+     *            The decoded challenge data, as a string.
+     *
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] authenticateServer(byte[] challenge) throws MessagingException {
+        // parse the challenge string and validate.
+        if (!parseChallenge(challenge)) {
+            return null;
+        }
+
+        try {
+            // like all of the client validation steps, the following is order
+            // critical.
+            // first add in the URI information.
+            digest.update((":smtp/" + host).getBytes("US-ASCII"));
+            // now mix in the response we sent originally
+            String responseString = clientResponse + new String(Hex.encode(digest.digest()), "US-ASCII");
+            digest.update(responseString.getBytes("US-ASCII"));
+
+            // now convert that into a hex encoded string.
+            String validationText = new String(Hex.encode(digest.digest()), "US-ASCII");
+
+            // if everything went well, this calculated value should match what
+            // we got back from the server.
+            // our response back is just a null string....
+            if (validationText.equals(authenticationResponse)) {
+                return new byte[0];
+            }
+            throw new AuthenticationFailedException("Invalid DIGEST-MD5 response from server");
+        } catch (UnsupportedEncodingException e) {
+            throw new MessagingException("Invalid character encodings");
+        }
+
+    }
+
+    /**
+     * Evaluate a DIGEST-MD5 login client authentication challenge, returning
+     * the a result string that should satisfy the clallenge.
+     *
+     * @param challenge
+     *            The decoded challenge data, as a string.
+     *
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] authenticateClient(byte[] challenge) throws MessagingException {
+        // parse the challenge string and validate.
+        if (!parseChallenge(challenge)) {
+            return null;
+        }
+
+        SecureRandom randomGenerator;
+        // before doing anything, make sure we can get the required crypto
+        // support.
+        try {
+            randomGenerator = new SecureRandom();
+            digest = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            throw new MessagingException("Unable to access cryptography libraries");
+        }
+
+        // if not configured for a realm, take the first realm from the list, if
+        // any
+        if (realm == null) {
+            // if not handed any realms, just use the host name.
+            if (realms.isEmpty()) {
+                realm = host;
+            } else {
+                // pretty arbitrary at this point, so just use the first one.
+                realm = (String) realms.get(0);
+            }
+        }
+
+        // use secure random to generate a collection of bytes. that is our
+        // cnonce value.
+        byte[] cnonceBytes = new byte[32];
+
+        randomGenerator.nextBytes(cnonceBytes);
+
+        try {
+            // and get this as a base64 encoded string.
+            String cnonce = new String(Base64.encode(cnonceBytes), "US-ASCII");
+
+            // Now the digest computation part. This gets a bit tricky, and must be
+            // done in strict order.
+            // this identifies where we're logging into.
+            String idString = username + ":" + realm + ":" + password;
+            // we get a digest for this string, then use the digest for the
+            // first stage
+            // of the next digest operation.
+            digest.update(digest.digest(idString.getBytes("US-ASCII")));
+
+            // now we add the nonce strings to the digest.
+            String nonceString = ":" + nonce + ":" + cnonce;
+            digest.update(nonceString.getBytes("US-ASCII"));
+
+            // hex encode this digest, and add on the string values
+            // NB, we only support "auth" for the quality of protection value
+            // (qop). We save this in an
+            // instance variable because we'll need this to validate the
+            // response back from the server.
+            clientResponse = new String(Hex.encode(digest.digest()), "US-ASCII") + ":" + nonce + ":00000001:" + cnonce + ":auth:";
+
+            // now we add in identification values to the hash.
+            String authString = "AUTHENTICATE:smtp/" + host;
+            digest.update(authString.getBytes("US-ASCII"));
+
+            // this gets added on to the client response
+            String responseString = clientResponse + new String(Hex.encode(digest.digest()), "US-ASCII");
+            // and this gets fed back into the digest
+            digest.update(responseString.getBytes("US-ASCII"));
+
+            // and FINALLY, the challege digest is hex encoded for sending back
+            // to the server (whew).
+            String challengeResponse = new String(Hex.encode(digest.digest()), "US-ASCII");
+
+            // now finally build the keyword/value part of the challenge
+            // response. These can be
+            // in any order.
+            StringBuffer response = new StringBuffer();
+
+            response.append("username=\"");
+            response.append(username);
+            response.append("\"");
+
+            response.append(",realm=\"");
+            response.append(realm);
+            response.append("\"");
+
+            // we only support auth qop values, and the nonce-count (nc) is
+            // always 1.
+            response.append(",qop=auth");
+            response.append(",nc=00000001");
+
+            response.append(",nonce=\"");
+            response.append(nonce);
+            response.append("\"");
+
+            response.append(",cnonce=\"");
+            response.append(cnonce);
+            response.append("\"");
+
+            response.append(",digest-uri=\"smtp/");
+            response.append(host);
+            response.append("\"");
+
+            response.append(",response=");
+            response.append(challengeResponse);
+
+            return response.toString().getBytes("US-ASCII");
+
+        } catch (UnsupportedEncodingException e) {
+            throw new MessagingException("Invalid character encodings");
+        }
+    }
+
+    /**
+     * Parse the challege string, pulling out information required for our
+     * challenge response.
+     *
+     * @param challenge
+     *            The challenge data.
+     *
+     * @return true if there were no errors parsing the string, false otherwise.
+     * @exception MessagingException
+     */
+    protected boolean parseChallenge(byte[] challenge) throws MessagingException {
+        realms = new ArrayList();
+
+        DigestParser parser = null;
+        try {
+            parser = new DigestParser(new String(challenge, "US-ASCII"));
+        } catch (UnsupportedEncodingException ex) {
+        }
+
+        // parse the entire string...but we ignore everything but the options we
+        // support.
+        while (parser.hasMore()) {
+            NameValuePair pair = parser.parseNameValuePair();
+
+            String name = pair.name;
+
+            // realm to add to our list?
+            if (name.equalsIgnoreCase("realm")) {
+                realms.add(pair.value);
+            }
+            // we need the nonce to evaluate the client challenge.
+            else if (name.equalsIgnoreCase("nonce")) {
+                nonce = pair.value;
+            }
+            // rspauth is the challenge replay back, which allows us to validate
+            // that server is also legit.
+            else if (name.equalsIgnoreCase("rspauth")) {
+                authenticationResponse = pair.value;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Inner class for parsing a DIGEST-MD5 challenge string, which is composed
+     * of "name=value" pairs, separated by "," characters.
+     */
+    class DigestParser {
+        // the challenge we're parsing
+        String challenge;
+
+        // length of the challenge
+        int length;
+
+        // current parsing position
+        int position;
+
+        /**
+         * Normal constructor.
+         *
+         * @param challenge
+         *            The challenge string to be parsed.
+         */
+        public DigestParser(String challenge) {
+            this.challenge = challenge;
+            this.length = challenge.length();
+            position = 0;
+        }
+
+        /**
+         * Test if there are more values to parse.
+         *
+         * @return true if we've not reached the end of the challenge string,
+         *         false if the challenge has been completely consumed.
+         */
+        private boolean hasMore() {
+            return position < length;
+        }
+
+        /**
+         * Return the character at the current parsing position.
+         *
+         * @return The string character for the current parse position.
+         */
+        private char currentChar() {
+            return challenge.charAt(position);
+        }
+
+        /**
+         * step forward to the next character position.
+         */
+        private void nextChar() {
+            position++;
+        }
+
+        /**
+         * Skip over any white space characters in the challenge string.
+         */
+        private void skipSpaces() {
+            while (position < length && Character.isWhitespace(currentChar())) {
+                position++;
+            }
+        }
+
+        /**
+         * Parse a quoted string used with a name/value pair, accounting for
+         * escape characters embedded within the string.
+         *
+         * @return The string value of the character string.
+         */
+        private String parseQuotedValue() {
+            // we're here because we found the starting double quote. Step over
+            // it and parse to the closing
+            // one.
+            nextChar();
+
+            StringBuffer value = new StringBuffer();
+
+            while (hasMore()) {
+                char ch = currentChar();
+
+                // is this an escape char?
+                if (ch == '\\') {
+                    // step past this, and grab the following character
+                    nextChar();
+                    // we have an invalid quoted string....
+                    if (!hasMore()) {
+                        return null;
+                    }
+                    value.append(currentChar());
+                }
+                // end of the string?
+                else if (ch == '"') {
+                    // step over this so the caller doesn't process it.
+                    nextChar();
+                    // return the constructed string.
+                    return value.toString();
+                } else {
+                    // step over the character and contine with the next
+                    // characteer1
+                    value.append(ch);
+                }
+                nextChar();
+            }
+            /* fell off the end without finding a closing quote! */
+            return null;
+        }
+
+        /**
+         * Parse a token value used with a name/value pair.
+         *
+         * @return The string value of the token. Returns null if nothing is
+         *         found up to the separater.
+         */
+        private String parseTokenValue() {
+
+            StringBuffer value = new StringBuffer();
+
+            while (hasMore()) {
+                char ch = currentChar();
+                switch (ch) {
+                // process the token separators.
+                case ' ':
+                case '\t':
+                case '(':
+                case ')':
+                case '<':
+                case '>':
+                case '@':
+                case ',':
+                case ';':
+                case ':':
+                case '\\':
+                case '"':
+                case '/':
+                case '[':
+                case ']':
+                case '?':
+                case '=':
+                case '{':
+                case '}':
+                    // no token characters found? this is bad.
+                    if (value.length() == 0) {
+                        return null;
+                    }
+                    // return the accumulated characters.
+                    return value.toString();
+
+                default:
+                    // is this a control character? That's a delimiter (likely
+                    // invalid for the next step,
+                    // but it is a token terminator.
+                    if (ch < 32 || ch > 127) {
+                        // no token characters found? this is bad.
+                        if (value.length() == 0) {
+                            return null;
+                        }
+                        // return the accumulated characters.
+                        return value.toString();
+                    }
+                    value.append(ch);
+                    break;
+                }
+                // step to the next character.
+                nextChar();
+            }
+            // no token characters found? this is bad.
+            if (value.length() == 0) {
+                return null;
+            }
+            // return the accumulated characters.
+            return value.toString();
+        }
+
+        /**
+         * Parse out a name token of a name/value pair.
+         *
+         * @return The string value of the name.
+         */
+        private String parseName() {
+            // skip to the value start
+            skipSpaces();
+
+            // the name is a token.
+            return parseTokenValue();
+        }
+
+        /**
+         * Parse out a a value of a name/value pair.
+         *
+         * @return The string value associated with the name.
+         */
+        private String parseValue() {
+            // skip to the value start
+            skipSpaces();
+
+            // start of a quoted string?
+            if (currentChar() == '"') {
+                // parse it out as a string.
+                return parseQuotedValue();
+            }
+            // the value must be a token.
+            return parseTokenValue();
+        }
+
+        /**
+         * Parse a name/value pair in an DIGEST-MD5 string.
+         *
+         * @return A NameValuePair object containing the two parts of the value.
+         * @exception MessagingException
+         */
+        public NameValuePair parseNameValuePair() throws MessagingException {
+            // get the name token
+            String name = parseName();
+            if (name == null) {
+                throw new MessagingException("Name syntax error");
+            }
+
+            // the name should be followed by an "=" sign
+            if (!hasMore() || currentChar() != '=') {
+                throw new MessagingException("Name/value pair syntax error");
+            }
+
+            // step over the equals
+            nextChar();
+
+            // now get the value part
+            String value = parseValue();
+            if (value == null) {
+                throw new MessagingException("Name/value pair syntax error");
+            }
+
+            // skip forward to the terminator, which should either be the end of
+            // the line or a ","
+            skipSpaces();
+            // all that work, only to have a syntax error at the end (sigh)
+            if (hasMore()) {
+                if (currentChar() != ',') {
+                    throw new MessagingException("Name/value pair syntax error");
+                }
+                // step over, and make sure we position ourselves at either the
+                // end or the first
+                // real character for parsing the next name/value pair.
+                nextChar();
+                skipSpaces();
+            }
+            return new NameValuePair(name, value);
+        }
+    }
+
+    /**
+     * Simple inner class to represent a name/value pair.
+     */
+    public class NameValuePair {
+        public String name;
+
+        public String value;
+
+        NameValuePair(String name, String value) {
+            this.name = name;
+            this.value = value;
+        }
+
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.java
new file mode 100644
index 0000000..9c52f62
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.java
@@ -0,0 +1,140 @@
+/*
+ * 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 javax.mail.MessagingException;
+
+public class LoginAuthenticator implements ClientAuthenticator {
+
+    // constants for the authentication stages
+    protected static final int USERNAME = 0;
+
+    protected static final int PASSWORD = 1;
+
+    protected static final int COMPLETE = 2;
+
+    // the user we're authenticating
+    protected String username;
+
+    // the user's password (the "shared secret")
+    protected String password;
+
+    // indicates whether we've gone through the entire challenge process.
+    protected int stage = USERNAME;
+
+    /**
+     * Main constructor.
+     * 
+     * @param username
+     *            The login user name.
+     * @param password
+     *            The login password.
+     */
+    public LoginAuthenticator(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    /**
+     * Respond to the hasInitialResponse query. This mechanism does not have an
+     * initial response.
+     * 
+     * @return Always returns false;
+     */
+    public boolean hasInitialResponse() {
+        return false;
+    }
+
+    /**
+     * Indicate whether the challenge/response process is complete.
+     * 
+     * @return True if the last challenge has been processed, false otherwise.
+     */
+    public boolean isComplete() {
+        return stage == COMPLETE;
+    }
+
+    /**
+     * Retrieve the authenticator mechanism name.
+     * 
+     * @return Always returns the string "LOGIN"
+     */
+    public String getMechanismName() {
+        return "LOGIN";
+    }
+
+    /**
+     * Evaluate a PLAIN login challenge, returning the a result string that
+     * should satisfy the clallenge.
+     * 
+     * @param challenge
+     *            The decoded challenge data, as a byte array
+     * 
+     * @return A formatted challege response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
+
+        // process the correct stage for the challenge
+        switch (stage) {
+        // should never happen
+        case COMPLETE:
+            throw new MessagingException("Invalid LOGIN challenge");
+
+        case USERNAME: {
+            byte[] userBytes;
+
+            try {
+                // get the username and password in an UTF-8 encoding to create
+                // the token
+                userBytes = username.getBytes("UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                // got an error, fail this (this should never happen).
+                throw new MessagingException("Invalid encoding");
+            }
+
+            // next time through we're looking for a password.
+            stage = PASSWORD;
+            // the user bytes are the entire challenge respose.
+            return userBytes;
+        }
+
+        case PASSWORD: {
+            byte[] passBytes;
+
+            try {
+                // get the username and password in an UTF-8 encoding to create
+                // the token
+                passBytes = password.getBytes("UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                // got an error, fail this (this should never happen).
+                throw new MessagingException("Invalid encoding");
+            }
+            // we're finished
+            stage = COMPLETE;
+            return passBytes;
+        }
+        }
+        // should never get here.
+        throw new MessagingException("Invalid LOGIN challenge");
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java
new file mode 100644
index 0000000..dd1e704
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java
@@ -0,0 +1,126 @@
+/*
+ * 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 javax.mail.MessagingException;
+
+//Implements RFC 4616 PLAIN SASL mechanism
+//See also RFC 3501, section 6.2.2"
+//an RFC 2595, section 6"
+public class PlainAuthenticator implements ClientAuthenticator {
+
+    // the sasl authzid we're authenticating
+    protected String authzid;
+
+    // the user we're authenticating
+    protected String username;
+
+    // the user's password (the "shared secret")
+    protected String password;
+
+    // indicates whether we've gone through the entire challenge process.
+    protected boolean complete = false;
+
+    /**
+     * Main constructor.
+     *
+     * @param authzid
+     *            SASL authenticationid (optional)
+     * @param username
+     *            The login user name.
+     * @param password
+     *            The login password.
+     */
+    public PlainAuthenticator(String authzid, String username, String password) {
+        this.authzid = authzid;
+        this.username = username;
+        this.password = password;
+    }
+
+     /**
+      * Constructor without authzid
+      *
+      * @param username
+      *            The login user name.
+      * @param password
+      *            The login password.
+      */
+     public PlainAuthenticator(String username, String password) {
+         this(null, username, password);
+     }
+
+    /**
+     * Respond to the hasInitialResponse query. This mechanism does have an
+     * initial response, which is the entire challenge sequence.
+     * 
+     * @return Always returns true.
+     */
+    public boolean hasInitialResponse() {
+        return true;
+    }
+
+    /**
+     * Indicate whether the challenge/response process is complete.
+     * 
+     * @return True if the last challenge has been processed, false otherwise.
+     */
+    public boolean isComplete() {
+        return complete;
+    }
+
+    /**
+     * Retrieve the authenticator mechanism name.
+     * 
+     * @return Always returns the string "PLAIN"
+     */
+    public String getMechanismName() {
+        return "PLAIN";
+    }
+
+    /**
+     * Evaluate a PLAIN login challenge, returning the a result string that
+     * should satisfy the challenge.
+     * 
+     * @param challenge
+     *            For PLAIN Authentication there is no challenge (so this is unused)
+     * 
+     * @return A formatted challenge response, as an array of bytes.
+     * @exception MessagingException
+     */
+    public byte[] evaluateChallenge(byte[] challenge) throws MessagingException {
+        try {
+
+            String result = "\0"+username+"\0"+password;
+
+            if(authzid != null && authzid.length() > 0) {
+                result = authzid+result;
+            }
+
+            complete = true;
+            return result.getBytes("UTF-8");
+
+        } catch (UnsupportedEncodingException e) {
+            // got an error, fail this
+            throw new MessagingException("Invalid encoding");
+        }
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/SASLAuthenticator.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/AbstractImageHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/AbstractImageHandler.java
new file mode 100644
index 0000000..e611a42
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/AbstractImageHandler.java
@@ -0,0 +1,91 @@
+/**
+ *  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.handlers;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.datatransfer.DataFlavor;  
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Iterator;
+import javax.activation.ActivationDataFlavor; 
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+import javax.activation.UnsupportedDataTypeException;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.ImageInputStream;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class AbstractImageHandler implements DataContentHandler {
+    private final ActivationDataFlavor flavour;
+
+    public AbstractImageHandler(ActivationDataFlavor flavour) {
+        this.flavour = flavour;
+    }
+
+    public DataFlavor[] getTransferDataFlavors() {
+        return new DataFlavor[]{flavour};
+    }
+
+    public Object getTransferData(DataFlavor dataFlavor, DataSource dataSource) throws UnsupportedFlavorException, IOException {
+        return flavour.equals(dataFlavor) ? getContent(dataSource) : null;
+    }
+
+    public Object getContent(DataSource ds) throws IOException {
+        Iterator i = ImageIO.getImageReadersByMIMEType(flavour.getMimeType());
+        if (!i.hasNext()) {
+            throw new UnsupportedDataTypeException("Unknown image type " + flavour.getMimeType());
+        }
+        ImageReader reader = (ImageReader) i.next();
+        ImageInputStream iis = ImageIO.createImageInputStream(ds.getInputStream());
+        reader.setInput(iis);
+        return reader.read(0);
+    }
+
+    public void writeTo(Object obj, String mimeType, OutputStream os) throws IOException {
+        Iterator i = ImageIO.getImageWritersByMIMEType(flavour.getMimeType());
+        if (!i.hasNext()) {
+            throw new UnsupportedDataTypeException("Unknown image type " + flavour.getMimeType());
+        }
+        ImageWriter writer = (ImageWriter) i.next();
+        writer.setOutput(ImageIO.createImageOutputStream(os));
+
+        if (obj instanceof RenderedImage) {
+            writer.write((RenderedImage) obj);
+        } else if (obj instanceof BufferedImage) {
+            BufferedImage buffered = (BufferedImage) obj;
+            writer.write(new IIOImage(buffered.getRaster(), null, null));
+        } else if (obj instanceof Image) {
+            Image image = (Image) obj;
+            BufferedImage buffered = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
+            Graphics2D graphics = buffered.createGraphics();
+            graphics.drawImage(image, 0, 0, null, null);
+            writer.write(new IIOImage(buffered.getRaster(), null, null));
+        } else {
+            throw new UnsupportedDataTypeException("Unknown image type " + obj.getClass().getName());
+        }
+        os.flush();
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTextHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTextHandler.java
new file mode 100644
index 0000000..b15a30c
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/AbstractTextHandler.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.handlers;
+
+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 java.io.UnsupportedEncodingException;
+
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+
+import javax.mail.internet.ContentType; 
+import javax.mail.internet.MimeUtility;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class AbstractTextHandler implements DataContentHandler {
+    private final DataFlavor flavour;
+
+    public AbstractTextHandler(DataFlavor flavour) {
+        this.flavour = flavour;
+    }
+
+    public DataFlavor[] getTransferDataFlavors() {
+        return new DataFlavor[] {flavour};
+    }
+
+    public Object getTransferData(DataFlavor dataFlavor, DataSource dataSource) throws UnsupportedFlavorException, IOException {
+        return flavour.equals(dataFlavor) ? getContent(dataSource) : null;
+    }
+
+    /**
+     * Read the content from the DataSource and transform 
+     * it into a text object (String). 
+     * 
+     * @param ds     The source DataSource.
+     * 
+     * @return The content object. 
+     * @exception IOException
+     */
+    public Object getContent(DataSource ds) throws IOException {
+        InputStream is = ds.getInputStream(); 
+        InputStreamReader reader;
+        // process any encoding to make sure the chars get transformed into the 
+        // correct byte types. 
+        try {
+            String charset = getCharSet(ds.getContentType());
+            reader = new InputStreamReader(is, charset);
+        } catch (Exception ex) {
+            throw new UnsupportedEncodingException(ex.toString());
+        }
+        StringBuffer result = new StringBuffer(1024);
+        char[] buffer = new char[32768];
+        int count;
+        while ((count = reader.read(buffer)) > 0) {
+            result.append(buffer, 0, count);
+        }
+        return result.toString();
+    }
+
+    
+    /**
+     * Write an object of "our" type out to the provided 
+     * output stream.  The content type might modify the 
+     * result based on the content type parameters. 
+     * 
+     * @param object The object to write.
+     * @param contentType
+     *               The content mime type, including parameters.
+     * @param outputstream
+     *               The target output stream.
+     * 
+     * @throws IOException
+     */
+    public void writeTo(Object o, String contentType, OutputStream outputstream) throws IOException {
+        String s;
+        if (o instanceof String) {
+            s = (String) o;
+        } else if (o != null) {
+            s = o.toString();
+        } else {
+            return;
+        }
+        // process any encoding to make sure the chars get transformed into the 
+        // correct byte types. 
+        OutputStreamWriter writer;
+        try {
+            String charset = getCharSet(contentType);
+            writer = new OutputStreamWriter(outputstream, charset);
+        } catch (Exception ex) {
+            ex.printStackTrace(); 
+            throw new UnsupportedEncodingException(ex.toString());
+        }
+        writer.write(s);
+        writer.flush();
+    }
+    
+
+    /**
+     * get the character set from content type
+     * @param contentType
+     * @return
+     * @throws ParseException
+     */
+    protected String getCharSet(String contentType) throws Exception {
+        ContentType type = new ContentType(contentType);
+        String charset = type.getParameter("charset");
+        if (charset == null) {
+            charset = "us-ascii";
+        }
+        return MimeUtility.javaCharset(charset);
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/ImageGifHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/ImageGifHandler.java
new file mode 100644
index 0000000..674409d
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/ImageGifHandler.java
@@ -0,0 +1,29 @@
+/**
+ *  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.handlers;
+
+import java.awt.Image;
+import javax.activation.ActivationDataFlavor;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class ImageGifHandler extends AbstractImageHandler {
+    public ImageGifHandler() {
+        super(new ActivationDataFlavor(Image.class, "image/gif", "GIF Image"));
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/ImageJpegHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/ImageJpegHandler.java
new file mode 100644
index 0000000..28bcfea
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/ImageJpegHandler.java
@@ -0,0 +1,29 @@
+/**
+ *  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.handlers;
+
+import java.awt.Image;
+import javax.activation.ActivationDataFlavor;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class ImageJpegHandler extends AbstractImageHandler {
+    public ImageJpegHandler() {
+        super(new ActivationDataFlavor(Image.class, "image/jpeg", "JPEG Image"));
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/MultipartHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/MultipartHandler.java
new file mode 100644
index 0000000..93093db
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/MultipartHandler.java
@@ -0,0 +1,65 @@
+/**
+ *  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.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.io.OutputStream;
+import javax.activation.ActivationDataFlavor;
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMultipart;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class MultipartHandler implements DataContentHandler {
+    private final DataFlavor flavour;
+
+    public MultipartHandler() {
+        flavour = new ActivationDataFlavor(MimeMultipart.class, "multipart/mixed", "Multipart MIME");
+    }
+
+    public DataFlavor[] getTransferDataFlavors() {
+        return new DataFlavor[]{flavour};
+    }
+
+    public Object getTransferData(DataFlavor df, DataSource ds) throws UnsupportedFlavorException, IOException {
+        return flavour.equals(df) ? getContent(ds) : null;
+    }
+
+    public Object getContent(DataSource ds) throws IOException {
+        try {
+            return new MimeMultipart(ds);
+        } catch (MessagingException e) {
+            throw (IOException) new IOException(e.getMessage()).initCause(e);
+        }
+    }
+
+    public void writeTo(Object obj, String mimeType, OutputStream os) throws IOException {
+        if (obj instanceof MimeMultipart) {
+            MimeMultipart mp = (MimeMultipart) obj;
+            try {
+                mp.writeTo(os);
+            } catch (MessagingException e) {
+                throw (IOException) new IOException(e.getMessage()).initCause(e);
+            }
+        }
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/RFC822MessageHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/RFC822MessageHandler.java
new file mode 100644
index 0000000..926cbe8
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/RFC822MessageHandler.java
@@ -0,0 +1,120 @@
+/**
+ *  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.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import javax.activation.ActivationDataFlavor;
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+import javax.mail.Message;
+import javax.mail.MessageAware;
+import javax.mail.MessageContext;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+
+/**
+ * Content handler for RFC-822 compliant messages.
+ * @version $Rev$ $Date$
+ */
+public class RFC822MessageHandler implements DataContentHandler {
+    // the data flavor defines what this looks like, and is fixed once the
+    // handler is instantiated
+    protected final DataFlavor flavour;
+
+    public RFC822MessageHandler() {
+        flavour = new ActivationDataFlavor(Message.class, "message/rfc822", "Message");
+    }
+
+    /**
+     * Return all of the flavors processed by this handler.  This
+     * is just the singleton flavor.
+     *
+     * @return An array of the transfer flavors supported by this handler.
+     */
+    public DataFlavor[] getTransferDataFlavors() {
+        return new DataFlavor[] { flavour };
+    }
+
+    /**
+     * Retrieve the transfer data from the data source, but
+     * only if the requested flavor matches what we support.
+     *
+     * @param df     The requested data flavor.
+     * @param ds     The source DataSource.
+     *
+     * @return The extracted content object, or null if there is a
+     *         mismatch of flavors.
+     * @exception UnsupportedFlavorException
+     * @exception IOException
+     */
+    public Object getTransferData(DataFlavor df, DataSource ds) throws UnsupportedFlavorException, IOException {
+        return flavour.equals(df) ? getContent(ds) : null;
+    }
+
+    /**
+     * Extract the RFC822 Message content from a DataSource.
+     *
+     * @param ds     The source data source.
+     *
+     * @return An extracted MimeMessage object.
+     * @exception IOException
+     */
+    public Object getContent(DataSource ds) throws IOException {
+        try {
+            // creating a MimeMessage instance requires a session.  If the DataSource
+            // is a MessageAware one, we can get the session information from the MessageContext.
+            // This is generally the case, but if it is not available, then just retrieve
+            // the default instance and use it.
+            if (ds instanceof MessageAware) {
+                MessageContext context = ((MessageAware)ds).getMessageContext();
+                return new MimeMessage(context.getSession(), ds.getInputStream());
+            }
+            else {
+                return new MimeMessage(Session.getDefaultInstance(new Properties(), null), ds.getInputStream());
+            }
+        } catch (MessagingException e) {
+            throw (IOException) new IOException(e.getMessage()).initCause(e);
+        }
+    }
+
+    /**
+     * Write an RFC 822 message object out to an output stream.
+     *
+     * @param obj      The source message object.
+     * @param mimeType The target mimetype
+     * @param os       The target output stream.
+     *
+     * @exception IOException
+     */
+    public void writeTo(Object obj, String mimeType, OutputStream os) throws IOException {
+        // we only handle message instances here
+        if (obj instanceof Message) {
+            Message message = (Message) obj;
+            try {
+                message.writeTo(os);
+            } catch (MessagingException e) {
+                throw (IOException) new IOException(e.getMessage()).initCause(e);
+            }
+        }
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/TextHtmlHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/TextHtmlHandler.java
new file mode 100644
index 0000000..4c93ff6
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/TextHtmlHandler.java
@@ -0,0 +1,29 @@
+/**
+ *  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.handlers;
+
+import javax.activation.ActivationDataFlavor;
+
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TextHtmlHandler extends AbstractTextHandler {
+    public TextHtmlHandler() {
+        super(new ActivationDataFlavor(String.class, "text/html", "HTML Text"));
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/TextPlainHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/TextPlainHandler.java
new file mode 100644
index 0000000..408380f
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/TextPlainHandler.java
@@ -0,0 +1,29 @@
+/**
+ *  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.handlers;
+
+import javax.activation.ActivationDataFlavor;
+
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TextPlainHandler extends AbstractTextHandler {
+    public TextPlainHandler() {
+        super(new ActivationDataFlavor(String.class, "text/plain", "Plain Text"));
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/TextXmlHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/TextXmlHandler.java
new file mode 100644
index 0000000..e4d095e
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/handlers/TextXmlHandler.java
@@ -0,0 +1,29 @@
+/**
+ *  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.handlers;
+
+import javax.activation.ActivationDataFlavor;
+
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TextXmlHandler extends AbstractTextHandler {
+    public TextXmlHandler() {
+        super(new ActivationDataFlavor(String.class, "text/xml", "XML Text"));
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/ACL.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/ACL.java
new file mode 100644
index 0000000..ba9a179
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/ACL.java
@@ -0,0 +1,93 @@
+/**
+ * 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. 
+     * 
+     * @return A cloned copy of this object.  This is a deep 
+     *         copy, given that a new Rights set is also created.
+     * @exception CloneNotSupportedException
+     */
+    protected Object clone() throws CloneNotSupportedException {
+        return new ACL(name, new Rights(rights)); 
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPAttachedMessage.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java
new file mode 100644
index 0000000..ab65156
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java
@@ -0,0 +1,2393 @@
+/**
+ * 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 Map messageCache;
+    // 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.  These are empty initially
+                messageCache = new HashMap();
+                uidCache = new HashMap();
+
+                // 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);
+            }
+        }
+	}
+
+
+    /**
+     * 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;
+        messageCache = 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);
+        // create the mapping key for this
+        Integer messageKey = new Integer(msgNum);
+        // ok, if the message number is within range, we should have this in the
+        // messages list.  Just return the element.
+        Message message = (Message)messageCache.get(messageKey);
+        // if not in the cache, create a dummy add it in.  The message body will be
+        // retrieved on demand
+        if (message == null) {
+            message = new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, msgNum);
+            messageCache.put(messageKey, message);
+        }
+        return message;
+    }
+
+
+    /**
+     * 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(messageCache) {
+            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 (messageCache) {
+            // 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 (messageCache) {
+            ((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 {
+
+        // first process the expunged message.  We need to return a Message instance, so
+        // force this to be added to the cache
+        IMAPMessage expungedMessage = (IMAPMessage)getMessage(sequenceNumber);
+        // mark the message as expunged.
+        expungedMessage.setExpunged(true);
+        // 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));
+        }
+        // because we need to jigger the keys of some of these, we had better have a working
+        // copy.
+        Map newCache = new HashMap();
+
+        // now process each message in the cache, making adjustments as necessary
+        Iterator i = messageCache.keySet().iterator();
+
+        while (i.hasNext()) {
+            Integer key = (Integer)i.next();
+            int index = key.intValue();
+            // if before the expunged message, just copy over to the
+            // new cache
+            if (index < sequenceNumber) {
+                newCache.put(key, messageCache.get(key));
+            }
+            // after the expunged message...we need to adjust this
+            else if (index > sequenceNumber) {
+                // retrieve the message using the current position,
+                // adjust the message sequence number, and add to the new
+                // message cache under the new key value
+                IMAPMessage message = (IMAPMessage)messageCache.get(key);
+                message.setSequenceNumber(index - 1);
+                newCache.put(new Integer(index - 1), message);
+            }
+            else {
+                // the expunged message.  We don't move this over to the new
+                // cache, and we've already done all processing of that message that's
+                // required
+            }
+        }
+
+        // replace the old cache now that everything has been adjusted
+        messageCache = newCache;
+
+        // 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();
+            // 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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessage.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessage.java
new file mode 100644
index 0000000..23f38b4
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMessage.java
@@ -0,0 +1,1300 @@
+/**
+ * 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;
+import org.apache.geronimo.javamail.util.MailConnection;
+
+/**
+ * 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 {
+
+    private static final byte[] CRLF = new byte[]{'\r', '\n'};
+
+    // 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 {
+        // no content loaded yet?
+        if (content == null) {
+            // make sure we're still valid
+            checkValidity();
+            // make sure the content is fully loaded
+            loadContent();
+        }
+
+        loadHeaders();
+
+        Enumeration e = headers.getAllHeaderLines();
+        while(e.hasMoreElements()) {
+            String line = (String)e.nextElement();
+            out.write(line.getBytes("ISO8859-1"));
+            out.write(CRLF);
+        }
+        out.write(CRLF);
+        out.write(CRLF);
+        out.write(content);
+    }
+
+	/******************************************************************
+	 * 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(addresses));
+        }
+    }
+
+    /**
+     * 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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMimeBodyPart.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMimeBodyPart.java
new file mode 100644
index 0000000..9370451
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMimeBodyPart.java
@@ -0,0 +1,359 @@
+/**
+ * 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;
+import org.apache.geronimo.mail.util.SessionUtil;
+
+
+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");
+        }
+        
+        // if we have a name, we might need to decode this if an additional property is set.
+        if (filename != null && SessionUtil.getBooleanProperty(MIME_DECODEFILENAME, false)) {
+            try {
+                filename = MimeUtility.decodeText(filename);
+            } catch (UnsupportedEncodingException e) {
+                throw new MessagingException("Unable to decode filename", e);
+            }
+        }
+        
+        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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPMultipartDataSource.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPNamespaceFolder.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPRootFolder.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.java
new file mode 100644
index 0000000..c844e9b
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPSSLStore.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;
+
+import javax.mail.Session;
+import javax.mail.URLName;
+
+/**
+ * IMAP implementation of javax.mail.Store for SSL connections. 
+ *
+ * @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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPStore.java
new file mode 100644
index 0000000..7b252d6
--- /dev/null
+++ b/geronimo-javamail_1.5/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 = 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 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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java
new file mode 100644
index 0000000..c08ef0f
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/Rights.java
@@ -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()); 
+        }
+    }
+    
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPACLResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBody.java
new file mode 100644
index 0000000..9efe633
--- /dev/null
+++ b/geronimo-javamail_1.5/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.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);
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodySection.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java
new file mode 100644
index 0000000..80cf50e
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructure.java
@@ -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"); 
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCapabilityResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java
new file mode 100644
index 0000000..d49b513
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPCommand.java
@@ -0,0 +1,1477 @@
+/**
+ * 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) {
+        try {
+            // work off the byte values
+            appendString(value.getBytes("ISO8859-1"));
+        } catch (UnsupportedEncodingException e) {
+        }
+    }
+
+
+    /**
+     * 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) {
+        try {
+            // work off the byte values
+            appendQuotedString(value.getBytes("ISO8859-1"));
+        } catch (UnsupportedEncodingException e) {
+        }
+    }
+
+
+    /**
+     * 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);
+        try {
+            // work off the byte values
+            appendString(value.getBytes("ISO8859-1"));
+        } catch (UnsupportedEncodingException e) {
+        }
+    }
+
+
+    /**
+     * 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) {
+            try {
+                // work off the byte values
+                appendString(value.getBytes("ISO8859-1"));
+            } catch (UnsupportedEncodingException e) {
+            }
+        }
+        else {
+            try {
+                // use the charset to extract the bytes
+                appendString(value.getBytes(charset));
+                throw new MessagingException("Invalid text encoding");
+            } catch (UnsupportedEncodingException e) {
+            }
+        }
+    }
+
+
+    /**
+     * 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) {
+        try {
+            appendAtom(value.getBytes("ISO8859-1"));
+        } catch (UnsupportedEncodingException e) {
+        }
+    }
+
+
+
+    /**
+     * 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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
new file mode 100644
index 0000000..c116fa3
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnection.java
@@ -0,0 +1,1992 @@
+/**
+ * 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 props  The protocol properties abstraction containing our
+     *               property modifiers.
+     * @param pool
+     */
+    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();
+            
+            boolean requireTLS = props.getBooleanProperty(MAIL_STARTTLS_REQUIRED, false);
+            boolean enableTLS = props.getBooleanProperty(MAIL_STARTTLS_ENABLE, false);
+            boolean serverSupportsTLS = hasCapability(CAPABILITY_STARTTLS);
+
+            // if we're not already using an SSL connection, and we have permission to issue STARTTLS or its even required
+            // try to setup a SSL connection
+            if (!sslConnection && (enableTLS || requireTLS)) {
+                
+                //if the server does not support TLS check if its required.
+                //If true then throw an error, if not establish a non SSL connection
+                if(requireTLS && !serverSupportsTLS) {
+                    throw new MessagingException("Server doesn't support required transport level security");
+                } else if (serverSupportsTLS){
+                    // 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;
+                    }
+                } else {
+                    if (debug) {
+                        debugOut("STARTTLS is enabled but not required and server does not support it. So we establish a connection without transport level security");
+                    }
+                }
+                
+            }
+
+            // 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 {
+        try {
+            return sendLine(data.getBytes("ISO8859-1"));
+        } catch (UnsupportedEncodingException e) {
+            // should never happen
+            return null;
+        }
+    }
+
+
+    /**
+     * 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(authid, 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 {
+        if (section == null) {
+            IMAPMessageText text = fetchText(sequenceNumber);
+            return text.getContent();
+        } else {
+            IMAPBody body = fetchBody(sequenceNumber, section);
+            return body.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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java
new file mode 100644
index 0000000..f89619c
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPConnectionPool.java
@@ -0,0 +1,589 @@
+/**
+ * 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 props  The property bundle that defines protocol properties
+     *               that alter the connection behavior.
+     */
+    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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPContinuationResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPDateFormat.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPEnvelope.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchBodyPart.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchDataItem.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFetchResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlags.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPFlagsResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternalDate.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPInternetHeader.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPListRightsResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMailboxStatus.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageSize.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMessageText.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPMyRightsResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespace.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPNamespaceResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPOkResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPPermanentFlagsResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPQuotaRootResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponse.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponse.java
new file mode 100644
index 0000000..20dd5f6
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponse.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.io.UnsupportedEncodingException;
+
+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() {
+        try {
+            return new String(response, "US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+        }
+        return new String(response);
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseBuffer.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseBuffer.java
new file mode 100644
index 0000000..cd28d54
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseBuffer.java
@@ -0,0 +1,138 @@
+/**
+ * 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;
+
+/**
+ * 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;
+            }
+
+            try {
+                String lenString = new String(buf, literalStart + 1, size() - (literalStart + 2), "US-ASCII");
+                try {
+                    return Integer.parseInt(lenString);
+                } catch (NumberFormatException e) {
+                }
+            } catch (UnsupportedEncodingException ex) {
+                // should never happen
+            }
+        }
+        // not a literal
+        return -1;
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseStream.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseStream.java
new file mode 100644
index 0000000..420934a
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseStream.java
@@ -0,0 +1,405 @@
+/**
+ * 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.io.UnsupportedEncodingException;
+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) {
+                try {
+                    throw new MessagingException("Unknown server response: " + new String(data, "ISO8859-1"));
+                } catch (UnsupportedEncodingException e) {
+                    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();
+            //handle plain authentication gracefully, see GERONIMO-6526
+            if("+".equals(tag) && status == null) {
+            	return new IMAPContinuationResponse(data);
+            }                      
+            // primary information in one of these is the status field, which hopefully
+            // is 'OK'
+            return new IMAPTaggedResponse(tag, status, tokenizer.getRemainder(), data);
+        }
+        try {
+            throw new MessagingException("Unknown server response: " + new String(data, "ISO8859-1"));
+        } catch (UnsupportedEncodingException e) {
+            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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseTokenizer.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseTokenizer.java
new file mode 100644
index 0000000..4856f76
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPResponseTokenizer.java
@@ -0,0 +1,1469 @@
+/**
+ * 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 "";
+        }
+
+        try {
+            return new String(response, pos, response.length - pos, "ISO8859-1");
+        } catch (UnsupportedEncodingException e) {
+            return null; 
+        }
+    }
+
+
+    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;
+            }
+        }
+
+        try {
+            // Numeric tokens we store as a different type.
+            String value = new String(response, start, pos - start, "ISO8859-1");
+            try {
+                int intValue = Integer.parseInt(value);
+                return new Token(Token.NUMERIC, value);
+            } catch (NumberFormatException e) {
+            }
+            return new Token(Token.ATOM, value);
+        } catch (UnsupportedEncodingException e) {
+            return null; 
+        }
+    }
+
+    /**
+     * 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 {
+        try {
+            String value = new String(readQuotedStringData(), "ISO8859-1");
+            return new Token(Token.QUOTEDSTRING, value);
+        } catch (UnsupportedEncodingException e) {
+            return null; 
+        }
+    }
+
+    /**
+     * 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 {
+        try {
+            String value = new String(readLiteralData(), "ISO8859-1");
+            return new Token(Token.LITERAL, value);
+        } catch (UnsupportedEncodingException e) {
+            return null; 
+        }
+    }
+
+
+    /**
+     * 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 ) {
+        try {
+            return new String(response, start, end - start, "ISO8859-1");
+        } catch (UnsupportedEncodingException e) {
+            return null; 
+        }
+    }
+
+
+    /**
+     * 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(true);
+        int type = token.getType();
+        if (type == Token.NIL) {
+            return null;
+        }
+        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) {
+            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(true);
+
+        if (token.getType() == '(') {
+            List list = new ArrayList();
+
+            next();
+
+            while (notListEnd()) {
+                String value = readString();
+                // this can be NIL, technically
+                if (value != null) {
+                    list.add(value);
+                }
+            }
+            // step over the closing paren
+            next();
+
+            return list;
+        }
+        else if (token != NIL) {
+            List list = new ArrayList();
+
+            // just a single string value.
+            String value = readString();
+            // this can be NIL, technically
+            if (value != null) {
+                list.add(value);
+            }
+
+            return list;
+        } else {
+            next();
+        }
+        return null;
+    }
+
+
+    /**
+     * 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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchDateFormat.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSearchResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPServerStatusResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPSizeResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPStatusResponse.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPStatusResponse.java
new file mode 100644
index 0000000..053a2db
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPStatusResponse.java
@@ -0,0 +1,82 @@
+/**
+ * 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();
+        if (flags == null) {
+            return;
+        }
+
+        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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPTaggedResponse.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPTaggedResponse.java
new file mode 100644
index 0000000..a5994f7
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPTaggedResponse.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;
+
+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. 
+    	
+    	//handle plain authentication gracefully, see GERONIMO-6526
+    	if(response.length <= 2){
+    		return null;
+    	}
+    	
+        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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUid.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponse.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/connection/IMAPUntaggedResponseHandler.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPFolder.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPFolder.java
new file mode 100644
index 0000000..ba330e2
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPFolder.java
@@ -0,0 +1,449 @@
+/*
+ * 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.nntp;
+
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.IllegalWriteException;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.MethodNotSupportedException;
+import javax.mail.Session;
+import javax.mail.event.ConnectionEvent;
+
+import org.apache.geronimo.javamail.transport.nntp.NNTPConnection;
+
+/**
+ * The base NNTP implementation of the javax.mail.Folder This is a base class
+ * for both the Root NNTP server and each NNTP group folder.
+ * 
+ * @see javax.mail.Folder
+ * 
+ * @version $Rev$
+ */
+public class NNTPFolder extends Folder {
+
+    // our active connection.
+    protected NNTPConnection connection;
+
+    // our attached session
+    protected Session session;
+
+    // the name of this folder (either the name of the server for the root or
+    // the news group name).
+    protected String name;
+
+    // the "full" name of the folder. For the root folder, this is the name
+    // returned by the connection
+    // welcome string. Otherwise, this is the same as the name.
+    protected String fullName;
+
+    // the parent folder. For the root folder, this is null. For a group folder,
+    // this is the root.
+    protected Folder parent;
+
+    // the folder open state
+    protected boolean folderOpen = false;
+
+    // the folder message count. For the root folder, this is always 0.
+    protected int messageCount = 0;
+
+    // the persistent flags we save in the store (basically just the SEEN flag).
+    protected Flags permanentFlags;
+
+    /**
+     * Super class constructor the base NNTPFolder class.
+     * 
+     * @param store
+     *            The javamail store this folder is attached to.
+     */
+    protected NNTPFolder(NNTPStore store) {
+        super(store);
+        // get the active connection from the store...all commands are sent
+        // there
+        this.connection = store.getConnection();
+        this.session = store.getSession();
+
+        // set up our permanent flags bit.
+        permanentFlags = new Flags();
+        permanentFlags.add(Flags.Flag.SEEN);
+    }
+
+    /**
+     * Retrieve the folder name.
+     * 
+     * @return The folder's name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Retrieve the folder's full name (including hierarchy information). NNTP
+     * folders are flat, so the full name is generally the same as the name.
+     * 
+     * @return The full name value.
+     */
+    public String getFullName() {
+        return fullName;
+    }
+
+    /**
+     * Returns the parent folder for this folder. Returns null if this is the
+     * root folder.
+     */
+    public Folder getParent() throws MessagingException {
+        return parent;
+    }
+
+    /**
+     * Indicated whether the folder "exists" or not. Existance in this context
+     * indicates that the group still exists on the server.
+     * 
+     * @return
+     * @exception MessagingException
+     */
+    public boolean exists() throws MessagingException {
+        // by default, return true. This is really only the case for the root.
+        // The group folder will
+        // need to override this.
+        return true;
+    }
+
+    /**
+     * List the subfolders. For group folders, this is a meaningless so we throw
+     * a MethodNotSupportedException.
+     * 
+     * @param pattern
+     *            The folder pattern string.
+     * 
+     * @return Never returns.
+     * @exception MessagingException
+     */
+    public Folder[] list(String pattern) throws MessagingException {
+        throw new MethodNotSupportedException("NNTP group folders cannot contain sub folders");
+    }
+
+    /**
+     * Retrieve the list of subscribed folders that match the given pattern
+     * string.
+     * 
+     * @param pattern
+     *            The pattern string used for the matching
+     * 
+     * @return An array of matching folders from the subscribed list.
+     */
+    public Folder[] listSubscribed(String pattern) throws MessagingException {
+        throw new MethodNotSupportedException("NNTP group folders cannot contain sub folders");
+    }
+
+    /**
+     * No sub folders, hence there is no notion of a seperator. We return a null
+     * character (consistent with what Sun returns for POP3 folders).
+     */
+    public char getSeparator() throws MessagingException {
+        return '\0';
+    }
+
+    /**
+     * Return whether this folder can hold just messages or also subfolders.
+     * Only the root folder can hold other folders, so it will need to override.
+     * 
+     * @return Either Folder.HOLDS_MESSAGES or Folder.HOLDS_FOLDERS.
+     * @exception MessagingException
+     */
+    public int getType() throws MessagingException {
+        return HOLDS_MESSAGES;
+    }
+
+    /**
+     * Create a new folder. NNTP folders are read only, so this is a nop.
+     * 
+     * @param type
+     *            The type of folder.
+     * 
+     * @return Not support, throws an exception.
+     * @exception MessagingException
+     */
+    public boolean create(int type) throws MessagingException {
+        throw new MethodNotSupportedException("Sub folders cannot be created in NNTP");
+    }
+
+    /**
+     * Check for new messages. We always return false for the root folder. The
+     * group folders will need to override.
+     * 
+     * @return Always returns false.
+     * @exception MessagingException
+     */
+    public boolean hasNewMessages() throws MessagingException {
+        return false;
+    }
+
+    /**
+     * Get a named subfolder from this folder. This only has meaning from the
+     * root NNTP folder.
+     * 
+     * @param name
+     *            The requested name.
+     * 
+     * @return If the folder exists, returns a Folder object representing the
+     *         named folder.
+     * @exception MessagingException
+     */
+    public Folder getFolder(String name) throws MessagingException {
+        throw new MethodNotSupportedException("NNTP Group folders do not support sub folders");
+    }
+
+    /**
+     * Delete a folder. This is not supported for NNTP.
+     * 
+     * @param recurse
+     *            The recusion flag.
+     * 
+     * @return Never returns.
+     * @exception MessagingException
+     */
+    public boolean delete(boolean recurse) throws MessagingException {
+        throw new MethodNotSupportedException("Deleting of NNTP folders is not supported");
+    }
+
+    /**
+     * Rename a folder. Not supported for NNTP folders.
+     * 
+     * @param f
+     *            The new folder specifying the rename location.
+     * 
+     * @return
+     * @exception MessagingException
+     */
+    public boolean renameTo(Folder f) throws MessagingException {
+        throw new MethodNotSupportedException("Renaming of NNTP folders is not supported.");
+    }
+
+    /**
+     * @see javax.mail.Folder#open(int)
+     */
+    public void open(int mode) throws MessagingException {
+
+        // we don't support READ_WRITE mode, so don't allow opening in that
+        // mode.
+        if (mode == READ_WRITE) {
+            throw new IllegalWriteException("Newsgroup folders cannot be opened read/write");
+        }
+
+        // an only be performed on a closed folder
+        checkClosed();
+
+        this.mode = mode;
+
+        // perform folder type-specific open actions.
+        openFolder();
+
+        folderOpen = true;
+
+        notifyConnectionListeners(ConnectionEvent.OPENED);
+    }
+
+    /**
+     * Perform folder type-specific open actions. The default action is to do
+     * nothing.
+     * 
+     * @exception MessagingException
+     */
+    protected void openFolder() throws MessagingException {
+    }
+
+    /**
+     * Peform folder type-specific close actions. The default action is to do
+     * nothing.
+     * 
+     * @exception MessagingException
+     */
+    protected void closeFolder() throws MessagingException {
+    }
+
+    /**
+     * Close the folder. Cleans up resources, potentially expunges messages
+     * marked for deletion, and sends an event notification.
+     * 
+     * @param expunge
+     *            The expunge flag, which is ignored for NNTP folders.
+     * 
+     * @exception MessagingException
+     */
+    public void close(boolean expunge) throws MessagingException {
+        // Can only be performed on an open folder
+        checkOpen();
+
+        // give the subclasses an opportunity to do some cleanup
+        closeFolder();
+
+        folderOpen = false;
+        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 Flags getPermanentFlags() {
+        // we need a copy of our master set.
+        return new Flags(permanentFlags);
+    }
+
+    /**
+     * Get the count of messages in this folder.
+     * 
+     * @return The message count.
+     * @exception MessagingException
+     */
+    public int getMessageCount() throws MessagingException {
+        return messageCount;
+    }
+
+    /**
+     * Checks wether the message is in cache, if not will create a new message
+     * object and return it.
+     * 
+     * @see javax.mail.Folder#getMessage(int)
+     */
+    public Message getMessage(int msgNum) throws MessagingException {
+        // for the base, we just throw an exception.
+        throw new MethodNotSupportedException("Root NNTP folder does not contain messages");
+    }
+
+    /**
+     * Append messages to a folder. NNTP folders are read only, so this is not
+     * supported.
+     * 
+     * @param msgs
+     *            The list of messages to append.
+     * 
+     * @exception MessagingException
+     */
+    public void appendMessages(Message[] msgs) throws MessagingException {
+        throw new MethodNotSupportedException("Root NNTP folder does not contain messages");
+
+    }
+
+    /**
+     * Expunge messages marked for deletion and return a list of the Messages.
+     * Not supported for NNTP.
+     * 
+     * @return Never returns.
+     * @exception MessagingException
+     */
+    public Message[] expunge() throws MessagingException {
+        throw new MethodNotSupportedException("Root NNTP folder does not contain messages");
+    }
+
+    /**
+     * Below is a list of convenience methods that avoid repeated checking for a
+     * value and throwing an exception
+     */
+
+    /** Ensure the folder is open */
+    protected void checkOpen() throws IllegalStateException {
+        if (!folderOpen) {
+            throw new IllegalStateException("Folder is not Open");
+        }
+    }
+
+    /** Ensure the folder is not open */
+    protected void checkClosed() throws IllegalStateException {
+        if (folderOpen) {
+            throw new IllegalStateException("Folder is Open");
+        }
+    }
+
+    /**
+     * @see javax.mail.Folder#notifyMessageChangedListeners(int,
+     *      javax.mail.Message)
+     * 
+     * this method is protected and cannot be used outside of Folder, therefore
+     * had to explicitly expose it via a method in NNTPFolder, so that
+     * NNTPMessage has access to it
+     * 
+     * Bad design on the part of the Java Mail API.
+     */
+    public void notifyMessageChangedListeners(int type, Message m) {
+        super.notifyMessageChangedListeners(type, m);
+    }
+
+    /**
+     * Retrieve the subscribed status for a folder. This default implementation
+     * just returns false (which is true for the root folder).
+     * 
+     * @return Always returns true.
+     */
+    public boolean isSubscribed() {
+        return false;
+    }
+
+    /**
+     * Set the subscribed status for a folder.
+     * 
+     * @param flag
+     *            The new subscribed status.
+     * 
+     * @exception MessagingException
+     */
+    public void setSubscribed(boolean flag) throws MessagingException {
+        throw new MessagingException("Root NNTP folder cannot be subscribed to");
+    }
+
+    /**
+     * Test if a given article number is marked as SEEN.
+     * 
+     * @param article
+     *            The target article number.
+     * 
+     * @return The articles current seen status.
+     */
+    public boolean isSeen(int article) {
+        return false;
+    }
+
+    /**
+     * Set the SEEN status for an article.
+     * 
+     * @param article
+     *            The target article.
+     * @param flag
+     *            The new seen setting.
+     * 
+     * @exception MessagingException
+     */
+    public void setSeen(int article, boolean flag) throws MessagingException {
+        throw new MessagingException("Root NNTP folder does not contain articles");
+    }
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPGroupFolder.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPGroupFolder.java
new file mode 100644
index 0000000..aa01696
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPGroupFolder.java
@@ -0,0 +1,391 @@
+/*
+ * 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.nntp;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.mail.FetchProfile;
+import javax.mail.FolderNotFoundException;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcGroup;
+import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
+
+/**
+ * The NNTP implementation of the javax.mail.Folder Note that only INBOX is
+ * supported in NNTP
+ * <p>
+ * <url>http://www.faqs.org/rfcs/rfc1939.html</url>
+ * </p>
+ * 
+ * @see javax.mail.Folder
+ * 
+ * @version $Rev$ $Date$
+ */
+public class NNTPGroupFolder extends NNTPFolder {
+
+    // holders for status information returned by the GROUP command.
+    protected int firstArticle = -1;
+
+    protected int lastArticle = -1;
+
+    // retrieved articles, mapped by article number.
+    Map articles;
+
+    // information stored in the newsrc group.
+    NNTPNewsrcGroup groupInfo;
+
+    /**
+     * Construct a "real" folder representing an NNTP news group.
+     * 
+     * @param parent
+     *            The parent root folder.
+     * @param store
+     *            The Store this folder is attached to.
+     * @param name
+     *            The folder name.
+     * @param groupInfo
+     *            The newsrc group information attached to the newsrc database.
+     *            This contains subscription and article "SEEN" information.
+     */
+    protected NNTPGroupFolder(NNTPRootFolder parent, NNTPStore store, String name, NNTPNewsrcGroup groupInfo) {
+        super(store);
+        // the name and the full name are the same.
+        this.name = name;
+        this.fullName = name;
+        // set the parent appropriately.
+        this.parent = parent = parent;
+        this.groupInfo = groupInfo;
+    }
+
+    /**
+     * Ping the server and update the group count, first, and last information.
+     * 
+     * @exception MessagingException
+     */
+    private void updateGroupStats() throws MessagingException {
+        // ask the server for information about the group. This is a one-line
+        // reponse with status on
+        // the group, if it exists.
+        NNTPReply reply = connection.sendCommand("GROUP " + name);
+
+        // explicitly not there?
+        if (reply.getCode() == NNTPReply.NO_SUCH_NEWSGROUP) {
+            throw new FolderNotFoundException(this, "Folder does not exist on server: " + reply);
+        } else if (reply.getCode() != NNTPReply.GROUP_SELECTED) {
+            throw new MessagingException("Error requesting group information: " + reply);
+        }
+
+        // we've gotten back a good response, now parse out the group specifics
+        // from the
+        // status response.
+
+        StringTokenizer tokenizer = new StringTokenizer(reply.getMessage());
+
+        // we should have a least 3 tokens here, in the order "count first
+        // last".
+
+        // article count
+        if (tokenizer.hasMoreTokens()) {
+            String count = tokenizer.nextToken();
+            try {
+                messageCount = Integer.parseInt(count);
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+        }
+
+        // first article number
+        if (tokenizer.hasMoreTokens()) {
+            String first = tokenizer.nextToken();
+            try {
+                firstArticle = Integer.parseInt(first);
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+        }
+
+        // last article number.
+        if (tokenizer.hasMoreTokens()) {
+            String last = tokenizer.nextToken();
+            try {
+                lastArticle = Integer.parseInt(last);
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Test to see if this folder actually exists. This pings the server for
+     * information about the GROUP and updates the article count and index
+     * information.
+     * 
+     * @return true if the newsgroup exists on the server, false otherwise.
+     * @exception MessagingException
+     */
+    public boolean exists() throws MessagingException {
+
+        try {
+            // update the group statistics. If the folder doesn't exist, we'll
+            // get an exception that we
+            // can turn into a false reply.
+            updateGroupStats();
+            // updated ok, so it must be there.
+            return true;
+        } catch (FolderNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Ping the NNTP server to check if a newsgroup has any new messages.
+     * 
+     * @return True if the server has new articles from the last time we
+     *         checked. Also returns true if this is the first time we've
+     *         checked.
+     * @exception MessagingException
+     */
+    public boolean hasNewMessages() throws MessagingException {
+        int oldLast = lastArticle;
+        updateGroupStats();
+
+        return lastArticle > oldLast;
+    }
+
+    /**
+     * Open the folder for use. This retrieves article count information from
+     * the server.
+     * 
+     * @exception MessagingException
+     */
+    protected void openFolder() throws MessagingException {
+        // update the group specifics, especially the message count.
+        updateGroupStats();
+
+        // get a cache for retrieved articles
+        articles = new HashMap();
+    }
+
+    /**
+     * Close the folder, which also clears out the article caches.
+     * 
+     * @exception MessagingException
+     */
+    public void closeFolder() throws MessagingException {
+        // get ride of any retrieve articles, and flip over the open for
+        // business sign.
+        articles = null;
+    }
+
+    /**
+     * Checks wether the message is in cache, if not will create a new message
+     * object and return it.
+     * 
+     * @see javax.mail.Folder#getMessage(int)
+     */
+    public Message getMessage(int msgNum) throws MessagingException {
+        // Can only be performed on an Open folder
+        checkOpen();
+
+        // get an object form to look up in the retrieve messages list (oh how I
+        // wish there was
+        // something like Map that could use integer keys directly!).
+        Integer key = new Integer(msgNum);
+        NNTPMessage message = (NNTPMessage) articles.get(key);
+        if (message != null) {
+            // piece of cake!
+            return message;
+        }
+
+        // we need to suck a message down from the server.
+        // but first, make sure the group is still valid.
+        updateGroupStats();
+
+        // just send a STAT command to this message. Right now, all we want is
+        // existance proof. We'll
+        // retrieve the other bits when requested.
+        NNTPReply reply = connection.sendCommand("STAT " + Integer.toString(msgNum));
+        if (reply.getCode() != NNTPReply.REQUEST_TEXT_SEPARATELY) {
+            throw new MessagingException("Error retrieving article from NNTP server: " + reply);
+        }
+
+        // we need to parse out the message id.
+        String response = reply.getMessage();
+
+        int idStart = response.indexOf('<');
+        int idEnd = response.indexOf('>');
+
+        // NB:  The "<" and ">" delimiters are required elements of the message id, not just 
+        // delimiters for the sake of the command.  We need to keep these around 
+        message = new NNTPMessage(this, (NNTPStore) store, msgNum, response.substring(idStart, idEnd + 1));
+
+        // add this to the article cache.
+        articles.put(key, message);
+
+        return message;
+    }
+
+    /**
+     * Retrieve all articles in the group.
+     * 
+     * @return An array of all messages in the group.
+     */
+    public Message[] getMessages() throws MessagingException {
+        // Can only be performed on an Open folder
+        checkOpen();
+        
+        // we're going to try first with XHDR, which will allow us to retrieve
+        // everything in one shot. If that
+        // fails, we'll fall back on issing STAT commands for the entire article
+        // range.
+        NNTPReply reply = connection.sendCommand("XHDR Message-ID " + Integer.toString(firstArticle) + "-"
+                + Integer.toString(lastArticle), NNTPReply.HEAD_FOLLOWS);
+
+        List messages = new ArrayList();
+
+        if (reply.getCode() == NNTPReply.HEAD_FOLLOWS) {
+            List lines = reply.getData();
+
+            for (int i = 0; i < lines.size(); i++) {
+                String line = (String) lines.get(i);
+
+                try {
+                    int pos = line.indexOf(' ');
+                    int articleID = Integer.parseInt(line.substring(0, pos));
+                    String messageID = line.substring(pos + 1);
+                    Integer key = new Integer(articleID);
+                    // see if we have this message cached, If not, create it.
+                    Message message = (Message)articles.get(key);
+                    if (message == null) {
+                        message = new NNTPMessage(this, (NNTPStore) store, key.intValue(), messageID);
+                        articles.put(key, message);
+                    }
+
+                    messages.add(message);
+
+                } catch (NumberFormatException e) {
+                    // should never happen, but just skip this entry if it does.
+                }
+            }
+        } else {
+            // grumble, we need to stat each article id to see if it
+            // exists....lots of round trips.
+            for (int i = firstArticle; i <= lastArticle; i++) {
+                try {
+                    messages.add(getMessage(i));
+                } catch (MessagingException e) {
+                    // just assume if there is an error, it's because the
+                    // message id doesn't exist.
+                }
+            }
+        }
+
+        return (Message[]) messages.toArray(new Message[0]);
+    }
+
+    /**
+     * @see javax.mail.Folder#fetch(javax.mail.Message[],
+     *      javax.mail.FetchProfile)
+     * 
+     * The JavaMail API recommends that this method be overrident to provide a
+     * meaningfull implementation.
+     */
+    public void fetch(Message[] msgs, FetchProfile fp) throws MessagingException {
+        // Can only be performed on an Open folder
+        checkOpen();
+
+        for (int i = 0; i < msgs.length; i++) {
+            Message msg = msgs[i];
+            // we can only perform this operation for NNTPMessages.
+            if (msg == null || !(msg instanceof NNTPMessage)) {
+                // we can't fetch if it's the wrong message type
+                continue;
+            }
+
+            // fetching both the headers and body?
+            if (fp.contains(FetchProfile.Item.ENVELOPE) && fp.contains(FetchProfile.Item.CONTENT_INFO)) {
+
+                // retrive everything
+                ((NNTPMessage) msg).loadArticle();
+            }
+            // headers only?
+            else if (fp.contains(FetchProfile.Item.ENVELOPE)) {
+                ((NNTPMessage) msg).loadHeaders();
+            } else if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
+                ((NNTPMessage) msg).loadContent();
+            }
+        }
+    }
+
+    /**
+     * Return the subscription status of this folder.
+     * 
+     * @return true if the folder is marked as subscribed, false for
+     *         unsubscribed.
+     */
+    public boolean isSubscribed() {
+        return groupInfo.isSubscribed();
+    }
+
+    /**
+     * Set or clear the subscription status of a file.
+     * 
+     * @param flag
+     *            The new subscription state.
+     */
+    public void setSubscribed(boolean flag) {
+        groupInfo.setSubscribed(flag);
+    }
+
+    /**
+     * Return the "seen" state for an article in a folder.
+     * 
+     * @param article
+     *            The article number.
+     * 
+     * @return true if the article is marked as seen in the newsrc file, false
+     *         for unseen files.
+     */
+    public boolean isSeen(int article) {
+        return groupInfo.isArticleSeen(article);
+    }
+
+    /**
+     * Set the seen state for an article in a folder.
+     * 
+     * @param article
+     *            The article number.
+     * @param flag
+     *            The new seen state.
+     */
+    public void setSeen(int article, boolean flag) {
+        if (flag) {
+            groupInfo.markArticleSeen(article);
+        } else {
+            groupInfo.markArticleUnseen(article);
+        }
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPMessage.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPMessage.java
new file mode 100644
index 0000000..e57d146
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPMessage.java
@@ -0,0 +1,377 @@
+/*
+ * 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.nntp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+
+import javax.mail.Flags;
+import javax.mail.IllegalWriteException;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.geronimo.javamail.transport.nntp.NNTPConnection;
+import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
+import org.apache.geronimo.javamail.transport.nntp.StringListInputStream;
+
+/**
+ * NNTP 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 NNTPMessage extends MimeMessage {
+    // the server message identifer
+    String messageID = null;
+
+    // our attached session
+    protected Session session;
+
+    // the Store we're stored in (which manages the connection and other stuff).
+    protected NNTPStore store;
+
+    // our active connection.
+    protected NNTPConnection connection;
+
+    // used to force loading of headers
+    protected boolean headersLoaded = false;
+
+    // use to force content loading
+    protected boolean contentLoaded = false;
+
+    /**
+     * Contruct an NNTPMessage instance.
+     * 
+     * @param folder
+     *            The hosting folder for the message.
+     * @param store
+     *            The Store owning the article (and folder).
+     * @param msgnum
+     *            The article message number.
+     * @param messageID
+     *            The article messageID (as assigned by the server).
+     * 
+     * @exception MessagingException
+     */
+    NNTPMessage(NNTPFolder folder, NNTPStore store, int msgnum, String messageID) throws MessagingException {
+        super(folder, msgnum);
+        this.messageID = messageID;
+        this.store = store;
+        this.session = ((NNTPStore) store).getSession();
+        // get the active connection from the store...all commands are sent
+        // there
+        this.connection = ((NNTPStore) store).getConnection();
+
+        // get our flag set from the folder.
+        flags = folder.getPermanentFlags();
+        // now check our initial SEEN state and set the flags appropriately
+        if (folder.isSeen(msgnum)) {
+            flags.add(Flags.Flag.SEEN);
+        } else {
+            flags.remove(Flags.Flag.SEEN);
+        }
+    }
+
+    /**
+     * 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 message content and continue with the
+        // superclass version.
+        loadContent();
+        return super.getSize();
+    }
+
+    /**
+     * Get a line count for the NNTP 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 {
+        String[] headers = getHeader("Lines");
+
+        // hopefully, there's only a single one of these. No sensible way of
+        // interpreting
+        // multiples.
+        if (headers.length == 1) {
+            try {
+                return Integer.parseInt(headers[0].trim());
+
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+        }
+        // dunno...and let them know I don't know.
+        return -1;
+    }
+
+    /**
+     * @see javax.mail.internet.MimeMessage#getContentStream()
+     */
+    protected InputStream getContentStream() throws MessagingException {
+        // get the article information.
+        loadArticle();
+        return super.getContentStream();
+    }
+
+    /***************************************************************************
+     * 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("NNTP messages are read-only");
+    }
+
+    public void setHeader(String name, String value) throws MessagingException {
+        throw new IllegalWriteException("NNTP messages are read-only");
+    }
+
+    public void removeHeader(String name) throws MessagingException {
+        throw new IllegalWriteException("NNTP 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("NNTP messages are read-only");
+    }
+
+    /**
+     * Retrieve the message headers from the NNTP server.
+     * 
+     * @exception MessagingException
+     */
+    public void loadHeaders() throws MessagingException {
+        // don't retrieve if already loaded.
+        if (headersLoaded) {
+            return;
+        }
+
+        NNTPReply reply = connection.sendCommand("HEAD " + messageID, NNTPReply.HEAD_FOLLOWS);
+
+        if (reply.getCode() == NNTPReply.HEAD_FOLLOWS) {
+            try {
+                // wrap a stream around the reply data and read as headers.
+                updateHeaders(new StringListInputStream(reply.getData()));
+            } catch (IOException e) {
+                throw new MessagingException("Error retrieving article headers from server", e);
+            }
+        } else {
+            throw new MessagingException("Error retrieving article headers from server: " + reply);
+        }
+    }
+
+    /**
+     * 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);
+        headersLoaded = true;
+    }
+
+    /**
+     * Load just the message content from the NNTP server.
+     * 
+     * @exception MessagingException
+     */
+    public void loadContent() throws MessagingException {
+        if (contentLoaded) {
+            return;
+        }
+
+        NNTPReply reply = connection.sendCommand("BODY " + messageID, NNTPReply.BODY_FOLLOWS);
+
+        if (reply.getCode() == NNTPReply.BODY_FOLLOWS) {
+            try {
+                InputStream in = new StringListInputStream(reply.getData());
+                updateContent(in);
+            } catch (IOException e) {
+                throw new MessagingException("Error retrieving article body from server", e);
+            }
+        } else {
+            throw new MessagingException("Error retrieving article body from server: " + reply);
+        }
+    }
+
+    /**
+     * Load the entire article from the NNTP server. This updates both the
+     * headers and the content.
+     * 
+     * @exception MessagingException
+     */
+    public void loadArticle() throws MessagingException {
+        // if the headers are already loaded, retrieve the content portion.
+        if (headersLoaded) {
+            loadContent();
+            return;
+        }
+
+        // we need to retrieve everything.
+        NNTPReply reply = connection.sendCommand("ARTICLE " + messageID, NNTPReply.ARTICLE_FOLLOWS);
+
+        if (reply.getCode() == NNTPReply.ARTICLE_FOLLOWS) {
+            try {
+                InputStream in = new StringListInputStream(reply.getData());
+                // update both the headers and the content.
+                updateHeaders(in);
+                updateContent(in);
+            } catch (IOException e) {
+                throw new MessagingException("Error retrieving article from server", e);
+            }
+        } else {
+            throw new MessagingException("Error retrieving article from server: " + reply);
+        }
+    }
+
+    /**
+     * Update the article content from an input stream.
+     * 
+     * @param in
+     *            The content data source.
+     * 
+     * @exception MessagingException
+     */
+    public void updateContent(InputStream in) throws MessagingException {
+        try {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+            byte[] buffer = new byte[4096];
+
+            // copy the content data from the stream into a byte buffer for the
+            // content.
+            while (true) {
+                int read = in.read(buffer);
+                if (read == -1) {
+                    break;
+                }
+                out.write(buffer, 0, read);
+            }
+
+            content = out.toByteArray();
+            contentLoaded = true;
+        } catch (IOException e) {
+            throw new MessagingException("Error retrieving message body from server", e);
+        }
+    }
+
+    /**
+     * Get the server assigned messageid for the article.
+     * 
+     * @return The server assigned message id.
+     */
+    public String getMessageId() {
+        return messageID;
+    }
+
+    /**
+     * Override of setFlags(). We need to ensure that if the SEEN flag is set or
+     * cleared, that the newsrc file correctly reflects the current state.
+     * 
+     * @param flag
+     *            The flag being set.
+     * @param newvalue
+     *            The new flag value.
+     * 
+     * @exception MessagingException
+     */
+    public void setFlags(Flags flag, boolean newvalue) throws MessagingException {
+        // if this is the SEEN flag, make sure we shadow this in the newsrc
+        // file.
+        if (flag.contains(Flags.Flag.SEEN)) {
+            ((NNTPFolder) folder).setSeen(msgnum, newvalue);
+        }
+        // have the superclass do the real flag setting.
+        super.setFlags(flag, newvalue);
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPRootFolder.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPRootFolder.java
new file mode 100644
index 0000000..605c59e
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPRootFolder.java
@@ -0,0 +1,399 @@
+/*
+ * 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.nntp;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcGroup;
+import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
+import org.apache.geronimo.mail.util.SessionUtil;
+
+/**
+ * The base NNTP implementation of the javax.mail.Folder This is a base class
+ * for both the Root NNTP server and each NNTP group folder.
+ * 
+ * @see javax.mail.Folder
+ * 
+ * @version $Rev$
+ */
+public class NNTPRootFolder extends NNTPFolder {
+    protected static final String NNTP_LISTALL = "mail.nntp.listall";
+
+    /**
+     * Construct the NNTPRootFolder.
+     * 
+     * @param store
+     *            The owning Store.
+     * @param name
+     *            The folder name (by default, this is the server host name).
+     * @param fullName
+     *            The fullName to use for this server (derived from welcome
+     *            string).
+     */
+    protected NNTPRootFolder(NNTPStore store, String name, String fullName) {
+        super(store);
+
+        this.name = name;
+        this.fullName = fullName;
+    }
+
+    /**
+     * List the subfolders. For group folders, this is a meaningless so we throw
+     * a MethodNotSupportedException.
+     * 
+     * @param pattern
+     *            The folder pattern string.
+     * 
+     * @return Never returns.
+     * @exception MessagingException
+     */
+    public synchronized Folder[] list(String pattern) throws MessagingException {
+        // the pattern specfied for javamail uses two wild card characters, "%"
+        // and "*". The "%" matches
+        // and character except hierarchy separators. Since we have a flag
+        // hierarchy, "%" and "*" are
+        // essentially the same. If we convert the "%" into "*", we can just
+        // treat this as a wildmat
+        // formatted pattern and pass this on to the server rather than having
+        // to read everything and
+        // process the strings on the client side.
+
+        pattern = pattern.replace('%', '*');
+
+        // if we're not supposed to list everything, then just filter the list
+        // of subscribed groups.
+        if (SessionUtil.getBooleanProperty(NNTP_LISTALL, false)) {
+            return filterActiveGroups(pattern);
+        } else {
+            return filterSubscribedGroups(pattern);
+        }
+    }
+
+    /**
+     * Retrieve the list of subscribed folders that match the given pattern
+     * string.
+     * 
+     * @param pattern
+     *            The pattern string used for the matching
+     * 
+     * @return An array of matching folders from the subscribed list.
+     */
+    public Folder[] listSubscribed(String pattern) throws MessagingException {
+        // the pattern specfied for javamail uses two wild card characters, "%"
+        // and "*". The "%" matches
+        // and character except hierarchy separators. Since we have a flag
+        // hierarchy, "%" and "*" are
+        // essentially the same. If we convert the "%" into "*", we can just
+        // treat this as a wildmat
+        // formatted pattern and pass this on to the server rather than having
+        // to read everything and
+        // process the strings on the client side.
+
+        pattern = pattern.replace('%', '*');
+
+        return filterSubscribedGroups(pattern);
+    }
+
+    /**
+     * Retrieve the list of matching groups from the NNTP server using the LIST
+     * ACTIVE 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 Folder[] filterActiveGroups(String pattern) throws MessagingException {
+        NNTPReply reply = connection.sendCommand("LIST ACTIVE " + pattern, NNTPReply.LIST_FOLLOWS);
+
+        // if the LIST ACTIVE command isn't supported,
+        if (reply.getCode() == NNTPReply.COMMAND_NOT_RECOGNIZED) {
+            // only way to list all is to retrieve all and filter.
+            return filterAllGroups(pattern);
+        } else if (reply.getCode() != NNTPReply.LIST_FOLLOWS) {
+            throw new MessagingException("Error retrieving group list from NNTP server: " + reply);
+        }
+
+        // get the response back from the server and process each returned group
+        // name.
+        List groups = reply.getData();
+
+        Folder[] folders = new Folder[groups.size()];
+        for (int i = 0; i < groups.size(); i++) {
+            folders[i] = getFolder(getGroupName((String) groups.get(i)));
+        }
+        return folders;
+    }
+
+    /**
+     * Retrieve a list of all groups from the server and filter on the names.
+     * Not recommended for the usenet servers, as there are over 30000 groups to
+     * process.
+     * 
+     * @param pattern
+     *            The pattern string used for the selection.
+     * 
+     * @return The Folders for the matching groups.
+     */
+    protected Folder[] filterAllGroups(String pattern) throws MessagingException {
+        NNTPReply reply = connection.sendCommand("LIST", NNTPReply.LIST_FOLLOWS);
+
+        if (reply.getCode() != NNTPReply.LIST_FOLLOWS) {
+            throw new MessagingException("Error retrieving group list from NNTP server: " + reply);
+        }
+
+        // get the response back from the server and process each returned group
+        // name.
+        List groups = reply.getData();
+
+        WildmatMatcher matcher = new WildmatMatcher(pattern);
+
+        List folders = new ArrayList();
+        for (int i = 0; i < groups.size(); i++) {
+            String name = getGroupName((String) groups.get(i));
+            // does this match our pattern? Add to the list
+            if (matcher.matches(name)) {
+                folders.add(getFolder(name));
+            }
+        }
+        return (Folder[]) folders.toArray(new Folder[0]);
+    }
+
+    /**
+     * Return the set of groups from the newsrc subscribed groups list that
+     * match a given filter.
+     * 
+     * @param pattern
+     *            The selection pattern.
+     * 
+     * @return The Folders for the matching groups.
+     */
+    protected Folder[] filterSubscribedGroups(String pattern) throws MessagingException {
+        Iterator groups = ((NNTPStore) store).getNewsrcGroups();
+
+        WildmatMatcher matcher = new WildmatMatcher(pattern);
+
+        List folders = new ArrayList();
+        while (groups.hasNext()) {
+            NNTPNewsrcGroup group = (NNTPNewsrcGroup) groups.next();
+            if (group.isSubscribed()) {
+                // does this match our pattern? Add to the list
+                if (matcher.matches(group.getName())) {
+                    folders.add(getFolder(group.getName()));
+                }
+            }
+        }
+        return (Folder[]) folders.toArray(new Folder[0]);
+    }
+
+    /**
+     * Utility method for extracting a name from a group list response.
+     * 
+     * @param response
+     *            The response string.
+     * 
+     * @return The group name.
+     */
+    protected String getGroupName(String response) {
+        int blank = response.indexOf(' ');
+        return response.substring(0, blank).trim();
+    }
+
+    /**
+     * Return whether this folder can hold just messages or also subfolders.
+     * Only the root folder can hold other folders, so it will need to override.
+     * 
+     * @return Always returns Folder.HOLDS_FOLDERS.
+     * @exception MessagingException
+     */
+    public int getType() throws MessagingException {
+        return HOLDS_FOLDERS;
+    }
+
+    /**
+     * Get a new folder from the root folder. This creates a new folder, which
+     * might not actually exist on the server. If the folder doesn't exist, an
+     * error will occur on folder open.
+     * 
+     * @param name
+     *            The name of the requested folder.
+     * 
+     * @return A new folder object for this folder.
+     * @exception MessagingException
+     */
+    public Folder getFolder(String name) throws MessagingException {
+        // create a new group folder and return
+        return new NNTPGroupFolder(this, (NNTPStore) store, name, ((NNTPStore) store).getNewsrcGroup(name));
+    }
+
+    /**
+     * Utility class to do Wildmat pattern matching on folder names.
+     */
+    class WildmatMatcher {
+        // middle match sections...because these are separated by wildcards, if
+        // they appear in
+        // sequence in the string, it is a match.
+        List matchSections = new ArrayList();
+
+        // just a "*" match, so everything is true
+        boolean matchAny = false;
+
+        // no wildcards, so this must be an exact match.
+        String exactMatch = null;
+
+        // a leading section which must be at the beginning
+        String firstSection = null;
+
+        // a trailing section which must be at the end of the string.
+        String lastSection = null;
+
+        /**
+         * Create a wildmat pattern matcher.
+         * 
+         * @param pattern
+         *            The wildmat pattern to apply to string matches.
+         */
+        public WildmatMatcher(String pattern) {
+            int section = 0;
+
+            // handle the easy cases first
+
+            // single wild card?
+            if (pattern.equals("*")) {
+                matchAny = true;
+                return;
+            }
+
+            // find the first wild card
+            int wildcard = pattern.indexOf('*');
+
+            // no wild card at all?
+            if (wildcard == -1) {
+                exactMatch = pattern;
+                return;
+            }
+
+            // pattern not begin with a wildcard? We need to pull off the
+            // leading section
+            if (!pattern.startsWith("*")) {
+                firstSection = pattern.substring(0, wildcard);
+                section = wildcard + 1;
+                // this could be "yada*", so we could be done.
+                if (section >= pattern.length()) {
+                    return;
+                }
+            }
+
+            // now parse off the middle sections, making sure to handle the end
+            // condition correctly.
+            while (section < pattern.length()) {
+                // find the next wildcard position
+                wildcard = pattern.indexOf('*', section);
+                if (wildcard == -1) {
+                    // not found, we're at the end of the pattern. We need to
+                    // match on the end.
+                    lastSection = pattern.substring(section);
+                    return;
+                }
+                // we could have a null section, which we'll just ignore.
+                else if (wildcard == section) {
+                    // step over the wild card
+                    section++;
+                } else {
+                    // pluck off the next section
+                    matchSections.add(pattern.substring(section, wildcard));
+                    // step over the wild card character and check if we've
+                    // reached the end.
+                    section = wildcard + 1;
+                }
+            }
+        }
+
+        /**
+         * Test if a name string matches to parsed wildmat pattern.
+         * 
+         * @param name
+         *            The name to test.
+         * 
+         * @return true if the string matches the pattern, false otherwise.
+         */
+        public boolean matches(String name) {
+
+            // handle the easy cases first
+
+            // full wildcard? Always matches
+            if (matchAny) {
+                return true;
+            }
+
+            // required exact matches are easy.
+            if (exactMatch != null) {
+                return exactMatch.equals(name);
+            }
+
+            int span = 0;
+
+            // must match the beginning?
+            if (firstSection != null) {
+                // if it doesn't start with that, it can't be true.
+                if (!name.startsWith(firstSection)) {
+                    return false;
+                }
+
+                // we do all additional matching activity from here.
+                span = firstSection.length();
+            }
+
+            // scan for each of the sections along the string
+            for (int i = 1; i < matchSections.size(); i++) {
+                // if a section is not found, this is false
+
+                String nextMatch = (String) matchSections.get(i);
+                int nextLocation = name.indexOf(nextMatch, span);
+                if (nextLocation == -1) {
+                    return false;
+                }
+                // step over that one
+                span = nextMatch.length() + nextLocation;
+            }
+
+            // we've matched everything up to this point, now check to see if
+            // need an end match
+            if (lastSection != null) {
+                // we need to have at least the number of characters of the end
+                // string left, else this fails.
+                if (name.length() - span < lastSection.length()) {
+                    return false;
+                }
+
+                // ok, make sure we end with this string
+                return name.endsWith(lastSection);
+            }
+
+            // no falsies, this must be the truth.
+            return true;
+        }
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPSSLStore.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPSSLStore.java
new file mode 100644
index 0000000..58a0351
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPSSLStore.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.nntp;
+
+import javax.mail.Session;
+import javax.mail.URLName;
+
+/**
+ * NNTP implementation of javax.mail.Store over an SSL connection.
+ *
+ * @version $Rev$ $Date$
+ */
+public class NNTPSSLStore extends NNTPStore {
+    /**
+     * Construct an NNTPSSLStore item.
+     *
+     * @param session The owning javamail Session.
+     * @param urlName The Store urlName, which can contain server target information.
+     */
+	public NNTPSSLStore(Session session, URLName urlName) {
+        // we're the imaps protocol, our default connection port is 563, and we must use
+        // an SSL connection for the initial hookup 
+		super(session, urlName, "nntps", DEFAULT_NNTP_SSL_PORT, true);
+	}
+}
+
+
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPStore.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPStore.java
new file mode 100644
index 0000000..0ab30fc
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPStore.java
@@ -0,0 +1,260 @@
+/*
+ * 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.nntp;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.Iterator;
+
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.URLName;
+
+import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrc;
+import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcFile;
+import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcGroup;
+import org.apache.geronimo.javamail.transport.nntp.NNTPConnection;
+import org.apache.geronimo.javamail.util.ProtocolProperties; 
+import org.apache.geronimo.mail.util.SessionUtil;
+
+/**
+ * NNTP implementation of javax.mail.Store POP protocol spec is implemented in
+ * org.apache.geronimo.javamail.store.pop3.NNTPConnection
+ * 
+ * @version $Rev$ $Date$
+ */
+public class NNTPStore extends Store {
+    protected static final String NNTP_NEWSRC = "newsrc";
+
+    protected static final String protocol = "nntp";
+
+    protected static final int DEFAULT_NNTP_PORT = 119;
+    protected static final int DEFAULT_NNTP_SSL_PORT = 563;
+
+    // our accessor for protocol properties and the holder of 
+    // protocol-specific information 
+    protected ProtocolProperties props; 
+    // our active connection object (shared code with the NNTPStore).
+    protected NNTPConnection connection;
+
+    // the root folder
+    protected NNTPRootFolder root;
+    // the newsrc file where we store subscriptions and seen message markers.
+    protected NNTPNewsrc newsrc;
+    
+    /**
+     * Construct an NNTPStore item. This will load the .newsrc file associated
+     * with the server.
+     * 
+     * @param session
+     *            The owning javamail Session.
+     * @param name
+     *            The Store urlName, which can contain server target
+     *            information.
+     */
+    public NNTPStore(Session session, URLName name) {
+        this(session, name, "nntp", DEFAULT_NNTP_PORT, false);
+    }
+
+    /**
+     * Common constructor used by the POP3Store and POP3SSLStore classes
+     * to do common initialization of defaults.
+     *
+     * @param session
+     *            The host session instance.
+     * @param name
+     *            The URLName of the target.
+     * @param protocol
+     *            The protocol type ("nntp" or "nntps"). This helps us in
+     *            retrieving protocol-specific session properties.
+     * @param defaultPort
+     *            The default port used by this protocol. For pop3, this will
+     *            be 110. The default for pop3 with ssl is 995.
+     * @param sslConnection
+     *            Indicates whether an SSL connection should be used to initial
+     *            contact the server. This is different from the STARTTLS
+     *            support, which switches the connection to SSL after the
+     *            initial startup.
+     */
+    protected NNTPStore(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
+        super(session, name);
+        
+        // create the protocol property holder.  This gives an abstraction over the different 
+        // flavors of the protocol. 
+        props = new ProtocolProperties(session, protocol, sslConnection, defaultPort); 
+
+        // the connection manages connection for the transport 
+        connection = new NNTPConnection(props); 
+    }
+
+    /**
+     * @see javax.mail.Store#getDefaultFolder()
+     * 
+     * This returns a root folder object for all of the news groups.
+     */
+    public Folder getDefaultFolder() throws MessagingException {
+        checkConnectionStatus();
+        if (root == null) {
+            return new NNTPRootFolder(this, connection.getHost(), connection.getWelcomeString());
+        }
+        return root;
+    }
+
+    /**
+     * @see javax.mail.Store#getFolder(java.lang.String)
+     */
+    public Folder getFolder(String name) throws MessagingException {
+        return getDefaultFolder().getFolder(name);
+    }
+
+    /**
+     * 
+     * @see javax.mail.Store#getFolder(javax.mail.URLName)
+     */
+    public Folder getFolder(URLName url) throws MessagingException {
+        return getDefaultFolder().getFolder(url.getFile());
+    }
+
+    
+    /**
+     * Do the protocol connection for an NNTP transport. This handles server
+     * authentication, if possible. Returns false if unable to connect to the
+     * server.
+     * 
+     * @param host
+     *            The target host name.
+     * @param port
+     *            The server port number.
+     * @param user
+     *            The authentication user (if any).
+     * @param password
+     *            The server password. Might not be sent directly if more
+     *            sophisticated authentication is used.
+     * 
+     * @return true if we were able to connect to the server properly, false for
+     *         any failures.
+     * @exception MessagingException
+     */
+    protected boolean protocolConnect(String host, int port, String username, String password)
+            throws MessagingException {
+        // the connection pool handles all of the details here. But don't proceed 
+        // without a connection 
+        if (!connection.protocolConnect(host, port, username, password)) {
+            return false; 
+        }
+
+        // see if we have a newsrc file location specified
+        String newsrcFile = props.getProperty(NNTP_NEWSRC);
+
+        File source = null;
+
+        // not given as a property? Then look for a file in user.home
+        if (newsrcFile != null) {
+            source = new File(newsrcFile);
+        } else {
+            // ok, look for a file in the user.home directory. If possible,
+            // we'll try for a file
+            // with the hostname appended.
+            String home = SessionUtil.getProperty("user.home");
+
+            // try for a host-specific file first. If not found, use (and
+            // potentially create) a generic
+            // .newsrc file.
+            newsrcFile = ".newsrc-" + host;
+            source = new File(home, newsrcFile);
+            if (!source.exists()) {
+                source = new File(home, ".newsrc");
+            }
+        }
+
+        // now create a newsrc read and load the file.
+        newsrc = new NNTPNewsrcFile(source);
+        newsrc.load();
+
+        // we're going to return success here, but in truth, the server may end
+        // up asking for our bonafides at any time, and we'll be expected to authenticate then.
+        return true;
+    }
+    
+
+    /**
+     * @see javax.mail.Service#close()
+     */
+    public void close() throws MessagingException {
+        // This is done to ensure proper event notification.
+        super.close();
+        // persist the newsrc file, if possible
+        if (newsrc != null) {
+            newsrc.close();
+            newsrc = null; 
+        }
+        connection.close();
+        connection = null;
+    }
+
+    private void checkConnectionStatus() throws MessagingException {
+        if (!this.isConnected()) {
+            throw new MessagingException("Not connected ");
+        }
+    }
+
+    /**
+     * Retrieve the server connection created by this store.
+     * 
+     * @return The active connection object.
+     */
+    NNTPConnection getConnection() {
+        return connection;
+    }
+
+    /**
+     * Retrieve the Session object this Store is operating under.
+     * 
+     * @return The attached Session instance.
+     */
+    Session getSession() {
+        return session;
+    }
+
+    /**
+     * Retrieve all of the groups we nave persistent store information about.
+     * 
+     * @return The set of groups contained in the newsrc file.
+     */
+    Iterator getNewsrcGroups() {
+        return newsrc.getGroups();
+    }
+
+    /**
+     * Retrieve the newsrc group information for a named group. If the file does
+     * not currently include this group, an unsubscribed group will be added to
+     * the file.
+     * 
+     * @param name
+     *            The name of the target group.
+     * 
+     * @return The NNTPNewsrcGroup item corresponding to this name.
+     */
+    NNTPNewsrcGroup getNewsrcGroup(String name) {
+        return newsrc.getGroup(name);
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrc.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrc.java
new file mode 100644
index 0000000..def485c
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrc.java
@@ -0,0 +1,182 @@
+/*
+ * 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.nntp.newsrc;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Base class implementation of a standard news reader news rc file. This is
+ * used to track newsgroup subscriptions and SEEN flags for articles. This is an
+ * abstract class designed for subclasses to bridge to the physical store type
+ * used for the newsgroup information.
+ */
+public abstract class NNTPNewsrc {
+
+    // the group information we've read from the news rc file.
+    Map groups = new HashMap();
+
+    // flag to let us know of we need to persist the newsrc file on close.
+    boolean dirty = false;
+
+    /**
+     * Base class constructor for NNTPNewsrc items. Subclasses provide their own
+     * domain-specific intialization.
+     */
+    protected NNTPNewsrc() {
+    }
+
+    /**
+     * Load the data from the newsrc file and parse into an instore group
+     * database.
+     */
+    public void load() {
+        BufferedReader in = null;
+
+        try {
+            in = getInputReader();
+
+            String line = in.readLine();
+
+            while (line != null) {
+                // parse the line...this returns null if it's something
+                // unrecognized.
+                NNTPNewsrcGroup group = NNTPNewsrcGroup.parse(this, line);
+                // if it parsed ok, add it to the group list, and potentially to
+                // the subscribed list.
+                if (group != null) {
+                    groups.put(group.getName(), group);
+                }
+
+                line = in.readLine();
+            }
+
+            in.close();
+        } catch (IOException e) {
+            // an IOException may mean that the file just doesn't exist, which
+            // is fine. We'll ignore and
+            // proceed with the information we have.
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    /**
+     * Save the newsrc file data back to the original source file.
+     * 
+     * @exception IOException
+     */
+    public void save() throws IOException {
+        Writer out = getOutputWriter();
+
+        Iterator i = groups.values().iterator();
+
+        while (i.hasNext()) {
+            NNTPNewsrcGroup group = (NNTPNewsrcGroup) i.next();
+            group.save(out);
+        }
+
+        out.close();
+    }
+
+    /**
+     * Abstract open method intended for sub class initialization. The subclass
+     * is responsible for creating the BufferedReaded used to read the .newsrc
+     * file.
+     * 
+     * @return A BufferedReader for reading the .newsrc file.
+     * @exception IOException
+     */
+    abstract public BufferedReader getInputReader() throws IOException;
+
+    /**
+     * Abstract open for output method intended for subclass implementation. The
+     * subclasses are reponsible for opening the output stream and creating an
+     * appropriate Writer for saving the .newsrc file.
+     * 
+     * @return A Writer target at the .newsrc file save location.
+     * @exception IOException
+     */
+    abstract public Writer getOutputWriter() throws IOException;
+
+    /**
+     * Retrieve the newsrc group information for a named group. If the file does
+     * not currently include this group, an unsubscribed group will be added to
+     * the file.
+     * 
+     * @param name
+     *            The name of the target group.
+     * 
+     * @return The NNTPNewsrcGroup item corresponding to this name.
+     */
+    public NNTPNewsrcGroup getGroup(String name) {
+        NNTPNewsrcGroup group = (NNTPNewsrcGroup) groups.get(name);
+        // if we don't know about this, create a new one and add to the list.
+        // This
+        // will be an unsubscribed one.
+        if (group == null) {
+            group = new NNTPNewsrcGroup(this, name, null, false);
+            groups.put(name, group);
+            // we've added a group, so we need to resave
+            dirty = true;
+        }
+        return group;
+    }
+
+    /**
+     * Mark this newsrc database as dirty.
+     */
+    public void setDirty() {
+        dirty = true;
+    }
+
+    /**
+     * Close the newsrc file, persisting it back to disk if the file has
+     * changed.
+     */
+    public void close() {
+        if (dirty) {
+            try {
+                save();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Retrieve the current set of loaded groups.
+     * 
+     * @return An iterator for traversing the group set.
+     */
+    public Iterator getGroups() {
+        return groups.values().iterator();
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcFile.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcFile.java
new file mode 100644
index 0000000..8a8a97c
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcFile.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.nntp.newsrc;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+public class NNTPNewsrcFile extends NNTPNewsrc {
+    // source for the file data
+    File source;
+
+    /**
+     * Construct a NNTPNewsrc object that is targetted at a file-based backing
+     * store.
+     * 
+     * @param source
+     *            The source File for the .newsrc data.
+     */
+    public NNTPNewsrcFile(File source) {
+        this.source = source;
+    }
+
+    /**
+     * Retrieve an input reader for loading the newsrc file.
+     * 
+     * @return A BufferedReader object for reading from the newsrc file.
+     * @exception IOException
+     */
+    public BufferedReader getInputReader() throws IOException {
+        return new BufferedReader(new InputStreamReader(new FileInputStream(source), "ISO8859-1"));
+    }
+
+    /**
+     * Obtain a writer for saving a newsrc file.
+     * 
+     * @return The output writer targetted to the newsrc file.
+     * @exception IOException
+     */
+    public Writer getOutputWriter() throws IOException {
+        // open this for overwriting
+        return new OutputStreamWriter(new FileOutputStream(source, false), "ISO8859-1");
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcGroup.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcGroup.java
new file mode 100644
index 0000000..b628361
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcGroup.java
@@ -0,0 +1,185 @@
+/*
+ * 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.nntp.newsrc;
+
+import java.io.IOException;
+import java.io.Writer;
+
+public class NNTPNewsrcGroup {
+    // the newsrc database we're part of
+    NNTPNewsrc newsrc;
+
+    // the name of the group
+    protected String name;
+
+    // the subscription flage
+    protected boolean subscribed;
+
+    // the range of already seen articles.
+    protected RangeList ranges;
+
+    /**
+     * Construct a NNTPNewsrcGroup item associated with a given .newsrc
+     * database.
+     * 
+     * @param newsrc
+     *            The owning .newsrc database.
+     * @param line
+     *            The .newsrc range entries in .newsrc format. These ranges are
+     *            parsed to create a set of seen flags.
+     * 
+     * @return A created NNTPNewsrcGroup item.
+     */
+    public static NNTPNewsrcGroup parse(NNTPNewsrc newsrc, String line) {
+        String groupName = null;
+        String ranges = null;
+
+        // subscribed lines have a ':' marker acting as a delimiter
+        int marker = line.indexOf(':');
+
+        if (marker != -1) {
+            groupName = line.substring(0, marker);
+            ranges = line.substring(marker + 1);
+            return new NNTPNewsrcGroup(newsrc, groupName, ranges, true);
+        }
+
+        // now check for an unsubscribed group
+        marker = line.indexOf('!');
+
+        if (marker != -1) {
+            groupName = line.substring(0, marker);
+            ranges = line.substring(marker + 1);
+            return new NNTPNewsrcGroup(newsrc, groupName, ranges, false);
+        }
+
+        // must be a comment line
+        return null;
+    }
+
+    /**
+     * Construct a .newsrc group item.
+     * 
+     * @param newsrc
+     *            The owning newsrc database.
+     * @param name
+     *            The group name.
+     * @param newsrcRanges
+     *            The initial set of seen ranges for the group (may be null).
+     * @param subscribed
+     *            The initial group subscription state.
+     */
+    public NNTPNewsrcGroup(NNTPNewsrc newsrc, String name, String newsrcRanges, boolean subscribed) {
+        this.newsrc = newsrc;
+        this.name = name;
+        this.subscribed = subscribed;
+        this.ranges = new RangeList(newsrcRanges);
+    }
+
+    /**
+     * Get the group name.
+     * 
+     * @return The String name of the group.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Get the newsrc subscribed status for an article.
+     * 
+     * @return The current subscription flag.
+     */
+    public boolean isSubscribed() {
+        return subscribed;
+    }
+
+    /**
+     * Set the subscription status for an article.
+     * 
+     * @param flag
+     *            The new subscription value.
+     */
+    public void setSubscribed(boolean flag) {
+        // we don't blindly set this to the new value since we only want to
+        // resave the newsrc file if
+        // something changes.
+        if (flag && !subscribed) {
+            subscribed = true;
+            newsrc.setDirty();
+        } else if (!flag && subscribed) {
+            subscribed = false;
+            newsrc.setDirty();
+        }
+    }
+
+    /**
+     * Test if an article has been seen yet.
+     * 
+     * @param article
+     *            The target article.
+     * 
+     * @return The seen mark for the article.
+     */
+    public boolean isArticleSeen(int article) {
+        return ranges.isMarked(article);
+    }
+
+    /**
+     * Mark an article as seen.
+     * 
+     * @param article
+     *            The target article number.
+     */
+    public void markArticleSeen(int article) {
+        ranges.setMarked(article);
+        if (ranges.isDirty()) {
+            newsrc.setDirty();
+        }
+    }
+
+    /**
+     * Mark an article as unseen.
+     * 
+     * @param article
+     *            The target article number.
+     */
+    public void markArticleUnseen(int article) {
+        ranges.setUnmarked(article);
+        if (ranges.isDirty()) {
+            newsrc.setDirty();
+        }
+    }
+
+    /**
+     * Save this group definition to a .newsrc file.
+     * 
+     * @param out
+     *            The output writer to send the information to.
+     * 
+     * @exception IOException
+     */
+    public void save(Writer out) throws IOException {
+        out.write(name);
+        out.write(subscribed ? ": " : "! ");
+        ranges.save(out);
+        // put a terminating line end
+        out.write("\r\n");
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/Range.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/Range.java
new file mode 100644
index 0000000..90e4707
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/Range.java
@@ -0,0 +1,318 @@
+/*
+ * 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.nntp.newsrc;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Represent a single Range in a newsrc file. A Range can be either a single
+ * number (start == end) or a span of article numbers.
+ */
+public class Range {
+    // the low end of the range
+    int start;
+
+    // the high end of the range (start and end are inclusive);
+    int end;
+
+    /**
+     * Construct a Range item for a single digit range.
+     * 
+     * @param spot
+     *            The location of the singleton.
+     */
+    public Range(int spot) {
+        this(spot, spot);
+    }
+
+    /**
+     * Construct a Range item.
+     * 
+     * @param start
+     *            The starting point of the Range.
+     * @param end
+     *            The Range end point (which may be equal to the starting
+     *            point).
+     */
+    public Range(int start, int end) {
+        this.start = start;
+        this.end = end;
+    }
+
+    /**
+     * Parse a section of a .newsrc range string into a single Range item. The
+     * range is either a single number, or a pair of numbers separated by a
+     * hyphen.
+     * 
+     * @param range
+     *            The range string.
+     * 
+     * @return A constructed Range item, or null if there is a parsing error.
+     */
+    static public Range parse(String range) {
+        // a range from a newsrc file is either a single number or in the format
+        // 'nnnn-mmmm'. We need
+        // to figure out which type this is.
+        int marker = range.indexOf('-');
+
+        try {
+            if (marker != -1) {
+                String rangeStart = range.substring(0, marker).trim();
+                String rangeEnd = range.substring(marker + 1).trim();
+
+                int start = Integer.parseInt(rangeStart);
+                int end = Integer.parseInt(rangeEnd);
+
+                if (start >= 0 && end >= 0) {
+                    return new Range(start, end);
+                }
+            } else {
+                // use the entire token
+                int start = Integer.parseInt(range);
+                // and start and the end are the same
+                return new Range(start, start);
+
+            }
+        } catch (NumberFormatException e) {
+        }
+        // return null for any bad values
+        return null;
+    }
+
+    /**
+     * Get the starting point for the Range.
+     * 
+     * @return The beginning of the mark range.
+     */
+    public int getStart() {
+        return start;
+    }
+
+    /**
+     * Set the starting point for a Range.
+     * 
+     * @param start
+     *            The new start value.
+     */
+    public void setStart(int start) {
+        this.start = start;
+    }
+
+    /**
+     * Get the ending point for the Range.
+     * 
+     * @return The end of the mark range.
+     */
+    public int getEnd() {
+        return end;
+    }
+
+    /**
+     * Set the ending point for a Range.
+     * 
+     * @param end
+     *            The new end value.
+     */
+    public void setEnd(int end) {
+        this.end = end;
+    }
+
+    /**
+     * Test if a range contains a point value.
+     * 
+     * @param target
+     *            The article location to test.
+     * 
+     * @return True if the target is between the start and end values,
+     *         inclusive.
+     */
+    public boolean contains(int target) {
+        return target >= start && target <= end;
+    }
+
+    /**
+     * Test if one range is completely contained within another Range.
+     * 
+     * @param other
+     *            The other test range.
+     * 
+     * @return true if the other start and end points are contained within this
+     *         range.
+     */
+    public boolean contains(Range other) {
+        return contains(other.getStart()) && contains(other.getEnd());
+    }
+
+    /**
+     * Tests if two ranges overlap
+     * 
+     * @param other
+     *            The other test range.
+     * 
+     * @return true if the start or end points of either range are contained
+     *         within the range of the other.
+     */
+    public boolean overlaps(Range other) {
+        return other.contains(start) || other.contains(end) || contains(other.getStart()) || contains(other.getEnd());
+    }
+
+    /**
+     * Test if two ranges exactly abutt each other.
+     * 
+     * @param other
+     *            The other Range to test.
+     * 
+     * @return true if the end of one range abutts the start of the other range.
+     */
+    public boolean abutts(Range other) {
+        return other.getStart() == end + 1 || other.getEnd() == start - 1;
+    }
+
+    /**
+     * Tests if a single point abutts either the start or end of this Range.
+     * 
+     * @param article
+     *            The point to test.
+     * 
+     * @return true if test point is equal to start - 1 or end + 1.
+     */
+    public boolean abutts(int article) {
+        return article == start - 1 || article == end + 1;
+    }
+
+    /**
+     * Test if a point is below the test Range.
+     * 
+     * @param article
+     *            The point to test.
+     * 
+     * @return true if the entire range is less than the test point.
+     */
+    public boolean lessThan(int article) {
+        return end < article;
+    }
+
+    /**
+     * Test if another Range is less than this Range.
+     * 
+     * @param other
+     *            The other Range to test.
+     * 
+     * @return true if the other Range lies completely below this Range.
+     */
+    public boolean lessThan(Range other) {
+        return end < other.start;
+    }
+
+    /**
+     * Test if a point is above the test Range.
+     * 
+     * @param article
+     *            The point to test.
+     * 
+     * @return true if the entire range is greater than the test point.
+     */
+    public boolean greaterThan(int article) {
+        return start > article;
+    }
+
+    /**
+     * Test if another Range is greater than this Range.
+     * 
+     * @param other
+     *            The other Range to test.
+     * 
+     * @return true if the other Range lies completely below this Range.
+     */
+    public boolean greaterThan(Range other) {
+        return start > other.end;
+    }
+
+    /**
+     * Merge another Range into this one. Merging will increase the bounds of
+     * this Range to encompass the entire span of the two. If the Ranges do not
+     * overlap, the newly created range will include the gap between the two.
+     * 
+     * @param other
+     *            The Range to merge.
+     */
+    public void merge(Range other) {
+        if (other.start < start) {
+            start = other.start;
+        }
+
+        if (other.end > end) {
+            end = other.end;
+        }
+    }
+
+    /**
+     * Split a range at a given split point. Splitting will truncate at the
+     * split location - 1 and return a new range beginning at location + 1; This
+     * code assumes that the split location is at neither end poing.
+     * 
+     * @param location
+     *            The split location. Location must be in the range start <
+     *            location < end.
+     * 
+     * @return A new Range object for the split portion of the range.
+     */
+    public Range split(int location) {
+        int newEnd = end;
+
+        end = location - 1;
+
+        return new Range(location + 1, newEnd);
+    }
+
+    /**
+     * Save an individual range element to a newsrc file. The range is expressed
+     * either as a single number, or a hypenated pair of numbers.
+     * 
+     * @param out
+     *            The output writer used to save the data.
+     * 
+     * @exception IOException
+     */
+    public void save(Writer out) throws IOException {
+        // do we have a single data point range?
+        if (start == end) {
+            out.write(Integer.toString(start));
+        } else {
+            out.write(Integer.toString(start));
+            out.write("-");
+            out.write(Integer.toString(end));
+        }
+    }
+
+    /**
+     * Convert a Range into String form. Used mostly for debugging.
+     * 
+     * @return The String representation of the Range.
+     */
+    public String toString() {
+        if (start == end) {
+            return Integer.toString(start);
+        } else {
+            return Integer.toString(start) + "-" + Integer.toString(end);
+        }
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/RangeList.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/RangeList.java
new file mode 100644
index 0000000..845f54a
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/RangeList.java
@@ -0,0 +1,227 @@
+/*
+ * 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.nntp.newsrc;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/**
+ * Manage a list of ranges values from a newsrc file.
+ */
+public class RangeList {
+    boolean dirty = false;
+
+    ArrayList ranges = new ArrayList();
+
+    /**
+     * Create a RangeList instance from a newsrc range line. Values are saved as
+     * a comma separated set of range values. A range value is either a single
+     * number or a hypenated pair of numbers.
+     * 
+     * @param line
+     *            The newsrc range line.
+     */
+    public RangeList(String line) {
+
+        // we might be creating an first time list, so nothing to parse.
+        if (line != null) {
+            // ranges are comma delimited tokens
+            StringTokenizer tokenizer = new StringTokenizer(line, ",");
+
+            while (tokenizer.hasMoreTokens()) {
+                String rangeString = (String) tokenizer.nextToken();
+                rangeString = rangeString.trim();
+                if (rangeString.length() != 0) {
+                    Range range = Range.parse(rangeString);
+                    if (range != null) {
+                        insert(range);
+                    }
+                }
+            }
+        }
+        // make sure we start out in a clean state. Any changes from this point
+        // will flip on the dirty flat.
+        dirty = false;
+    }
+
+    /**
+     * Insert a range item into our list. If possible, the inserted range will
+     * be merged with existing ranges.
+     * 
+     * @param newRange
+     *            The new range item.
+     */
+    public void insert(Range newRange) {
+        // first find the insertion point
+        for (int i = 0; i < ranges.size(); i++) {
+            Range range = (Range) ranges.get(i);
+            // does an existing range fully contain the new range, we don't need
+            // to insert anything.
+            if (range.contains(newRange)) {
+                return;
+            }
+
+            // does the current one abutt or overlap with the inserted range?
+            if (range.abutts(newRange) || range.overlaps(newRange)) {
+                // rats, we have an overlap...and it is possible that we could
+                // overlap with
+                // the next range after this one too. Therefore, we merge these
+                // two ranges together,
+                // remove the place where we're at, and then recursively insert
+                // the larger range into
+                // the list.
+                dirty = true;
+                newRange.merge(range);
+                ranges.remove(i);
+                insert(newRange);
+                return;
+            }
+
+            // ok, we don't touch the current one at all. If it is completely
+            // above
+            // range we're adding, we can just poke this into the list here.
+            if (newRange.lessThan(range)) {
+                dirty = true;
+                ranges.add(i, newRange);
+                return;
+            }
+        }
+        dirty = true;
+        // this is easy (and fairly common)...we just tack this on to the end.
+        ranges.add(newRange);
+    }
+
+    /**
+     * Test if a given article point falls within one of the contained Ranges.
+     * 
+     * @param article
+     *            The test point.
+     * 
+     * @return true if this falls within one of our current mark Ranges, false
+     *         otherwise.
+     */
+    public boolean isMarked(int article) {
+        for (int i = 0; i < ranges.size(); i++) {
+            Range range = (Range) ranges.get(i);
+            if (range.contains(article)) {
+                return true;
+            }
+            // we've passed the point where a match is possible.
+            if (range.greaterThan(article)) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Mark a target article as having been seen.
+     * 
+     * @param article
+     *            The target article number.
+     */
+    public void setMarked(int article) {
+        // go through the insertion logic.
+        insert(new Range(article, article));
+    }
+
+    /**
+     * Clear the seen mark for a target article.
+     * 
+     * @param article
+     *            The target article number.
+     */
+    public void setUnmarked(int article) {
+        for (int i = 0; i < ranges.size(); i++) {
+            Range range = (Range) ranges.get(i);
+            // does this fall within an existing range? We don't need to do
+            // anything here.
+            if (range.contains(article)) {
+                // ok, we've found where to insert, now to figure out how to
+                // insert
+                // article is at the beginning of the range. We can just
+                // increment the lower
+                // bound, or if this is a single element range, we can remove it
+                // entirely.
+                if (range.getStart() == article) {
+                    if (range.getEnd() == article) {
+                        // piece of cake!
+                        ranges.remove(i);
+                    } else {
+                        // still pretty easy.
+                        range.setStart(article + 1);
+                    }
+                } else if (range.getEnd() == article) {
+                    // pretty easy also
+                    range.setEnd(article - 1);
+                } else {
+                    // split this into two ranges and insert the trailing piece
+                    // after this.
+                    Range section = range.split(article);
+                    ranges.add(i + 1, section);
+                }
+                dirty = true;
+                return;
+            }
+            // did we find a point where any articles are greater?
+            if (range.greaterThan(article)) {
+                // nothing to do
+                return;
+            }
+        }
+        // didn't find it at all. That was easy!
+    }
+
+    /**
+     * Save this List of Ranges out to a .newsrc file. This creates a
+     * comma-separated list of range values from each of the Ranges.
+     * 
+     * @param out
+     *            The target output stream.
+     * 
+     * @exception IOException
+     */
+    public void save(Writer out) throws IOException {
+        // we have an empty list
+        if (ranges.size() == 0) {
+            return;
+        }
+
+        Range range = (Range) ranges.get(0);
+        range.save(out);
+
+        for (int i = 1; i < ranges.size(); i++) {
+            out.write(",");
+            range = (Range) ranges.get(i);
+            range.save(out);
+        }
+    }
+
+    /**
+     * Return the state of the dirty flag.
+     * 
+     * @return True if the range list information has changed, false otherwise.
+     */
+    public boolean isDirty() {
+        return dirty;
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.java
new file mode 100644
index 0000000..6b3f5ef
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.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.pop3;
+
+/**
+ * Defines a few constants that are used throught the implementation.
+ * 
+ * @version $Rev$ $Date$
+ */
+
+public interface POP3Constants {
+    public final static String SPACE = " ";
+
+    public final static String CRLF = "\r\n";
+
+    public final static int DOT = '.';
+
+    public final static int OK = 0;
+
+    public final static int ERR = 1;
+    
+    public final static int CHALLENGE = 2;
+}
\ No newline at end of file
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java
new file mode 100644
index 0000000..98a0232
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java
@@ -0,0 +1,515 @@
+/*
+ * 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.pop3;
+
+import java.util.List;     
+
+import javax.mail.FetchProfile;
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.FolderClosedException;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.MethodNotSupportedException;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.URLName;
+import javax.mail.event.ConnectionEvent;
+
+import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection; 
+import org.apache.geronimo.javamail.store.pop3.connection.POP3StatusResponse; 
+
+/**
+ * The POP3 implementation of the javax.mail.Folder Note that only INBOX is
+ * supported in POP3
+ * <p>
+ * <url>http://www.faqs.org/rfcs/rfc1939.html</url>
+ * </p>
+ * 
+ * @see javax.mail.Folder
+ * 
+ * @version $Rev$ $Date$
+ */
+public class POP3Folder extends Folder {
+
+    protected boolean isFolderOpen = false;
+
+    protected int mode;
+
+    protected int msgCount;
+
+    private POP3Message[] messageCache; 
+    // The fully qualified name of the folder.  For a POP3 folder, this is either "" for the root or 
+    // "INPUT" for the in-basket.  It is possible to create other folders, but they will report that 
+    // they don't exist. 
+    protected String fullName;  
+    // indicates whether this folder exists or not 
+    protected boolean exists = false; 
+    // indicates the type of folder this is. 
+    protected int folderType; 
+    
+    /**
+     * Create a new folder associate with a POP3 store instance.
+     * 
+     * @param store  The owning Store.
+     * @param name   The name of the folder.  Note that POP3 stores only
+     *               have 2 real folders, the root ("") and the in-basket
+     *               ("INBOX").  It is possible to create other instances
+     *               of Folder associated with the Store, but they will
+     *               be non-functional.
+     */
+     public POP3Folder(POP3Store store, String name) {
+        super(store);
+        this.fullName = name; 
+        // if this is the input folder, this exists 
+        if (name.equalsIgnoreCase("INBOX")) {
+            exists = true; 
+        }
+        // by default, we're holding messages. 
+        folderType = Folder.HOLDS_MESSAGES; 
+    }
+    
+    
+    /**
+     * 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() {
+        // the name and the full name are always the same
+        return fullName; 
+	}
+
+    /**
+     * 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;
+	}
+
+    
+    /**
+     * Never return "this" as the parent folder. Somebody not familliar with
+     * POP3 may do something like while(getParent() != null) or something
+     * simmilar which will result in an infinte loop
+     */
+    public Folder getParent() throws MessagingException {
+        // the default folder returns null.  We return the default 
+        // folder 
+        return store.getDefaultFolder(); 
+    }
+
+    /**
+     * Indicate whether a folder exists.  Only the root 
+     * folder and "INBOX" will ever return true. 
+     * 
+     * @return true for real POP3 folders, false for any other 
+     *         instances that have been created.
+     * @exception MessagingException
+     */
+    public boolean exists() throws MessagingException {
+        // only one folder truely exists...this might be it.
+        return exists; 
+    }
+
+    public Folder[] list(String pattern) throws MessagingException {
+        throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
+    }
+
+    /**
+     * No sub folders, hence there is no notion of a seperator.  This is always a null character. 
+     */
+    public char getSeparator() throws MessagingException {
+        return '\0';
+    }
+
+    /**
+     * There's no hierarchy in POP3, so the only type 
+     * is HOLDS_MESSAGES (and only one of those exists).
+     * 
+     * @return Always returns HOLDS_MESSAGES. 
+     * @exception MessagingException
+     */
+    public int getType() throws MessagingException {
+        return folderType;      
+    }
+
+    /**
+     * Always returns false as any creation operation must 
+     * fail. 
+     * 
+     * @param type   The type of folder to create.  This is ignored.
+     * 
+     * @return Always returns false. 
+     * @exception MessagingException
+     */
+    public boolean create(int type) throws MessagingException {
+        return false; 
+    }
+
+    /**
+     * No way to detect new messages, so always return false. 
+     * 
+     * @return Always returns false. 
+     * @exception MessagingException
+     */
+    public boolean hasNewMessages() throws MessagingException {
+        return false; 
+    }
+
+    public Folder getFolder(String name) throws MessagingException {
+        throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
+    }
+
+    public boolean delete(boolean recurse) throws MessagingException {
+        throw new MethodNotSupportedException("Only INBOX is supported in POP3 and INBOX cannot be deleted");
+    }
+
+    public boolean renameTo(Folder f) throws MessagingException {
+        throw new MethodNotSupportedException("Only INBOX is supported in POP3 and INBOX cannot be renamed");
+    }
+
+    /**
+     * @see javax.mail.Folder#open(int)
+     */
+    public void open(int mode) throws MessagingException {
+        // Can only be performed on a closed folder
+        checkClosed();
+
+        // get a connection object 
+        POP3Connection connection = getConnection(); 
+        
+        try {
+            POP3StatusResponse res = connection.retrieveMailboxStatus();
+            this.mode = mode;
+            this.isFolderOpen = true;
+            this.msgCount = res.getNumMessages();
+            // JavaMail API has no method in Folder to expose the total
+            // size (no of bytes) of the mail drop;
+
+            // NB:  We use the actual message number to access the messages from 
+            // the cache, which is origin 1.  Vectors are origin 0, so we have to subtract each time 
+            // we access a messagge.  
+            messageCache = new POP3Message[msgCount]; 
+        } catch (Exception e) {
+            throw new MessagingException("Unable to execute STAT command", e);
+        }
+        finally {
+            // return the connection when finished 
+            releaseConnection(connection); 
+        }
+
+        notifyConnectionListeners(ConnectionEvent.OPENED);
+    }
+
+    /**
+     * Close a POP3 folder.
+     * 
+     * @param expunge The expunge flag (ignored for POP3).
+     * 
+     * @exception MessagingException
+     */
+    public void close(boolean expunge) throws MessagingException {
+        // Can only be performed on an open folder
+        checkOpen();
+
+        // get a connection object 
+        POP3Connection connection = getConnection(); 
+        try {
+            // we might need to reset the connection before we 
+            // process deleted messages and send the QUIT.  The 
+            // connection knows if we need to do this. 
+            connection.reset(); 
+            // clean up any messages marked for deletion 
+            expungeDeletedMessages(connection); 
+        } finally {
+            // return the connection when finished 
+            releaseConnection(connection); 
+            // cleanup the the state even if exceptions occur when deleting the 
+            // messages. 
+            cleanupFolder(false); 
+        }
+    }
+    
+    /**
+     * Mark any messages we've flagged as deleted from the 
+     * POP3 server before closing. 
+     * 
+     * @exception MessagingException
+     */
+    protected void expungeDeletedMessages(POP3Connection connection) throws MessagingException {
+        if (mode == READ_WRITE) {
+            for (int i = 0; i < messageCache.length; i++) {
+                POP3Message msg = messageCache[i]; 
+                if (msg != null) {
+                    // if the deleted flag is set, go delete this 
+                    // message. NB:  We adjust the index back to an 
+                    // origin 1 value 
+                    if (msg.isSet(Flags.Flag.DELETED)) {
+                        try {
+                            connection.deleteMessage(i + 1); 
+                        } catch (MessagingException e) {
+                            throw new MessagingException("Exception deleting message number " + (i + 1), e); 
+                        }
+                    }
+                }
+            }
+        }
+    }
+    
+    
+    /**
+     * 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 disconnected) throws MessagingException {
+        messageCache = null;
+        isFolderOpen = false;
+		notifyConnectionListeners(ConnectionEvent.CLOSED);
+    }
+    
+    
+    /**
+     * 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 POP3Connection getMessageConnection() throws MessagingException {
+        // we always get one from the store.  If we're fully single threaded, then 
+        // we can get away with just a single one. 
+        return getConnection(); 
+    }
+    
+    
+    /**
+     * Release the connection object back to the Folder instance.  
+     * 
+     * @param connection The connection being released.
+     * 
+     * @exception MessagingException
+     */
+    void releaseMessageConnection(POP3Connection connection) throws MessagingException {
+        // give this back to the store 
+        releaseConnection(connection); 
+    }
+
+    public boolean isOpen() {
+        // if we're not open, we're not open 
+        if (!isFolderOpen) {
+            return false; 
+        }
+        
+        try {
+            // we might be open, but the Store has been closed.  In which case, we're not any more
+            // closing also changes the isFolderOpen flag. 
+            if (!((POP3Store)store).isConnected()) {
+                close(false); 
+            }
+        } catch (MessagingException e) {
+        }
+        return isFolderOpen;
+    }
+
+    public Flags getPermanentFlags() {
+        // unfortunately doesn't have a throws clause for this method
+        // throw new MethodNotSupportedException("POP3 doesn't support permanent
+        // flags");
+
+        // Better than returning null, save the extra condition from a user to
+        // check for null
+        // and avoids a NullPointerException for the careless.
+        return new Flags();
+    }
+
+    /**
+     * Get the folder message count.
+     * 
+     * @return The number of messages in the folder.
+     * @exception MessagingException
+     */
+    public int getMessageCount() throws MessagingException {
+        // NB: returns -1 if the folder isn't open. 
+        return msgCount;
+    }
+
+    /**
+     * Checks wether the message is in cache, if not will create a new message
+     * object and return it.
+     * 
+     * @see javax.mail.Folder#getMessage(int)
+     */
+    public Message getMessage(int msgNum) throws MessagingException {
+        // Can only be performed on an Open folder
+        checkOpen();
+        if (msgNum < 1 || msgNum > getMessageCount()) {
+            throw new MessagingException("Invalid Message number");
+        }
+
+        Message msg = messageCache[msgNum - 1];
+        if (msg == null) {
+            msg = new POP3Message(this, msgNum); 
+            messageCache[msgNum - 1] = (POP3Message)msg; 
+        }
+
+        return msg;
+    }
+
+    public void appendMessages(Message[] msgs) throws MessagingException {
+        throw new MethodNotSupportedException("Message appending is not supported in POP3");
+
+    }
+
+    public Message[] expunge() throws MessagingException {
+        throw new MethodNotSupportedException("Expunge is not supported in POP3");
+    }
+
+    public int getMode() throws IllegalStateException {
+        // Can only be performed on an Open folder
+        checkOpen();
+        return mode;
+    }
+
+    /**
+     * @see javax.mail.Folder#fetch(javax.mail.Message[],
+     *      javax.mail.FetchProfile)
+     * 
+     * The JavaMail API recommends that this method be overrident to provide a
+     * meaningfull implementation.
+     */
+    public synchronized void fetch(Message[] msgs, FetchProfile fp) throws MessagingException {
+        // Can only be performed on an Open folder
+        checkOpen();
+        for (int i = 0; i < msgs.length; i++) {
+            Message msg = msgs[i];
+            
+            if (fp.contains(FetchProfile.Item.ENVELOPE)) {
+                // fetching the size and the subject will force all of the 
+                // envelope information to load 
+                msg.getHeader("Subject"); 
+                msg.getSize(); 
+            }
+            if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
+                // force the content to load...this also fetches the header information. 
+                // C'est la vie. 
+                ((POP3Message)msg).loadContent(); 
+                msg.getSize(); 
+            }
+            // force flag loading for this message 
+            if (fp.contains(FetchProfile.Item.FLAGS)) {
+                msg.getFlags(); 
+            }
+            
+            if (fp.getHeaderNames().length > 0) {
+                // loading any header loads all headers, so just grab the header set. 
+                msg.getHeader("Subject"); 
+            }
+        }
+    }
+    
+    /**
+     * Retrieve the UID for a given message.
+     * 
+     * @param msg    The message of interest.
+     * 
+     * @return The String UID value for this message.
+     * @exception MessagingException
+     */
+    public synchronized String getUID(Message msg) throws MessagingException {
+        checkOpen(); 
+        // the Message knows how to do this 
+        return ((POP3Message)msg).getUID(); 
+    }
+    
+
+    /**
+     * Below is a list of covinience methods that avoid repeated checking for a
+     * value and throwing an exception
+     */
+
+    /** Ensure the folder is open */
+    private void checkOpen() throws IllegalStateException {
+        if (!isFolderOpen) {
+            throw new IllegalStateException("Folder is not Open");
+        }
+    }
+
+    /** Ensure the folder is not open */
+    private void checkClosed() throws IllegalStateException {
+        if (isFolderOpen) {
+            throw new IllegalStateException("Folder is Open");
+        }
+    }
+
+    /**
+     * @see javax.mail.Folder#notifyMessageChangedListeners(int,
+     *      javax.mail.Message)
+     * 
+     * this method is protected and cannot be used outside of Folder, therefore
+     * had to explicitly expose it via a method in POP3Folder, so that
+     * POP3Message has access to it
+     * 
+     * Bad design on the part of the Java Mail API.
+     */
+    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 POP3Connection getConnection() throws MessagingException {
+        // request a connection from the central store. 
+        return ((POP3Store)store).getFolderConnection(this); 
+    }
+    
+    
+    /**
+     * Release our connection back to the Store.
+     * 
+     * @param connection The connection to release.
+     * 
+     * @exception MessagingException
+     */
+    protected void releaseConnection(POP3Connection connection) throws MessagingException {
+        // we need to release the connection to the Store once we're finished with it 
+        ((POP3Store)store).releaseFolderConnection(this, connection); 
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Message.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Message.java
new file mode 100644
index 0000000..966da66
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Message.java
@@ -0,0 +1,378 @@
+/*
+ * 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.pop3;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.IllegalWriteException;
+import javax.mail.MessagingException;
+import javax.mail.event.MessageChangedEvent;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection;
+
+/**
+ * POP3 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 POP3Message extends MimeMessage {
+    // the size of the message, in bytes
+    protected int msgSize = -1;
+    // the size of the headers.  We keep this around, as it's needed to 
+    // properly calculate the size of the message 
+    protected int headerSize = -1;
+    // the UID value retrieved from the server 
+    protected String uid; 
+    // the raw message data from loading the message
+    protected byte[] messageData; 
+
+    /**
+     * Create a new POP3 message associated with a folder.
+     * 
+     * @param folder The owning folder.
+     * @param msgnum The message sequence number in the folder.
+     */
+    protected POP3Message(Folder folder, int msgnum) {
+        super(folder, msgnum);
+        this.session = session;
+        // force the headers to empty so we'll load them the first time they're referenced. 
+        this.headers = null; 
+    }
+
+    /**
+     * Get an InputStream for reading the message content. 
+     * 
+     * @return An InputStream instance initialized to read the message 
+     *         content.
+     * @exception MessagingException
+     */
+    protected InputStream getContentStream() throws MessagingException {
+        // make sure the content is loaded first 
+        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 {
+        // make sure we have everything loaded 
+        loadContent(); 
+        // just write out the raw message data 
+        out.write(messageData); 
+    }
+    
+
+    /**
+     * Set a flag value for this Message.  The flags are 
+     * only set locally, not the server.  When the folder 
+     * is closed, any messages with the Deleted flag set 
+     * will be removed from the server. 
+     * 
+     * @param newFlags The new flag values.
+     * @param set      Indicates whether this is a set or an unset operation.
+     * 
+     * @exception MessagingException
+     */
+    public void setFlags(Flags newFlags, boolean set) throws MessagingException {
+        Flags oldFlags = (Flags) flags.clone();
+        super.setFlags(newFlags, set);
+
+        if (!flags.equals(oldFlags)) {
+            ((POP3Folder) folder).notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, this);
+        }
+    }
+
+    /**
+     * Unconditionally load the headers from an inputstream. 
+     * When retrieving content, we get back the entire message, 
+     * including the headers.  This allows us to skip over 
+     * them to reach the content, even if we already have 
+     * headers loaded. 
+     * 
+     * @param in     The InputStream with the header data.
+     * 
+     * @exception MessagingException
+     */
+    protected void loadHeaders(InputStream in) throws MessagingException {
+        try {
+            headerSize = in.available(); 
+            // just load and replace the haders 
+            headers = new InternetHeaders(in);
+            headerSize -= in.available(); 
+        } catch (IOException e) {
+            // reading from a ByteArrayInputStream...this should never happen. 
+        }
+    }
+    
+    /**
+     * Lazy loading of the message content. 
+     * 
+     * @exception MessagingException
+     */
+    protected void loadContent() throws MessagingException {
+        if (content == null) {
+            POP3Connection connection = getConnection(); 
+            try {
+                // retrieve (and save the raw message data 
+                messageData = connection.retrieveMessageData(msgnum);
+            } finally {
+                // done with the connection
+                releaseConnection(connection); 
+            }
+            // now create a input stream for splitting this into headers and 
+            // content 
+            ByteArrayInputStream in = new ByteArrayInputStream(messageData); 
+            
+            // the Sun implementation has an option that forces headers loaded using TOP 
+            // should be forgotten when retrieving the message content.  This is because 
+            // some POP3 servers return different results for TOP and RETR.  Since we need to 
+            // retrieve the headers anyway, and this set should be the most complete, we'll 
+            // just replace the headers unconditionally. 
+            loadHeaders(in);
+            // load headers stops loading at the header terminator.  Everything 
+            // after that is content. 
+            loadContent(in);
+        }
+    }
+
+    /**
+     * Load the message content from the server.
+     * 
+     * @param stream A ByteArrayInputStream containing the message content.
+     *               We explicitly use ByteArrayInputStream because
+     *               there are some optimizations that can take advantage
+     *               of the fact it is such a stream.
+     * 
+     * @exception MessagingException
+     */
+    protected void loadContent(ByteArrayInputStream stream) throws MessagingException {
+        // since this is a byte array input stream, available() returns reliable value. 
+        content = new byte[stream.available()];
+        try {
+            // just read everything in to the array 
+            stream.read(content); 
+        } catch (IOException e) {
+            // should never happen 
+            throw new MessagingException("Error loading content info", e);
+        }
+    }
+
+    /**
+     * Get the size of the message.
+     * 
+     * @return The calculated message size, in bytes. 
+     * @exception MessagingException
+     */
+    public int getSize() throws MessagingException {
+        if (msgSize < 0) {
+            // we need to get the headers loaded, since we need that information to calculate the total 
+            // content size without retrieving the content. 
+            loadHeaders();  
+            
+            POP3Connection connection = getConnection(); 
+            try {
+
+                // get the total message size, and adjust by size of the headers to get the content size. 
+                msgSize = connection.retrieveMessageSize(msgnum) - headerSize; 
+            } finally {
+                // done with the connection
+                releaseConnection(connection); 
+            }
+        }
+        return msgSize;
+    }
+
+    /**
+     * notice that we pass zero as the no of lines from the message,as it
+     * doesn't serv any purpose to get only a certain number of lines.
+     * 
+     * However this maybe important if a mail client only shows 3 or 4 lines of
+     * the message in the list and then when the user clicks they would load the
+     * message on demand.
+     * 
+     */
+    protected void loadHeaders() throws MessagingException {
+        if (headers == null) {
+            POP3Connection connection = getConnection(); 
+            try {
+                loadHeaders(connection.retrieveMessageHeaders(msgnum)); 
+            } finally {
+                // done with the connection
+                releaseConnection(connection); 
+            }
+        }
+    }
+    
+    /**
+     * Retrieve the message UID from the server.
+     * 
+     * @return The string UID value. 
+     * @exception MessagingException
+     */
+    protected String getUID() throws MessagingException {
+        if (uid == null) {
+            POP3Connection connection = getConnection(); 
+            try {
+                uid = connection.retrieveMessageUid(msgnum); 
+            } finally {
+                // done with the connection
+                releaseConnection(connection); 
+            }
+        }
+        return uid; 
+    }
+    
+    // The following are methods that deal with all header accesses.  Most of the 
+    // methods that retrieve information from the headers funnel through these, so we 
+    // can lazy-retrieve the header information. 
+
+    public String[] getHeader(String name) throws MessagingException {
+        // make sure the headers are loaded 
+        loadHeaders(); 
+        // allow the super class to handle everything from here 
+        return super.getHeader(name); 
+    }
+
+    public String getHeader(String name, String delimiter) throws MessagingException {
+        // make sure the headers are loaded 
+        loadHeaders(); 
+        // allow the super class to handle everything from here 
+        return super.getHeader(name, delimiter); 
+    }
+
+    public Enumeration getAllHeaders() throws MessagingException {
+        // make sure the headers are loaded 
+        loadHeaders(); 
+        // allow the super class to handle everything from here 
+        return super.getAllHeaders(); 
+    }
+
+    public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
+        // make sure the headers are loaded 
+        loadHeaders(); 
+        // allow the super class to handle everything from here 
+        return super.getMatchingHeaders(names); 
+    }
+
+    public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
+        // make sure the headers are loaded 
+        loadHeaders(); 
+        // allow the super class to handle everything from here 
+        return super.getNonMatchingHeaders(names); 
+    }
+
+    public Enumeration getAllHeaderLines() throws MessagingException {
+        // make sure the headers are loaded 
+        loadHeaders(); 
+        // allow the super class to handle everything from here 
+        return super.getAllHeaderLines();       
+    }
+
+    public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
+        // make sure the headers are loaded 
+        loadHeaders(); 
+        // allow the super class to handle everything from here 
+        return super.getMatchingHeaderLines(names); 
+    }
+
+    public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
+        // make sure the headers are loaded 
+        loadHeaders(); 
+        // allow the super class to handle everything from here 
+        return super.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("POP3 messages are read-only");
+    }
+
+    public void setHeader(String name, String value) throws MessagingException {
+        throw new IllegalWriteException("POP3 messages are read-only");
+    }
+
+    public void removeHeader(String name) throws MessagingException {
+        throw new IllegalWriteException("POP3 messages are read-only");
+    }
+
+    public void addHeaderLine(String line) throws MessagingException {
+        throw new IllegalWriteException("POP3 messages are read-only");
+    }
+
+    /**
+     * We cannot modify these messages
+     */
+    public void saveChanges() throws MessagingException {
+        throw new IllegalWriteException("POP3 messages are read-only");
+    }
+
+    
+    /**
+     * 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 POP3Connection getConnection() throws MessagingException {
+        // the folder owns everything.
+        return ((POP3Folder)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(POP3Connection connection) throws MessagingException {
+        // the folder owns everything.
+        ((POP3Folder)folder).releaseMessageConnection(connection);
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3RootFolder.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3RootFolder.java
new file mode 100644
index 0000000..99cde3b
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3RootFolder.java
@@ -0,0 +1,142 @@
+/**
+ * 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.pop3;
+
+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.pop3.connection.POP3Connection; 
+
+/**
+ * An POP3 folder instance for the root of POP3 folder tree.  This has 
+ * some of the folder operations disabled. 
+ */
+public class POP3RootFolder extends POP3Folder {
+    // the inbox folder is the only one that exists 
+    protected Folder inbox; 
+    
+    /**
+     * Create a default POP3RootFolder attached to a specific Store instance.
+     * 
+     * @param store  The Store instance this is the root for.
+     */
+    public POP3RootFolder(POP3Store store) {
+        // create a folder with a null string name and the default separator. 
+        super(store, ""); 
+        // this only holds folders 
+        folderType = HOLDS_FOLDERS; 
+        // this folder does exist
+        exists = true; 
+        // no messages in this folder 
+        msgCount = 0; 
+    }
+
+    
+    /**
+     * Get the parent.  This is the root folder, which 
+     * never has a parent. 
+     * 
+     * @return Always returns null. 
+     */
+    public Folder getParent() {
+        // we never have a parent folder 
+        return null; 
+    }
+
+    /**
+     * We have a separator because the root folder is "special". 
+     */
+    public char getSeparator() throws MessagingException {
+        return '/';
+    }
+    
+    /**
+     * Retrieve a list of folders that match a pattern.
+     * 
+     * @param pattern The match pattern.
+     * 
+     * @return An array of matching folders.
+     * @exception MessagingException
+     */
+    public Folder[] list(String pattern) throws MessagingException {
+        // I'm not sure this is correct, but the Sun implementation appears to 
+        // return a array containing the inbox regardless of what pattern was specified. 
+        return new Folder[] { getInbox() };
+    }
+    
+    /**
+     * Get a folder of a given name from the root folder.
+     * The Sun implementation seems somewhat inconsistent 
+     * here.  The docs for Store claim that only INBOX is 
+     * supported, but it will return a Folder instance for any 
+     * name.  On the other hand, the root folder raises 
+     * an exception for anything but the INBOX.
+     * 
+     * @param name   The folder name (which must be "INBOX".
+     * 
+     * @return The inbox folder instance. 
+     * @exception MessagingException
+     */
+    public Folder getFolder(String name) throws MessagingException {
+        if (!name.equalsIgnoreCase("INBOX")) {
+            throw new MessagingException("Only the INBOX folder is supported"); 
+        }
+        // return the inbox folder 
+        return getInbox(); 
+    }
+    
+    /**
+     * Override for the isOpen method.  The root folder can 
+     * never be opened. 
+     * 
+     * @return always returns false. 
+     */
+    public boolean isOpen() {
+        return false; 
+    }
+    
+    public void open(int mode) throws MessagingException {
+        throw new MessagingException("POP3 root folder cannot be opened"); 
+    }
+    
+    public void open(boolean expunge) throws MessagingException {
+        throw new MessagingException("POP3 root folder cannot be close"); 
+    }
+    
+    
+    /**
+     * Retrieve the INBOX folder from the root. 
+     * 
+     * @return The Folder instance for the inbox. 
+     * @exception MessagingException
+     */
+    protected Folder getInbox() throws MessagingException {
+        // we're the only place that creates folders, and 
+        // we only create the single instance. 
+        if (inbox == null) {
+            inbox = new POP3Folder((POP3Store)store, "INBOX"); 
+        }
+        return inbox; 
+    }
+}
+
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3SSLStore.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3SSLStore.java
new file mode 100644
index 0000000..67c254a
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3SSLStore.java
@@ -0,0 +1,43 @@
+/**
+ * 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.pop3;
+
+import javax.mail.Session;
+import javax.mail.URLName;
+
+/**
+ * POP3 implementation of javax.mail.Store over an SSL connection.
+ *
+ * @version $Rev$ $Date$
+ */
+public class POP3SSLStore extends POP3Store {
+    /**
+     * Construct an POP3SSLStore item.
+     *
+     * @param session The owning javamail Session.
+     * @param urlName The Store urlName, which can contain server target information.
+     */
+	public POP3SSLStore(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, "pop3s", DEFAULT_POP3_SSL_PORT, true);
+	}
+}
+
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java
new file mode 100644
index 0000000..f5d2d73
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java
@@ -0,0 +1,359 @@
+/*
+ * 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.pop3;
+ 
+import java.io.PrintStream; 
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.mail.AuthenticationFailedException;
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.URLName;
+
+import org.apache.geronimo.javamail.store.pop3.connection.POP3Connection; 
+import org.apache.geronimo.javamail.store.pop3.connection.POP3ConnectionPool; 
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+
+/**
+ * POP3 implementation of javax.mail.Store POP protocol spec is implemented in
+ * org.apache.geronimo.javamail.store.pop3.POP3Connection
+ * 
+ * @version $Rev$ $Date$
+ */
+
+public class POP3Store extends Store {
+    protected static final int DEFAULT_POP3_PORT = 110;
+    protected static final int DEFAULT_POP3_SSL_PORT = 995;
+    
+    
+    // our accessor for protocol properties and the holder of 
+    // protocol-specific information 
+    protected ProtocolProperties props; 
+    // our connection object    
+    protected POP3ConnectionPool connectionPool; 
+    // our session provided debug output stream.
+    protected PrintStream debugStream;
+    // the debug flag 
+    protected boolean debug; 
+    // the root folder 
+    protected POP3RootFolder root; 
+    // until we're connected, we're closed 
+    boolean closedForBusiness = true; 
+    protected LinkedList openFolders = new LinkedList(); 
+    
+    
+    public POP3Store(Session session, URLName name) {
+        this(session, name, "pop3", DEFAULT_POP3_PORT, false);
+    }
+
+    /**
+     * Common constructor used by the POP3Store and POP3SSLStore classes
+     * to do common initialization of defaults.
+     *
+     * @param session
+     *            The host session instance.
+     * @param name
+     *            The URLName of the target.
+     * @param protocol
+     *            The protocol type ("pop3"). This helps us in
+     *            retrieving protocol-specific session properties.
+     * @param defaultPort
+     *            The default port used by this protocol. For pop3, this will
+     *            be 110. The default for pop3 with ssl is 995.
+     * @param sslConnection
+     *            Indicates whether an SSL connection should be used to initial
+     *            contact the server. This is different from the STARTTLS
+     *            support, which switches the connection to SSL after the
+     *            initial startup.
+     */
+    protected POP3Store(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
+        super(session, name);
+        
+        // create the protocol property holder.  This gives an abstraction over the different 
+        // flavors of the protocol. 
+        props = new ProtocolProperties(session, protocol, sslConnection, defaultPort); 
+
+        // get our debug settings
+        debugStream = session.getDebugOut();
+        debug = session.getDebug(); 
+        // the connection pool manages connections for the stores, folder, and message usage. 
+        connectionPool = new POP3ConnectionPool(this, props); 
+    }
+
+
+    /**
+     * 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 POP3RootFolder(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());
+	}
+
+    
+    /**
+     * @see javax.mail.Service#protocolConnect(java.lang.String, int,
+     *      java.lang.String, java.lang.String)
+     */
+    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; 
+    }
+    
+    
+    /**
+     * Get a connection for the store. 
+     * 
+     * @return The request connection object. 
+     * @exception MessagingException
+     */
+    protected POP3Connection getConnection() throws MessagingException {
+        return connectionPool.getConnection(); 
+    }
+    
+    /**
+     * Return a connection back to the connection pool after 
+     * it has been used for a request. 
+     * 
+     * @param connection The return connection.
+     * 
+     * @exception MessagingException
+     */
+    protected void releaseConnection(POP3Connection connection) throws MessagingException {
+        connectionPool.releaseConnection(connection); 
+    }
+    
+    /**
+     * Get a connection object for a folder to use. 
+     * 
+     * @param folder The requesting folder (always the inbox for POP3).
+     * 
+     * @return An active POP3Connection. 
+     * @exception MessagingException
+     */
+    synchronized POP3Connection getFolderConnection(POP3Folder folder) throws MessagingException {
+        POP3Connection connection = connectionPool.getConnection(); 
+        openFolders.add(folder);
+        return connection; 
+    }
+    
+    /**
+     * Release a connection object after a folder is 
+     * finished with a request. 
+     * 
+     * @param folder     The requesting folder.
+     * @param connection
+     * 
+     * @exception MessagingException
+     */
+    synchronized void releaseFolderConnection(POP3Folder folder, POP3Connection connection) throws MessagingException {
+        openFolders.remove(folder); 
+        // return this back to the pool 
+        connectionPool.releaseConnection(connection); 
+    }
+    
+    /**
+     * 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++) {
+                POP3Folder folder = (POP3Folder)folders.get(i);
+                try {
+                    folder.close(false);
+                } catch (MessagingException e) {
+                }
+            }
+        }
+    }
+    
+
+    /**
+     * @see javax.mail.Service#isConnected()
+     */
+    public boolean isConnected() {
+        // the connect() method of the super class checks here first.  If the connected flag 
+        // is off, then it's too early for use to try to get a connection and verify we still 
+        // have a live one.  
+        if (!super.isConnected()) {
+            return false; 
+        }
+        try {
+            POP3Connection connection = getConnection(); 
+            // a null connection likely means we had a failure establishing a 
+            // new connection to the POP3 server.  
+            if (connection == null) {
+                return false; 
+            }
+            try {
+                // make sure the server is really there 
+                connection.pingServer(); 
+                return true; 
+            }
+            finally {
+                // return the connection to the pool when finished 
+                if (connection != null) {
+                    releaseConnection(connection); 
+                }
+            }
+        } catch (MessagingException e) {
+        }
+        return false; 
+    }
+
+    /**
+     * Close the store, and any open folders associated with the 
+     * store. 
+     * 
+     * @exception MessagingException
+     */
+	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();
+	}
+
+    /**
+     * Check the status of our connection. 
+     * 
+     * @exception MessagingException
+     */
+    private void checkConnectionStatus() throws MessagingException {
+        if (!this.isConnected()) {
+            throw new MessagingException("Not connected ");
+        }
+    }
+
+    /**
+     * Internal debug output routine.
+     *
+     * @param value  The string value to output.
+     */
+    void debugOut(String message) {
+        debugStream.println("POP3Store 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);
+    }
+    
+    /**
+     * Finalizer to perform IMAPStore() cleanup when 
+     * no longer in use. 
+     * 
+     * @exception Throwable
+     */
+    protected void finalize() throws Throwable {
+        super.finalize(); 
+        close(); 
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java
new file mode 100644
index 0000000..aa90882
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Connection.java
@@ -0,0 +1,693 @@
+/**
+ * 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.pop3.connection;
+
+import java.io.*;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+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.MessagingException;
+import javax.mail.internet.InternetHeaders;
+
+import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
+import org.apache.geronimo.javamail.store.imap.connection.IMAPResponseStream;
+import org.apache.geronimo.javamail.store.pop3.POP3Constants;
+import org.apache.geronimo.javamail.util.CommandFailedException;
+import org.apache.geronimo.javamail.util.InvalidCommandException;
+import org.apache.geronimo.javamail.util.MIMEInputReader;
+import org.apache.geronimo.javamail.util.MailConnection;
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.Hex;
+
+/**
+ * Simple implementation of POP3 transport.
+ *
+ * @version $Rev$ $Date$
+ */
+public class POP3Connection extends MailConnection implements POP3Constants {
+
+    static final protected String MAIL_APOP_ENABLED = "apop.enable";
+    static final protected String MAIL_AUTH_ENABLED = "auth.enable";
+    static final protected String MAIL_RESET_QUIT = "rsetbeforequit";
+    static final protected String MAIL_DISABLE_TOP = "disabletop";
+    //static final protected String MAIL_FORGET_TOP = "forgettopheaders"; //TODO forgettopheaders
+
+    // the initial greeting string, which might be required for APOP authentication.
+    protected String greeting;
+    // is use of the AUTH command enabled
+    protected boolean authEnabled;
+    // is use of APOP command enabled
+    protected boolean apopEnabled;
+    // input reader wrapped around the socket input stream
+    protected BufferedReader reader;
+    // output writer wrapped around the socket output stream.
+    protected PrintWriter writer;
+    // this connection was closed unexpectedly
+    protected boolean closed;
+    // indicates whether this connection is currently logged in.  Once
+    // we send a QUIT, we're finished.
+    protected boolean loggedIn;
+    // indicates whether we need to avoid using the TOP command
+    // when retrieving headers
+    protected boolean topDisabled = false;
+    // is TLS enabled on our part?
+    protected boolean useTLS = false;
+    // is TLS required on our part?
+    protected boolean requireTLS = false;
+
+    /**
+     * Normal constructor for an POP3Connection() object.
+     *
+     * @param props  The protocol properties abstraction containing our
+     *               property modifiers.
+     */
+    public POP3Connection(ProtocolProperties props) {
+        super(props);
+
+        // get our login properties flags
+        authEnabled = props.getBooleanProperty(MAIL_AUTH_ENABLED, false);
+        apopEnabled = props.getBooleanProperty(MAIL_APOP_ENABLED, false);
+        topDisabled = props.getBooleanProperty(MAIL_DISABLE_TOP, false);
+        // and also check for TLS enablement.
+        useTLS = props.getBooleanProperty(MAIL_STARTTLS_ENABLE, false);
+        // and also check if TLS is required.
+        requireTLS = props.getBooleanProperty(MAIL_STARTTLS_REQUIRED, false);
+       
+    }
+
+
+    /**
+     * 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;
+
+        try {
+            // create socket and connect to server.
+            getConnection();
+            // consume the welcome line
+            getWelcome();
+            
+            // if we're not already using an SSL connection, and we have permission to issue STARTTLS or its even required
+            // try to setup a SSL connection
+            if (!sslConnection && (useTLS || requireTLS)) {
+                
+                    // tell the server of our intention to start a TLS session
+                    POP3Response starttlsResponse = null;
+                    try {
+                        starttlsResponse = sendCommand("STLS");
+                    } catch (CommandFailedException e) {
+                       
+                    }
+                    
+                    //if the server does not support TLS check if its required.
+                    //If true then throw an error, if not establish a non SSL connection
+                    if(requireTLS && (starttlsResponse == null || starttlsResponse.isError())) {
+                        throw new MessagingException("Server doesn't support required transport level security");
+                    } else if(starttlsResponse != null && starttlsResponse.getStatus() == POP3Response.OK) {
+                     // The connection is then handled by the superclass level.
+                        getConnectedTLSSocket();
+                    } else {
+                        if (debug) {
+                            debugOut("STARTTLS is enabled but not required and server does not support it. So we establish a connection without transport level security");
+                        }
+                    }
+            }
+            
+            getConnection();
+            
+            // go login with the server
+            if (login())
+            {
+                loggedIn = true;
+                return true;
+            }
+            return false;
+        } catch (IOException e) {
+            if (debug) {
+                debugOut("I/O exception establishing connection", e);
+            }
+            throw new MessagingException("Connection error", e);
+        }
+    }
+
+
+    /**
+     * Create a transport connection object and connect it to the
+     * target server.
+     *
+     * @exception MessagingException
+     */
+    protected void getConnection() throws MessagingException
+    {
+        try {
+            // do all of the non-protocol specific set up.  This will get our socket established
+            // and ready use.
+            super.getConnection();
+        } catch (IOException e) {
+            throw new MessagingException("Unable to obtain a connection to the POP3 server", e);
+        }
+
+        // The POP3 protocol is inherently a string-based protocol, so we get
+        // string readers/writers for the connection streams.  Note that we explicitly
+        // set the encoding to ensure that an inappropriate native encoding is not picked up.
+        try {
+            reader = new BufferedReader(new InputStreamReader(inputStream, "ISO8859-1"));
+            writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(outputStream), "ISO8859-1"));
+        } catch (UnsupportedEncodingException e) {
+        }
+    }
+
+    protected void getWelcome() throws IOException {
+        // just read the line and consume it.  If debug is
+        // enabled, there I/O stream will be traced
+        greeting = reader.readLine();
+    }
+
+    public String toString() {
+        return "POP3Connection host: " + serverHost + " port: " + serverPort;
+    }
+
+
+    /**
+     * 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;
+            writer = null;
+        }
+    }
+
+
+    /**
+     * 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 connection has been forcibly closed.
+     *
+     * @return True if the server disconnected the connection.
+     */
+    public boolean isClosed() {
+        return closed;
+    }
+
+    protected POP3Response sendCommand(String cmd) throws MessagingException {
+        return sendCommand(cmd, false);
+    }
+
+    protected POP3Response sendMultiLineCommand(String cmd) throws MessagingException {
+        return sendCommand(cmd, true);
+    }
+
+    protected synchronized POP3Response sendCommand(String cmd, boolean multiLine) throws MessagingException {
+        if (socket.isConnected()) {
+            {
+                // NOTE:  We don't use println() because it uses the platform concept of a newline rather
+                // than using CRLF, which is required by the POP3 protocol.
+                writer.write(cmd);
+                writer.write("\r\n");
+                writer.flush();
+
+                POP3Response response = buildResponse(multiLine);
+                if (response.isError()) {
+                    throw new CommandFailedException("Error issuing POP3 command: " + cmd);
+                }
+                return response;
+            }
+        }
+        throw new MessagingException("Connection to Mail Server is lost, connection " + this.toString());
+    }
+
+    /**
+     * Build a POP3Response item from the response stream.
+     *
+     * @param isMultiLineResponse
+     *               If true, this command is expecting multiple lines back from the server.
+     *
+     * @return A POP3Response item with all of the command response data.
+     * @exception MessagingException
+     */
+    protected POP3Response buildResponse(boolean isMultiLineResponse) throws MessagingException {
+        int status = ERR;
+        byte[] data = null;
+
+        String line;
+        //MIMEInputReader source = new MIMEInputReader(reader); //TODO unused
+
+        try {
+            line = reader.readLine();
+        } catch (IOException e) {
+            throw new MessagingException("Error in receving response");
+        }
+
+        if (line == null || line.trim().equals("")) {
+            throw new MessagingException("Empty Response");
+        }
+
+        if (line.startsWith("+OK")) {
+            status = OK;
+            line = removeStatusField(line);
+            if (isMultiLineResponse) {
+                data = getMultiLineResponse();
+            }
+        } else if (line.startsWith("-ERR")) {
+            status = ERR;
+            line = removeStatusField(line);
+        }else if (line.startsWith("+")) {
+        	status = CHALLENGE;
+        	line = removeStatusField(line);
+        	if (isMultiLineResponse) {
+        		data = getMultiLineResponse();
+        	}
+        } else {
+            throw new MessagingException("Unexpected response: " + line);
+        }
+        return new POP3Response(status, line, data);
+    }
+
+    private static String removeStatusField(String line) {
+        return line.substring(line.indexOf(SPACE) + 1);
+    }
+
+    /**
+     * This could be a multiline response
+     */
+    private byte[] getMultiLineResponse() throws MessagingException {
+
+        MIMEInputReader source = new MIMEInputReader(reader);
+
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+        // it's more efficient to do this a buffer at a time.
+        // the MIMEInputReader takes care of the byte-stuffing and
+        // ".\r\n" input terminator for us.
+        try {
+            OutputStreamWriter outWriter = new OutputStreamWriter(out, "ISO8859-1");
+            char buffer[] = new char[500];
+            try {
+                int charsRead = -1;
+                while ((charsRead = source.read(buffer)) >= 0) {
+                    outWriter.write(buffer, 0, charsRead);
+                }
+                outWriter.flush();
+            } catch (IOException e) {
+                throw new MessagingException("Error processing a multi-line response", e);
+            }
+        } catch (UnsupportedEncodingException e) {
+        }
+        return out.toByteArray();
+    }
+
+
+    /**
+     * Retrieve the raw message content from the POP3
+     * server.  This is all of the message data, including
+     * the header.
+     *
+     * @param sequenceNumber
+     *               The message sequence number.
+     *
+     * @return A byte array containing all of the message data.
+     * @exception MessagingException
+     */
+    public byte[] retrieveMessageData(int sequenceNumber) throws MessagingException {
+        POP3Response msgResponse = sendMultiLineCommand("RETR " + sequenceNumber);
+        // we want the data directly in this case.
+        return msgResponse.getData();
+    }
+
+    /**
+     * Retrieve the message header information for a given
+     * message, returned as an input stream suitable
+     * for loading the message data.
+     *
+     * @param sequenceNumber
+     *               The server sequence number for the message.
+     *
+     * @return An inputstream that can be used to read the message
+     *         data.
+     * @exception MessagingException
+     */
+    public ByteArrayInputStream retrieveMessageHeaders(int sequenceNumber) throws MessagingException {
+        POP3Response msgResponse;
+
+        // some POP3 servers don't correctly implement TOP, so this can be disabled.  If
+        // we can't use TOP, then use RETR and retrieve everything.  We can just hand back
+        // the stream, as the header loading routine will stop at the first
+        // null line.
+        if (topDisabled) {
+            msgResponse = sendMultiLineCommand("RETR " + sequenceNumber);
+        }
+        else {
+            msgResponse = sendMultiLineCommand("TOP " + sequenceNumber + " 0");
+        }
+
+        // just load the returned message data as a set of headers
+        return msgResponse.getContentStream();
+    }
+
+    /**
+     * Retrieve the total message size from the mail
+     * server.  This is the size of the headers plus
+     * the size of the message content.
+     *
+     * @param sequenceNumber
+     *               The message sequence number.
+     *
+     * @return The full size of the message.
+     * @exception MessagingException
+     */
+    public int retrieveMessageSize(int sequenceNumber) throws MessagingException {
+        POP3Response msgResponse = sendCommand("LIST " + sequenceNumber);
+        // Convert this into the parsed response type we need.
+        POP3ListResponse list = new POP3ListResponse(msgResponse);
+        // this returns the total message size
+        return list.getSize();
+    }
+
+    /**
+     * Retrieve the mail drop status information.
+     *
+     * @return An object representing the returned mail drop status.
+     * @exception MessagingException
+     */
+    public POP3StatusResponse retrieveMailboxStatus() throws MessagingException {
+        // issue the STAT command and return this into a status response
+        return new POP3StatusResponse(sendCommand("STAT"));
+    }
+
+
+    /**
+     * Retrieve the UID for an individual message.
+     *
+     * @param sequenceNumber
+     *               The target message sequence number.
+     *
+     * @return The string UID maintained by the server.
+     * @exception MessagingException
+     */
+    public String retrieveMessageUid(int sequenceNumber) throws MessagingException {
+        POP3Response msgResponse = sendCommand("UIDL " + sequenceNumber);
+
+        String message = msgResponse.getFirstLine();
+        // the UID is everything after the blank separating the message number and the UID.
+        // there's not supposed to be anything else on the message, but trim it of whitespace
+        // just to be on the safe side.
+        return message.substring(message.indexOf(' ') + 1).trim();
+    }
+
+
+    /**
+     * Delete a single message from the mail server.
+     *
+     * @param sequenceNumber
+     *               The sequence number of the message to delete.
+     *
+     * @exception MessagingException
+     */
+    public void deleteMessage(int sequenceNumber) throws MessagingException {
+        // just issue the command...we ignore the command response
+        sendCommand("DELE " + sequenceNumber);
+    }
+
+    /**
+     * Logout from the mail server.  This sends a QUIT
+     * command, which will likely sever the mail connection.
+     *
+     * @exception MessagingException
+     */
+    public void logout() throws MessagingException {
+        // we may have already sent the QUIT command
+        if (!loggedIn) {
+            return;
+        }
+        // just issue the command...we ignore the command response
+        sendCommand("QUIT");
+        loggedIn = false;
+    }
+
+    /**
+     * Perform a reset on the mail server.
+     *
+     * @exception MessagingException
+     */
+    public void reset() throws MessagingException {
+        // some mail servers mark retrieved messages for deletion
+        // automatically.  This will reset the read flags before
+        // we go through normal cleanup.
+        if (props.getBooleanProperty(MAIL_RESET_QUIT, false)) {
+            // just send an RSET command first
+            sendCommand("RSET");
+        }
+    }
+
+    /**
+     * Ping the mail server to see if we still have an active connection.
+     *
+     * @exception MessagingException thrown if we do not have an active connection.
+     */
+    public void pingServer() throws MessagingException {
+        // just issue the command...we ignore the command response
+        sendCommand("NOOP");
+    }
+
+    /**
+     * Login to the mail server, using whichever method is
+     * configured.  This will try multiple methods, if allowed,
+     * in decreasing levels of security.
+     *
+     * @return true if the login was successful.
+     * @exception MessagingException
+     */
+    public synchronized boolean login() throws MessagingException {
+        // permitted to use the AUTH command?
+        if (authEnabled) {
+            try {
+                // go do the SASL thing
+                return processSaslAuthentication();
+            } catch (MessagingException e) {
+                // Any error here means fall back to the next mechanism
+            }
+        }
+
+        if (apopEnabled) {
+            try {
+                // go do the SASL thing
+                return processAPOPAuthentication();
+            } catch (MessagingException e) {
+                // Any error here means fall back to the next mechanism
+            }
+        }
+
+        try {
+            // do the tried and true login processing.
+            return processLogin();
+        } catch (MessagingException e) {
+        }
+        // everything failed...can't get in
+        return false;
+    }
+
+
+    /**
+     * Process a basic LOGIN operation, using the
+     * plain test USER/PASS command combo.
+     *
+     * @return true if we logged successfully.
+     * @exception MessagingException
+     */
+    public boolean processLogin() throws MessagingException {
+        // start by sending the USER command, followed by
+        // the PASS command
+        sendCommand("USER " + username);
+        sendCommand("PASS " + password);
+        return true;       // we're in
+    }
+
+    /**
+     * Process logging in using the APOP command.  Only
+     * works on servers that give a timestamp value
+     * in the welcome response.
+     *
+     * @return true if the login was accepted.
+     * @exception MessagingException
+     */
+    public boolean processAPOPAuthentication() throws MessagingException {
+        int timeStart = greeting.indexOf('<');
+        // if we didn't get an APOP challenge on the greeting, throw an exception
+        // the main login processor will swallow that and fall back to the next
+        // mechanism
+        if (timeStart == -1) {
+            throw new MessagingException("POP3 Server does not support APOP");
+        }
+        int timeEnd = greeting.indexOf('>');
+        String timeStamp = greeting.substring(timeStart, timeEnd + 1);
+
+        // we create the digest password using the timestamp value sent to use
+        // concatenated with the password.
+        String digestPassword = timeStamp + password;
+
+        byte[] digest;
+
+        try {
+            // create a digest value from the password.
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            digest = md.digest(digestPassword.getBytes("iso-8859-1"));
+        } catch (NoSuchAlgorithmException e) {
+            // this shouldn't happen, but if it does, we'll just try a plain
+            // login.
+            throw new MessagingException("Unable to create MD5 digest", e);
+        } catch (UnsupportedEncodingException e) {
+            // this shouldn't happen, but if it does, we'll just try a plain
+            // login.
+            throw new MessagingException("Unable to create MD5 digest", e);
+        }
+        // this will throw an exception if it gives an error failure
+        sendCommand("APOP " + username + " " + new String(Hex.encode(digest)));
+        // no exception, we must have passed
+        return true;
+    }
+
+
+    /**
+     * 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) {
+            throw new MessagingException("Unable to obtain SASL authenticator");
+        }
+
+        // go process the login.
+        return processLogin(authenticator);
+    }
+
+    /**
+     * Attempt to retrieve a SASL authenticator for this
+     * protocol.
+     *
+     * @return A SASL authenticator, or null if a suitable one
+     *         was not located.
+     */
+    protected ClientAuthenticator getSaslAuthenticator() {
+        return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
+    }
+
+
+    /**
+     * 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());
+        }
+
+        POP3Response response = sendCommand("AUTH " + authenticator.getMechanismName());
+
+        // now process the challenge sequence.  We get a continuation response back for each stage of the
+        // authentication, and finally an OK when everything passes muster.
+        while (true) {
+            // this should be a continuation reply, if things are still good.
+            if (response.isChallenge()) {
+                // we're passed back a challenge value, Base64 encoded.
+                byte[] challenge = response.decodeChallengeResponse();
+
+                try {
+                    String responseString = new String(Base64.encode(authenticator.evaluateChallenge(challenge)), "US-ASCII");
+
+                    // have the authenticator evaluate and send back the encoded response.
+                    response = sendCommand(responseString);
+                } catch (UnsupportedEncodingException ex) {
+                }
+            }
+            else {
+                // there are only two choices here, OK or a continuation.  OK means
+                // we've passed muster and are in.
+                return true;
+            }
+        }
+    }
+
+
+    /**
+     * 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() {
+        // just return the set that have been explicity permitted
+        return getSaslMechanisms();
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java
new file mode 100644
index 0000000..8aa9c4b
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ConnectionPool.java
@@ -0,0 +1,224 @@
+/**
+ * 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.pop3.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.pop3.POP3Store; 
+import org.apache.geronimo.javamail.util.ProtocolProperties; 
+
+public class POP3ConnectionPool {
+
+    protected static final String MAIL_PORT = "port";
+    
+    protected static final String MAIL_SASL_REALM = "sasl.realm"; 
+    protected static final String MAIL_AUTHORIZATIONID = "sasl.authorizationid"; 
+
+    protected static final String DEFAULT_MAIL_HOST = "localhost";
+
+    // Our hosting Store instance
+    protected POP3Store store;
+    // our Protocol abstraction 
+    protected ProtocolProperties props; 
+    // POP3 is not nearly as multi-threaded as IMAP.  We really just have a single folder, 
+    // plus the Store, but the Store doesn't really talk to the server very much.  We only
+    // hold one connection available, and on the off chance there is a situation where 
+    // we need to create a new one, we'll authenticate on demand.  The one case where 
+    // I know this might be an issue is a folder checking back with the Store to see it if
+    // it is still connected.  
+    protected POP3Connection availableConnection;      
+    
+    // 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.  
+    protected String authid; 
+    // Turned on when the store is closed for business. 
+    protected boolean closed = false; 
+
+    /**
+     * Create a connection pool associated with a give POP3Store instance.  The
+     * connection pool manages handing out connections for both the Store and
+     * Folder and Message usage.
+     * 
+     * @param store  The Store we're creating the pool for.
+     * @param props  The protocol properties abstraction we use.
+     */
+    public POP3ConnectionPool(POP3Store store, ProtocolProperties props) {
+        this.store = store;
+        this.props = props; 
+    }
+
+
+    /**
+     * Manage the initial connection to the POP3 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 mail server.
+     * @param port     The mail 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. 
+        availableConnection = createPoolConnection(); 
+        if (availableConnection == null) {
+            return false; 
+        }
+        // we're connected, 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 POP3Connection createPoolConnection() throws MessagingException {
+        POP3Connection connection = new POP3Connection(props);
+        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; 
+        }
+        // just return this 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 A POP3Connection object that is connected to the server.
+     */
+    public synchronized POP3Connection getConnection() throws MessagingException {
+        // if we have an available one (common when opening the INBOX), just return it 
+        POP3Connection connection = availableConnection; 
+        
+        if (connection != null) {
+            availableConnection = null; 
+            return connection; 
+        }
+        // we need an additional connection...rare, but it can happen if we've closed the INBOX folder. 
+        return createPoolConnection(); 
+    }
+    
+    
+    /**
+     * Return a connection to the connection pool.
+     * 
+     * @param connection The connection getting returned.
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void releaseConnection(POP3Connection connection) throws MessagingException
+    {
+        // we're generally only called if the store needed to talk to the server and 
+        // then returned the connection to the pool.  So it's pretty likely that we'll just cache this
+        if (availableConnection == null) {
+            availableConnection = connection; 
+        }
+        else {
+            // got too many connections created...not sure how, but get rid of this one. 
+            connection.close(); 
+        }
+    }
+    
+    
+    /**
+     * Close the entire connection pool. 
+     * 
+     * @exception MessagingException
+     */
+    public synchronized void close() throws MessagingException {
+        // we'll on have the single connection in reserver 
+        if (availableConnection != null) {
+            availableConnection.close(); 
+            availableConnection = null; 
+        }
+        // turn out the lights, hang the closed sign on the wall. 
+        closed = true; 
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java
new file mode 100644
index 0000000..d1b2066
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3ListResponse.java
@@ -0,0 +1,99 @@
+/*
+ * 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.pop3.connection;
+
+import java.io.ByteArrayInputStream; 
+
+import java.util.ArrayList; 
+import java.util.List;     
+
+import javax.mail.MessagingException;
+
+/**
+ * This class adds functionality to the basic response by parsing the reply for
+ * LIST command and obtaining specific information about the msgnum and the
+ * size. It could be for one or more msgs depending on wether a msg number was
+ * passed or not into the LIST command
+ * 
+ * @see org.apache.geronimo.javamail.store.pop3.POP3Response
+ * @see org.apache.geronimo.javamail.store.pop3.response.DefaultPOP3Response
+ * 
+ * @version $Rev$ $Date$
+ */
+
+public class POP3ListResponse extends POP3Response {
+
+    private int msgnum = 0;
+
+    private int size = 0;
+
+    private List multipleMsgs = null;
+
+    POP3ListResponse(POP3Response baseRes) throws MessagingException {
+        super(baseRes.getStatus(), baseRes.getFirstLine(), baseRes.getData());
+
+        // if ERR not worth proceeding any further
+        if (OK == getStatus()) {
+
+            // if data == null, then it mean it's a single line response
+            if (baseRes.getData() == null) {
+                String[] args = getFirstLine().split(SPACE);
+                try {
+                    msgnum = Integer.parseInt(args[0]);
+                } catch (NumberFormatException e) {
+                    throw new MessagingException("Invalid response for LIST command", e);
+                }
+                try {
+                    size = Integer.parseInt(args[1]);
+                } catch (NumberFormatException e) {
+                    throw new MessagingException("Invalid response for LIST command", e);
+                }
+            } else {
+                int totalMsgs = 0;
+                String[] args = getFirstLine().split(SPACE);
+                try {
+                    totalMsgs = Integer.parseInt(args[0]);
+                } catch (NumberFormatException e) {
+                    throw new MessagingException("Invalid response for LIST command", e);
+                }
+                multipleMsgs = new ArrayList(totalMsgs);
+                // Todo : multi-line response parsing
+            }
+
+        }
+    }
+
+    public int getMessageNumber() {
+        return msgnum;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    /**
+     * Messages can be accessed by multipleMsgs.getElementAt(msgnum)
+     * 
+     */
+    public List getMultipleMessageDetails() {
+        return multipleMsgs;
+    }
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java
new file mode 100644
index 0000000..2351187
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3Response.java
@@ -0,0 +1,85 @@
+/*
+ * 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.pop3.connection;
+
+import java.io.ByteArrayInputStream;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Constants;
+
+import org.apache.geronimo.mail.util.Base64;
+
+/**
+ * This class provides the basic implementation for the POP3Response.
+ * 
+ * @see org.apache.geronimo.javamail.store.pop3.POP3Response
+ * @version $Rev$ $Date$
+ */
+
+public class POP3Response implements POP3Constants {
+
+    private int status = ERR;
+
+    private String firstLine;
+
+    private byte[] data;
+
+    POP3Response(int status, String firstLine, byte []data) {
+        this.status = status;
+        this.firstLine = firstLine;
+        this.data = data;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+    
+    public byte[] getData() {
+        return data; 
+    }
+
+    public ByteArrayInputStream getContentStream() {
+        return new ByteArrayInputStream(data);
+    }
+
+    public String getFirstLine() {
+        return firstLine;
+    }
+    
+    public boolean isError() {
+        return status == ERR; 
+    }
+    
+    public boolean isChallenge() {
+        return status == CHALLENGE; 
+    }
+    
+    /**
+     * Decode the message portion of a continuation challenge response.
+     * 
+     * @return The byte array containing the decoded data. 
+     */
+    public byte[] decodeChallengeResponse() 
+    {
+        // the challenge response is a base64 encoded string...
+        return Base64.decode(firstLine.trim()); 
+    }
+
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.java
new file mode 100644
index 0000000..dbd3e53
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/connection/POP3StatusResponse.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.pop3.connection;
+
+import javax.mail.MessagingException;
+
+/**
+ * This class adds functionality to the basic response by parsing the status
+ * line and obtaining specific information about num of msgs and the size
+ * 
+ * @see org.apache.geronimo.javamail.store.pop3.POP3Response
+ * @see org.apache.geronimo.javamail.store.pop3.response.DefaultPOP3Response
+ * 
+ * @version $Rev$ $Date$
+ */
+
+public class POP3StatusResponse extends POP3Response {
+
+    private int numMessages = 0;
+
+    private int size = 0;
+
+    POP3StatusResponse(POP3Response baseRes) throws MessagingException {
+        super(baseRes.getStatus(), baseRes.getFirstLine(), baseRes.getData());
+
+        // if ERR not worth proceeding any further
+        if (OK == getStatus()) {
+            String[] args = getFirstLine().split(SPACE);
+            try {
+                numMessages = Integer.parseInt(args[0]);
+            } catch (NumberFormatException e) {
+                throw new MessagingException("Invalid response for STAT command", e);
+            }
+            try {
+                size = Integer.parseInt(args[1]);
+            } catch (NumberFormatException e) {
+                throw new MessagingException("Invalid response for STAT command", e);
+            }
+        }
+    }
+
+    public int getNumMessages() {
+        return numMessages;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
new file mode 100644
index 0000000..7ec2efb
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
@@ -0,0 +1,728 @@
+/*
+ * 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.transport.nntp;
+
+import java.io.BufferedReader;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
+import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
+import org.apache.geronimo.javamail.util.MailConnection;
+import org.apache.geronimo.javamail.util.MIMEOutputStream;
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.SessionUtil;
+
+/**
+ * Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
+ *
+ * @version $Rev$ $Date$
+ */
+public class NNTPConnection extends 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 int DEFAULT_NNTP_PORT = 119;
+    // does the server support posting?
+    protected boolean postingAllowed = true;
+
+    // different authentication mechanisms
+    protected boolean authInfoUserAllowed = false;
+    protected boolean authInfoSaslAllowed = false;
+
+    // the last response line received from the server.
+    protected NNTPReply lastServerResponse = null;
+
+    // the welcome string from the server.
+    protected String welcomeString = null;
+
+    // input reader wrapped around the socket input stream
+    protected BufferedReader reader;
+    // output writer wrapped around the socket output stream.
+    protected PrintWriter writer;
+
+    /**
+     * Normal constructor for an NNTPConnection() object.
+     *
+     * @param props  The property bundle for this protocol instance.
+     */
+    public NNTPConnection(ProtocolProperties props) {
+        super(props);
+    }
+
+
+    /**
+     * Connect to the server and do the initial handshaking.
+     *
+     * @param host     The target host name.
+     * @param port     The target port
+     * @param username The connection username (can be null)
+     * @param password The authentication password (can be null).
+     *
+     * @return true if we were able to obtain a connection and
+     *         authenticate.
+     * @exception MessagingException
+     */
+    public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
+        super.protocolConnect(host, port, username, password);
+        // create socket and connect to server.
+        getConnection();
+
+        // receive welcoming message
+        getWelcome();
+
+        return true;
+    }
+
+
+    /**
+     * Create a transport connection object and connect it to the
+     * target server.
+     *
+     * @exception MessagingException
+     */
+    protected void getConnection() throws MessagingException
+    {
+        try {
+            // do all of the non-protocol specific set up.  This will get our socket established
+            // and ready use.
+            super.getConnection();
+        } catch (IOException e) {
+            throw new MessagingException("Unable to obtain a connection to the NNTP server", e);
+        }
+
+        // The NNTP protocol is inherently a string-based protocol, so we get
+        // string readers/writers for the connection streams.  Note that we explicitly
+        // set the encoding to ensure that an inappropriate native encoding is not picked up.
+        try {
+            reader = new BufferedReader(new InputStreamReader(inputStream, "ISO8859-1"));
+            writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(outputStream), "ISO8859-1"));
+        } catch (UnsupportedEncodingException e) {
+        }
+    }
+
+
+    /**
+     * 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
+            sendQuit();
+        } 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;
+            writer = null;
+        }
+    }
+
+    public String toString() {
+        return "NNTPConnection host: " + serverHost + " port: " + serverPort;
+    }
+
+
+    /**
+     * Get the servers welcome blob from the wire....
+     */
+    public void getWelcome() throws MessagingException {
+        NNTPReply line = getReply();
+
+        //
+        if (line.isError()) {
+            throw new MessagingException("Error connecting to news server: " + line.getMessage());
+        }
+
+        // remember we can post.
+        if (line.getCode() == NNTPReply.POSTING_ALLOWED) {
+            postingAllowed = true;
+        } else {
+            postingAllowed = false;
+        }
+
+        // the NNTP store will want to use the welcome string, so save it.
+        welcomeString = line.getMessage();
+
+        // find out what extensions this server supports.
+        getExtensions();
+    }
+
+
+    /**
+     * Sends the QUIT message and receieves the response
+     */
+    public void sendQuit() throws MessagingException {
+        sendLine("QUIT");
+    }
+
+
+    /**
+     * Tell the server to switch to a named group.
+     *
+     * @param name
+     *            The name of the target group.
+     *
+     * @return The server response to the GROUP command.
+     */
+    public NNTPReply selectGroup(String name) throws MessagingException {
+        // send the GROUP command
+        return sendCommand("GROUP " + name);
+    }
+
+
+    /**
+     * Ask the server what extensions it supports.
+     *
+     * @return True if the command was accepted ok, false for any errors.
+     * @exception MessagingException
+     */
+    protected void getExtensions() throws MessagingException {
+        NNTPReply reply = sendCommand("LIST EXTENSIONS", NNTPReply.EXTENSIONS_SUPPORTED);
+
+        // we get a 202 code back. The first line is just a greeting, and
+        // extensions are delivered as data
+        // lines terminated with a "." line.
+        if (reply.getCode() != NNTPReply.EXTENSIONS_SUPPORTED) {
+            return;
+        }
+
+        // get a fresh extension mapping table.
+        capabilities = new HashMap();
+        authentications = new ArrayList();
+
+        // get the extension data lines.
+        List extensions = reply.getData();
+
+        // process all of the continuation lines
+        for (int i = 0; i < extensions.size(); i++) {
+            // go process the extention
+            processExtension((String) extensions.get(i));
+        }
+    }
+
+
+    /**
+     * Process an extension string passed back as the LIST EXTENSIONS response.
+     *
+     * @param extension
+     *            The string value of the extension (which will be of the form
+     *            "NAME arguments").
+     */
+    protected void processExtension(String extension) {
+        String extensionName = extension.toUpperCase();
+        String argument = "";
+
+        int delimiter = extension.indexOf(' ');
+        // if we have a keyword with arguments, parse them out and add to the
+        // argument map.
+        if (delimiter != -1) {
+            extensionName = extension.substring(0, delimiter).toUpperCase();
+            argument = extension.substring(delimiter + 1);
+        }
+
+        // add this to the map so it can be tested later.
+        capabilities.put(extensionName, argument);
+
+        // we need to determine which authentication mechanisms are supported here
+        if (extensionName.equals("AUTHINFO")) {
+            StringTokenizer tokenizer = new StringTokenizer(argument);
+
+            while (tokenizer.hasMoreTokens()) {
+                // we only know how to do USER or SASL
+                String mechanism = tokenizer.nextToken().toUpperCase();
+                if (mechanism.equals("SASL")) {
+                    authInfoSaslAllowed = true;
+                }
+                else if (mechanism.equals("USER")) {
+                    authInfoUserAllowed = true;
+                }
+            }
+        }
+        // special case for some older servers.
+        else if (extensionName.equals("SASL")) {
+            // The security mechanisms are blank delimited tokens.
+            StringTokenizer tokenizer = new StringTokenizer(argument);
+
+            while (tokenizer.hasMoreTokens()) {
+                String mechanism = tokenizer.nextToken().toUpperCase();
+                authentications.add(mechanism);
+            }
+        }
+    }
+
+
+    /**
+     * Retrieve any argument information associated with a extension reported
+     * back by the server on the EHLO command.
+     *
+     * @param name
+     *            The name of the target server extension.
+     *
+     * @return Any argument passed on a server extension. Returns null if the
+     *         extension did not include an argument or the extension was not
+     *         supported.
+     */
+    public String extensionParameter(String name) {
+        if (capabilities != null) {
+            return (String) capabilities.get(name);
+        }
+        return null;
+    }
+
+    /**
+     * Tests whether the target server supports a named extension.
+     *
+     * @param name
+     *            The target extension name.
+     *
+     * @return true if the target server reported on the EHLO command that is
+     *         supports the targer server, false if the extension was not
+     *         supported.
+     */
+    public boolean supportsExtension(String name) {
+        // this only returns null if we don't have this extension
+        return extensionParameter(name) != null;
+    }
+
+
+    /**
+     * Sends the data in the message down the socket. This presumes the server
+     * is in the right place and ready for getting the DATA message and the data
+     * right place in the sequence
+     */
+    public synchronized void sendPost(Message msg) throws MessagingException {
+
+        // send the POST command
+        NNTPReply line = sendCommand("POST");
+
+        if (line.getCode() != NNTPReply.SEND_ARTICLE) {
+            throw new MessagingException("Server rejected POST command: " + line);
+        }
+
+        // we've received permission to send the data, so ask the message to
+        // write itself out.
+        try {
+            // the data content has two requirements we need to meet by
+            // filtering the
+            // output stream. Requirement 1 is to conicalize any line breaks.
+            // All line
+            // breaks will be transformed into properly formed CRLF sequences.
+            //
+            // Requirement 2 is to perform byte-stuff for any line that begins
+            // with a "."
+            // so that data is not confused with the end-of-data marker (a
+            // "\r\n.\r\n" sequence.
+            //
+            // The MIME output stream performs those two functions on behalf of
+            // the content
+            // writer.
+            MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
+
+            msg.writeTo(mimeOut);
+
+            // now to finish, we send a CRLF sequence, followed by a ".".
+            mimeOut.writeSMTPTerminator();
+            // and flush the data to send it along
+            mimeOut.flush();
+        } catch (IOException e) {
+            throw new MessagingException("I/O error posting message", e);
+        } catch (MessagingException e) {
+            throw new MessagingException("Exception posting message", e);
+        }
+
+        // use a longer time out here to give the server time to process the
+        // data.
+        line = new NNTPReply(receiveLine());
+
+        if (line.getCode() != NNTPReply.POSTED_OK) {
+            throw new MessagingException("Server rejected POST command: " + line);
+        }
+    }
+
+    /**
+     * Issue a command and retrieve the response. If the given success indicator
+     * is received, the command is returning a longer response, terminated by a
+     * "crlf.crlf" sequence. These lines are attached to the reply.
+     *
+     * @param command
+     *            The command to issue.
+     * @param success
+     *            The command reply that indicates additional data should be
+     *            retrieved.
+     *
+     * @return The command reply.
+     */
+    public synchronized NNTPReply sendCommand(String command, int success) throws MessagingException {
+        NNTPReply reply = sendCommand(command);
+        if (reply.getCode() == success) {
+            reply.retrieveData(reader);
+        }
+        return reply;
+    }
+
+    /**
+     * Send a command to the server, returning the first response line back as a
+     * reply.
+     *
+     * @param data
+     *            The data to send.
+     *
+     * @return A reply object with the reply line.
+     * @exception MessagingException
+     */
+    public NNTPReply sendCommand(String data) throws MessagingException {
+        sendLine(data);
+        NNTPReply reply = getReply();
+        // did the server just inform us we need to authenticate? The spec
+        // allows this
+        // response to be sent at any time, so we need to try to authenticate
+        // and then retry the command.
+        if (reply.getCode() == NNTPReply.AUTHINFO_REQUIRED || reply.getCode() == NNTPReply.AUTHINFO_SIMPLE_REQUIRED) {
+            debugOut("Authentication required received from server.");
+            // authenticate with the server, if necessary
+            processAuthentication(reply.getCode());
+            // if we've safely authenticated, we can reissue the command and
+            // process the response.
+            sendLine(data);
+            reply = getReply();
+        }
+        return reply;
+    }
+
+    /**
+     * Send a command to the server, returning the first response line back as a
+     * reply.
+     *
+     * @param data
+     *            The data to send.
+     *
+     * @return A reply object with the reply line.
+     * @exception MessagingException
+     */
+    public NNTPReply sendAuthCommand(String data) throws MessagingException {
+        sendLine(data);
+        return getReply();
+    }
+
+    /**
+     * Sends a message down the socket and terminates with the appropriate CRLF
+     */
+    public void sendLine(String data) throws MessagingException {
+        if (socket == null || !socket.isConnected()) {
+            throw new MessagingException("no connection");
+        }
+        try {
+            outputStream.write(data.getBytes("ISO8859-1"));
+            outputStream.write(CR);
+            outputStream.write(LF);
+            outputStream.flush();
+        } catch (IOException e) {
+            throw new MessagingException(e.toString());
+        }
+    }
+
+    /**
+     * Get a reply line for an NNTP command.
+     *
+     * @return An NNTP reply object from the stream.
+     */
+    public NNTPReply getReply() throws MessagingException {
+        lastServerResponse = new NNTPReply(receiveLine());
+        return lastServerResponse;
+    }
+
+    /**
+     * Retrieve the last response received from the NNTP server.
+     *
+     * @return The raw response string (including the error code) returned from
+     *         the NNTP server.
+     */
+    public String getLastServerResponse() {
+        if (lastServerResponse == null) {
+            return "";
+        }
+        return lastServerResponse.getReply();
+    }
+
+    /**
+     * Receives one line from the server. A line is a sequence of bytes
+     * terminated by a CRLF
+     *
+     * @return the line from the server as String
+     */
+    public String receiveLine() throws MessagingException {
+        if (socket == null || !socket.isConnected()) {
+            throw new MessagingException("no connection");
+        }
+
+        try {
+            String line = reader.readLine();
+            if (line == null) {
+                throw new MessagingException("Unexpected end of stream");
+            }
+            return line;
+        } catch (IOException e) {
+            throw new MessagingException("Error reading from server", e);
+        }
+    }
+
+
+    /**
+     * Authenticate with the server, if necessary (or possible).
+     */
+    protected void processAuthentication(int request) throws MessagingException {
+        // we need to authenticate, but we don't have userid/password
+        // information...fail this
+        // immediately.
+        if (username == null || password == null) {
+            throw new MessagingException("Server requires user authentication");
+        }
+
+        if (request == NNTPReply.AUTHINFO_SIMPLE_REQUIRED) {
+            processAuthinfoSimple();
+        } else {
+            if (!processSaslAuthentication()) {
+                processAuthinfoUser();
+            }
+        }
+    }
+
+    /**
+     * Process an AUTHINFO SIMPLE command. Not widely used, but if the server
+     * asks for it, we can respond.
+     *
+     * @exception MessagingException
+     */
+    protected void processAuthinfoSimple() throws MessagingException {
+        NNTPReply reply = sendAuthCommand("AUTHINFO SIMPLE");
+        if (reply.getCode() != NNTPReply.AUTHINFO_CONTINUE) {
+            throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
+        }
+        reply = sendAuthCommand(username + " " + password);
+        if (reply.getCode() != NNTPReply.AUTHINFO_ACCEPTED) {
+            throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
+        }
+    }
+
+
+    /**
+     * 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 {
+        // only do this if permitted
+        if (!authInfoSaslAllowed) {
+            return false;
+        }
+        // if unable to get an appropriate authenticator, just fail it.
+        ClientAuthenticator authenticator = getSaslAuthenticator();
+        if (authenticator == null) {
+            throw new MessagingException("Unable to obtain SASL authenticator");
+        }
+
+        // go process the login.
+        return processLogin(authenticator);
+    }
+
+    /**
+     * Attempt to retrieve a SASL authenticator for this
+     * protocol.
+     *
+     * @return A SASL authenticator, or null if a suitable one
+     *         was not located.
+     */
+    protected ClientAuthenticator getSaslAuthenticator() {
+        return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
+    }
+
+
+    /**
+     * 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 {
+        debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
+
+        // if the authenticator has some initial data, we compose a command
+        // containing the initial data.
+        if (authenticator.hasInitialResponse()) {
+            StringBuffer command = new StringBuffer();
+            // the auth command initiates the handshaking.
+            command.append("AUTHINFO SASL ");
+            // and tell the server which mechanism we're using.
+            command.append(authenticator.getMechanismName());
+            command.append(" ");
+            // and append the response data
+            try {
+                command.append(new String(Base64.encode(authenticator.evaluateChallenge(null)), "US-ASCII"));
+            } catch (UnsupportedEncodingException e) {
+            }
+            // send the command now
+            sendLine(command.toString());
+        }
+        // we just send an auth command with the command type.
+        else {
+            StringBuffer command = new StringBuffer();
+            // the auth command initiates the handshaking.
+            command.append("AUTHINFO SASL");
+            // and tell the server which mechanism we're using.
+            command.append(authenticator.getMechanismName());
+            // send the command now
+            sendLine(command.toString());
+        }
+
+        // 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) {
+            // get the next line, and if it is an error response, return now.
+            NNTPReply line = getReply();
+
+            // if we get a completion return, we've passed muster, so give an
+            // authentication response.
+            if (line.getCode() == NNTPReply.AUTHINFO_ACCEPTED || line.getCode() == NNTPReply.AUTHINFO_ACCEPTED_FINAL) {
+                debugOut("Successful SMTP authentication");
+                return true;
+            }
+            // we have an additional challenge to process.
+            else if (line.getCode() == NNTPReply.AUTHINFO_CHALLENGE) {
+                // Does the authenticator think it is finished? We can't answer
+                // an additional challenge,
+                // so fail this.
+                if (authenticator.isComplete()) {
+                    debugOut("Extra authentication challenge " + line);
+                    return false;
+                }
+
+                // we're passed back a challenge value, Base64 encoded.
+                try {
+                    byte[] challenge = Base64.decode(line.getMessage().getBytes("ISO8859-1"));
+
+                    // have the authenticator evaluate and send back the encoded
+                    // response.
+                    sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge)), "US-ASCII"));
+                } catch (UnsupportedEncodingException e) {
+                }
+            }
+            // completion or challenge are the only responses we know how to
+            // handle. Anything else must
+            // be a failure.
+            else {
+                debugOut("Authentication failure " + line);
+                return false;
+            }
+        }
+    }
+
+
+    /**
+     * Process an AUTHINFO USER command. Most common form of NNTP
+     * authentication.
+     *
+     * @exception MessagingException
+     */
+    protected void processAuthinfoUser() throws MessagingException {
+        // only do this if allowed by the server
+        if (!authInfoUserAllowed) {
+            return;
+        }
+        NNTPReply reply = sendAuthCommand("AUTHINFO USER " + username);
+        // accepted without a password (uncommon, but allowed), we're done
+        if (reply.getCode() == NNTPReply.AUTHINFO_ACCEPTED) {
+            return;
+        }
+        // the only other non-error response is continue.
+        if (reply.getCode() != NNTPReply.AUTHINFO_CONTINUE) {
+            throw new MessagingException("Error authenticating with server using AUTHINFO USER: " + reply);
+        }
+        // now send the password. We expect an accepted response.
+        reply = sendAuthCommand("AUTHINFO PASS " + password);
+        if (reply.getCode() != NNTPReply.AUTHINFO_ACCEPTED) {
+            throw new MessagingException("Error authenticating with server using AUTHINFO SIMPLE");
+        }
+    }
+
+
+    /**
+     * Indicate whether posting is allowed for a given server.
+     *
+     * @return True if the server allows posting, false if the server is
+     *         read-only.
+     */
+    public boolean isPostingAllowed() {
+        return postingAllowed;
+    }
+
+    /**
+     * Retrieve the welcome string sent back from the server.
+     *
+     * @return The server provided welcome string.
+     */
+    public String getWelcomeString() {
+        return welcomeString;
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPReply.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPReply.java
new file mode 100644
index 0000000..676458b
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPReply.java
@@ -0,0 +1,242 @@
+/*
+ * 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.transport.nntp;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+
+/**
+ * Util class to represent a reply from a NNTP server
+ * 
+ * @version $Rev$ $Date$
+ */
+public class NNTPReply {
+    // general server responses
+    public static final int CAPABILITY_LIST = 101; 
+    
+    public static final int POSTING_ALLOWED = 200;
+
+    public static final int NO_POSTING_ALLOWED = 201;
+
+    public static final int EXTENSIONS_SUPPORTED = 202;
+
+    public static final int SERVICE_DISCONTINUED = 400;
+
+    public static final int COMMAND_NOT_RECOGNIZED = 500;
+
+    public static final int COMMAND_SYNTAX_ERROR = 501;
+
+    public static final int PERMISSION_DENIED = 502;
+
+    public static final int PROGRAM_FAULT = 503;
+
+    // article responses
+    public static final int ARTICLE_FOLLOWS = 220;
+
+    public static final int HEAD_FOLLOWS = 221;
+
+    public static final int BODY_FOLLOWS = 222;
+
+    public static final int REQUEST_TEXT_SEPARATELY = 223;
+
+    public static final int OVERVIEW_FOLLOWS = 224;
+
+    public static final int NEW_ARTICLES_FOLLOWS = 230;
+
+    public static final int NEW_GROUPS_FOLLOWS = 231;
+
+    public static final int ARTICLE_TRANSFERRED = 235;
+
+    public static final int NO_NEWSGROUP_SELECTED = 412;
+
+    public static final int NO_ARTICLE_SELECTED = 420;
+
+    public static final int NO_ARTICLE_NUMBER = 423;
+
+    public static final int NO_ARTICLE_FOUND = 430;
+
+    // group responses
+    public static final int GROUP_SELECTED = 211;
+
+    public static final int NO_SUCH_NEWSGROUP = 411;
+
+    // post responses
+    public static final int POSTED_OK = 240;
+
+    public static final int SEND_ARTICLE = 340;
+
+    public static final int POSTING_NOT_ALLOWED = 440;
+
+    public static final int POSTING_FAILED = 441;
+
+    // quit responses
+    public static final int CLOSING_CONNECTION = 205;
+
+    // authentication responses
+    public static final int AUTHINFO_ACCEPTED = 250;
+
+    public static final int AUTHINFO_ACCEPTED_FINAL = 251;
+
+    public static final int AUTHINFO_CONTINUE = 350;
+
+    public static final int AUTHINFO_CHALLENGE = 350;
+
+    public static final int AUTHINFO_SIMPLE_REJECTED = 402;
+
+    public static final int AUTHENTICATION_ACCEPTED = 281;
+
+    public static final int MORE_AUTHENTICATION_REQUIRED = 381;
+
+    public static final int AUTHINFO_REQUIRED = 480;
+
+    public static final int AUTHINFO_SIMPLE_REQUIRED = 450;
+
+    public static final int AUTHENTICATION_REJECTED = 482;
+
+    // list active reponses
+    public static final int LIST_FOLLOWS = 215;
+
+    // The original reply string
+    private final String reply;
+
+    // returned message code
+    private final int code;
+
+    // the returned message text
+    private final String message;
+
+    // data associated with a long response command.
+    private ArrayList data;
+
+    NNTPReply(String s) throws MessagingException {
+        // save the reply
+        reply = s;
+
+        // In a normal response, the first 3 must be the return code. However,
+        // the response back from a QUIT command is frequently a null string.
+        // Therefore, if the result is
+        // too short, just default the code to -1 and use the entire text for
+        // the message.
+        if (s == null || s.length() < 3) {
+            code = -1;
+            message = s;
+            return;
+        }
+
+        try {
+            code = Integer.parseInt(s.substring(0, 3));
+
+            // message should be separated by a space OR a continuation
+            // character if this is a
+            // multi-line response.
+            if (s.length() > 4) {
+                message = s.substring(4);
+            } else {
+                message = "";
+            }
+        } catch (NumberFormatException e) {
+            throw new MessagingException("error in parsing reply code", e);
+        }
+    }
+
+    /**
+     * Retrieve data associated with a multi-line reponse from a server stream.
+     * 
+     * @param in
+     *            The reader that's the source of the additional lines.
+     * 
+     * @exception IOException
+     */
+    public void retrieveData(BufferedReader in) throws MessagingException {
+        try {
+            data = new ArrayList();
+
+            String line = in.readLine();
+            // read until the end of file or until we see the end of data
+            // marker.
+            while (line != null && !line.equals(".")) {
+                // this line is not the terminator, but it may have been byte
+                // stuffed. If it starts with
+                // '.', throw away the leading one.
+                if (line.startsWith(".")) {
+                    line = line.substring(1);
+                }
+
+                // just add the line to the list
+                data.add(line);
+                line = in.readLine();
+            }
+        } catch (IOException e) {
+            throw new MessagingException("Error reading message reply", e);
+        }
+    }
+
+    /**
+     * Retrieve the long-command data from this response.
+     * 
+     * @return The data list. Returns null if there is no associated data.
+     */
+    public List getData() {
+        return data;
+    }
+
+    /**
+     * Return the code value associated with the reply.
+     * 
+     * @return The integer code associated with the reply.
+     */
+    public int getCode() {
+        return this.code;
+    }
+
+    /**
+     * Get the message text associated with the reply.
+     * 
+     * @return The string value of the message from the reply.
+     */
+    public String getMessage() {
+        return this.message;
+    }
+
+    /**
+     * Retrieve the raw reply string for the reponse.
+     * 
+     * @return The original reply string from the server.
+     */
+    public String getReply() {
+        return reply;
+    }
+
+    /**
+     * Indicates if reply is an error condition
+     */
+    boolean isError() {
+        // error codes are all above 400
+        return code >= 400;
+    }
+
+    public String toString() {
+        return "CODE = " + getCode() + " : MSG = " + getMessage();
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPSSLTransport.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPSSLTransport.java
new file mode 100644
index 0000000..e27a064
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPSSLTransport.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.transport.nntp;
+
+import javax.mail.Session;
+import javax.mail.URLName;
+
+public class NNTPSSLTransport extends NNTPTransport {
+    /**
+     * @param session
+     * @param name
+     */
+    public NNTPSSLTransport(Session session, URLName name) {
+        super(session, name, "nntp-posts", DEFAULT_NNTP_SSL_PORT, true);
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
new file mode 100644
index 0000000..ec399c4
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
@@ -0,0 +1,260 @@
+/*
+ * 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.transport.nntp;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.URLName;
+import javax.mail.event.TransportEvent;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.NewsAddress;
+
+import org.apache.geronimo.javamail.util.ProtocolProperties; 
+
+/**
+ * Simple implementation of NNTP 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 NNTPTransport extends Transport {
+    /**
+     * property keys for protocol properties.
+     */
+    protected static final String NNTP_FROM = "from";
+
+    protected static final int DEFAULT_NNTP_PORT = 119;
+    protected static final int DEFAULT_NNTP_SSL_PORT = 563;
+
+    // our accessor for protocol properties and the holder of 
+    // protocol-specific information 
+    protected ProtocolProperties props; 
+    // our active connection object (shared code with the NNTPStore).
+    protected NNTPConnection connection;
+
+    /**
+     * Normal constructor for an NNTPTransport() object. This constructor is
+     * used to build a transport instance for the "smtp" protocol.
+     * 
+     * @param session
+     *            The attached session.
+     * @param name
+     *            An optional URLName object containing target information.
+     */
+    public NNTPTransport(Session session, URLName name) {
+        this(session, name, "nntp-post", DEFAULT_NNTP_PORT, false);
+    }
+
+    /**
+     * Common constructor used by the POP3Store and POP3SSLStore classes
+     * to do common initialization of defaults.
+     *
+     * @param session
+     *            The host session instance.
+     * @param name
+     *            The URLName of the target.
+     * @param protocol
+     *            The protocol type ("pop3"). This helps us in
+     *            retrieving protocol-specific session properties.
+     * @param defaultPort
+     *            The default port used by this protocol. For pop3, this will
+     *            be 110. The default for pop3 with ssl is 995.
+     * @param sslConnection
+     *            Indicates whether an SSL connection should be used to initial
+     *            contact the server. This is different from the STARTTLS
+     *            support, which switches the connection to SSL after the
+     *            initial startup.
+     */
+    protected NNTPTransport(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
+        super(session, name);
+        
+        // create the protocol property holder.  This gives an abstraction over the different 
+        // flavors of the protocol. 
+        props = new ProtocolProperties(session, protocol, sslConnection, defaultPort); 
+        // the connection manages connection for the transport 
+        connection = new NNTPConnection(props); 
+    }
+
+    /**
+     * Do the protocol connection for an NNTP transport. This handles server
+     * authentication, if possible. Returns false if unable to connect to the
+     * server.
+     * 
+     * @param host
+     *            The target host name.
+     * @param port
+     *            The server port number.
+     * @param user
+     *            The authentication user (if any).
+     * @param password
+     *            The server password. Might not be sent directly if more
+     *            sophisticated authentication is used.
+     * 
+     * @return true if we were able to connect to the server properly, false for
+     *         any failures.
+     * @exception MessagingException
+     */
+    protected boolean protocolConnect(String host, int port, String username, String password)
+            throws MessagingException {
+        // the connection pool handles all of the details here. 
+        return connection.protocolConnect(host, port, username, password);
+    }
+    
+
+    /**
+     * Send a message to multiple addressees.
+     * 
+     * @param message
+     *            The message we're sending.
+     * @param addresses
+     *            An array of addresses to send to.
+     * 
+     * @exception MessagingException
+     */
+    public void sendMessage(Message message, Address[] addresses) throws MessagingException {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected");
+        }
+
+        if (!connection.isPostingAllowed()) {
+            throw new MessagingException("Posting disabled for host server");
+        }
+        // don't bother me w/ null messages or no addreses
+        if (message == null) {
+            throw new MessagingException("Null message");
+        }
+
+        // NNTP only handles instances of MimeMessage, not the more general
+        // message case.
+        if (!(message instanceof MimeMessage)) {
+            throw new MessagingException("NNTP can only send MimeMessages");
+        }
+
+        // need to sort the from value out from a variety of sources.
+        InternetAddress from = null;
+
+        Address[] fromAddresses = message.getFrom();
+
+        // If the message has a From address set, we just use that. Otherwise,
+        // we set a From using
+        // the property version, if available.
+        if (fromAddresses == null || fromAddresses.length == 0) {
+            // the from value can be set explicitly as a property
+            String defaultFrom = props.getProperty(NNTP_FROM);
+            if (defaultFrom == null) {
+                message.setFrom(new InternetAddress(defaultFrom));
+            }
+        }
+
+        // we must have a message list.
+        if (addresses == null || addresses.length == 0) {
+            throw new MessagingException("Null or empty address array");
+        }
+
+        boolean haveGroup = false;
+
+        // enforce the requirement that all of the targets are NewsAddress
+        // instances.
+        for (int i = 0; i < addresses.length; i++) {
+            if (!(addresses[i] instanceof NewsAddress)) {
+                throw new MessagingException("Illegal NewsAddress " + addresses[i]);
+            }
+        }
+
+        // event notifcation requires we send lists of successes and failures
+        // broken down by category.
+        // The categories are:
+        //
+        // 1) addresses successfully processed.
+        // 2) addresses deemed valid, but had a processing failure that
+        // prevented sending.
+        // 3) addressed deemed invalid (basically all other processing
+        // failures).
+        ArrayList sentAddresses = new ArrayList();
+        ArrayList unsentAddresses = new ArrayList();
+        ArrayList invalidAddresses = new ArrayList();
+
+        boolean sendFailure = false;
+
+        // now try to post this message to the different news groups.
+        for (int i = 0; i < addresses.length; i++) {
+            try {
+                // select the target news group
+                NNTPReply reply = connection.selectGroup(((NewsAddress) addresses[i]).getNewsgroup());
+
+                if (reply.getCode() != NNTPReply.GROUP_SELECTED) {
+                    invalidAddresses.add(addresses[i]);
+                    sendFailure = true;
+                } else {
+                    // send data
+                    connection.sendPost(message);
+                    sentAddresses.add(addresses[i]);
+                }
+            } catch (MessagingException e) {
+                unsentAddresses.add(addresses[i]);
+                sendFailure = true;
+            }
+        }
+
+        // create our lists for notification and exception reporting from this
+        // point on.
+        Address[] sent = (Address[]) sentAddresses.toArray(new Address[0]);
+        Address[] unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
+        Address[] invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
+
+        if (sendFailure) {
+            // did we deliver anything at all?
+            if (sent.length == 0) {
+                // notify of the error.
+                notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
+            } else {
+                // notify that we delivered at least part of this
+                notifyTransportListeners(TransportEvent.MESSAGE_PARTIALLY_DELIVERED, sent, unsent, invalid, message);
+            }
+
+            throw new MessagingException("Error posting NNTP message");
+        }
+
+        // notify our listeners of successful delivery.
+        notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED, sent, unsent, invalid, message);
+    }
+
+    /**
+     * 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 {
+        // This is done to ensure proper event notification.
+        super.close();
+        // NB:  We reuse the connection if asked to reconnect 
+        connection.close();
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/StringListInputStream.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/StringListInputStream.java
new file mode 100644
index 0000000..62dab16
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/StringListInputStream.java
@@ -0,0 +1,112 @@
+/*
+ * 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.transport.nntp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class StringListInputStream extends InputStream {
+    // the list of lines we're reading from
+    protected List lines;
+
+    // the next line to process.
+    protected int nextLine = 0;
+
+    // current buffer of bytes to read from
+    byte[] buffer;
+
+    // current offset within the buffer;
+    int offset;
+
+    // indicator that we've left off at a split between the CR and LF of a line
+    // break.
+    boolean atLineBreak = false;
+
+    public StringListInputStream(List lines) throws IOException {
+        this.lines = lines;
+        nextLine = 0;
+        buffer = null;
+        offset = 0;
+        atLineBreak = false;
+
+        // if we have at least one line in the list, get the bytes now.
+        if (lines.size() > 0) {
+            nextBuffer();
+        }
+    }
+
+    /**
+     * Just override the single byte read version, which handles all of the
+     * lineend markers correctly.
+     * 
+     * @return The next byte from the stream or -1 if we've hit the EOF.
+     */
+    public int read() throws IOException {
+        // leave off at the split between a line?
+        if (atLineBreak) {
+            // flip this off and return the second line end character. Also step
+            // to the next line.
+            atLineBreak = false;
+            nextBuffer();
+            return '\n';
+        }
+        // gone past the end? Got an EOF
+        if (buffer == null) {
+            return -1;
+        }
+
+        // reach the end of the line?
+        if (offset >= buffer.length) {
+            // we're now working on a virtual linebreak
+            atLineBreak = true;
+            return '\r';
+        }
+        // just return the next byte
+        return buffer[offset++];
+
+    }
+
+    /**
+     * Step to the next buffer of string data.
+     * 
+     * @exception IOException
+     */
+    protected void nextBuffer() throws IOException {
+        // give an eof check.
+        if (nextLine >= lines.size()) {
+            buffer = null;
+        } else {
+            try {
+                String next = (String) lines.get(nextLine++);
+                buffer = next.getBytes("US-ASCII");
+
+            } catch (UnsupportedEncodingException e) {
+                throw new IOException("Invalid string encoding");
+            }
+        }
+
+        offset = 0;
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.java
new file mode 100644
index 0000000..ad1b7fa
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.java
@@ -0,0 +1,39 @@
+/*
+ * 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.transport.smtp;
+
+/**
+ * Exception for when a SMTP reply string has a problem
+ * 
+ * @version $Rev$ $Date$
+ */
+class MalformedSMTPReplyException extends Exception {
+    MalformedSMTPReplyException() {
+        super();
+    }
+
+    MalformedSMTPReplyException(String msg) {
+        super(msg);
+    }
+
+    MalformedSMTPReplyException(String msg, Exception t) {
+        super(msg, t);
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.java
new file mode 100644
index 0000000..bfd2699
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.java
@@ -0,0 +1,80 @@
+/*
+ * 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.transport.smtp;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+
+public class SMTPAddressFailedException extends MessagingException {
+    // the failing address
+    InternetAddress addr;
+
+    // the failing command
+    protected String cmd;
+
+    // the error code for the failure
+    protected int rc;
+
+    /**
+     * Constructor for an SMTPAddressFailingException.
+     * 
+     * @param addr
+     *            The failing address.
+     * @param cmd
+     *            The failing command string.
+     * @param rc
+     *            The error code for the command.
+     * @param err
+     *            An error message for the exception.
+     */
+    SMTPAddressFailedException(InternetAddress addr, java.lang.String cmd, int rc, java.lang.String err) {
+        super(err);
+        this.cmd = cmd;
+        this.rc = rc;
+        this.addr = addr;
+    }
+
+    /**
+     * Get the failing command string for the exception.
+     * 
+     * @return The string value of the failing command.
+     */
+    public String getCommand() {
+        return cmd;
+    }
+
+    /**
+     * The failing command return code.
+     * 
+     * @return The failure return code.
+     */
+    public int getReturnCode() {
+        return rc;
+    }
+
+    /**
+     * Retrieve the internet address associated with this exception.
+     * 
+     * @return The provided InternetAddress object.
+     */
+    public InternetAddress getAddress() {
+        return addr;
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.java
new file mode 100644
index 0000000..990d656
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.java
@@ -0,0 +1,80 @@
+/*
+ * 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.transport.smtp;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+
+public class SMTPAddressSucceededException extends MessagingException {
+    // the succeeding address
+    InternetAddress addr;
+
+    // the failing command
+    protected String cmd;
+
+    // the error code for the failure
+    protected int rc;
+
+    /**
+     * Constructor for an SMTPAddressSucceededException.
+     * 
+     * @param addr
+     *            The succeeding address.
+     * @param cmd
+     *            The succeeding command string.
+     * @param rc
+     *            The error code for the command.
+     * @param err
+     *            An error message for the exception.
+     */
+    SMTPAddressSucceededException(InternetAddress addr, java.lang.String cmd, int rc, java.lang.String err) {
+        super(err);
+        this.cmd = cmd;
+        this.rc = rc;
+        this.addr = addr;
+    }
+
+    /**
+     * Get the failing command string for the exception.
+     * 
+     * @return The string value of the failing command.
+     */
+    public String getCommand() {
+        return cmd;
+    }
+
+    /**
+     * The failing command return code.
+     * 
+     * @return The failure return code.
+     */
+    public int getReturnCode() {
+        return rc;
+    }
+
+    /**
+     * Retrieve the internet address associated with this exception.
+     * 
+     * @return The provided InternetAddress object.
+     */
+    public InternetAddress getAddress() {
+        return addr;
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.java
new file mode 100644
index 0000000..063e383
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPConnection.java
@@ -0,0 +1,1370 @@
+/*
+ * 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.transport.smtp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.mail.Address;
+import javax.mail.AuthenticationFailedException;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimePart;
+import javax.mail.Session;
+
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
+import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
+import org.apache.geronimo.javamail.util.CountingOutputStream;
+import org.apache.geronimo.javamail.util.MailConnection;
+import org.apache.geronimo.javamail.util.MIMEOutputStream;
+import org.apache.geronimo.javamail.util.ProtocolProperties;
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.XText;
+
+/**
+ * Simple implementation of SMTP transport. Just does plain RFC977-ish delivery.
+ *
+ * @version $Rev$ $Date$
+ */
+public class SMTPConnection extends MailConnection {
+    protected static final String MAIL_SMTP_QUITWAIT = "quitwait";
+    protected static final String MAIL_SMTP_EXTENSION = "mailextension";
+    protected static final String MAIL_SMTP_EHLO = "ehlo";
+    protected static final String MAIL_SMTP_ALLOW8BITMIME = "allow8bitmime";
+    protected static final String MAIL_SMTP_REPORT_SUCCESS = "reportsuccess";
+    protected static final String MAIL_SMTP_STARTTLS_ENABLE = "starttls.enable";
+    protected static final String MAIL_SMTP_STARTTLS_REQUIRED = "starttls.required";
+    protected static final String MAIL_SMTP_AUTH = "auth";
+    protected static final String MAIL_SMTP_FROM = "from";
+    protected static final String MAIL_SMTP_DSN_RET = "dsn.ret";
+    protected static final String MAIL_SMTP_SUBMITTER = "submitter";
+
+    /**
+     * property keys for protocol properties.
+     */
+    protected static final int DEFAULT_NNTP_PORT = 119;
+
+    // the last response line received from the server.
+    protected SMTPReply lastServerResponse = null;
+
+    // do we report success after completion of each mail send.
+    protected boolean reportSuccess;
+    // does the server support transport level security?
+    protected boolean serverTLS = false;
+    // is TLS enabled on our part?
+    protected boolean useTLS = false;
+    // is TLS required on our part?
+    protected boolean requireTLS = false;
+    // should we use 8BITMIME encoding if supported by the server?
+    protected boolean use8bit = false;
+
+    /**
+     * Normal constructor for an SMTPConnection() object.
+     *
+     * @param props  The property bundle for this protocol instance.
+     */
+    public SMTPConnection(ProtocolProperties props) {
+        super(props);
+
+        // check to see if we need to throw an exception after a send operation.
+        reportSuccess = props.getBooleanProperty(MAIL_SMTP_REPORT_SUCCESS, false);
+        // and also check for TLS enablement.
+        useTLS = props.getBooleanProperty(MAIL_SMTP_STARTTLS_ENABLE, false);
+        // and also check if TLS is required.
+        requireTLS = props.getBooleanProperty(MAIL_SMTP_STARTTLS_REQUIRED, false);
+        // and also check for 8bitmime support
+        use8bit = props.getBooleanProperty(MAIL_SMTP_ALLOW8BITMIME, false);
+    }
+
+
+    /**
+     * Connect to the server and do the initial handshaking.
+     *
+     * @param host     The target host name.
+     * @param port     The target port
+     * @param username The connection username (can be null)
+     * @param password The authentication password (can be null).
+     *
+     * @return true if we were able to obtain a connection and
+     *         authenticate.
+     * @exception MessagingException
+     */
+    public boolean protocolConnect(String host, int port, String username, String password) throws MessagingException {
+
+        // now check to see if we need to authenticate. If we need this, then
+        // we must have a username and
+        // password specified. Failing this may result in a user prompt to
+        // collect the information.
+        boolean mustAuthenticate = props.getBooleanProperty(MAIL_SMTP_AUTH, false);
+
+        // if we need to authenticate, and we don't have both a userid and
+        // password, then we fail this
+        // immediately. The Service.connect() method will try to obtain the user
+        // information and retry the
+        // connection one time.
+        if (mustAuthenticate && (username == null || password == null)) {
+            debugOut("Failing connection for missing authentication information");
+            return false;
+        }
+
+        super.protocolConnect(host, port, username, password);
+
+        try {
+            // create socket and connect to server.
+            getConnection();
+
+            // receive welcoming message
+            if (!getWelcome()) {
+                debugOut("Error getting welcome message");
+                throw new MessagingException("Error in getting welcome msg");
+            }
+
+            // say hello
+            if (!sendHandshake()) {
+                debugOut("Error getting processing handshake message");
+                throw new MessagingException("Error in saying EHLO to server");
+            }
+
+            // authenticate with the server, if necessary
+            if (!processAuthentication()) {
+                debugOut("User authentication failure");
+                throw new AuthenticationFailedException("Error authenticating with server");
+            }
+        } catch (IOException e) {
+            debugOut("I/O exception establishing connection", e);
+            throw new MessagingException("Connection error", e);
+        }
+        debugOut("Successful connection");
+        return true;
+    }
+
+
+    /**
+     * 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
+            sendQuit();
+        } 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();
+        }
+    }
+
+    public String toString() {
+        return "SMTPConnection host: " + serverHost + " port: " + serverPort;
+    }
+
+
+    /**
+     * Set the sender for this mail.
+     *
+     * @param message
+     *                   The message we're sending.
+     *
+     * @return True if the command was accepted, false otherwise.
+     * @exception MessagingException
+     */
+    protected boolean sendMailFrom(Message message) throws MessagingException {
+
+        // need to sort the from value out from a variety of sources.
+        String from = null;
+
+        // first potential source is from the message itself, if it's an
+        // instance of SMTPMessage.
+        if (message instanceof SMTPMessage) {
+            from = ((SMTPMessage) message).getEnvelopeFrom();
+        }
+
+        // if not available from the message, check the protocol property next
+        if (from == null || from.length() == 0) {
+            // the from value can be set explicitly as a property
+            from = props.getProperty(MAIL_SMTP_FROM);
+        }
+
+        // if not there, see if we have something in the message header.
+        if (from == null || from.length() == 0) {
+            Address[] fromAddresses = message.getFrom();
+
+            // if we have some addresses in the header, then take the first one
+            // as our From: address
+            if (fromAddresses != null && fromAddresses.length > 0) {
+                from = ((InternetAddress) fromAddresses[0]).getAddress();
+            }
+            // get what the InternetAddress class believes to be the local
+            // address.
+            else {
+                InternetAddress local = InternetAddress.getLocalAddress(session);
+                if (local != null) {
+                    from = local.getAddress();
+                }
+            }
+        }
+
+        if (from == null || from.length() == 0) {
+            throw new MessagingException("no FROM address");
+        }
+
+        StringBuffer command = new StringBuffer();
+
+        // start building up the command
+        command.append("MAIL FROM: ");
+        command.append(fixEmailAddress(from));
+
+        // If the server supports the 8BITMIME extension, we might need to change the
+        // transfer encoding for the content to allow for direct transmission of the
+        // 8-bit codes.
+        if (supportsExtension("8BITMIME")) {
+            // we only do this if the capability was enabled via a property option or
+            // by explicitly setting the property on the message object.
+            if (use8bit || (message instanceof SMTPMessage && ((SMTPMessage)message).getAllow8bitMIME())) {
+                // make sure we add the BODY= option to the FROM message.
+                command.append(" BODY=8BITMIME");
+
+                // go check the content and see if the can convert the transfer encoding to
+                // allow direct 8-bit transmission.
+                if (convertTransferEncoding((MimeMessage)message)) {
+                    // if we changed the encoding on any of the parts, then we
+                    // need to save the message again
+                    message.saveChanges();
+                }
+            }
+        }
+
+        // some servers ask for a size estimate on the initial send
+        if (supportsExtension("SIZE")) {
+            int estimate = getSizeEstimate(message);
+            if (estimate > 0) {
+                command.append(" SIZE=" + estimate);
+            }
+        }
+
+        // does this server support Delivery Status Notification? Then we may
+        // need to add some extra to the command.
+        if (supportsExtension("DSN")) {
+            String returnNotification = null;
+
+            // the return notification stuff might be set as value on the
+            // message object itself.
+            if (message instanceof SMTPMessage) {
+                // we need to convert the option into a string value.
+                switch (((SMTPMessage) message).getReturnOption()) {
+                case SMTPMessage.RETURN_FULL:
+                    returnNotification = "FULL";
+                    break;
+
+                case SMTPMessage.RETURN_HDRS:
+                    returnNotification = "HDRS";
+                    break;
+                }
+            }
+
+            // if not obtained from the message object, it can also be set as a
+            // property.
+            if (returnNotification == null) {
+                // the DSN value is set by yet another property.
+                returnNotification = props.getProperty(MAIL_SMTP_DSN_RET);
+            }
+
+            // if we have a target, add the notification stuff to our FROM
+            // command.
+            if (returnNotification != null) {
+                command.append(" RET=");
+                command.append(returnNotification);
+            }
+        }
+
+        // if this server supports AUTH and we have submitter information, then
+        // we also add the
+        // "AUTH=" keyword to the MAIL FROM command (see RFC 2554).
+
+        if (supportsExtension("AUTH")) {
+            String submitter = null;
+
+            // another option that can be specified on the message object.
+            if (message instanceof SMTPMessage) {
+                submitter = ((SMTPMessage) message).getSubmitter();
+            }
+            // if not part of the object, try for a propery version.
+            if (submitter == null) {
+                // we only send the extra keyword is a submitter is specified.
+                submitter = props.getProperty(MAIL_SMTP_SUBMITTER);
+            }
+            // we have one...add the keyword, plus the submitter info in xtext
+            // format (defined by RFC 1891).
+            if (submitter != null) {
+                command.append(" AUTH=");
+                try {
+                    // add this encoded
+                    command.append(new String(XText.encode(submitter.getBytes("US-ASCII")), "US-ASCII"));
+                } catch (UnsupportedEncodingException e) {
+                    throw new MessagingException("Invalid submitter value " + submitter);
+                }
+            }
+        }
+
+        String extension = null;
+
+        // now see if we need to add any additional extension info to this
+        // command. The extension is not
+        // checked for validity. That's the reponsibility of the caller.
+        if (message instanceof SMTPMessage) {
+            extension = ((SMTPMessage) message).getMailExtension();
+        }
+        // this can come either from the object or from a set property.
+        if (extension == null) {
+            extension = props.getProperty(MAIL_SMTP_EXTENSION);
+        }
+
+        // have something real to add?
+        if (extension != null && extension.length() != 0) {
+            // tack this on the end with a blank delimiter.
+            command.append(' ');
+            command.append(extension);
+        }
+
+        // and finally send the command
+        SMTPReply line = sendCommand(command.toString());
+
+        // 250 response indicates success.
+        return line.getCode() == SMTPReply.COMMAND_ACCEPTED;
+    }
+
+
+    /**
+     * Check to see if a MIME body part can have its
+     * encoding changed from quoted-printable or base64
+     * encoding to 8bit encoding.  In order for this
+     * to work, it must follow the rules laid out in
+     * RFC 2045.  To qualify for conversion, the text
+     * must be:
+     *
+     * 1)  No more than 998 bytes long
+     * 2)  All lines are terminated with CRLF sequences
+     * 3)  CR and LF characters only occur in properly
+     * formed line separators
+     * 4)  No null characters are allowed.
+     *
+     * The conversion will only be applied to text
+     * elements, and this will recurse through the
+     * different elements of MultiPart content.
+     *
+     * @param bodyPart The bodyPart to convert. Initially, this will be
+     *                 the message itself.
+     *
+     * @return true if any conversion was performed, false if
+     *         nothing was converted.
+     */
+    protected boolean convertTransferEncoding(MimePart bodyPart)
+    {
+        boolean converted = false;
+        try {
+            // if this is a multipart element, apply the conversion rules
+            // to each of the parts.
+            if (bodyPart.isMimeType("multipart/")) {
+                MimeMultipart parts = (MimeMultipart)bodyPart.getContent();
+                for (int i = 0; i < parts.getCount(); i++) {
+                    // convert each body part, and accumulate the conversion result
+                    converted = converted && convertTransferEncoding((MimePart)parts.getBodyPart(i));
+                }
+            }
+            else {
+                // we only do this if the encoding is quoted-printable or base64
+                String encoding =  bodyPart.getEncoding();
+                if (encoding != null) {
+                    encoding = encoding.toLowerCase();
+                    if (encoding.equals("quoted-printable") || encoding.equals("base64")) {
+                        // this requires encoding.  Read the actual content to see if
+                        // it conforms to the 8bit encoding rules.
+                        if (isValid8bit(bodyPart.getInputStream())) {
+                            // There's a huge hidden gotcha lurking under the covers here.
+                            // If the content just exists as an encoded byte array, then just
+                            // switching the transfer encoding will mess things up because the
+                            // already encoded data gets transmitted in encoded form, but with
+                            // and 8bit encoding style.  As a result, it doesn't get unencoded on
+                            // the receiving end.  This is a nasty problem to debug.
+                            //
+                            // The solution is to get the content as it's object type, set it back
+                            // on the the message in raw form.  Requesting the content will apply the
+                            // current transfer encoding value to the data.  Once we have set the
+                            // content value back, we can reset the transfer encoding.
+                            bodyPart.setContent(bodyPart.getContent(), bodyPart.getContentType());
+
+                            // it's valid, so change the transfer encoding to just
+                            // pass the data through.
+                            bodyPart.setHeader("Content-Transfer-Encoding", "8bit");
+                            converted = true;   // we've changed something
+                        }
+                    }
+                }
+            }
+        } catch (MessagingException e) {
+        } catch (IOException e) {
+        }
+        return converted;
+    }
+
+
+    /**
+     * Get the server's welcome blob from the wire....
+     */
+    protected boolean getWelcome() throws MessagingException {
+        SMTPReply line = getReply();
+        // just return the error status...we don't care about any of the
+        // response information
+        return !line.isError();
+    }
+
+
+    /**
+     * Get an estimate of the transmission size for this
+     * message.  This size is the complete message as it is
+     * encoded and transmitted on the DATA command, not counting
+     * the terminating ".CRLF".
+     *
+     * @param msg    The message we're sending.
+     *
+     * @return The count of bytes, if it can be calculated.
+     */
+    protected int getSizeEstimate(Message msg) {
+        // now the data... I could look at the type, but
+        try {
+            CountingOutputStream outputStream = new CountingOutputStream();
+
+            // the data content has two requirements we need to meet by
+            // filtering the
+            // output stream. Requirement 1 is to conicalize any line breaks.
+            // All line
+            // breaks will be transformed into properly formed CRLF sequences.
+            //
+            // Requirement 2 is to perform byte-stuff for any line that begins
+            // with a "."
+            // so that data is not confused with the end-of-data marker (a
+            // "\r\n.\r\n" sequence.
+            //
+            // The MIME output stream performs those two functions on behalf of
+            // the content
+            // writer.
+            MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
+
+            msg.writeTo(mimeOut);
+
+            // now to finish, we make sure there's a line break at the end.
+            mimeOut.forceTerminatingLineBreak();
+            // and flush the data to send it along
+            mimeOut.flush();
+
+            return outputStream.getCount();
+        } catch (IOException e) {
+            return 0;     // can't get an estimate
+        } catch (MessagingException e) {
+            return 0;     // can't get an estimate
+        }
+    }
+
+
+    /**
+     * Sends the data in the message down the socket. This presumes the server
+     * is in the right place and ready for getting the DATA message and the data
+     * right place in the sequence
+     */
+    protected void sendData(MimeMessage msg) throws MessagingException {
+
+        // send the DATA command
+        SMTPReply line = sendCommand("DATA");
+
+        if (line.isError()) {
+            throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
+        }
+
+        // now the data... I could look at the type, but
+        try {
+            // the data content has two requirements we need to meet by
+            // filtering the
+            // output stream. Requirement 1 is to conicalize any line breaks.
+            // All line
+            // breaks will be transformed into properly formed CRLF sequences.
+            //
+            // Requirement 2 is to perform byte-stuff for any line that begins
+            // with a "."
+            // so that data is not confused with the end-of-data marker (a
+            // "\r\n.\r\n" sequence.
+            //
+            // The MIME output stream performs those two functions on behalf of
+            // the content
+            // writer.
+            MIMEOutputStream mimeOut = new MIMEOutputStream(outputStream);
+
+            msg.writeTo(mimeOut, new String[] {"Bcc", "Content-Length"});
+
+            // now to finish, we send a CRLF sequence, followed by a ".".
+            mimeOut.writeSMTPTerminator();
+            // and flush the data to send it along
+            mimeOut.flush();
+            this.outputStream.flush(); // most of the time MIMEOutputStream#flush does nothing so ensure we actually flush the data
+        } catch (IOException e) {
+            throw new MessagingException(e.toString());
+        } catch (MessagingException e) {
+            throw new MessagingException(e.toString());
+        }
+
+        // use a longer time out here to give the server time to process the
+        // data.
+        line = getReply(TIMEOUT * 2);
+
+        if (line.isError()) {
+            throw new MessagingException("Error issuing SMTP 'DATA' command: " + line);
+        }
+    }
+
+    /**
+     * Sends the QUIT message and receieves the response
+     */
+    protected void sendQuit() throws MessagingException {
+        // there's yet another property that controls whether we should wait for
+        // a reply for a QUIT command. If true, we're suppposed to wait for a response
+        // from the QUIT command.  Otherwise we just send the QUIT and bail.  The default
+        // is "false"
+        if (props.getBooleanProperty(MAIL_SMTP_QUITWAIT, true)) {
+            // handle as a real command...we're going to ignore the response.
+            sendCommand("QUIT");
+        } else {
+            // just send the command without waiting for a response.
+            sendLine("QUIT");
+        }
+    }
+
+    /**
+     * Sets a receiver address for the current message
+     *
+     * @param addr
+     *            The target address.
+     * @param dsn
+     *            An optional DSN option appended to the RCPT TO command.
+     *
+     * @return The status for this particular send operation.
+     * @exception MessagingException
+     */
+    public SendStatus sendRcptTo(InternetAddress addr, String dsn) throws MessagingException {
+        // compose the command using the fixed up email address. Normally, this
+        // involves adding
+        // "<" and ">" around the address.
+
+        StringBuffer command = new StringBuffer();
+
+        // compose the first part of the command
+        command.append("RCPT TO: ");
+        command.append(fixEmailAddress(addr.getAddress()));
+
+        // if we have DSN information, append it to the command.
+        if (dsn != null) {
+            command.append(" NOTIFY=");
+            command.append(dsn);
+        }
+
+        // get a string version of this command.
+        String commandString = command.toString();
+
+        SMTPReply line = sendCommand(commandString);
+
+        switch (line.getCode()) {
+        // these two are both successful transmissions
+        case SMTPReply.COMMAND_ACCEPTED:
+        case SMTPReply.ADDRESS_NOT_LOCAL:
+            // we get out of here with the status information.
+            return new SendStatus(SendStatus.SUCCESS, addr, commandString, line);
+
+        // these are considered invalid address errors
+        case SMTPReply.PARAMETER_SYNTAX_ERROR:
+        case SMTPReply.INVALID_COMMAND_SEQUENCE:
+        case SMTPReply.MAILBOX_NOT_FOUND:
+        case SMTPReply.INVALID_MAILBOX:
+        case SMTPReply.USER_NOT_LOCAL:
+            // we get out of here with the status information.
+            return new SendStatus(SendStatus.INVALID_ADDRESS, addr, commandString, line);
+
+        // the command was valid, but something went wrong in the server.
+        case SMTPReply.SERVICE_NOT_AVAILABLE:
+        case SMTPReply.MAILBOX_BUSY:
+        case SMTPReply.PROCESSING_ERROR:
+        case SMTPReply.INSUFFICIENT_STORAGE:
+        case SMTPReply.MAILBOX_FULL:
+            // we get out of here with the status information.
+            return new SendStatus(SendStatus.SEND_FAILURE, addr, commandString, line);
+
+        // everything else is considered really bad...
+        default:
+            // we get out of here with the status information.
+            return new SendStatus(SendStatus.GENERAL_ERROR, addr, commandString, line);
+        }
+    }
+
+    /**
+     * Send a command to the server, returning the first response line back as a
+     * reply.
+     *
+     * @param data
+     *            The data to send.
+     *
+     * @return A reply object with the reply line.
+     * @exception MessagingException
+     */
+    protected SMTPReply sendCommand(String data) throws MessagingException {
+        sendLine(data);
+        return getReply();
+    }
+
+    /**
+     * Sends a message down the socket and terminates with the appropriate CRLF
+     */
+    protected void sendLine(String data) throws MessagingException {
+        if (socket == null || !socket.isConnected()) {
+            throw new MessagingException("no connection");
+        }
+        try { // don't write it in multiple times, ie build the data + "\r\n" string in memory to not get surprises on servers read() side
+            outputStream.write((data + "\r\n").getBytes("ISO8859-1"));
+            outputStream.flush();
+        } catch (IOException e) {
+            throw new MessagingException(e.toString());
+        }
+    }
+
+    /**
+     * Receives one line from the server. A line is a sequence of bytes
+     * terminated by a CRLF
+     *
+     * @return the line from the server as String
+     */
+    protected String receiveLine() throws MessagingException {
+        return receiveLine(TIMEOUT);
+    }
+
+    protected SMTPReply getReply() throws MessagingException {
+        return getReply(TIMEOUT);
+    }
+
+    /**
+     * Get a reply line for an SMTP command.
+     *
+     * @return An SMTP reply object from the stream.
+     */
+    protected SMTPReply getReply(int timeout) throws MessagingException {
+        try {
+            lastServerResponse = new SMTPReply(receiveLine(timeout));
+            // if the first line we receive is a continuation, continue
+            // reading lines until we reach the non-continued one.
+            while (lastServerResponse.isContinued()) {
+                lastServerResponse.addLine(receiveLine(timeout));
+            }
+        } catch (MalformedSMTPReplyException e) {
+            throw new MessagingException(e.toString());
+        }
+        return lastServerResponse;
+    }
+
+    /**
+     * Retrieve the last response received from the SMTP server.
+     *
+     * @return The raw response string (including the error code) returned from
+     *         the SMTP server.
+     */
+    public SMTPReply getLastServerResponse() {
+        return lastServerResponse;
+    }
+
+
+    /**
+     * Receives one line from the server. A line is a sequence of bytes
+     * terminated by a CRLF
+     *
+     * @return the line from the server as String
+     */
+    protected String receiveLine(int delayMillis) throws MessagingException {
+        if (socket == null || !socket.isConnected()) {
+            throw new MessagingException("no connection");
+        }
+
+        int timeout = 0;
+
+        try {
+            // for now, read byte for byte, looking for a CRLF
+            timeout = socket.getSoTimeout();
+
+            socket.setSoTimeout(delayMillis);
+
+            StringBuffer buff = new StringBuffer();
+
+            int c;
+            boolean crFound = false, lfFound = false;
+
+            while ((c = inputStream.read()) != -1 && crFound == false && lfFound == false) {
+                // we're looking for a CRLF sequence, so mark each one as seen.
+                // Any other
+                // character gets appended to the end of the buffer.
+                if (c == CR) {
+                    crFound = true;
+                } else if (c == LF) {
+                    lfFound = true;
+                } else {
+                    buff.append((char) c);
+                }
+            }
+
+            String line = buff.toString();
+            return line;
+
+        } catch (SocketException e) {
+            throw new MessagingException(e.toString());
+        } catch (IOException e) {
+            throw new MessagingException(e.toString());
+        } finally {
+            try {
+                socket.setSoTimeout(timeout);
+            } catch (SocketException e) {
+                // ignore - was just trying to do the decent thing...
+            }
+        }
+    }
+
+    /**
+     * Convert an InternetAddress into a form sendable on an SMTP mail command.
+     * InternetAddress.getAddress() generally returns just the address portion
+     * of the full address, minus route address markers. We need to ensure we
+     * have an address with '<' and '>' delimiters.
+     *
+     * @param mail
+     *            The mail address returned from InternetAddress.getAddress().
+     *
+     * @return A string formatted for sending.
+     */
+    protected String fixEmailAddress(String mail) {
+        if (mail.charAt(0) == '<') {
+            return mail;
+        }
+        return "<" + mail + ">";
+    }
+
+    /**
+     * Start the handshake process with the server, including setting up and
+     * TLS-level work. At the completion of this task, we should be ready to
+     * authenticate with the server, if needed.
+     */
+    protected boolean sendHandshake() throws MessagingException {
+        // check to see what sort of initial handshake we need to make.
+        boolean useEhlo = props.getBooleanProperty(MAIL_SMTP_EHLO, true);
+        // if we're to use Ehlo, send it and then fall back to just a HELO
+        // message if it fails.
+        if (useEhlo) {
+            if (!sendEhlo()) {
+                sendHelo();
+            }
+        } else {
+            // send the initial hello response.
+            sendHelo();
+        }
+
+        if (useTLS || requireTLS) {
+            // if we've been told to use TLS
+            // if its not required and server does not support it we establish an unsecure connection
+            //see GERONIMO-5873 and GERONIMO-5430
+            if (requireTLS && !serverTLS) {
+                // if we've been told to use TLS, and this server doesn't support
+                // it, then this is a failure
+                throw new MessagingException("Server doesn't support required transport level security");
+            } else if (serverTLS){
+                // if the server supports TLS, then use it for the connection.
+                // on our connection.
+                getConnectedTLSSocket();
+
+                // some servers (gmail is one that I know of) only send a STARTTLS
+                // extension message on the
+                // first EHLO command. Now that we have the TLS handshaking
+                // established, we need to send a
+                // second EHLO message to retrieve the AUTH records from the server.
+                if (!sendEhlo()) {
+                    throw new MessagingException("Failure sending EHLO command to SMTP server");
+                }
+            } else {
+                if (debug) {
+                    debugOut("STARTTLS is enabled but not required and server does not support it. So we establish a connection without transport level security");
+                }
+            }
+        }
+
+        // this worked.
+        return true;
+    }
+
+
+    /**
+     * Switch the connection to using TLS level security, switching to an SSL
+     * socket.
+     */
+    protected void getConnectedTLSSocket() throws MessagingException {
+        debugOut("Attempting to negotiate STARTTLS with server " + serverHost);
+        // tell the server of our intention to start a TLS session
+        SMTPReply line = sendCommand("STARTTLS");
+
+        if (line.getCode() != SMTPReply.SERVICE_READY) {
+            debugOut("STARTTLS command rejected by SMTP server " + serverHost);
+            throw new MessagingException("Unable to make TLS server connection");
+        }
+
+        debugOut("STARTTLS command accepted");
+
+        // the base class handles the socket switch details
+        super.getConnectedTLSSocket();
+    }
+
+
+    /**
+     * Send the EHLO command to the SMTP server.
+     *
+     * @return True if the command was accepted ok, false for any errors.
+     * @exception SMTPTransportException
+     * @exception MalformedSMTPReplyException
+     * @exception MessagingException
+     */
+    protected boolean sendEhlo() throws MessagingException {
+        sendLine("EHLO " + getLocalHost());
+
+        SMTPReply reply = getReply();
+
+        // we get a 250 code back. The first line is just a greeting, and
+        // extensions are identifed on
+        // continuations. If this fails, then we'll try once more with HELO to
+        // establish bona fides.
+        if (reply.getCode() != SMTPReply.COMMAND_ACCEPTED) {
+            return false;
+        }
+
+        // create a fresh mapping and authentications table
+        capabilities = new HashMap();
+        authentications = new ArrayList();
+
+        List lines = reply.getLines();
+        // process all of the continuation lines
+        for (int i = 1; i < lines.size(); i++) {
+            // go process the extention
+            processExtension((String)lines.get(i));
+        }
+        return true;
+    }
+
+    /**
+     * Send the HELO command to the SMTP server.
+     *
+     * @exception MessagingException
+     */
+    protected void sendHelo() throws MessagingException {
+        // create a fresh mapping and authentications table
+        // these will be empty, but it will prevent NPEs
+        capabilities = new HashMap();
+        authentications = new ArrayList();
+
+        sendLine("HELO " + getLocalHost());
+
+        SMTPReply line = getReply();
+
+        // we get a 250 code back. The first line is just a greeting, and
+        // extensions are identifed on
+        // continuations. If this fails, then we'll try once more with HELO to
+        // establish bona fides.
+        if (line.getCode() != SMTPReply.COMMAND_ACCEPTED) {
+            throw new MessagingException("Failure sending HELO command to SMTP server");
+        }
+    }
+
+    /**
+     * Return the current startTLS property.
+     *
+     * @return The current startTLS property.
+     */
+    public boolean getStartTLS() {
+        return useTLS;
+    }
+
+
+    /**
+     * Set a new value for the startTLS property.
+     *
+     * @param start
+     *            The new setting.
+     */
+    public void setStartTLS(boolean start) {
+        useTLS = start;
+    }
+
+    
+    /**
+     * Return the current requireTLS property.
+     * 
+     * @return The current requireTLS property.
+     */
+    public boolean getRequireTLS() {
+        return requireTLS;
+    }
+
+
+    /**
+     * Set a new value for the requireTLS property.
+     * 
+     * @param require
+     *                  The new setting.
+     */
+    public void setRequireTLS(boolean require) {
+        requireTLS = require;
+    }
+
+
+    /**
+     * Process an extension string passed back as the EHLP response.
+     *
+     * @param extension
+     *            The string value of the extension (which will be of the form
+     *            "NAME arguments").
+     */
+    protected void processExtension(String extension) {
+        debugOut("Processing extension " + extension);
+        String extensionName = extension.toUpperCase();
+        String argument = "";
+
+        int delimiter = extension.indexOf(' ');
+        // if we have a keyword with arguments, parse them out and add to the
+        // argument map.
+        if (delimiter != -1) {
+            extensionName = extension.substring(0, delimiter).toUpperCase();
+            argument = extension.substring(delimiter + 1);
+        }
+
+        // add this to the map so it can be tested later.
+        capabilities.put(extensionName, argument);
+
+        // process a few special ones that don't require extra parsing.
+        // AUTH and AUTH=LOGIN are handled the same
+        if (extensionName.equals("AUTH")) {
+            // if we don't have an argument on AUTH, this means LOGIN.
+            if (argument == null) {
+                authentications.add("LOGIN");
+            } else {
+                // The security mechanisms are blank delimited tokens.
+                StringTokenizer tokenizer = new StringTokenizer(argument);
+
+                while (tokenizer.hasMoreTokens()) {
+                    String mechanism = tokenizer.nextToken().toUpperCase();
+                    authentications.add(mechanism);
+                }
+            }
+        }
+        // special case for some older servers.
+        else if (extensionName.equals("AUTH=LOGIN")) {
+            authentications.add("LOGIN");
+        }
+        // does this support transport level security?
+        else if (extensionName.equals("STARTTLS")) {
+            // flag this for later
+            serverTLS = true;
+        }
+    }
+
+
+    /**
+     * Retrieve any argument information associated with a extension reported
+     * back by the server on the EHLO command.
+     *
+     * @param name
+     *            The name of the target server extension.
+     *
+     * @return Any argument passed on a server extension. Returns null if the
+     *         extension did not include an argument or the extension was not
+     *         supported.
+     */
+    public String extensionParameter(String name) {
+        if (capabilities != null) {
+            return (String)capabilities.get(name);
+        }
+        return null;
+    }
+
+
+    /**
+     * Tests whether the target server supports a named extension.
+     *
+     * @param name
+     *            The target extension name.
+     *
+     * @return true if the target server reported on the EHLO command that is
+     *         supports the targer server, false if the extension was not
+     *         supported.
+     */
+    public boolean supportsExtension(String name) {
+        // this only returns null if we don't have this extension
+        return extensionParameter(name) != null;
+    }
+
+
+    /**
+     * Authenticate with the server, if necessary (or possible).
+     *
+     * @return true if we are ok to proceed, false for an authentication
+     *         failures.
+     */
+    protected boolean processAuthentication() throws MessagingException {
+        // no authentication defined?
+        if (!props.getBooleanProperty(MAIL_SMTP_AUTH, false)) {
+            return true;
+        }
+
+        // we need to authenticate, but we don't have userid/password
+        // information...fail this
+        // immediately.
+        if (username == null || password == null) {
+            return false;
+        }
+
+        // if unable to get an appropriate authenticator, just fail it.
+        ClientAuthenticator authenticator = getSaslAuthenticator();
+        if (authenticator == null) {
+            throw new MessagingException("Unable to obtain SASL authenticator");
+        }
+
+
+        if (debug) {
+            debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
+        }
+
+        // if the authenticator has some initial data, we compose a command
+        // containing the initial data.
+        if (authenticator.hasInitialResponse()) {
+            StringBuffer command = new StringBuffer();
+            // the auth command initiates the handshaking.
+            command.append("AUTH ");
+            // and tell the server which mechanism we're using.
+            command.append(authenticator.getMechanismName());
+            command.append(" ");
+            // and append the response data
+            try {
+                command.append(new String(Base64.encode(authenticator.evaluateChallenge(null)), "US-ASCII"));
+            } catch (UnsupportedEncodingException e) {
+            }
+            // send the command now
+            sendLine(command.toString());
+        }
+        // we just send an auth command with the command type.
+        else {
+            StringBuffer command = new StringBuffer();
+            // the auth command initiates the handshaking.
+            command.append("AUTH ");
+            // and tell the server which mechanism we're using.
+            command.append(authenticator.getMechanismName());
+            // send the command now
+            sendLine(command.toString());
+        }
+
+        // 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) {
+            // get the next line, and if it is an error response, return now.
+            SMTPReply line;
+            try {
+                line = new SMTPReply(receiveLine());
+            } catch (MalformedSMTPReplyException e) {
+                throw new MessagingException(e.toString());
+            } catch (MessagingException e) {
+                throw e;
+            }
+
+            // if we get a completion return, we've passed muster, so give an
+            // authentication response.
+            if (line.getCode() == SMTPReply.AUTHENTICATION_COMPLETE) {
+                debugOut("Successful SMTP authentication");
+                return true;
+            }
+            // we have an additional challenge to process.
+            else if (line.getCode() == SMTPReply.AUTHENTICATION_CHALLENGE) {
+                // Does the authenticator think it is finished? We can't answer
+                // an additional challenge,
+                // so fail this.
+                if (authenticator.isComplete()) {
+                    return false;
+                }
+
+                try {
+                    // we're passed back a challenge value, Base64 encoded.
+                    byte[] challenge = Base64.decode(line.getMessage().getBytes("ISO8859-1"));
+
+                    // have the authenticator evaluate and send back the encoded
+                    // response.
+                    sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge)), "US-ASCII"));
+                } catch (UnsupportedEncodingException e) {
+                }
+            }
+            // completion or challenge are the only responses we know how to
+            // handle. Anything else must
+            // be a failure.
+            else {
+                if (debug) {
+                    debugOut("Authentication failure " + line);
+                }
+                return false;
+            }
+        }
+    }
+
+
+    /**
+     * Attempt to retrieve a SASL authenticator for this
+     * protocol.
+     *
+     * @return A SASL authenticator, or null if a suitable one
+     *         was not located.
+     */
+    protected ClientAuthenticator getSaslAuthenticator() {
+        return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
+    }
+
+
+    /**
+     * Read the bytes in a stream a test to see if this
+     * conforms to the RFC 2045 rules for 8bit encoding.
+     *
+     * 1)  No more than 998 bytes long
+     * 2)  All lines are terminated with CRLF sequences
+     * 3)  CR and LF characters only occur in properly
+     * formed line separators
+     * 4)  No null characters are allowed.
+     *
+     * @param inStream The source input stream.
+     *
+     * @return true if this can be transmitted successfully
+     *         using 8bit encoding, false if an alternate encoding
+     *         will be required.
+     */
+    protected boolean isValid8bit(InputStream inStream) {
+        try {
+            int ch;
+            int lineLength = 0;
+            while ((ch = inStream.read()) >= 0) {
+                // nulls are decidedly not allowed
+                if (ch == 0) {
+                    return false;
+                }
+                // start of a CRLF sequence (potentially)
+                else if (ch == '\r') {
+                    // check the next character.  There must be one,
+                    // and it must be a LF for this to be value
+                    ch = inStream.read();
+                    if (ch != '\n') {
+                        return false;
+                    }
+                    // reset the line length
+                    lineLength = 0;
+                }
+                else {
+                    // a normal character
+                    lineLength++;
+                    // make sure the line is not too long
+                    if (lineLength > 998) {
+                        return false;
+                    }
+                }
+
+            }
+        } catch (IOException e) {
+            return false;  // can't read this, don't try passing it
+        }
+        // this converted ok
+        return true;
+    }
+
+
+    /**
+     * Simple holder class for the address/send status duple, as we can have
+     * mixed success for a set of addresses and a message
+     */
+    static public class SendStatus {
+        public final static int SUCCESS = 0;
+
+        public final static int INVALID_ADDRESS = 1;
+
+        public final static int SEND_FAILURE = 2;
+
+        public final static int GENERAL_ERROR = 3;
+
+        // the status type of the send operation.
+        int status;
+
+        // the address associated with this status
+        InternetAddress address;
+
+        // the command string send to the server.
+        String cmd;
+
+        // the reply from the server.
+        SMTPReply reply;
+
+        /**
+         * Constructor for a SendStatus item.
+         *
+         * @param s
+         *            The status type.
+         * @param a
+         *            The address this is the status for.
+         * @param c
+         *            The command string associated with this status.
+         * @param r
+         *            The reply information from the server.
+         */
+        public SendStatus(int s, InternetAddress a, String c, SMTPReply r) {
+            this.cmd = c;
+            this.status = s;
+            this.address = a;
+            this.reply = r;
+        }
+
+        /**
+         * Get the status information for this item.
+         *
+         * @return The current status code.
+         */
+        public int getStatus() {
+            return this.status;
+        }
+
+        /**
+         * Retrieve the InternetAddress object associated with this send
+         * operation.
+         *
+         * @return The associated address object.
+         */
+        public InternetAddress getAddress() {
+            return this.address;
+        }
+
+        /**
+         * Retrieve the reply information associated with this send operati
+         *
+         * @return The SMTPReply object received for the operation.
+         */
+        public SMTPReply getReply() {
+            return reply;
+        }
+
+        /**
+         * Get the command string sent for this send operation.
+         *
+         * @return The command string for the MAIL TO command sent to the
+         *         server.
+         */
+        public String getCommand() {
+            return cmd;
+        }
+
+        /**
+         * Get an exception object associated with this send operation. There is
+         * a mechanism for reporting send success via a send operation, so this
+         * will be either a success or failure exception.
+         *
+         * @param reportSuccess
+         *            Indicates if we want success operations too.
+         *
+         * @return A newly constructed exception object.
+         */
+        public MessagingException getException(boolean reportSuccess) {
+            if (status != SUCCESS) {
+                return new SMTPAddressFailedException(address, cmd, reply.getCode(), reply.getMessage());
+            } else {
+                if (reportSuccess) {
+                    return new SMTPAddressSucceededException(address, cmd, reply.getCode(), reply.getMessage());
+                }
+            }
+            return null;
+        }
+    }
+
+
+    /**
+     * Reset the server connection after an error.
+     *
+     * @exception MessagingException
+     */
+    public void resetConnection() throws MessagingException {
+        // we want the caller to retrieve the last response responsbile for
+        // requiring the reset, so save and
+        // restore that info around the reset.
+        SMTPReply last = lastServerResponse;
+
+        // send a reset command.
+        SMTPReply line = sendCommand("RSET");
+
+        // if this did not reset ok, just close the connection
+        if (line.getCode() != SMTPReply.COMMAND_ACCEPTED) {
+            close();
+        }
+        // restore this.
+        lastServerResponse = last;
+    }
+
+
+    /**
+     * Return the current reportSuccess property.
+     *
+     * @return The current reportSuccess property.
+     */
+    public boolean getReportSuccess() {
+        return reportSuccess;
+    }
+
+    /**
+     * Set a new value for the reportSuccess property.
+     *
+     * @param report
+     *            The new setting.
+     */
+    public void setReportSuccess(boolean report) {
+        reportSuccess = report;
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.java
new file mode 100644
index 0000000..91d8c95
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.java
@@ -0,0 +1,237 @@
+/*
+ * 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.transport.smtp;
+
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+
+public class SMTPMessage extends MimeMessage {
+
+    // never notify
+    public static final int NOTIFY_NEVER = -1;
+
+    // notify of successful deliveries.
+    public static final int NOTIFY_SUCCESS = 1;
+
+    // notify of delivery failures.
+    public static final int NOTIFY_FAILURE = 2;
+
+    // notify of delivery delays
+    public static final int NOTIFY_DELAY = 4;
+
+    // return full message with status notifications
+    public static final int RETURN_FULL = 1;
+
+    // return only message headers with status notifications
+    public static final int RETURN_HDRS = 2;
+
+    // support 8BitMime encodings
+    protected boolean allow8bitMIME = false;
+
+    // a from address specified in the message envelope. Overrides other from
+    // sources.
+    protected String envelopeFrom = null;
+
+    // an option string to append to the MAIL command on sending.
+    protected String mailExtension = null;
+
+    // SMTP mail notification options if DSN is supported.
+    protected int notifyOptions = 0;
+
+    // DSN return option notification values.
+    protected int returnOption = 0;
+
+    // allow sending if some addresses give errors.
+    protected boolean sendPartial = false;
+
+    // an RFC 2554 AUTH= value.
+    protected String submitter = null;
+
+    /**
+     * Default (and normal) constructor for an SMTPMessage.
+     * 
+     * @param session
+     *            The hosting Javamail Session.
+     */
+    public SMTPMessage(Session session) {
+        // this is a simple one.
+        super(session);
+    }
+
+    /**
+     * Construct an SMTPMessage instance by reading and parsing the data from
+     * the provided InputStream. The InputStream will be left positioned at the
+     * end of the message data on constructor completion.
+     * 
+     * @param session
+     *            The hosting Javamail Session.
+     */
+    public SMTPMessage(Session session, InputStream source) throws MessagingException {
+        // this is a simple one.
+        super(session, source);
+    }
+
+    /**
+     * Construct an SMTPMimeMessage from another source MimeMessage object. The
+     * new object and the old object are independent of each other.
+     * 
+     * @param source
+     *            The source MimeMessage object.
+     */
+    public SMTPMessage(MimeMessage source) throws MessagingException {
+        super(source);
+    }
+
+    /**
+     * Change the allow8BitMime attribute for the message.
+     * 
+     * @param a
+     *            The new setting.
+     */
+    public void setAllow8bitMIME(boolean a) {
+        allow8bitMIME = a;
+    }
+
+    /**
+     * Retrieve the current 8bitMIME attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public boolean getAllow8bitMIME() {
+        return allow8bitMIME;
+    }
+
+    /**
+     * Change the envelopeFrom attribute for the message.
+     * 
+     * @param from
+     *            The new setting.
+     */
+    public void setEnvelopeFrom(String from) {
+        envelopeFrom = from;
+    }
+
+    /**
+     * Retrieve the current evelopeFrom attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public String getEnvelopeFrom() {
+        return envelopeFrom;
+    }
+
+    /**
+     * Change the mailExtension attribute for the message.
+     * 
+     * @param e
+     *            The new setting.
+     */
+    public void setMailExtension(String e) {
+        mailExtension = e;
+    }
+
+    /**
+     * Retrieve the current mailExtension attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public String getMailExtension() {
+        return mailExtension;
+    }
+
+    /**
+     * Change the notifyOptions attribute for the message.
+     * 
+     * @param options
+     *            The new setting.
+     */
+    public void setNotifyOptions(int options) {
+        notifyOptions = options;
+    }
+
+    /**
+     * Retrieve the current notifyOptions attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public int getNotifyOptions() {
+        return notifyOptions;
+    }
+
+    /**
+     * Change the returnOptions attribute for the message.
+     * 
+     * @param option
+     *            The new setting.
+     */
+    public void setReturnOption(int option) {
+        returnOption = option;
+    }
+
+    /**
+     * Retrieve the current returnOption attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public int getReturnOption() {
+        return returnOption;
+    }
+
+    /**
+     * Change the sendPartial attribute for the message.
+     * 
+     * @param a
+     *            The new setting.
+     */
+    public void setSendPartial(boolean a) {
+        sendPartial = a;
+    }
+
+    /**
+     * Retrieve the current sendPartial attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public boolean getSendPartial() {
+        return sendPartial;
+    }
+
+    /**
+     * Change the submitter attribute for the message.
+     * 
+     * @param s
+     *            The new setting.
+     */
+    public void setSubmitter(String s) {
+        submitter = s;
+    }
+
+    /**
+     * Retrieve the current submitter attribute.
+     * 
+     * @return The current attribute value.
+     */
+    public String getSubmitter() {
+        return submitter;
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
new file mode 100644
index 0000000..ec9999f
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
@@ -0,0 +1,208 @@
+/*
+ * 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.transport.smtp;
+ 
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Util class to represent a reply from a SMTP server
+ * 
+ * @version $Rev$ $Date$
+ */
+class SMTPReply {
+    // SMTP reply codes
+    public static final int SERVICE_READY = 220;
+
+    public static final int SERVICE_CLOSING = 221;
+
+    public static final int AUTHENTICATION_COMPLETE = 235;
+
+    public static final int COMMAND_ACCEPTED = 250;
+
+    public static final int ADDRESS_NOT_LOCAL = 251;
+
+    public static final int AUTHENTICATION_CHALLENGE = 334;
+
+    public static final int START_MAIL_INPUT = 354;
+
+    public static final int SERVICE_NOT_AVAILABLE = 421;
+
+    public static final int MAILBOX_BUSY = 450;
+
+    public static final int PROCESSING_ERROR = 451;
+
+    public static final int INSUFFICIENT_STORAGE = 452;
+
+    public static final int COMMAND_SYNTAX_ERROR = 500;
+
+    public static final int PARAMETER_SYNTAX_ERROR = 501;
+
+    public static final int COMMAND_NOT_IMPLEMENTED = 502;
+
+    public static final int INVALID_COMMAND_SEQUENCE = 503;
+
+    public static final int COMMAND_PARAMETER_NOT_IMPLEMENTED = 504;
+
+    public static final int MAILBOX_NOT_FOUND = 550;
+
+    public static final int USER_NOT_LOCAL = 551;
+
+    public static final int MAILBOX_FULL = 552;
+
+    public static final int INVALID_MAILBOX = 553;
+
+    public static final int TRANSACTION_FAILED = 553;
+    
+    // The original reply string
+    private final String reply;
+
+    // returned message code
+    private final int code;
+
+    // the returned message text
+    private final String message;
+    
+    // additional returned lines from a continued response 
+    private List lines; 
+
+    // indicates that this is a continuation response
+    private boolean continued;
+
+    SMTPReply(String s) throws MalformedSMTPReplyException {
+        // save the reply
+        reply = s;
+
+        // In a normal response, the first 3 must be the return code. However,
+        // the response back from a QUIT command is frequently a null string.
+        // Therefore, if the result is
+        // too short, just default the code to -1 and use the entire text for
+        // the message.
+        if (s == null || s.length() < 3) {
+            code = -1;
+            message = s;
+            return;
+        }
+
+        try {
+            continued = false;
+            code = Integer.parseInt(s.substring(0, 3));
+
+            // message should be separated by a space OR a continuation
+            // character if this is a
+            // multi-line response.
+            if (s.length() > 4) {
+                //
+                if (s.charAt(3) == '-') {
+                    continued = true;
+                }
+                message = s.substring(4);
+            } else {
+                message = "";
+            }
+        } catch (NumberFormatException e) {
+            throw new MalformedSMTPReplyException("error in parsing code", e);
+        }
+    }
+    
+    /**
+     * Add a line to a continued response.  This will 
+     * update the continued status if the end of the 
+     * response is reached. 
+     * 
+     * @param line   The line to add.
+     */
+    public void addLine(String line) {
+        if (lines == null) {
+            lines = new ArrayList(); 
+            lines.add(message); 
+        }
+        // mark if we're still continued 
+        continued = line.charAt(3) == '-';
+        // add the line to the list 
+        lines.add(line.substring(4)); 
+    }
+    
+    /**
+     * Get the list of all of the lines associated with 
+     * this reply. 
+     * 
+     * @return A List containing all lines associated with this
+     *         reply.
+     */
+    public List getLines() {
+        if (lines == null) {
+            lines = new ArrayList(); 
+            lines.add(message); 
+        }
+        return lines;
+    }
+    
+
+    /**
+     * Return the code value associated with the reply.
+     * 
+     * @return The integer code associated with the reply.
+     */
+    public int getCode() {
+        return this.code;
+    }
+
+    /**
+     * Get the message text associated with the reply.
+     * 
+     * @return The string value of the message from the reply.
+     */
+    public String getMessage() {
+        return this.message;
+    }
+
+    /**
+     * Retrieve the raw reply string for the reponse.
+     * 
+     * @return The original reply string from the server.
+     */
+    public String getReply() {
+        return reply;
+    }
+
+    /**
+     * Indicates if reply is an error condition
+     */
+    boolean isError() {
+        // error codes are all above 400
+        return code >= 400;
+    }
+
+    /**
+     * Indicates whether this response is flagged as part of a multiple line
+     * response.
+     * 
+     * @return true if the response has multiple lines, false if this is the
+     *         last line of the response.
+     */
+    public boolean isContinued() {
+        return continued;
+    }
+
+    public String toString() {
+        return "CODE = " + getCode() + " : MSG = " + getMessage();
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.java
new file mode 100644
index 0000000..0794065
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.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.transport.smtp;
+
+import javax.mail.Session;
+import javax.mail.URLName;
+
+public class SMTPSTransport extends SMTPTransport {
+    /**
+     * @param session
+     * @param name
+     */
+    public SMTPSTransport(Session session, URLName name) {
+        super(session, name, "smtps", 465, true);
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.java
new file mode 100644
index 0000000..20ff08a
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.java
@@ -0,0 +1,74 @@
+/*
+ * 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.transport.smtp;
+
+import javax.mail.Address;
+import javax.mail.SendFailedException;
+
+public class SMTPSendFailedException extends SendFailedException {
+    // the failing command
+    protected String cmd;
+
+    // the error code for the failure
+    protected int rc;
+
+    /**
+     * Constructor for an SMTPSendFaileException.
+     * 
+     * @param cmd
+     *            The failing command string.
+     * @param rc
+     *            The error code for the failing command.
+     * @param err
+     *            An error message for the exception.
+     * @param ex
+     *            Any associated nested exception.
+     * @param vs
+     *            An array of valid, sent addresses.
+     * @param vus
+     *            An array of addresses that were valid, but were unsent.
+     * @param inv
+     *            An array of addresses deemed invalid.
+     */
+    SMTPSendFailedException(java.lang.String cmd, int rc, java.lang.String err, java.lang.Exception ex, Address[] vs,
+            Address[] vus, Address[] inv) {
+        super(err, ex, vs, vus, inv);
+        this.cmd = cmd;
+        this.rc = rc;
+    }
+
+    /**
+     * Get the failing command string for the exception.
+     * 
+     * @return The string value of the failing command.
+     */
+    public String getCommand() {
+        return cmd;
+    }
+
+    /**
+     * The failing command return code.
+     * 
+     * @return The failure return code.
+     */
+    public int getReturnCode() {
+        return rc;
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
new file mode 100644
index 0000000..1d14d86
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
@@ -0,0 +1,656 @@
+/*
+ * 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.transport.smtp;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.Socket; 
+import java.util.ArrayList;
+
+import javax.mail.Address;
+import javax.mail.AuthenticationFailedException;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.URLName;
+import javax.mail.event.TransportEvent;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;     
+import javax.mail.internet.MimePart;     
+
+import org.apache.geronimo.javamail.util.ProtocolProperties; 
+import org.apache.geronimo.javamail.transport.smtp.SMTPConnection.SendStatus; 
+
+/**
+ * Simple implementation of SMTP transport. Just does plain RFC821-ish delivery.
+ * <p/> Supported properties : <p/>
+ * <ul>
+ * <li> mail.host : to set the server to deliver to. Default = localhost</li>
+ * <li> mail.smtp.port : to set the port. Default = 25</li>
+ * <li> mail.smtp.locahost : name to use for HELO/EHLO - default getHostName()</li>
+ * </ul>
+ * <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/> TODO : lots.
+ * ESMTP, user/pass, indicate failure, etc...
+ *
+ * @version $Rev$ $Date$
+ */
+public class SMTPTransport extends Transport {
+    /**
+     * property keys for protocol properties. The actual property name will be
+     * appended with "mail." + protocol + ".", where the protocol is either
+     * "smtp" or "smtps".
+     */
+    protected static final String MAIL_SMTP_DSN_NOTIFY = "dsn.notify";
+    protected static final String MAIL_SMTP_SENDPARTIAL = "sendpartial";
+    protected static final String MAIL_SMTP_EXTENSION = "mailextension";
+    protected static final String DEFAULT_MAIL_HOST = "localhost";
+
+    protected static final int DEFAULT_MAIL_SMTP_PORT = 25;
+    protected static final int DEFAULT_MAIL_SMTPS_PORT = 465;
+
+
+    // do we use SSL for our initial connection?
+    protected boolean sslConnection = false;
+    
+    // our accessor for protocol properties and the holder of 
+    // protocol-specific information 
+    protected ProtocolProperties props; 
+    // our active connection object 
+    protected SMTPConnection connection;
+
+    // the last response line received from the server.
+    protected SMTPReply lastServerResponse = null;
+
+    /**
+     * Normal constructor for an SMTPTransport() object. This constructor is
+     * used to build a transport instance for the "smtp" protocol.
+     *
+     * @param session
+     *            The attached session.
+     * @param name
+     *            An optional URLName object containing target information.
+     */
+    public SMTPTransport(Session session, URLName name) {
+        this(session, name, "smtp", DEFAULT_MAIL_SMTP_PORT, false);
+    }
+    
+
+    /**
+     * Common constructor used by the SMTPTransport and SMTPSTransport classes
+     * to do common initialization of defaults.
+     *
+     * @param session
+     *            The host session instance.
+     * @param name
+     *            The URLName of the target.
+     * @param protocol
+     *            The protocol type (either "smtp" or "smtps". This helps us in
+     *            retrieving protocol-specific session properties.
+     * @param defaultPort
+     *            The default port used by this protocol. For "smtp", this will
+     *            be 25. The default for "smtps" is 465.
+     * @param sslConnection
+     *            Indicates whether an SSL connection should be used to initial
+     *            contact the server. This is different from the STARTTLS
+     *            support, which switches the connection to SSL after the
+     *            initial startup.
+     */
+    protected SMTPTransport(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
+        super(session, name);
+        
+        // create the protocol property holder.  This gives an abstraction over the different 
+        // flavors of the protocol. 
+        props = new ProtocolProperties(session, protocol, sslConnection, defaultPort); 
+        // the connection manages connection for the transport 
+        connection = new SMTPConnection(props); 
+    }
+
+    
+    /**
+     * Connect to a server using an already created socket. This connection is
+     * just like any other connection, except we will not create a new socket.
+     *
+     * @param socket
+     *            The socket connection to use.
+     */
+    public void connect(Socket socket) throws MessagingException {
+        connection.connect(socket); 
+        super.connect();
+    }
+    
+
+    /**
+     * Do the protocol connection for an SMTP transport. This handles server
+     * authentication, if possible. Returns false if unable to connect to the
+     * server.
+     *
+     * @param host
+     *            The target host name.
+     * @param port
+     *            The server port number.
+     * @param user
+     *            The authentication user (if any).
+     * @param password
+     *            The server password. Might not be sent directly if more
+     *            sophisticated authentication is used.
+     *
+     * @return true if we were able to connect to the server properly, false for
+     *         any failures.
+     * @exception MessagingException
+     */
+    protected boolean protocolConnect(String host, int port, String username, String password)
+            throws MessagingException {
+        // the connection pool handles all of the details here. 
+        return connection.protocolConnect(host, port, username, password);
+    }
+
+    /**
+     * Send a message to multiple addressees.
+     *
+     * @param message
+     *            The message we're sending.
+     * @param addresses
+     *            An array of addresses to send to.
+     *
+     * @exception MessagingException
+     */
+    public void sendMessage(Message message, Address[] addresses) throws MessagingException {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected");
+        }
+        // don't bother me w/ null messages or no addreses
+        if (message == null) {
+            throw new MessagingException("Null message");
+        }
+
+        // SMTP only handles instances of MimeMessage, not the more general
+        // message case.
+        if (!(message instanceof MimeMessage)) {
+            throw new MessagingException("SMTP can only send MimeMessages");
+        }
+
+        // we must have a message list.
+        if (addresses == null || addresses.length == 0) {
+            throw new MessagingException("Null or empty address array");
+        }
+        
+        boolean reportSuccess = getReportSuccess(); 
+        
+        // now see how we're configured for this send operation.
+        boolean partialSends = false;
+
+        // this can be attached directly to the message.
+        if (message instanceof SMTPMessage) {
+            partialSends = ((SMTPMessage) message).getSendPartial();
+        }
+
+        // if still false on the message object, check for a property
+        // version also
+        if (!partialSends) {
+            partialSends = props.getBooleanProperty(MAIL_SMTP_SENDPARTIAL, false);
+        }
+
+        boolean haveGroup = false;
+
+        // enforce the requirement that all of the targets are InternetAddress
+        // instances.
+        for (int i = 0; i < addresses.length; i++) {
+            if (addresses[i] instanceof InternetAddress) {
+                // and while we're here, see if we have a groups in the address
+                // list. If we do, then
+                // we're going to need to expand these before sending.
+                if (((InternetAddress) addresses[i]).isGroup()) {
+                    haveGroup = true;
+                }
+            } else {
+                throw new MessagingException("Illegal InternetAddress " + addresses[i]);
+            }
+        }
+
+        // did we find a group? Time to expand this into our full target list.
+        if (haveGroup) {
+            addresses = expandGroups(addresses);
+        }
+
+        SendStatus[] stats = new SendStatus[addresses.length];
+
+        // create our lists for notification and exception reporting.
+        Address[] sent = null;
+        Address[] unsent = null;
+        Address[] invalid = null;
+
+        try {
+            // send sender first. If this failed, send a failure notice of the
+            // event, using the full list of
+            // addresses as the unsent, and nothing for the rest.
+            if (!connection.sendMailFrom(message)) {
+                unsent = addresses;
+                sent = new Address[0];
+                invalid = new Address[0];
+                // notify of the error.
+                notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
+
+                // include the reponse information here.
+                SMTPReply last = connection.getLastServerResponse(); 
+                // now send an "uber-exception" to indicate the failure.
+                throw new SMTPSendFailedException("MAIL FROM", last.getCode(), last.getMessage(), null, sent, unsent,
+                        invalid);
+            }
+
+            // get the additional notification status, if available 
+            String dsn = getDeliveryStatusNotification(message);
+
+            // we need to know about any failures once we've gone through the
+            // complete list, so keep a
+            // failure flag.
+            boolean sendFailure = false;
+
+            // event notifcation requires we send lists of successes and
+            // failures broken down by category.
+            // The categories are:
+            //
+            // 1) addresses successfully processed.
+            // 2) addresses deemed valid, but had a processing failure that
+            // prevented sending.
+            // 3) addressed deemed invalid (basically all other processing
+            // failures).
+            ArrayList sentAddresses = new ArrayList();
+            ArrayList unsentAddresses = new ArrayList();
+            ArrayList invalidAddresses = new ArrayList();
+
+            // Now we add a MAIL TO record for each recipient. At this point, we
+            // just collect
+            for (int i = 0; i < addresses.length; i++) {
+                InternetAddress target = (InternetAddress) addresses[i];
+
+                // write out the record now.
+                SendStatus status = connection.sendRcptTo(target, dsn);
+                stats[i] = status;
+
+                switch (status.getStatus()) {
+                    // successfully sent
+                    case SendStatus.SUCCESS:
+                        sentAddresses.add(target);
+                        break;
+
+                    // we have an invalid address of some sort, or a general sending
+                    // error (which we'll
+                    // interpret as due to an invalid address.
+                    case SendStatus.INVALID_ADDRESS:
+                    case SendStatus.GENERAL_ERROR:
+                        sendFailure = true;
+                        invalidAddresses.add(target);
+                        break;
+
+                    // good address, but this was a send failure.
+                    case SendStatus.SEND_FAILURE:
+                        sendFailure = true;
+                        unsentAddresses.add(target);
+                        break;
+                    }
+            }
+
+            // if we had a send failure, then we need to check if we allow
+            // partial sends. If not allowed,
+            // we abort the send operation now.
+            if (sendFailure) {
+                // if we're not allowing partial successes or we've failed on
+                // all of the addresses, it's
+                // time to abort.
+                if (!partialSends || sentAddresses.isEmpty()) {
+                    // we send along the valid and invalid address lists on the
+                    // notifications and
+                    // exceptions.
+                    // however, since we're aborting the entire send, the
+                    // successes need to become
+                    // members of the failure list.
+                    unsentAddresses.addAll(sentAddresses);
+
+                    // this one is empty.
+                    sent = new Address[0];
+                    unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
+                    invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
+
+                    // go reset our connection so we can process additional
+                    // sends.
+                    connection.resetConnection();
+
+                    // get a list of chained exceptions for all of the failures.
+                    MessagingException failures = generateExceptionChain(stats, false);
+
+                    // now send an "uber-exception" to indicate the failure.
+                    throw new SMTPSendFailedException("MAIL TO", 0, "Invalid Address", failures, sent, unsent, invalid);
+                }
+            }
+
+            try {
+                // try to send the data
+                connection.sendData((MimeMessage)message);
+            } catch (MessagingException e) {
+                // If there's an error at this point, this is a complete
+                // delivery failure.
+                // we send along the valid and invalid address lists on the
+                // notifications and
+                // exceptions.
+                // however, since we're aborting the entire send, the successes
+                // need to become
+                // members of the failure list.
+                unsentAddresses.addAll(sentAddresses);
+
+                // this one is empty.
+                sent = new Address[0];
+                unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
+                invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
+                // notify of the error.
+                notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
+                // send a send failure exception.
+                throw new SMTPSendFailedException("DATA", 0, "Send failure", e, sent, unsent, invalid);
+            }
+
+            // create our lists for notification and exception reporting from
+            // this point on.
+            sent = (Address[]) sentAddresses.toArray(new Address[0]);
+            unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
+            invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
+
+            // if sendFailure is true, we had an error during the address phase,
+            // but we had permission to
+            // process this as a partial send operation. Now that the data has
+            // been sent ok, it's time to
+            // report the partial failure.
+            if (sendFailure) {
+                // notify our listeners of the partial delivery.
+                notifyTransportListeners(TransportEvent.MESSAGE_PARTIALLY_DELIVERED, sent, unsent, invalid, message);
+
+                // get a list of chained exceptions for all of the failures (and
+                // the successes, if reportSuccess has been
+                // turned on).
+                MessagingException failures = generateExceptionChain(stats, reportSuccess);
+
+                // now send an "uber-exception" to indicate the failure.
+                throw new SMTPSendFailedException("MAIL TO", 0, "Invalid Address", failures, sent, unsent, invalid);
+            }
+
+            // notify our listeners of successful delivery.
+            notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED, sent, unsent, invalid, message);
+
+            // we've not had any failures, but we've been asked to report
+            // success as an exception. Do
+            // this now.
+            if (reportSuccess) {
+                // generate the chain of success exceptions (we already know
+                // there are no failure ones to report).
+                MessagingException successes = generateExceptionChain(stats, reportSuccess);
+                if (successes != null) {
+                    throw successes;
+                }
+            }
+        } catch (SMTPSendFailedException e) {
+            // if this is a send failure, we've already handled
+            // notifications....just rethrow it.
+            throw e;
+        } catch (MessagingException e) {
+            // notify of the error.
+            notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
+            throw e;
+        }
+    }
+    
+    
+    /**
+     * Determine what delivery status notification should
+     * be added to the RCPT TO: command. 
+     * 
+     * @param message The message we're sending.
+     * 
+     * @return The string NOTIFY= value to add to the command. 
+     */
+    protected String getDeliveryStatusNotification(Message message) {
+        String dsn = null;
+
+        // there's an optional notification argument that can be added to
+        // MAIL TO. See if we've been
+        // provided with one.
+
+        // an SMTPMessage object is the first source
+        if (message instanceof SMTPMessage) {
+            // get the notification options
+            int options = ((SMTPMessage) message).getNotifyOptions();
+
+            switch (options) {
+            // a zero value indicates nothing is set.
+            case 0:
+                break;
+
+            case SMTPMessage.NOTIFY_NEVER:
+                dsn = "NEVER";
+                break;
+
+            case SMTPMessage.NOTIFY_SUCCESS:
+                dsn = "SUCCESS";
+                break;
+
+            case SMTPMessage.NOTIFY_FAILURE:
+                dsn = "FAILURE";
+                break;
+
+            case SMTPMessage.NOTIFY_DELAY:
+                dsn = "DELAY";
+                break;
+
+            // now for combinations...there are few enough combinations here
+            // that we can just handle this in the switch statement rather
+            // than have to
+            // concatentate everything together.
+            case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE):
+                dsn = "SUCCESS,FAILURE";
+                break;
+
+            case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_DELAY):
+                dsn = "SUCCESS,DELAY";
+                break;
+
+            case (SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
+                dsn = "FAILURE,DELAY";
+                break;
+
+            case (SMTPMessage.NOTIFY_SUCCESS + SMTPMessage.NOTIFY_FAILURE + SMTPMessage.NOTIFY_DELAY):
+                dsn = "SUCCESS,FAILURE,DELAY";
+                break;
+            }
+        }
+
+        // if still null, grab a property value (yada, yada, yada...)
+        if (dsn == null) {
+            dsn = props.getProperty(MAIL_SMTP_DSN_NOTIFY);
+        }
+        return dsn; 
+    }
+    
+
+
+    /**
+     * 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 {
+        // This is done to ensure proper event notification.
+        super.close();
+        // NB:  We reuse the connection if asked to reconnect 
+        connection.close();
+    }
+    
+    
+    /**
+     * Turn a series of send status items into a chain of exceptions indicating
+     * the state of each send operation.
+     *
+     * @param stats
+     *            The list of SendStatus items.
+     * @param reportSuccess
+     *            Indicates whether we should include the report success items.
+     *
+     * @return The head of a chained list of MessagingExceptions.
+     */
+    protected MessagingException generateExceptionChain(SendStatus[] stats, boolean reportSuccess) {
+        MessagingException current = null;
+
+        for (int i = 0; i < stats.length; i++) {
+            SendStatus status = stats[i];
+
+            if (status != null) {
+                MessagingException nextException = stats[i].getException(reportSuccess);
+                // if there's an exception associated with this status, chain it
+                // up with the rest.
+                if (nextException != null) {
+                    if (current == null) {
+                        current = nextException;
+                    } else {
+                        current.setNextException(nextException);
+                        current = nextException;
+                    }
+                }
+            }
+        }
+        return current;
+    }
+
+    /**
+     * Expand the address list by converting any group addresses into single
+     * address targets.
+     *
+     * @param addresses
+     *            The input array of addresses.
+     *
+     * @return The expanded array of addresses.
+     * @exception MessagingException
+     */
+    protected Address[] expandGroups(Address[] addresses) throws MessagingException {
+        ArrayList expandedAddresses = new ArrayList();
+
+        // run the list looking for group addresses, and add the full group list
+        // to our targets.
+        for (int i = 0; i < addresses.length; i++) {
+            InternetAddress address = (InternetAddress) addresses[i];
+            // not a group? Just copy over to the other list.
+            if (!address.isGroup()) {
+                expandedAddresses.add(address);
+            } else {
+                // get the group address and copy each member of the group into
+                // the expanded list.
+                InternetAddress[] groupAddresses = address.getGroup(true);
+                for (int j = 1; j < groupAddresses.length; j++) {
+                    expandedAddresses.add(groupAddresses[j]);
+                }
+            }
+        }
+
+        // convert back into an array.
+        return (Address[]) expandedAddresses.toArray(new Address[0]);
+    }
+    
+
+    /**
+     * Retrieve the local client host name.
+     *
+     * @return The string version of the local host name.
+     * @exception SMTPTransportException
+     */
+    public String getLocalHost() throws MessagingException {
+        return connection.getLocalHost(); 
+    }
+
+    
+    /**
+     * Explicitly set the local host information.
+     *
+     * @param localHost
+     *            The new localHost name.
+     */
+    public void setLocalHost(String localHost) {
+        connection.setLocalHost(localHost); 
+    }
+
+    
+    /**
+     * Return the current reportSuccess property.
+     *
+     * @return The current reportSuccess property.
+     */
+    public boolean getReportSuccess() {
+        return connection.getReportSuccess(); 
+    }
+
+    /**
+     * Set a new value for the reportSuccess property.
+     *
+     * @param report
+     *            The new setting.
+     */
+    public void setReportSuccess(boolean report) {
+        connection.setReportSuccess(report); 
+    }
+
+    /**
+     * Return the current startTLS property.
+     *
+     * @return The current startTLS property.
+     */
+    public boolean getStartTLS() {
+        return connection.getStartTLS(); 
+    }
+
+    /**
+     * Set a new value for the startTLS property.
+     *
+     * @param start
+     *            The new setting.
+     */
+    public void setStartTLS(boolean start) {
+        connection.setStartTLS(start); 
+    }
+
+    /**
+     * Retrieve the SASL realm used for DIGEST-MD5 authentication. This will
+     * either be explicitly set, or retrieved using the mail.smtp.sasl.realm
+     * session property.
+     *
+     * @return The current realm information (which can be null).
+     */
+    public String getSASLRealm() {
+        return connection.getSASLRealm(); 
+    }
+
+    /**
+     * Explicitly set the SASL realm used for DIGEST-MD5 authenticaiton.
+     *
+     * @param name
+     *            The new realm name.
+     */
+    public void setSASLRealm(String name) {
+        connection.setSASLRealm(name); 
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransportException.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransportException.java
new file mode 100644
index 0000000..636bbde
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransportException.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.transport.smtp;
+
+/**
+ * General purpose Exception
+ * 
+ * @version $Id$
+ */
+class SMTPTransportException extends Exception {
+
+    SMTPTransportException() {
+        super();
+    }
+
+    SMTPTransportException(String s) {
+        super(s);
+    }
+
+    SMTPTransportException(String s, Exception t) {
+        super(s, t);
+    }
+
+    SMTPTransportException(Exception t) {
+        super("SMTP Transport error", t);
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/CommandFailedException.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ConnectionException.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/CountingOutputStream.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/CountingOutputStream.java
new file mode 100644
index 0000000..8a772aa
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/CountingOutputStream.java
@@ -0,0 +1,60 @@
+/*
+ * 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.OutputStream;
+
+/**
+ * An implementation of an OutputStream just counts
+ * the number of bytes written to the stream. 
+ * @version $Rev$ $Date$
+ */
+public class CountingOutputStream extends OutputStream {
+    // the counting accumulator 
+    int count = 0; 
+
+    // in order for this to work, we only need override the single character
+    // form, as the others
+    // funnel through this one by default.
+    public void write(int ch) throws IOException {
+        // just increment the count 
+        count++; 
+    }
+    
+    
+    /**
+     * Get the current accumulator total for this stream. 
+     * 
+     * @return The current count. 
+     */
+    public int getCount() {
+        return count; 
+    }
+    
+    
+    /**
+     * Reset the counter to zero. 
+     */
+    public void reset() {
+        count = 0; 
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/InvalidCommandException.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEInputReader.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEInputReader.java
new file mode 100644
index 0000000..5241e78
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEInputReader.java
@@ -0,0 +1,140 @@
+/*
+ * 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.Reader; 
+
+/**
+ * An implementation of an OutputStream that performs MIME linebreak
+ * canonicalization and "byte-stuff" so that data content does not get mistaken
+ * for a message data-end marker (CRLF.CRLF)l
+ * 
+ * @version $Rev$ $Date$
+ */
+public class MIMEInputReader extends Reader {
+
+    // the wrappered output stream.
+    protected Reader source;
+
+    // a flag to indicate we've just processed a line break. This is used for
+    // byte stuffing purposes. This
+    // is initially true, because if the first character of the content is a
+    // period, we need to byte-stuff
+    // immediately.
+    protected boolean atLineBreak = true;
+    // we've hit the terminating marker on the data
+    protected boolean endOfData = false; 
+    
+
+    /**
+     * Create an input reader that reads from the source input reader  
+     * 
+     * @param out
+     *            The wrapped Reader        
+     */
+    public MIMEInputReader(Reader source) {
+        this.source = source; 
+    }
+    
+    /**
+     * Concrete implementation of the Reader read() 
+     * abstract method.  This appears to be the only 
+     * abstract method, so all of the other reads must 
+     * funnel through this method. 
+     * 
+     * @param buffer The buffer to fill.
+     * @param off    The offset to start adding characters.
+     * @param len    The number of requested characters.
+     * 
+     * @return The actual count of characters read.  Returns -1 
+     *         if we hit an EOF without reading any characters.
+     * @exception IOException
+     */
+    public int read(char buffer[], int off, int len) throws IOException {
+        // we've been asked for nothing, we'll return nothing. 
+        if (len == 0) {
+            return 0; 
+        }
+        
+        // have we hit the end of data?  Return a -1 indicator
+        if (endOfData) {
+            return -1; 
+        }
+        
+        // number of bytes read 
+        int bytesRead = 0; 
+        
+        int lastRead; 
+        
+        while (bytesRead < len && (lastRead = source.read()) >= 0) {
+            // We are checking for the end of a multiline response
+            // the format is .CRLF
+            
+            // we also have to check for byte-stuffing situation 
+            // where we remove a leading period.  
+            if (atLineBreak && lastRead == '.') {
+                // step to the next character 
+                lastRead = source.read();
+                // we have ".CR"...this is our end of stream 
+                // marker.  Consume the LF from the reader and return 
+                if (lastRead == '\r') {
+                    source.read(); 
+                    // no more reads from this point. 
+                    endOfData = true; 
+                    break; 
+                }
+                // the next character SHOULD be a ".".  We swallow the first 
+                // dot and just write the next character to the buffer 
+                atLineBreak = false; 
+            }
+            else if (lastRead == '\n') {
+                // hit an end-of-line marker?
+                // remember we just had a line break 
+                atLineBreak = true; 
+            }
+            else 
+            {
+                // something other than a line break character 
+                atLineBreak = false; 
+            }
+            // add the character to the buffer 
+            buffer[off++] = (char)lastRead; 
+            bytesRead++; 
+        }
+        
+        // we must have had an EOF condition of some sort 
+        if (bytesRead == 0) {
+            return -1; 
+        }
+        // return the actual length read in 
+        return bytesRead; 
+    }
+    
+     /**
+      * Close the stream.  This is a NOP for this stream.  
+      * 
+      * @exception IOException
+      */
+     public void close() throws IOException {
+         // does nothing 
+     }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEOutputStream.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEOutputStream.java
new file mode 100644
index 0000000..cd073bd
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEOutputStream.java
@@ -0,0 +1,132 @@
+/*
+ * 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.OutputStream;
+
+/**
+ * An implementation of an OutputStream that performs MIME linebreak
+ * canonicalization and "byte-stuff" so that data content does not get mistaken
+ * for a message data-end marker (CRLF.CRLF)l
+ * 
+ * @version $Rev$ $Date$
+ */
+public class MIMEOutputStream extends OutputStream {
+
+    // the wrappered output stream.
+    protected OutputStream out;
+
+    // last character we handled...used to recongnize line breaks.
+    protected int lastWrite = -1;
+
+    // a flag to indicate we've just processed a line break. This is used for
+    // byte stuffing purposes. This
+    // is initially true, because if the first character of the content is a
+    // period, we need to byte-stuff
+    // immediately.
+    protected boolean atLineBreak = true;
+
+    /**
+     * Create an output stream that writes to the target output stream.
+     * 
+     * @param out
+     *            The wrapped output stream.
+     */
+    public MIMEOutputStream(OutputStream out) {
+        this.out = out;
+    }
+
+    // in order for this to work, we only need override the single character
+    // form, as the others
+    // funnel through this one by default.
+    public void write(int ch) throws IOException {
+        // if this is a CR character, always write out a full sequence, and
+        // remember that we just did this.
+        if (ch == '\r') {
+            out.write((byte) '\r');
+            out.write((byte) '\n');
+            // we've just taken a break;
+            atLineBreak = true;
+        }
+        // if this is a new line, then we need to determine if this is a loner
+        // or part of a CRLF sequence.
+        else if (ch == '\n') {
+            // is this a lone ranger?
+            if (lastWrite != '\r') {
+                // write the full CRLF sequence.
+                out.write((byte) '\r');
+                out.write((byte) '\n');
+            }
+            // regardless of whether we wrote something or not, we're still at a
+            // line break.
+            atLineBreak = true;
+        }
+        // potential byte-stuffing situation?
+        else if (ch == '.') {
+            // ok, this is a potential stuff situation. Did we just have a line
+            // break? Double up the character.
+            if (atLineBreak) {
+                out.write('.');
+            }
+            out.write('.');
+            atLineBreak = false;
+        } else {
+            // just write this out and flip the linebreak flag.
+            out.write(ch);
+            atLineBreak = false;
+        }
+        // remember this last one for CRLF tracking purposes.
+        lastWrite = ch;
+    }
+    
+    
+    /**
+     * Force the stream to be terminated at a line break. 
+     * This is generally in preparation for the transport to 
+     * write out an end-of-data marker, which generally 
+     * needs to be preceded by a CRLF sequence. 
+     * 
+     * @exception IOException
+     */
+    public void forceTerminatingLineBreak() throws IOException {
+        if (!atLineBreak) {
+            out.write((byte) '\r');
+            out.write((byte) '\n');
+            // we've just taken a break;
+            atLineBreak = true;
+        }
+    }
+    
+    
+    /**
+     * Write out the SMTP terminator to the output stream. 
+     * This ensures that we don't write out an extra 
+     * CRLF if the data terminates with that value.  
+     * 
+     * @exception IOException
+     */
+    public void writeSMTPTerminator() throws IOException {
+        forceTerminatingLineBreak(); 
+        out.write('.'); 
+        out.write('\r'); 
+        out.write('\n'); 
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java
new file mode 100644
index 0000000..af079ae
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MailConnection.java
@@ -0,0 +1,960 @@
+/**
+ * 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.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+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;
+
+/**
+ * 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_PORT = "port";
+    protected static final String MAIL_LOCALHOST = "localhost";
+    protected static final String MAIL_STARTTLS_ENABLE = "starttls.enable";
+    protected static final String MAIL_STARTTLS_REQUIRED = "starttls.required";
+    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_AUTHORIZATIONID = "sasl.authorizationid"; 
+    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 = "socketFactory"; //GERONIMO-5429
+    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 = "ssl.socketFactory"; //GERONIMO-5429
+    protected static final String MAIL_SSL_FACTORY_CLASS = "ssl.socketFactory.class";
+    protected static final String MAIL_SSL_FACTORY_PORT = "ssl.socketFactory.port";
+    protected static final String MAIL_SSL_PROTOCOLS = "ssl.protocols";
+    protected static final String MAIL_SSL_CIPHERSUITES = "ssl.ciphersuites";
+    protected static final String MAIL_SSL_TRUST = "ssl.trust";
+
+    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 local host name
+    protected String localHost;
+    
+    // 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();
+        
+        String mailSSLEnable = props.getProperty(MAIL_SSL_ENABLE);
+        if(mailSSLEnable != null) {
+            this.sslConnection = Boolean.valueOf(mailSSLEnable);
+        }
+    }
+    
+    
+    /**
+     * Connect to the server and do the initial handshaking.
+     * 
+     * @param host     The target host name.
+     * @param port     The target port
+     * @param username The connection username (can be null)
+     * @param password The authentication password (can be null).
+     * 
+     * @return true if we were able to obtain a connection and 
+     *         authenticate.
+     * @exception MessagingException
+     */
+    public 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 successfully received a host
+    	if ( host == null ) {
+    		host = DEFAULT_MAIL_HOST;
+    	}
+        
+        this.serverHost = host;
+        this.serverPort = 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);
+        return true; 
+    }
+    
+    
+    /**
+     * Establish a connection using an existing socket. 
+     * 
+     * @param s      The socket to use.
+     */
+    public void connect(Socket s) {
+        // just save the socket connection 
+        this.socket = s; 
+    }
+    
+    
+    /**
+     * 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 {
+        debugOut("Attempting plain socket connection to server " + serverHost + ":" + serverPort);
+
+        // make sure this is null 
+        socket = null;
+        
+        createSocket(false);
+        
+        // if we have a timeout value, set that before returning 
+        if (timeout >= 0) {
+            socket.setSoTimeout(timeout);
+        }
+    }
+    
+    private boolean createSocketFromFactory(boolean ssl, boolean layer) throws IOException {
+        
+        String socketFactoryClass = props.getProperty(ssl?MAIL_SSL_FACTORY_CLASS:MAIL_FACTORY_CLASS);
+        
+        if(socketFactoryClass == null) {
+            return false;
+        }
+        
+        // we'll try this with potentially two different factories if we're allowed to fall back.
+        boolean fallback = props.getBooleanProperty(MAIL_FACTORY_FALLBACK, false);
+        int socketFactoryPort = props.getIntProperty(ssl?MAIL_SSL_FACTORY_PORT:MAIL_FACTORY_PORT, -1);
+        Integer portArg = new Integer(socketFactoryPort == -1 ? serverPort : socketFactoryPort);
+        
+        debugOut("Creating "+(ssl?"":"non-")+"SSL socket using factory " + socketFactoryClass+ " listening on port "+portArg);
+
+        while (true) {
+            try {
+                
+                // use the current context loader to resolve this.
+                ClassLoader loader = Thread.currentThread().getContextClassLoader();
+                Class factoryClass = loader.loadClass(socketFactoryClass);
+
+                // done indirectly, we need to invoke the method using reflection.
+                // This retrieves a factory instance.
+                //Method getDefault = factoryClass.getMethod("getDefault", new Class[0]); //TODO check instantiation of socket factory
+                Object defFactory = factoryClass.newInstance();// 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 && !layer) {
+                    // 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 {
+                    if(layer) {
+                     // retrieve the createSocket(String, int) method.
+                        Class[] createSocketSig = new Class[] { Socket.class, String.class, Integer.TYPE, Boolean.TYPE };
+                        Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+                        Object[] createSocketArgs = new Object[] { socket, serverHost, new Integer(serverPort), Boolean.TRUE };
+                        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) {
+                    debugOut("First attempt at creating "+(ssl?"":"non-")+"SSL socket failed, falling back to default factory");
+                    socketFactoryClass = ssl?"javax.net.ssl.SSLSocketFactory":"javax.net.SocketFactory";
+                    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();
+                    }
+
+                    debugOut("Failure creating "+(ssl?"":"non-")+"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;
+                }
+            }
+        }
+        
+        return true;
+    }
+    
+    private void createSocketFromFactory(SocketFactory sf, boolean layer) throws IOException {
+        
+        if(sf instanceof SSLSocketFactory && layer) {
+            socket = ((SSLSocketFactory) sf).createSocket(socket, serverHost, serverPort, true);
+            return;
+        }
+        
+        if (localAddress != null) {
+            socket = sf.createSocket(serverHost, serverPort, localAddress, localPort);
+        } else
+        {
+            socket = sf.createSocket(serverHost, serverPort);
+        }
+    }
+    
+    private boolean createSocketFromConfiguredFactoryInstance(boolean ssl, boolean layer) throws IOException {
+        
+        
+        
+        if (ssl) {
+            Object sfProp = props.getPropertyAsObject(MAIL_SSL_FACTORY);
+            if (sfProp != null && sfProp instanceof SSLSocketFactory) {
+                createSocketFromFactory((SSLSocketFactory) sfProp, layer);
+                debugOut("Creating "+(ssl?"":"non-")+"SSL "+(layer?"layered":"non-layered")+" socket using a instance of factory " + sfProp.getClass()+ " listening");
+                return true;
+            }
+        } else {
+            Object sfProp = props.getPropertyAsObject(MAIL_FACTORY);
+            if (sfProp != null && sfProp instanceof SocketFactory) {
+                createSocketFromFactory((SocketFactory) sfProp, layer);
+                debugOut("Creating "+(ssl?"":"non-")+"SSL "+(layer?"layered":"non-layered")+" socket using a instance of factory " + sfProp.getClass()+ " listening");
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    private void createSSLSocketFromSSLContext(boolean layer) throws IOException{
+        
+        debugOut("Creating "+(layer?"layered ":"non-layered ")+"SSL socket using SSL Context");
+
+        
+        try {
+            SSLContext sslcontext = SSLContext.getInstance("TLS");
+            
+            String sslTrust = props.getProperty(MAIL_SSL_TRUST);
+            
+            TrustManager trustManager = null;
+            
+            if(sslTrust != null) {
+                if(sslTrust.equals("*")) {
+                    trustManager = new SSLTrustManager(null, true); //trust all
+                } else
+                {
+                   String[] trustedHosts = sslTrust.split("\\s+");
+                   trustManager = new SSLTrustManager(trustedHosts, false); //trust some
+                   
+                   if(serverHost == null || serverHost.isEmpty() || !Arrays.asList(trustedHosts).contains(serverHost)) {
+                       throw new IOException("Server is not trusted: " + serverHost);
+                   }
+                   
+                }
+            } else {
+                trustManager = new SSLTrustManager(null, false); //default
+                
+            }
+             
+            sslcontext.init(null, new TrustManager[]{trustManager}, null);
+            
+            createSocketFromFactory(sslcontext.getSocketFactory(), layer);
+        } catch (KeyManagementException e) {
+            //cannot happen
+            throw new IOException(e);
+        } catch (NoSuchAlgorithmException e) {
+            //cannot happen
+            throw new IOException(e);
+        }
+    }
+    
+    private void createSocket(boolean ssl) throws IOException {
+        
+        if(createSocketFromConfiguredFactoryInstance(ssl, false)) {
+            return;
+        }
+
+        if(createSocketFromFactory(ssl, false)) {
+            return;
+        }
+        
+        if(!ssl) {
+            createSocketFromFactory(SocketFactory.getDefault(), false);
+            return;
+        }
+        
+          
+        createSSLSocketFromSSLContext(false);
+    }
+
+
+    /**
+     * Creates a connected SSL socket for an initial SSL connection.
+     *
+     * @exception MessagingException
+     */
+    protected void getConnectedSSLSocket() throws IOException {
+        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.
+
+        // make sure this is null 
+        socket = null;
+        
+        createSocket(true);
+
+        // and set the timeout value 
+        if (timeout >= 0) {
+            socket.setSoTimeout(timeout);
+        }
+        
+        // if there is a list of protocols specified, we need to break this down into 
+        // the individual names 
+        String protocols = props.getProperty(MAIL_SSL_PROTOCOLS); 
+        if (protocols != null) {
+            ArrayList list = new ArrayList(); 
+            StringTokenizer t = new StringTokenizer(protocols); 
+            
+            while (t.hasMoreTokens()) {
+                list.add(t.nextToken()); 
+            }
+            
+            ((SSLSocket)socket).setEnabledProtocols((String[])list.toArray(new String[list.size()])); 
+        }
+        
+        // and do the same for any cipher suites 
+        String suites = props.getProperty(MAIL_SSL_CIPHERSUITES); 
+        if (suites != null) {
+            ArrayList list = new ArrayList(); 
+            StringTokenizer t = new StringTokenizer(suites); 
+            
+            while (t.hasMoreTokens()) {
+                list.add(t.nextToken()); 
+            }
+            
+            ((SSLSocket)socket).setEnabledCipherSuites((String[])list.toArray(new String[list.size()])); 
+        }
+    }
+
+
+    /**
+     * Switch the connection to using TLS level security,
+     * switching to an SSL socket.
+     */
+    protected void getConnectedTLSSocket() throws MessagingException {
+     	// it worked, now switch the socket into TLS mode
+     	try {
+
+            // we use the same target and port as the current connection.
+            serverHost = socket.getInetAddress().getHostName();
+            serverPort = socket.getPort();
+
+            // the socket factory can be specified via a session property.  By default, we use
+            // the native SSL factory.
+            if(createSocketFromConfiguredFactoryInstance(true, true)) {
+                debugOut("TLS socket factory configured as instance"); 
+            } else if(createSocketFromFactory(true, true)) {
+                debugOut("TLS socket factory configured as class"); 
+            } else {
+                debugOut("TLS socket factory from SSLContext"); 
+                createSSLSocketFromSSLContext(true);
+            }
+
+            // 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 (socket instanceof SSLSocket) {
+                String[] suites = ((SSLSocket)socket).getSupportedCipherSuites();
+                ((SSLSocket)socket).setEnabledCipherSuites(suites);
+                ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1"} );
+                ((SSLSocket)socket).setUseClientMode(true);
+                debugOut("Initiating STARTTLS handshake");
+                ((SSLSocket)socket).startHandshake();
+            } else {
+                throw new IOException("Socket is not an instance of SSLSocket, maybe wrong configured ssl factory?");
+            }
+
+            getConnectionStreams(); 
+            debugOut("TLS connection established"); 
+     	}
+        catch (Exception e) {
+            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(authid, 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) {
+        if (debug) {
+            debugStream.println(protocol + " 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) {
+        if (debug) {
+            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); 
+    }
+    
+    
+    /**
+     * Retrieve the connection host. 
+     * 
+     * @return The host name. 
+     */
+    public String getHost() {
+        return serverHost; 
+    }
+    
+
+    /**
+     * Retrieve the local client host name.
+     *
+     * @return The string version of the local host name.
+     * @exception SMTPTransportException
+     */
+    public String getLocalHost() throws MessagingException {
+        if (localHost == null) {
+
+            if (localHost == null) {
+                localHost = props.getProperty(MAIL_LOCALHOST);
+            }
+
+            if (localHost == null) {
+                localHost = props.getSessionProperty(MAIL_LOCALHOST);
+            }
+
+            if (localHost == null) {
+        	try {
+            	    localHost = InetAddress.getLocalHost().getHostName();
+                } catch (UnknownHostException e) {
+	            // fine, we're misconfigured - ignore
+    	        }
+	    }
+
+            if (localHost == null) {
+                throw new MessagingException("Can't get local hostname. "
+                        + " Please correctly configure JDK/DNS or set mail.smtp.localhost");
+            }
+        }
+
+        return localHost;
+    }
+
+    
+    /**
+     * Explicitly set the local host information.
+     *
+     * @param localHost
+     *            The new localHost name.
+     */
+    public void setLocalHost(String localHost) {
+        this.localHost = localHost;
+    }
+    
+    private class SSLTrustManager implements X509TrustManager {
+        
+        private final X509TrustManager defaultTrustManager;
+        
+        private final boolean trustAll;
+        private final String[] trustedHosts;
+
+        SSLTrustManager(String[] trustedHosts, boolean trustAll) throws IOException{
+            super();
+            this.trustAll = trustAll;
+            this.trustedHosts = trustedHosts;
+            
+            try {
+                TrustManagerFactory defaultTrustManagerFactory = TrustManagerFactory.getInstance("X509");
+                defaultTrustManagerFactory.init((KeyStore)null);
+                defaultTrustManager = (X509TrustManager) defaultTrustManagerFactory.getTrustManagers()[0];
+            } catch (NoSuchAlgorithmException e) {
+                //cannot happen
+                throw new IOException(e);
+            } catch (KeyStoreException e) {
+                //cannot happen
+                throw new IOException(e);
+            }
+            
+        }
+
+        /* (non-Javadoc)
+         * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], java.lang.String)
+         */
+        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+            defaultTrustManager.checkClientTrusted(chain, authType);
+            
+        }
+
+        /* (non-Javadoc)
+         * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String)
+         */
+        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+            if (!trustAll || trustedHosts != null) {
+                defaultTrustManager.checkServerTrusted(chain, authType);
+            }
+            
+        }
+
+        /* (non-Javadoc)
+         * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
+         */
+        public X509Certificate[] getAcceptedIssuers() {
+            return defaultTrustManager.getAcceptedIssuers();
+        }
+        
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ProtocolProperties.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ProtocolProperties.java
new file mode 100644
index 0000000..7ef1a6d
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ProtocolProperties.java
@@ -0,0 +1,291 @@
+/**
+ * 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 concatenates 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 (as object) 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 Object getPropertyAsObject(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.getProperties().get(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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/ResponseFormatException.java b/geronimo-javamail_1.5/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.5/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.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceInputStream.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceInputStream.java
new file mode 100644
index 0000000..8465fed
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceInputStream.java
@@ -0,0 +1,110 @@
+/*
+ * 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.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.geronimo.mail.james.mime4j.codec.QuotedPrintableOutputStream;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TraceInputStream extends FilterInputStream {
+    // the current debug setting
+    protected boolean debug = false;
+
+    // the target trace output stream.
+    protected OutputStream traceStream;
+
+    /**
+     * Construct a debug trace stream.
+     * 
+     * @param in
+     *            The source input stream.
+     * @param traceStream
+     *            The side trace stream to which trace data gets written.
+     * @param encode
+     *            Indicates whether we wish the Trace data to be Q-P encoded.
+     */
+    public TraceInputStream(InputStream in, OutputStream traceStream, boolean debug, boolean encode) {
+        super(in);
+        this.debug = debug;
+        if (encode) {
+            this.traceStream = new QuotedPrintableOutputStream(traceStream, false);
+        } else {
+            this.traceStream = traceStream;
+        }
+    }
+
+    /**
+     * Set the current setting of the debug trace stream debug flag.
+     * 
+     * @param d
+     *            The new debug flag settings.
+     */
+    public void setDebug(boolean d) {
+        debug = d;
+    }
+
+    /**
+     * Reads up to len bytes of data from this input stream, placing them directly 
+     * into the provided byte array. 
+     * 
+     * @param b   the provided data buffer. 
+     * @param off the starting offset within the buffer for placing the data. 
+     * @param len the maximum number of bytes to read. 
+     * @return    that number of bytes that have been read and copied into the 
+     *            buffer or -1 if an end of stream occurs. 
+     * @exception IOException for any I/O errors. 
+     */
+    public int read(byte b[], int off, int len) throws IOException {
+        int count = in.read(b, off, len);
+        if (debug && count > 0) {
+            traceStream.write(b, off, count);
+        }
+        return count;
+    }
+
+    /**
+     * Read the next byte of data from the input stream, returning it as an 
+     * int value.  Returns -1 if the end of stream is detected. 
+     * 
+     * @return The next byte of data or -1 to indicate end-of-stream.      
+     * @exception IOException for any I/O errors
+     */
+    public int read() throws IOException {
+        int b = in.read();
+        if (debug) {
+            traceStream.write(b);
+        }
+        return b;
+    }
+
+    public int read(byte[] b) throws IOException {
+        final int read = in.read(b);
+        if (debug && read > 0) {
+            traceStream.write(b, 0, read);
+        }
+        return read;
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceOutputStream.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceOutputStream.java
new file mode 100644
index 0000000..7910626
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceOutputStream.java
@@ -0,0 +1,97 @@
+/*
+ * 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.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.geronimo.mail.james.mime4j.codec.QuotedPrintableOutputStream;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TraceOutputStream extends FilterOutputStream {
+    // the current debug setting
+    protected boolean debug = false;
+
+    // the target trace output stream.
+    protected OutputStream traceStream;
+
+    /**
+     * Construct a debug trace stream.
+     *
+     * @param out
+     *            The target out put stream.
+     * @param traceStream
+     *            The side trace stream to which trace data gets written.
+     * @param encode
+     *            Indicates whether we wish the Trace data to be Q-P encoded.
+     */
+    public TraceOutputStream(OutputStream out, OutputStream traceStream, boolean debug, boolean encode) {
+        super(out);
+        this.debug = debug;
+        if (encode) {
+            this.traceStream = new QuotedPrintableOutputStream(traceStream, false);
+        } else {
+            this.traceStream = traceStream;
+        }
+    }
+
+    /**
+     * Set the current setting of the debug trace stream debug flag.
+     *
+     * @param d
+     *            The new debug flag settings.
+     */
+    public void setDebug(boolean d) {
+        debug = d;
+    }
+
+
+    /**
+     * Write a single byte to the output stream.
+     * 
+     * @param b      The byte to be written.
+     * 
+     * @exception IOException
+     *                   thrown for any I/O errors.
+     */
+    public void write(int b) throws IOException {
+        if (debug) {
+            traceStream.write(b);
+        }
+        super.write(b);
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+        if (this.debug) {
+            this.traceStream.write(b, off, len);
+        }
+        out.write(b, off, len);
+    }
+
+    public void write(byte[] b) throws IOException {
+        if (this.debug) {
+            this.traceStream.write(b);
+        }
+        out.write(b);
+    } 
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.address.map b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.address.map
new file mode 100644
index 0000000..3bba278
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.address.map
@@ -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.

+##

+

+##

+## $Rev: 437934 $ $Date: 2006-08-28 20:27:42 -0700 (Mon, 28 Aug 2006) $

+##

+

+#

+# This file configures the default behaviour of JavaMail. DO NOT EDIT.

+# Create a new file /META-INF/javamail.address.map and put

+# the same format lines in there.

+#

+# Note that you can't override these defaults, merely add to them.

+#

+# $Rev: 351866 $ $Date: 2005-12-02 20:12:14 -0500 (Fri, 02 Dec 2005) $

+#

+rfc822=smtp

+news=nntp

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers
new file mode 100644
index 0000000..e4db1a3
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers
@@ -0,0 +1,43 @@
+##

+## 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.

+##

+

+##

+## $Rev: 437934 $ $Date: 2006-08-28 20:27:42 -0700 (Mon, 28 Aug 2006) $

+##

+

+#

+# This file configures the default behaviour of JavaMail. DO NOT EDIT.

+# Create a new file /META-INF/javamail.providers and put

+# the same format lines in there.

+#

+# Note that you can't override these defaults, merely add to them.

+#

+# $Rev: 398634 $ $Date: 2006-05-01 12:56:06 -0400 (Mon, 01 May 2006) $

+#

+protocol=smtp; type=transport; class=org.apache.geronimo.javamail.transport.smtp.SMTPTransport; vendor=Apache Software Foundation; version=1.0

+protocol=smtps; type=transport; class=org.apache.geronimo.javamail.transport.smtp.SMTPSTransport; vendor=Apache Software Foundation; version=1.0

+protocol=nntp-post; type=transport; class=org.apache.geronimo.javamail.transport.nntp.NNTPTransport; vendor=Apache Software Foundation; version=1.0

+protocol=nntp-posts; type=transport; class=org.apache.geronimo.javamail.transport.nntp.NNTPSSLTransport; 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=nntps; type=store; class=org.apache.geronimo.javamail.store.nntp.NNTPSSLStore; 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=pop3s; type=store; class=org.apache.geronimo.javamail.store.pop3.POP3SSLStore; 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.5/geronimo-javamail_1.4_provider/src/main/resources/META-INF/mailcap b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/META-INF/mailcap
new file mode 100644
index 0000000..2783191
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/META-INF/mailcap
@@ -0,0 +1,31 @@
+##

+## 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.

+##

+## $Rev$ $Date$

+##

+

+text/plain;;    x-java-content-handler=org.apache.geronimo.javamail.handlers.TextPlainHandler

+text/html;;     x-java-content-handler=org.apache.geronimo.javamail.handlers.TextHtmlHandler

+text/xml;;      x-java-content-handler=org.apache.geronimo.javamail.handlers.TextXmlHandler

+

+## These are not implemented in the reference implementation because the required support                  

+## is not available on server JVMs. 

+## image/gif;;     x-java-content-handler=org.apache.geronimo.javamail.handlers.ImageGifHandler

+## image/jpeg;;    x-java-content-handler=org.apache.geronimo.javamail.handlers.ImageJpegHandler

+## image/jpg;;    x-java-content-handler=org.apache.geronimo.javamail.handlers.ImageJpegHandler

+

+multipart/*;;   x-java-content-handler=org.apache.geronimo.javamail.handlers.MultipartHandler

+message/rfc822;; x-java-content-handler=org.apache.geronimo.javamail.handlers.RFC822MessageHandler

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.ImageGifHandler b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.ImageGifHandler
new file mode 100644
index 0000000..c6e297b
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.ImageGifHandler
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.handlers.ImageGifHandler   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.ImageJpegHandler b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.ImageJpegHandler
new file mode 100644
index 0000000..c8148bb
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.ImageJpegHandler
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.handlers.ImageJpegHandler   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.MultipartHandler b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.MultipartHandler
new file mode 100644
index 0000000..39d2e16
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.MultipartHandler
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.handlers.MultipartHandler   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.RFC822MessageHandler b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.RFC822MessageHandler
new file mode 100644
index 0000000..a091915
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.RFC822MessageHandler
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.handlers.RFC822MessageHandler   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextHtmlHandler b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextHtmlHandler
new file mode 100644
index 0000000..0da58ec
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextHtmlHandler
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.handlers.TextHtmlHandler   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextPlainHandler b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextPlainHandler
new file mode 100644
index 0000000..c3518a0
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextPlainHandler
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.handlers.TextPlainHandler   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextXmlHandler b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextXmlHandler
new file mode 100644
index 0000000..04b32e3
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.handlers.TextXmlHandler
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.handlers.TextXmlHandler   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.imap.IMAPSSSLStore b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.imap.IMAPSSSLStore
new file mode 100644
index 0000000..18127c3
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.imap.IMAPSSSLStore
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.store.imap.IMAPSSLStore   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.imap.IMAPStore b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.imap.IMAPStore
new file mode 100644
index 0000000..6c35475
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.imap.IMAPStore
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.store.imap.IMAPStore   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.nntp.NNTPSSLStore b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.nntp.NNTPSSLStore
new file mode 100644
index 0000000..c8df215
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.nntp.NNTPSSLStore
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.store.nntp.NNTPSSLStore   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.nntp.NNTPStore b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.nntp.NNTPStore
new file mode 100644
index 0000000..83d3d3a
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.nntp.NNTPStore
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.store.nntp.NNTPStore   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.pop3.POP3SSLStore b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.pop3.POP3SSLStore
new file mode 100644
index 0000000..ace45de
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.pop3.POP3SSLStore
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.store.pop3.POP3SSLStore   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.pop3.POP3Store b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.pop3.POP3Store
new file mode 100644
index 0000000..28dd416
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.store.pop3.POP3Store
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.store.pop3.POP3Store   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.nntp.NNTPSSLTransport b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.nntp.NNTPSSLTransport
new file mode 100644
index 0000000..5af212f
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.nntp.NNTPSSLTransport
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.transport.nntp.NNTPSSLTransport   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.nntp.NNTPTransport b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.nntp.NNTPTransport
new file mode 100644
index 0000000..7db7ae2
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.nntp.NNTPTransport
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.transport.nntp.NNTPTransport   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.smtp.SMTPSSLTransport b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.smtp.SMTPSSLTransport
new file mode 100644
index 0000000..cc5bbb5
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.smtp.SMTPSSLTransport
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.transport.smtp.SMTPSTransport   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.smtp.SMTPTransport b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.smtp.SMTPTransport
new file mode 100644
index 0000000..a16aa75
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/main/resources/OSGI-INF/providers/org.apache.javamail.transport.smtp.SMTPTransport
@@ -0,0 +1 @@
+org.apache.geronimo.javamail.transport.smtp.SMTPTransport   # This is directly mapped back to the same class name

diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/site/site.xml b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/site/site.xml
new file mode 100644
index 0000000..80f99dd
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/site/site.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+
+<!-- $Rev$ $Date$ -->
+
+<project name="${project.name}">
+    
+    <body>
+        
+        ${parentProject}
+        
+        ${modules}
+        
+        ${reports}
+        
+    </body>
+
+</project>
+
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/AbstractHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/AbstractHandler.java
new file mode 100644
index 0000000..6ac56ba
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/AbstractHandler.java
@@ -0,0 +1,63 @@
+/**
+ *  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.handlers;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.ByteArrayOutputStream;
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public abstract class AbstractHandler extends TestCase {
+    protected DataContentHandler dch;
+    protected String mimeType;
+
+    public void testGetContent() throws Exception {
+        final byte[] bytes = "Hello World".getBytes();
+        DataSource ds = new DataSource() {
+            public InputStream getInputStream() {
+                return new ByteArrayInputStream(bytes);
+            }
+
+            public OutputStream getOutputStream() {
+                throw new UnsupportedOperationException();
+            }
+
+            public String getContentType() {
+                return mimeType; 
+            }
+
+            public String getName() {
+                throw new UnsupportedOperationException();
+            }
+        };
+        Object o = dch.getContent(ds);
+        assertEquals("Hello World", o);
+    }
+
+    public void testWriteTo() throws Exception {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        dch.writeTo("Hello World", mimeType, baos);
+        assertEquals("Hello World", baos.toString());
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/TextHtmlTest.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/TextHtmlTest.java
new file mode 100644
index 0000000..427fe71
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/TextHtmlTest.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.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TextHtmlTest extends AbstractHandler {
+    public void testDataFlavor() {
+        DataFlavor[] flavours = dch.getTransferDataFlavors();
+        assertEquals(1, flavours.length);
+        DataFlavor flavor = flavours[0];
+        assertEquals(String.class, flavor.getRepresentationClass());
+        assertEquals("text/html", flavor.getMimeType());
+        assertEquals("HTML Text", flavor.getHumanPresentableName());
+    }
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        dch = new TextHtmlHandler();
+        mimeType = "text/html"; 
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/TextPlainTest.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/TextPlainTest.java
new file mode 100644
index 0000000..b2e2291
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/TextPlainTest.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.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TextPlainTest extends AbstractHandler {
+    public void testDataFlavor() {
+        DataFlavor[] flavours = dch.getTransferDataFlavors();
+        assertEquals(1, flavours.length);
+        DataFlavor flavor = flavours[0];
+        assertEquals(String.class, flavor.getRepresentationClass());
+        assertEquals("text/plain", flavor.getMimeType());
+        assertEquals("Plain Text", flavor.getHumanPresentableName());
+    }
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        dch = new TextPlainHandler();
+        mimeType = "text/plain"; 
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/TextXmlTest.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/TextXmlTest.java
new file mode 100644
index 0000000..001ef63
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/handlers/TextXmlTest.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.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TextXmlTest extends AbstractHandler {
+    public void testDataFlavor() {
+        DataFlavor[] flavours = dch.getTransferDataFlavors();
+        assertEquals(1, flavours.length);
+        DataFlavor flavor = flavours[0];
+        assertEquals(String.class, flavor.getRepresentationClass());
+        assertEquals("text/xml", flavor.getMimeType());
+        assertEquals("XML Text", flavor.getHumanPresentableName());
+    }
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        dch = new TextXmlHandler();
+        mimeType = "text/xml"; 
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/issues/GERONIMO6480Test.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/issues/GERONIMO6480Test.java
new file mode 100755
index 0000000..e999475
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/issues/GERONIMO6480Test.java
@@ -0,0 +1,139 @@
+/**
+ *  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.issues;
+
+import java.io.File;
+import java.util.Properties;
+
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.BodyPart;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import junit.framework.Assert;
+
+import org.apache.geronimo.javamail.testserver.AbstractProtocolTest;
+
+public class GERONIMO6480Test extends AbstractProtocolTest {
+
+    public void testGERONIMO6480_0() throws Exception {
+        BodyPart attachmentPart = new MimeBodyPart();
+        attachmentPart.setDataHandler(new DataHandler(new FileDataSource(getAbsoluteFilePathFromClassPath("pdf-test.pdf"))));
+        attachmentPart.setFileName("test.pdf");
+        String contentType = getSendedAttachmentContentType(attachmentPart);
+        Assert.assertEquals("application/octet-stream; name=test.pdf".toLowerCase(), contentType.toLowerCase());
+        // "text/plain; name=test.pdf" with Geronimo because setFileName force it to 'text/plain' when adding the 'name=' part instead of keeping it null
+    }
+
+    public void testGERONIMO6480_1() throws Exception {
+        BodyPart attachmentPart = new MimeBodyPart();
+        attachmentPart.addHeader("Content-Type", "aplication/pdf");
+        // setDataHandler reset "Content-Type" so equivalent to previous test
+        attachmentPart.setDataHandler(new DataHandler(new FileDataSource(getAbsoluteFilePathFromClassPath("pdf-test.pdf"))));
+        attachmentPart.setFileName("test.pdf");
+        String contentType = getSendedAttachmentContentType(attachmentPart);
+        Assert.assertEquals("application/octet-stream; name=test.pdf".toLowerCase(), contentType.toLowerCase());
+        // "text/plain; name=test.pdf" with Geronimo because setFileName force it to 'text/plain' when adding the 'name=' part instead of keeping it null
+    }
+
+    public void testGERONIMO6480_2() throws Exception {
+        BodyPart attachmentPart = new MimeBodyPart();
+        attachmentPart.setDataHandler(new DataHandler(new FileDataSource(getAbsoluteFilePathFromClassPath("pdf-test.pdf"))));
+        attachmentPart.addHeader("Content-Type", "aplication/pdf");
+        attachmentPart.setFileName("test.pdf");
+        String contentType = getSendedAttachmentContentType(attachmentPart);
+        Assert.assertEquals("aplication/pdf; name=test.pdf".toLowerCase(), contentType.toLowerCase());
+    }
+
+    public void testGERONIMO6480_3() throws Exception {
+        System.setProperty("mail.mime.setcontenttypefilename", Boolean.FALSE.toString());
+        try {
+            BodyPart attachmentPart = new MimeBodyPart();
+            attachmentPart.setDataHandler(new DataHandler(new FileDataSource(getAbsoluteFilePathFromClassPath("pdf-test.pdf"))));
+            attachmentPart.setFileName("test.pdf");
+            String contentType = getSendedAttachmentContentType(attachmentPart);
+            Assert.assertEquals("application/octet-stream; name=test.pdf".toLowerCase(), contentType.toLowerCase());
+        } finally {
+            System.setProperty("mail.mime.setcontenttypefilename", Boolean.TRUE.toString());
+        }
+    }
+
+    public void testGERONIMO6480_4() throws Exception {
+        BodyPart attachmentPart = new MimeBodyPart();
+        attachmentPart.setFileName("test.pdf");
+        attachmentPart.setDataHandler(new DataHandler(new FileDataSource(getAbsoluteFilePathFromClassPath("pdf-test.pdf"))));
+        String contentType = getSendedAttachmentContentType(attachmentPart);
+        Assert.assertEquals("application/octet-stream; name=test.pdf".toLowerCase(), contentType.toLowerCase());
+    }
+
+    private File getAbsoluteFilePathFromClassPath(String filename) throws Exception {
+        return new File(GERONIMO6480Test.class.getClassLoader().getResource(filename).toURI());
+    }
+
+    private String getSendedAttachmentContentType(BodyPart attachmentPart) throws Exception {
+
+        start();
+        Properties props = new Properties();
+        props.setProperty("mail.transport.protocol", "smtp");
+        props.setProperty("mail.store.protocol", "imap");
+        props.setProperty("mail.imap.port", String.valueOf(imapConf.getListenerPort()));
+        props.setProperty("mail.smtp.port", String.valueOf(smtpConf.getListenerPort()));
+        //props.setProperty("mail.debug", "true");
+        Session session = Session.getInstance(props);
+
+        BodyPart messageBodyPart = new MimeBodyPart();
+        messageBodyPart.setText("See attachment.");
+
+        MimeMultipart multipart = new MimeMultipart();
+        multipart.addBodyPart(messageBodyPart);
+        multipart.addBodyPart(attachmentPart);
+
+        Message message = new MimeMessage(session);
+        message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("test@mockserver.com"));
+        message.setSubject("Test attachment content-type");
+        message.setContent(multipart);
+
+        Transport.send(message);
+
+        return getAttachmentContentType(session);
+    }
+
+    private String getAttachmentContentType(Session session) throws Exception {
+        Store store = session.getStore();
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+
+        Folder folder = store.getDefaultFolder();
+        folder = folder.getFolder("inbox");
+        folder.open(Folder.READ_ONLY);
+
+        server.ensureMsgCount(1);
+
+        Message message = folder.getMessage(1);
+        MimeMultipart multipart = (MimeMultipart) message.getContent();
+        BodyPart attachmentPart = multipart.getBodyPart(1);
+        return attachmentPart.getContentType();
+    }
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/issues/IssuesTest.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/issues/IssuesTest.java
new file mode 100644
index 0000000..e10bf5a
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/issues/IssuesTest.java
@@ -0,0 +1,149 @@
+/**
+ *  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.issues;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintStream;
+import java.util.Properties;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.activation.FileDataSource;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import junit.framework.Assert;
+
+import org.apache.geronimo.javamail.testserver.AbstractProtocolTest;
+import org.apache.geronimo.javamail.testserver.MailServer;
+
+public class IssuesTest extends AbstractProtocolTest {
+
+    public void testGERONIMO6519() throws Exception {
+
+        PrintStream original = System.out;
+
+        try {
+
+            start();
+            // Setup JavaMail session
+            Properties props = new Properties();
+            props.setProperty("mail.debug", "true");
+            props.setProperty("mail.smtp.port", String.valueOf(smtpConf.getListenerPort()));
+            props.setProperty("mail.smtp.localhost", "some.full.qualified.name.com");
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            System.setOut(new PrintStream(baos, true));
+
+            Session session = Session.getInstance(props);
+            MimeMessage message = new MimeMessage(session);
+            message.setFrom(new InternetAddress("test@localhost"));
+            message.setRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress("test@localhost"));
+            message.setText("test");
+
+            Transport.send(message);
+            Assert.assertTrue(baos.toString().contains("EHLO some.full.qualified.name.com"));
+
+        } finally {
+            System.setOut(original);
+        }
+
+    }
+    
+    public void testGERONIMO4594() throws Exception {
+        Assert.assertTrue(doGERONIMO4594(true, true));
+    }
+    
+    public void testGERONIMO4594Fail0() throws Exception {
+        Assert.assertFalse(doGERONIMO4594(false, true));
+    }
+    
+    public void testGERONIMO4594Fail1() throws Exception {
+        Assert.assertFalse(doGERONIMO4594(false, false));
+    }
+    
+    public void testGERONIMO4594Fail2() throws Exception {
+        Assert.assertFalse(doGERONIMO4594(true, false));
+    }
+        
+    private boolean doGERONIMO4594(boolean decode, boolean encode) throws Exception {
+
+        final String specialFileName = "encoded_filename_\u00C4\u00DC\u00D6\u0226(test).pdf";
+        
+        System.setProperty("mail.mime.decodefilename", String.valueOf(decode));
+        System.setProperty("mail.mime.encodefilename", String.valueOf(encode));
+        try {
+
+            start();
+
+            // Setup JavaMail session
+            Properties props = new Properties();
+            props.setProperty("mail.transport.protocol", "smtp");
+            props.setProperty("mail.smtp.port", String.valueOf(smtpConf.getListenerPort()));
+            props.setProperty("mail.store.protocol", "imap");
+            props.setProperty("mail.imap.port", String.valueOf(imapConf.getListenerPort()));
+            //props.setProperty("mail.debug","true");
+            Session session = Session.getInstance(props);
+
+            MimeMessage msg = new MimeMessage(session);
+            msg.setSubject("a file for you");
+            msg.setRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress("serveruser@localhost"));
+            msg.setFrom(new InternetAddress("serveruser@localhost"));
+
+            MimeBodyPart messageBodyPart = new MimeBodyPart();
+            Multipart multipart = new MimeMultipart();
+            messageBodyPart.setText("This is message body");
+            File file = MailServer.getAbsoluteFilePathFromClassPath("pdf-test.pdf");
+            Assert.assertTrue(file.exists());
+            DataSource source = new FileDataSource(file.getAbsoluteFile());
+            messageBodyPart.setDataHandler(new DataHandler(source));
+            messageBodyPart.setFileName(specialFileName);
+            multipart.addBodyPart(messageBodyPart);
+            msg.setContent(multipart);
+            sendMessage(msg);
+            server.ensureMsgCount(1);
+
+            Session jmsession = Session.getInstance(props);
+            Store store = jmsession.getStore();
+            store.connect("127.0.0.1", "serveruser", "serverpass");
+            Folder f = store.getFolder("INBOX");
+            f.open(Folder.READ_ONLY); //TODO STAT only when folder open???
+            Assert.assertEquals(1, f.getMessageCount());
+            Message[] messages = new Message[2];
+            messages[0] = f.getMessage(1);
+            boolean match = specialFileName.equals(((Multipart) messages[0].getContent()).getBodyPart(0).getFileName());
+            f.close(false);
+            store.close();
+            return match;
+
+        } finally {
+            System.setProperty("mail.mime.decodefilename", "false");
+            System.setProperty("mail.mime.encodefilename", "false");
+        }
+
+    }
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/imap/AuthenticationTest.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/imap/AuthenticationTest.java
new file mode 100644
index 0000000..ff09eb8
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/imap/AuthenticationTest.java
@@ -0,0 +1,208 @@
+/**
+ *  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.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Properties;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Store;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.james.protocols.lib.PortUtil;
+
+public class AuthenticationTest extends TestCase {
+
+    public void testImplUsage() throws Exception {
+
+        //check that we load our mail impl
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "imap");
+        Session jmsession = Session.getInstance(props);
+        Assert.assertEquals(org.apache.geronimo.javamail.store.imap.IMAPStore.class, jmsession.getStore().getClass());
+        
+    }
+    
+    public void testAuthenticatePlain() throws Exception {
+
+        final int listenerPort = PortUtil.getNonPrivilegedPort();
+        //greenmail does not have AUTHENTICATE "PLAIN" support
+        FakeImapAuthPlainServer fs = new FakeImapAuthPlainServer(null, "user", "pass");
+        fs.startServer(listenerPort);
+        // Setup JavaMail session
+        Properties props = new Properties();
+        props.setProperty("mail.imap.port", String.valueOf(listenerPort));
+        props.setProperty("mail.debug", String.valueOf(true));
+        props.setProperty("mail.debug.auth", String.valueOf(true));
+
+        Session session = Session.getInstance(props);
+        Store store = session.getStore("imap");
+        store.connect("localhost", "user", "pass");
+        assertTrue(store.isConnected());
+        fs.join();
+        assertNull(fs.exception);
+    }
+
+    public void testAuthenticatePlainFail() throws Exception {
+
+        final int listenerPort = PortUtil.getNonPrivilegedPort();
+        //greenmail does not have AUTHENTICATE "PLAIN" support
+        FakeImapAuthPlainServer fs = new FakeImapAuthPlainServer(null, "user", "pass");
+        fs.startServer(listenerPort);
+        // Setup JavaMail session
+        Properties props = new Properties();
+        props.setProperty("mail.imap.port", String.valueOf(listenerPort));
+        props.setProperty("mail.debug", String.valueOf(true));
+        props.setProperty("mail.debug.auth", String.valueOf(true));
+        Session session = Session.getInstance(props);
+        Store store = session.getStore("imap");
+
+        try {
+
+            store.connect("localhost", "userXXX", "passXXX");
+            fail();
+        } catch (MessagingException e) {
+            //expected
+        }
+    }
+
+    public void testAuthenticatePlainAuthzid() throws Exception {
+
+        final int listenerPort = PortUtil.getNonPrivilegedPort();
+        //greenmail does not have AUTHENTICATE "PLAIN" support
+        FakeImapAuthPlainServer fs = new FakeImapAuthPlainServer("authzid", "user", "pass");
+        fs.startServer(listenerPort);
+        // Setup JavaMail session
+        Properties props = new Properties();
+        props.setProperty("mail.imap.port", String.valueOf(listenerPort));
+        props.setProperty("mail.debug", String.valueOf(true));
+        props.setProperty("mail.debug.auth", String.valueOf(true));
+        props.setProperty("mail.imap.sasl.authorizationid", "authzid");
+
+        Session session = Session.getInstance(props);
+        Store store = session.getStore("imap");
+        store.connect("localhost", "user", "pass");
+        assertTrue(store.isConnected());
+        fs.join();
+        assertNull(fs.exception);
+    }
+
+
+    private class FakeImapAuthPlainServer extends Thread{
+
+        private ServerSocket serverSocket;
+        private Socket socket;
+        private String authzid;
+        private String username;
+        private String password;
+        Exception exception;
+
+        private FakeImapAuthPlainServer(String authzid, String username, String password) {
+            this.password = password;
+            this.username = username;
+            this.authzid = authzid==null?"":authzid;
+        }
+
+        void startServer(int port) throws IOException {
+
+            serverSocket = new ServerSocket(port);
+            this.setDaemon(false);
+            this.start();
+
+        }
+
+
+        public void run() {
+            try {
+                socket = serverSocket.accept();
+                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+                PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
+                pw.write("* OK ready\r\n");
+                pw.flush();
+                String tag = br.readLine().split(" ")[0];
+                pw.write("* OK IMAP4rev1 Server ready\r\n");
+                pw.write("* CAPABILITY IMAP4rev1 AUTH=PLAIN\r\n");
+                pw.write(tag+" OK CAPABILITY completed.\r\n");
+                pw.flush();
+                tag = br.readLine().split(" ")[0];
+                pw.write("+ \r\n");
+                pw.flush();
+                String authline = new String(Base64.decode(br.readLine()));
+                System.out.println("authline : "+authline );
+
+                if(!"".equals(authzid) && !(authzid+"\0"+username+"\0"+password).equals(authline)) {
+                    pw.write(tag+" BAD username password invalid.\r\n");
+                    pw.flush();
+                    return;
+                }
+
+                if("".equals(authzid) && !(username+"\0"+username+"\0"+password).equals(authline) && !("\0"+username+"\0"+password).equals(authline)) {
+                    pw.write(tag+" BAD username password invalid.\r\n");
+                    pw.flush();
+                    return;
+                }
+
+                pw.write(tag + " OK Authenticated.\r\n");
+                pw.flush();
+
+                String fin = br.readLine();
+                tag = fin.split(" ")[0];
+
+                if(fin.contains("CAPA")) {
+                    pw.write("* CAPABILITY IMAP4rev1 AUTH=PLAIN\r\n");
+                    pw.write(tag+" OK CAPABILITY completed.\r\n");
+                    pw.flush();
+                    tag = br.readLine().split(" ")[0];
+                    pw.write(tag+" OK NOOP.\r\n");
+                }
+                else {
+                    pw.write(tag+" OK NOOP.\r\n");
+                }
+
+                pw.flush();
+
+            } catch (Exception e) {
+                exception = e;
+            }finally {
+
+                try {
+                    socket.close();
+                } catch (Exception e) {
+                    //ignore
+                }
+
+                try {
+                    serverSocket.close();
+                } catch (Exception e) {
+                    //ignore
+                }
+
+            }
+        }
+    }
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/imap/IMAPStoreTest.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/imap/IMAPStoreTest.java
new file mode 100644
index 0000000..3dd7866
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/imap/IMAPStoreTest.java
@@ -0,0 +1,89 @@
+/**
+ *  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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Properties;
+
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.apache.geronimo.javamail.testserver.AbstractProtocolTest;
+
+
+public class IMAPStoreTest extends AbstractProtocolTest {
+    
+    public void testSimple() throws Exception {
+       
+        start();
+        sendTestMsgs();
+        
+        Properties props = new Properties();
+        props.setProperty("mail.imap.port", String.valueOf(imapConf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+        Session session = Session.getInstance(props);
+        
+        // Load the message from IMAP
+        Store store = session.getStore("imap");
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+        Folder folder = store.getFolder("INBOX");
+        folder.open(Folder.READ_ONLY);
+        Message[] messages = new Message[2];
+        messages[0] = folder.getMessage(1);
+        messages[1] = folder.getMessage(2);
+        checkMessages(messages);
+        folder.close(false);
+        store.close();
+    }
+    
+    
+    private void checkMessages(Message[] messages) throws Exception {
+        MimeMessage msg1 = (MimeMessage)messages[0];
+        Object content = msg1.getContent();
+        assertTrue(content instanceof MimeMultipart);
+        MimeMultipart multipart = (MimeMultipart)content;
+        assertEquals("First part", multipart.getBodyPart(0).getContent());
+        assertEquals("Second part", multipart.getBodyPart(1).getContent());        
+        checkMessage(msg1);
+        
+        MimeMessage msg2 = (MimeMessage)messages[1];
+        assertEquals("Foo Bar", msg2.getContent().toString().trim());
+        checkMessage(msg2);
+    }
+    
+    private void checkMessage(MimeMessage input) throws Exception {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        input.writeTo(out);
+        
+        Properties props = new Properties();
+        Session s = Session.getInstance(props);
+        
+        byte [] inputData = out.toByteArray();
+        System.out.println(new String(inputData, 0, inputData.length));
+        
+        MimeMessage output = new MimeMessage(s, new ByteArrayInputStream(inputData));
+        
+        assertEquals(input.getContentType().toLowerCase(), output.getContentType().toLowerCase());        
+    }
+    
+ 
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructureTest.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructureTest.java
new file mode 100644
index 0000000..9ef05e7
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/imap/connection/IMAPBodyStructureTest.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.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+
+import javax.mail.internet.MimeMessage;
+
+import junit.framework.TestCase;
+import org.apache.geronimo.javamail.store.imap.IMAPStoreTest;
+
+public class IMAPBodyStructureTest extends TestCase {
+
+    public void testMultipart() throws Exception {
+        InputStream in = IMAPStoreTest.class.getResourceAsStream("/imap/multipart.bodystructure");
+        BufferedReader r = new BufferedReader(new InputStreamReader(in));
+        try {
+            IMAPResponseTokenizer tokenizer = new IMAPResponseTokenizer(r.readLine().getBytes("ISO8859-1"));
+            IMAPBodyStructure s = new IMAPBodyStructure(tokenizer);
+            assertNull(s.disposition.getDisposition());
+            assertNull(s.md5Hash);
+        } finally {
+            in.close();
+        }
+    }
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/pop3/POP3StoreTest.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/pop3/POP3StoreTest.java
new file mode 100644
index 0000000..a91133d
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/store/pop3/POP3StoreTest.java
@@ -0,0 +1,449 @@
+/**
+ *  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.pop3;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.FetchProfile;
+import javax.mail.Flags;
+import javax.mail.Flags.Flag;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Transport;
+import javax.mail.UIDFolder;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import junit.framework.Assert;
+
+import org.apache.geronimo.javamail.testserver.AbstractProtocolTest;
+import org.apache.geronimo.javamail.testserver.MailServer.DummySocketFactory;
+
+public class POP3StoreTest extends AbstractProtocolTest {
+
+    
+    
+    
+    public void testSendRetrieve() throws Exception {
+        
+        start();
+        
+        // Setup JavaMail session
+        Properties props = new Properties();
+        props.setProperty("mail.smtp.port", String.valueOf(smtpConf.getListenerPort()));
+        props.setProperty("mail.debug","true");
+        Session session = Session.getInstance(props);
+        // Send messages for the current test to James
+        sendMessage(session, "/messages/multipart.msg");
+        sendMessage(session, "/messages/simple.msg");
+        server.ensureMsgCount(2);
+        
+        props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3");
+        props.setProperty("mail.pop3.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+        Folder f = store.getFolder("INBOX");
+        f.open(Folder.READ_ONLY); //TODO STAT only when folder open???
+        Assert.assertEquals(2, f.getMessageCount());
+        Message[] messages = new Message[2];
+        messages[0] = f.getMessage(1);
+        messages[1] = f.getMessage(2);
+        checkMessages(messages);
+        f.close(false);
+        store.close();
+    }
+    
+
+    
+    
+    private void checkMessages(Message[] messages) throws Exception {
+        MimeMessage msg1 = (MimeMessage)messages[0];
+        Object content = msg1.getContent();
+        assertTrue(content instanceof MimeMultipart);
+        MimeMultipart multipart = (MimeMultipart)content;
+        assertEquals("First part", multipart.getBodyPart(0).getContent());
+        assertEquals("Second part", multipart.getBodyPart(1).getContent());        
+        checkMessage(msg1);
+        
+        MimeMessage msg2 = (MimeMessage)messages[1];
+        assertEquals("Foo Bar", msg2.getContent().toString().trim());
+        checkMessage(msg2);
+    }
+    
+    private void checkMessage(MimeMessage input) throws Exception {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        input.writeTo(out);
+        
+        Properties props = new Properties();
+        Session s = Session.getInstance(props);
+        
+        byte [] inputData = out.toByteArray();
+        System.out.println(new String(inputData, 0, inputData.length));
+        
+        MimeMessage output = new MimeMessage(s, new ByteArrayInputStream(inputData));
+        
+        assertEquals(input.getContentType().toLowerCase(), output.getContentType().toLowerCase());        
+    }
+    
+
+    public void testStartTLS() throws Exception {
+
+        pop3Conf.enableSSL(true, false);
+
+        start();
+
+        sendTestMsgs();
+        
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3");
+        props.setProperty("mail.pop3.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+        props.setProperty("mail.pop3.starttls.required", "true");
+        props.setProperty("mail.pop3.ssl.trust", "*");
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+        Folder f = store.getFolder("INBOX");
+        f.open(Folder.READ_ONLY); //TODO STAT only when folder open???
+        Assert.assertEquals(2, f.getMessageCount());
+        f.close(false);
+        store.close();
+
+    }
+
+    public void testAPOP() throws Exception {
+
+        pop3Conf.enableSSL(true, false);
+
+        start();
+        sendTestMsgs();
+        
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3");
+        props.setProperty("mail.pop3.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+        props.setProperty("mail.pop3.apop.enable", "true");
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+        Folder f = store.getFolder("INBOX");
+        f.open(Folder.READ_ONLY); //TODO STAT only when folder open???
+        Assert.assertEquals(2, f.getMessageCount());
+        f.close(false);
+        store.close();
+
+    }
+
+    public void testFetch() throws Exception {
+
+        
+        pop3Conf.enableSSL(true, false);
+
+        start();
+        sendTestMsgs();
+        
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3");
+        props.setProperty("mail.pop3.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+        Folder f = store.getFolder("INBOX");
+        f.open(Folder.READ_ONLY); //TODO STAT only when folder open???
+        FetchProfile fp = new FetchProfile();
+        fp.add(UIDFolder.FetchProfileItem.UID);
+        fp.add(FetchProfile.Item.CONTENT_INFO);
+        
+        Message[] msgs = f.getMessages();
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        Assert.assertEquals(2, msgs.length);
+        
+        f.fetch(msgs, fp);
+        Assert.assertEquals(2, f.getMessageCount());
+        
+        for (int i = 0; i < msgs.length; i++) {
+            Message message = msgs[i];
+            message.writeTo(bout);
+            String msg = bout.toString();
+            Assert.assertNotNull(msg);
+            int num = message.getMessageNumber();
+            Assert.assertTrue(num > 0);
+            String uid = ((POP3Folder) f).getUID(message);
+            Assert.assertNotNull(uid);
+            Assert.assertTrue(!uid.isEmpty());
+        }
+        
+        f.close(false);
+        store.close();
+
+    }
+    
+    
+    
+    public void testDelete() throws Exception {
+
+        
+        pop3Conf.enableSSL(true, false);
+
+        start();
+        sendTestMsgs();
+        
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3");
+        props.setProperty("mail.pop3.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+        Folder f = store.getFolder("INBOX");
+        f.open(Folder.READ_WRITE); //TODO STAT only when folder open???
+        Assert.assertEquals(2, f.getMessageCount());
+        Message[] msgs =  f.getMessages();
+        f.setFlags(msgs, new Flags(Flag.DELETED), true);
+        Assert.assertEquals(2, f.getMessageCount());
+        f.getMessage(1).getSubject(); //should fail
+        //Assert.assertEquals(2, f.expunge());
+        f.close(false);
+        f.open(Folder.READ_ONLY); //TODO STAT only when folder open???
+        Assert.assertEquals(0, f.getMessageCount());
+        store.close();
+
+    }
+    
+    
+    
+    public void testStartTLSFail() throws Exception {
+
+        
+        pop3Conf.enableSSL(false, false);
+
+        start();
+        sendTestMsgs();
+        
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3");
+        props.setProperty("mail.pop3.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+        props.setProperty("mail.pop3.starttls.required", "true");
+        props.setProperty("mail.pop3.ssl.trust", "*");
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        try {
+            store.connect("127.0.0.1", "serveruser", "serverpass");
+            fail();
+        } catch (MessagingException e) {
+            //Expected
+        }
+    }
+
+    public void testSSLEnable() throws Exception {
+
+        
+        pop3Conf.enableSSL(false, true);
+
+        start();
+        sendTestMsgs();
+
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3");
+        props.setProperty("mail.pop3.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+        props.setProperty("mail.pop3.ssl.enable", "true");
+        props.setProperty("mail.pop3.ssl.trust", "*");
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+        Folder f = store.getFolder("INBOX");
+        f.open(Folder.READ_ONLY); //TODO STAT only when folder open???
+        Assert.assertEquals(2, f.getMessageCount());
+        f.close(false);
+        store.close();
+
+    }
+
+    public void testSSLPop3s() throws Exception {
+
+        
+        pop3Conf.enableSSL(false, true);
+
+        start();
+        sendTestMsgs();
+
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3s");
+        props.setProperty("mail.pop3s.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+        props.setProperty("mail.pop3s.ssl.trust", "*");
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+        Folder f = store.getFolder("INBOX");
+        f.open(Folder.READ_ONLY); //TODO STAT only when folder open???
+        Assert.assertEquals(2, f.getMessageCount());
+        f.close(false);
+        store.close();
+
+    }
+    
+    public void testSSLPop3sFactoryClass() throws Exception {
+
+        
+        pop3Conf.enableSSL(false, true);
+
+        start();
+        sendTestMsgs();
+
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3s");
+        props.setProperty("mail.pop3s.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+        props.setProperty("mail.pop3s.ssl.trust", "*");
+        props.setProperty("mail.pop3s.ssl.socketFactory.class", DummySocketFactory.class.getName());
+       
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        try {
+            store.connect("127.0.0.1", "serveruser", "serverpass");
+            fail();
+        } catch (MessagingException e) {
+            Assert.assertEquals("dummy socket factory", e.getCause().getCause().getMessage());
+            
+            //Expected
+        }
+
+        
+        
+    }
+
+    public void testSSLPop3sFactoryInstance() throws Exception {
+
+        
+        pop3Conf.enableSSL(false, true);
+
+        start();
+        sendTestMsgs();
+
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3s");
+        props.setProperty("mail.pop3s.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+        props.setProperty("mail.pop3s.ssl.trust", "*");
+        props.put("mail.pop3s.ssl.socketFactory", new DummySocketFactory());
+       
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        try {
+            store.connect("127.0.0.1", "serveruser", "serverpass");
+            fail();
+        } catch (MessagingException e) {
+            Assert.assertEquals("dummy socket factory", e.getCause().getMessage());
+            
+            //Expected
+        }
+
+    }
+    
+    public void testSSLPop3sNotEnabled() throws Exception {
+
+        
+        pop3Conf.enableSSL(false, false);
+
+        start();
+        sendTestMsgs();
+
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3s");
+        props.setProperty("mail.pop3s.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+        props.setProperty("mail.pop3s.ssl.trust", "*");
+        props.setProperty("mail.pop3s.ssl.enable", "false");
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+        Folder f = store.getFolder("INBOX");
+        f.open(Folder.READ_ONLY); //TODO STAT only when folder open???
+        Assert.assertEquals(2, f.getMessageCount());
+        f.close(false);
+        store.close();
+
+    }
+    
+    public void testPop3GetMsgs() throws Exception {
+
+        
+        pop3Conf.enableSSL(false, false);
+
+        start();
+        sendTestMsgs();
+
+        Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3");
+        props.setProperty("mail.pop3.port", String.valueOf(pop3Conf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+
+        Session jmsession = Session.getInstance(props);
+        Store store = jmsession.getStore();
+        store.connect("127.0.0.1", "serveruser", "serverpass");
+        Folder f = store.getFolder("INBOX");
+        f.open(Folder.READ_ONLY); //TODO STAT only when folder open???
+        
+        
+        Message[] msgs =  f.getMessages();
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        Assert.assertEquals(2, msgs.length);
+        
+        for (int i = 0; i < msgs.length; i++) {
+            Message message = msgs[i];
+            message.writeTo(bout);
+            String msg = bout.toString();
+            Assert.assertNotNull(msg);
+            int num = message.getMessageNumber();
+            Assert.assertTrue(num > 0);
+            String uid = ((POP3Folder) f).getUID(message);
+            Assert.assertNotNull(uid);
+            Assert.assertTrue(!uid.isEmpty());
+        }
+        
+        f.close(false);
+        store.close();
+
+    }
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/AbstractProtocolTest.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/AbstractProtocolTest.java
new file mode 100644
index 0000000..8648869
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/AbstractProtocolTest.java
@@ -0,0 +1,115 @@
+/**
+ *  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.testserver;
+
+import java.io.InputStream;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import org.apache.geronimo.javamail.store.pop3.POP3StoreTest;
+
+public abstract class AbstractProtocolTest extends TestCase {
+
+    protected MailServer server = new MailServer();
+    protected MailServer.Pop3TestConfiguration pop3Conf;
+    protected MailServer.SmtpTestConfiguration smtpConf;
+    protected MailServer.ImapTestConfiguration imapConf;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        pop3Conf = new MailServer.Pop3TestConfiguration();
+        smtpConf = new MailServer.SmtpTestConfiguration();
+        imapConf = new MailServer.ImapTestConfiguration();
+
+    }
+
+    protected void start() throws Exception {
+
+        server.start(smtpConf, pop3Conf, imapConf);
+
+    }
+
+    public void testImplUsageImap() throws Exception {
+
+        //check that we load our mail impl
+        final Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "imap");
+        final Session jmsession = Session.getInstance(props);
+        Assert.assertEquals(org.apache.geronimo.javamail.store.imap.IMAPStore.class, jmsession.getStore().getClass());
+
+    }
+
+    public void testImplUsagePop3() throws Exception {
+
+        //check that we load our mail impl
+        final Properties props = new Properties();
+        props.setProperty("mail.store.protocol", "pop3");
+        final Session jmsession = Session.getInstance(props);
+        Assert.assertEquals(org.apache.geronimo.javamail.store.pop3.POP3Store.class, jmsession.getStore().getClass());
+
+    }
+
+    public void testImplUsageSmtp() throws Exception {
+
+        //check that we load our mail impl
+        final Properties props = new Properties();
+        props.setProperty("mail.transport.protocol", "smtp");
+        final Session jmsession = Session.getInstance(props);
+        Assert.assertEquals(org.apache.geronimo.javamail.transport.smtp.SMTPTransport.class, jmsession.getTransport().getClass());
+
+    }
+
+    protected void sendTestMsgs() throws Exception {
+        final Properties props = new Properties();
+        props.setProperty("mail.smtp.port", String.valueOf(smtpConf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+        final Session session = Session.getInstance(props);
+        sendMessage(session, "/messages/multipart.msg");
+        sendMessage(session, "/messages/simple.msg");
+        server.ensureMsgCount(2);
+    }
+
+    protected void sendMessage(final Session session, final String msgFile) throws Exception {
+        MimeMessage message;
+        final InputStream in = POP3StoreTest.class.getResourceAsStream(msgFile);
+        try {
+            message = new MimeMessage(session, in);
+        } finally {
+            in.close();
+        }
+        Transport.send(message, new Address[] { new InternetAddress("serveruser@localhost") });
+    }
+    
+    protected void sendMessage(final MimeMessage message) throws Exception {
+        Transport.send(message, new Address[] { new InternetAddress("serveruser@localhost") });
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        server.stop();
+    }
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/ApopCmdHandler.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/ApopCmdHandler.java
new file mode 100644
index 0000000..4c0bead
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/ApopCmdHandler.java
@@ -0,0 +1,119 @@
+/**
+ *  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.testserver;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.annotation.Resource;
+
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.exception.BadCredentialsException;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.pop3server.mailbox.MailboxAdapter;
+import org.apache.james.protocols.api.Request;
+import org.apache.james.protocols.api.Response;
+import org.apache.james.protocols.lib.POP3BeforeSMTPHelper;
+import org.apache.james.protocols.lib.Slf4jLoggerAdapter;
+import org.apache.james.protocols.pop3.POP3Response;
+import org.apache.james.protocols.pop3.POP3Session;
+import org.apache.james.protocols.pop3.core.AbstractApopCmdHandler;
+import org.apache.james.protocols.pop3.mailbox.Mailbox;
+
+public class ApopCmdHandler extends AbstractApopCmdHandler  {
+
+    private MailboxManager manager;
+
+
+    @Resource(name = "mailboxmanager")
+    public void setMailboxManager(MailboxManager manager) {
+        this.manager = manager;
+    }
+
+    @Override
+    public Response onCommand(POP3Session session, Request request) {
+        Response response =  super.onCommand(session, request);
+        if (POP3Response.OK_RESPONSE.equals(response.getRetCode())) {
+            POP3BeforeSMTPHelper.addIPAddress(session.getRemoteAddress().getAddress().getHostAddress());
+        }
+        return response;
+    }
+
+    @Override
+    protected Mailbox auth(POP3Session session, String apopTimestamp, String user, String digest) throws Exception {
+        MailboxSession mSession = null;
+        
+        String plaintextpassword = "serverpass";
+        
+        try {
+            final String toHash = apopTimestamp.trim()+plaintextpassword;
+            
+            if(!getMD5(toHash).equals(digest))
+            {
+                System.out.println("Digests does not match");
+                return null;
+            }
+            
+            
+            session.setUser(user);
+            
+            mSession = manager.createSystemSession(session.getUser(), new Slf4jLoggerAdapter(session.getLogger()));
+            manager.startProcessingRequest(mSession);
+            MailboxPath inbox = MailboxPath.inbox(mSession);
+            
+            // check if the mailbox exists, if not create it
+            if (!manager.mailboxExists(inbox, mSession)) {
+                manager.createMailbox(inbox, mSession);
+            }
+            MessageManager mailbox = manager.getMailbox(MailboxPath.inbox(mSession), mSession);
+            return new MailboxAdapter(manager, mailbox, mSession);
+        } catch (BadCredentialsException e) {
+            return null;
+        } catch (MailboxException e) {
+            throw new IOException("Unable to access mailbox for user " + session.getUser(), e);
+        } finally {
+            if (mSession != null) {
+                manager.endProcessingRequest(mSession);
+            }
+        }
+
+    }
+    
+    private static String getMD5(final String input) {
+        try {
+            final MessageDigest md = MessageDigest.getInstance("MD5");
+            final byte[] messageDigest = md.digest(input.getBytes());
+            final BigInteger number = new BigInteger(1, messageDigest);
+            String hashtext = number.toString(16);
+            // Now we need to zero pad it if you actually want the full 32 chars.
+            while (hashtext.length() < 32) {
+                hashtext = "0" + hashtext;
+            }
+            return hashtext;
+        }
+        catch (final NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/MailServer.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/MailServer.java
new file mode 100644
index 0000000..b73d6f4
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/MailServer.java
@@ -0,0 +1,590 @@
+/**
+ *  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.testserver;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+
+import javax.mail.Flags;
+import javax.mail.internet.MimeMessage;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.apache.commons.configuration.DefaultConfigurationBuilder;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.domainlist.api.DomainListException;
+import org.apache.james.domainlist.api.mock.SimpleDomainList;
+import org.apache.james.filesystem.api.mock.MockFileSystem;
+import org.apache.james.imap.encode.main.DefaultImapEncoderFactory;
+import org.apache.james.imap.encode.main.DefaultLocalizer;
+import org.apache.james.imap.main.DefaultImapDecoderFactory;
+import org.apache.james.imap.processor.main.DefaultImapProcessorFactory;
+import org.apache.james.imapserver.netty.IMAPServer;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.acl.GroupMembershipResolver;
+import org.apache.james.mailbox.acl.MailboxACLResolver;
+import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver;
+import org.apache.james.mailbox.acl.UnionMailboxACLResolver;
+import org.apache.james.mailbox.inmemory.InMemoryMailboxSessionMapperFactory;
+import org.apache.james.mailbox.model.MailboxConstants;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.store.Authenticator;
+import org.apache.james.mailbox.store.StoreMailboxManager;
+import org.apache.james.mailrepository.mock.MockMailRepositoryStore;
+import org.apache.james.pop3server.netty.POP3Server;
+import org.apache.james.protocols.lib.PortUtil;
+import org.apache.james.protocols.lib.mock.MockProtocolHandlerLoader;
+import org.apache.james.queue.api.MailQueue;
+import org.apache.james.queue.api.MailQueue.MailQueueItem;
+import org.apache.james.queue.api.MailQueueFactory;
+import org.apache.james.queue.file.FileMailQueueFactory;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.smtpserver.netty.SMTPServer;
+import org.apache.james.user.api.UsersRepositoryException;
+import org.apache.james.user.lib.mock.MockUsersRepository;
+import org.apache.mailet.HostAddress;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+//James based POP3 or IMAP or SMTP server (for unittesting only)
+public class MailServer {
+
+    private POP3Server pop3Server;
+    private IMAPServer imapServer;
+    private SMTPServer smtpServer;
+    private AlterableDNSServer dnsServer;
+    private final MockUsersRepository usersRepository = new MockUsersRepository();
+    private final MockFileSystem fileSystem = new MockFileSystem();
+    private MockProtocolHandlerLoader protocolHandlerChain;
+    private StoreMailboxManager<Long> mailboxManager;
+
+    private MockMailRepositoryStore store;
+    private DNSService dnsService;
+    private MailQueueFactory queueFactory;
+    private MailQueue queue;
+    private final Semaphore sem = new Semaphore(0);
+    private final Logger log = LoggerFactory.getLogger("Mock");
+
+    public void ensureMsgCount(final int count) throws InterruptedException {
+        sem.acquire(count);
+    }
+
+    private class Fetcher extends Thread {
+
+        private final MailQueue queue;
+        private final MessageManager mailbox;
+        private final MailboxSession session;
+
+        Fetcher(final MailQueue queue, final MessageManager mailbox, final MailboxSession session) {
+            super();
+            this.queue = queue;
+            this.mailbox = mailbox;
+            this.session = session;
+        }
+
+        @Override
+        public void run() {
+            while (true) {
+                try {
+                    System.out.println("Await new mail ...");
+                    final MailQueueItem item = queue.deQueue();
+                    System.out.println("got it");
+                    final MimeMessage msg = item.getMail().getMessage();
+                    final ByteArrayOutputStream bout = new ByteArrayOutputStream();
+                    msg.writeTo(bout);
+                    mailbox.appendMessage(new ByteArrayInputStream(bout.toByteArray()), new Date(), session, true, new Flags());
+                    item.done(true);
+                    sem.release();
+                    System.out.println("mail copied over");
+                } catch (final Exception e) {
+                    e.printStackTrace();
+                    return;
+                }
+            }
+        }
+
+    }
+
+    public MailServer() {
+        super();
+        try {
+            usersRepository.addUser("serveruser", "serverpass");
+        } catch (final UsersRepositoryException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    public void start(final SmtpTestConfiguration smtpConfig, final Pop3TestConfiguration pop3Config, final ImapTestConfiguration imapConfig)
+            throws Exception {
+        setUpServiceManager();
+
+        imapServer = new IMAPServer();
+
+        imapServer.setImapEncoder(DefaultImapEncoderFactory.createDefaultEncoder(new DefaultLocalizer(), false));
+        imapServer.setImapDecoder(DefaultImapDecoderFactory.createDecoder());
+
+        pop3Server = new POP3Server();
+        pop3Server.setProtocolHandlerLoader(protocolHandlerChain);
+
+        smtpServer = new SMTPServer() {
+            @Override
+            protected java.lang.Class<? extends org.apache.james.protocols.lib.handler.HandlersPackage> getJMXHandlersPackage() {
+                return RefinedJMXHandlersLoader.class;
+            };
+
+        };
+        smtpServer.setProtocolHandlerLoader(protocolHandlerChain);
+        smtpServer.setDNSService(dnsServer);
+
+        imapServer.setFileSystem(fileSystem);
+        pop3Server.setFileSystem(fileSystem);
+        smtpServer.setFileSystem(fileSystem);
+
+        imapServer.setLog(log);
+        pop3Server.setLog(log);
+        smtpServer.setLog(log);
+
+        final MailboxPath mailboxPath = new MailboxPath(MailboxConstants.USER_NAMESPACE, "serveruser", "INBOX");
+        final MailboxSession session = mailboxManager.login("serveruser", "serverpass", LoggerFactory.getLogger("Test"));
+
+        if (!mailboxManager.mailboxExists(mailboxPath, session)) {
+            mailboxManager.createMailbox(mailboxPath, session);
+        }
+
+        imapServer.setImapProcessor(DefaultImapProcessorFactory.createXListSupportingProcessor(mailboxManager, null, null));//new StoreSubscriptionManager(new InMemoryMailboxSessionMapperFactory()), null));
+
+        //setupTestMails(session, mailboxManager.getMailbox(mailboxPath, session));
+
+        new Fetcher(queue, mailboxManager.getMailbox(mailboxPath, session), session).start();
+
+        smtpConfig.init();
+        pop3Config.init();
+        imapConfig.init();
+
+        smtpServer.configure(smtpConfig);
+        pop3Server.configure(pop3Config);
+        imapServer.configure(imapConfig);
+
+        smtpServer.init();
+        pop3Server.init();
+        imapServer.init();
+
+    }
+
+    public void stop() throws Exception {
+
+        if (protocolHandlerChain != null) {
+            protocolHandlerChain.dispose();
+        }
+
+        if (imapServer != null) {
+            imapServer.destroy();
+        }
+
+        if (pop3Server != null) {
+            pop3Server.destroy();
+        }
+
+        if (smtpServer != null) {
+            smtpServer.destroy();
+        }
+
+    }
+
+    /* protected void setupTestMailsx(MailboxSession session, MessageManager mailbox) throws MailboxException {
+         mailbox.appendMessage(new ByteArrayInputStream(content), new Date(), session, true, new Flags());
+         byte[] content2 = ("EMPTY").getBytes();
+         mailbox.appendMessage(new ByteArrayInputStream(content2), new Date(), session, true, new Flags());
+     }*/
+
+    protected void setUpServiceManager() throws Exception {
+        protocolHandlerChain = new MockProtocolHandlerLoader();
+        protocolHandlerChain.put("usersrepository", usersRepository);
+
+        final InMemoryMailboxSessionMapperFactory factory = new InMemoryMailboxSessionMapperFactory();
+        final MailboxACLResolver aclResolver = new UnionMailboxACLResolver();
+        final GroupMembershipResolver groupMembershipResolver = new SimpleGroupMembershipResolver();
+        mailboxManager = new StoreMailboxManager<Long>(factory, new Authenticator() {
+
+            public boolean isAuthentic(final String userid, final CharSequence passwd) {
+                try {
+                    return usersRepository.test(userid, passwd.toString());
+                } catch (final UsersRepositoryException e) {
+                    e.printStackTrace();
+                    return false;
+                }
+            }
+        }, aclResolver, groupMembershipResolver);
+        mailboxManager.init();
+
+        protocolHandlerChain.put("mailboxmanager", mailboxManager);
+
+        protocolHandlerChain.put("fileSystem", fileSystem);
+
+        //smtp
+        dnsServer = new AlterableDNSServer();
+        store = new MockMailRepositoryStore();
+        protocolHandlerChain.put("mailStore", store);
+        protocolHandlerChain.put("dnsservice", dnsServer);
+        protocolHandlerChain.put("org.apache.james.smtpserver.protocol.DNSService", dnsService);
+
+        protocolHandlerChain.put("recipientrewritetable", new RecipientRewriteTable() {
+
+            public void addRegexMapping(final String user, final String domain, final String regex) throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public void removeRegexMapping(final String user, final String domain, final String regex)
+                    throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public void addAddressMapping(final String user, final String domain, final String address)
+                    throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public void removeAddressMapping(final String user, final String domain, final String address)
+                    throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public void addErrorMapping(final String user, final String domain, final String error) throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public void removeErrorMapping(final String user, final String domain, final String error)
+                    throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public Collection<String> getUserDomainMappings(final String user, final String domain) throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public void addMapping(final String user, final String domain, final String mapping) throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public void removeMapping(final String user, final String domain, final String mapping) throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public Map<String, Collection<String>> getAllMappings() throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public void addAliasDomainMapping(final String aliasDomain, final String realDomain) throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public void removeAliasDomainMapping(final String aliasDomain, final String realDomain) throws RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+
+            public Collection<String> getMappings(final String user, final String domain) throws ErrorMappingException,
+            RecipientRewriteTableException {
+                throw new UnsupportedOperationException("Not implemented");
+            }
+        });
+
+        protocolHandlerChain.put("org.apache.james.smtpserver.protocol.DNSService", dnsService);
+
+        final FileMailQueueFactory ff = new FileMailQueueFactory();// MockMailQueueFactory();
+        ff.setLog(log);
+        ff.setFileSystem(fileSystem);
+        queueFactory = ff;
+
+        queue = queueFactory.getQueue(MailQueueFactory.SPOOL);
+        protocolHandlerChain.put("mailqueuefactory", queueFactory);
+        protocolHandlerChain.put("domainlist", new SimpleDomainList() {
+
+            @Override
+            public String getDefaultDomain() {
+                return "localhost";
+            }
+
+            @Override
+            public String[] getDomains() throws DomainListException {
+                return new String[] { "localhost" };
+            }
+
+            @Override
+            public boolean containsDomain(final String serverName) {
+                return "localhost".equals(serverName);
+            }
+        });
+
+    }
+
+    /**
+     * @return the queue
+     */
+    public MailQueue getQueue() {
+        return queue;
+    }
+
+    public static File getAbsoluteFilePathFromClassPath(final String fileNameFromClasspath) throws FileNotFoundException {
+
+        File configFile = null;
+        final URL configURL = MailServer.class.getClassLoader().getResource(fileNameFromClasspath);
+        if (configURL != null) {
+            try {
+                configFile = new File(configURL.toURI());
+            } catch (URISyntaxException e) {
+                configFile = new File(configURL.getPath());
+            }
+
+            //Java 7 only
+            /*if(!configFile.exists()) {
+                try {
+                    configFile = Paths.get(configURL.toURI()).toFile();
+                } catch (URISyntaxException e) {
+                    throw new FileNotFoundException("Failed to load " + fileNameFromClasspath+ " due to "+e);
+                }
+            }*/
+
+            if (configFile.exists()) {
+                return configFile;
+            } else {
+                throw new FileNotFoundException("Cannot read from "+configFile.getAbsolutePath()+" (original resource was "+fileNameFromClasspath+", URL: "+configURL+"), because the file does not exist");
+            }
+            
+        } else {
+            throw new FileNotFoundException("Failed to load " + fileNameFromClasspath+", because resource cannot be found within the classpath");
+        }
+
+    }
+
+    public static abstract class AbstractTestConfiguration extends DefaultConfigurationBuilder {
+
+        private final int listenerPort = PortUtil.getNonPrivilegedPort();
+
+        /**
+         * @return the listenerPort
+         */
+        public int getListenerPort() {
+            return listenerPort;
+        }
+
+        public AbstractTestConfiguration enableSSL(final boolean enableStartTLS, final boolean enableSSL) throws FileNotFoundException {
+            addProperty("tls.[@startTLS]", enableStartTLS);
+            addProperty("tls.[@socketTLS]", enableSSL);
+            addProperty("tls.keystore", "file://" + getAbsoluteFilePathFromClassPath("dummykeystore.jks").getAbsolutePath());
+            addProperty("tls.secret", "123456");
+            addProperty("tls.provider", "org.bouncycastle.jce.provider.BouncyCastleProvider");
+            return this;
+        }
+
+        public void init() {
+            addProperty("[@enabled]", true);
+            addProperty("bind", "127.0.0.1:" + this.listenerPort);
+            addProperty("connectiontimeout", "360000");
+            //addProperty("jmxName", getServertype().name()+"on"+this.listenerPort);
+            addProperty("helloName", "jamesserver");
+            addProperty("helloName.[@autodetect]", false);
+        }
+
+    }
+
+    public static class Pop3TestConfiguration extends AbstractTestConfiguration {
+
+        @Override
+        public void init() {
+            super.init();
+
+            addProperty("helloName", "pop3 on port " + getListenerPort());
+
+            addProperty("handlerchain.[@coreHandlersPackage]", RefinedCoreCmdHandlerLoader.class.getName());
+
+        }
+
+    }
+
+    public static class ImapTestConfiguration extends AbstractTestConfiguration {
+
+        @Override
+        public void init() {
+            super.init();
+
+            addProperty("helloName", "imap on port " + getListenerPort());
+
+        }
+
+    }
+
+    public static class SmtpTestConfiguration extends AbstractTestConfiguration {
+
+        @Override
+        public void init() {
+            super.init();
+            addProperty("handlerchain.handler[@class]", RefinedSmtpCoreCmdHandlerLoader.class.getName());
+
+        }
+
+        public SmtpTestConfiguration setRequireAuth(final boolean requireAuth) {
+
+            addProperty("authRequired", requireAuth);
+            return this;
+        }
+
+        public SmtpTestConfiguration setHeloEhloEnforcement(final boolean heloEhloEnforcement) {
+
+            addProperty("heloEhloEnforcement", heloEhloEnforcement);
+            return this;
+        }
+
+    }
+
+    public static class DummySocketFactory extends SSLSocketFactory {
+
+        @Override
+        public Socket createSocket(final String host, final int port) throws IOException, UnknownHostException {
+            throw new IOException("dummy socket factory");
+        }
+
+        @Override
+        public Socket createSocket(final InetAddress host, final int port) throws IOException {
+            throw new IOException("dummy socket factory");
+        }
+
+        @Override
+        public Socket createSocket(final String host, final int port, final InetAddress localHost, final int localPort) throws IOException,
+                UnknownHostException {
+            throw new IOException("dummy socket factory");
+        }
+
+        @Override
+        public Socket createSocket(final InetAddress address, final int port, final InetAddress localAddress, final int localPort)
+                throws IOException {
+            throw new IOException("dummy socket factory");
+        }
+
+        @Override
+        public Socket createSocket(final Socket arg0, final String arg1, final int arg2, final boolean arg3) throws IOException {
+            throw new IOException("dummy socket factory");
+        }
+
+        @Override
+        public String[] getDefaultCipherSuites() {
+            return new String[0];
+        }
+
+        @Override
+        public String[] getSupportedCipherSuites() {
+            return new String[0];
+        }
+
+    }
+
+    private final class AlterableDNSServer implements DNSService {
+
+        private InetAddress localhostByName = null;
+
+        public Collection<String> findMXRecords(final String hostname) {
+            final List<String> res = new ArrayList<String>();
+            if (hostname == null) {
+                return res;
+            }
+            if ("james.apache.org".equals(hostname)) {
+                res.add("nagoya.apache.org");
+            }
+            return res;
+        }
+
+        public Iterator<HostAddress> getSMTPHostAddresses(final String domainName) {
+            throw new UnsupportedOperationException("Unimplemented mock service");
+        }
+
+        public InetAddress[] getAllByName(final String host) throws UnknownHostException {
+            return new InetAddress[] { getByName(host) };
+        }
+
+        public InetAddress getByName(final String host) throws UnknownHostException {
+            if (getLocalhostByName() != null) {
+                if ("127.0.0.1".equals(host)) {
+                    return getLocalhostByName();
+                }
+            }
+
+            if ("0.0.0.0".equals(host)) {
+                return InetAddress.getByName("0.0.0.0");
+            }
+
+            if ("james.apache.org".equals(host)) {
+                return InetAddress.getByName("james.apache.org");
+            }
+
+            if ("abgsfe3rsf.de".equals(host)) {
+                throw new UnknownHostException();
+            }
+
+            if ("128.0.0.1".equals(host) || "192.168.0.1".equals(host) || "127.0.0.1".equals(host) || "127.0.0.0".equals(host)
+                    || "255.0.0.0".equals(host) || "255.255.255.255".equals(host)) {
+                return InetAddress.getByName(host);
+            }
+
+            throw new UnsupportedOperationException("getByName not implemented in mock for host: " + host);
+        }
+
+        public Collection<String> findTXTRecords(final String hostname) {
+            final List<String> res = new ArrayList<String>();
+            if (hostname == null) {
+                return res;
+            }
+
+            if ("2.0.0.127.bl.spamcop.net.".equals(hostname)) {
+                res.add("Blocked - see http://www.spamcop.net/bl.shtml?127.0.0.2");
+            }
+            return res;
+        }
+
+        public InetAddress getLocalhostByName() {
+            return localhostByName;
+        }
+
+        public void setLocalhostByName(final InetAddress localhostByName) {
+            this.localhostByName = localhostByName;
+        }
+
+        public String getHostName(final InetAddress addr) {
+            return addr.getHostName();
+        }
+
+        public InetAddress getLocalHost() throws UnknownHostException {
+            return InetAddress.getLocalHost();
+        }
+    }
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/RefinedCoreCmdHandlerLoader.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/RefinedCoreCmdHandlerLoader.java
new file mode 100644
index 0000000..d2c0e9b
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/RefinedCoreCmdHandlerLoader.java
@@ -0,0 +1,31 @@
+/**
+ *  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.testserver;
+
+
+
+public class RefinedCoreCmdHandlerLoader extends org.apache.james.pop3server.core.CoreCmdHandlerLoader {
+
+    public RefinedCoreCmdHandlerLoader() {
+        super();
+        getHandlers().add(ApopCmdHandler.class.getName());
+        
+    }
+    
+    
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/RefinedJMXHandlersLoader.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/RefinedJMXHandlersLoader.java
new file mode 100644
index 0000000..32fd915
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/RefinedJMXHandlersLoader.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.testserver;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.james.protocols.lib.handler.HandlersPackage;
+import org.apache.james.smtpserver.jmx.CommandHandlerResultJMXMonitor;
+import org.apache.james.smtpserver.jmx.ConnectHandlerResultJMXMonitor;
+import org.apache.james.smtpserver.jmx.HookResultJMXMonitor;
+//import org.apache.james.smtpserver.jmx.LineHandlerResultJMXMonitor;
+
+public class RefinedJMXHandlersLoader implements HandlersPackage {
+
+    private final List<String> handlers = new ArrayList<String>();
+
+    public RefinedJMXHandlersLoader() {
+        handlers.add(ConnectHandlerResultJMXMonitor.class.getName());
+        handlers.add(CommandHandlerResultJMXMonitor.class.getName());
+        //handlers.add(LineHandlerResultJMXMonitor.class.getName());
+        handlers.add(HookResultJMXMonitor.class.getName());
+    }
+
+    /**
+     * @see org.apache.james.protocols.api.handler.HandlersPackage#getHandlers()
+     */
+    public List<String> getHandlers() {
+        return handlers;
+    }
+
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/RefinedSmtpCoreCmdHandlerLoader.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/RefinedSmtpCoreCmdHandlerLoader.java
new file mode 100644
index 0000000..ba7f59d
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/testserver/RefinedSmtpCoreCmdHandlerLoader.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.testserver;
+
+ /****************************************************************
+ * 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.                                           *
+ ****************************************************************/
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.james.protocols.api.handler.CommandDispatcher;
+import org.apache.james.protocols.api.handler.CommandHandlerResultLogger;
+import org.apache.james.protocols.lib.handler.HandlersPackage;
+import org.apache.james.protocols.smtp.core.ExpnCmdHandler;
+import org.apache.james.protocols.smtp.core.HeloCmdHandler;
+import org.apache.james.protocols.smtp.core.HelpCmdHandler;
+import org.apache.james.protocols.smtp.core.NoopCmdHandler;
+import org.apache.james.protocols.smtp.core.PostmasterAbuseRcptHook;
+import org.apache.james.protocols.smtp.core.QuitCmdHandler;
+import org.apache.james.protocols.smtp.core.ReceivedDataLineFilter;
+import org.apache.james.protocols.smtp.core.RsetCmdHandler;
+import org.apache.james.protocols.smtp.core.VrfyCmdHandler;
+import org.apache.james.protocols.smtp.core.esmtp.AuthCmdHandler;
+import org.apache.james.protocols.smtp.core.esmtp.EhloCmdHandler;
+import org.apache.james.protocols.smtp.core.esmtp.MailSizeEsmtpExtension;
+import org.apache.james.protocols.smtp.core.esmtp.StartTlsCmdHandler;
+import org.apache.james.protocols.smtp.core.log.HookResultLogger;
+import org.apache.james.smtpserver.AddDefaultAttributesMessageHook;
+import org.apache.james.smtpserver.AuthRequiredToRelayRcptHook;
+import org.apache.james.smtpserver.DataLineJamesMessageHookHandler;
+import org.apache.james.smtpserver.JamesDataCmdHandler;
+import org.apache.james.smtpserver.JamesMailCmdHandler;
+import org.apache.james.smtpserver.JamesRcptCmdHandler;
+import org.apache.james.smtpserver.JamesWelcomeMessageHandler;
+import org.apache.james.smtpserver.SendMailHandler;
+import org.apache.james.smtpserver.SenderAuthIdentifyVerificationRcptHook;
+import org.apache.james.smtpserver.UsersRepositoryAuthHook;
+
+/**
+ * This class represent the base command handlers which are shipped with james.
+ */
+public class RefinedSmtpCoreCmdHandlerLoader implements HandlersPackage {
+
+    private final String COMMANDDISPATCHER = CommandDispatcher.class.getName();
+    private final String AUTHCMDHANDLER = AuthCmdHandler.class.getName();
+    private final String DATACMDHANDLER = JamesDataCmdHandler.class.getName();
+    private final String EHLOCMDHANDLER = EhloCmdHandler.class.getName();
+    private final String EXPNCMDHANDLER = ExpnCmdHandler.class.getName();
+    private final String HELOCMDHANDLER = HeloCmdHandler.class.getName();
+    private final String HELPCMDHANDLER = HelpCmdHandler.class.getName();
+    private final String MAILCMDHANDLER = JamesMailCmdHandler.class.getName();
+    private final String NOOPCMDHANDLER = NoopCmdHandler.class.getName();
+    private final String QUITCMDHANDLER = QuitCmdHandler.class.getName();
+    private final String RCPTCMDHANDLER = JamesRcptCmdHandler.class.getName();
+    private final String RSETCMDHANDLER = RsetCmdHandler.class.getName();
+    private final String VRFYCMDHANDLER = VrfyCmdHandler.class.getName();
+    private final String MAILSIZEHOOK = MailSizeEsmtpExtension.class.getName();
+    private final String WELCOMEMESSAGEHANDLER = JamesWelcomeMessageHandler.class.getName();
+    private final String USERSREPOSITORYAUTHHANDLER = UsersRepositoryAuthHook.class.getName();
+    private final String POSTMASTERABUSEHOOK = PostmasterAbuseRcptHook.class.getName();
+    private final String AUTHREQUIREDTORELAY = AuthRequiredToRelayRcptHook.class.getName();
+    private final String SENDERAUTHIDENTITYVERIFICATION = SenderAuthIdentifyVerificationRcptHook.class.getName();
+    private final String RECEIVEDDATALINEFILTER = ReceivedDataLineFilter.class.getName();
+    private final String DATALINEMESSAGEHOOKHANDLER = DataLineJamesMessageHookHandler.class.getName();
+    private final String STARTTLSHANDLER = StartTlsCmdHandler.class.getName();
+
+    // MessageHooks
+    private final String ADDDEFAULTATTRIBUTESHANDLER = AddDefaultAttributesMessageHook.class.getName();
+    private final String SENDMAILHANDLER = SendMailHandler.class.getName();
+
+    // logging stuff
+    private final String COMMANDHANDLERRESULTLOGGER = CommandHandlerResultLogger.class.getName();
+    private final String HOOKRESULTLOGGER = HookResultLogger.class.getName();
+
+    private final List<String> commands = new LinkedList<String>();
+
+    public RefinedSmtpCoreCmdHandlerLoader() {
+        // Insert the base commands in the Map
+        commands.add(WELCOMEMESSAGEHANDLER);
+        commands.add(COMMANDDISPATCHER);
+        commands.add(AUTHCMDHANDLER);
+        commands.add(DATACMDHANDLER);
+        commands.add(EHLOCMDHANDLER);
+        commands.add(EXPNCMDHANDLER);
+        commands.add(HELOCMDHANDLER);
+        commands.add(HELPCMDHANDLER);
+        commands.add(MAILCMDHANDLER);
+        commands.add(NOOPCMDHANDLER);
+        commands.add(QUITCMDHANDLER);
+        commands.add(RCPTCMDHANDLER);
+        commands.add(RSETCMDHANDLER);
+        commands.add(VRFYCMDHANDLER);
+        commands.add(MAILSIZEHOOK);
+        commands.add(USERSREPOSITORYAUTHHANDLER);
+        commands.add(AUTHREQUIREDTORELAY);
+        commands.add(SENDERAUTHIDENTITYVERIFICATION);
+        commands.add(POSTMASTERABUSEHOOK);
+        commands.add(RECEIVEDDATALINEFILTER);
+        commands.add(DATALINEMESSAGEHOOKHANDLER);
+        commands.add(STARTTLSHANDLER);
+        // Add the default messageHooks
+        commands.add(ADDDEFAULTATTRIBUTESHANDLER);
+        commands.add(SENDMAILHANDLER);
+
+        // Add logging stuff
+        commands.add(COMMANDHANDLERRESULTLOGGER);
+        commands.add(HOOKRESULTLOGGER);
+    }
+
+    /**
+     * @see org.apache.james.protocols.api.handler.HandlersPackage#getHandlers()
+     */
+    public List<String> getHandlers() {
+        return commands;
+    }
+}
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransportTest.java b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransportTest.java
new file mode 100644
index 0000000..d0f9ead
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransportTest.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.transport.smtp;
+
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.geronimo.javamail.testserver.AbstractProtocolTest;
+
+public class SMTPTransportTest extends AbstractProtocolTest {
+
+    public void testSSLEnable() throws Exception {
+
+        
+        smtpConf.enableSSL(false, false);
+
+        start();
+
+        Properties props = new Properties();
+        props.setProperty("mail.transport.protocol", "smtp");
+        props.setProperty("mail.smtp.port", String.valueOf(smtpConf.getListenerPort()));
+        props.setProperty("mail.debug", "true");
+
+        Session jmsession = Session.getInstance(props);
+        Transport t = jmsession.getTransport();
+        t.connect();
+        
+        MimeMessage msg = new MimeMessage(jmsession);
+        msg.setFrom(new InternetAddress("test@apache.org"));
+        msg.setSubject("Hi!");
+        msg.setText("All your base are belong to us");
+        
+        
+        t.sendMessage(msg, new InternetAddress[]{new InternetAddress("testto@apache.org")});
+
+    }
+
+
+}
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/dummykeystore.jks b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/dummykeystore.jks
new file mode 100644
index 0000000..cb140cd
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/dummykeystore.jks
Binary files differ
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/imap/multipart.bodystructure b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/imap/multipart.bodystructure
new file mode 100644
index 0000000..e406923
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/imap/multipart.bodystructure
@@ -0,0 +1 @@
+(("TEXT" "PLAIN" ("CHARSET" "ISO-8859-1") NIL NIL "7BIT" 1281 28 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "ISO-8859-1") NIL NIL "7BIT" 1510 33 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "0016e6d976ed8989d30464e986d1") NIL NIL)
\ No newline at end of file
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/messages/multipart.msg b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/messages/multipart.msg
new file mode 100644
index 0000000..8fbc2af
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/messages/multipart.msg
@@ -0,0 +1,19 @@
+Date: Sat, 11 Oct 2008 00:48:01 +0200 (CEST)
+From: from@localhost
+To: serveruser@localhost
+Message-ID: urn:uuid:219365EB848AD9CACB1223678880948
+Subject: Test
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="----=_Part_0_6727097.1223678881682"
+
+------=_Part_0_6727097.1223678881682
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+
+First part
+------=_Part_0_6727097.1223678881682
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+
+Second part
+------=_Part_0_6727097.1223678881682--
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/messages/simple.msg b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/messages/simple.msg
new file mode 100644
index 0000000..9e429ed
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/messages/simple.msg
@@ -0,0 +1,11 @@
+Date: Sat, 11 Oct 2008 00:48:01 +0200 (CEST)
+From: from@localhost
+To: serveruser@localhost
+Subject: Test Foo
+MIME-Version: 1.0
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+
+Foo Bar
+
+
diff --git a/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/pdf-test.pdf b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/pdf-test.pdf
new file mode 100644
index 0000000..8671636
--- /dev/null
+++ b/geronimo-javamail_1.5/geronimo-javamail_1.4_provider/src/test/resources/pdf-test.pdf
Binary files differ
diff --git a/geronimo-javamail_1.5/pom.xml b/geronimo-javamail_1.5/pom.xml
new file mode 100644
index 0000000..a1231ab
--- /dev/null
+++ b/geronimo-javamail_1.5/pom.xml
@@ -0,0 +1,273 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+
+<!-- $Rev$ $Date: 2014-07-20 09:36:35 +0200 (So, 20. Jul 2014) 
+    $ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.geronimo.genesis</groupId>
+        <artifactId>genesis-java5-flava</artifactId>
+        <version>2.2</version>
+    </parent>
+
+    <groupId>org.apache.geronimo.javamail</groupId>
+    <artifactId>geronimo-javamail_1.4</artifactId>
+    <name>Geronimo JavaMail 1.4</name>
+    <packaging>pom</packaging>
+
+    <version>1.9.0-SNAPSHOT</version>
+
+    <description>
+        Geronimmo JavaMail Bundle.
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/geronimo/javamail/trunk/geronimo-javamail_1.4</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/geronimo/javamail/trunk/geronimo-javamail_1.4</developerConnection>
+        <url>http://svn.apache.org/viewvc/geronimo/javamail/trunk/geronimo-javamail_1.4</url>
+    </scm>
+
+    <properties>
+        <siteId>javamail/${project.artifactId}</siteId>
+        <projectName>Apache Geronimo Javamail Bundle</projectName>
+    </properties>
+
+    <url>http://geronimo.apache.org/maven/${siteId}/${project.version}</url>
+
+    <distributionManagement>
+        <site>
+            <id>apache-website</id>
+            <url>${site.deploy.url}/maven/${siteId}/${project.version}</url>
+        </site>
+    </distributionManagement>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.apache.geronimo.specs</groupId>
+                <artifactId>geronimo-activation_1.1_spec</artifactId>
+                <version>1.1</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.geronimo.specs</groupId>
+                <artifactId>geronimo-javamail_1.4_spec</artifactId>
+                <version>1.7.2-alpha-1</version>
+            </dependency>
+
+            <dependency>
+                <groupId>junit</groupId>
+                <artifactId>junit</artifactId>
+                <version>3.8.2</version>
+            </dependency>
+
+            <!-- INTERNAL -->
+
+            <dependency>
+                <groupId>org.apache.geronimo.javamail</groupId>
+                <artifactId>geronimo-javamail_1.4_provider</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.geronimo.specs</groupId>
+                <artifactId>geronimo-osgi-locator</artifactId>
+                <version>1.0</version>
+                <scope>provided</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-shade-plugin</artifactId>
+                    <version>2.3</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>2.18</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-site-plugin</artifactId>
+                    <version>3.3</version>
+                    <configuration>
+                        <stagingDirectory>${staging.directory}</stagingDirectory>
+                    </configuration>
+                    <dependencies>
+                        <dependency>
+                            <groupId>org.apache.maven.doxia</groupId>
+                            <artifactId>doxia-module-markdown</artifactId>
+                            <version>1.3</version>
+                        </dependency>
+                    </dependencies>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+
+        <plugins>
+
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <includes>
+                        <include>src/**/*</include>
+                        <include>pom.xml</include>
+                    </includes>
+                    <excludes>
+                        <exclude>**/*/MANIFEST.MF</exclude>
+                        <exclude>.git</exclude>
+                        <exclude>.gitignore</exclude>
+                        <exclude>.idea</exclude>
+                        <exclude>*.iws</exclude>
+                        <exclude>*.iml</exclude>
+                        <exclude>*.ipr</exclude>
+                        <exclude>**/src/test/resources/**/*.bodystructure</exclude>
+                        <exclude>**/src/test/resources/**/*.msg</exclude>
+                        <exclude>**/resources/OSGI-INF/providers/**/*</exclude>
+                    </excludes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <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 -->
+                                <requireJavaVersion>
+                                    <version>[1.5,)</version>
+                                </requireJavaVersion>
+
+                                <!-- Allow any Maven >= 2.0.7 -->
+                                <requireMavenVersion>
+                                    <version>[2.0.7,)</version>
+                                </requireMavenVersion>
+                            </rules>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <modules>
+        <module>geronimo-javamail_1.4_provider</module>
+        <module>geronimo-javamail_1.4_mail</module>
+    </modules>
+
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>findbugs-maven-plugin</artifactId>
+                <version>3.0.0</version>
+                <configuration>
+                    <xmlOutput>true</xmlOutput>
+                    <!-- Optional directory to put findbugs xdoc xml report -->
+                    <xmlOutputDirectory>target/site</xmlOutputDirectory>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-pmd-plugin</artifactId>
+                <version>3.2</version>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-project-info-reports-plugin</artifactId>
+                <version>2.7</version>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.9.1</version>
+                <configuration>
+                    <notimestamp>true</notimestamp>
+                    <show>private</show>
+                </configuration>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>javadoc</report>
+                        </reports>
+                    </reportSet>
+                    <reportSet>
+                        <inherited>false</inherited>
+                        <reports>
+                            <report>aggregate</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-report-plugin</artifactId>
+                <version>2.18</version>
+                <configuration>
+                    <aggregate>true</aggregate>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>cobertura-maven-plugin</artifactId>
+                <version>2.6</version>
+                <configuration>
+                    <formats>
+                        <format>html</format>
+                    </formats>
+                    <aggregate>true</aggregate>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>versions-maven-plugin</artifactId>
+                <version>2.1</version>
+                <reportSets>
+                    <reportSet>
+                        <reports>
+                            <report>dependency-updates-report</report>
+                            <report>plugin-updates-report</report>
+                            <report>property-updates-report</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
+        </plugins>
+    </reporting>
+
+</project>
diff --git a/geronimo-javamail_1.5/src/site/apt/privacy-policy.apt b/geronimo-javamail_1.5/src/site/apt/privacy-policy.apt
new file mode 100644
index 0000000..b842f21
--- /dev/null
+++ b/geronimo-javamail_1.5/src/site/apt/privacy-policy.apt
@@ -0,0 +1,52 @@
+ ----
+ Privacy Policy
+ -----
+ Olivier Lamy
+ -----
+ 2013-11-13
+ -----
+
+~~ 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.
+
+Privacy Policy
+
+  Information about your use of this website is collected using server access logs and a tracking cookie. The 
+  collected information consists of the following:
+
+  [[1]] The IP address from which you access the website;
+  
+  [[2]] The type of browser and operating system you use to access our site;
+  
+  [[3]] The date and time you access our site;
+  
+  [[4]] The pages you visit; and
+  
+  [[5]] The addresses of pages from where you followed a link to our site.
+
+  []
+
+  Part of this information is gathered using a tracking cookie set by the 
+  {{{http://www.google.com/analytics/}Google Analytics}} service and handled by Google as described in their 
+  {{{http://www.google.com/privacy.html}privacy policy}}. See your browser documentation for instructions on how to 
+  disable the cookie if you prefer not to share this data with Google.
+
+  We use the gathered information to help us make our site more useful to visitors and to better understand how and 
+  when our site is used. We do not track or collect personally identifiable information or associate gathered data 
+  with any personally identifying information from other sources.
+
+  By using this website, you consent to the collection of this data in the manner and for the purpose described above.
diff --git a/geronimo-javamail_1.5/src/site/markdown/index.md b/geronimo-javamail_1.5/src/site/markdown/index.md
new file mode 100644
index 0000000..9f1e5b9
--- /dev/null
+++ b/geronimo-javamail_1.5/src/site/markdown/index.md
@@ -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.
+-->
+# Geronimo JavaMail 1.4
+
+Geronimo JavaMail 1.4
+
+## Get started
+
+Just get it from maven
+
+### Core
+
+<pre class="prettyprint linenums"><![CDATA[
+<dependency>
+	<groupId>org.apache.geronimo.javamail</groupId>
+	<artifactId>geronimo-javamail_1.4_provider</artifactId>
+	<version>1.9.0-SNAPSHOT</version>
+</dependency>
+]]></pre>
\ No newline at end of file
diff --git a/geronimo-javamail_1.5/src/site/site.xml b/geronimo-javamail_1.5/src/site/site.xml
new file mode 100644
index 0000000..272648e
--- /dev/null
+++ b/geronimo-javamail_1.5/src/site/site.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ 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.
+-->
+<project name="Apache Johnzon"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/DECORATION/1.0.1"
+         xsi:schemaLocation="http://maven.apache.org/DECORATION/1.0.1
+                             http://maven.apache.org/xsd/decoration-1.0.1.xsd">
+  <bannerLeft>
+    <name>Geronimo JavaMail</name>
+    <alt>Geronimo JavaMail</alt>
+    <href>/index.html</href>
+  </bannerLeft>
+  <bannerRight>
+    <src>http://geronimo.apache.org/images/topleft_logo_437x64.gif</src>
+    <href>http://geronimo.apache.org/</href>
+  </bannerRight>
+
+  <custom>
+    <fluidoSkin>
+      <topBarEnabled>true</topBarEnabled>
+      <sideBarEnabled>true</sideBarEnabled>
+      <sourceLineNumbersEnabled>true</sourceLineNumbersEnabled>
+    </fluidoSkin>
+  </custom>
+
+  <skin>
+    <groupId>org.apache.maven.skins</groupId>
+    <artifactId>maven-fluido-skin</artifactId>
+    <version>1.3.0</version>
+  </skin>
+
+  <body>
+
+    <head>
+
+      <script type="text/javascript">
+
+        (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+        })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+        ga('create', 'UA-3211522-15', 'apache.org');
+        ga('send', 'pageview');
+
+      </script>
+
+    </head>
+
+    <menu name="User Guide">
+      <item name="Home" href="/index.html"/>
+    </menu>
+
+    <menu ref="reports" inherit="bottom"/>
+
+    <menu name="ASF">
+      <item name="How Apache Works" href="http://www.apache.org/foundation/how-it-works.html"/>
+      <item name="Foundation" href="http://www.apache.org/foundation/"/>
+      <item name="Sponsoring Apache" href="http://www.apache.org/foundation/sponsorship.html"/>
+      <item name="Thanks" href="http://www.apache.org/foundation/thanks.html"/>
+    </menu>
+
+    <footer>
+      <div class="row span16"><div>Apache Geronimo, Apache, the Apache feather logo, and the Apache Johnzon project logos are trademarks of The Apache Software Foundation.
+        All other marks mentioned may be trademarks or registered trademarks of their respective owners.</div>
+        <a href="${project.url}/privacy-policy.html">Privacy Policy</a>
+      </div>
+    </footer>
+
+  </body>
+</project>