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>