Create new trunk for updates.
git-svn-id: https://svn.apache.org/repos/asf/geronimo/javamail/trunk@568081 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/geronimo-javamail_1.4/LICENSE.txt b/geronimo-javamail_1.4/LICENSE.txt
new file mode 100644
index 0000000..6b0b127
--- /dev/null
+++ b/geronimo-javamail_1.4/LICENSE.txt
@@ -0,0 +1,203 @@
+
+ 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.4/NOTICE.txt b/geronimo-javamail_1.4/NOTICE.txt
new file mode 100644
index 0000000..9d2d74b
--- /dev/null
+++ b/geronimo-javamail_1.4/NOTICE.txt
@@ -0,0 +1,5 @@
+Apache Geronimo
+Copyright 2006 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/geronimo-javamail_1.4/README.txt b/geronimo-javamail_1.4/README.txt
new file mode 100644
index 0000000..c39bd24
--- /dev/null
+++ b/geronimo-javamail_1.4/README.txt
@@ -0,0 +1,20 @@
+
+Building
+========
+
+To build you will need:
+
+ * J2SE SDK 1.4.2+ (http://java.sun.com/j2se/1.4.2)
+ * Maven 2.0.4+ (http://maven.apache.org)
+
+NOTE: If you use JDK 1.5 you may run into unexpected errors, so stick to 1.4.
+
+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.4/geronimo-javamail_1.4_mail/LICENSE.txt b/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/LICENSE.txt
new file mode 100644
index 0000000..6b0b127
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/LICENSE.txt
@@ -0,0 +1,203 @@
+
+ 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.4/geronimo-javamail_1.4_mail/NOTICE.txt b/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/NOTICE.txt
new file mode 100644
index 0000000..439eb83
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/NOTICE.txt
@@ -0,0 +1,3 @@
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/pom.xml b/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/pom.xml
new file mode 100644
index 0000000..e6fd2b3
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/pom.xml
@@ -0,0 +1,75 @@
+<?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.2</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>geronimo-javamail_1.4_mail</artifactId>
+ <name>Geronimo JavaMail :: 1.4 Mail</name>
+ <packaging>pom</packaging>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.geronimo.javamail</groupId>
+ <artifactId>geronimo-javamail_1.4_provider</artifactId>
+ <version>${pom.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-javamail_1.4_spec</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>attached</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+ <descriptor>${pom.basedir}/src/main/assembly/javamail.xml</descriptor>
+ </descriptors>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/src/main/assembly/javamail.xml b/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/src/main/assembly/javamail.xml
new file mode 100644
index 0000000..023e8ca
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/src/main/assembly/javamail.xml
@@ -0,0 +1,42 @@
+<?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$ -->
+
+<assembly>
+
+ <formats>
+ <format>jar</format>
+ </formats>
+
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <dependencySets>
+ <dependencySet>
+ <outputDirectory>/</outputDirectory>
+ <unpack>true</unpack>
+ <scope>runtime</scope>
+ <excludes>
+ <exclude>org.apache.geronimo.specs:geronimo-activation_1.1_spec</exclude>
+ </excludes>
+ </dependencySet>
+ </dependencySets>
+
+</assembly>
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/src/site/site.xml b/geronimo-javamail_1.4/geronimo-javamail_1.4_mail/src/site/site.xml
new file mode 100644
index 0000000..80f99dd
--- /dev/null
+++ b/geronimo-javamail_1.4/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.4/geronimo-javamail_1.4_provider/LICENSE.txt b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/LICENSE.txt
new file mode 100644
index 0000000..6b0b127
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/LICENSE.txt
@@ -0,0 +1,203 @@
+
+ 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.4/geronimo-javamail_1.4_provider/NOTICE.txt b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/NOTICE.txt
new file mode 100644
index 0000000..439eb83
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/NOTICE.txt
@@ -0,0 +1,3 @@
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/pom.xml b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/pom.xml
new file mode 100644
index 0000000..7dcef0e
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/pom.xml
@@ -0,0 +1,51 @@
+<?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.2</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>geronimo-javamail_1.4_provider</artifactId>
+ <name>Geronimo JavaMail :: 1.4 Provider</name>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-activation_1.1_spec</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-javamail_1.4_spec</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/ClientAuthenticator.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/CramMD5Authenticator.java
new file mode 100644
index 0000000..17da4db
--- /dev/null
+++ b/geronimo-javamail_1.4/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));
+ complete = true;
+ return responseString.getBytes();
+ } 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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java
new file mode 100644
index 0000000..c762f70
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/DigestMD5Authenticator.java
@@ -0,0 +1,624 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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()));
+ digest.update(responseString.getBytes("US-ASCII"));
+
+ // now convert that into a hex encoded string.
+ String validationText = new String(Hex.encode(digest.digest()));
+
+ // 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);
+ // and get this as a base64 encoded string.
+ String cnonce = new String(Base64.encode(cnonceBytes));
+
+ // Now the digest computation part. This gets a bit tricky, and must be
+ // done in strict order.
+
+ try {
+ // 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())) + ":" + 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()));
+ // 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()));
+
+ // 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 = new DigestParser(new String(challenge));
+
+ // 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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/LoginAuthenticator.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.java
new file mode 100644
index 0000000..95711ed
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/authentication/PlainAuthenticator.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.authentication;
+
+import java.io.UnsupportedEncodingException;
+
+import javax.mail.MessagingException;
+
+public class PlainAuthenticator 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 PlainAuthenticator(String username, String password) {
+ this.username = username;
+ this.password = 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 clallenge.
+ *
+ * @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 {
+ try {
+ // get the username and password in an UTF-8 encoding to create the
+ // token
+ byte[] userBytes = username.getBytes("UTF-8");
+ byte[] passBytes = password.getBytes("UTF-8");
+
+ // our token has two copies of the username, one copy of the
+ // password, and nulls
+ // between
+ byte[] tokenBytes = new byte[(userBytes.length * 2) + passBytes.length + 2];
+
+ System.arraycopy(userBytes, 0, tokenBytes, 0, userBytes.length);
+ System.arraycopy(userBytes, 0, tokenBytes, userBytes.length + 1, userBytes.length);
+ System.arraycopy(passBytes, 0, tokenBytes, (userBytes.length * 2) + 2, passBytes.length);
+
+ complete = true;
+ return tokenBytes;
+
+ } catch (UnsupportedEncodingException e) {
+ // got an error, fail this
+ throw new MessagingException("Invalid encoding");
+ }
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPFolder.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPGroupFolder.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPGroupFolder.java
new file mode 100644
index 0000000..19e51fd
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPGroupFolder.java
@@ -0,0 +1,386 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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
+ */
+ public void openFolder() throws MessagingException {
+ // update the group specifics, especially the message count.
+ updateGroupStats();
+
+ // get a cache for retrieve 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('>');
+
+ message = new NNTPMessage(this, (NNTPStore) store, msgNum, response.substring(idStart + 1, idEnd));
+
+ // 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 {
+ // 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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPMessage.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPRootFolder.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPStore.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPStore.java
new file mode 100644
index 0000000..b69bea4
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/NNTPStore.java
@@ -0,0 +1,345 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.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_AUTH = "auth";
+
+ protected static final String NNTP_PORT = "port";
+
+ protected static final String NNTP_NEWSRC = "newsrc";
+
+ protected static final String protocol = "nntp";
+
+ protected static final int DEFAULT_NNTP_PORT = 119;
+
+ // the active connection object.
+ protected NNTPConnection connection;
+
+ // the newsrc file where we store subscriptions and seen message markers.
+ protected NNTPNewsrc newsrc;
+
+ // the root folder
+ protected NNTPRootFolder root;
+
+ // our session provided debug output stream.
+ protected PrintStream debugStream;
+
+ /**
+ * Construct an NNTPStore item. This will load the .newsrc file associated
+ * with the server.
+ *
+ * @param session
+ * The owning javamail Session.
+ * @param urlName
+ * The Store urlName, which can contain server target
+ * information.
+ */
+ public NNTPStore(Session session, URLName urlName) {
+ super(session, urlName);
+
+ // get our debug output.
+ debugStream = session.getDebugOut();
+
+ }
+
+ /**
+ * @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());
+ }
+
+ /**
+ * @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);
+ }
+
+ // first 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 = getBooleanProperty(NNTP_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)) {
+ return false;
+ }
+
+ // 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 = getIntProperty(NNTP_PORT, DEFAULT_NNTP_PORT);
+ }
+
+ // create socket and connect to server.
+ connection = new NNTPConnection(protocol, session, host, port, username, password, debug);
+ connection.connect();
+
+ // see if we have a newsrc file location specified
+ String newsrcFile = 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
+ newsrc.close();
+ connection.close();
+ connection = null;
+ }
+
+ 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("NNTPTransport 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.
+ */
+ 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);
+ }
+
+ /**
+ * 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).
+ */
+ 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 (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + 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).
+ */
+ 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 (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + name;
+ return SessionUtil.getProperty(session, fullName, defaultValue);
+ }
+
+ /**
+ * 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.
+ */
+ int getIntProperty(String name, int defaultValue) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + name;
+ return SessionUtil.getIntProperty(session, fullName, defaultValue);
+ }
+
+ /**
+ * 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
+ */
+ boolean getBooleanProperty(String name, boolean defaultValue) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + name;
+ return SessionUtil.getBooleanProperty(session, fullName, defaultValue);
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrc.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcFile.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcFile.java
new file mode 100644
index 0000000..9211aca
--- /dev/null
+++ b/geronimo-javamail_1.4/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)));
+ }
+
+ /**
+ * 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));
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/NNTPNewsrcGroup.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/Range.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/nntp/newsrc/RangeList.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Command.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Command.java
new file mode 100644
index 0000000..b161994
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Command.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.pop3;
+
+/**
+ * An abstraction for POP3Commands
+ *
+ * @see org.apache.geronimo.javamail.store.pop3.POP3CommandFactory
+ *
+ * @version $Rev$ $Date$
+ */
+public interface POP3Command {
+
+ /**
+ * This method will get the POP3 command in string format according o
+ * rfc1939
+ */
+ public String getCommand();
+
+ /**
+ * Indicates wether this command expects a multiline response or not
+ *
+ */
+ public boolean isMultiLineResponse();
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3CommandFactory.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3CommandFactory.java
new file mode 100644
index 0000000..a66f97f
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3CommandFactory.java
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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;
+
+/**
+ * Provides concrete implementations of
+ * org.apache.geronimo.javamail.store.pop3.POP3Command objects representing the
+ * POP3 commands defined in rfc 1939
+ *
+ * @link http://www.faqs.org/rfcs/rfc1939.html
+ * @version $Rev$ $Date$
+ */
+public final class POP3CommandFactory implements POP3Constants {
+
+ public static POP3Command getCOMMAND_USER(final String user) {
+ return new POP3Command() {
+ public String getCommand() {
+ return "USER" + SPACE + user + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return false;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_PASS(final String passwd) {
+ return new POP3Command() {
+ public String getCommand() {
+ return "PASS" + SPACE + passwd + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return false;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_QUIT() {
+ return new POP3Command() {
+ public String getCommand() {
+ return "QUIT" + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return false;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_NOOP() {
+ return new POP3Command() {
+ public String getCommand() {
+ return "NOOP" + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return false;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_STAT() {
+ return new POP3Command() {
+ public String getCommand() {
+ return "STAT" + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return false;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_LIST() {
+ return getCOMMAND_LIST(-1);
+ }
+
+ public static POP3Command getCOMMAND_LIST(final int msgNo) {
+ return new POP3Command() {
+ public String getCommand() {
+ if (msgNo > 0) {
+ return "LIST" + SPACE + msgNo + CRLF;
+ } else {
+ return "LIST" + CRLF;
+ }
+ }
+
+ /**
+ * If a msg num is specified then the the message details will be on
+ * the first line for ex. +OK 3 4520
+ *
+ * if no msgnum is specified then all the msg details are return in
+ * a multiline format for ex. +OK 2 messages 1 456 2 46456 ..... n
+ * 366
+ */
+ public boolean isMultiLineResponse() {
+ return (msgNo < 0);
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_RETR(final int msgNo) {
+ return new POP3Command() {
+ public String getCommand() {
+ return "RETR" + SPACE + msgNo + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return true;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_DELE(final int msgNo) {
+ return new POP3Command() {
+ public String getCommand() {
+ return "DELE" + SPACE + msgNo + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return false;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_REST(final int msgNo) {
+ return new POP3Command() {
+ public String getCommand() {
+ return "REST" + SPACE + msgNo + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return false;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_TOP(final int msgNo, final int numLines) {
+ return new POP3Command() {
+ public String getCommand() {
+ return "TOP" + SPACE + msgNo + SPACE + numLines + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return true;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_UIDL() {
+ return getCOMMAND_UIDL(-1);
+ }
+
+ public static POP3Command getCOMMAND_UIDL(final int msgNo) {
+ return new POP3Command() {
+ public String getCommand() {
+ if (msgNo > 0) {
+ return "UIDL" + SPACE + msgNo + CRLF;
+ } else {
+ return "UIDL" + CRLF;
+ }
+ }
+
+ public boolean isMultiLineResponse() {
+ return true;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_CAPA() {
+ return new POP3Command() {
+ public String getCommand() {
+ return "CAPA" + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return true;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_AUTH(final String protocol) {
+ return new POP3Command() {
+ public String getCommand() {
+ return "AUTH " + protocol + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return false;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_AUTH(final String protocol, final String initialResponse) {
+ return new POP3Command() {
+ public String getCommand() {
+ return "AUTH " + protocol + " " + initialResponse + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return false;
+ }
+ };
+ }
+
+ public static POP3Command getCOMMAND_ChallengeReply(final String command) {
+ return new POP3Command() {
+ public String getCommand() {
+ return command + CRLF;
+ }
+
+ public boolean isMultiLineResponse() {
+ return false;
+ }
+ };
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Connection.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Connection.java
new file mode 100644
index 0000000..e5ccd4f
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Connection.java
@@ -0,0 +1,564 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+
+/**
+ * Represents a connection with the POP3 mail server. The connection is owned by
+ * a pop3 store and is only associated with one user who owns the respective
+ * POP3Store instance
+ *
+ * @version $Rev$ $Date$
+ */
+
+
+public class POP3Connection {
+
+ protected static final String MAIL_SSLFACTORY_CLASS = "mail.SSLSocketFactory.class";
+
+ protected static final String MAIL_POP3_FACTORY_CLASS = "socketFactory.class";
+
+ protected static final String MAIL_POP3_FACTORY_FALLBACK = "socketFactory.fallback";
+
+ protected static final String MAIL_POP3_FACTORY_PORT = "socketFactory.port";
+
+ protected static final String MAIL_POP3_LOCALADDRESS = "localAddress";
+
+ protected static final String MAIL_POP3_LOCALPORT = "localPort";
+
+ protected static final String MAIL_POP3_TIMEOUT = "timeout";
+
+ private Socket socket;
+
+ private Session session;
+
+ private String host;
+
+ private int port;
+
+ private PrintWriter writer;
+
+ private BufferedReader reader;
+
+ private String protocol;
+
+ private boolean sslConnection;
+
+ POP3Connection(Session session, String host, int port, boolean sslConnection, String protocol) {
+
+ this.session = session;
+ this.host = host;
+ this.port = port;
+ this.sslConnection = sslConnection;
+ this.protocol = protocol;
+ }
+
+ public void open() throws Exception {
+ try {
+
+ if (!sslConnection) {
+ getConnectedSocket();
+ } else {
+ getConnectedSSLSocket();
+ }
+
+ if (session.getDebug()) {
+ session.getDebugOut().println("Connection successful " + this.toString());
+ }
+
+ buildInputReader();
+ buildOutputWriter();
+
+ // consume the greeting
+ if (session.getDebug()) {
+ session.getDebugOut().println("Greeting from server " + reader.readLine());
+ } else {
+ reader.readLine();
+ }
+
+ } catch (IOException e) {
+ Exception ex = new Exception("Error opening connection " + this.toString(), e);
+ throw ex;
+ }
+ }
+
+ void close() throws Exception {
+ try {
+ socket.close();
+ if (session.getDebug()) {
+ session.getDebugOut().println("Connection successfuly closed " + this.toString());
+ }
+
+ } catch (IOException e) {
+ Exception ex = new Exception("Error closing connection " + this.toString(), e);
+ throw ex;
+ }
+
+ }
+
+ public synchronized POP3Response sendCommand(POP3Command cmd) throws MessagingException {
+ if (socket.isConnected()) {
+
+ // if the underlying output stream is down
+ // attempt to rebuild the writer
+ if (socket.isOutputShutdown()) {
+ buildOutputWriter();
+ }
+
+ // if the underlying inout stream is down
+ // attempt to rebuild the reader
+ if (socket.isInputShutdown()) {
+ buildInputReader();
+ }
+
+ if (session.getDebug()) {
+ session.getDebugOut().println("\nCommand sent " + cmd.getCommand());
+ }
+
+ POP3Response res = null;
+
+ // this method supresses IOException
+ // but choose bcos of ease of use
+ {
+ writer.write(cmd.getCommand());
+ writer.flush();
+ res = POP3ResponseBuilder.buildResponse(session, reader, cmd.isMultiLineResponse());
+ }
+
+ return res;
+ }
+
+ throw new MessagingException("Connection to Mail Server is lost, connection " + this.toString());
+ }
+
+ private void buildInputReader() throws MessagingException {
+ try {
+ reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+ } catch (IOException e) {
+ throw new MessagingException("Error obtaining input stream " + this.toString(), e);
+ }
+ }
+
+ private void buildOutputWriter() throws MessagingException {
+ try {
+ writer = new PrintWriter(new BufferedOutputStream(socket.getOutputStream()));
+ } catch (IOException e) {
+ throw new MessagingException("Error obtaining output stream " + this.toString(), e);
+ }
+ }
+
+ public String toString() {
+ return "POP3Connection host: " + host + " port: " + port;
+ }
+
+
+ /**
+ * Creates a connected socket
+ *
+ * @exception MessagingException
+ */
+ protected void getConnectedSocket() throws IOException {
+
+ // the socket factory can be specified via a session property. By
+ // default, we just directly
+ // instantiate a socket without using a factor.
+ String socketFactory = getProtocolProperty(MAIL_POP3_FACTORY_CLASS);
+
+ // there are several protocol properties that can be set to tune the
+ // created socket. We need to
+ // retrieve those bits before creating the socket.
+ int timeout = getIntProtocolProperty(MAIL_POP3_TIMEOUT, -1);
+ InetAddress localAddress = null;
+ // see if we have a local address override.
+ String localAddrProp = getProtocolProperty(MAIL_POP3_LOCALADDRESS);
+ if (localAddrProp != null) {
+ localAddress = InetAddress.getByName(localAddrProp);
+ }
+
+ // check for a local port...default is to allow socket to choose.
+ int localPort = getIntProtocolProperty(MAIL_POP3_LOCALPORT, 0);
+
+ socket = null;
+
+ // if there is no socket factory defined (normal), we just create a
+ // socket directly.
+ if (socketFactory == null) {
+ socket = new Socket(host, port, localAddress, localPort);
+ }
+
+ else {
+ try {
+ int socketFactoryPort = getIntProtocolProperty(MAIL_POP3_FACTORY_PORT, -1);
+
+ // we choose the port used by the socket based on overrides.
+ Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
+
+ // use the current context loader to resolve this.
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Class factoryClass = loader.loadClass(socketFactory);
+
+ // done indirectly, we need to invoke the method using
+ // reflection.
+ // This retrieves a factory instance.
+ Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
+ Object defFactory = getDefault.invoke(new Object(), new Object[0]);
+
+ // now that we have the factory, there are two different
+ // createSocket() calls we use,
+ // depending on whether we have a localAddress override.
+
+ if (localAddress != null) {
+ // retrieve the createSocket(String, int, InetAddress, int)
+ // method.
+ Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
+ socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+ } else {
+ // retrieve the createSocket(String, int) method.
+ Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { host, portArg };
+ socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+ }
+ } catch (Throwable e) {
+ // if a socket factory is specified, then we may need to fall
+ // back to a default. This behavior
+ // is controlled by (surprise) more session properties.
+ if (isProtocolPropertyTrue(MAIL_POP3_FACTORY_FALLBACK)) {
+ socket = new Socket(host, port, localAddress, localPort);
+ }
+ // we have an exception. We're going to throw an IOException,
+ // which may require unwrapping
+ // or rewrapping the exception.
+ else {
+ // we have an exception from the reflection, so unwrap the
+ // base exception
+ if (e instanceof InvocationTargetException) {
+ e = ((InvocationTargetException) e).getTargetException();
+ }
+
+
+ // throw this as an IOException, with the original exception
+ // attached.
+ IOException ioe = new IOException("Error connecting to " + host + ", " + port);
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+ }
+
+ if (timeout >= 0) {
+ socket.setSoTimeout(timeout);
+ }
+ }
+
+ /**
+ * Creates a connected SSL socket for an initial SSL connection.
+ *
+ * @exception MessagingException
+ */
+ protected void getConnectedSSLSocket() throws IOException {
+
+ if (session.getDebug()) {
+ session.getDebugOut().println("Attempting SSL socket connection to server " + host + ":" + port);
+ }
+ // the socket factory can be specified via a protocol property, a
+ // session property, and if all else
+ // fails (which it usually does), we fall back to the standard factory
+ // class.
+ String socketFactory = getProtocolProperty(MAIL_POP3_FACTORY_CLASS, getSessionProperty(MAIL_SSLFACTORY_CLASS,
+ "javax.net.ssl.SSLSocketFactory"));
+
+ // there are several protocol properties that can be set to tune the
+ // created socket. We need to
+ // retrieve those bits before creating the socket.
+ int timeout = getIntProtocolProperty(MAIL_POP3_TIMEOUT, -1);
+ InetAddress localAddress = null;
+ // see if we have a local address override.
+ String localAddrProp = getProtocolProperty(MAIL_POP3_LOCALADDRESS);
+ if (localAddrProp != null) {
+ localAddress = InetAddress.getByName(localAddrProp);
+ }
+
+ // check for a local port...default is to allow socket to choose.
+ int localPort = getIntProtocolProperty(MAIL_POP3_LOCALPORT, 0);
+
+ socket = null;
+
+ // if there is no socket factory defined (normal), we just create a
+ // socket directly.
+ if (socketFactory == null) {
+ System.out.println("SocketFactory was null so creating the connection using a default");
+ socket = new Socket(host, port, localAddress, localPort);
+ }
+
+ else {
+ // we'll try this with potentially two different factories if we're
+ // allowed to fall back.
+ boolean fallback = isProtocolPropertyTrue(MAIL_POP3_FACTORY_FALLBACK);
+ while(true) {
+ try {
+
+
+ if (socket != null) {
+ if (socket.isConnected())
+ break;
+ }
+
+ if (session.getDebug()) {
+ session.getDebugOut().println("Creating SSL socket using factory " + socketFactory);
+ }
+
+ int socketFactoryPort = getIntProtocolProperty(MAIL_POP3_FACTORY_PORT, -1);
+
+ // we choose the port used by the socket based on overrides.
+ Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
+
+ // use the current context loader to resolve this.
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Class factoryClass = loader.loadClass(socketFactory);
+
+ // done indirectly, we need to invoke the method using
+ // reflection.
+ // This retrieves a factory instance.
+ Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
+ Object defFactory = getDefault.invoke(new Object(), new Object[0]);
+
+ // now that we have the factory, there are two different
+ // createSocket() calls we use,
+ // depending on whether we have a localAddress override.
+
+ if (localAddress != null) {
+ // retrieve the createSocket(String, int, InetAddress,
+ // int) method.
+ Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class,
+ Integer.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
+ socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+ } else {
+ // retrieve the createSocket(String, int) method.
+ Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { host, portArg };
+ socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+ }
+ } 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 (session.getDebug()) {
+ session.getDebugOut().println("First attempt at creating SSL socket failed, falling back to default factory");
+ }
+ if (fallback) {
+ socketFactory = "javax.net.ssl.SSLSocketFactory";
+ fallback = false;
+ continue;
+ }
+ // we have an exception. We're going to throw an
+ // IOException, which may require unwrapping
+ // or rewrapping the exception.
+ else {
+ // we have an exception from the reflection, so unwrap
+ // the base exception
+ if (e instanceof InvocationTargetException) {
+ e = ((InvocationTargetException) e).getTargetException();
+ }
+
+ if (session.getDebug()) {
+ session.getDebugOut().println("Failure creating SSL socket: " + e);
+ }
+ // throw this as an IOException, with the original
+ // exception attached.
+ IOException ioe = new IOException("Error connecting to " + host + ", " + port);
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+ }
+ }
+
+ if (timeout >= 0) {
+ socket.setSoTimeout(timeout);
+ }
+ }
+
+ /**
+ * Process a session property as a boolean value, returning either true or
+ * false.
+ *
+ * @return True if the property value is "true". Returns false for any other
+ * value (including null).
+ */
+ protected boolean isProtocolPropertyTrue(String name) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol ("pop3").
+ String fullName = "mail." + protocol + "." + name;
+ return isSessionPropertyTrue(fullName);
+ }
+
+ /**
+ * Process a session property as a boolean value, returning either true or
+ * false.
+ *
+ * @return True if the property value is "true". Returns false for any other
+ * value (including null).
+ */
+ protected boolean isSessionPropertyTrue(String name) {
+ String property = session.getProperty(name);
+ if (property != null) {
+ return property.equals("true");
+ }
+ return false;
+ }
+
+ /**
+ * 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.
+ */
+ protected int getIntProtocolProperty(String name, int defaultValue) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (pop3).
+ String fullName = "mail." + protocol + "." + name;
+ return getIntSessionProperty(fullName, defaultValue);
+ }
+
+ /**
+ * 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.
+ */
+ protected int getIntSessionProperty(String name, int defaultValue) {
+ String result = getSessionProperty(name);
+ if (result != null) {
+ try {
+ // convert into an int value.
+ return Integer.parseInt(result);
+ } catch (NumberFormatException e) {
+ }
+ }
+ // return default value if it doesn't exist is isn't convertable.
+ return defaultValue;
+ }
+
+ /**
+ * 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).
+ */
+ protected String getSessionProperty(String name, String defaultValue) {
+ String result = session.getProperty(name);
+ if (result == null) {
+ return defaultValue;
+ }
+ return result;
+ }
+
+ /**
+ * 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).
+ */
+ protected String getProtocolProperty(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 ("pop3").
+ String fullName = "mail." + protocol + "." + name;
+ return getSessionProperty(fullName, defaultValue);
+ }
+
+ /**
+ * 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).
+ */
+ protected String getProtocolProperty(String name) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol ("pop3").
+ String fullName = "mail." + protocol + "." + name;
+ return getSessionProperty(fullName);
+ }
+
+ /**
+ * Get a property associated with this mail session.
+ *
+ * @param name
+ * The name of the property.
+ *
+ * @return The property value (returns null if the property has not been
+ * set).
+ */
+ protected String getSessionProperty(String name) {
+ return session.getProperty(name);
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.java
new file mode 100644
index 0000000..57aed7e
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Constants.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.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 LF = '\n';
+
+ public final static int CR = '\r';
+
+ 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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java
new file mode 100644
index 0000000..042c170
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Folder.java
@@ -0,0 +1,346 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.Vector;
+
+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.Session;
+import javax.mail.Store;
+import javax.mail.URLName;
+import javax.mail.event.ConnectionEvent;
+
+import org.apache.geronimo.javamail.store.pop3.message.POP3Message;
+import org.apache.geronimo.javamail.store.pop3.message.POP3MessageFactory;
+import org.apache.geronimo.javamail.store.pop3.response.POP3ResponseFactory;
+import org.apache.geronimo.javamail.store.pop3.response.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 {
+
+ private boolean isFolderOpen = false;
+
+ private int mode;
+
+ private POP3Connection pop3Con;
+
+ private int msgCount;
+
+ private Session session;
+
+ /**
+ * Vector is synchronized so choose over the other Collection impls This is
+ * initialized on open A chache will save the expensive operation of
+ * retrieving the message again from the server.
+ */
+ private Vector msgCache;
+
+ protected POP3Folder(Store store, URLName url) {
+ super(store);
+ }
+
+ protected POP3Folder(Store store, Session session, POP3Connection pop3Con) {
+ super(store);
+ this.pop3Con = pop3Con;
+ this.session = session;
+ }
+
+ public String getName() {
+ return "INBOX";
+ }
+
+ public String getFullName() {
+ return "INBOX";
+ }
+
+ /**
+ * 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 {
+ throw new MethodNotSupportedException("INBOX is the root folder");
+ }
+
+ public boolean exists() throws MessagingException {
+ // INBOX always exists at the backend
+ return true;
+ }
+
+ 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
+ */
+ public char getSeparator() throws MessagingException {
+ throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
+ }
+
+ public int getType() throws MessagingException {
+ return HOLDS_MESSAGES;
+ }
+
+ public boolean create(int type) throws MessagingException {
+ throw new MethodNotSupportedException("Only INBOX is supported in POP3, no sub folders");
+ }
+
+ public boolean hasNewMessages() throws MessagingException {
+ throw new MethodNotSupportedException("POP3 doesn't support this operation");
+ }
+
+ 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();
+
+ try {
+
+ POP3StatusResponse res = (POP3StatusResponse) POP3ResponseFactory.getStatusResponse(pop3Con
+ .sendCommand(POP3CommandFactory.getCOMMAND_STAT()));
+
+ // I am not checking for the res == null condition as the
+ // try catch block will handle it.
+
+ 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 add one additional
+ // element and burn the
+ msgCache = new Vector(msgCount + 1);
+ msgCache.setSize(msgCount + 1);
+
+ } catch (Exception e) {
+ throw new MessagingException("Unable to execute STAT command", e);
+ }
+
+ notifyConnectionListeners(ConnectionEvent.OPENED);
+ }
+
+ public void close(boolean expunge) throws MessagingException {
+ // Can only be performed on an open folder
+ checkOpen();
+
+ try {
+ if (mode == READ_WRITE) {
+ // find all messages marked deleted and issue DELE commands
+ POP3Message m;
+ // NB: the first element in the cache is not used.
+ for (int i = 1; i < msgCache.size(); i++) {
+ if ((m = (POP3Message) msgCache.elementAt(i)) != null) {
+ if (m.isSet(Flags.Flag.DELETED)) {
+ try {
+ pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_DELE(i + 1));
+ } catch (Exception e) {
+ throw new MessagingException("Exception deleting message no [" + (i + 1)
+ + "] during close", e);
+ }
+ }
+ }
+ }
+ }
+
+ try {
+ pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_QUIT());
+ } catch (Exception e) {
+ // doesn't really care about the response
+ }
+ // dosn't need a catch block here, but added incase something goes
+ // wrong
+ // so that the finnaly is garunteed to execute in such a case.
+ } finally {
+ try {
+ pop3Con.close();
+ } catch (Exception e) {
+ // doesn't really care about the response
+ // all we can do is to set the reference explicitly to null
+ pop3Con = null;
+ }
+
+ /*
+ * The message numbers depend on the mail drop if the connection is
+ * closed, then purge the cache
+ */
+ msgCache = null;
+ isFolderOpen = false;
+ notifyConnectionListeners(ConnectionEvent.CLOSED);
+ }
+ }
+
+ public boolean isOpen() {
+ 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();
+ }
+
+ public int getMessageCount() throws MessagingException {
+ 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 = null;
+ try {
+ msg = (Message) msgCache.elementAt(msgNum);
+ } catch (RuntimeException e) {
+ session.getDebugOut().println("Message not in cache");
+ }
+ if (msg == null) {
+ msg = POP3MessageFactory.createMessage(this, session, pop3Con, msgNum);
+ msgCache.setElementAt(msg, msgNum);
+ }
+
+ 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 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 (msg == null) {
+ msg = POP3MessageFactory.createMessage(this, session, pop3Con, i);
+ }
+ if (fp.contains(FetchProfile.Item.ENVELOPE)) {
+ msg = POP3MessageFactory.createMessageWithEvelope((POP3Message) msg);
+ }
+
+ if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
+ msg = POP3MessageFactory.createMessageWithContentInfo((POP3Message) msg);
+ }
+
+ if (fp.contains(FetchProfile.Item.FLAGS)) {
+ msg = POP3MessageFactory.createMessageWithFlags((POP3Message) msg);
+ }
+
+ msgs[i] = msg;
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Response.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Response.java
new file mode 100644
index 0000000..197ca20
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Response.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.InputStream;
+
+/**
+ * An abstraction for POP3 Response
+ *
+ * @see org.apache.geronimo.javamail.store.pop3.response.POP3ResponseFactory
+ * @see org.apache.geronimo.javamail.store.pop3.response.DefaultPOP3Response
+ * @see org.apache.geronimo.javamail.store.pop3.response.POP3StatusResponse
+ *
+ * @version $Rev$ $Date$
+ */
+public interface POP3Response {
+
+ /**
+ * Returns the response OK, CHALLENGE or ERR
+ * <ul>
+ * <li>OK --> +OK in pop3 spec
+ * <li>CHALLENGE --> + in pop3 spec
+ * <li>ERR --> -ERR in pop3 spec
+ * </ul>
+ */
+ public int getStatus();
+
+ /**
+ * this corresponds to the line with the status however the status will be
+ * removed and the remainder is returned. Ex. "+OK 132 3023673" is the first
+ * line of response for a STAT command this method will return "132 3023673"
+ *
+ * So any subsequent process can parse the params 132 as no of msgs and
+ * 3023674 as the size.
+ *
+ * @see org.apache.geronimo.javamail.store.pop3.response.POP3StatusResponse
+ */
+ public String getFirstLine();
+
+ /**
+ * This way we are not restricting anybody as InputStream.class is the most
+ * basic type to represent an inputstream and ppl can decorate it anyway
+ * they want, for ex BufferedInputStream or as an InputStreamReader allowing
+ * maximum flexibility in using it.
+ */
+ public InputStream getData();
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3ResponseBuilder.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3ResponseBuilder.java
new file mode 100644
index 0000000..7b0ab4a
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3ResponseBuilder.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+
+import org.apache.geronimo.javamail.store.pop3.response.POP3ResponseFactory;
+
+;
+
+/**
+ * Builds a basic response out of the input stream received by the connection.
+ * Performs only two basic functions
+ * <ul>
+ * <li>Extrats the status code</li>
+ * <li>If multi-line response then extract the data as an input stream</li>
+ * </ul>
+ *
+ * @version $Rev$ $Date$
+ */
+
+public final class POP3ResponseBuilder implements POP3Constants {
+
+ public static POP3Response buildResponse(Session session, BufferedReader reader, boolean isMultiLineResponse)
+ throws MessagingException {
+
+ int status = ERR;
+ InputStream data = null;
+
+ String line;
+ try {
+ line = reader.readLine();
+ } catch (IOException e) {
+ throw new MessagingException("Error in receving response");
+ }
+ if (line == null || line.trim().equals("")) {
+ if (session.getDebug()) {
+ session.getDebugOut().println("Empty Response");
+ }
+ throw new MessagingException("Empty Response");
+ }
+ if (session.getDebug()) {
+ session.getDebugOut().println("Response From Server " + line);
+ }
+
+ if (line.startsWith("+OK")) {
+ status = OK;
+ line = removeStatusField(line);
+ if (isMultiLineResponse) {
+ data = getMultiLineResponse(session, reader);
+ }
+ } else if (line.startsWith("-ERR")) {
+ status = ERR;
+ line = removeStatusField(line);
+ }else if (line.startsWith("+")) {
+ status = CHALLENGE;
+ line = removeStatusField(line);
+ if (isMultiLineResponse) {
+ data = getMultiLineResponse(session, reader);
+ }
+ } else {
+ throw new MessagingException("Unexpected response: " + line);
+ }
+
+ return POP3ResponseFactory.getDefaultResponse(status, line, data);
+ }
+
+ private static String removeStatusField(String line) {
+ return line.substring(line.indexOf(SPACE) + 1);
+ }
+
+ /**
+ * This could be a multiline response
+ */
+ private static InputStream getMultiLineResponse(Session session, BufferedReader reader) throws MessagingException {
+
+ int byteRead = -1;
+ int lastByteRead = LF;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ while ((byteRead = reader.read()) >= 0) {
+ // We are checking for the end of a multiline response
+ // the format is .CRLF
+
+ // checking for the DOT and CR
+ if (lastByteRead == DOT && byteRead == CR) {
+ byteRead = reader.read();
+ // now checking for the LF of the second CRLF
+ if (byteRead == LF) {
+ // end of response
+ break;
+ }
+ }
+
+ out.write(byteRead);
+ lastByteRead = byteRead;
+ }
+
+ if (session.getDebug()) {
+ session.getDebugOut().println("\n============================ Response Content==================\n");
+ session.getDebugOut().write(out.toByteArray());
+ session.getDebugOut().println("\n==============================================================\n");
+ }
+
+ } catch (IOException e) {
+ throw new MessagingException("Error processing a multi-line response", e);
+ }
+
+ return new ByteArrayInputStream(out.toByteArray());
+ }
+
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java
new file mode 100644
index 0000000..5caed89
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/POP3Store.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.AuthenticationFailedException;
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.URLName;
+
+/**
+ * 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 {
+
+ private POP3Connection pop3Con;
+
+ protected static final int DEFAULT_MAIL_POP3_PORT = 110;
+ private boolean sslConnection;
+ private int defaultPort;
+
+ private String protocol;
+ public POP3Store(Session session, URLName name) {
+ this(session, name, "pop3", DEFAULT_MAIL_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);
+ this.protocol = protocol;
+
+ // these are defaults based on what the superclass specifies.
+ this.sslConnection = sslConnection;
+ this.defaultPort = defaultPort;
+
+ }
+ /**
+ * @see javax.mail.Store#getDefaultFolder()
+ *
+ * There is only INBOX supported in POP3 so the default folder is inbox
+ */
+ public Folder getDefaultFolder() throws MessagingException {
+ return getFolder("INBOX");
+ }
+
+ /**
+ * @see javax.mail.Store#getFolder(java.lang.String)
+ */
+ public Folder getFolder(String name) throws MessagingException {
+
+ checkConnectionStatus();
+
+ if (!"INBOX".equalsIgnoreCase(name)) {
+ throw new MessagingException("Only INBOX is supported in POP3");
+ }
+ return new POP3Folder(this, session, pop3Con);
+ }
+
+ /**
+ * @see javax.mail.Store#getFolder(javax.mail.URLName)
+ */
+ public Folder getFolder(URLName url) throws MessagingException {
+ return 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 portNum, String user, String passwd)
+ throws MessagingException {
+
+ // Never store the user, passwd for security reasons
+
+ // if these values are null, no connection attempt should be made
+ if (host == null || passwd == null || user == null) {
+ return false;
+ }
+
+ // validate port num
+ if (portNum < 1) {
+ String portstring = session.getProperty("mail.pop3.port");
+ if (portstring != null) {
+ try {
+ portNum = Integer.parseInt(portstring);
+ } catch (NumberFormatException e) {
+ portNum = defaultPort;
+ }
+ }
+ }
+
+ /*
+ * Obtaining a connection to the server.
+ *
+ */
+ pop3Con = new POP3Connection(this.session, host, portNum, sslConnection, protocol);
+ try {
+ pop3Con.open();
+ } catch (Exception e) {
+ throw new MessagingException("Connection failed", e);
+ }
+
+ /*
+ * Sending the USER command with username
+ *
+ */
+ POP3Response resUser = null;
+ try {
+ resUser = pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_USER(user));
+ } catch (Exception e) {
+ throw new MessagingException("Connection failed", e);
+ }
+
+ if (POP3Constants.ERR == resUser.getStatus()) {
+
+ /*
+ * Authentication failed so sending QUIT
+ *
+ */
+ try {
+ pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_QUIT());
+ } catch (Exception e) {
+ // We don't care about the response or if any error happens
+ // just trying to comply with the spec.
+ // Most likely the server would have terminated the connection
+ // by now.
+ }
+
+ throw new AuthenticationFailedException(resUser.getFirstLine());
+ }
+
+ /*
+ * Sending the PASS command with password
+ *
+ */
+ POP3Response resPwd = null;
+ try {
+ resPwd = pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_PASS(passwd));
+ } catch (Exception e) {
+ throw new MessagingException("Connection failed", e);
+ }
+
+ if (POP3Constants.ERR == resPwd.getStatus()) {
+
+ /*
+ * Authentication failed so sending QUIT
+ *
+ */
+ try {
+ pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_QUIT());
+ } catch (Exception e) {
+ // We don't care about the response or if any error happens
+ // just trying to comply with the spec.
+ // Most likely the server would have terminated the connection
+ // by now.
+ }
+
+ throw new AuthenticationFailedException(resPwd.getFirstLine());
+ }
+
+ return true;
+ }
+
+ /**
+ * @see javax.mail.Service#isConnected()
+ */
+ public boolean isConnected() {
+ POP3Response res = null;
+ try {
+ res = pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_NOOP());
+ } catch (Exception e) {
+ return false;
+ }
+
+ return (POP3Constants.OK == res.getStatus());
+ }
+
+ /**
+ * @see javax.mail.Service#close()
+ */
+ public void close() throws MessagingException {
+ // This is done to ensure proper event notification.
+ super.close();
+ try {
+ pop3Con.close();
+ } catch (Exception e) {
+ // A message is already set at the connection level
+ // unfortuantely there is no constructor that takes only
+ // the root exception
+ new MessagingException("", e);
+ }
+ }
+
+ private void checkConnectionStatus() throws MessagingException {
+ if (!this.isConnected()) {
+ throw new MessagingException("Not connected ");
+ }
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3Message.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3Message.java
new file mode 100644
index 0000000..c8183ec
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3Message.java
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.message;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.IllegalWriteException;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.event.MessageChangedEvent;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.geronimo.javamail.store.pop3.POP3CommandFactory;
+import org.apache.geronimo.javamail.store.pop3.POP3Connection;
+import org.apache.geronimo.javamail.store.pop3.POP3Folder;
+import org.apache.geronimo.javamail.store.pop3.POP3Response;
+import org.apache.geronimo.javamail.store.pop3.response.POP3ListResponse;
+import org.apache.geronimo.javamail.store.pop3.response.POP3ResponseFactory;
+
+/**
+ * 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 {
+
+ private POP3Connection pop3Con;
+
+ private int msgSize = -1;
+
+ private int headerSize = -1;
+
+ // We can't use header bcos it's already initialize to
+ // to an empty InternetHeader
+ private InputStream rawHeaders;
+
+ // used to force loading of headers again
+ private boolean loadHeaders = true;
+
+ // to get accessed to the debug setting and log
+ private Session session;
+
+ protected POP3Message(Folder folder, int msgnum, Session session, POP3Connection pop3Con) {
+ super(folder, msgnum);
+ this.pop3Con = pop3Con;
+ this.session = session;
+ }
+
+ /**
+ * @see javax.mail.internet.MimeMessage#getContentStream()
+ */
+ protected InputStream getContentStream() throws MessagingException {
+ POP3Response msgResponse = null;
+ try {
+ msgResponse = pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_RETR(msgnum));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ loadHeaders = true;
+ loadHeaders(msgResponse.getData());
+ loadContent(msgResponse.getData());
+
+ return contentStream;
+ }
+
+ 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);
+ }
+ }
+
+ protected void loadHeaders(InputStream in) throws MessagingException {
+ if (loadHeaders || rawHeaders == null) {
+ rawHeaders = in;
+ headers = new InternetHeaders(rawHeaders);
+ loadHeaders = false;
+ }
+ }
+
+ protected void loadContent(InputStream stream) throws MessagingException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ int byteRead = stream.read();
+ int lastByte = -1;
+ for (; byteRead > 0;) {
+ if (byteRead == ' ' && lastByte == '\n') {
+ break;
+ }
+ lastByte = byteRead;
+ byteRead = stream.read();
+ }
+
+ for (; stream.available() > 0;) {
+ out.write(stream.read());
+ }
+
+ contentStream = new ByteArrayInputStream(out.toByteArray());
+ msgSize = contentStream.available();
+
+ } catch (IOException e) {
+
+ throw new MessagingException("Error loading content info", e);
+ }
+ }
+
+ public int getSize() throws MessagingException {
+ if (msgSize >= 0) {
+ return msgSize;
+ }
+ try {
+
+ if (msgSize < 0) {
+ if (rawHeaders == null) {
+ loadHeaders();
+ }
+ POP3ListResponse res = (POP3ListResponse) POP3ResponseFactory.getListResponse(pop3Con
+ .sendCommand(POP3CommandFactory.getCOMMAND_LIST(msgnum)));
+ msgSize = res.getSize() - headerSize;
+ }
+ return msgSize;
+ } catch (MessagingException ex) {
+ throw new MessagingException("error getting size", ex);
+ }
+ }
+
+ /**
+ * 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 {
+ POP3Response msgResponse = null;
+ try {
+
+ msgResponse = pop3Con.sendCommand(POP3CommandFactory.getCOMMAND_TOP(msgnum, 0));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ loadHeaders(msgResponse.getData());
+ }
+
+ /***************************************************************************
+ * Following is a set of methods that deal with headers I have tried to use
+ * the bare minimum
+ *
+ * Used sun's POP3 impl & JavaMail API as a guide in decided which methods
+ * are important.
+ **************************************************************************/
+
+ public String[] getHeader(String name) throws MessagingException {
+ if (rawHeaders == null)
+ loadHeaders();
+ return headers.getHeader(name);
+ }
+
+ public String getHeader(String name, String delimiter) throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getHeader(name, delimiter);
+ }
+
+ public Enumeration getAllHeaders() throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getAllHeaders();
+ }
+
+ public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getMatchingHeaders(names);
+ }
+
+ public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getNonMatchingHeaders(names);
+ }
+
+ public Enumeration getAllHeaderLines() throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getAllHeaderLines();
+ }
+
+ public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
+ if (headers == null)
+ loadHeaders();
+ return headers.getMatchingHeaderLines(names);
+ }
+
+ public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
+ if (headers == null)
+ 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("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");
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageFactory.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageFactory.java
new file mode 100644
index 0000000..7c8d493
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageFactory.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.store.pop3.message;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage.RecipientType;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Connection;
+import org.apache.geronimo.javamail.store.pop3.POP3Folder;
+
+/**
+ * Fctory class to create POP3Messages based on the fetch profile
+ *
+ * @version $Rev$ $Date$
+ */
+public final class POP3MessageFactory {
+
+ /**
+ * Creates a basic method with no items, the items will be loaded on demand
+ *
+ * @param folder
+ * @param session
+ * @param pop3Con
+ * @param msgNum
+ * @return
+ */
+ public static Message createMessage(POP3Folder folder, Session session, POP3Connection pop3Con, int msgNum) {
+ return new POP3Message(folder, msgNum, session, pop3Con);
+ }
+
+ /**
+ * Created in response to <cpde>FetchProfile.ENVELOPE</code>
+ */
+ public static Message createMessageWithEvelope(POP3Message msg) throws MessagingException {
+ msg.getAllHeaders();
+ msg.getSender();
+ msg.getSentDate();
+ msg.getSubject();
+ msg.getReplyTo();
+ msg.getReceivedDate();
+ msg.getRecipients(RecipientType.TO);
+
+ return msg;
+ }
+
+ /**
+ * Created in response to <code>FetchProfile.CONTENT_INFO</code>
+ */
+ public static Message createMessageWithContentInfo(POP3Message msg) throws MessagingException {
+ msg.getContentType();
+ msg.getDisposition();
+ msg.getDescription();
+ msg.getSize();
+ msg.getLineCount();
+
+ return msg;
+ }
+
+ /**
+ * Created in response to <code>FetchProfile.FLAGS</code>
+ */
+ public static Message createMessageWithFlags(POP3Message msg) throws MessagingException {
+ msg.getFlags();
+ return msg;
+ }
+
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithContentInfo.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithContentInfo.java
new file mode 100644
index 0000000..17ca20d
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithContentInfo.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.pop3.message;
+
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Connection;
+
+/**
+ * light-weight Message object will be created in response to
+ * FetchProfile.CONTENT_INFO other details will be filled on demand *
+ *
+ * @version $Rev$ $Date$
+ *
+ */
+
+public class POP3MessageWithContentInfo extends POP3Message {
+
+ public POP3MessageWithContentInfo(Folder folder, int msgnum, Session session, POP3Connection pop3Con)
+ throws MessagingException {
+ super(folder, msgnum, null, pop3Con);
+ this.getContentType();
+ this.getDisposition();
+ this.getDescription();
+ this.getSize();
+ this.getLineCount();
+ }
+
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithEnvelope.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithEnvelope.java
new file mode 100644
index 0000000..bc05d39
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithEnvelope.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.pop3.message;
+
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Connection;
+
+/**
+ * light-weight Message object will be created in response to
+ * FetchProfile.ENVELOPE other details will be filled on demand *
+ *
+ * @version $Rev$ $Date$
+ */
+
+public class POP3MessageWithEnvelope extends POP3Message {
+
+ protected POP3MessageWithEnvelope(Folder folder, int msgnum, Session session, POP3Connection pop3Con)
+ throws MessagingException {
+ super(folder, msgnum, session, pop3Con);
+ this.getAllHeaders();
+ this.getSender();
+ this.getSentDate();
+ this.getSubject();
+ this.getReplyTo();
+ this.getReceivedDate();
+ this.getRecipients(RecipientType.TO);
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithFlags.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithFlags.java
new file mode 100644
index 0000000..2c6ac64
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/message/POP3MessageWithFlags.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.pop3.message;
+
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Connection;
+
+/**
+ * light-weight Message object will be created in response to FetchProfile.FLAGS
+ * other details will be filled on demand *
+ *
+ * @version $Rev$ $Date$
+ */
+
+public class POP3MessageWithFlags extends POP3Message {
+
+ protected POP3MessageWithFlags(Folder folder, int msgnum, Session session, POP3Connection pop3Con)
+ throws MessagingException {
+ super(folder, msgnum, session, pop3Con);
+ this.getFlags();
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/DefaultPOP3Response.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/DefaultPOP3Response.java
new file mode 100644
index 0000000..43e33ed
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/DefaultPOP3Response.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.store.pop3.response;
+
+import java.io.InputStream;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Constants;
+import org.apache.geronimo.javamail.store.pop3.POP3Response;
+
+/**
+ * This class provides the basic implementation for the POP3Response.
+ *
+ * @see org.apache.geronimo.javamail.store.pop3.POP3Response
+ * @version $Rev$ $Date$
+ */
+
+public class DefaultPOP3Response implements POP3Response, POP3Constants {
+
+ private int status = ERR;
+
+ private String firstLine;
+
+ private InputStream data;
+
+ DefaultPOP3Response(int status, String firstLine, InputStream data) {
+ this.status = status;
+ this.firstLine = firstLine;
+ this.data = data;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public InputStream getData() {
+ return data;
+ }
+
+ public String getFirstLine() {
+ return firstLine;
+ }
+
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ListResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ListResponse.java
new file mode 100644
index 0000000..448322a
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/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.response;
+
+import java.util.Vector;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Response;
+
+/**
+ * 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 DefaultPOP3Response {
+
+ private int msgnum = 0;
+
+ private int size = 0;
+
+ private Vector 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 STAT command", e);
+ }
+ try {
+ size = Integer.parseInt(args[1]);
+ } catch (NumberFormatException e) {
+ throw new MessagingException("Invalid response for STAT 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 STAT command", e);
+ }
+ multipleMsgs = new Vector(totalMsgs);
+ multipleMsgs.setSize(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 Vector getMultipleMessageDetails() {
+ return multipleMsgs;
+ }
+
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ResponseFactory.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ResponseFactory.java
new file mode 100644
index 0000000..5412746
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3ResponseFactory.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.pop3.response;
+
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Constants;
+import org.apache.geronimo.javamail.store.pop3.POP3Response;
+
+/**
+ * This factory provides a uniform way of handling the creation of response
+ * objects.
+ *
+ * @version $Rev$ $Date$
+ */
+
+public final class POP3ResponseFactory implements POP3Constants {
+
+ public static POP3Response getDefaultResponse(int status, String line, InputStream data) {
+ return new DefaultPOP3Response(status, line, data);
+ }
+
+ public static POP3Response getStatusResponse(POP3Response baseRes) throws MessagingException {
+ return new POP3StatusResponse(baseRes);
+ }
+
+ public static POP3Response getListResponse(POP3Response baseRes) throws MessagingException {
+ return new POP3StatusResponse(baseRes);
+ }
+
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3StatusResponse.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3StatusResponse.java
new file mode 100644
index 0000000..4fceb84
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/pop3/response/POP3StatusResponse.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.pop3.response;
+
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.javamail.store.pop3.POP3Response;
+
+/**
+ * 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 DefaultPOP3Response {
+
+ 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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
new file mode 100644
index 0000000..a8ca219
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPConnection.java
@@ -0,0 +1,1081 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.io.InputStream;
+import java.io.InputStreamReader;
+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.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.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.util.MIMEOutputStream;
+import org.apache.geronimo.javamail.util.TraceInputStream;
+import org.apache.geronimo.javamail.util.TraceOutputStream;
+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.
+ * <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 NNTPConnection {
+
+ /**
+ * 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_NNTP_AUTH = "auth";
+
+ protected static final String MAIL_NNTP_PORT = "port";
+
+ protected static final String MAIL_NNTP_TIMEOUT = "timeout";
+
+ protected static final String MAIL_NNTP_SASL_REALM = "sasl.realm";
+
+ protected static final String MAIL_NNTP_FACTORY_CLASS = "socketFactory.class";
+
+ protected static final String MAIL_NNTP_FACTORY_FALLBACK = "fallback";
+
+ protected static final String MAIL_NNTP_LOCALADDRESS = "localaddress";
+
+ protected static final String MAIL_NNTP_LOCALPORT = "localport";
+
+ protected static final String MAIL_NNTP_QUITWAIT = "quitwait";
+
+ protected static final String MAIL_NNTP_FACTORY_PORT = "socketFactory.port";
+
+ protected static final String MAIL_NNTP_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 int DEFAULT_NNTP_PORT = 119;
+
+ 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 protocol in use (either nntp or nntp-post).
+ String protocol;
+
+ // the target host
+ protected String host;
+
+ // the target server port.
+ protected int port;
+
+ // the connection socket...can be a plain socket or SSLSocket, if TLS is
+ // being used.
+ protected Socket socket;
+
+ // 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 test reader wrapped around the input stream.
+ protected BufferedReader in;
+
+ // the other end of the connection pipeline.
+ protected OutputStream outputStream;
+
+ // does the server support posting?
+ protected boolean postingAllowed = true;
+
+ // the username we connect with
+ protected String username;
+
+ // the authentication password.
+ protected String password;
+
+ // the target SASL realm (normally null unless explicitly set or we have an
+ // authentication mechanism that
+ // requires it.
+ protected String realm;
+
+ // the last response line received from the server.
+ protected NNTPReply lastServerResponse = null;
+
+ // our attached session
+ protected Session session;
+
+ // 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 HashMap serverAuthenticationMechanisms;
+
+ // map of server extension arguments
+ protected HashMap serverExtensionArgs;
+
+ // the welcome string from the server.
+ protected String welcomeString = null;
+
+ /**
+ * Normal constructor for an NNTPConnection() object.
+ *
+ * @param session
+ * The attached session.
+ * @param host
+ * The target host name of the NNTP server.
+ * @param port
+ * The target listening port of the server. Defaults to 119 if
+ * the port is specified as -1.
+ * @param username
+ * The login user name (can be null unless authentication is
+ * required).
+ * @param password
+ * Password associated with the userid account. Can be null if
+ * authentication is not required.
+ * @param debug
+ * The session debug flag.
+ */
+ public NNTPConnection(String protocol, Session session, String host, int port, String username, String password,
+ boolean debug) {
+ this.protocol = protocol;
+ this.session = session;
+ this.host = host;
+ this.port = port;
+ this.username = username;
+ this.password = password;
+ this.debug = debug;
+
+ // get our debug output.
+ debugStream = session.getDebugOut();
+ }
+
+ /**
+ * Connect to the server and do the initial handshaking.
+ *
+ * @exception MessagingException
+ */
+ public void connect() throws MessagingException {
+ try {
+
+ // create socket and connect to server.
+ getConnection();
+
+ // receive welcoming message
+ getWelcome();
+
+ } catch (IOException e) {
+ if (debug) {
+ debugOut("I/O exception establishing connection", e);
+ }
+ throw new MessagingException("Connection error", 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();
+ }
+ }
+
+ /**
+ * Create a transport connection object and connect it to the target server.
+ *
+ * @exception MessagingException
+ */
+ protected void getConnection() throws IOException {
+ // We might have been passed a socket to connect with...if not, we need
+ // to create one of the correct type.
+ if (socket == null) {
+ getConnectedSocket();
+ }
+ // if we already have a socket, get some information from it and
+ // override what we've been passed.
+ else {
+ port = socket.getPort();
+ host = socket.getInetAddress().getHostName();
+ }
+
+ // now set up the input/output streams.
+ inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug, getBooleanProperty(
+ MAIL_NNTP_ENCODE_TRACE, false));
+ ;
+ outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug, getBooleanProperty(
+ MAIL_NNTP_ENCODE_TRACE, false));
+
+ // get a reader to read the input as lines
+ in = new BufferedReader(new InputStreamReader(inputStream));
+ }
+
+ /**
+ * Close the server connection at termination.
+ */
+ public void closeServerConnection() {
+ try {
+ socket.close();
+ } catch (IOException ignored) {
+ }
+
+ socket = null;
+ inputStream = null;
+ outputStream = null;
+ in = null;
+ }
+
+ /**
+ * Creates a connected socket
+ *
+ * @exception MessagingException
+ */
+ public void getConnectedSocket() throws IOException {
+ if (debug) {
+ debugOut("Attempting plain socket connection to server " + host + ":" + port);
+ }
+
+ // the socket factory can be specified via a session property. By
+ // default, we just directly
+ // instantiate a socket without using a factor.
+ String socketFactory = getProperty(MAIL_NNTP_FACTORY_CLASS);
+
+ // there are several protocol properties that can be set to tune the
+ // created socket. We need to
+ // retrieve those bits before creating the socket.
+ int timeout = getIntProperty(MAIL_NNTP_TIMEOUT, -1);
+ InetAddress localAddress = null;
+ // see if we have a local address override.
+ String localAddrProp = getProperty(MAIL_NNTP_LOCALADDRESS);
+ if (localAddrProp != null) {
+ localAddress = InetAddress.getByName(localAddrProp);
+ }
+
+ // check for a local port...default is to allow socket to choose.
+ int localPort = getIntProperty(MAIL_NNTP_LOCALPORT, 0);
+
+ socket = null;
+
+ // if there is no socket factory defined (normal), we just create a
+ // socket directly.
+ if (socketFactory == null) {
+ socket = new Socket(host, port, localAddress, localPort);
+ }
+
+ else {
+ try {
+ int socketFactoryPort = getIntProperty(MAIL_NNTP_FACTORY_PORT, -1);
+
+ // we choose the port used by the socket based on overrides.
+ Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
+
+ // use the current context loader to resolve this.
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Class factoryClass = loader.loadClass(socketFactory);
+
+ // done indirectly, we need to invoke the method using
+ // reflection.
+ // This retrieves a factory instance.
+ Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
+ Object defFactory = getDefault.invoke(new Object(), new Object[0]);
+
+ // now that we have the factory, there are two different
+ // createSocket() calls we use,
+ // depending on whether we have a localAddress override.
+
+ if (localAddress != null) {
+ // retrieve the createSocket(String, int, InetAddress, int)
+ // method.
+ Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
+ socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+ } else {
+ // retrieve the createSocket(String, int) method.
+ Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { host, portArg };
+ socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+ }
+ } catch (Throwable e) {
+ // if a socket factor is specified, then we may need to fall
+ // back to a default. This behavior
+ // is controlled by (surprise) more session properties.
+ if (getBooleanProperty(MAIL_NNTP_FACTORY_FALLBACK, false)) {
+ if (debug) {
+ debugOut("First plain socket attempt faile, falling back to default factory", e);
+ }
+ socket = new Socket(host, port, localAddress, localPort);
+ }
+ // we have an exception. We're going to throw an IOException,
+ // which may require unwrapping
+ // or rewrapping the exception.
+ else {
+ // we have an exception from the reflection, so unwrap the
+ // base exception
+ if (e instanceof InvocationTargetException) {
+ e = ((InvocationTargetException) e).getTargetException();
+ }
+
+ if (debug) {
+ debugOut("Plain socket creation failure", e);
+ }
+
+ // throw this as an IOException, with the original exception
+ // attached.
+ IOException ioe = new IOException("Error connecting to " + host + ", " + port);
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+ }
+
+ if (timeout >= 0) {
+ socket.setSoTimeout(timeout);
+ }
+ }
+
+ /**
+ * 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 {
+ // there's yet another property that controls whether we should wait for
+ // a
+ // reply for a QUIT command. If on, just send the command and get outta
+ // here.
+ if (getBooleanProperty(MAIL_NNTP_QUITWAIT, false)) {
+ sendLine("QUIT");
+ } else {
+ // handle as a real command...we're going to ignore the response.
+ sendCommand("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 deliverd as data
+ // lines terminated with a "." line.
+ if (reply.getCode() != NNTPReply.EXTENSIONS_SUPPORTED) {
+ return;
+ }
+
+ // get a fresh extension mapping table.
+ serverExtensionArgs = new HashMap();
+ serverAuthenticationMechanisms = new HashMap();
+
+ // 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 EHLP 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.
+ serverExtensionArgs.put(extensionName, argument);
+
+ // process a few special ones that don't require extra parsing.
+ // AUTHINFO is entered in as a auth mechanism.
+ if (extensionName.equals("AUTHINFO")) {
+ serverAuthenticationMechanisms.put("AUTHINFO", "AUTHINFO");
+ }
+ // 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();
+ serverAuthenticationMechanisms.put(mechanism, 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 (serverExtensionArgs != null) {
+ return (String) serverExtensionArgs.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;
+ }
+
+ /**
+ * Determine if the target server supports a given authentication mechanism.
+ *
+ * @param mechanism
+ * The mechanism name.
+ *
+ * @return true if the server EHLO response indicates it supports the
+ * mechanism, false otherwise.
+ */
+ protected boolean supportsAuthentication(String mechanism) {
+ return serverAuthenticationMechanisms.get(mechanism) != 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.
+ OutputStream mimeOut = new MIMEOutputStream(outputStream);
+
+ msg.writeTo(mimeOut);
+ mimeOut.flush();
+ } catch (IOException e) {
+ throw new MessagingException("I/O error posting message", e);
+ } catch (MessagingException e) {
+ throw new MessagingException("Exception posting message", e);
+ }
+
+ // now to finish, we send a CRLF sequence, followed by a ".".
+ sendLine("");
+ sendLine(".");
+
+ // 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(in);
+ }
+ 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) {
+ if (debug) {
+ 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());
+ 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 = in.readLine();
+ if (line == null) {
+ throw new MessagingException("Unexpected end of stream");
+ }
+ return line;
+ } catch (IOException e) {
+ throw new MessagingException("Error reading from server", e);
+ }
+ }
+
+ /**
+ * Retrieve the SASL realm used for DIGEST-MD5 authentication. This will
+ * either be explicitly set, or retrieved using the mail.nntp.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 = getProperty(MAIL_NNTP_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;
+ }
+
+ /**
+ * 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 (!processAuthinfoSasl()) {
+ 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 AUTHINFO GENERIC. Right now, this appears not to be widely used
+ * and information on how the conversations are handled for different auth
+ * types is lacking, so right now, this just returns false to force the
+ * userid/password form to be used.
+ *
+ * @return Always returns false.
+ * @exception MessagingException
+ */
+ protected boolean processAuthinfoGeneric() throws MessagingException {
+ return false;
+ }
+
+ /**
+ * Process AUTHINFO SASL.
+ *
+ * @return Returns true if the server support a SASL authentication
+ * mechanism and accepted reponse challenges.
+ * @exception MessagingException
+ */
+ protected boolean processAuthinfoSasl() throws MessagingException {
+ ClientAuthenticator authenticator = null;
+
+ // now go through the progression of mechanisms we support, from the
+ // most secure to the
+ // least secure.
+
+ if (supportsAuthentication(AUTHENTICATION_DIGESTMD5)) {
+ authenticator = new DigestMD5Authenticator(host, username, password, getSASLRealm());
+ } else if (supportsAuthentication(AUTHENTICATION_CRAMMD5)) {
+ authenticator = new CramMD5Authenticator(username, password);
+ } else if (supportsAuthentication(AUTHENTICATION_LOGIN)) {
+ authenticator = new LoginAuthenticator(username, password);
+ } else if (supportsAuthentication(AUTHENTICATION_PLAIN)) {
+ authenticator = new PlainAuthenticator(username, password);
+ } else {
+ // can't find a mechanism we support in common
+ return false;
+ }
+
+ 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("AUTHINFO SASL ");
+ // and tell the server which mechanism we're using.
+ command.append(authenticator.getMechanismName());
+ command.append(" ");
+ // and append the response data
+ command.append(new String(Base64.encode(authenticator.evaluateChallenge(null))));
+ // 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) {
+ if (debug) {
+ 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()) {
+ if (debug) {
+ debugOut("Extra authentication challenge " + line);
+ }
+ return false;
+ }
+
+ // we're passed back a challenge value, Base64 encoded.
+ byte[] challenge = Base64.decode(line.getMessage().getBytes());
+
+ // have the authenticator evaluate and send back the encoded
+ // response.
+ sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge))));
+ }
+ // 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;
+ }
+ }
+ }
+
+ /**
+ * Process an AUTHINFO USER command. Most common form of NNTP
+ * authentication.
+ *
+ * @exception MessagingException
+ */
+ protected void processAuthinfoUser() throws MessagingException {
+ 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");
+ }
+ }
+
+ /**
+ * Internal debug output routine.
+ *
+ * @param value
+ * The string value to output.
+ */
+ protected void debugOut(String message) {
+ debugStream.println("NNTPTransport DEBUG: " + message);
+ }
+
+ /**
+ * Internal debugging routine for reporting exceptions.
+ *
+ * @param message
+ * A message associated with the exception context.
+ * @param e
+ * The received exception.
+ */
+ protected void debugOut(String message, Throwable e) {
+ debugOut("Received exception -> " + message);
+ debugOut("Exception message -> " + e.getMessage());
+ e.printStackTrace(debugStream);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Return the server host for this connection.
+ *
+ * @return The String name of the server host.
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * 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).
+ */
+ 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 (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + 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).
+ */
+ 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 (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + name;
+ return SessionUtil.getProperty(session, fullName, defaultValue);
+ }
+
+ /**
+ * 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.
+ */
+ int getIntProperty(String name, int defaultValue) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + name;
+ return SessionUtil.getIntProperty(session, fullName, defaultValue);
+ }
+
+ /**
+ * 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
+ */
+ boolean getBooleanProperty(String name, boolean defaultValue) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + name;
+ return SessionUtil.getBooleanProperty(session, fullName, defaultValue);
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPReply.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPReply.java
new file mode 100644
index 0000000..6328b92
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPReply.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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 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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
new file mode 100644
index 0000000..9e953eb
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/NNTPTransport.java
@@ -0,0 +1,374 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.mail.util.SessionUtil;
+
+/**
+ * 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_AUTH = "auth";
+
+ protected static final String NNTP_PORT = "port";
+
+ protected static final String NNTP_FROM = "from";
+
+ protected static final String protocol = "nntp-post";
+
+ protected static final int DEFAULT_NNTP_PORT = 119;
+
+ // our active connection object (shared code with the NNTPStore).
+ protected NNTPConnection connection;
+
+ // our session provided debug output stream.
+ protected PrintStream debugStream;
+
+ /**
+ * 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) {
+ super(session, name);
+
+ // get our debug output.
+ debugStream = session.getDebugOut();
+ }
+
+ /**
+ * 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 {
+ if (debug) {
+ debugOut("Connecting to server " + host + ":" + port + " for user " + username);
+ }
+
+ // first 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 = SessionUtil.getBooleanProperty(session, NNTP_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)) {
+ return false;
+ }
+
+ // 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 = SessionUtil.getIntProperty(session, NNTP_PORT, DEFAULT_NNTP_PORT);
+ }
+
+ // create socket and connect to server.
+ connection = new NNTPConnection(protocol, session, host, port, username, password, debug);
+ connection.connect();
+
+ // 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;
+ }
+
+ /**
+ * 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 = session.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)) {
+ System.out.println("Illegal address is of class " + addresses[i].getClass());
+ 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();
+ connection.close();
+ connection = null;
+ }
+
+ /**
+ * Internal debug output routine.
+ *
+ * @param value
+ * The string value to output.
+ */
+ protected void debugOut(String message) {
+ debugStream.println("NNTPTransport DEBUG: " + message);
+ }
+
+ /**
+ * Internal debugging routine for reporting exceptions.
+ *
+ * @param message
+ * A message associated with the exception context.
+ * @param e
+ * The received exception.
+ */
+ protected void debugOut(String message, Throwable e) {
+ debugOut("Received exception -> " + message);
+ debugOut("Exception message -> " + e.getMessage());
+ e.printStackTrace(debugStream);
+ }
+
+ /**
+ * 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).
+ */
+ 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 (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + 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).
+ */
+ 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 (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + name;
+ return SessionUtil.getProperty(session, fullName, defaultValue);
+ }
+
+ /**
+ * 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.
+ */
+ int getIntProperty(String name, int defaultValue) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + name;
+ return SessionUtil.getIntProperty(session, fullName, defaultValue);
+ }
+
+ /**
+ * 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
+ */
+ boolean getBooleanProperty(String name, boolean defaultValue) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (either "nntp" or "nntp-post").
+ String fullName = "mail." + protocol + "." + name;
+ return SessionUtil.getBooleanProperty(session, fullName, defaultValue);
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/nntp/StringListInputStream.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/MalformedSMTPReplyException.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressFailedException.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPAddressSucceededException.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPMessage.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.java
new file mode 100644
index 0000000..dbee40e
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPReply.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.transport.smtp;
+
+/**
+ * Util class to represent a reply from a SMTP server
+ *
+ * @version $Rev$ $Date$
+ */
+class SMTPReply {
+ // The original reply string
+ private final String reply;
+
+ // returned message code
+ private final int code;
+
+ // the returned message text
+ private final String message;
+
+ // 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);
+ }
+ }
+
+ /**
+ * 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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSTransport.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPSendFailedException.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
new file mode 100644
index 0000000..7b96d52
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
@@ -0,0 +1,2362 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.PrintStream;
+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.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+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.net.ssl.SSLSocket;
+
+import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
+import org.apache.geronimo.javamail.authentication.CramMD5Authenticator;
+import org.apache.geronimo.javamail.authentication.DigestMD5Authenticator;
+import org.apache.geronimo.javamail.authentication.LoginAuthenticator;
+import org.apache.geronimo.javamail.authentication.PlainAuthenticator;
+import org.apache.geronimo.javamail.util.MIMEOutputStream;
+import org.apache.geronimo.javamail.util.TraceInputStream;
+import org.apache.geronimo.javamail.util.TraceOutputStream;
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.XText;
+
+/**
+ * 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 {
+
+ /**
+ * constants for EOL termination
+ */
+ protected static final char CR = '\r';
+
+ protected static final char LF = '\n';
+
+ /**
+ * property keys for top level session properties.
+ */
+ protected static final String MAIL_LOCALHOST = "mail.localhost";
+
+ protected static final String MAIL_SSLFACTORY_CLASS = "mail.SSLSocketFactory.class";
+
+ /**
+ * 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_AUTH = "auth";
+
+ protected static final String MAIL_SMTP_PORT = "port";
+
+ protected static final String MAIL_SMTP_LOCALHOST = "localhost";
+
+ protected static final String MAIL_SMTP_TIMEOUT = "timeout";
+
+ protected static final String MAIL_SMTP_SASL_REALM = "sasl.realm";
+
+ protected static final String MAIL_SMTP_TLS = "starttls.enable";
+
+ protected static final String MAIL_SMTP_FACTORY_CLASS = "socketFactory.class";
+
+ protected static final String MAIL_SMTP_FACTORY_FALLBACK = "socketFactory.fallback";
+
+ protected static final String MAIL_SMTP_FACTORY_PORT = "socketFactory.port";
+
+ 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_DSN_NOTIFY = "dsn.notify";
+
+ protected static final String MAIL_SMTP_SENDPARTIAL = "sendpartial";
+
+ protected static final String MAIL_SMTP_LOCALADDRESS = "localaddress";
+
+ protected static final String MAIL_SMTP_LOCALPORT = "localport";
+
+ protected static final String MAIL_SMTP_QUITWAIT = "quitwait";
+
+ 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";
+
+ protected static final String MAIL_SMTP_EXTENSION = "mailextension";
+
+ protected static final String MAIL_SMTP_EHLO = "ehlo";
+
+ protected static final String MAIL_SMTP_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 int DEFAULT_MAIL_SMTP_PORT = 25;
+
+ protected static final int DEFAULT_MAIL_SMTPS_PORT = 465;
+
+ // SMTP reply codes
+ protected static final int SERVICE_READY = 220;
+
+ protected static final int SERVICE_CLOSING = 221;
+
+ protected static final int AUTHENTICATION_COMPLETE = 235;
+
+ protected static final int COMMAND_ACCEPTED = 250;
+
+ protected static final int ADDRESS_NOT_LOCAL = 251;
+
+ protected static final int AUTHENTICATION_CHALLENGE = 334;
+
+ protected static final int START_MAIL_INPUT = 354;
+
+ protected static final int SERVICE_NOT_AVAILABLE = 421;
+
+ protected static final int MAILBOX_BUSY = 450;
+
+ protected static final int PROCESSING_ERROR = 451;
+
+ protected static final int INSUFFICIENT_STORAGE = 452;
+
+ protected static final int COMMAND_SYNTAX_ERROR = 500;
+
+ protected static final int PARAMETER_SYNTAX_ERROR = 501;
+
+ protected static final int COMMAND_NOT_IMPLEMENTED = 502;
+
+ protected static final int INVALID_COMMAND_SEQUENCE = 503;
+
+ protected static final int COMMAND_PARAMETER_NOT_IMPLEMENTED = 504;
+
+ protected static final int MAILBOX_NOT_FOUND = 550;
+
+ protected static final int USER_NOT_LOCAL = 551;
+
+ protected static final int MAILBOX_FULL = 552;
+
+ protected static final int INVALID_MAILBOX = 553;
+
+ protected static final int TRANSACTION_FAILED = 553;
+
+ 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 protocol we're working with. This will be either "smtp" or "smtps".
+ protected String protocol;
+
+ // the target host
+ protected String host;
+
+ // the default port to use for this protocol (differs between "smtp" and
+ // "smtps").
+ protected int defaultPort;
+
+ // the target server port.
+ protected int port;
+
+ // the connection socket...can be a plain socket or SSLSocket, if TLS is
+ // being used.
+ protected Socket socket;
+
+ // our local host name
+ protected String localHost;
+
+ // 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;
+
+ // list of authentication mechanisms supported by the server
+ protected HashMap serverAuthenticationMechanisms;
+
+ // map of server extension arguments
+ protected HashMap serverExtensionArgs;
+
+ // 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;
+
+ // do we use SSL for our initial connection?
+ protected boolean sslConnection = false;
+
+ // the username we connect with
+ protected String username;
+
+ // the authentication password.
+ protected String password;
+
+ // the target SASL realm (normally null unless explicitly set or we have an
+ // authentication mechanism that
+ // requires it.
+ protected String realm;
+
+ // the last response line received from the server.
+ protected SMTPReply lastServerResponse = null;
+
+ // our session provided debug output stream.
+ protected PrintStream debugStream;
+
+ /**
+ * 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);
+ this.protocol = protocol;
+
+ // these are defaults based on what the superclass specifies.
+ this.defaultPort = defaultPort;
+ this.sslConnection = sslConnection;
+ // check to see if we need to throw an exception after a send operation.
+ reportSuccess = isProtocolPropertyTrue(MAIL_SMTP_REPORT_SUCCESS);
+ // and also check for TLS enablement.
+ useTLS = isProtocolPropertyTrue(MAIL_SMTP_STARTTLS_ENABLE);
+
+ // get our debug output.
+ debugStream = session.getDebugOut();
+ }
+
+ /**
+ * 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 {
+ this.socket = 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 {
+ if (debug) {
+ debugOut("Connecting to server " + host + ":" + port + " for user " + username);
+ }
+
+ // 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 = isProtocolPropertyTrue(MAIL_SMTP_AUTH);
+
+ // 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)) {
+ return false;
+ }
+
+ // 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) {
+ // take the default first.
+ port = defaultPort;
+ String configuredPort = getProtocolProperty(MAIL_SMTP_PORT);
+ if (configuredPort != null) {
+ port = Integer.parseInt(configuredPort);
+ }
+ }
+
+ // Before we do anything, let's make sure that we succesfully received a host
+ if ( host == null ) {
+ host = DEFAULT_MAIL_HOST;
+ }
+
+ try {
+
+ // create socket and connect to server.
+ getConnection(host, port, username, password);
+
+ // receive welcoming message
+ if (!getWelcome()) {
+ throw new MessagingException("Error in getting welcome msg");
+ }
+
+ // say hello
+ if (!sendHandshake()) {
+ throw new MessagingException("Error in saying EHLO to server");
+ }
+
+ // authenticate with the server, if necessary
+ if (!processAuthentication()) {
+ if (debug) {
+ debugOut("User authentication failure");
+ }
+ throw new AuthenticationFailedException("Error authenticating with server");
+ }
+ } catch (IOException e) {
+ if (debug) {
+ debugOut("I/O exception establishing connection", e);
+ }
+ throw new MessagingException("Connection error", e);
+ }
+ return true;
+ }
+
+ /**
+ * 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 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 (!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 = lastServerResponse;
+ // now send an "uber-exception" to indicate the failure.
+ throw new SMTPSendFailedException("MAIL FROM", last.getCode(), last.getMessage(), null, sent, unsent,
+ invalid);
+ }
+
+ 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 = getProtocolProperty(MAIL_SMTP_DSN_NOTIFY);
+ }
+
+ // 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 = 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) {
+ // 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 = isProtocolPropertyTrue(MAIL_SMTP_SENDPARTIAL);
+ }
+
+ // 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.
+ 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
+ sendData(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, getReportSuccess());
+
+ // 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;
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Reset the server connection after an error.
+ *
+ * @exception MessagingException
+ */
+ protected 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() != COMMAND_ACCEPTED) {
+ close();
+ }
+ // restore this.
+ lastServerResponse = last;
+ }
+
+ /**
+ * 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]);
+ }
+
+ /**
+ * Create a transport connection object and connect it to the target server.
+ *
+ * @param host
+ * The target server host.
+ * @param port
+ * The connection port.
+ *
+ * @exception MessagingException
+ */
+ protected void getConnection(String host, int port, String username, String password) throws IOException {
+ this.host = host;
+ this.port = port;
+ this.username = username;
+ this.password = password;
+ // and see if STARTTLS is enabled.
+ useTLS = isProtocolPropertyTrue(MAIL_SMTP_TLS);
+ serverAuthenticationMechanisms = new HashMap();
+ // We might have been passed a socket to connect with...if not, we need
+ // to create one of the correct type.
+ if (socket == null) {
+ // if this is the "smtps" 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 {
+ port = socket.getPort();
+ host = socket.getInetAddress().getHostName();
+ }
+ // now set up the input/output streams.
+ inputStream = new TraceInputStream(socket.getInputStream(), debugStream, debug,
+ isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
+ ;
+ outputStream = new TraceOutputStream(socket.getOutputStream(), debugStream, debug,
+ isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
+ }
+
+ /**
+ * 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).
+ */
+ protected String getProtocolProperty(String name) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (either "smtp" or "smtps").
+ String fullName = "mail." + protocol + "." + name;
+ return getSessionProperty(fullName);
+ }
+
+ /**
+ * Get a property associated with this mail session.
+ *
+ * @param name
+ * The name of the property.
+ *
+ * @return The property value (returns null if the property has not been
+ * set).
+ */
+ protected String getSessionProperty(String name) {
+ return session.getProperty(name);
+ }
+
+ /**
+ * 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).
+ */
+ protected String getSessionProperty(String name, String defaultValue) {
+ String result = session.getProperty(name);
+ if (result == null) {
+ return defaultValue;
+ }
+ return result;
+ }
+
+ /**
+ * 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).
+ */
+ protected String getProtocolProperty(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 (either "smtp" or "smtps").
+ String fullName = "mail." + protocol + "." + name;
+ return getSessionProperty(fullName, defaultValue);
+ }
+
+ /**
+ * 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.
+ */
+ protected int getIntSessionProperty(String name, int defaultValue) {
+ String result = getSessionProperty(name);
+ if (result != null) {
+ try {
+ // convert into an int value.
+ return Integer.parseInt(result);
+ } catch (NumberFormatException e) {
+ }
+ }
+ // return default value if it doesn't exist is isn't convertable.
+ return defaultValue;
+ }
+
+ /**
+ * 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.
+ */
+ protected int getIntProtocolProperty(String name, int defaultValue) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (either "smtp" or "smtps").
+ String fullName = "mail." + protocol + "." + name;
+ return getIntSessionProperty(fullName, defaultValue);
+ }
+
+ /**
+ * Process a session property as a boolean value, returning either true or
+ * false.
+ *
+ * @return True if the property value is "true". Returns false for any other
+ * value (including null).
+ */
+ protected boolean isProtocolPropertyTrue(String name) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (either "smtp" or "smtps").
+ String fullName = "mail." + protocol + "." + name;
+ return isSessionPropertyTrue(fullName);
+ }
+
+ /**
+ * Process a session property as a boolean value, returning either true or
+ * false.
+ *
+ * @return True if the property value is "true". Returns false for any other
+ * value (including null).
+ */
+ protected boolean isSessionPropertyTrue(String name) {
+ String property = session.getProperty(name);
+ if (property != null) {
+ return property.equals("true");
+ }
+ return false;
+ }
+
+ /**
+ * Process a session property as a boolean value, returning either true or
+ * false.
+ *
+ * @return True if the property value is "false". Returns false for other
+ * value (including null).
+ */
+ protected boolean isSessionPropertyFalse(String name) {
+ String property = session.getProperty(name);
+ if (property != null) {
+ return property.equals("false");
+ }
+ return false;
+ }
+
+ /**
+ * Process a session property as a boolean value, returning either true or
+ * false.
+ *
+ * @return True if the property value is "false". Returns false for other
+ * value (including null).
+ */
+ protected boolean isProtocolPropertyFalse(String name) {
+ // the name we're given is the least qualified part of the name. We
+ // construct the full property name
+ // using the protocol (either "smtp" or "smtps").
+ String fullName = "mail." + protocol + "." + name;
+ return isSessionPropertyTrue(fullName);
+ }
+
+ /**
+ * Close the server connection at termination.
+ */
+ protected void closeServerConnection() {
+ try {
+ socket.close();
+ } catch (IOException ignored) {
+ }
+
+ socket = null;
+ inputStream = null;
+ outputStream = null;
+ }
+
+ /**
+ * Creates a connected socket
+ *
+ * @exception MessagingException
+ */
+ protected void getConnectedSocket() throws IOException {
+ if (debug) {
+ debugOut("Attempting plain socket connection to server " + host + ":" + port);
+ }
+
+ // the socket factory can be specified via a session property. By
+ // default, we just directly
+ // instantiate a socket without using a factor.
+ String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS);
+
+ // there are several protocol properties that can be set to tune the
+ // created socket. We need to
+ // retrieve those bits before creating the socket.
+ int timeout = getIntProtocolProperty(MAIL_SMTP_TIMEOUT, -1);
+ InetAddress localAddress = null;
+ // see if we have a local address override.
+ String localAddrProp = getProtocolProperty(MAIL_SMTP_LOCALADDRESS);
+ if (localAddrProp != null) {
+ localAddress = InetAddress.getByName(localAddrProp);
+ }
+
+ // check for a local port...default is to allow socket to choose.
+ int localPort = getIntProtocolProperty(MAIL_SMTP_LOCALPORT, 0);
+
+ socket = null;
+
+ // if there is no socket factory defined (normal), we just create a
+ // socket directly.
+ if (socketFactory == null) {
+ socket = new Socket(host, port, localAddress, localPort);
+ }
+
+ else {
+ try {
+ int socketFactoryPort = getIntProtocolProperty(MAIL_SMTP_FACTORY_PORT, -1);
+
+ // we choose the port used by the socket based on overrides.
+ Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
+
+ // use the current context loader to resolve this.
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Class factoryClass = loader.loadClass(socketFactory);
+
+ // done indirectly, we need to invoke the method using
+ // reflection.
+ // This retrieves a factory instance.
+ Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
+ Object defFactory = getDefault.invoke(new Object(), new Object[0]);
+
+ // now that we have the factory, there are two different
+ // createSocket() calls we use,
+ // depending on whether we have a localAddress override.
+
+ if (localAddress != null) {
+ // retrieve the createSocket(String, int, InetAddress, int)
+ // method.
+ Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class, Integer.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
+ socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+ } else {
+ // retrieve the createSocket(String, int) method.
+ Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { host, portArg };
+ socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+ }
+ } catch (Throwable e) {
+ // if a socket factor is specified, then we may need to fall
+ // back to a default. This behavior
+ // is controlled by (surprise) more session properties.
+ if (isProtocolPropertyTrue(MAIL_SMTP_FACTORY_FALLBACK)) {
+ if (debug) {
+ debugOut("First plain socket attempt faile, falling back to default factory", e);
+ }
+ socket = new Socket(host, port, localAddress, localPort);
+ }
+ // we have an exception. We're going to throw an IOException,
+ // which may require unwrapping
+ // or rewrapping the exception.
+ else {
+ // we have an exception from the reflection, so unwrap the
+ // base exception
+ if (e instanceof InvocationTargetException) {
+ e = ((InvocationTargetException) e).getTargetException();
+ }
+
+ if (debug) {
+ debugOut("Plain socket creation failure", e);
+ }
+
+ // throw this as an IOException, with the original exception
+ // attached.
+ IOException ioe = new IOException("Error connecting to " + host + ", " + port);
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+ }
+
+ if (timeout >= 0) {
+ socket.setSoTimeout(timeout);
+ }
+ }
+
+ /**
+ * Creates a connected SSL socket for an initial SSL connection.
+ *
+ * @exception MessagingException
+ */
+ protected void getConnectedSSLSocket() throws IOException {
+ if (debug) {
+ debugOut("Attempting SSL socket connection to server " + host + ":" + port);
+ }
+ // the socket factory can be specified via a protocol property, a
+ // session property, and if all else
+ // fails (which it usually does), we fall back to the standard factory
+ // class.
+ String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS, getSessionProperty(MAIL_SSLFACTORY_CLASS,
+ "javax.net.ssl.SSLSocketFactory"));
+
+ // there are several protocol properties that can be set to tune the
+ // created socket. We need to
+ // retrieve those bits before creating the socket.
+ int timeout = getIntProtocolProperty(MAIL_SMTP_TIMEOUT, -1);
+ InetAddress localAddress = null;
+ // see if we have a local address override.
+ String localAddrProp = getProtocolProperty(MAIL_SMTP_LOCALADDRESS);
+ if (localAddrProp != null) {
+ localAddress = InetAddress.getByName(localAddrProp);
+ }
+
+ // check for a local port...default is to allow socket to choose.
+ int localPort = getIntProtocolProperty(MAIL_SMTP_LOCALPORT, 0);
+
+ socket = null;
+
+ // if there is no socket factory defined (normal), we just create a
+ // socket directly.
+ if (socketFactory == null) {
+ socket = new Socket(host, port, localAddress, localPort);
+ }
+
+ else {
+ // we'll try this with potentially two different factories if we're
+ // allowed to fall back.
+ boolean fallback = isProtocolPropertyTrue(MAIL_SMTP_FACTORY_FALLBACK);
+
+ while (true) {
+ try {
+ if (debug) {
+ debugOut("Creating SSL socket using factory " + socketFactory);
+ }
+
+ int socketFactoryPort = getIntProtocolProperty(MAIL_SMTP_FACTORY_PORT, -1);
+
+ // we choose the port used by the socket based on overrides.
+ Integer portArg = new Integer(socketFactoryPort == -1 ? port : socketFactoryPort);
+
+ // use the current context loader to resolve this.
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Class factoryClass = loader.loadClass(socketFactory);
+
+ // done indirectly, we need to invoke the method using
+ // reflection.
+ // This retrieves a factory instance.
+ Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
+ Object defFactory = getDefault.invoke(new Object(), new Object[0]);
+
+ // now that we have the factory, there are two different
+ // createSocket() calls we use,
+ // depending on whether we have a localAddress override.
+
+ if (localAddress != null) {
+ // retrieve the createSocket(String, int, InetAddress,
+ // int) method.
+ Class[] createSocketSig = new Class[] { String.class, Integer.TYPE, InetAddress.class,
+ Integer.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { host, portArg, localAddress, new Integer(localPort) };
+ socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+ } else {
+ // retrieve the createSocket(String, int) method.
+ Class[] createSocketSig = new Class[] { String.class, Integer.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { host, portArg };
+ socket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+ }
+ // now break out and configure the socket.
+ break;
+ } catch (Throwable e) {
+ // if we're allowed to fallback, then use the default
+ // factory and try this again. We only
+ // allow this to happen once.
+ if (fallback) {
+ if (debug) {
+ debugOut("First attempt at creating SSL socket failed, falling back to default factory");
+ }
+ socketFactory = "javax.net.ssl.SSLSocketFactory";
+ fallback = false;
+ continue;
+ }
+ // we have an exception. We're going to throw an
+ // IOException, which may require unwrapping
+ // or rewrapping the exception.
+ else {
+ // we have an exception from the reflection, so unwrap
+ // the base exception
+ if (e instanceof InvocationTargetException) {
+ e = ((InvocationTargetException) e).getTargetException();
+ }
+
+ if (debug) {
+ debugOut("Failure creating SSL socket", e);
+ }
+
+ // throw this as an IOException, with the original
+ // exception attached.
+ IOException ioe = new IOException("Error connecting to " + host + ", " + port);
+ ioe.initCause(e);
+ throw ioe;
+ }
+ }
+ }
+ }
+
+ if (timeout >= 0) {
+ socket.setSoTimeout(timeout);
+ }
+ }
+
+ /**
+ * Switch the connection to using TLS level security, switching to an SSL
+ * socket.
+ */
+ protected void getConnectedTLSSocket() throws MessagingException {
+ if (debug) {
+ debugOut("Attempting to negotiate STARTTLS with server " + host);
+ }
+ // tell the server of our intention to start a TLS session
+ SMTPReply line = sendCommand("STARTTLS");
+
+ if (line.getCode() != SERVICE_READY) {
+ if (debug) {
+ debugOut("STARTTLS command rejected by SMTP server " + host);
+ }
+ throw new MessagingException("Unable to make TLS server connection");
+ }
+ // it worked, now switch the socket into TLS mode
+ try {
+
+ // we use the same target and port as the current connection.
+ String host = socket.getInetAddress().getHostName();
+ int port = socket.getPort();
+
+ // the socket factory can be specified via a session property. By
+ // default, we use
+ // the native SSL factory.
+ String socketFactory = getProtocolProperty(MAIL_SMTP_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
+
+ // use the current context loader to resolve this.
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Class factoryClass = loader.loadClass(socketFactory);
+
+ // done indirectly, we need to invoke the method using reflection.
+ // This retrieves a factory instance.
+ Method getDefault = factoryClass.getMethod("getDefault", new Class[0]);
+ Object defFactory = getDefault.invoke(new Object(), new Object[0]);
+
+ // now we need to invoke createSocket()
+ Class[] createSocketSig = new Class[] { Socket.class, String.class, Integer.TYPE, Boolean.TYPE };
+ Method createSocket = factoryClass.getMethod("createSocket", createSocketSig);
+
+ Object[] createSocketArgs = new Object[] { socket, host, new Integer(port), Boolean.TRUE };
+
+ // and finally create the socket
+ Socket sslSocket = (Socket) createSocket.invoke(defFactory, createSocketArgs);
+
+ // if this is an instance of SSLSocket (very common), try setting
+ // the protocol to be
+ // "TLSv1". If this is some other class because of a factory
+ // override, we'll just have to
+ // accept that things will work.
+ if (sslSocket instanceof SSLSocket) {
+ ((SSLSocket) sslSocket).setEnabledProtocols(new String[] { "TLSv1" });
+ ((SSLSocket) sslSocket).setUseClientMode(true);
+ ((SSLSocket) sslSocket).startHandshake();
+ }
+
+ // and finally, as a last step, replace our input streams with the
+ // secure ones.
+ // now set up the input/output streams.
+ inputStream = new TraceInputStream(sslSocket.getInputStream(), debugStream, debug,
+ isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
+ ;
+ outputStream = new TraceOutputStream(sslSocket.getOutputStream(), debugStream, debug,
+ isProtocolPropertyTrue(MAIL_SMTP_ENCODE_TRACE));
+ // this is our active socket now
+ socket = sslSocket;
+
+ } catch (Exception e) {
+ if (debug) {
+ debugOut("Failure attempting to convert connection to TLS", e);
+ }
+ throw new MessagingException("Unable to convert connection to SSL", e);
+ }
+ }
+
+ /**
+ * Get the servers welcome blob from the wire....
+ */
+ protected boolean getWelcome() throws MessagingException {
+ SMTPReply line = getReply();
+ return !line.isError();
+ }
+
+ /**
+ * 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(Message 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.
+ OutputStream mimeOut = new MIMEOutputStream(outputStream);
+
+ msg.writeTo(mimeOut);
+ mimeOut.flush();
+ } catch (IOException e) {
+ throw new MessagingException(e.toString());
+ } catch (MessagingException e) {
+ throw new MessagingException(e.toString());
+ }
+
+ // now to finish, we send a CRLF sequence, followed by a ".".
+ sendLine("");
+ sendLine(".");
+
+ // use a longer time out here to give the server time to process the
+ // data.
+ try {
+ line = new SMTPReply(receiveLine(TIMEOUT * 2));
+ } catch (MalformedSMTPReplyException e) {
+ throw new MessagingException(e.toString());
+ } catch (MessagingException e) {
+ throw new MessagingException(e.toString());
+ }
+
+ 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 (isProtocolPropertyTrue(MAIL_SMTP_QUITWAIT)) {
+ // 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 notification address appended to the MAIL command.
+ *
+ * @return The status for this particular send operation.
+ * @exception MessagingException
+ */
+ protected 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 COMMAND_ACCEPTED:
+ case 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 PARAMETER_SYNTAX_ERROR:
+ case INVALID_COMMAND_SEQUENCE:
+ case MAILBOX_NOT_FOUND:
+ case INVALID_MAILBOX:
+ case 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 SERVICE_NOT_AVAILABLE:
+ case MAILBOX_BUSY:
+ case PROCESSING_ERROR:
+ case INSUFFICIENT_STORAGE:
+ case 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);
+ }
+ }
+
+ /**
+ * Set the sender for this mail.
+ *
+ * @param message
+ * The message we're sending.
+ *
+ * @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 = getProtocolProperty(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));
+
+ // 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 = getProtocolProperty(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 = getProtocolProperty(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"))));
+ } 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 = getProtocolProperty(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() == COMMAND_ACCEPTED;
+ }
+
+ /**
+ * 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 {
+ System.out.println(">>>>>Sending data " + data + "<<<<<<");
+ outputStream.write(data.getBytes());
+ outputStream.write(CR);
+ outputStream.write(LF);
+ 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);
+ }
+
+ /**
+ * Get a reply line for an SMTP command.
+ *
+ * @return An SMTP reply object from the stream.
+ */
+ protected SMTPReply getReply() throws MessagingException {
+ try {
+ lastServerResponse = new SMTPReply(receiveLine());
+ } catch (MalformedSMTPReplyException e) {
+ throw new MessagingException(e.toString());
+ } catch (MessagingException e) {
+ throw e;
+ }
+ 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 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
+ */
+ 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 = !isProtocolPropertyFalse(MAIL_SMTP_EHLO);
+
+ // 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) {
+ // if we've been told to use TLS, and this server doesn't support
+ // it, then this is a failure
+ if (!serverTLS) {
+ throw new MessagingException("Server doesn't support required transport level security");
+ }
+ // 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.
+ serverAuthenticationMechanisms.clear();
+ if (!sendEhlo()) {
+ throw new MessagingException("Failure sending EHLO command to SMTP server");
+ }
+ }
+
+ // this worked.
+ return true;
+ }
+
+ /**
+ * 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 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() != COMMAND_ACCEPTED) {
+ return false;
+ }
+
+ // get a fresh extension mapping table.
+ serverExtensionArgs = new HashMap();
+
+ // process all of the continuation lines
+ while (line.isContinued()) {
+ // get the next line
+ line = getReply();
+ if (line.getCode() != COMMAND_ACCEPTED) {
+ // all EHLO failures go back to the HELO failback step.
+ return false;
+ }
+ // go process the extention
+ processExtension(line.getMessage());
+ }
+ return true;
+ }
+
+ /**
+ * Send the HELO command to the SMTP server.
+ *
+ * @exception MessagingException
+ */
+ protected void sendHelo() throws MessagingException {
+ 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() != COMMAND_ACCEPTED) {
+ throw new MessagingException("Failure sending HELO command to SMTP server");
+ }
+ }
+
+ /**
+ * 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) {
+
+ try {
+ localHost = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ // fine, we're misconfigured - ignore
+ }
+
+ if (localHost == null) {
+ localHost = getProtocolProperty(MAIL_SMTP_LOCALHOST);
+ }
+
+ if (localHost == null) {
+ localHost = getSessionProperty(MAIL_LOCALHOST);
+ }
+
+ if (localHost == null) {
+ throw new MessagingException("Can't get local hostname. "
+ + " Please correctly configure JDK/DNS or set mail.smtp.localhost");
+ }
+ }
+
+ return localHost;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Return the current startTLS property.
+ *
+ * @return The current startTLS property.
+ */
+ public boolean getStartTLS() {
+ return reportSuccess;
+ }
+
+ /**
+ * Set a new value for the startTLS property.
+ *
+ * @param start
+ * The new setting.
+ */
+ public void setStartTLS(boolean start) {
+ useTLS = 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() {
+ // if the realm is null, retrieve it using the realm session property.
+ if (realm == null) {
+ realm = getProtocolProperty(MAIL_SMTP_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;
+ }
+
+ /**
+ * Explicitly set the local host information.
+ *
+ * @param localHost
+ * The new localHost name.
+ */
+ public void setLocalHost(String localHost) {
+ this.localHost = localHost;
+ }
+
+ /**
+ * 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) {
+ 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.
+ serverExtensionArgs.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) {
+ serverAuthenticationMechanisms.put("LOGIN", "LOGIN");
+ } else {
+ // The security mechanisms are blank delimited tokens.
+ StringTokenizer tokenizer = new StringTokenizer(argument);
+
+ while (tokenizer.hasMoreTokens()) {
+ String mechanism = tokenizer.nextToken().toUpperCase();
+ serverAuthenticationMechanisms.put(mechanism, mechanism);
+ }
+ }
+ }
+ // special case for some older servers.
+ else if (extensionName.equals("AUTH=LOGIN")) {
+ serverAuthenticationMechanisms.put("LOGIN", "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 (serverExtensionArgs != null) {
+ return (String) serverExtensionArgs.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;
+ }
+
+ /**
+ * Determine if the target server supports a given authentication mechanism.
+ *
+ * @param mechanism
+ * The mechanism name.
+ *
+ * @return true if the server EHLO response indicates it supports the
+ * mechanism, false otherwise.
+ */
+ protected boolean supportsAuthentication(String mechanism) {
+ return serverAuthenticationMechanisms.get(mechanism) != 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 (!isProtocolPropertyTrue(MAIL_SMTP_AUTH)) {
+ return true;
+ }
+
+ // we need to authenticate, but we don't have userid/password
+ // information...fail this
+ // immediately.
+ if (username == null || password == null) {
+ return false;
+ }
+
+ ClientAuthenticator authenticator = null;
+
+ // now go through the progression of mechanisms we support, from the
+ // most secure to the
+ // least secure.
+
+ if (supportsAuthentication(AUTHENTICATION_DIGESTMD5)) {
+ authenticator = new DigestMD5Authenticator(host, username, password, getSASLRealm());
+ } else if (supportsAuthentication(AUTHENTICATION_CRAMMD5)) {
+ authenticator = new CramMD5Authenticator(username, password);
+ } else if (supportsAuthentication(AUTHENTICATION_LOGIN)) {
+ authenticator = new LoginAuthenticator(username, password);
+ } else if (supportsAuthentication(AUTHENTICATION_PLAIN)) {
+ authenticator = new PlainAuthenticator(username, password);
+ } else {
+ // can't find a mechanism we support in common
+ return false;
+ }
+
+ 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
+ command.append(new String(Base64.encode(authenticator.evaluateChallenge(null))));
+ // 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() == AUTHENTICATION_COMPLETE) {
+ if (debug) {
+ debugOut("Successful SMTP authentication");
+ }
+ return true;
+ }
+ // we have an additional challenge to process.
+ else if (line.getCode() == AUTHENTICATION_CHALLENGE) {
+ // Does the authenticator think it is finished? We can't answer
+ // an additional challenge,
+ // so fail this.
+ if (authenticator.isComplete()) {
+ return false;
+ }
+
+ // we're passed back a challenge value, Base64 encoded.
+ byte[] challenge = Base64.decode(line.getMessage().getBytes());
+
+ // have the authenticator evaluate and send back the encoded
+ // response.
+ sendLine(new String(Base64.encode(authenticator.evaluateChallenge(challenge))));
+ }
+ // 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;
+ }
+ }
+ }
+
+ /**
+ * Simple holder class for the address/send status duple, as we can have
+ * mixed success for a set of addresses and a message
+ */
+ 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;
+ }
+ }
+
+ /**
+ * Internal debug output routine.
+ *
+ * @param value
+ * The string value to output.
+ */
+ protected void debugOut(String message) {
+ debugStream.println("SMTPTransport DEBUG: " + message);
+ }
+
+ /**
+ * Internal debugging routine for reporting exceptions.
+ *
+ * @param message
+ * A message associated with the exception context.
+ * @param e
+ * The received exception.
+ */
+ protected void debugOut(String message, Throwable e) {
+ debugOut("Received exception -> " + message);
+ debugOut("Exception message -> " + e.getMessage());
+ e.printStackTrace(debugStream);
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransportException.java b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEOutputStream.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEOutputStream.java
new file mode 100644
index 0000000..0409331
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/MIMEOutputStream.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.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;
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceInputStream.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceInputStream.java
new file mode 100644
index 0000000..39ee6dd
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceInputStream.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.util.QuotedPrintableEncoderStream;
+
+/**
+ * @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 QuotedPrintableEncoderStream(traceStream);
+ } 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 <code>len</code> bytes of data from this input stream into
+ * an array of bytes. This method blocks until some input is available.
+ * <p>
+ * This method simply performs <code>in.read(b, off, len)</code> and
+ * returns the result.
+ *
+ * @param b
+ * the buffer into which the data is read.
+ * @param off
+ * the start offset of the data.
+ * @param len
+ * the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * <code>-1</code> if there is no more data because the end of the
+ * stream has been reached.
+ * @exception IOException
+ * if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ 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;
+ }
+
+ /**
+ * Reads the next byte of data from this input stream. The value byte is
+ * returned as an <code>int</code> in the range <code>0</code> to
+ * <code>255</code>. If no byte is available because the end of the
+ * stream has been reached, the value <code>-1</code> is returned. This
+ * method blocks until input data is available, the end of the stream is
+ * detected, or an exception is thrown.
+ * <p>
+ * This method simply performs <code>in.read()</code> and returns the
+ * result.
+ *
+ * @return the next byte of data, or <code>-1</code> if the end of the
+ * stream is reached.
+ * @exception IOException
+ * if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ public int read() throws IOException {
+ int b = in.read();
+ if (debug) {
+ traceStream.write(b);
+ }
+ return b;
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceOutputStream.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceOutputStream.java
new file mode 100644
index 0000000..94cebd8
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/util/TraceOutputStream.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.util;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.geronimo.mail.util.QuotedPrintableEncoderStream;
+
+/**
+ * @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 QuotedPrintableEncoderStream(traceStream);
+ } 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;
+ }
+
+
+ /**
+ * Writes the specified <code>byte</code> to this output stream.
+ * <p>
+ * The <code>write</code> method of <code>FilterOutputStream</code>
+ * calls the <code>write</code> method of its underlying output stream,
+ * that is, it performs <tt>out.write(b)</tt>.
+ * <p>
+ * Implements the abstract <tt>write</tt> method of <tt>OutputStream</tt>.
+ *
+ * @param b
+ * the <code>byte</code>.
+ * @exception IOException
+ * if an I/O error occurs.
+ */
+ public void write(int b) throws IOException {
+ if (debug) {
+ traceStream.write(b);
+ }
+ super.write(b);
+ }
+}
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.address.map b/geronimo-javamail_1.4/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.4/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.4/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers
new file mode 100644
index 0000000..fb8ee77
--- /dev/null
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/resources/META-INF/javamail.default.providers
@@ -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.
+##
+
+##
+## $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.SMTPTSransport; 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; type=store; class=org.apache.geronimo.javamail.store.nntp.NNTPStore; vendor=Apache Software Foundation; version=1.0
+protocol=pop3; type=store; class=org.apache.geronimo.javamail.store.pop3.POP3Store; vendor=Apache Software Foundation; version=1.0
+
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/site/site.xml b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/site/site.xml
new file mode 100644
index 0000000..80f99dd
--- /dev/null
+++ b/geronimo-javamail_1.4/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.4/pom.xml b/geronimo-javamail_1.4/pom.xml
new file mode 100644
index 0000000..cc60809
--- /dev/null
+++ b/geronimo-javamail_1.4/pom.xml
@@ -0,0 +1,142 @@
+<?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.genesis.config</groupId>
+ <artifactId>project-config</artifactId>
+ <version>1.1</version>
+ </parent>
+
+ <groupId>org.apache.geronimo.javamail</groupId>
+ <artifactId>geronimo-javamail-1.4</artifactId>
+ <name>Geronimo JavaMail</name>
+ <packaging>pom</packaging>
+
+ <version>1.2</version>
+
+ <description>
+ Geronimmo JavaMail provider.
+ </description>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/geronimo/javamail/tags/geronimo-javamail_1.4-1.2</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/geronimo/javamail/tags/geronimo-javamail_1.4-1.2</developerConnection>
+ <url>http://svn.apache.org/viewvc/geronimo/javamail/tags/geronimo-javamail_1.4-1.2</url>
+ </scm>
+
+ <dependencyManagement>
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-activation_1.1_spec</artifactId>
+ <version>1.0</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-javamail_1.4_spec</artifactId>
+ <version>1.1</version>
+ </dependency>
+
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.4</source>
+ <target>1.4</target>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-idea-plugin</artifactId>
+ <configuration>
+ <jdkName>1.4</jdkName>
+ <linkModules>true</linkModules>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <configuration>
+ <tagBase>https://svn.apache.org/repos/asf/geronimo/javamail/tags</tagBase>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <repositories>
+ <!--
+ NOTE: The default repositories are picked up by Genesis, but need to
+ specify where Genesis lives to pick it up + any additional repositories.
+ -->
+
+ <repository>
+ <id>apache-snapshots</id>
+ <name>Apache Snapshots Repository</name>
+ <url>http://people.apache.org/repo/m2-snapshot-repository</url>
+ <layout>default</layout>
+ <snapshots>
+ <enabled>true</enabled>
+ <updatePolicy>daily</updatePolicy>
+ <checksumPolicy>ignore</checksumPolicy>
+ </snapshots>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ </repository>
+ </repositories>
+
+ <distributionManagement>
+ <site>
+ <id>geronimo-website</id>
+ <url>scp://people.apache.org/www/geronimo.apache.org/maven/javamail</url>
+ </site>
+ </distributionManagement>
+
+ <profiles>
+ <profile>
+ <id>default</id>
+
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+
+ <modules>
+ <module>geronimo-javamail_1.4_provider</module>
+ <module>geronimo-javamail_1.4_mail</module>
+ </modules>
+ </profile>
+ </profiles>
+
+</project>