diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index acfc8a3f..fe378a15 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,18 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
#
-# Copyright 2021 Google LLC
+# http://www.apache.org/licenses/LICENSE-2.0
#
-# 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
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.1
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
\ No newline at end of file
diff --git a/external/geronimo_javamail/LICENSE b/external/geronimo_javamail/LICENSE
new file mode 100644
index 00000000..0b3778bc
--- /dev/null
+++ b/external/geronimo_javamail/LICENSE
@@ -0,0 +1,257 @@
+
+ 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.
+
+
+#########################################################################
+## ADDITIONAL LICENSES ##
+#########################################################################
+
+The XMLSchema.dtd included in this project was developed by the
+W3C Consortium (http://www.w3c.org/).
+Use of the source code, thus licensed, and the resultant binary are
+subject to the terms and conditions of the following license.
+
+W3C¨ SOFTWARE NOTICE AND LICENSE
+Copyright © 1994-2002 World Wide Web Consortium, (Massachusetts Institute of
+Technology, Institut National de Recherche en Informatique et en Automatique,
+Keio University). All Rights Reserved. http://www.w3.org/Consortium/Legal/
+
+This W3C work (including software, documents, or other related items) is
+being provided by the copyright holders under the following license. By
+obtaining, using and/or copying this work, you (the licensee) agree that you
+have read, understood, and will comply with the following terms and
+conditions:
+
+Permission to use, copy, modify, and distribute this software and its
+documentation, with or without modification, for any purpose and without
+fee or royalty is hereby granted, provided that you include the following on
+ALL copies of the software and documentation or portions thereof, including
+modifications, that you make:
+
+ 1. The full text of this NOTICE in a location viewable to users of the
+ redistributed or derivative work.
+ 2. Any pre-existing intellectual property disclaimers, notices, or terms
+ and conditions. If none exist, a short notice of the following form
+ (hypertext is preferred, text is permitted) should be used within
+ the body of any redistributed or derivative code: "Copyright ©
+ [$date-of-software] World Wide Web Consortium, (Massachusetts Institute
+ of Technology, Institut National de Recherche en Informatique et en
+ Automatique, Keio University). All Rights Reserved.
+ http://www.w3.org/Consortium/Legal/"
+ 3. Notice of any changes or modifications to the W3C files, including the
+ date changes were made. (We recommend you provide URIs to the location
+ from which the code is derived.)
+
+THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE
+NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT
+THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS,
+COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
+
+COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
+
+The name and trademarks of copyright holders may NOT be used in advertising or
+publicity pertaining to the software without specific, written prior permission.
+Title to copyright in this software and any associated documentation will at all
+times remain with copyright holders.
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Address.java b/external/geronimo_javamail/src/main/java/javax/mail/Address.java
new file mode 100644
index 00000000..431084e5
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Address.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 javax.mail;
+
+import java.io.Serializable;
+
+/**
+ * This abstract class models the addresses in a message.
+ * Addresses are Serializable so that they may be serialized along with other search terms.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class Address implements Serializable {
+ /**
+ * Subclasses must provide a suitable implementation of equals().
+ *
+ * @param object the object to compare t
+ * @return true if the subclass determines the other object is equal to this Address
+ */
+ public abstract boolean equals(Object object);
+
+ /**
+ * Return a String that identifies this address type.
+ * @return the type of this address
+ */
+ public abstract String getType();
+
+ /**
+ * Subclasses must provide a suitable representation of their address.
+ * @return a representation of an Address as a String
+ */
+ public abstract String toString();
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/AuthenticationFailedException.java b/external/geronimo_javamail/src/main/java/javax/mail/AuthenticationFailedException.java
new file mode 100644
index 00000000..69a29bbb
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/AuthenticationFailedException.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 javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class AuthenticationFailedException extends MessagingException {
+ public AuthenticationFailedException() {
+ super();
+ }
+
+ public AuthenticationFailedException(String message) {
+ super(message);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Authenticator.java b/external/geronimo_javamail/src/main/java/javax/mail/Authenticator.java
new file mode 100644
index 00000000..4b71431e
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Authenticator.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 javax.mail;
+
+import java.net.InetAddress;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class Authenticator {
+ private InetAddress host;
+ private int port;
+ private String prompt;
+ private String protocol;
+ private String username;
+
+ synchronized PasswordAuthentication authenticate(InetAddress host, int port, String protocol, String prompt, String username) {
+ this.host = host;
+ this.port = port;
+ this.protocol = protocol;
+ this.prompt = prompt;
+ this.username = username;
+ return getPasswordAuthentication();
+ }
+
+ protected final String getDefaultUserName() {
+ return username;
+ }
+
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return null;
+ }
+
+ protected final int getRequestingPort() {
+ return port;
+ }
+
+ protected final String getRequestingPrompt() {
+ return prompt;
+ }
+
+ protected final String getRequestingProtocol() {
+ return protocol;
+ }
+
+ protected final InetAddress getRequestingSite() {
+ return host;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/BodyPart.java b/external/geronimo_javamail/src/main/java/javax/mail/BodyPart.java
new file mode 100644
index 00000000..917b6316
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/BodyPart.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class BodyPart implements Part {
+
+ protected Multipart parent;
+
+ public Multipart getParent() {
+ return parent;
+ }
+
+ // Can't be public. Not strictly required for spec, but mirrors Sun's javamail api impl.
+ void setParent(Multipart parent)
+ {
+ this.parent = parent;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/EventQueue.java b/external/geronimo_javamail/src/main/java/javax/mail/EventQueue.java
new file mode 100644
index 00000000..00f12758
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/EventQueue.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//
+// This source code implements specifications defined by the Java
+// Community Process. In order to remain compliant with the specification
+// DO NOT add / change / or delete method signatures!
+//
+package javax.mail;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.mail.event.MailEvent;
+
+/**
+ * This is an event queue to dispatch javamail events on separate threads
+ * from the main thread. EventQueues are created by javamail Services
+ * (Transport and Store instances), as well as Folders created from Store
+ * instances. Each entity will have its own private EventQueue instance, but
+ * will delay creating it until it has an event to dispatch to a real listener.
+ *
+ * NOTE: It would be nice to use the concurrency support in Java 5 to
+ * manage the queue, but this code needs to run on Java 1.4 still. We also
+ * don't want to have dependencies on other packages with this, so no
+ * outside concurrency packages can be used either.
+ * @version $Rev: 582842 $ $Date: 2007-10-08 10:13:51 -0500 (Mon, 08 Oct 2007) $
+ */
+class EventQueue implements Runnable {
+ /**
+ * The dispatch thread that handles notification events.
+ */
+ protected Thread dispatchThread;
+
+ /**
+ * The dispatching queue for events.
+ */
+ protected List eventQueue = new LinkedList();
+
+ /**
+ * Create a new EventQueue, including starting the new thread.
+ */
+ public EventQueue() {
+ dispatchThread = new Thread(this, "JavaMail-EventQueue");
+ dispatchThread.setDaemon(true); // this is a background server thread.
+ // start the thread up
+ dispatchThread.start();
+ }
+
+ /**
+ * When an object implementing interface Runnable
is used
+ * to create a thread, starting the thread causes the object's
+ * run
method to be called in that separately executing
+ * thread.
+ *
+ * The general contract of the method run
is that it may
+ * take any action whatsoever.
+ *
+ * @see java.lang.Thread#run()
+ */
+ public void run() {
+ try {
+ while (true) {
+ // get the next event
+ PendingEvent p = dequeueEvent();
+ // an empty event on the queue means time to shut things down.
+ if (p.event == null) {
+ return;
+ }
+
+ // and tap the listeners on the shoulder.
+ dispatchEvent(p.event, p.listeners);
+ }
+ } catch (InterruptedException e) {
+ // been told to stop, so we stop
+ }
+ }
+
+
+ /**
+ * Stop the EventQueue. This will terminate the dispatcher thread as soon
+ * as it can, so there may be undispatched events in the queue that will
+ * not get dispatched.
+ */
+ public synchronized void stop() {
+ // if the thread has not been stopped yet, interrupt it
+ // and clear the reference.
+ if (dispatchThread != null) {
+ // push a dummy marker on to the event queue
+ // to force the dispatch thread to wake up.
+ queueEvent(null, null);
+ dispatchThread = null;
+ }
+ }
+
+ /**
+ * Add a new event to the queue.
+ *
+ * @param event The event to dispatch.
+ * @param listeners The List of listeners to dispatch this to. This is assumed to be a
+ * static snapshot of the listeners that will not change between the time
+ * the event is queued and the dispatcher thread makes the calls to the
+ * handlers.
+ */
+ public synchronized void queueEvent(MailEvent event, List listeners) {
+ // add an element to the list, then notify the processing thread.
+ // Note that we make a copy of the listeners list. This ensures
+ // we're going to dispatch this to the snapshot of the listeners
+ PendingEvent p = new PendingEvent(event, listeners);
+ eventQueue.add(p);
+ // wake up the dispatch thread
+ notify();
+ }
+
+ /**
+ * Remove the next event from the message queue.
+ *
+ * @return The PendingEvent item from the queue.
+ */
+ protected synchronized PendingEvent dequeueEvent() throws InterruptedException {
+ // a little spin loop to wait for an event
+ while (eventQueue.isEmpty()) {
+ wait();
+ }
+
+ // just remove the first element of this
+ return (PendingEvent)eventQueue.remove(0);
+ }
+
+
+ /**
+ * Dispatch an event to a list of listeners. Any exceptions thrown by
+ * the listeners will be swallowed.
+ *
+ * @param event The event to dispatch.
+ * @param listeners The list of listeners this gets dispatched to.
+ */
+ protected void dispatchEvent(MailEvent event, List listeners) {
+ // iterate through the listeners list calling the handlers.
+ for (int i = 0; i < listeners.size(); i++) {
+ try {
+ event.dispatch(listeners.get(i));
+ } catch (Throwable e) {
+ // just eat these
+ }
+ }
+ }
+
+
+ /**
+ * Small helper class to give a single reference handle for a pending event.
+ */
+ class PendingEvent {
+ // the event we're broadcasting
+ MailEvent event;
+ // the list of listeners we send this to.
+ List listeners;
+
+ PendingEvent(MailEvent event, List listeners) {
+ this.event = event;
+ this.listeners = listeners;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/FetchProfile.java b/external/geronimo_javamail/src/main/java/javax/mail/FetchProfile.java
new file mode 100644
index 00000000..aba745a6
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/FetchProfile.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A FetchProfile defines a list of message attributes that a client wishes to prefetch
+ * from the server during a fetch operation.
+ *
+ * Clients can either specify individual headers, or can reference common profiles
+ * as defined by {@link FetchProfile.Item FetchProfile.Item}.
+ *
+ * @version $Rev: 582797 $ $Date: 2007-10-08 07:29:12 -0500 (Mon, 08 Oct 2007) $
+ */
+public class FetchProfile {
+ /**
+ * Inner class that defines sets of headers that are commonly bundled together
+ * in a FetchProfile.
+ */
+ public static class Item {
+ /**
+ * Item for fetching information about the content of the message.
+ *
+ * This includes all the headers about the content including but not limited to:
+ * Content-Type, Content-Disposition, Content-Description, Size and Line-Count
+ */
+ public static final Item CONTENT_INFO = new Item("CONTENT_INFO");
+
+ /**
+ * Item for fetching information about the envelope of the message.
+ *
+ * This includes all the headers comprising the envelope including but not limited to:
+ * From, To, Cc, Bcc, Reply-To, Subject and Date
+ *
+ * For IMAP4, this should also include the ENVELOPE data item.
+ *
+ */
+ public static final Item ENVELOPE = new Item("ENVELOPE");
+
+ /**
+ * Item for fetching information about message flags.
+ * Generall corresponds to the X-Flags header.
+ */
+ public static final Item FLAGS = new Item("FLAGS");
+
+ protected Item(String name) {
+ // hmmm, name is passed in but we are not allowed to provide accessors
+ // or to override equals/hashCode so what use is it?
+ }
+ }
+
+ // use Lists as we don't expect contains to be called often and the number of elements should be small
+ private final List items = new ArrayList();
+ private final List headers = new ArrayList();
+
+ /**
+ * Add a predefined profile of headers.
+ *
+ * @param item the profile to add
+ */
+ public void add(Item item) {
+ items.add(item);
+ }
+
+ /**
+ * Add a specific header.
+ * @param header the header whose value should be prefetched
+ */
+ public void add(String header) {
+ headers.add(header);
+ }
+
+ /**
+ * Determine if the given profile item is already included.
+ * @param item the profile to check for
+ * @return true if the profile item is already included
+ */
+ public boolean contains(Item item) {
+ return items.contains(item);
+ }
+
+ /**
+ * Determine if the specified header is already included.
+ * @param header the header to check for
+ * @return true if the header is already included
+ */
+ public boolean contains(String header) {
+ return headers.contains(header);
+ }
+
+ /**
+ * Get the profile items already included.
+ * @return the items already added to this profile
+ */
+ public Item[] getItems() {
+ return (Item[]) items.toArray(new Item[items.size()]);
+ }
+
+ /** Get the headers that have already been included.
+ * @return the headers already added to this profile
+ */
+ public String[] getHeaderNames() {
+ return (String[]) headers.toArray(new String[headers.size()]);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Flags.java b/external/geronimo_javamail/src/main/java/javax/mail/Flags.java
new file mode 100644
index 00000000..af153247
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Flags.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 javax.mail;
+
+import java.io.Serializable;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Representation of flags that may be associated with a message.
+ * Flags can either be system flags, defined by the {@link Flags.Flag Flag} inner class,
+ * or user-defined flags defined by a String. The system flags represent those expected
+ * to be provided by most folder systems; user-defined flags allow for additional flags
+ * on a per-provider basis.
+ *
search(term, getMessages())
+ * applying the search over all messages in the folder; subclasses may provide
+ * a more efficient mechanism.
+ *
+ * @param term the search criteria
+ * @return an array containing messages that match the criteria
+ * @throws MessagingException if there was a problem accessing the store
+ */
+ public Message[] search(SearchTerm term) throws MessagingException {
+ return search(term, getMessages());
+ }
+
+ /**
+ * Search the supplied messages for those that match the supplied criteria;
+ * messages must belong to this folder.
+ * The default implementation iterates through the messages, returning those
+ * whose {@link Message#match(javax.mail.search.SearchTerm)} method returns true;
+ * subclasses may provide a more efficient implementation.
+ *
+ * @param term the search criteria
+ * @param messages the messages to search
+ * @return an array containing messages that match the criteria
+ * @throws MessagingException if there was a problem accessing the store
+ */
+ public Message[] search(SearchTerm term, Message[] messages) throws MessagingException {
+ List result = new ArrayList(messages.length);
+ for (int i = 0; i < messages.length; i++) {
+ Message message = messages[i];
+ if (message.match(term)) {
+ result.add(message);
+ }
+ }
+ return (Message[]) result.toArray(new Message[result.size()]);
+ }
+
+ public void addConnectionListener(ConnectionListener listener) {
+ connectionListeners.add(listener);
+ }
+
+ public void removeConnectionListener(ConnectionListener listener) {
+ connectionListeners.remove(listener);
+ }
+
+ protected void notifyConnectionListeners(int type) {
+ queueEvent(new ConnectionEvent(this, type), connectionListeners);
+ }
+
+ public void addFolderListener(FolderListener listener) {
+ folderListeners.add(listener);
+ }
+
+ public void removeFolderListener(FolderListener listener) {
+ folderListeners.remove(listener);
+ }
+
+ protected void notifyFolderListeners(int type) {
+ queueEvent(new FolderEvent(this, this, type), folderListeners);
+ }
+
+ protected void notifyFolderRenamedListeners(Folder newFolder) {
+ queueEvent(new FolderEvent(this, this, newFolder, FolderEvent.RENAMED), folderListeners);
+ }
+
+ public void addMessageCountListener(MessageCountListener listener) {
+ messageCountListeners.add(listener);
+ }
+
+ public void removeMessageCountListener(MessageCountListener listener) {
+ messageCountListeners.remove(listener);
+ }
+
+ protected void notifyMessageAddedListeners(Message[] messages) {
+ queueEvent(new MessageCountEvent(this, MessageCountEvent.ADDED, false, messages), messageChangedListeners);
+ }
+
+ protected void notifyMessageRemovedListeners(boolean removed, Message[] messages) {
+ queueEvent(new MessageCountEvent(this, MessageCountEvent.REMOVED, removed, messages), messageChangedListeners);
+ }
+
+ public void addMessageChangedListener(MessageChangedListener listener) {
+ messageChangedListeners.add(listener);
+ }
+
+ public void removeMessageChangedListener(MessageChangedListener listener) {
+ messageChangedListeners.remove(listener);
+ }
+
+ protected void notifyMessageChangedListeners(int type, Message message) {
+ queueEvent(new MessageChangedEvent(this, type, message), messageChangedListeners);
+ }
+
+ /**
+ * Unregisters all listeners.
+ */
+ protected void finalize() throws Throwable {
+ // shut our queue down, if needed.
+ if (queue != null) {
+ queue.stop();
+ queue = null;
+ }
+ connectionListeners.clear();
+ folderListeners.clear();
+ messageChangedListeners.clear();
+ messageCountListeners.clear();
+ store = null;
+ super.finalize();
+ }
+
+ /**
+ * Returns the full name of this folder; if null, returns the value from the superclass.
+ * @return a string form of this folder
+ */
+ public String toString() {
+ String name = getFullName();
+ return name == null ? super.toString() : name;
+ }
+
+
+ /**
+ * Add an event on the event queue, creating the queue if this is the
+ * first event with actual listeners.
+ *
+ * @param event The event to dispatch.
+ * @param listeners The listener list.
+ */
+ private synchronized void queueEvent(MailEvent event, ArrayList listeners) {
+ // if there are no listeners to dispatch this to, don't put it on the queue.
+ // This allows us to delay creating the queue (and its new thread) until
+ // we
+ if (listeners.isEmpty()) {
+ return;
+ }
+ // first real event? Time to get the queue kicked off.
+ if (queue == null) {
+ queue = new EventQueue();
+ }
+ // tee it up and let it rip.
+ queue.queueEvent(event, (List)listeners.clone());
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/FolderClosedException.java b/external/geronimo_javamail/src/main/java/javax/mail/FolderClosedException.java
new file mode 100644
index 00000000..bdeaa718
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/FolderClosedException.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class FolderClosedException extends MessagingException {
+ private transient Folder _folder;
+
+ public FolderClosedException(Folder folder) {
+ this(folder, "Folder Closed: " + folder.getName());
+ }
+
+ public FolderClosedException(Folder folder, String message) {
+ super(message);
+ _folder = folder;
+ }
+
+ public Folder getFolder() {
+ return _folder;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/FolderNotFoundException.java b/external/geronimo_javamail/src/main/java/javax/mail/FolderNotFoundException.java
new file mode 100644
index 00000000..2add458e
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/FolderNotFoundException.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 javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class FolderNotFoundException extends MessagingException {
+ private transient Folder _folder;
+
+ public FolderNotFoundException() {
+ super();
+ }
+
+ public FolderNotFoundException(Folder folder) {
+ this(folder, "Folder not found: " + folder.getName());
+ }
+
+ public FolderNotFoundException(Folder folder, String message) {
+ super(message);
+ _folder = folder;
+ }
+
+ public FolderNotFoundException(String message, Folder folder) {
+ this(folder, message);
+ }
+
+ public Folder getFolder() {
+ return _folder;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Header.java b/external/geronimo_javamail/src/main/java/javax/mail/Header.java
new file mode 100644
index 00000000..bf5a6163
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Header.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * Class representing a header field.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class Header {
+ /**
+ * The name of the header.
+ */
+ protected String name;
+ /**
+ * The header value (can be null).
+ */
+ protected String value;
+
+ /**
+ * Constructor initializing all immutable fields.
+ *
+ * @param name the name of this header
+ * @param value the value of this header
+ */
+ public Header(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ /**
+ * Return the name of this header.
+ *
+ * @return the name of this header
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return the value of this header.
+ *
+ * @return the value of this header
+ */
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/IllegalWriteException.java b/external/geronimo_javamail/src/main/java/javax/mail/IllegalWriteException.java
new file mode 100644
index 00000000..dfc81ab2
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/IllegalWriteException.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 javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class IllegalWriteException extends MessagingException {
+ public IllegalWriteException() {
+ super();
+ }
+
+ public IllegalWriteException(String message) {
+ super(message);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Message.java b/external/geronimo_javamail/src/main/java/javax/mail/Message.java
new file mode 100644
index 00000000..8e91cd69
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Message.java
@@ -0,0 +1,434 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.io.InvalidObjectException;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.Date;
+import javax.mail.search.SearchTerm;
+
+/**
+ * @version $Rev: 578802 $ $Date: 2007-09-24 08:16:44 -0500 (Mon, 24 Sep 2007) $
+ */
+public abstract class Message implements Part {
+ /**
+ * Enumeration of types of recipients allowed by the Message class.
+ */
+ public static class RecipientType implements Serializable {
+ /**
+ * A "To" or primary recipient.
+ */
+ public static final RecipientType TO = new RecipientType("To");
+ /**
+ * A "Cc" or carbon-copy recipient.
+ */
+ public static final RecipientType CC = new RecipientType("Cc");
+ /**
+ * A "Bcc" or blind carbon-copy recipient.
+ */
+ public static final RecipientType BCC = new RecipientType("Bcc");
+ protected String type;
+
+ protected RecipientType(String type) {
+ this.type = type;
+ }
+
+ protected Object readResolve() throws ObjectStreamException {
+ if (type.equals("To")) {
+ return TO;
+ } else if (type.equals("Cc")) {
+ return CC;
+ } else if (type.equals("Bcc")) {
+ return BCC;
+ } else {
+ throw new InvalidObjectException("Invalid RecipientType: " + type);
+ }
+ }
+
+ public String toString() {
+ return type;
+ }
+ }
+
+ /**
+ * The index of a message within its folder, or zero if the message was not retrieved from a folder.
+ */
+ protected int msgnum;
+ /**
+ * True if this message has been expunged from the Store.
+ */
+ protected boolean expunged;
+ /**
+ * The {@link Folder} that contains this message, or null if it was not obtained from a folder.
+ */
+ protected Folder folder;
+ /**
+ * The {@link Session} associated with this message.
+ */
+ protected Session session;
+
+ /**
+ * Default constructor.
+ */
+ protected Message() {
+ }
+
+ /**
+ * Constructor initializing folder and message msgnum; intended to be used by implementations of Folder.
+ *
+ * @param folder the folder that contains the message
+ * @param msgnum the message index within the folder
+ */
+ protected Message(Folder folder, int msgnum) {
+ this.folder = folder;
+ this.msgnum = msgnum;
+ // make sure we copy the session information from the folder.
+ this.session = folder.getStore().getSession();
+ }
+
+ /**
+ * Constructor initializing the session; intended to by used by client created instances.
+ *
+ * @param session the session associated with this message
+ */
+ protected Message(Session session) {
+ this.session = session;
+ }
+
+ /**
+ * Return the "From" header indicating the identity of the person the message is from;
+ * in some circumstances this may be different than the actual sender.
+ *
+ * @return a list of addresses this message is from; may be empty if the header is present but empty, or null
+ * if the header is not present
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract Address[] getFrom() throws MessagingException;
+
+ /**
+ * Set the "From" header for this message to the value of the "mail.user" property,
+ * or if that property is not set, to the value of the system property "user.name"
+ *
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract void setFrom() throws MessagingException;
+
+ /**
+ * Set the "From" header to the supplied address.
+ *
+ * @param address the address of the person the message is from
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract void setFrom(Address address) throws MessagingException;
+
+ /**
+ * Add multiple addresses to the "From" header.
+ *
+ * @param addresses the addresses to add
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract void addFrom(Address[] addresses) throws MessagingException;
+
+ /**
+ * Get all recipients of the given type.
+ *
+ * @param type the type of recipient to get
+ * @return a list of addresses; may be empty if the header is present but empty,
+ * or null if the header is not present
+ * @throws MessagingException if there was a problem accessing the Store
+ * @see RecipientType
+ */
+ public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
+
+ /**
+ * Get all recipients of this message.
+ * The default implementation extracts the To, Cc, and Bcc recipients using {@link #getRecipients(javax.mail.Message.RecipientType)}
+ * and then concatentates the results into a single array; it returns null if no headers are defined.
+ *
+ * @return an array containing all recipients
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public Address[] getAllRecipients() throws MessagingException {
+ Address[] to = getRecipients(RecipientType.TO);
+ Address[] cc = getRecipients(RecipientType.CC);
+ Address[] bcc = getRecipients(RecipientType.BCC);
+ if (to == null && cc == null && bcc == null) {
+ return null;
+ }
+ int length = (to != null ? to.length : 0) + (cc != null ? cc.length : 0) + (bcc != null ? bcc.length : 0);
+ Address[] result = new Address[length];
+ int j = 0;
+ if (to != null) {
+ for (int i = 0; i < to.length; i++) {
+ result[j++] = to[i];
+ }
+ }
+ if (cc != null) {
+ for (int i = 0; i < cc.length; i++) {
+ result[j++] = cc[i];
+ }
+ }
+ if (bcc != null) {
+ for (int i = 0; i < bcc.length; i++) {
+ result[j++] = bcc[i];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Set the list of recipients for the specified type.
+ *
+ * @param type the type of recipient
+ * @param addresses the new addresses
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract void setRecipients(RecipientType type, Address[] addresses) throws MessagingException;
+
+ /**
+ * Set the list of recipients for the specified type to a single address.
+ *
+ * @param type the type of recipient
+ * @param address the new address
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public void setRecipient(RecipientType type, Address address) throws MessagingException {
+ setRecipients(type, new Address[]{address});
+ }
+
+ /**
+ * Add recipents of a specified type.
+ *
+ * @param type the type of recipient
+ * @param addresses the addresses to add
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract void addRecipients(RecipientType type, Address[] addresses) throws MessagingException;
+
+ /**
+ * Add a recipent of a specified type.
+ *
+ * @param type the type of recipient
+ * @param address the address to add
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public void addRecipient(RecipientType type, Address address) throws MessagingException {
+ addRecipients(type, new Address[]{address});
+ }
+
+ /**
+ * Get the addresses to which replies should be directed.
+ *
+ * As the most common behavior is to return to sender, the default implementation
+ * simply calls {@link #getFrom()}.
+ *
+ * @return a list of addresses to which replies should be directed
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public Address[] getReplyTo() throws MessagingException {
+ return getFrom();
+ }
+
+ /**
+ * Set the addresses to which replies should be directed.
+ *
+ * The default implementation throws a MethodNotSupportedException.
+ *
+ * @param addresses to which replies should be directed
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public void setReplyTo(Address[] addresses) throws MessagingException {
+ throw new MethodNotSupportedException("setReplyTo not supported");
+ }
+
+ /**
+ * Get the subject for this message.
+ *
+ * @return the subject
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract String getSubject() throws MessagingException;
+
+ /**
+ * Set the subject of this message
+ *
+ * @param subject the subject
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract void setSubject(String subject) throws MessagingException;
+
+ /**
+ * Return the date that this message was sent.
+ *
+ * @return the date this message was sent
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract Date getSentDate() throws MessagingException;
+
+ /**
+ * Set the date this message was sent.
+ *
+ * @param sent the date when this message was sent
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract void setSentDate(Date sent) throws MessagingException;
+
+ /**
+ * Return the date this message was received.
+ *
+ * @return the date this message was received
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract Date getReceivedDate() throws MessagingException;
+
+ /**
+ * Return a copy the flags associated with this message.
+ *
+ * @return a copy of the flags for this message
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract Flags getFlags() throws MessagingException;
+
+ /**
+ * Check whether the supplied flag is set.
+ * The default implementation checks the flags returned by {@link #getFlags()}.
+ *
+ * @param flag the flags to check for
+ * @return true if the flags is set
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public boolean isSet(Flags.Flag flag) throws MessagingException {
+ return getFlags().contains(flag);
+ }
+
+ /**
+ * Set the flags specified to the supplied value; flags not included in the
+ * supplied {@link Flags} parameter are not affected.
+ *
+ * @param flags the flags to modify
+ * @param set the new value of those flags
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract void setFlags(Flags flags, boolean set) throws MessagingException;
+
+ /**
+ * Set a flag to the supplied value.
+ * The default implmentation uses {@link #setFlags(Flags, boolean)}.
+ *
+ * @param flag the flag to set
+ * @param set the value for that flag
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public void setFlag(Flags.Flag flag, boolean set) throws MessagingException {
+ setFlags(new Flags(flag), set);
+ }
+
+ /**
+ * Return the message number for this Message.
+ * This number refers to the relative position of this message in a Folder; the message
+ * number for any given message can change during a session if the Folder is expunged.
+ * Message numbers for messages in a folder start at one; the value zero indicates that
+ * this message does not belong to a folder.
+ *
+ * @return the message number
+ */
+ public int getMessageNumber() {
+ return msgnum;
+ }
+
+ /**
+ * Set the message number for this Message.
+ * This must be invoked by implementation classes when the message number changes.
+ *
+ * @param number the new message number
+ */
+ protected void setMessageNumber(int number) {
+ msgnum = number;
+ }
+
+ /**
+ * Return the folder containing this message. If this is a new or nested message
+ * then this method returns null.
+ *
+ * @return the folder containing this message
+ */
+ public Folder getFolder() {
+ return folder;
+ }
+
+ /**
+ * Checks to see if this message has been expunged. If true, all methods other than
+ * {@link #getMessageNumber()} are invalid.
+ *
+ * @return true if this method has been expunged
+ */
+ public boolean isExpunged() {
+ return expunged;
+ }
+
+ /**
+ * Set the expunged flag for this message.
+ *
+ * @param expunged true if this message has been expunged
+ */
+ protected void setExpunged(boolean expunged) {
+ this.expunged = expunged;
+ }
+
+ /**
+ * Create a new message suitable as a reply to this message with all headers set
+ * up appropriately. The message body will be empty.
+ *
+ * if replyToAll is set then the new message will be addressed to all recipients
+ * of this message; otherwise the reply will be addressed only to the sender as
+ * returned by {@link #getReplyTo()}.
+ *
+ * The subject field will be initialized with the subject field from the orginal
+ * message; the text "Re:" will be prepended unless it is already present.
+ *
+ * @param replyToAll if true, indciates the message should be addressed to all recipients not just the sender
+ * @return a new message suitable as a reply to this message
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract Message reply(boolean replyToAll) throws MessagingException;
+
+ /**
+ * To ensure changes are saved to the Store, this message should be invoked
+ * before its containing Folder is closed. Implementations may save modifications
+ * immediately but are free to defer such updates to they may be sent to the server
+ * in one batch; if saveChanges is not called then such changes may not be made
+ * permanent.
+ *
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public abstract void saveChanges() throws MessagingException;
+
+ /**
+ * Apply the specified search criteria to this message
+ *
+ * @param term the search criteria
+ * @return true if this message matches the search criteria.
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public boolean match(SearchTerm term) throws MessagingException {
+ return term.match(this);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/MessageAware.java b/external/geronimo_javamail/src/main/java/javax/mail/MessageAware.java
new file mode 100644
index 00000000..9b1b83f3
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/MessageAware.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public interface MessageAware {
+ public abstract MessageContext getMessageContext();
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/MessageContext.java b/external/geronimo_javamail/src/main/java/javax/mail/MessageContext.java
new file mode 100644
index 00000000..0bcf8021
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/MessageContext.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * The context in which a piece of message content is contained.
+ *
+ * @version $Rev: 578802 $ $Date: 2007-09-24 08:16:44 -0500 (Mon, 24 Sep 2007) $
+ */
+public class MessageContext {
+ private final Part part;
+
+ /**
+ * Create a MessageContext object describing the context of the supplied Part.
+ *
+ * @param part the containing part
+ */
+ public MessageContext(Part part) {
+ this.part = part;
+ }
+
+ /**
+ * Return the {@link Part} that contains the content.
+ *
+ * @return the part
+ */
+ public Part getPart() {
+ return part;
+ }
+
+ /**
+ * Return the message that contains the content; if the Part is a {@link Multipart}
+ * then recurse up the chain until a {@link Message} is found.
+ *
+ * @return
+ */
+ public Message getMessage() {
+ return getMessageFrom(part);
+ }
+
+ /**
+ * Return the session associated with the Message containing this Part.
+ *
+ * @return the session associated with this context's root message
+ */
+ public Session getSession() {
+ Message message = getMessage();
+ if (message == null) {
+ return null;
+ } else {
+ return message.session;
+ }
+ }
+
+ /**
+ * recurse up the chain of MultiPart/BodyPart parts until we hit a message
+ *
+ * @param p The starting part.
+ *
+ * @return The encountered Message or null if no Message parts
+ * are found.
+ */
+ private Message getMessageFrom(Part p) {
+ while (p != null) {
+ if (p instanceof Message) {
+ return (Message) p;
+ }
+ Multipart mp = ((BodyPart) p).getParent();
+ if (mp == null) {
+ return null;
+ }
+ p = mp.getParent();
+ }
+ return null;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/MessageRemovedException.java b/external/geronimo_javamail/src/main/java/javax/mail/MessageRemovedException.java
new file mode 100644
index 00000000..38c5a23a
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/MessageRemovedException.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 javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class MessageRemovedException extends MessagingException {
+ public MessageRemovedException() {
+ super();
+ }
+
+ public MessageRemovedException(String message) {
+ super(message);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/MessagingException.java b/external/geronimo_javamail/src/main/java/javax/mail/MessagingException.java
new file mode 100644
index 00000000..bdbe5c92
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/MessagingException.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class MessagingException extends Exception {
+ // Required because serialization expects it to be here
+ private Exception next;
+
+ public MessagingException() {
+ super();
+ }
+
+ public MessagingException(String message) {
+ super(message);
+ }
+
+ public MessagingException(String message, Exception cause) {
+ super(message, cause);
+ next = cause;
+ }
+
+ public Exception getNextException() {
+ return next;
+ }
+
+ public synchronized boolean setNextException(Exception cause) {
+ if (next == null) {
+ initCause(cause);
+ next = cause;
+ return true;
+ } else if (next instanceof MessagingException) {
+ return ((MessagingException) next).setNextException(cause);
+ } else {
+ return false;
+ }
+ }
+
+ public String getMessage() {
+ Exception next = getNextException();
+ if (next == null) {
+ return super.getMessage();
+ } else {
+ return super.getMessage()
+ + " ("
+ + next.getClass().getName()
+ + ": "
+ + next.getMessage()
+ + ")";
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/MethodNotSupportedException.java b/external/geronimo_javamail/src/main/java/javax/mail/MethodNotSupportedException.java
new file mode 100644
index 00000000..7ff61320
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/MethodNotSupportedException.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 javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class MethodNotSupportedException extends MessagingException {
+ public MethodNotSupportedException() {
+ super();
+ }
+
+ public MethodNotSupportedException(String message) {
+ super(message);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Multipart.java b/external/geronimo_javamail/src/main/java/javax/mail/Multipart.java
new file mode 100644
index 00000000..1214f4a7
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Multipart.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Vector;
+
+/**
+ * A container for multiple {@link BodyPart BodyParts}.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class Multipart {
+ /**
+ * Vector of sub-parts.
+ */
+ protected Vector parts = new Vector();
+
+ /**
+ * The content type of this multipart object; defaults to "multipart/mixed"
+ */
+ protected String contentType = "multipart/mixed";
+
+ /**
+ * The Part that contains this multipart.
+ */
+ protected Part parent;
+
+ protected Multipart() {
+ }
+
+ /**
+ * Initialize this multipart object from the supplied data source.
+ * This adds any {@link BodyPart BodyParts} into this object and initializes the content type.
+ *
+ * @param mds the data source
+ * @throws MessagingException
+ */
+ protected void setMultipartDataSource(MultipartDataSource mds) throws MessagingException {
+ parts.clear();
+ contentType = mds.getContentType();
+ int size = mds.getCount();
+ for (int i = 0; i < size; i++) {
+ parts.add(mds.getBodyPart(i));
+ }
+ }
+
+ /**
+ * Return the content type.
+ *
+ * @return the content type
+ */
+ public String getContentType() {
+ return contentType;
+ }
+
+ /**
+ * Return the number of enclosed parts
+ *
+ * @return the number of parts
+ * @throws MessagingException
+ */
+ public int getCount() throws MessagingException {
+ return parts.size();
+ }
+
+ /**
+ * Get the specified part; numbering starts at zero.
+ *
+ * @param index the part to get
+ * @return the part
+ * @throws MessagingException
+ */
+ public BodyPart getBodyPart(int index) throws MessagingException {
+ return (BodyPart) parts.get(index);
+ }
+
+ /**
+ * Remove the supplied part from the list.
+ *
+ * @param part the part to remove
+ * @return true if the part was removed
+ * @throws MessagingException
+ */
+ public boolean removeBodyPart(BodyPart part) throws MessagingException {
+ return parts.remove(part);
+ }
+
+ /**
+ * Remove the specified part; all others move down one
+ *
+ * @param index the part to remove
+ * @throws MessagingException
+ */
+ public void removeBodyPart(int index) throws MessagingException {
+ parts.remove(index);
+ }
+
+ /**
+ * Add a part to the end of the list.
+ *
+ * @param part the part to add
+ * @throws MessagingException
+ */
+ public void addBodyPart(BodyPart part) throws MessagingException {
+ parts.add(part);
+ }
+
+ /**
+ * Insert a part into the list at a designated point; all subsequent parts move down
+ *
+ * @param part the part to add
+ * @param pos the index of the new part
+ * @throws MessagingException
+ */
+ public void addBodyPart(BodyPart part, int pos) throws MessagingException {
+ parts.add(pos, part);
+ }
+
+ /**
+ * Encode and write this multipart to the supplied OutputStream; the encoding
+ * used is determined by the implementation.
+ *
+ * @param out the stream to write to
+ * @throws IOException
+ * @throws MessagingException
+ */
+ public abstract void writeTo(OutputStream out) throws IOException, MessagingException;
+
+ /**
+ * Return the Part containing this Multipart object or null if unknown.
+ *
+ * @return this Multipart's parent
+ */
+ public Part getParent() {
+ return parent;
+ }
+
+ /**
+ * Set the parent of this Multipart object
+ *
+ * @param part this object's parent
+ */
+ public void setParent(Part part) {
+ parent = part;
+ }
+
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/MultipartDataSource.java b/external/geronimo_javamail/src/main/java/javax/mail/MultipartDataSource.java
new file mode 100644
index 00000000..0b6623e3
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/MultipartDataSource.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import javax.activation.DataSource;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public interface MultipartDataSource extends DataSource {
+ public abstract BodyPart getBodyPart(int index) throws MessagingException;
+
+ public abstract int getCount();
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/NoSuchProviderException.java b/external/geronimo_javamail/src/main/java/javax/mail/NoSuchProviderException.java
new file mode 100644
index 00000000..473a4b1f
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/NoSuchProviderException.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 javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class NoSuchProviderException extends MessagingException {
+ public NoSuchProviderException() {
+ super();
+ }
+
+ public NoSuchProviderException(String message) {
+ super(message);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Part.java b/external/geronimo_javamail/src/main/java/javax/mail/Part.java
new file mode 100644
index 00000000..663c150f
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Part.java
@@ -0,0 +1,315 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+import javax.activation.DataHandler;
+
+/**
+ * Note: Parts are used in Collections so implementing classes must provide
+ * a suitable implementation of equals and hashCode.
+ *
+ * @version $Rev: 578802 $ $Date: 2007-09-24 08:16:44 -0500 (Mon, 24 Sep 2007) $
+ */
+public interface Part {
+ /**
+ * This part should be presented as an attachment.
+ */
+ public static final String ATTACHMENT = "attachment";
+ /**
+ * This part should be presented or rendered inline.
+ */
+ public static final String INLINE = "inline";
+
+ /**
+ * Add this value to the existing headers with the given name. This method
+ * does not replace any headers that may already exist.
+ *
+ * @param name The name of the target header.
+ * @param value The value to be added to the header set.
+ *
+ * @exception MessagingException
+ */
+ public abstract void addHeader(String name, String value) throws MessagingException;
+
+ /**
+ * Return all headers as an Enumeration of Header objects.
+ *
+ * @return An Enumeration containing all of the current Header objects.
+ * @exception MessagingException
+ */
+ public abstract Enumeration getAllHeaders() throws MessagingException;
+
+ /**
+ * Return a content object for this Part. The
+ * content object type is dependent upon the
+ * DataHandler for the Part.
+ *
+ * @return A content object for this Part.
+ * @exception IOException
+ * @exception MessagingException
+ */
+ public abstract Object getContent() throws IOException, MessagingException;
+
+ /**
+ * Get the ContentType for this part, or null if the
+ * ContentType has not been set. The ContentType
+ * is expressed using the MIME typing system.
+ *
+ * @return The ContentType for this part.
+ * @exception MessagingException
+ */
+ public abstract String getContentType() throws MessagingException;
+
+ /**
+ * Returns a DataHandler instance for the content
+ * with in the Part.
+ *
+ * @return A DataHandler appropriate for the Part content.
+ * @exception MessagingException
+ */
+ public abstract DataHandler getDataHandler() throws MessagingException;
+
+ /**
+ * Returns a description string for this Part. Returns
+ * null if a description has not been set.
+ *
+ * @return The description string.
+ * @exception MessagingException
+ */
+ public abstract String getDescription() throws MessagingException;
+
+ /**
+ * Return the disposition of the part. The disposition
+ * determines how the part should be presented to the
+ * user. Two common disposition values are ATTACHMENT
+ * and INLINE.
+ *
+ * @return The current disposition value.
+ * @exception MessagingException
+ */
+ public abstract String getDisposition() throws MessagingException;
+
+ /**
+ * Get a file name associated with this part. The
+ * file name is useful for presenting attachment
+ * parts as their original source. The file names
+ * are generally simple names without containing
+ * any directory information. Returns null if the
+ * filename has not been set.
+ *
+ * @return The string filename, if any.
+ * @exception MessagingException
+ */
+ public abstract String getFileName() throws MessagingException;
+
+ /**
+ * Get all Headers for this header name. Returns null if no headers with
+ * the given name exist.
+ *
+ * @param name The target header name.
+ *
+ * @return An array of all matching header values, or null if the given header
+ * does not exist.
+ * @exception MessagingException
+ */
+ public abstract String[] getHeader(String name) throws MessagingException;
+
+ /**
+ * Return an InputStream for accessing the Part
+ * content. Any mail-related transfer encodings
+ * will be removed, so the data presented with
+ * be the actual part content.
+ *
+ * @return An InputStream for accessing the part content.
+ * @exception IOException
+ * @exception MessagingException
+ */
+ public abstract InputStream getInputStream() throws IOException, MessagingException;
+
+ /**
+ * Return the number of lines in the content, or
+ * -1 if the line count cannot be determined.
+ *
+ * @return The estimated number of lines in the content.
+ * @exception MessagingException
+ */
+ public abstract int getLineCount() throws MessagingException;
+
+ /**
+ * Return all headers that match the list of names as an Enumeration of
+ * Header objects.
+ *
+ * @param names An array of names of the desired headers.
+ *
+ * @return An Enumeration of Header objects containing the matching headers.
+ * @exception MessagingException
+ */
+ public abstract Enumeration getMatchingHeaders(String[] names) throws MessagingException;
+
+ /**
+ * Return an Enumeration of all Headers except those that match the names
+ * given in the exclusion list.
+ *
+ * @param names An array of String header names that will be excluded from the return
+ * Enumeration set.
+ *
+ * @return An Enumeration of Headers containing all headers except for those named
+ * in the exclusion list.
+ * @exception MessagingException
+ */
+ public abstract Enumeration getNonMatchingHeaders(String[] names) throws MessagingException;
+
+ /**
+ * Return the size of this part, or -1 if the size
+ * cannot be reliably determined.
+ *
+ * Note: the returned size does not take into account
+ * internal encodings, nor is it an estimate of
+ * how many bytes are required to transfer this
+ * part across a network. This value is intended
+ * to give email clients a rough idea of the amount
+ * of space that might be required to present the
+ * item.
+ *
+ * @return The estimated part size, or -1 if the size
+ * information cannot be determined.
+ * @exception MessagingException
+ */
+ public abstract int getSize() throws MessagingException;
+
+ /**
+ * Tests if the part is of the specified MIME type.
+ * Only the primaryPart and subPart of the MIME
+ * type are used for the comparison; arguments are
+ * ignored. The wildcard value of "*" may be used
+ * to match all subTypes.
+ *
+ * @param mimeType The target MIME type.
+ *
+ * @return true if the part matches the input MIME type,
+ * false if it is not of the requested type.
+ * @exception MessagingException
+ */
+ public abstract boolean isMimeType(String mimeType) throws MessagingException;
+
+ /**
+ * Remove all headers with the given name from the Part.
+ *
+ * @param name The target header name used for removal.
+ *
+ * @exception MessagingException
+ */
+ public abstract void removeHeader(String name) throws MessagingException;
+
+ public abstract void setContent(Multipart content) throws MessagingException;
+
+ /**
+ * Set a content object for this part. Internally,
+ * the Part will use the MIME type encoded in the
+ * type argument to wrap the provided content object.
+ * In order for this to work properly, an appropriate
+ * DataHandler must be installed in the Java Activation
+ * Framework.
+ *
+ * @param content The content object.
+ * @param type The MIME type for the inserted content Object.
+ *
+ * @exception MessagingException
+ */
+ public abstract void setContent(Object content, String type) throws MessagingException;
+
+ /**
+ * Set a DataHandler for this part that defines the
+ * Part content. The DataHandler is used to access
+ * all Part content.
+ *
+ * @param handler The DataHandler instance.
+ *
+ * @exception MessagingException
+ */
+ public abstract void setDataHandler(DataHandler handler) throws MessagingException;
+
+ /**
+ * Set a descriptive string for this part.
+ *
+ * @param description
+ * The new description.
+ *
+ * @exception MessagingException
+ */
+ public abstract void setDescription(String description) throws MessagingException;
+
+ /**
+ * Set the disposition for this Part.
+ *
+ * @param disposition
+ * The disposition string.
+ *
+ * @exception MessagingException
+ */
+ public abstract void setDisposition(String disposition) throws MessagingException;
+
+ /**
+ * Set a descriptive file name for this part. The
+ * name should be a simple name that does not include
+ * directory information.
+ *
+ * @param name The new name value.
+ *
+ * @exception MessagingException
+ */
+ public abstract void setFileName(String name) throws MessagingException;
+
+ /**
+ * Sets a value for the given header. This operation will replace all
+ * existing headers with the given name.
+ *
+ * @param name The name of the target header.
+ * @param value The new value for the indicated header.
+ *
+ * @exception MessagingException
+ */
+ public abstract void setHeader(String name, String value) throws MessagingException;
+
+ /**
+ * Set the Part content as text. This is a convenience method that sets
+ * the content to a MIME type of "text/plain".
+ *
+ * @param content The new text content, as a String object.
+ *
+ * @exception MessagingException
+ */
+ public abstract void setText(String content) throws MessagingException;
+
+ /**
+ * Write the Part content out to the provided OutputStream as a byte
+ * stream using an encoding appropriate to the Part content.
+ *
+ * @param out The target OutputStream.
+ *
+ * @exception IOException
+ * @exception MessagingException
+ */
+ public abstract void writeTo(OutputStream out) throws IOException, MessagingException;
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/PasswordAuthentication.java b/external/geronimo_javamail/src/main/java/javax/mail/PasswordAuthentication.java
new file mode 100644
index 00000000..e0e2e91c
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/PasswordAuthentication.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * A data holder used by Authenticator to contain a username and password.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public final class PasswordAuthentication {
+ private final String user;
+ private final String password;
+
+ public PasswordAuthentication(String user, String password) {
+ this.user = user;
+ this.password = password;
+ }
+
+ public String getUserName() {
+ return user;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Provider.java b/external/geronimo_javamail/src/main/java/javax/mail/Provider.java
new file mode 100644
index 00000000..7decc9a5
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Provider.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class Provider {
+ /**
+ * A enumeration inner class that defines Provider types.
+ */
+ public static class Type {
+ /**
+ * A message store provider such as POP3 or IMAP4.
+ */
+ public static final Type STORE = new Type();
+
+ /**
+ * A message transport provider such as SMTP.
+ */
+ public static final Type TRANSPORT = new Type();
+
+ private Type() {
+ }
+ }
+
+ private final String className;
+ private final String protocol;
+ private final Type type;
+ private final String vendor;
+ private final String version;
+
+ public Provider(Type type, String protocol, String className, String vendor, String version) {
+ this.protocol = protocol;
+ this.className = className;
+ this.type = type;
+ this.vendor = vendor;
+ this.version = version;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public String getVendor() {
+ return vendor;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String toString() {
+ return "protocol="
+ + protocol
+ + "; type="
+ + type
+ + "; class="
+ + className
+ + (vendor == null ? "" : "; vendor=" + vendor)
+ + (version == null ? "" : ";version=" + version);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Quota.java b/external/geronimo_javamail/src/main/java/javax/mail/Quota.java
new file mode 100644
index 00000000..85cfda47
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Quota.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * A representation of a Quota item for a given quota root.
+ *
+ * @version $Rev: 578802 $ $Date: 2007-09-24 08:16:44 -0500 (Mon, 24 Sep 2007) $
+ */
+public class Quota {
+ /**
+ * The name of the quota root.
+ */
+ public String quotaRoot;
+
+ /**
+ * The resources associated with this quota root.
+ */
+ public Resource[] resources;
+
+
+ /**
+ * Create a Quota with the given name and no resources.
+ *
+ * @param quotaRoot The quota root name.
+ */
+ public Quota(String quotaRoot) {
+ this.quotaRoot = quotaRoot;
+ }
+
+ /**
+ * Set a limit value for a resource. If the resource is not
+ * currently associated with this Quota, a new Resource item is
+ * added to the resources list.
+ *
+ * @param name The target resource name.
+ * @param limit The new limit value for the resource.
+ */
+ public void setResourceLimit(String name, long limit) {
+ Resource target = findResource(name);
+ target.limit = limit;
+ }
+
+ /**
+ * Locate a particular named resource, adding one to the list
+ * if it does not exist.
+ *
+ * @param name The target resource name.
+ *
+ * @return A Resource item for this named resource (either existing or new).
+ */
+ private Resource findResource(String name) {
+ // no resources yet? Make it so.
+ if (resources == null) {
+ Resource target = new Resource(name, 0, 0);
+ resources = new Resource[] { target };
+ return target;
+ }
+
+ // see if this one exists and return it.
+ for (int i = 0; i < resources.length; i++) {
+ Resource current = resources[i];
+ if (current.name.equalsIgnoreCase(name)) {
+ return current;
+ }
+ }
+
+ // have to extend the array...this is a pain.
+ Resource[] newResources = new Resource[resources.length + 1];
+ System.arraycopy(resources, 0, newResources, 0, resources.length);
+ Resource target = new Resource(name, 0, 0);
+ newResources[resources.length] = target;
+ resources = newResources;
+ return target;
+ }
+
+
+
+ /**
+ * A representation of a given resource definition.
+ */
+ public static class Resource {
+ /**
+ * The resource name.
+ */
+ public String name;
+ /**
+ * The current resource usage.
+ */
+ public long usage;
+ /**
+ * The limit value for this resource.
+ */
+ public long limit;
+
+
+ /**
+ * Construct a Resource object from the given name and usage/limit
+ * information.
+ *
+ * @param name The Resource name.
+ * @param usage The current resource usage.
+ * @param limit The Resource limit value.
+ */
+ public Resource(String name, long usage, long limit) {
+ this.name = name;
+ this.usage = usage;
+ this.limit = limit;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/QuotaAwareStore.java b/external/geronimo_javamail/src/main/java/javax/mail/QuotaAwareStore.java
new file mode 100644
index 00000000..b455fdb6
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/QuotaAwareStore.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * An interface for Store implementations to support the IMAP RFC 2087 Quota extension.
+ *
+ * @version $Rev: 581202 $ $Date: 2007-10-02 07:05:22 -0500 (Tue, 02 Oct 2007) $
+ */
+public interface QuotaAwareStore {
+
+ /**
+ * Get the quotas for the specified root element.
+ *
+ * @param root The root name for the quota information.
+ *
+ * @return An array of Quota objects defined for the root.
+ * @throws MessagingException if the quotas cannot be retrieved
+ */
+ public Quota[] getQuota(String root) throws javax.mail.MessagingException;
+
+ /**
+ * Set a quota item. The root contained in the Quota item identifies
+ * the quota target.
+ *
+ * @param quota The source quota item.
+ * @throws MessagingException if the quota cannot be set
+ */
+ public void setQuota(Quota quota) throws javax.mail.MessagingException;
+}
+
+
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/ReadOnlyFolderException.java b/external/geronimo_javamail/src/main/java/javax/mail/ReadOnlyFolderException.java
new file mode 100644
index 00000000..f0fa3cf3
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/ReadOnlyFolderException.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class ReadOnlyFolderException extends MessagingException {
+ private transient Folder _folder;
+
+ public ReadOnlyFolderException(Folder folder) {
+ this(folder, "Folder not found: " + folder.getName());
+ }
+
+ public ReadOnlyFolderException(Folder folder, String message) {
+ super(message);
+ _folder = folder;
+ }
+
+ public Folder getFolder() {
+ return _folder;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/SendFailedException.java b/external/geronimo_javamail/src/main/java/javax/mail/SendFailedException.java
new file mode 100644
index 00000000..0d3e8a9b
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/SendFailedException.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 javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class SendFailedException extends MessagingException {
+ protected transient Address invalid[];
+ protected transient Address validSent[];
+ protected transient Address validUnsent[];
+
+ public SendFailedException() {
+ super();
+ }
+
+ public SendFailedException(String message) {
+ super(message);
+ }
+
+ public SendFailedException(String message, Exception cause) {
+ super(message, cause);
+ }
+
+ public SendFailedException(String message,
+ Exception cause,
+ Address[] validSent,
+ Address[] validUnsent,
+ Address[] invalid) {
+ this(message, cause);
+ this.invalid = invalid;
+ this.validSent = validSent;
+ this.validUnsent = validUnsent;
+ }
+
+ public Address[] getValidSentAddresses() {
+ return validSent;
+ }
+
+ public Address[] getValidUnsentAddresses() {
+ return validUnsent;
+ }
+
+ public Address[] getInvalidAddresses() {
+ return invalid;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Service.java b/external/geronimo_javamail/src/main/java/javax/mail/Service.java
new file mode 100644
index 00000000..612729e6
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Service.java
@@ -0,0 +1,434 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Vector;
+
+import javax.mail.event.ConnectionEvent;
+import javax.mail.event.ConnectionListener;
+import javax.mail.event.MailEvent;
+
+/**
+ * @version $Rev: 597092 $ $Date: 2007-11-21 08:02:38 -0600 (Wed, 21 Nov 2007) $
+ */
+public abstract class Service {
+ /**
+ * The session from which this service was created.
+ */
+ protected Session session;
+ /**
+ * The URLName of this service
+ */
+ protected URLName url;
+ /**
+ * Debug flag for this service, set from the Session's debug flag.
+ */
+ protected boolean debug;
+
+ private boolean connected;
+ private final Vector connectionListeners = new Vector(2);
+ // the EventQueue spins off a new thread, so we only create this
+ // if we have actual listeners to dispatch an event to.
+ private EventQueue queue = null;
+ // when returning the URL, we need to ensure that the password and file information is
+ // stripped out.
+ private URLName exposedUrl;
+
+ /**
+ * Construct a new Service.
+ * @param session the session from which this service was created
+ * @param url the URLName of this service
+ */
+ protected Service(Session session, URLName url) {
+ this.session = session;
+ this.url = url;
+ this.debug = session.getDebug();
+ }
+
+ /**
+ * A generic connect method that takes no parameters allowing subclasses
+ * to implement an appropriate authentication scheme.
+ * The default implementation calls connect(null, null, null)
+ * @throws AuthenticationFailedException if authentication fails
+ * @throws MessagingException for other failures
+ */
+ public void connect() throws MessagingException {
+ connect(null, null, null);
+ }
+
+ /**
+ * Connect to the specified host using a simple username/password authenticaion scheme
+ * and the default port.
+ * The default implementation calls connect(host, -1, user, password)
+ *
+ * @param host the host to connect to
+ * @param user the user name
+ * @param password the user's password
+ * @throws AuthenticationFailedException if authentication fails
+ * @throws MessagingException for other failures
+ */
+ public void connect(String host, String user, String password) throws MessagingException {
+ connect(host, -1, user, password);
+ }
+
+ /**
+ * Connect to the specified host using a simple username/password authenticaion scheme
+ * and the default host and port.
+ * The default implementation calls connect(host, -1, user, password)
+ *
+ * @param user the user name
+ * @param password the user's password
+ * @throws AuthenticationFailedException if authentication fails
+ * @throws MessagingException for other failures
+ */
+ public void connect(String user, String password) throws MessagingException {
+ connect(null, -1, user, password);
+ }
+
+ /**
+ * Connect to the specified host at the specified port using a simple username/password authenticaion scheme.
+ *
+ * If this Service is already connected, an IllegalStateException is thrown.
+ *
+ * @param host the host to connect to
+ * @param port the port to connect to; pass -1 to use the default for the protocol
+ * @param user the user name
+ * @param password the user's password
+ * @throws AuthenticationFailedException if authentication fails
+ * @throws MessagingException for other failures
+ * @throws IllegalStateException if this service is already connected
+ */
+ public void connect(String host, int port, String user, String password) throws MessagingException {
+
+ if (isConnected()) {
+ throw new IllegalStateException("Already connected");
+ }
+
+ // before we try to connect, we need to derive values for some parameters that may not have
+ // been explicitly specified. For example, the normal connect() method leaves us to derive all
+ // of these from other sources. Some of the values are derived from our URLName value, others
+ // from session parameters. We need to go through all of these to develop a set of values we
+ // can connect with.
+
+ // this is the protocol we're connecting with. We use this largely to derive configured values from
+ // session properties.
+ String protocol = null;
+
+ // if we're working with the URL form, then we can retrieve the protocol from the URL.
+ if (url != null) {
+ protocol = url.getProtocol();
+ }
+
+ // if the port is -1, see if we have an override from url.
+ if (port == -1) {
+ if (protocol != null) {
+ port = url.getPort();
+ }
+ }
+
+ // now try to derive values for any of the arguments we've been given as defaults
+ if (host == null) {
+ // first choice is from the url, if we have
+ if (url != null) {
+ host = url.getHost();
+ // it is possible that this could return null (rare). If it does, try to get a
+ // value from a protocol specific session variable.
+ if (host == null) {
+ if (protocol != null) {
+ host = session.getProperty("mail." + protocol + ".host");
+ }
+ }
+ }
+ // this may still be null...get the global mail property
+ if (host == null) {
+ host = session.getProperty("mail.host");
+ }
+ }
+
+ // ok, go after userid information next.
+ if (user == null) {
+ // first choice is from the url, if we have
+ if (url != null) {
+ user = url.getUsername();
+ // make sure we get the password from the url, if we can.
+ if (password == null) {
+ password = url.getPassword();
+ }
+ // user still null? We have several levels of properties to try yet
+ if (user == null) {
+ if (protocol != null) {
+ user = session.getProperty("mail." + protocol + ".user");
+ }
+ }
+ }
+
+ // this may still be null...get the global mail property
+ if (user == null) {
+ user = session.getProperty("mail.user");
+ }
+
+ // finally, we try getting the system defined user name
+ try {
+ user = System.getProperty("user.name");
+ } catch (SecurityException e) {
+ // we ignore this, and just us a null username.
+ }
+ }
+ // if we have an explicitly given user name, we need to see if this matches the url one and
+ // grab the password from there.
+ else {
+ if (url != null && user.equals(url.getUsername())) {
+ password = url.getPassword();
+ }
+ }
+
+ // we need to update the URLName associated with this connection once we have all of the information,
+ // which means we also need to propogate the file portion of the URLName if we have this form when
+ // we start.
+ String file = null;
+ if (url != null) {
+ file = url.getFile();
+ }
+
+ // see if we have cached security information to use. If this is not cached, we'll save it
+ // after we successfully connect.
+ boolean cachePassword = false;
+
+
+ // still have a null password to this point, and using a url form?
+ if (password == null && url != null) {
+ // construct a new URL, filling in any pieces that may have been explicitly specified.
+ setURLName(new URLName(protocol, host, port, file, user, password));
+ // now see if we have a saved password from a previous request.
+ PasswordAuthentication cachedPassword = session.getPasswordAuthentication(getURLName());
+
+ // if we found a saved one, see if we need to get any the pieces from here.
+ if (cachedPassword != null) {
+ // not even a resolved userid? Then use both bits.
+ if (user == null) {
+ user = cachedPassword.getUserName();
+ password = cachedPassword.getPassword();
+ }
+ // our user name must match the cached name to be valid.
+ else if (user.equals(cachedPassword.getUserName())) {
+ password = cachedPassword.getPassword();
+ }
+ }
+ else
+ {
+ // nothing found in the cache, so we need to save this if we can connect successfully.
+ cachePassword = true;
+ }
+ }
+
+ // we've done our best up to this point to obtain all of the information needed to make the
+ // connection. Now we pass this off to the protocol handler to see if it works. If we get a
+ // connection failure, we may need to prompt for a password before continuing.
+ try {
+ connected = protocolConnect(host, port, user, password);
+ }
+ catch (AuthenticationFailedException e) {
+ }
+
+ if (!connected) {
+ InetAddress ipAddress = null;
+
+ try {
+ ipAddress = InetAddress.getByName(host);
+ } catch (UnknownHostException e) {
+ }
+
+ // now ask the session to try prompting for a password.
+ PasswordAuthentication promptPassword = session.requestPasswordAuthentication(ipAddress, port, protocol, null, user);
+
+ // if we were able to obtain new information from the session, then try again using the
+ // provided information .
+ if (promptPassword != null) {
+ user = promptPassword.getUserName();
+ password = promptPassword.getPassword();
+ }
+
+ connected = protocolConnect(host, port, user, password);
+ }
+
+
+ // if we're still not connected, then this is an exception.
+ if (!connected) {
+ throw new AuthenticationFailedException();
+ }
+
+ // the URL name needs to reflect the most recent information.
+ setURLName(new URLName(protocol, host, port, file, user, password));
+
+ // we need to update the global password cache with this information.
+ if (cachePassword) {
+ session.setPasswordAuthentication(getURLName(), new PasswordAuthentication(user, password));
+ }
+
+ // we're now connected....broadcast this to any interested parties.
+ setConnected(connected);
+ notifyConnectionListeners(ConnectionEvent.OPENED);
+ }
+
+ /**
+ * Attempt the protocol-specific connection; subclasses should override this to establish
+ * a connection in the appropriate manner.
+ *
+ * This method should return true if the connection was established.
+ * It may return false to cause the {@link #connect(String, int, String, String)} method to
+ * reattempt the connection after trying to obtain user and password information from the user.
+ * Alternatively it may throw a AuthenticatedFailedException to abandon the conection attempt.
+ *
+ * @param host The target host name of the service.
+ * @param port The connection port for the service.
+ * @param user The user name used for the connection.
+ * @param password The password used for the connection.
+ *
+ * @return true if a connection was established, false if there was authentication
+ * error with the connection.
+ * @throws AuthenticationFailedException
+ * if authentication fails
+ * @throws MessagingException
+ * for other failures
+ */
+ protected boolean protocolConnect(String host, int port, String user, String password) throws MessagingException {
+ return false;
+ }
+
+ /**
+ * Check if this service is currently connected.
+ * The default implementation simply returns the value of a private boolean field;
+ * subclasses may wish to override this method to verify the physical connection.
+ *
+ * @return true if this service is connected
+ */
+ public boolean isConnected() {
+ return connected;
+ }
+
+ /**
+ * Notification to subclasses that the connection state has changed.
+ * This method is called by the connect() and close() methods to indicate state change;
+ * subclasses should also call this method if the connection is automatically closed
+ * for some reason.
+ *
+ * @param connected the connection state
+ */
+ protected void setConnected(boolean connected) {
+ this.connected = connected;
+ }
+
+ /**
+ * Close this service and terminate its physical connection.
+ * The default implementation simply calls setConnected(false) and then
+ * sends a CLOSED event to all registered ConnectionListeners.
+ * Subclasses overriding this method should still ensure it is closed; they should
+ * also ensure that it is called if the connection is closed automatically, for
+ * for example in a finalizer.
+ *
+ *@throws MessagingException if there were errors closing; the connection is still closed
+ */
+ public void close() throws MessagingException {
+ setConnected(false);
+ notifyConnectionListeners(ConnectionEvent.CLOSED);
+ }
+
+ /**
+ * Return a copy of the URLName representing this service with the password and file information removed.
+ *
+ * @return the URLName for this service
+ */
+ public URLName getURLName() {
+ // if we haven't composed the URL version we hand out, create it now. But only if we really
+ // have a URL.
+ if (exposedUrl == null) {
+ if (url != null) {
+ exposedUrl = new URLName(url.getProtocol(), url.getHost(), url.getPort(), null, url.getUsername(), null);
+ }
+ }
+ return exposedUrl;
+ }
+
+ /**
+ * Set the url field.
+ * @param url the new value
+ */
+ protected void setURLName(URLName url) {
+ this.url = url;
+ }
+
+ public void addConnectionListener(ConnectionListener listener) {
+ connectionListeners.add(listener);
+ }
+
+ public void removeConnectionListener(ConnectionListener listener) {
+ connectionListeners.remove(listener);
+ }
+
+ protected void notifyConnectionListeners(int type) {
+ queueEvent(new ConnectionEvent(this, type), connectionListeners);
+ }
+
+ public String toString() {
+ // NOTE: We call getURLName() rather than use the URL directly
+ // because the get method strips out the password information.
+ URLName url = getURLName();
+
+ return url == null ? super.toString() : url.toString();
+ }
+
+ protected void queueEvent(MailEvent event, Vector listeners) {
+ // if there are no listeners to dispatch this to, don't put it on the queue.
+ // This allows us to delay creating the queue (and its new thread) until
+ // we
+ if (listeners.isEmpty()) {
+ return;
+ }
+ // first real event? Time to get the queue kicked off.
+ if (queue == null) {
+ queue = new EventQueue();
+ }
+ // tee it up and let it rip.
+ queue.queueEvent(event, (List)listeners.clone());
+ }
+
+ protected void finalize() throws Throwable {
+ // stop our event queue if we had to create one
+ if (queue != null) {
+ queue.stop();
+ }
+ connectionListeners.clear();
+ super.finalize();
+ }
+
+
+ /**
+ * Package scope utility method to allow Message instances
+ * access to the Service's session.
+ *
+ * @return The Session the service is associated with.
+ */
+ Session getSession() {
+ return session;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Session.java b/external/geronimo_javamail/src/main/java/javax/mail/Session.java
new file mode 100644
index 00000000..240def8e
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Session.java
@@ -0,0 +1,807 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.WeakHashMap;
+
+
+/**
+ * OK, so we have a final class in the API with a heck of a lot of implementation required...
+ * let's try and figure out what it is meant to do.
+ *
+ * It is supposed to collect together properties and defaults so that they can be
+ * shared by multiple applications on a desktop; with process isolation and no
+ * real concept of shared memory, this seems challenging. These properties and
+ * defaults rely on system properties, making management in a app server harder,
+ * and on resources loaded from "mail.jar" which may lead to skew between
+ * differnet independent implementations of this API.
+ *
+ * @version $Rev: 702537 $ $Date: 2008-10-07 11:39:34 -0500 (Tue, 07 Oct 2008) $
+ */
+public final class Session {
+ private static final Class[] PARAM_TYPES = {Session.class, URLName.class};
+ private static final WeakHashMap addressMapsByClassLoader = new WeakHashMap();
+ private static Session DEFAULT_SESSION;
+
+ private Map passwordAuthentications = new HashMap();
+
+ private final Properties properties;
+ private final Authenticator authenticator;
+ private boolean debug;
+ private PrintStream debugOut = System.out;
+
+ private static final WeakHashMap providersByClassLoader = new WeakHashMap();
+
+ /**
+ * No public constrcutor allowed.
+ */
+ private Session(Properties properties, Authenticator authenticator) {
+ this.properties = properties;
+ this.authenticator = authenticator;
+ debug = Boolean.valueOf(properties.getProperty("mail.debug")).booleanValue();
+ }
+
+ /**
+ * Create a new session initialized with the supplied properties which uses the supplied authenticator.
+ * Clients should ensure the properties listed in Appendix A of the JavaMail specification are
+ * set as the defaults are unlikey to work in most scenarios; particular attention should be given
+ * to:
+ *
+ * - mail.store.protocol
+ * - mail.transport.protocol
+ * - mail.host
+ * - mail.user
+ * - mail.from
+ *
+ *
+ * @param properties the session properties
+ * @param authenticator an authenticator for callbacks to the user
+ * @return a new session
+ */
+ public static Session getInstance(Properties properties, Authenticator authenticator) {
+ return new Session(new Properties(properties), authenticator);
+ }
+
+ /**
+ * Create a new session initialized with the supplied properties with no authenticator.
+ *
+ * @param properties the session properties
+ * @return a new session
+ * @see #getInstance(java.util.Properties, Authenticator)
+ */
+ public static Session getInstance(Properties properties) {
+ return getInstance(properties, null);
+ }
+
+ /**
+ * Get the "default" instance assuming no authenticator is required.
+ *
+ * @param properties the session properties
+ * @return if "default" session
+ * @throws SecurityException if the does not have permission to access the default session
+ */
+ public synchronized static Session getDefaultInstance(Properties properties) {
+ return getDefaultInstance(properties, null);
+ }
+
+ /**
+ * Get the "default" session.
+ * If there is not current "default", a new Session is created and installed as the default.
+ *
+ * @param properties
+ * @param authenticator
+ * @return if "default" session
+ * @throws SecurityException if the does not have permission to access the default session
+ */
+ public synchronized static Session getDefaultInstance(Properties properties, Authenticator authenticator) {
+ if (DEFAULT_SESSION == null) {
+ DEFAULT_SESSION = getInstance(properties, authenticator);
+ } else {
+ if (authenticator != DEFAULT_SESSION.authenticator) {
+ if (authenticator == null || DEFAULT_SESSION.authenticator == null || authenticator.getClass().getClassLoader() != DEFAULT_SESSION.authenticator.getClass().getClassLoader()) {
+ throw new SecurityException();
+ }
+ }
+ // todo we should check with the SecurityManager here as well
+ }
+ return DEFAULT_SESSION;
+ }
+
+ /**
+ * Enable debugging for this session.
+ * Debugging can also be enabled by setting the "mail.debug" property to true when
+ * the session is being created.
+ *
+ * @param debug the debug setting
+ */
+ public void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+ /**
+ * Get the debug setting for this session.
+ *
+ * @return the debug setting
+ */
+ public boolean getDebug() {
+ return debug;
+ }
+
+ /**
+ * Set the output stream where debug information should be sent.
+ * If set to null, System.out will be used.
+ *
+ * @param out the stream to write debug information to
+ */
+ public void setDebugOut(PrintStream out) {
+ debugOut = out == null ? System.out : out;
+ }
+
+ /**
+ * Return the debug output stream.
+ *
+ * @return the debug output stream
+ */
+ public PrintStream getDebugOut() {
+ return debugOut;
+ }
+
+ /**
+ * Return the list of providers available to this application.
+ * This method searches for providers that are defined in the javamail.providers
+ * and javamail.default.providers resources available through the current context
+ * classloader, or if that is not available, the classloader that loaded this class.
+ *
+ * As searching for providers is potentially expensive, this implementation maintains
+ * a WeakHashMap of providers indexed by ClassLoader.
+ *
+ * @return an array of providers
+ */
+ public Provider[] getProviders() {
+ ProviderInfo info = getProviderInfo();
+ return (Provider[]) info.all.toArray(new Provider[info.all.size()]);
+ }
+
+ /**
+ * Return the provider for a specific protocol.
+ * This implementation initially looks in the Session properties for an property with the name
+ * "mail..class"; if found it attempts to create an instance of the class named in that
+ * property throwing a NoSuchProviderException if the class cannot be loaded.
+ * If this property is not found, it searches the providers returned by {@link #getProviders()}
+ * for a entry for the specified protocol.
+ *
+ * @param protocol the protocol to get a provider for
+ * @return a provider for that protocol
+ * @throws NoSuchProviderException
+ */
+ public Provider getProvider(String protocol) throws NoSuchProviderException {
+ ProviderInfo info = getProviderInfo();
+ Provider provider = null;
+ String providerName = properties.getProperty("mail." + protocol + ".class");
+ if (providerName != null) {
+ provider = (Provider) info.byClassName.get(providerName);
+ if (debug) {
+ writeDebug("DEBUG: new provider loaded: " + provider.toString());
+ }
+ }
+
+ // if not able to locate this by class name, just grab a registered protocol.
+ if (provider == null) {
+ provider = (Provider) info.byProtocol.get(protocol);
+ }
+
+ if (provider == null) {
+ throw new NoSuchProviderException("Unable to locate provider for protocol: " + protocol);
+ }
+ if (debug) {
+ writeDebug("DEBUG: getProvider() returning provider " + provider.toString());
+ }
+ return provider;
+ }
+
+ /**
+ * Make the supplied Provider the default for its protocol.
+ *
+ * @param provider the new default Provider
+ * @throws NoSuchProviderException
+ */
+ public void setProvider(Provider provider) throws NoSuchProviderException {
+ ProviderInfo info = getProviderInfo();
+ info.byProtocol.put(provider.getProtocol(), provider);
+ }
+
+ /**
+ * Return a Store for the default protocol defined by the mail.store.protocol property.
+ *
+ * @return the store for the default protocol
+ * @throws NoSuchProviderException
+ */
+ public Store getStore() throws NoSuchProviderException {
+ String protocol = properties.getProperty("mail.store.protocol");
+ if (protocol == null) {
+ throw new NoSuchProviderException("mail.store.protocol property is not set");
+ }
+ return getStore(protocol);
+ }
+
+ /**
+ * Return a Store for the specified protocol.
+ *
+ * @param protocol the protocol to get a Store for
+ * @return a Store
+ * @throws NoSuchProviderException if no provider is defined for the specified protocol
+ */
+ public Store getStore(String protocol) throws NoSuchProviderException {
+ Provider provider = getProvider(protocol);
+ return getStore(provider);
+ }
+
+ /**
+ * Return a Store for the protocol specified in the given URL
+ *
+ * @param url the URL of the Store
+ * @return a Store
+ * @throws NoSuchProviderException if no provider is defined for the specified protocol
+ */
+ public Store getStore(URLName url) throws NoSuchProviderException {
+ return (Store) getService(getProvider(url.getProtocol()), url);
+ }
+
+ /**
+ * Return the Store specified by the given provider.
+ *
+ * @param provider the provider to create from
+ * @return a Store
+ * @throws NoSuchProviderException if there was a problem creating the Store
+ */
+ public Store getStore(Provider provider) throws NoSuchProviderException {
+ if (Provider.Type.STORE != provider.getType()) {
+ throw new NoSuchProviderException("Not a Store Provider: " + provider);
+ }
+ return (Store) getService(provider, null);
+ }
+
+ /**
+ * Return a closed folder for the supplied URLName, or null if it cannot be obtained.
+ *
+ * The scheme portion of the URL is used to locate the Provider and create the Store;
+ * the returned Store is then used to obtain the folder.
+ *
+ * @param name the location of the folder
+ * @return the requested folder, or null if it is unavailable
+ * @throws NoSuchProviderException if there is no provider
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public Folder getFolder(URLName name) throws MessagingException {
+ Store store = getStore(name);
+ return store.getFolder(name);
+ }
+
+ /**
+ * Return a Transport for the default protocol specified by the
+ * mail.transport.protocol
property.
+ *
+ * @return a Transport
+ * @throws NoSuchProviderException
+ */
+ public Transport getTransport() throws NoSuchProviderException {
+ String protocol = properties.getProperty("mail.transport.protocol");
+ if (protocol == null) {
+ throw new NoSuchProviderException("mail.transport.protocol property is not set");
+ }
+ return getTransport(protocol);
+ }
+
+ /**
+ * Return a Transport for the specified protocol.
+ *
+ * @param protocol the protocol to use
+ * @return a Transport
+ * @throws NoSuchProviderException
+ */
+ public Transport getTransport(String protocol) throws NoSuchProviderException {
+ Provider provider = getProvider(protocol);
+ return getTransport(provider);
+ }
+
+ /**
+ * Return a transport for the protocol specified in the URL.
+ *
+ * @param name the URL whose scheme specifies the protocol
+ * @return a Transport
+ * @throws NoSuchProviderException
+ */
+ public Transport getTransport(URLName name) throws NoSuchProviderException {
+ return (Transport) getService(getProvider(name.getProtocol()), name);
+ }
+
+ /**
+ * Return a transport for the protocol associated with the type of this address.
+ *
+ * @param address the address we are trying to deliver to
+ * @return a Transport
+ * @throws NoSuchProviderException
+ */
+ public Transport getTransport(Address address) throws NoSuchProviderException {
+ String type = address.getType();
+ // load the address map from the resource files.
+ Map addressMap = getAddressMap();
+ String protocolName = (String)addressMap.get(type);
+ if (protocolName == null) {
+ throw new NoSuchProviderException("No provider for address type " + type);
+ }
+ return getTransport(protocolName);
+ }
+
+ /**
+ * Return the Transport specified by a Provider
+ *
+ * @param provider the defining Provider
+ * @return a Transport
+ * @throws NoSuchProviderException
+ */
+ public Transport getTransport(Provider provider) throws NoSuchProviderException {
+ return (Transport) getService(provider, null);
+ }
+
+ /**
+ * Set the password authentication associated with a URL.
+ *
+ * @param name the url
+ * @param authenticator the authenticator
+ */
+ public void setPasswordAuthentication(URLName name, PasswordAuthentication authenticator) {
+ if (authenticator == null) {
+ passwordAuthentications.remove(name);
+ } else {
+ passwordAuthentications.put(name, authenticator);
+ }
+ }
+
+ /**
+ * Get the password authentication associated with a URL
+ *
+ * @param name the URL
+ * @return any authenticator for that url, or null if none
+ */
+ public PasswordAuthentication getPasswordAuthentication(URLName name) {
+ return (PasswordAuthentication) passwordAuthentications.get(name);
+ }
+
+ /**
+ * Call back to the application supplied authenticator to get the needed username add password.
+ *
+ * @param host the host we are trying to connect to, may be null
+ * @param port the port on that host
+ * @param protocol the protocol trying to be used
+ * @param prompt a String to show as part of the prompt, may be null
+ * @param defaultUserName the default username, may be null
+ * @return the authentication information collected by the authenticator; may be null
+ */
+ public PasswordAuthentication requestPasswordAuthentication(InetAddress host, int port, String protocol, String prompt, String defaultUserName) {
+ if (authenticator == null) {
+ return null;
+ }
+ return authenticator.authenticate(host, port, protocol, prompt, defaultUserName);
+ }
+
+ /**
+ * Return the properties object for this Session; this is a live collection.
+ *
+ * @return the properties for the Session
+ */
+ public Properties getProperties() {
+ return properties;
+ }
+
+ /**
+ * Return the specified property.
+ *
+ * @param property the property to get
+ * @return its value, or null if not present
+ */
+ public String getProperty(String property) {
+ return getProperties().getProperty(property);
+ }
+
+
+ /**
+ * Add a provider to the Session managed provider list.
+ *
+ * @param provider The new provider to add.
+ */
+ public synchronized void addProvider(Provider provider) {
+ ProviderInfo info = getProviderInfo();
+ info.addProvider(provider);
+ }
+
+
+
+ /**
+ * Add a mapping between an address type and a protocol used
+ * to process that address type.
+ *
+ * @param addressType
+ * The address type identifier.
+ * @param protocol The protocol name mapping.
+ */
+ public void setProtocolForAddress(String addressType, String protocol) {
+ Map addressMap = getAddressMap();
+
+ // no protocol specified is a removal
+ if (protocol == null) {
+ addressMap.remove(addressType);
+ }
+ else {
+ addressMap.put(addressType, protocol);
+ }
+ }
+
+
+ private Service getService(Provider provider, URLName name) throws NoSuchProviderException {
+ try {
+ if (name == null) {
+ name = new URLName(provider.getProtocol(), null, -1, null, null, null);
+ }
+ ClassLoader cl = getClassLoader();
+ Class clazz = cl.loadClass(provider.getClassName());
+ Constructor ctr = clazz.getConstructor(PARAM_TYPES);
+ return (Service) ctr.newInstance(new Object[]{this, name});
+ } catch (ClassNotFoundException e) {
+ throw (NoSuchProviderException) new NoSuchProviderException("Unable to load class for provider: " + provider).initCause(e);
+ } catch (NoSuchMethodException e) {
+ throw (NoSuchProviderException) new NoSuchProviderException("Provider class does not have a constructor(Session, URLName): " + provider).initCause(e);
+ } catch (InstantiationException e) {
+ throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
+ } catch (IllegalAccessException e) {
+ throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
+ } catch (InvocationTargetException e) {
+ throw (NoSuchProviderException) new NoSuchProviderException("Exception from constructor of provider class: " + provider).initCause(e.getCause());
+ }
+ }
+
+ private ProviderInfo getProviderInfo() {
+ ClassLoader cl = getClassLoader();
+ synchronized (providersByClassLoader) {
+ ProviderInfo info = (ProviderInfo) providersByClassLoader.get(cl);
+ if (info == null) {
+ info = loadProviders(cl);
+ }
+ return info;
+ }
+ }
+
+ private synchronized Map getAddressMap() {
+ ClassLoader cl = getClassLoader();
+ Map addressMap = (Map)addressMapsByClassLoader.get(cl);
+ if (addressMap == null) {
+ addressMap = loadAddressMap(cl);
+ }
+ return addressMap;
+ }
+
+
+ /**
+ * Resolve a class loader used to resolve context resources. The
+ * class loader used is either a current thread context class
+ * loader (if set), the class loader used to load an authenticator
+ * we've been initialized with, or the class loader used to load
+ * this class instance (which may be a subclass of Session).
+ *
+ * @return The class loader used to load resources.
+ */
+ private ClassLoader getClassLoader() {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ if (cl == null) {
+ if (authenticator != null) {
+ cl = authenticator.getClass().getClassLoader();
+ }
+ else {
+ cl = this.getClass().getClassLoader();
+ }
+ }
+ return cl;
+ }
+
+ private ProviderInfo loadProviders(ClassLoader cl) {
+ // we create a merged map from reading all of the potential address map entries. The locations
+ // searched are:
+ // 1. java.home/lib/javamail.address.map
+ // 2. META-INF/javamail.address.map
+ // 3. META-INF/javamail.default.address.map
+ //
+ ProviderInfo info = new ProviderInfo();
+
+ // NOTE: Unlike the addressMap, we process these in the defined order. The loading routine
+ // will not overwrite entries if they already exist in the map.
+
+ try {
+ File file = new File(System.getProperty("java.home"), "lib/javamail.providers");
+ InputStream is = new FileInputStream(file);
+ try {
+ loadProviders(info, is);
+ if (debug) {
+ writeDebug("Loaded lib/javamail.providers from " + file.toString());
+ }
+ } finally{
+ is.close();
+ }
+ } catch (SecurityException e) {
+ // ignore
+ } catch (IOException e) {
+ // ignore
+ }
+
+ try {
+ Enumeration e = cl.getResources("META-INF/javamail.providers");
+ while (e.hasMoreElements()) {
+ URL url = (URL) e.nextElement();
+ if (debug) {
+ writeDebug("Loading META-INF/javamail.providers from " + url.toString());
+ }
+ InputStream is = url.openStream();
+ try {
+ loadProviders(info, is);
+ } finally{
+ is.close();
+ }
+ }
+ } catch (SecurityException e) {
+ // ignore
+ } catch (IOException e) {
+ // ignore
+ }
+
+ try {
+ Enumeration e = cl.getResources("META-INF/javamail.default.providers");
+ while (e.hasMoreElements()) {
+ URL url = (URL) e.nextElement();
+ if (debug) {
+ writeDebug("Loading javamail.default.providers from " + url.toString());
+ }
+
+ InputStream is = url.openStream();
+ try {
+ loadProviders(info, is);
+ } finally{
+ is.close();
+ }
+ }
+ } catch (SecurityException e) {
+ // ignore
+ } catch (IOException e) {
+ // ignore
+ }
+
+ // make sure this is added to the global map.
+ providersByClassLoader.put(cl, info);
+
+ return info;
+ }
+
+ private void loadProviders(ProviderInfo info, InputStream is) throws IOException {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ // Lines beginning with "#" are just comments.
+ if (line.startsWith("#")) {
+ continue;
+ }
+
+ StringTokenizer tok = new StringTokenizer(line, ";");
+ String protocol = null;
+ Provider.Type type = null;
+ String className = null;
+ String vendor = null;
+ String version = null;
+ while (tok.hasMoreTokens()) {
+ String property = tok.nextToken();
+ int index = property.indexOf('=');
+ if (index == -1) {
+ continue;
+ }
+ String key = property.substring(0, index).trim().toLowerCase();
+ String value = property.substring(index+1).trim();
+ if (protocol == null && "protocol".equals(key)) {
+ protocol = value;
+ } else if (type == null && "type".equals(key)) {
+ if ("store".equals(value)) {
+ type = Provider.Type.STORE;
+ } else if ("transport".equals(value)) {
+ type = Provider.Type.TRANSPORT;
+ }
+ } else if (className == null && "class".equals(key)) {
+ className = value;
+ } else if ("vendor".equals(key)) {
+ vendor = value;
+ } else if ("version".equals(key)) {
+ version = value;
+ }
+ }
+ if (protocol == null || type == null || className == null) {
+ //todo should we log a warning?
+ continue;
+ }
+
+ if (debug) {
+ writeDebug("DEBUG: loading new provider protocol=" + protocol + ", className=" + className + ", vendor=" + vendor + ", version=" + version);
+ }
+ Provider provider = new Provider(type, protocol, className, vendor, version);
+ // add to the info list.
+ info.addProvider(provider);
+ }
+ }
+
+ /**
+ * Load up an address map associated with a using class loader
+ * instance.
+ *
+ * @param cl The class loader used to resolve the address map.
+ *
+ * @return A map containing the entries associated with this classloader
+ * instance.
+ */
+ private static Map loadAddressMap(ClassLoader cl) {
+ // we create a merged map from reading all of the potential address map entries. The locations
+ // searched are:
+ // 1. java.home/lib/javamail.address.map
+ // 2. META-INF/javamail.address.map
+ // 3. META-INF/javamail.default.address.map
+ //
+ // if all of the above searches fail, we just set up some "default" defaults.
+
+ // the format of the address.map file is defined as a property file. We can cheat and
+ // just use Properties.load() to read in the files.
+ Properties addressMap = new Properties();
+
+ // add this to the tracking map.
+ addressMapsByClassLoader.put(cl, addressMap);
+
+ // NOTE: We are reading these resources in reverse order of what's cited above. This allows
+ // user defined entries to overwrite default entries if there are similarly named items.
+
+ try {
+ Enumeration e = cl.getResources("META-INF/javamail.default.address.map");
+ while (e.hasMoreElements()) {
+ URL url = (URL) e.nextElement();
+ InputStream is = url.openStream();
+ try {
+ // load as a property file
+ addressMap.load(is);
+ } finally{
+ is.close();
+ }
+ }
+ } catch (SecurityException e) {
+ // ignore
+ } catch (IOException e) {
+ // ignore
+ }
+
+
+ try {
+ Enumeration e = cl.getResources("META-INF/javamail.address.map");
+ while (e.hasMoreElements()) {
+ URL url = (URL) e.nextElement();
+ InputStream is = url.openStream();
+ try {
+ // load as a property file
+ addressMap.load(is);
+ } finally{
+ is.close();
+ }
+ }
+ } catch (SecurityException e) {
+ // ignore
+ } catch (IOException e) {
+ // ignore
+ }
+
+
+ try {
+ File file = new File(System.getProperty("java.home"), "lib/javamail.address.map");
+ InputStream is = new FileInputStream(file);
+ try {
+ // load as a property file
+ addressMap.load(is);
+ } finally{
+ is.close();
+ }
+ } catch (SecurityException e) {
+ // ignore
+ } catch (IOException e) {
+ // ignore
+ }
+
+ try {
+ Enumeration e = cl.getResources("META-INF/javamail.address.map");
+ while (e.hasMoreElements()) {
+ URL url = (URL) e.nextElement();
+ InputStream is = url.openStream();
+ try {
+ // load as a property file
+ addressMap.load(is);
+ } finally{
+ is.close();
+ }
+ }
+ } catch (SecurityException e) {
+ // ignore
+ } catch (IOException e) {
+ // ignore
+ }
+
+
+ // if unable to load anything, at least create the MimeMessage-smtp protocol mapping.
+ if (addressMap.isEmpty()) {
+ addressMap.put("rfc822", "smtp");
+ }
+
+ return addressMap;
+ }
+
+ /**
+ * Private convenience routine for debug output.
+ *
+ * @param msg The message to write out to the debug stream.
+ */
+ private void writeDebug(String msg) {
+ debugOut.println(msg);
+ }
+
+
+ private static class ProviderInfo {
+ private final Map byClassName = new HashMap();
+ private final Map byProtocol = new HashMap();
+ private final List all = new ArrayList();
+
+ public void addProvider(Provider provider) {
+ String className = provider.getClassName();
+
+ if (!byClassName.containsKey(className)) {
+ byClassName.put(className, provider);
+ }
+
+ String protocol = provider.getProtocol();
+ if (!byProtocol.containsKey(protocol)) {
+ byProtocol.put(protocol, provider);
+ }
+ all.add(provider);
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Store.java b/external/geronimo_javamail/src/main/java/javax/mail/Store.java
new file mode 100644
index 00000000..4a9bdf25
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Store.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.util.Vector;
+import javax.mail.event.FolderEvent;
+import javax.mail.event.FolderListener;
+import javax.mail.event.StoreEvent;
+import javax.mail.event.StoreListener;
+
+/**
+ * Abstract class that represents a message store.
+ *
+ * @version $Rev: 578802 $ $Date: 2007-09-24 08:16:44 -0500 (Mon, 24 Sep 2007) $
+ */
+public abstract class Store extends Service {
+ private static final Folder[] FOLDER_ARRAY = new Folder[0];
+ private final Vector folderListeners = new Vector(2);
+ private final Vector storeListeners = new Vector(2);
+
+ /**
+ * Constructor specifying session and url of this store.
+ * Subclasses MUST provide a constructor with this signature.
+ *
+ * @param session the session associated with this store
+ * @param name the URL of the store
+ */
+ protected Store(Session session, URLName name) {
+ super(session, name);
+ }
+
+ /**
+ * Return a Folder object that represents the root of the namespace for the current user.
+ *
+ * Note that in some store configurations (such as IMAP4) the root folder might
+ * not be the INBOX folder.
+ *
+ * @return the root Folder
+ * @throws MessagingException if there was a problem accessing the store
+ */
+ public abstract Folder getDefaultFolder() throws MessagingException;
+
+ /**
+ * Return the Folder corresponding to the given name.
+ * The folder might not physically exist; the {@link Folder#exists()} method can be used
+ * to determine if it is real.
+ * @param name the name of the Folder to return
+ * @return the corresponding folder
+ * @throws MessagingException if there was a problem accessing the store
+ */
+ public abstract Folder getFolder(String name) throws MessagingException;
+
+ /**
+ * Return the folder identified by the URLName; the URLName must refer to this Store.
+ * Implementations may use the {@link URLName#getFile()} method to determined the folder name.
+ *
+ * @param name the folder to return
+ * @return the corresponding folder
+ * @throws MessagingException if there was a problem accessing the store
+ */
+ public abstract Folder getFolder(URLName name) throws MessagingException;
+
+ /**
+ * Return the root folders of the personal namespace belonging to the current user.
+ *
+ * The default implementation simply returns an array containing the folder returned by {@link #getDefaultFolder()}.
+ * @return the root folders of the user's peronal namespaces
+ * @throws MessagingException if there was a problem accessing the store
+ */
+ public Folder[] getPersonalNamespaces() throws MessagingException {
+ return new Folder[]{getDefaultFolder()};
+ }
+
+ /**
+ * Return the root folders of the personal namespaces belonging to the supplied user.
+ *
+ * The default implementation simply returns an empty array.
+ *
+ * @param user the user whose namespaces should be returned
+ * @return the root folders of the given user's peronal namespaces
+ * @throws MessagingException if there was a problem accessing the store
+ */
+ public Folder[] getUserNamespaces(String user) throws MessagingException {
+ return FOLDER_ARRAY;
+ }
+
+ /**
+ * Return the root folders of namespaces that are intended to be shared between users.
+ *
+ * The default implementation simply returns an empty array.
+ * @return the root folders of all shared namespaces
+ * @throws MessagingException if there was a problem accessing the store
+ */
+ public Folder[] getSharedNamespaces() throws MessagingException {
+ return FOLDER_ARRAY;
+ }
+
+
+ public void addStoreListener(StoreListener listener) {
+ storeListeners.add(listener);
+ }
+
+ public void removeStoreListener(StoreListener listener) {
+ storeListeners.remove(listener);
+ }
+
+ protected void notifyStoreListeners(int type, String message) {
+ queueEvent(new StoreEvent(this, type, message), storeListeners);
+ }
+
+
+ public void addFolderListener(FolderListener listener) {
+ folderListeners.add(listener);
+ }
+
+ public void removeFolderListener(FolderListener listener) {
+ folderListeners.remove(listener);
+ }
+
+ protected void notifyFolderListeners(int type, Folder folder) {
+ queueEvent(new FolderEvent(this, folder, type), folderListeners);
+ }
+
+ protected void notifyFolderRenamedListeners(Folder oldFolder, Folder newFolder) {
+ queueEvent(new FolderEvent(this, oldFolder, newFolder, FolderEvent.RENAMED), folderListeners);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/StoreClosedException.java b/external/geronimo_javamail/src/main/java/javax/mail/StoreClosedException.java
new file mode 100644
index 00000000..bfdeb08b
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/StoreClosedException.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class StoreClosedException extends MessagingException {
+ private transient Store _store;
+
+ public StoreClosedException(Store store) {
+ super();
+ _store = store;
+ }
+
+ public StoreClosedException(Store store, String message) {
+ super(message);
+ }
+
+ public Store getStore() {
+ return _store;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Transport.java b/external/geronimo_javamail/src/main/java/javax/mail/Transport.java
new file mode 100644
index 00000000..32bd3d92
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/Transport.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import javax.mail.event.TransportEvent;
+import javax.mail.event.TransportListener;
+
+/**
+ * Abstract class modeling a message transport.
+ *
+ * @version $Rev: 582780 $ $Date: 2007-10-08 06:17:15 -0500 (Mon, 08 Oct 2007) $
+ */
+public abstract class Transport extends Service {
+ /**
+ * Send a message to all recipient addresses the message contains (as returned by {@link Message#getAllRecipients()})
+ * using message transports appropriate for each address. Message addresses are checked during submission,
+ * but there is no guarantee that the ultimate address is valid or that the message will ever be delivered.
+ *
+ * {@link Message#saveChanges()} will be called before the message is actually sent.
+ *
+ * @param message the message to send
+ * @throws MessagingException if there was a problem sending the message
+ */
+ public static void send(Message message) throws MessagingException {
+ send(message, message.getAllRecipients());
+ }
+
+ /**
+ * Send a message to all addresses provided irrespective of any recipients contained in the message,
+ * using message transports appropriate for each address. Message addresses are checked during submission,
+ * but there is no guarantee that the ultimate address is valid or that the message will ever be delivered.
+ *
+ * {@link Message#saveChanges()} will be called before the message is actually sent.
+ *
+ * @param message the message to send
+ * @param addresses the addesses to send to
+ * @throws MessagingException if there was a problem sending the message
+ */
+ public static void send(Message message, Address[] addresses) throws MessagingException {
+ Session session = message.session;
+ Map msgsByTransport = new HashMap();
+ for (int i = 0; i < addresses.length; i++) {
+ Address address = addresses[i];
+ Transport transport = session.getTransport(address);
+ List addrs = (List) msgsByTransport.get(transport);
+ if (addrs == null) {
+ addrs = new ArrayList();
+ msgsByTransport.put(transport, addrs);
+ }
+ addrs.add(address);
+ }
+
+ message.saveChanges();
+
+ // Since we might be sending to multiple protocols, we need to catch and process each exception
+ // when we send and then throw a new SendFailedException when everything is done. Unfortunately, this
+ // also means unwrapping the information in any SendFailedExceptions we receive and building
+ // composite failed list.
+ MessagingException chainedException = null;
+ ArrayList sentAddresses = new ArrayList();
+ ArrayList unsentAddresses = new ArrayList();
+ ArrayList invalidAddresses = new ArrayList();
+
+
+ for (Iterator i = msgsByTransport.entrySet().iterator(); i.hasNext();) {
+ Map.Entry entry = (Map.Entry) i.next();
+ Transport transport = (Transport) entry.getKey();
+ List addrs = (List) entry.getValue();
+ try {
+ // we MUST connect to the transport before attempting to send.
+ transport.connect();
+ transport.sendMessage(message, (Address[]) addrs.toArray(new Address[addrs.size()]));
+ // if we have to throw an exception because of another failure, these addresses need to
+ // be in the valid list. Since we succeeded here, we can add these now.
+ sentAddresses.addAll(addrs);
+ } catch (SendFailedException e) {
+ // a true send failure. The exception contains a wealth of information about
+ // the failures, including a potential chain of exceptions explaining what went wrong. We're
+ // going to send a new one of these, so we need to merge the information.
+
+ // add this to our exception chain
+ if (chainedException == null) {
+ chainedException = e;
+ }
+ else {
+ chainedException.setNextException(e);
+ }
+
+ // now extract each of the address categories from
+ Address[] exAddrs = e.getValidSentAddresses();
+ if (exAddrs != null) {
+ for (int j = 0; j < exAddrs.length; j++) {
+ sentAddresses.add(exAddrs[j]);
+ }
+ }
+
+ exAddrs = e.getValidUnsentAddresses();
+ if (exAddrs != null) {
+ for (int j = 0; j < exAddrs.length; j++) {
+ unsentAddresses.add(exAddrs[j]);
+ }
+ }
+
+ exAddrs = e.getInvalidAddresses();
+ if (exAddrs != null) {
+ for (int j = 0; j < exAddrs.length; j++) {
+ invalidAddresses.add(exAddrs[j]);
+ }
+ }
+
+ } catch (MessagingException e) {
+ // add this to our exception chain
+ if (chainedException == null) {
+ chainedException = e;
+ }
+ else {
+ chainedException.setNextException(e);
+ }
+ }
+ finally {
+ transport.close();
+ }
+ }
+
+ // if we have an exception chain then we need to throw a new exception giving the failure
+ // information.
+ if (chainedException != null) {
+ // if we're only sending to a single transport (common), and we received a SendFailedException
+ // as a result, then we have a fully formed exception already. Rather than wrap this in another
+ // exception, we can just rethrow the one we have.
+ if (msgsByTransport.size() == 1 && chainedException instanceof SendFailedException) {
+ throw chainedException;
+ }
+
+ // 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]);
+
+ throw new SendFailedException("Send failure", chainedException, sent, unsent, invalid);
+ }
+ }
+
+ /**
+ * Constructor taking Session and URLName parameters required for {@link Service#Service(Session, URLName)}.
+ *
+ * @param session the Session this transport is for
+ * @param name the location this transport is for
+ */
+ public Transport(Session session, URLName name) {
+ super(session, name);
+ }
+
+ /**
+ * Send a message to the supplied addresses using this transport; if any of the addresses are
+ * invalid then a {@link SendFailedException} is thrown. Whether the message is actually sent
+ * to any of the addresses is undefined.
+ *
+ * Unlike the static {@link #send(Message, Address[])} method, {@link Message#saveChanges()} is
+ * not called. A {@link TransportEvent} will be sent to registered listeners once the delivery
+ * attempt has been made.
+ *
+ * @param message the message to send
+ * @param addresses list of addresses to send it to
+ * @throws SendFailedException if the send failed
+ * @throws MessagingException if there was a problem sending the message
+ */
+ public abstract void sendMessage(Message message, Address[] addresses) throws MessagingException;
+
+ private Vector transportListeners = new Vector();
+
+ public void addTransportListener(TransportListener listener) {
+ transportListeners.add(listener);
+ }
+
+ public void removeTransportListener(TransportListener listener) {
+ transportListeners.remove(listener);
+ }
+
+ protected void notifyTransportListeners(int type, Address[] validSent, Address[] validUnsent, Address[] invalid, Message message) {
+ queueEvent(new TransportEvent(this, type, validSent, validUnsent, invalid, message), transportListeners);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/UIDFolder.java b/external/geronimo_javamail/src/main/java/javax/mail/UIDFolder.java
new file mode 100644
index 00000000..c0056f8b
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/UIDFolder.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+/**
+ * @version $Rev: 582797 $ $Date: 2007-10-08 07:29:12 -0500 (Mon, 08 Oct 2007) $
+ */
+public interface UIDFolder {
+ /**
+ * A special value than can be passed as the end
parameter to
+ * {@link Folder#getMessages(int, int)} to indicate the last message in this folder.
+ */
+ public static final long LASTUID = -1;
+
+ /**
+ * Get the UID validity value for this Folder.
+ *
+ * @return The current UID validity value, as a long.
+ * @exception MessagingException
+ */
+ public abstract long getUIDValidity() throws MessagingException;
+
+ /**
+ * Retrieve a message using the UID rather than the
+ * message sequence number. Returns null if the message
+ * doesn't exist.
+ *
+ * @param uid The target UID.
+ *
+ * @return the Message object. Returns null if the message does
+ * not exist.
+ * @exception MessagingException
+ */
+ public abstract Message getMessageByUID(long uid)
+ throws MessagingException;
+
+ /**
+ * Get a series of messages using a UID range. The
+ * special value LASTUID can be used to mark the
+ * last available message.
+ *
+ * @param start The start of the UID range.
+ * @param end The end of the UID range. The special value
+ * LASTUID can be used to request all messages up
+ * to the last UID.
+ *
+ * @return An array containing all of the messages in the
+ * range.
+ * @exception MessagingException
+ */
+ public abstract Message[] getMessagesByUID(long start, long end)
+ throws MessagingException;
+
+ /**
+ * Retrieve a set of messages by explicit UIDs. If
+ * any message in the list does not exist, null
+ * will be returned for the corresponding item.
+ *
+ * @param ids An array of UID values to be retrieved.
+ *
+ * @return An array of Message items the same size as the ids
+ * argument array. This array will contain null
+ * entries for any UIDs that do not exist.
+ * @exception MessagingException
+ */
+ public abstract Message[] getMessagesByUID(long[] ids)
+ throws MessagingException;
+
+ /**
+ * Retrieve the UID for a message from this Folder.
+ * The argument Message MUST belong to this Folder
+ * instance, otherwise a NoSuchElementException will
+ * be thrown.
+ *
+ * @param message The target message.
+ *
+ * @return The UID associated with this message.
+ * @exception MessagingException
+ */
+ public abstract long getUID(Message message) throws MessagingException;
+
+ /**
+ * Special profile item used for fetching UID information.
+ */
+ public static class FetchProfileItem extends FetchProfile.Item {
+ public static final FetchProfileItem UID = new FetchProfileItem("UID");
+
+ protected FetchProfileItem(String name) {
+ super(name);
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/URLName.java b/external/geronimo_javamail/src/main/java/javax/mail/URLName.java
new file mode 100644
index 00000000..c1a3f969
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/URLName.java
@@ -0,0 +1,317 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.io.ByteArrayOutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * @version $Rev: 593290 $ $Date: 2007-11-08 14:18:29 -0600 (Thu, 08 Nov 2007) $
+ */
+public class URLName {
+ private static final String nonEncodedChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-.*";
+
+ private String file;
+ private String host;
+ private String password;
+ private int port;
+ private String protocol;
+ private String ref;
+ private String username;
+ protected String fullURL;
+ private int hashCode;
+
+ public URLName(String url) {
+ parseString(url);
+ }
+
+ protected void parseString(String url) {
+ URI uri;
+ try {
+ if (url == null) {
+ uri = null;
+ } else {
+ uri = new URI(url);
+ }
+ } catch (URISyntaxException e) {
+ uri = null;
+ }
+ if (uri == null) {
+ protocol = null;
+ host = null;
+ port = -1;
+ file = null;
+ ref = null;
+ username = null;
+ password = null;
+ return;
+ }
+
+ protocol = checkBlank(uri.getScheme());
+ host = checkBlank(uri.getHost());
+ port = uri.getPort();
+ file = checkBlank(uri.getPath());
+ // if the file starts with "/", we need to strip that off.
+ // URL and URLName do not have the same behavior when it comes
+ // to keeping that there.
+ if (file != null && file.length() > 1 && file.startsWith("/")) {
+ file = checkBlank(file.substring(1));
+ }
+
+ ref = checkBlank(uri.getFragment());
+ String userInfo = checkBlank(uri.getUserInfo());
+ if (userInfo == null) {
+ username = null;
+ password = null;
+ } else {
+ int pos = userInfo.indexOf(':');
+ if (pos == -1) {
+ username = userInfo;
+ password = null;
+ } else {
+ username = userInfo.substring(0, pos);
+ password = userInfo.substring(pos + 1);
+ }
+ }
+ updateFullURL();
+ }
+
+ public URLName(String protocol, String host, int port, String file, String username, String password) {
+ this.protocol = checkBlank(protocol);
+ this.host = checkBlank(host);
+ this.port = port;
+ if (file == null || file.length() == 0) {
+ this.file = null;
+ ref = null;
+ } else {
+ int pos = file.indexOf('#');
+ if (pos == -1) {
+ this.file = file;
+ ref = null;
+ } else {
+ this.file = file.substring(0, pos);
+ ref = file.substring(pos + 1);
+ }
+ }
+ this.username = checkBlank(username);
+ if (this.username != null) {
+ this.password = checkBlank(password);
+ } else {
+ this.password = null;
+ }
+ username = encode(username);
+ password = encode(password);
+ updateFullURL();
+ }
+
+ public URLName(URL url) {
+ protocol = checkBlank(url.getProtocol());
+ host = checkBlank(url.getHost());
+ port = url.getPort();
+ file = checkBlank(url.getFile());
+ ref = checkBlank(url.getRef());
+ String userInfo = checkBlank(url.getUserInfo());
+ if (userInfo == null) {
+ username = null;
+ password = null;
+ } else {
+ int pos = userInfo.indexOf(':');
+ if (pos == -1) {
+ username = userInfo;
+ password = null;
+ } else {
+ username = userInfo.substring(0, pos);
+ password = userInfo.substring(pos + 1);
+ }
+ }
+ updateFullURL();
+ }
+
+ private static String checkBlank(String target) {
+ if (target == null || target.length() == 0) {
+ return null;
+ } else {
+ return target;
+ }
+ }
+
+ private void updateFullURL() {
+ hashCode = 0;
+ StringBuffer buf = new StringBuffer(100);
+ if (protocol != null) {
+ buf.append(protocol).append(':');
+ if (host != null) {
+ buf.append("//");
+ if (username != null) {
+ buf.append(encode(username));
+ if (password != null) {
+ buf.append(':').append(encode(password));
+ }
+ buf.append('@');
+ }
+ buf.append(host);
+ if (port != -1) {
+ buf.append(':').append(port);
+ }
+ if (file != null) {
+ buf.append('/').append(file);
+ }
+ hashCode = buf.toString().hashCode();
+ if (ref != null) {
+ buf.append('#').append(ref);
+ }
+ }
+ }
+ fullURL = buf.toString();
+ }
+
+ public boolean equals(Object o) {
+ if (o instanceof URLName == false) {
+ return false;
+ }
+ URLName other = (URLName) o;
+ // check same protocol - false if either is null
+ if (protocol == null || other.protocol == null || !protocol.equals(other.protocol)) {
+ return false;
+ }
+
+ if (port != other.port) {
+ return false;
+ }
+
+ // check host - false if not (both null or both equal)
+ return areSame(host, other.host) && areSame(file, other.file) && areSame(username, other.username) && areSame(password, other.password);
+ }
+
+ private static boolean areSame(String s1, String s2) {
+ if (s1 == null) {
+ return s2 == null;
+ } else {
+ return s1.equals(s2);
+ }
+ }
+
+ public int hashCode() {
+ return hashCode;
+ }
+
+ public String toString() {
+ return fullURL;
+ }
+
+ public String getFile() {
+ return file;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public String getRef() {
+ return ref;
+ }
+
+ public URL getURL() throws MalformedURLException {
+ return new URL(fullURL);
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * Perform an HTTP encoding to the username and
+ * password elements of the URLName.
+ *
+ * @param v The input (uncoded) string.
+ *
+ * @return The HTTP encoded version of the string.
+ */
+ private static String encode(String v) {
+ // make sure we don't operate on a null string
+ if (v == null) {
+ return null;
+ }
+ boolean needsEncoding = false;
+ for (int i = 0; i < v.length(); i++) {
+ // not in the list of things that don't need encoding?
+ if (nonEncodedChars.indexOf(v.charAt(i)) == -1) {
+ // got to do this the hard way
+ needsEncoding = true;
+ break;
+ }
+ }
+ // just fine the way it is.
+ if (!needsEncoding) {
+ return v;
+ }
+
+ // we know we're going to be larger, but not sure by how much.
+ // just give a little extra
+ StringBuffer encoded = new StringBuffer(v.length() + 10);
+
+ // we get the bytes so that we can have the default encoding applied to
+ // this string. This will flag the ones we need to give special processing to.
+ byte[] data = v.getBytes();
+
+ for (int i = 0; i < data.length; i++) {
+ // pick this up as a one-byte character The 7-bit ascii ones will be fine
+ // here.
+ char ch = (char)(data[i] & 0xff);
+ // blanks get special treatment
+ if (ch == ' ') {
+ encoded.append('+');
+ }
+ // not in the list of things that don't need encoding?
+ else if (nonEncodedChars.indexOf(ch) == -1) {
+ // forDigit() uses the lowercase letters for the radix. The HTML specifications
+ // require the uppercase letters.
+ char firstChar = Character.toUpperCase(Character.forDigit((ch >> 4) & 0xf, 16));
+ char secondChar = Character.toUpperCase(Character.forDigit(ch & 0xf, 16));
+
+ // now append the encoded triplet.
+ encoded.append('%');
+ encoded.append(firstChar);
+ encoded.append(secondChar);
+ }
+ else {
+ // just add this one to the buffer
+ encoded.append(ch);
+ }
+ }
+ // convert to string form.
+ return encoded.toString();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/ConnectionAdapter.java b/external/geronimo_javamail/src/main/java/javax/mail/event/ConnectionAdapter.java
new file mode 100644
index 00000000..009f0093
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/ConnectionAdapter.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+/**
+ * An adaptor that receives connection events.
+ * This is a default implementation where the handlers perform no action.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class ConnectionAdapter implements ConnectionListener {
+ public void closed(ConnectionEvent event) {
+ }
+
+ public void disconnected(ConnectionEvent event) {
+ }
+
+ public void opened(ConnectionEvent event) {
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/ConnectionEvent.java b/external/geronimo_javamail/src/main/java/javax/mail/event/ConnectionEvent.java
new file mode 100644
index 00000000..5ffb6ed6
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/ConnectionEvent.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 javax.mail.event;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class ConnectionEvent extends MailEvent {
+ /**
+ * A connection was opened.
+ */
+ public static final int OPENED = 1;
+
+ /**
+ * A connection was disconnected.
+ */
+ public static final int DISCONNECTED = 2;
+
+ /**
+ * A connection was closed.
+ */
+ public static final int CLOSED = 3;
+
+ protected int type;
+
+ public ConnectionEvent(Object source, int type) {
+ super(source);
+ this.type = type;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void dispatch(Object listener) {
+ // assume that it is the right listener type
+ ConnectionListener l = (ConnectionListener) listener;
+ switch (type) {
+ case OPENED:
+ l.opened(this);
+ break;
+ case DISCONNECTED:
+ l.disconnected(this);
+ break;
+ case CLOSED:
+ l.closed(this);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid type " + type);
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/ConnectionListener.java b/external/geronimo_javamail/src/main/java/javax/mail/event/ConnectionListener.java
new file mode 100644
index 00000000..08323803
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/ConnectionListener.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 javax.mail.event;
+
+import java.util.EventListener;
+
+/**
+ * Listener for handling connection events.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public interface ConnectionListener extends EventListener {
+ /**
+ * Called when a connection is opened.
+ */
+ public abstract void opened(ConnectionEvent event);
+
+ /**
+ * Called when a connection is disconnected.
+ */
+ public abstract void disconnected(ConnectionEvent event);
+
+ /**
+ * Called when a connection is closed.
+ */
+ public abstract void closed(ConnectionEvent event);
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/FolderAdapter.java b/external/geronimo_javamail/src/main/java/javax/mail/event/FolderAdapter.java
new file mode 100644
index 00000000..ef788e25
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/FolderAdapter.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+/**
+ * An adaptor that receives connection events.
+ * This is a default implementation where the handlers perform no action.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class FolderAdapter implements FolderListener {
+ public void folderCreated(FolderEvent event) {
+ }
+
+ public void folderDeleted(FolderEvent event) {
+ }
+
+ public void folderRenamed(FolderEvent event) {
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/FolderEvent.java b/external/geronimo_javamail/src/main/java/javax/mail/event/FolderEvent.java
new file mode 100644
index 00000000..b5da9906
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/FolderEvent.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import javax.mail.Folder;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class FolderEvent extends MailEvent {
+ public static final int CREATED = 1;
+ public static final int DELETED = 2;
+ public static final int RENAMED = 3;
+
+ protected transient Folder folder;
+ protected transient Folder newFolder;
+ protected int type;
+
+ /**
+ * Constructor used for RENAMED events.
+ *
+ * @param source the source of the event
+ * @param oldFolder the folder that was renamed
+ * @param newFolder the folder with the new name
+ * @param type the event type
+ */
+ public FolderEvent(Object source, Folder oldFolder, Folder newFolder, int type) {
+ super(source);
+ folder = oldFolder;
+ this.newFolder = newFolder;
+ this.type = type;
+ }
+
+ /**
+ * Constructor other events.
+ *
+ * @param source the source of the event
+ * @param folder the folder affected
+ * @param type the event type
+ */
+ public FolderEvent(Object source, Folder folder, int type) {
+ this(source, folder, null, type);
+ }
+
+ public void dispatch(Object listener) {
+ FolderListener l = (FolderListener) listener;
+ switch (type) {
+ case CREATED:
+ l.folderCreated(this);
+ break;
+ case DELETED:
+ l.folderDeleted(this);
+ break;
+ case RENAMED:
+ l.folderRenamed(this);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid type " + type);
+ }
+ }
+
+ /**
+ * Return the affected folder.
+ * @return the affected folder
+ */
+ public Folder getFolder() {
+ return folder;
+ }
+
+ /**
+ * Return the new folder; only applicable to RENAMED events.
+ * @return the new folder
+ */
+ public Folder getNewFolder() {
+ return newFolder;
+ }
+
+ /**
+ * Return the event type.
+ * @return the event type
+ */
+ public int getType() {
+ return type;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/FolderListener.java b/external/geronimo_javamail/src/main/java/javax/mail/event/FolderListener.java
new file mode 100644
index 00000000..ba26d74f
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/FolderListener.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 javax.mail.event;
+
+import java.util.EventListener;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public interface FolderListener extends EventListener {
+ public abstract void folderCreated(FolderEvent event);
+
+ public abstract void folderDeleted(FolderEvent event);
+
+ public abstract void folderRenamed(FolderEvent event);
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/MailEvent.java b/external/geronimo_javamail/src/main/java/javax/mail/event/MailEvent.java
new file mode 100644
index 00000000..d38d3f4a
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/MailEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import java.util.EventObject;
+
+/**
+ * Common base class for mail events.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class MailEvent extends EventObject {
+ public MailEvent(Object source) {
+ super(source);
+ }
+
+ public abstract void dispatch(Object listener);
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/MessageChangedEvent.java b/external/geronimo_javamail/src/main/java/javax/mail/event/MessageChangedEvent.java
new file mode 100644
index 00000000..1c19f014
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/MessageChangedEvent.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 javax.mail.event;
+
+import javax.mail.Message;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class MessageChangedEvent extends MailEvent {
+ /**
+ * The message's flags changed.
+ */
+ public static final int FLAGS_CHANGED = 1;
+
+ /**
+ * The messages envelope changed.
+ */
+ public static final int ENVELOPE_CHANGED = 2;
+
+ protected transient Message msg;
+ protected int type;
+
+ /**
+ * Constructor.
+ *
+ * @param source the folder that owns the message
+ * @param type the event type
+ * @param message the affected message
+ */
+ public MessageChangedEvent(Object source, int type, Message message) {
+ super(source);
+ msg = message;
+ this.type = type;
+ }
+
+ public void dispatch(Object listener) {
+ MessageChangedListener l = (MessageChangedListener) listener;
+ l.messageChanged(this);
+ }
+
+ /**
+ * Return the affected message.
+ * @return the affected message
+ */
+ public Message getMessage() {
+ return msg;
+ }
+
+ /**
+ * Return the type of change.
+ * @return the event type
+ */
+ public int getMessageChangeType() {
+ return type;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/MessageChangedListener.java b/external/geronimo_javamail/src/main/java/javax/mail/event/MessageChangedListener.java
new file mode 100644
index 00000000..1095f4ec
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/MessageChangedListener.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import java.util.EventListener;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public interface MessageChangedListener extends EventListener {
+ public abstract void messageChanged(MessageChangedEvent event);
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/MessageCountAdapter.java b/external/geronimo_javamail/src/main/java/javax/mail/event/MessageCountAdapter.java
new file mode 100644
index 00000000..35db10a0
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/MessageCountAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+/**
+ * An adaptor that receives message count events.
+ * This is a default implementation where the handlers perform no action.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class MessageCountAdapter implements MessageCountListener {
+ public void messagesAdded(MessageCountEvent event) {
+ }
+
+ public void messagesRemoved(MessageCountEvent event) {
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/MessageCountEvent.java b/external/geronimo_javamail/src/main/java/javax/mail/event/MessageCountEvent.java
new file mode 100644
index 00000000..a7fb86bf
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/MessageCountEvent.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 javax.mail.event;
+
+import javax.mail.Folder;
+import javax.mail.Message;
+
+/**
+ * Event indicating a change in the number of messages in a folder.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class MessageCountEvent extends MailEvent {
+ /**
+ * Messages were added to the folder.
+ */
+ public static final int ADDED = 1;
+
+ /**
+ * Messages were removed from the folder.
+ */
+ public static final int REMOVED = 2;
+
+ /**
+ * The affected messages.
+ */
+ protected transient Message msgs[];
+
+ /**
+ * The event type.
+ */
+ protected int type;
+
+ /**
+ * If true, then messages were expunged from the folder by this client
+ * and message numbers reflect the deletion; if false, then the change
+ * was the result of an expunge by a different client.
+ */
+ protected boolean removed;
+
+ /**
+ * Construct a new event.
+ *
+ * @param folder the folder containing the messages
+ * @param type the event type
+ * @param removed indicator of whether messages were expunged by this client
+ * @param messages the affected messages
+ */
+ public MessageCountEvent(Folder folder, int type, boolean removed, Message messages[]) {
+ super(folder);
+ this.msgs = messages;
+ this.type = type;
+ this.removed = removed;
+ }
+
+ /**
+ * Return the event type.
+ *
+ * @return the event type
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * @return whether this event was the result of an expunge by this client
+ * @see MessageCountEvent#removed
+ */
+ public boolean isRemoved() {
+ return removed;
+ }
+
+ /**
+ * Return the affected messages.
+ *
+ * @return the affected messages
+ */
+ public Message[] getMessages() {
+ return msgs;
+ }
+
+ public void dispatch(Object listener) {
+ MessageCountListener l = (MessageCountListener) listener;
+ switch (type) {
+ case ADDED:
+ l.messagesAdded(this);
+ break;
+ case REMOVED:
+ l.messagesRemoved(this);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid type " + type);
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/MessageCountListener.java b/external/geronimo_javamail/src/main/java/javax/mail/event/MessageCountListener.java
new file mode 100644
index 00000000..8098fea4
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/MessageCountListener.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import java.util.EventListener;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public interface MessageCountListener extends EventListener {
+ public abstract void messagesAdded(MessageCountEvent event);
+
+ public abstract void messagesRemoved(MessageCountEvent event);
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/StoreEvent.java b/external/geronimo_javamail/src/main/java/javax/mail/event/StoreEvent.java
new file mode 100644
index 00000000..d0842f4a
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/StoreEvent.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import javax.mail.Store;
+
+/**
+ * Event representing motifications from the Store connection.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class StoreEvent extends MailEvent {
+ /**
+ * Indicates that this message is an alert.
+ */
+ public static final int ALERT = 1;
+
+ /**
+ * Indicates that this message is a notice.
+ */
+ public static final int NOTICE = 2;
+
+ /**
+ * The message type.
+ */
+ protected int type;
+
+ /**
+ * The text to be presented to the user.
+ */
+ protected String message;
+
+ /**
+ * Construct a new event.
+ *
+ * @param store the Store that initiated the notification
+ * @param type the message type
+ * @param message the text to be presented to the user
+ */
+ public StoreEvent(Store store, int type, String message) {
+ super(store);
+ this.type = type;
+ this.message = message;
+ }
+
+ /**
+ * Return the message type.
+ *
+ * @return the message type
+ */
+ public int getMessageType() {
+ return type;
+ }
+
+ /**
+ * Return the text to be displayed to the user.
+ *
+ * @return the text to be displayed to the user
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ public void dispatch(Object listener) {
+ ((StoreListener) listener).notification(this);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/StoreListener.java b/external/geronimo_javamail/src/main/java/javax/mail/event/StoreListener.java
new file mode 100644
index 00000000..021ba893
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/StoreListener.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import java.util.EventListener;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public interface StoreListener extends EventListener {
+ public abstract void notification(StoreEvent event);
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/TransportAdapter.java b/external/geronimo_javamail/src/main/java/javax/mail/event/TransportAdapter.java
new file mode 100644
index 00000000..6893ce49
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/TransportAdapter.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+/**
+ * An adaptor that receives transport events.
+ * This is a default implementation where the handlers perform no action.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class TransportAdapter implements TransportListener {
+ public void messageDelivered(TransportEvent event) {
+ }
+
+ public void messageNotDelivered(TransportEvent event) {
+ }
+
+ public void messagePartiallyDelivered(TransportEvent event) {
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/TransportEvent.java b/external/geronimo_javamail/src/main/java/javax/mail/event/TransportEvent.java
new file mode 100644
index 00000000..1182353d
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/TransportEvent.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.Transport;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class TransportEvent extends MailEvent {
+ /**
+ * Indicates that the message has successfully been delivered to all
+ * recipients.
+ */
+ public static final int MESSAGE_DELIVERED = 1;
+
+ /**
+ * Indicates that no messages could be delivered.
+ */
+ public static final int MESSAGE_NOT_DELIVERED = 2;
+
+ /**
+ * Indicates that some of the messages were successfully delivered
+ * but that some failed.
+ */
+ public static final int MESSAGE_PARTIALLY_DELIVERED = 3;
+
+ /**
+ * The event type.
+ */
+ protected int type;
+
+ /**
+ * Addresses to which the message was successfully delivered.
+ */
+ protected transient Address[] validSent;
+
+ /**
+ * Addresses which are valid but to which the message was not sent.
+ */
+ protected transient Address[] validUnsent;
+
+ /**
+ * Addresses that are invalid.
+ */
+ protected transient Address[] invalid;
+
+ /**
+ * The message associated with this event.
+ */
+ protected transient Message msg;
+
+ /**
+ * Construct a new event,
+ *
+ * @param transport the transport attempting to deliver the message
+ * @param type the event type
+ * @param validSent addresses to which the message was successfully delivered
+ * @param validUnsent addresses which are valid but to which the message was not sent
+ * @param invalid invalid addresses
+ * @param message the associated message
+ */
+ public TransportEvent(Transport transport, int type, Address[] validSent, Address[] validUnsent, Address[] invalid, Message message) {
+ super(transport);
+ this.type = type;
+ this.validSent = validSent;
+ this.validUnsent = validUnsent;
+ this.invalid = invalid;
+ this.msg = message;
+ }
+
+ public Address[] getValidSentAddresses() {
+ return validSent;
+ }
+
+ public Address[] getValidUnsentAddresses() {
+ return validUnsent;
+ }
+
+ public Address[] getInvalidAddresses() {
+ return invalid;
+ }
+
+ public Message getMessage() {
+ return msg;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void dispatch(Object listener) {
+ TransportListener l = (TransportListener) listener;
+ switch (type) {
+ case MESSAGE_DELIVERED:
+ l.messageDelivered(this);
+ break;
+ case MESSAGE_NOT_DELIVERED:
+ l.messageNotDelivered(this);
+ break;
+ case MESSAGE_PARTIALLY_DELIVERED:
+ l.messagePartiallyDelivered(this);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid type " + type);
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/event/TransportListener.java b/external/geronimo_javamail/src/main/java/javax/mail/event/TransportListener.java
new file mode 100644
index 00000000..57928e5f
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/event/TransportListener.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 javax.mail.event;
+
+import java.util.EventListener;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public interface TransportListener extends EventListener {
+ public abstract void messageDelivered(TransportEvent event);
+
+ public abstract void messageNotDelivered(TransportEvent event);
+
+ public abstract void messagePartiallyDelivered(TransportEvent event);
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/AddressException.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/AddressException.java
new file mode 100644
index 00000000..a3b0e237
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/AddressException.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class AddressException extends ParseException {
+ protected int pos;
+ protected String ref;
+
+ public AddressException() {
+ this(null);
+ }
+
+ public AddressException(String message) {
+ this(message, null);
+ }
+
+ public AddressException(String message, String ref) {
+ this(message, null, -1);
+ }
+
+ public AddressException(String message, String ref, int pos) {
+ super(message);
+ this.ref = ref;
+ this.pos = pos;
+ }
+
+ public String getRef() {
+ return ref;
+ }
+
+ public int getPos() {
+ return pos;
+ }
+
+ public String toString() {
+ return super.toString() + " (" + ref + "," + pos + ")";
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/AddressParser.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/AddressParser.java
new file mode 100644
index 00000000..af7dd2f1
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/AddressParser.java
@@ -0,0 +1,2002 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+
+class AddressParser {
+
+ // the validation strictness levels, from most lenient to most conformant.
+ static public final int NONSTRICT = 0;
+ static public final int PARSE_HEADER = 1;
+ static public final int STRICT = 2;
+
+ // different mailbox types
+ static protected final int UNKNOWN = 0;
+ static protected final int ROUTE_ADDR = 1;
+ static protected final int GROUP_ADDR = 2;
+ static protected final int SIMPLE_ADDR = 3;
+
+ // constants for token types.
+ static protected final int END_OF_TOKENS = '\0';
+ static protected final int PERIOD = '.';
+ static protected final int LEFT_ANGLE = '<';
+ static protected final int RIGHT_ANGLE = '>';
+ static protected final int COMMA = ',';
+ static protected final int AT_SIGN = '@';
+ static protected final int SEMICOLON = ';';
+ static protected final int COLON = ':';
+ static protected final int QUOTED_LITERAL = '"';
+ static protected final int DOMAIN_LITERAL = '[';
+ static protected final int COMMENT = '(';
+ static protected final int ATOM = 'A';
+ static protected final int WHITESPACE = ' ';
+
+
+ // the string we're parsing
+ private String addresses;
+ // the current parsing position
+ private int position;
+ // the end position of the string
+ private int end;
+ // the strictness flag
+ private int validationLevel;
+
+ public AddressParser(String addresses, int validation) {
+ this.addresses = addresses;
+ validationLevel = validation;
+ }
+
+
+ /**
+ * Parse an address list into an array of internet addresses.
+ *
+ * @return An array containing all of the non-null addresses in the list.
+ * @exception AddressException
+ * Thrown for any validation errors.
+ */
+ public InternetAddress[] parseAddressList() throws AddressException
+ {
+ // get the address as a set of tokens we can process.
+ TokenStream tokens = tokenizeAddress();
+
+ // get an array list accumulator.
+ ArrayList addressList = new ArrayList();
+
+ // we process sections of the token stream until we run out of tokens.
+ while (true) {
+ // parse off a single address. Address lists can have null elements,
+ // so this might return a null value. The null value does not get added
+ // to the address accumulator.
+ addressList.addAll(parseSingleAddress(tokens, false));
+ // This token should be either a "," delimiter or a stream terminator. If we're
+ // at the end, time to get out.
+ AddressToken token = tokens.nextToken();
+ if (token.type == END_OF_TOKENS) {
+ break;
+ }
+ }
+
+ return (InternetAddress [])addressList.toArray(new InternetAddress[0]);
+ }
+
+
+ /**
+ * Parse a single internet address. This must be a single address,
+ * not an address list.
+ *
+ * @exception AddressException
+ */
+ public InternetAddress parseAddress() throws AddressException
+ {
+ // get the address as a set of tokens we can process.
+ TokenStream tokens = tokenizeAddress();
+
+ // parse off a single address. Address lists can have null elements,
+ // so this might return a null value. The null value does not get added
+ // to the address accumulator.
+ List addressList = parseSingleAddress(tokens, false);
+ // we must get exactly one address back from this.
+ if (addressList.isEmpty()) {
+ throw new AddressException("Null address", addresses, 0);
+ }
+ // this could be a simple list of blank delimited tokens. Ensure we only got one back.
+ if (addressList.size() > 1) {
+ throw new AddressException("Illegal Address", addresses, 0);
+ }
+
+ // This token must be a stream stream terminator, or we have an error.
+ AddressToken token = tokens.nextToken();
+ if (token.type != END_OF_TOKENS) {
+ illegalAddress("Illegal Address", token);
+ }
+
+ return (InternetAddress)addressList.get(0);
+ }
+
+
+ /**
+ * Validate an internet address. This must be a single address,
+ * not a list of addresses. The address also must not contain
+ * and personal information to be valid.
+ *
+ * @exception AddressException
+ */
+ public void validateAddress() throws AddressException
+ {
+ // get the address as a set of tokens we can process.
+ TokenStream tokens = tokenizeAddress();
+
+ // parse off a single address. Address lists can have null elements,
+ // so this might return a null value. The null value does not get added
+ // to the address accumulator.
+ List addressList = parseSingleAddress(tokens, false);
+ if (addressList.isEmpty()) {
+ throw new AddressException("Null address", addresses, 0);
+ }
+
+ // this could be a simple list of blank delimited tokens. Ensure we only got one back.
+ if (addressList.size() > 1) {
+ throw new AddressException("Illegal Address", addresses, 0);
+ }
+
+ InternetAddress address = (InternetAddress)addressList.get(0);
+
+ // validation occurs on an address that's already been split into personal and address
+ // data.
+ if (address.personal != null) {
+ throw new AddressException("Illegal Address", addresses, 0);
+ }
+ // This token must be a stream stream terminator, or we have an error.
+ AddressToken token = tokens.nextToken();
+ if (token.type != END_OF_TOKENS) {
+ illegalAddress("Illegal Address", token);
+ }
+ }
+
+
+ /**
+ * Extract the set of address from a group Internet specification.
+ *
+ * @return An array containing all of the non-null addresses in the list.
+ * @exception AddressException
+ */
+ public InternetAddress[] extractGroupList() throws AddressException
+ {
+ // get the address as a set of tokens we can process.
+ TokenStream tokens = tokenizeAddress();
+
+ // get an array list accumulator.
+ ArrayList addresses = new ArrayList();
+
+ AddressToken token = tokens.nextToken();
+
+ // scan forward to the ':' that starts the group list. If we don't find one,
+ // this is an exception.
+ while (token.type != COLON) {
+ if (token.type == END_OF_TOKENS) {
+ illegalAddress("Missing ':'", token);
+ }
+ token = tokens.nextToken();
+ }
+
+ // we process sections of the token stream until we run out of tokens.
+ while (true) {
+ // parse off a single address. Address lists can have null elements,
+ // so this might return a null value. The null value does not get added
+ // to the address accumulator.
+ addresses.addAll(parseSingleAddress(tokens, true));
+ // This token should be either a "," delimiter or a group terminator. If we're
+ // at the end, this is an error.
+ token = tokens.nextToken();
+ if (token.type == SEMICOLON) {
+ break;
+ }
+ else if (token.type == END_OF_TOKENS) {
+ illegalAddress("Missing ';'", token);
+ }
+ }
+
+ return (InternetAddress [])addresses.toArray(new InternetAddress[0]);
+ }
+
+
+ /**
+ * Parse out a single address from a string from a string
+ * of address tokens, returning an InternetAddress object that
+ * represents the address.
+ *
+ * @param tokens The token source for this address.
+ *
+ * @return A parsed out and constructed InternetAddress object for
+ * the next address. Returns null if this is an "empty"
+ * address in a list.
+ * @exception AddressException
+ */
+ private List parseSingleAddress(TokenStream tokens, boolean inGroup) throws AddressException
+ {
+ List parsedAddresses = new ArrayList();
+
+ // index markers for personal information
+ AddressToken personalStart = null;
+ AddressToken personalEnd = null;
+
+ // and similar bits for the address information.
+ AddressToken addressStart = null;
+ AddressToken addressEnd = null;
+
+ // there is a fall-back set of rules allowed that will parse the address as a set of blank delimited
+ // tokens. However, we do NOT allow this if we encounter any tokens that fall outside of these
+ // rules. For example, comment fields and quoted strings will disallow the very lenient rule set.
+ boolean nonStrictRules = true;
+
+ // we don't know the type of address yet
+ int addressType = UNKNOWN;
+
+ // the parsing goes in two stages. Stage one runs through the tokens locating the bounds
+ // of the address we're working on, resolving the personal information, and also validating
+ // some of the larger scale syntax features of an address (matched delimiters for routes and
+ // groups, invalid nesting checks, etc.).
+
+ // get the next token from the queue and save this. We're going to scan ahead a bit to
+ // figure out what type of address we're looking at, then reset to do the actually parsing
+ // once we've figured out a form.
+ AddressToken first = tokens.nextToken();
+ // push it back on before starting processing.
+ tokens.pushToken(first);
+
+ // scan ahead for a trigger token that tells us what we've got.
+ while (addressType == UNKNOWN) {
+
+ AddressToken token = tokens.nextToken();
+ switch (token.type) {
+ // skip these for now...after we've processed everything and found that this is a simple
+ // address form, then we'll check for a leading comment token in the first position and use
+ // if as personal information.
+ case COMMENT:
+ // comments do, however, denote that this must be parsed according to RFC822 rules.
+ nonStrictRules = false;
+ break;
+
+ // a semi-colon when processing a group is an address terminator. we need to
+ // process this like a comma then
+ case SEMICOLON:
+ if (inGroup) {
+ // we need to push the terminator back on for the caller to see.
+ tokens.pushToken(token);
+ // if we've not tagged any tokens as being the address beginning, so this must be a
+ // null address.
+ if (addressStart == null) {
+ // just return the empty list from this.
+ return parsedAddresses;
+ }
+ // the end token is the back part.
+ addressEnd = tokens.previousToken(token);
+ // without a '<' for a route addr, we can't distinguish address tokens from personal data.
+ // We'll use a leading comment, if there is one.
+ personalStart = null;
+ // this is just a simple form.
+ addressType = SIMPLE_ADDR;
+ break;
+ }
+
+ // NOTE: The above falls through if this is not a group.
+
+ // any of these tokens are a real token that can be the start of an address. Many of
+ // them are not valid as first tokens in this context, but we flag them later if validation
+ // has been requested. For now, we just mark these as the potential address start.
+ case DOMAIN_LITERAL:
+ case QUOTED_LITERAL:
+ // this set of tokens require fuller RFC822 parsing, so turn off the flag.
+ nonStrictRules = false;
+
+ case ATOM:
+ case AT_SIGN:
+ case PERIOD:
+ // if we're not determined the start of the address yet, then check to see if we
+ // need to consider this the personal start.
+ if (addressStart == null) {
+ if (personalStart == null) {
+ personalStart = token;
+ }
+ // This is the first real token of the address, which at this point can
+ // be either the personal info or the first token of the address. If we hit
+ // an address terminator without encountering either a route trigger or group
+ // trigger, then this is the real address.
+ addressStart = token;
+ }
+ break;
+
+ // a LEFT_ANGLE indicates we have a full RFC822 mailbox form. The leading phrase
+ // is the personal info. The address is inside the brackets.
+ case LEFT_ANGLE:
+ // a route address automatically switches off the blank-delimited token mode.
+ nonStrictRules = false;
+ // this is a route address
+ addressType = ROUTE_ADDR;
+ // the address is placed in the InternetAddress object without the route
+ // brackets, so our start is one past this.
+ addressStart = tokens.nextRealToken();
+ // push this back on the queue so the scanner picks it up properly.
+ tokens.pushToken(addressStart);
+ // make sure we flag the end of the personal section too.
+ if (personalStart != null) {
+ personalEnd = tokens.previousToken(token);
+ }
+ // scan the rest of a route address.
+ addressEnd = scanRouteAddress(tokens, false);
+ break;
+
+ // a COLON indicates this is a group specifier...parse the group.
+ case COLON:
+ // Colons would not be valid in simple lists, so turn it off.
+ nonStrictRules = false;
+ // if we're scanning a group, we shouldn't encounter a ":". This is a
+ // recursion error if found.
+ if (inGroup) {
+ illegalAddress("Nested group element", token);
+ }
+ addressType = GROUP_ADDR;
+ // groups don't have any personal sections.
+ personalStart = null;
+ // our real start was back at the beginning
+ addressStart = first;
+ addressEnd = scanGroupAddress(tokens);
+ break;
+
+ // a semi colon can the same as a comma if we're processing a group.
+
+
+ // reached the end of string...this might be a null address, or one of the very simple name
+ // forms used for non-strict RFC822 versions. Reset, and try that form
+ case END_OF_TOKENS:
+ // if we're scanning a group, we shouldn't encounter an end token. This is an
+ // error if found.
+ if (inGroup) {
+ illegalAddress("Missing ';'", token);
+ }
+
+ // NOTE: fall through from above.
+
+ // this is either a terminator for an address list or a a group terminator.
+ case COMMA:
+ // we need to push the terminator back on for the caller to see.
+ tokens.pushToken(token);
+ // if we've not tagged any tokens as being the address beginning, so this must be a
+ // null address.
+ if (addressStart == null) {
+ // just return the empty list from this.
+ return parsedAddresses;
+ }
+ // the end token is the back part.
+ addressEnd = tokens.previousToken(token);
+ // without a '<' for a route addr, we can't distinguish address tokens from personal data.
+ // We'll use a leading comment, if there is one.
+ personalStart = null;
+ // this is just a simple form.
+ addressType = SIMPLE_ADDR;
+ break;
+
+ // right angle tokens are pushed, because parsing of the bracketing is not necessarily simple.
+ // we need to flag these here.
+ case RIGHT_ANGLE:
+ illegalAddress("Unexpected '>'", token);
+
+ }
+ }
+
+ String personal = null;
+
+ // if we have personal data, then convert it to a string value.
+ if (personalStart != null) {
+ TokenStream personalTokens = tokens.section(personalStart, personalEnd);
+ personal = personalToString(personalTokens);
+ }
+ // if we have a simple address, then check the first token to see if it's a comment. For simple addresses,
+ // we'll accept the first comment token as the personal information.
+ else {
+ if (addressType == SIMPLE_ADDR && first.type == COMMENT) {
+ personal = first.value;
+ }
+ }
+
+ TokenStream addressTokens = tokens.section(addressStart, addressEnd);
+
+ // if this is one of the strictly RFC822 types, then we always validate the address. If this is a
+ // a simple address, then we only validate if strict parsing rules are in effect or we've been asked
+ // to validate.
+ if (validationLevel != PARSE_HEADER) {
+ switch (addressType) {
+ case GROUP_ADDR:
+ validateGroup(addressTokens);
+ break;
+
+ case ROUTE_ADDR:
+ validateRouteAddr(addressTokens, false);
+ break;
+
+ case SIMPLE_ADDR:
+ // this is a conditional validation
+ validateSimpleAddress(addressTokens);
+ break;
+ }
+ }
+
+ // more complex addresses and addresses containing tokens other than just simple addresses
+ // need proper handling.
+ if (validationLevel != NONSTRICT || addressType != SIMPLE_ADDR || !nonStrictRules) {
+ // we might have traversed this already when we validated, so reset the
+ // position before using this again.
+ addressTokens.reset();
+ String address = addressToString(addressTokens);
+
+ // get the parsed out sections as string values.
+ InternetAddress result = new InternetAddress();
+ result.setAddress(address);
+ try {
+ result.setPersonal(personal);
+ } catch (UnsupportedEncodingException e) {
+ }
+ // even though we have a single address, we return this as an array. Simple addresses
+ // can be produce an array of items, so we need to return everything.
+ parsedAddresses.add(result);
+ return parsedAddresses;
+ }
+ else {
+ addressTokens.reset();
+
+ TokenStream nextAddress = addressTokens.getBlankDelimitedToken();
+ while (nextAddress != null) {
+ String address = addressToString(nextAddress);
+ // get the parsed out sections as string values.
+ InternetAddress result = new InternetAddress();
+ result.setAddress(address);
+ parsedAddresses.add(result);
+ nextAddress = addressTokens.getBlankDelimitedToken();
+ }
+ return parsedAddresses;
+ }
+ }
+
+
+ /**
+ * Scan the token stream, parsing off a route addr spec. This
+ * will do some basic syntax validation, but will not actually
+ * validate any of the address information. Comments will be
+ * discarded.
+ *
+ * @param tokens The stream of tokens.
+ *
+ * @return The last token of the route address (the one preceeding the
+ * terminating '>'.
+ */
+ private AddressToken scanRouteAddress(TokenStream tokens, boolean inGroup) throws AddressException {
+ // get the first token and ensure we have something between the "<" and ">".
+ AddressToken token = tokens.nextRealToken();
+ // the last processed non-whitespace token, which is the actual address end once the
+ // right angle bracket is encountered.
+
+ AddressToken previous = null;
+
+ // if this route-addr has route information, the first token after the '<' must be a '@'.
+ // this determines if/where a colon or comma can appear.
+ boolean inRoute = token.type == AT_SIGN;
+
+ // now scan until we reach the terminator. The only validation is done on illegal characters.
+ while (true) {
+ switch (token.type) {
+ // The following tokens are all valid between the brackets, so just skip over them.
+ case ATOM:
+ case QUOTED_LITERAL:
+ case DOMAIN_LITERAL:
+ case PERIOD:
+ case AT_SIGN:
+ break;
+
+ case COLON:
+ // if not processing route information, this is illegal.
+ if (!inRoute) {
+ illegalAddress("Unexpected ':'", token);
+ }
+ // this is the end of the route information, the rules now change.
+ inRoute = false;
+ break;
+
+ case COMMA:
+ // if not processing route information, this is illegal.
+ if (!inRoute) {
+ illegalAddress("Unexpected ','", token);
+ }
+ break;
+
+ case RIGHT_ANGLE:
+ // if previous is null, we've had a route address which is "<>". That's illegal.
+ if (previous == null) {
+ illegalAddress("Illegal address", token);
+ }
+ // step to the next token..this had better be either a comma for another address or
+ // the very end of the address list .
+ token = tokens.nextRealToken();
+ // if we're scanning part of a group, then the allowed terminators are either ',' or ';'.
+ if (inGroup) {
+ if (token.type != COMMA && token.type != SEMICOLON) {
+ illegalAddress("Illegal address", token);
+ }
+ }
+ // a normal address should have either a ',' for a list or the end.
+ else {
+ if (token.type != COMMA && token.type != END_OF_TOKENS) {
+ illegalAddress("Illegal address", token);
+ }
+ }
+ // we need to push the termination token back on.
+ tokens.pushToken(token);
+ // return the previous token as the updated position.
+ return previous;
+
+ case END_OF_TOKENS:
+ illegalAddress("Missing '>'", token);
+
+ // now for the illegal ones in this context.
+ case SEMICOLON:
+ illegalAddress("Unexpected ';'", token);
+
+ case LEFT_ANGLE:
+ illegalAddress("Unexpected '<'", token);
+ }
+ // remember the previous token.
+ previous = token;
+ token = tokens.nextRealToken();
+ }
+ }
+
+
+ /**
+ * Scan the token stream, parsing off a group address. This
+ * will do some basic syntax validation, but will not actually
+ * validate any of the address information. Comments will be
+ * ignored.
+ *
+ * @param tokens The stream of tokens.
+ *
+ * @return The last token of the group address (the terminating ':").
+ */
+ private AddressToken scanGroupAddress(TokenStream tokens) throws AddressException {
+ // A group does not require that there be anything between the ':' and ';". This is
+ // just a group with an empty list.
+ AddressToken token = tokens.nextRealToken();
+
+ // now scan until we reach the terminator. The only validation is done on illegal characters.
+ while (true) {
+ switch (token.type) {
+ // The following tokens are all valid in group addresses, so just skip over them.
+ case ATOM:
+ case QUOTED_LITERAL:
+ case DOMAIN_LITERAL:
+ case PERIOD:
+ case AT_SIGN:
+ case COMMA:
+ break;
+
+ case COLON:
+ illegalAddress("Nested group", token);
+
+ // route address within a group specifier....we need to at least verify the bracket nesting
+ // and higher level syntax of the route.
+ case LEFT_ANGLE:
+ scanRouteAddress(tokens, true);
+ break;
+
+ // the only allowed terminator is the ';'
+ case END_OF_TOKENS:
+ illegalAddress("Missing ';'", token);
+
+ // now for the illegal ones in this context.
+ case SEMICOLON:
+ // verify there's nothing illegal after this.
+ AddressToken next = tokens.nextRealToken();
+ if (next.type != COMMA && next.type != END_OF_TOKENS) {
+ illegalAddress("Illegal address", token);
+ }
+ // don't forget to put this back on...our caller will need it.
+ tokens.pushToken(next);
+ return token;
+
+ case RIGHT_ANGLE:
+ illegalAddress("Unexpected '>'", token);
+ }
+ token = tokens.nextRealToken();
+ }
+ }
+
+
+ /**
+ * Parse the provided internet address into a set of tokens. This
+ * phase only does a syntax check on the tokens. The interpretation
+ * of the tokens is the next phase.
+ *
+ * @exception AddressException
+ */
+ private TokenStream tokenizeAddress() throws AddressException {
+
+ // get a list for the set of tokens
+ TokenStream tokens = new TokenStream();
+
+ end = addresses.length(); // our parsing end marker
+
+ // now scan along the string looking for the special characters in an internet address.
+ while (moreCharacters()) {
+ char ch = currentChar();
+
+ switch (ch) {
+ // start of a comment bit...ignore everything until we hit a closing paren.
+ case '(':
+ scanComment(tokens);
+ break;
+ // a closing paren found outside of normal processing.
+ case ')':
+ syntaxError("Unexpected ')'", position);
+
+
+ // start of a quoted string
+ case '"':
+ scanQuotedLiteral(tokens);
+ break;
+ // domain literal
+ case '[':
+ scanDomainLiteral(tokens);
+ break;
+
+ // a naked closing bracket...not valid except as part of a domain literal.
+ case ']':
+ syntaxError("Unexpected ']'", position);
+
+ // special character delimiters
+ case '<':
+ tokens.addToken(new AddressToken(LEFT_ANGLE, position));
+ nextChar();
+ break;
+
+ // a naked closing bracket...not valid without a starting one, but
+ // we need to handle this in context.
+ case '>':
+ tokens.addToken(new AddressToken(RIGHT_ANGLE, position));
+ nextChar();
+ break;
+ case ':':
+ tokens.addToken(new AddressToken(COLON, position));
+ nextChar();
+ break;
+ case ',':
+ tokens.addToken(new AddressToken(COMMA, position));
+ nextChar();
+ break;
+ case '.':
+ tokens.addToken(new AddressToken(PERIOD, position));
+ nextChar();
+ break;
+ case ';':
+ tokens.addToken(new AddressToken(SEMICOLON, position));
+ nextChar();
+ break;
+ case '@':
+ tokens.addToken(new AddressToken(AT_SIGN, position));
+ nextChar();
+ break;
+
+ // white space characters. These are mostly token delimiters, but there are some relaxed
+ // situations where they get processed, so we need to add a white space token for the first
+ // one we encounter in a span.
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ // add a single white space token
+ tokens.addToken(new AddressToken(WHITESPACE, position));
+
+ nextChar();
+ // step over any space characters, leaving us positioned either at the end
+ // or the first
+ while (moreCharacters()) {
+ char nextChar = currentChar();
+ if (nextChar == ' ' || nextChar == '\t' || nextChar == '\r' || nextChar == '\n') {
+ nextChar();
+ }
+ else {
+ break;
+ }
+ }
+ break;
+
+ // potentially an atom...if it starts with an allowed atom character, we
+ // parse out the token, otherwise this is invalid.
+ default:
+ if (ch < 040 || ch >= 0177) {
+ syntaxError("Illegal character in address", position);
+ }
+
+ scanAtom(tokens);
+ break;
+ }
+ }
+
+ // for this end marker, give an end position.
+ tokens.addToken(new AddressToken(END_OF_TOKENS, addresses.length()));
+ return tokens;
+ }
+
+
+ /**
+ * Step to the next character position while parsing.
+ */
+ private void nextChar() {
+ position++;
+ }
+
+
+ /**
+ * Retrieve the character at the current parsing position.
+ *
+ * @return The current character.
+ */
+ private char currentChar() {
+ return addresses.charAt(position);
+ }
+
+ /**
+ * Test if there are more characters left to parse.
+ *
+ * @return True if we've hit the last character, false otherwise.
+ */
+ private boolean moreCharacters() {
+ return position < end;
+ }
+
+
+ /**
+ * Parse a quoted string as specified by the RFC822 specification.
+ *
+ * @param tokens The TokenStream where the parsed out token is added.
+ */
+ private void scanQuotedLiteral(TokenStream tokens) throws AddressException {
+ StringBuffer value = new StringBuffer();
+
+ // save the start position for the token.
+ int startPosition = position;
+ // step over the quote delimiter.
+ nextChar();
+
+ while (moreCharacters()) {
+ char ch = currentChar();
+
+ // is this an escape char?
+ if (ch == '\\') {
+ // step past this, and grab the following character
+ nextChar();
+ if (!moreCharacters()) {
+ syntaxError("Missing '\"'", position);
+ }
+ value.append(currentChar());
+ }
+ // end of the string?
+ else if (ch == '"') {
+ // return the constructed string.
+ tokens.addToken(new AddressToken(value.toString(), QUOTED_LITERAL, position));
+ // step over the close delimiter for the benefit of the next token.
+ nextChar();
+ return;
+ }
+ // the RFC822 spec disallows CR characters.
+ else if (ch == '\r') {
+ syntaxError("Illegal line end in literal", position);
+ }
+ else
+ {
+ value.append(ch);
+ }
+ nextChar();
+ }
+ // missing delimiter
+ syntaxError("Missing '\"'", position);
+ }
+
+
+ /**
+ * Parse a domain literal as specified by the RFC822 specification.
+ *
+ * @param tokens The TokenStream where the parsed out token is added.
+ */
+ private void scanDomainLiteral(TokenStream tokens) throws AddressException {
+ StringBuffer value = new StringBuffer();
+
+ int startPosition = position;
+ // step over the quote delimiter.
+ nextChar();
+
+ while (moreCharacters()) {
+ char ch = currentChar();
+
+ // is this an escape char?
+ if (ch == '\\') {
+ // because domain literals don't get extra escaping, we render them
+ // with the escaped characters intact. Therefore, append the '\' escape
+ // first, then append the escaped character without examination.
+ value.append(currentChar());
+ // step past this, and grab the following character
+ nextChar();
+ if (!moreCharacters()) {
+ syntaxError("Missing '\"'", position);
+ }
+ value.append(currentChar());
+ }
+ // end of the string?
+ else if (ch == ']') {
+ // return the constructed string.
+ tokens.addToken(new AddressToken(value.toString(), DOMAIN_LITERAL, startPosition));
+ // step over the close delimiter for the benefit of the next token.
+ nextChar();
+ return;
+ }
+ // the RFC822 spec says no nesting
+ else if (ch == '[') {
+ syntaxError("Unexpected '['", position);
+ }
+ // carriage returns are similarly illegal.
+ else if (ch == '\r') {
+ syntaxError("Illegal line end in domain literal", position);
+ }
+ else
+ {
+ value.append(ch);
+ }
+ nextChar();
+ }
+ // missing delimiter
+ syntaxError("Missing ']'", position);
+ }
+
+ /**
+ * Scan an atom in an internet address, using the RFC822 rules
+ * for atom delimiters.
+ *
+ * @param tokens The TokenStream where the parsed out token is added.
+ */
+ private void scanAtom(TokenStream tokens) throws AddressException {
+ int start = position;
+ nextChar();
+ while (moreCharacters()) {
+
+ char ch = currentChar();
+ if (isAtom(ch)) {
+ nextChar();
+ }
+ else {
+ break;
+ }
+ }
+
+ // return the scanned part of the string.
+ tokens.addToken(new AddressToken(addresses.substring(start, position), ATOM, start));
+ }
+
+
+ /**
+ * Parse an internet address comment field as specified by
+ * RFC822. Includes support for quoted characters and nesting.
+ *
+ * @param tokens The TokenStream where the parsed out token is added.
+ */
+ private void scanComment(TokenStream tokens) throws AddressException {
+ StringBuffer value = new StringBuffer();
+
+ int startPosition = position;
+ // step past the start character
+ nextChar();
+
+ // we're at the top nesting level on the comment.
+ int nest = 1;
+
+ // scan while we have more characters.
+ while (moreCharacters()) {
+ char ch = currentChar();
+ // escape character?
+ if (ch == '\\') {
+ // step over this...if escaped, we must have at least one more character
+ // in the string.
+ nextChar();
+ if (!moreCharacters()) {
+ syntaxError("Missing ')'", position);
+ }
+ value.append(currentChar());
+ }
+ // nested comment?
+ else if (ch == '(') {
+ // step the nesting level...we treat the comment as a single unit, with the delimiters
+ // for the nested comments embedded in the middle
+ nest++;
+ value.append(ch);
+ }
+ // is this the comment close?
+ else if (ch == ')') {
+ // reduce the nesting level. If we still have more to process, add the delimiter character
+ // and keep going.
+ nest--;
+ if (nest > 0) {
+ value.append(ch);
+ }
+ else {
+ // step past this and return. The outermost comment delimiter is not included in
+ // the string value, since this is frequently used as personal data on the
+ // InternetAddress objects.
+ nextChar();
+ tokens.addToken(new AddressToken(value.toString(), COMMENT, startPosition));
+ return;
+ }
+ }
+ else if (ch == '\r') {
+ syntaxError("Illegal line end in comment", position);
+ }
+ else {
+ value.append(ch);
+ }
+ // step to the next character.
+ nextChar();
+ }
+ // ran out of data before seeing the closing bit, not good
+ syntaxError("Missing ')'", position);
+ }
+
+
+ /**
+ * Validate the syntax of an RFC822 group internet address specification.
+ *
+ * @param tokens The stream of tokens for the address.
+ *
+ * @exception AddressException
+ */
+ private void validateGroup(TokenStream tokens) throws AddressException {
+ // we know already this is an address in the form "phrase:group;". Now we need to validate the
+ // elements.
+
+ int phraseCount = 0;
+
+ AddressToken token = tokens.nextRealToken();
+ // now scan to the semi color, ensuring we have only word or comment tokens.
+ while (token.type != COLON) {
+ // only these tokens are allowed here.
+ if (token.type != ATOM && token.type != QUOTED_LITERAL) {
+ invalidToken(token);
+ }
+ phraseCount++;
+ token = tokens.nextRealToken();
+ }
+
+
+ // RFC822 groups require a leading phrase in group specifiers.
+ if (phraseCount == 0) {
+ illegalAddress("Missing group identifier phrase", token);
+ }
+
+ // now we do the remainder of the parsing using the initial phrase list as the sink...the entire
+ // address will be converted to a string later.
+
+ // ok, we only know this has been valid up to the ":", now we have some real checks to perform.
+ while (true) {
+ // go scan off a mailbox. if everything goes according to plan, we should be positioned at either
+ // a comma or a semicolon.
+ validateGroupMailbox(tokens);
+
+ token = tokens.nextRealToken();
+
+ // we're at the end of the group. Make sure this is truely the end.
+ if (token.type == SEMICOLON) {
+ token = tokens.nextRealToken();
+ if (token.type != END_OF_TOKENS) {
+ illegalAddress("Illegal group address", token);
+ }
+ return;
+ }
+
+ // if not a semicolon, this better be a comma.
+ else if (token.type != COMMA) {
+ illegalAddress("Illegal group address", token);
+ }
+ }
+ }
+
+
+ /**
+ * Validate the syntax of single mailbox within a group address.
+ *
+ * @param tokens The stream of tokens representing the address.
+ *
+ * @exception AddressException
+ */
+ private void validateGroupMailbox(TokenStream tokens) throws AddressException {
+ AddressToken first = tokens.nextRealToken();
+ // is this just a null address in the list? then push the terminator back and return.
+ if (first.type == COMMA || first.type == SEMICOLON) {
+ tokens.pushToken(first);
+ return;
+ }
+
+ // now we need to scan ahead to see if we can determine the type.
+ AddressToken token = first;
+
+
+ // we need to scan forward to figure out what sort of address this is.
+ while (first != null) {
+ switch (token.type) {
+ // until we know the context, these are all just ignored.
+ case QUOTED_LITERAL:
+ case ATOM:
+ break;
+
+ // a LEFT_ANGLE indicates we have a full RFC822 mailbox form. The leading phrase
+ // is the personal info. The address is inside the brackets.
+ case LEFT_ANGLE:
+ tokens.pushToken(first);
+ validatePhrase(tokens, false);
+ validateRouteAddr(tokens, true);
+ return;
+
+ // we've hit a period as the first non-word token. This should be part of a local-part
+ // of an address.
+ case PERIOD:
+ // we've hit an "@" as the first non-word token. This is probably a simple address in
+ // the form "user@domain".
+ case AT_SIGN:
+ tokens.pushToken(first);
+ validateAddressSpec(tokens);
+ return;
+
+ // reached the end of string...this might be a null address, or one of the very simple name
+ // forms used for non-strict RFC822 versions. Reset, and try that form
+ case COMMA:
+ // this is the end of the group...handle it like a comma for now.
+ case SEMICOLON:
+ tokens.pushToken(first);
+ validateAddressSpec(tokens);
+ return;
+
+ case END_OF_TOKENS:
+ illegalAddress("Missing ';'", token);
+
+ }
+ token = tokens.nextRealToken();
+ }
+ }
+
+
+ /**
+ * Utility method for throwing an AddressException caused by an
+ * unexpected primitive token.
+ *
+ * @param token The token causing the problem (must not be a value type token).
+ *
+ * @exception AddressException
+ */
+ private void invalidToken(AddressToken token) throws AddressException {
+ illegalAddress("Unexpected '" + token.type + "'", token);
+ }
+
+
+ /**
+ * Raise an error about illegal syntax.
+ *
+ * @param message The message used in the thrown exception.
+ * @param position The parsing position within the string.
+ *
+ * @exception AddressException
+ */
+ private void syntaxError(String message, int position) throws AddressException
+ {
+ throw new AddressException(message, addresses, position);
+ }
+
+
+ /**
+ * Throw an exception based on the position of an invalid token.
+ *
+ * @param message The exception message.
+ * @param token The token causing the error. This tokens position is used
+ * in the exception information.
+ */
+ private void illegalAddress(String message, AddressToken token) throws AddressException {
+ throw new AddressException(message, addresses, token.position);
+ }
+
+
+ /**
+ * Validate that a required phrase exists.
+ *
+ * @param tokens The set of tokens to validate. positioned at the phrase start.
+ * @param required A flag indicating whether the phrase is optional or required.
+ *
+ * @exception AddressException
+ */
+ private void validatePhrase(TokenStream tokens, boolean required) throws AddressException {
+ // we need to have at least one WORD token in the phrase...everything is optional
+ // after that.
+ AddressToken token = tokens.nextRealToken();
+ if (token.type != ATOM && token.type != QUOTED_LITERAL) {
+ if (required) {
+ illegalAddress("Missing group phrase", token);
+ }
+ }
+
+ // now scan forward to the end of the phrase
+ token = tokens.nextRealToken();
+ while (token.type == ATOM || token.type == QUOTED_LITERAL) {
+ token = tokens.nextRealToken();
+ }
+ }
+
+
+ /**
+ * validate a routeaddr specification
+ *
+ * @param tokens The tokens representing the address portion (personal information
+ * already removed).
+ * @param ingroup true indicates we're validating a route address inside a
+ * group list. false indicates we're validating a standalone
+ * address.
+ *
+ * @exception AddressException
+ */
+ private void validateRouteAddr(TokenStream tokens, boolean ingroup) throws AddressException {
+ // get the next real token.
+ AddressToken token = tokens.nextRealToken();
+ // if this is an at sign, then we have a list of domains to parse.
+ if (token.type == AT_SIGN) {
+ // push the marker token back in for the route parser, and step past that part.
+ tokens.pushToken(token);
+ validateRoute(tokens);
+ }
+ else {
+ // we need to push this back on to validate the local part.
+ tokens.pushToken(token);
+ }
+
+ // now we expect to see an address spec.
+ validateAddressSpec(tokens);
+
+ token = tokens.nextRealToken();
+ if (ingroup) {
+ // if we're validating within a group specification, the angle brackets are still there (and
+ // required).
+ if (token.type != RIGHT_ANGLE) {
+ illegalAddress("Missing '>'", token);
+ }
+ }
+ else {
+ // the angle brackets were removed to make this an address, so we should be done. Make sure we
+ // have a terminator here.
+ if (token.type != END_OF_TOKENS) {
+ illegalAddress("Illegal Address", token);
+ }
+ }
+ }
+
+
+
+ /**
+ * Validate a simple address in the form "user@domain".
+ *
+ * @param tokens The stream of tokens representing the address.
+ */
+ private void validateSimpleAddress(TokenStream tokens) throws AddressException {
+
+ // the validation routines occur after addresses have been split into
+ // personal and address forms. Therefore, our validation begins directly
+ // with the first token.
+ validateAddressSpec(tokens);
+
+ // get the next token and see if there is something here...anything but the terminator is an error
+ AddressToken token = tokens.nextRealToken();
+ if (token.type != END_OF_TOKENS) {
+ illegalAddress("Illegal Address", token);
+ }
+ }
+
+ /**
+ * Validate the addr-spec portion of an address. RFC822 requires
+ * this be of the form "local-part@domain". However, javamail also
+ * allows simple address of the form "local-part". We only require
+ * the domain if an '@' is encountered.
+ *
+ * @param tokens
+ */
+ private void validateAddressSpec(TokenStream tokens) throws AddressException {
+ // all addresses, even the simple ones, must have at least a local part.
+ validateLocalPart(tokens);
+
+ // now see if we have a domain portion to look at.
+ AddressToken token = tokens.nextRealToken();
+ if (token.type == AT_SIGN) {
+ validateDomain(tokens);
+ }
+ else {
+ // put this back for termination
+ tokens.pushToken(token);
+ }
+
+ }
+
+
+ /**
+ * Validate the route portion of a route-addr. This is a list
+ * of domain values in the form 1#("@" domain) ":".
+ *
+ * @param tokens The token stream holding the address information.
+ */
+ private void validateRoute(TokenStream tokens) throws AddressException {
+ while (true) {
+ AddressToken token = tokens.nextRealToken();
+ // if this is the first part of the list, go parse off a domain
+ if (token.type == AT_SIGN) {
+ validateDomain(tokens);
+ }
+ // another element in the list? Go around again
+ else if (token.type == COMMA) {
+ continue;
+ }
+ // the list is terminated by a colon...stop this part of the validation once we hit one.
+ else if (token.type == COLON) {
+ return;
+ }
+ // the list is terminated by a colon. If this isn't one of those, we have an error.
+ else {
+ illegalAddress("Missing ':'", token);
+ }
+ }
+ }
+
+
+ /**
+ * Parse the local part of an address spec. The local part
+ * is a series of "words" separated by ".".
+ */
+ private void validateLocalPart(TokenStream tokens) throws AddressException {
+ while (true) {
+ // get the token.
+ AddressToken token = tokens.nextRealToken();
+
+ // this must be either an atom or a literal.
+ if (token.type != ATOM && token.type != QUOTED_LITERAL) {
+ illegalAddress("Invalid local part", token);
+ }
+
+ // get the next token (white space and comments ignored)
+ token = tokens.nextRealToken();
+ // if this is a period, we continue parsing
+ if (token.type != PERIOD) {
+ tokens.pushToken(token);
+ // return the token
+ return;
+ }
+ }
+ }
+
+
+
+ /**
+ * Parse a domain name of the form sub-domain *("." sub-domain).
+ * a sub-domain is either an atom or a domain-literal.
+ */
+ private void validateDomain(TokenStream tokens) throws AddressException {
+ while (true) {
+ // get the token.
+ AddressToken token = tokens.nextRealToken();
+
+ // this must be either an atom or a domain literal.
+ if (token.type != ATOM && token.type != DOMAIN_LITERAL) {
+ illegalAddress("Invalid domain", token);
+ }
+
+ // get the next token (white space is ignored)
+ token = tokens.nextRealToken();
+ // if this is a period, we continue parsing
+ if (token.type != PERIOD) {
+ // return the token
+ tokens.pushToken(token);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Convert a list of word tokens into a phrase string. The
+ * rules for this are a little hard to puzzle out, but there
+ * is a logic to it. If the list is empty, the phrase is
+ * just a null value.
+ *
+ * If we have a phrase, then the quoted strings need to
+ * handled appropriately. In multi-token phrases, the
+ * quoted literals are concatenated with the quotes intact,
+ * regardless of content. Thus a phrase that comes in like this:
+ *
+ * "Geronimo" Apache
+ *
+ * gets converted back to the same string.
+ *
+ * If there is just a single token in the phrase, AND the token
+ * is a quoted string AND the string does not contain embedded
+ * special characters ("\.,@<>()[]:;), then the phrase
+ * is expressed as an atom. Thus the literal
+ *
+ * "Geronimo"
+ *
+ * becomes
+ *
+ * Geronimo
+ *
+ * but
+ *
+ * "(Geronimo)"
+ *
+ * remains
+ *
+ * "(Geronimo)"
+ *
+ * Note that we're generating a canonical form of the phrase,
+ * which removes comments and reduces linear whitespace down
+ * to a single separator token.
+ *
+ * @param phrase An array list of phrase tokens (which may be empty).
+ */
+ private String personalToString(TokenStream tokens) {
+
+ // no tokens in the stream? This is a null value.
+ AddressToken token = tokens.nextToken();
+
+ if (token.type == END_OF_TOKENS) {
+ return null;
+ }
+
+ AddressToken next = tokens.nextToken();
+
+ // single element phrases get special treatment.
+ if (next.type == END_OF_TOKENS) {
+ // this can be used directly...if it contains special characters, quoting will be
+ // performed when it's converted to a string value.
+ return token.value;
+ }
+
+ // reset to the beginning
+ tokens.pushToken(token);
+
+ // have at least two tokens,
+ StringBuffer buffer = new StringBuffer();
+
+ // get the first token. After the first, we add these as blank delimited values.
+ token = tokens.nextToken();
+ addTokenValue(token, buffer);
+
+ token = tokens.nextToken();
+ while (token.type != END_OF_TOKENS) {
+ // add a blank separator
+ buffer.append(' ');
+ // now add the next tokens value
+ addTokenValue(token, buffer);
+ token = tokens.nextToken();
+ }
+ // and return the canonicalized value
+ return buffer.toString();
+ }
+
+
+ /**
+ * take a canonicalized set of address tokens and reformat it back into a string value,
+ * inserting whitespace where appropriate.
+ *
+ * @param tokens The set of tokens representing the address.
+ *
+ * @return The string value of the tokens.
+ */
+ private String addressToString(TokenStream tokens) {
+ StringBuffer buffer = new StringBuffer();
+
+ // this flag controls whether we insert a blank delimiter between tokens as
+ // we advance through the list. Blanks are only inserted between consequtive value tokens.
+ // Initially, this is false, then we flip it to true whenever we add a value token, and
+ // back to false for any special character token.
+ boolean spaceRequired = false;
+
+ // we use nextToken rather than nextRealToken(), since we need to process the comments also.
+ AddressToken token = tokens.nextToken();
+
+ // now add each of the tokens
+ while (token.type != END_OF_TOKENS) {
+ switch (token.type) {
+ // the word tokens are the only ones where we need to worry about adding
+ // whitespace delimiters.
+ case ATOM:
+ case QUOTED_LITERAL:
+ // was the last token also a word? Insert a blank first.
+ if (spaceRequired) {
+ buffer.append(' ');
+ }
+ addTokenValue(token, buffer);
+ // let the next iteration know we just added a word to the list.
+ spaceRequired = true;
+ break;
+
+ // these special characters are just added in. The constants for the character types
+ // were carefully selected to be the character value in question. This allows us to
+ // just append the value.
+ case LEFT_ANGLE:
+ case RIGHT_ANGLE:
+ case COMMA:
+ case COLON:
+ case AT_SIGN:
+ case SEMICOLON:
+ case PERIOD:
+ buffer.append((char)token.type);
+ // no spaces around specials
+ spaceRequired = false;
+ break;
+
+ // Domain literals self delimiting...we can just append them and turn off the space flag.
+ case DOMAIN_LITERAL:
+ addTokenValue(token, buffer);
+ spaceRequired = false;
+ break;
+
+ // Comments are also self delimitin.
+ case COMMENT:
+ addTokenValue(token, buffer);
+ spaceRequired = false;
+ break;
+ }
+ token = tokens.nextToken();
+ }
+ return buffer.toString();
+ }
+
+
+ /**
+ * Append a value token on to a string buffer used to create
+ * the canonicalized string value.
+ *
+ * @param token The token we're adding.
+ * @param buffer The target string buffer.
+ */
+ private void addTokenValue(AddressToken token, StringBuffer buffer) {
+ // atom values can be added directly.
+ if (token.type == ATOM) {
+ buffer.append(token.value);
+ }
+ // a literal value? Add this as a quoted string
+ else if (token.type == QUOTED_LITERAL) {
+ buffer.append(formatQuotedString(token.value));
+ }
+ // could be a domain literal of the form "[value]"
+ else if (token.type == DOMAIN_LITERAL) {
+ buffer.append('[');
+ buffer.append(token.value);
+ buffer.append(']');
+ }
+ // comments also have values
+ else if (token.type == COMMENT) {
+ buffer.append('(');
+ buffer.append(token.value);
+ buffer.append(')');
+ }
+ }
+
+
+
+ private static final byte[] CHARMAP = {
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x06, 0x02, 0x06, 0x02, 0x02, 0x06, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x00,
+
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ };
+
+ private static final byte FLG_SPECIAL = 1;
+ private static final byte FLG_CONTROL = 2;
+ private static final byte FLG_SPACE = 4;
+
+ private static boolean isSpace(char ch) {
+ if (ch > '\u007f') {
+ return false;
+ } else {
+ return (CHARMAP[ch] & FLG_SPACE) != 0;
+ }
+ }
+
+ /**
+ * Quick test to see if a character is an allowed atom character
+ * or not.
+ *
+ * @param ch The test character.
+ *
+ * @return true if this character is allowed in atoms, false for any
+ * control characters, special characters, or blanks.
+ */
+ public static boolean isAtom(char ch) {
+ if (ch > '\u007f') {
+ return false;
+ }
+ else if (ch == ' ') {
+ return false;
+ }
+ else {
+ return (CHARMAP[ch] & (FLG_SPECIAL | FLG_CONTROL)) == 0;
+ }
+ }
+
+ /**
+ * Tests one string to determine if it contains any of the
+ * characters in a supplied test string.
+ *
+ * @param s The string we're testing.
+ * @param chars The set of characters we're testing against.
+ *
+ * @return true if any of the characters is found, false otherwise.
+ */
+ public static boolean containsCharacters(String s, String chars)
+ {
+ for (int i = 0; i < s.length(); i++) {
+ if (chars.indexOf(s.charAt(i)) >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Tests if a string contains any non-special characters that
+ * would require encoding the value as a quoted string rather
+ * than a simple atom value.
+ *
+ * @param s The test string.
+ *
+ * @return True if the string contains only blanks or allowed atom
+ * characters.
+ */
+ public static boolean containsSpecials(String s)
+ {
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ // must be either a blank or an allowed atom char.
+ if (ch == ' ' || isAtom(ch)) {
+ continue;
+ }
+ else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Tests if a string contains any non-special characters that
+ * would require encoding the value as a quoted string rather
+ * than a simple atom value.
+ *
+ * @param s The test string.
+ *
+ * @return True if the string contains only blanks or allowed atom
+ * characters.
+ */
+ public static boolean isAtom(String s)
+ {
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ // must be an allowed atom character
+ if (!isAtom(ch)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Apply RFC822 quoting rules to a literal string value. This
+ * will search the string to see if there are any characters that
+ * require special escaping, and apply the escapes. If the
+ * string is just a string of blank-delimited atoms, the string
+ * value is returned without quotes.
+ *
+ * @param s The source string.
+ *
+ * @return A version of the string as a valid RFC822 quoted literal.
+ */
+ public static String quoteString(String s) {
+
+ // only backslash and double quote require escaping. If the string does not
+ // contain any of these, then we can just slap on some quotes and go.
+ if (s.indexOf('\\') == -1 && s.indexOf('"') == -1) {
+ // if the string is an atom (or a series of blank-delimited atoms), we can just return it directly.
+ if (!containsSpecials(s)) {
+ return s;
+ }
+ StringBuffer buffer = new StringBuffer(s.length() + 2);
+ buffer.append('"');
+ buffer.append(s);
+ buffer.append('"');
+ return buffer.toString();
+ }
+
+ // get a buffer sufficiently large for the string, two quote characters, and a "reasonable"
+ // number of escaped values.
+ StringBuffer buffer = new StringBuffer(s.length() + 10);
+ buffer.append('"');
+
+ // now check all of the characters.
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ // character requiring escaping?
+ if (ch == '\\' || ch == '"') {
+ // add an extra backslash
+ buffer.append('\\');
+ }
+ // and add on the character
+ buffer.append(ch);
+ }
+ buffer.append('"');
+ return buffer.toString();
+ }
+
+ /**
+ * Apply RFC822 quoting rules to a literal string value. This
+ * will search the string to see if there are any characters that
+ * require special escaping, and apply the escapes. The returned
+ * value is enclosed in quotes.
+ *
+ * @param s The source string.
+ *
+ * @return A version of the string as a valid RFC822 quoted literal.
+ */
+ public static String formatQuotedString(String s) {
+ // only backslash and double quote require escaping. If the string does not
+ // contain any of these, then we can just slap on some quotes and go.
+ if (s.indexOf('\\') == -1 && s.indexOf('"') == -1) {
+ StringBuffer buffer = new StringBuffer(s.length() + 2);
+ buffer.append('"');
+ buffer.append(s);
+ buffer.append('"');
+ return buffer.toString();
+ }
+
+ // get a buffer sufficiently large for the string, two quote characters, and a "reasonable"
+ // number of escaped values.
+ StringBuffer buffer = new StringBuffer(s.length() + 10);
+ buffer.append('"');
+
+ // now check all of the characters.
+ for (int i = 0; i < s.length(); i++) {
+ char ch = s.charAt(i);
+ // character requiring escaping?
+ if (ch == '\\' || ch == '"') {
+ // add an extra backslash
+ buffer.append('\\');
+ }
+ // and add on the character
+ buffer.append(ch);
+ }
+ buffer.append('"');
+ return buffer.toString();
+ }
+
+ public class TokenStream {
+ // the set of tokens in the parsed address list, as determined by RFC822 syntax rules.
+ private List tokens;
+
+ // the current token position
+ int currentToken = 0;
+
+
+ /**
+ * Default constructor for a TokenStream. This creates an
+ * empty TokenStream for purposes of tokenizing an address.
+ * It is the creator's responsibility to terminate the stream
+ * with a terminator token.
+ */
+ public TokenStream() {
+ tokens = new ArrayList();
+ }
+
+
+ /**
+ * Construct a TokenStream from a list of tokens. A terminator
+ * token is added to the end.
+ *
+ * @param tokens An existing token list.
+ */
+ public TokenStream(List tokens) {
+ this.tokens = tokens;
+ tokens.add(new AddressToken(END_OF_TOKENS, -1));
+ }
+
+ /**
+ * Add an address token to the token list.
+ *
+ * @param t The new token to add to the list.
+ */
+ public void addToken(AddressToken token) {
+ tokens.add(token);
+ }
+
+ /**
+ * Get the next token at the cursor position, advancing the
+ * position accordingly.
+ *
+ * @return The token at the current token position.
+ */
+ public AddressToken nextToken() {
+ AddressToken token = (AddressToken)tokens.get(currentToken++);
+ // we skip over white space tokens when operating in this mode, so
+ // check the token and iterate until we get a non-white space.
+ while (token.type == WHITESPACE) {
+ token = (AddressToken)tokens.get(currentToken++);
+ }
+ return token;
+ }
+
+
+ /**
+ * Get the next token at the cursor position, without advancing the
+ * position.
+ *
+ * @return The token at the current token position.
+ */
+ public AddressToken currentToken() {
+ // return the current token and step the cursor
+ return (AddressToken)tokens.get(currentToken);
+ }
+
+
+ /**
+ * Get the next non-comment token from the string. Comments are ignored, except as personal information
+ * for very simple address specifications.
+ *
+ * @return A token guaranteed not to be a whitespace token.
+ */
+ public AddressToken nextRealToken()
+ {
+ AddressToken token = nextToken();
+ if (token.type == COMMENT) {
+ token = nextToken();
+ }
+ return token;
+ }
+
+ /**
+ * Push a token back on to the queue, making the index of this
+ * token the current cursor position.
+ *
+ * @param token The token to push.
+ */
+ public void pushToken(AddressToken token) {
+ // just reset the cursor to the token's index position.
+ currentToken = tokenIndex(token);
+ }
+
+ /**
+ * Get the next token after a given token, without advancing the
+ * token position.
+ *
+ * @param token The token we're retrieving a token relative to.
+ *
+ * @return The next token in the list.
+ */
+ public AddressToken nextToken(AddressToken token) {
+ return (AddressToken)tokens.get(tokenIndex(token) + 1);
+ }
+
+
+ /**
+ * Return the token prior to a given token.
+ *
+ * @param token The token used for the index.
+ *
+ * @return The token prior to the index token in the list.
+ */
+ public AddressToken previousToken(AddressToken token) {
+ return (AddressToken)tokens.get(tokenIndex(token) - 1);
+ }
+
+
+ /**
+ * Retrieve a token at a given index position.
+ *
+ * @param index The target index.
+ */
+ public AddressToken getToken(int index)
+ {
+ return (AddressToken)tokens.get(index);
+ }
+
+
+ /**
+ * Retrieve the index of a particular token in the stream.
+ *
+ * @param token The target token.
+ *
+ * @return The index of the token within the stream. Returns -1 if this
+ * token is somehow not in the stream.
+ */
+ public int tokenIndex(AddressToken token) {
+ return tokens.indexOf(token);
+ }
+
+
+ /**
+ * Extract a new TokenStream running from the start token to the
+ * token preceeding the end token.
+ *
+ * @param start The starting token of the section.
+ * @param end The last token (+1) for the target section.
+ *
+ * @return A new TokenStream object for processing this section of tokens.
+ */
+ public TokenStream section(AddressToken start, AddressToken end) {
+ int startIndex = tokenIndex(start);
+ int endIndex = tokenIndex(end);
+
+ // List.subList() returns a list backed by the original list. Since we need to add a
+ // terminator token to this list when we take the sublist, we need to manually copy the
+ // references so we don't end up munging the original list.
+ ArrayList list = new ArrayList(endIndex - startIndex + 2);
+
+ for (int i = startIndex; i <= endIndex; i++) {
+ list.add(tokens.get(i));
+ }
+ return new TokenStream(list);
+ }
+
+
+ /**
+ * Reset the token position back to the beginning of the
+ * stream.
+ */
+ public void reset() {
+ currentToken = 0;
+ }
+
+ /**
+ * Scan forward looking for a non-blank token.
+ *
+ * @return The first non-blank token in the stream.
+ */
+ public AddressToken getNonBlank()
+ {
+ AddressToken token = currentToken();
+ while (token.type == WHITESPACE) {
+ currentToken++;
+ token = currentToken();
+ }
+ return token;
+ }
+
+
+ /**
+ * Extract a blank delimited token from a TokenStream. A blank
+ * delimited token is the set of tokens up to the next real whitespace
+ * token (comments not included).
+ *
+ * @return A TokenStream object with the new set of tokens.
+ */
+ public TokenStream getBlankDelimitedToken()
+ {
+ // get the next non-whitespace token.
+ AddressToken first = getNonBlank();
+ // if this is the end, we return null.
+ if (first.type == END_OF_TOKENS) {
+ return null;
+ }
+
+ AddressToken last = first;
+
+ // the methods for retrieving tokens skip over whitespace, so we're going to process this
+ // by index.
+ currentToken++;
+
+ AddressToken token = currentToken();
+ while (true) {
+ // if this is our marker, then pluck out the section and return it.
+ if (token.type == END_OF_TOKENS || token.type == WHITESPACE) {
+ return section(first, last);
+ }
+ last = token;
+ currentToken++;
+ // we accept any and all tokens here.
+ token = currentToken();
+ }
+ }
+
+ /**
+ * Return the index of the current cursor position.
+ *
+ * @return The integer index of the current token.
+ */
+ public int currentIndex() {
+ return currentToken;
+ }
+
+ public void dumpTokens()
+ {
+ System.out.println(">>>>>>>>> Start dumping TokenStream tokens");
+ for (int i = 0; i < tokens.size(); i++) {
+ System.out.println("-------- Token: " + tokens.get(i));
+ }
+
+ System.out.println("++++++++ cursor position=" + currentToken);
+ System.out.println(">>>>>>>>> End dumping TokenStream tokens");
+ }
+ }
+
+
+ /**
+ * Simple utility class for representing address tokens.
+ */
+ public class AddressToken {
+
+ // the token type
+ int type;
+
+ // string value of the token (can be null)
+ String value;
+
+ // position of the token within the address string.
+ int position;
+
+ AddressToken(int type, int position)
+ {
+ this.type = type;
+ this.value = null;
+ this.position = position;
+ }
+
+ AddressToken(String value, int type, int position)
+ {
+ this.type = type;
+ this.value = value;
+ this.position = position;
+ }
+
+ public String toString()
+ {
+ if (type == END_OF_TOKENS) {
+ return "AddressToken: type=END_OF_TOKENS";
+ }
+ if (value == null) {
+ return "AddressToken: type=" + (char)type;
+ }
+ else {
+ return "AddressToken: type=" + (char)type + " value=" + value;
+ }
+ }
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/ContentDisposition.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/ContentDisposition.java
new file mode 100644
index 00000000..5a0b3150
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/ContentDisposition.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 javax.mail.internet;
+
+// http://www.faqs.org/rfcs/rfc2183.html
+
+/**
+ * @version $Rev: 669445 $ $Date: 2008-06-19 05:48:18 -0500 (Thu, 19 Jun 2008) $
+ */
+public class ContentDisposition {
+ private String _disposition;
+ private ParameterList _list;
+
+ public ContentDisposition() {
+ setDisposition(null);
+ setParameterList(null);
+ }
+
+ public ContentDisposition(String disposition) throws ParseException {
+ // get a token parser for the type information
+ HeaderTokenizer tokenizer = new HeaderTokenizer(disposition, HeaderTokenizer.MIME);
+
+ // get the first token, which must be an ATOM
+ HeaderTokenizer.Token token = tokenizer.next();
+ if (token.getType() != HeaderTokenizer.Token.ATOM) {
+ throw new ParseException("Invalid content disposition");
+ }
+
+ _disposition = token.getValue();
+
+ // the remainder is parameters, which ParameterList will take care of parsing.
+ String remainder = tokenizer.getRemainder();
+ if (remainder != null) {
+ _list = new ParameterList(remainder);
+ }
+ }
+
+ public ContentDisposition(String disposition, ParameterList list) {
+ setDisposition(disposition);
+ setParameterList(list);
+ }
+
+ public String getDisposition() {
+ return _disposition;
+ }
+
+ public String getParameter(String name) {
+ if (_list == null) {
+ return null;
+ } else {
+ return _list.get(name);
+ }
+ }
+
+ public ParameterList getParameterList() {
+ return _list;
+ }
+
+ public void setDisposition(String string) {
+ _disposition = string;
+ }
+
+ public void setParameter(String name, String value) {
+ if (_list == null) {
+ _list = new ParameterList();
+ }
+ _list.set(name, value);
+ }
+
+ public void setParameterList(ParameterList list) {
+ if (list == null) {
+ _list = new ParameterList();
+ } else {
+ _list = list;
+ }
+ }
+
+ public String toString() {
+ // it is possible we might have a parameter list, but this is meaningless if
+ // there is no disposition string. Return a failure.
+ if (_disposition == null) {
+ return null;
+ }
+
+
+ // no parameter list? Just return the disposition string
+ if (_list == null) {
+ return _disposition;
+ }
+
+ // format this for use on a Content-Disposition header, which means we need to
+ // account for the length of the header part too.
+ return _disposition + _list.toString("Content-Disposition".length() + _disposition.length());
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/ContentType.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/ContentType.java
new file mode 100644
index 00000000..fc74c8ac
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/ContentType.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+// can be in the form major/minor; charset=jobby
+
+/**
+ * @version $Rev: 669445 $ $Date: 2008-06-19 05:48:18 -0500 (Thu, 19 Jun 2008) $
+ */
+public class ContentType {
+ private ParameterList _list;
+ private String _minor;
+ private String _major;
+
+ public ContentType() {
+ // the Sun version makes everything null here.
+ }
+
+ public ContentType(String major, String minor, ParameterList list) {
+ _major = major;
+ _minor = minor;
+ _list = list;
+ }
+
+ public ContentType(String type) throws ParseException {
+ // get a token parser for the type information
+ HeaderTokenizer tokenizer = new HeaderTokenizer(type, HeaderTokenizer.MIME);
+
+ // get the first token, which must be an ATOM
+ HeaderTokenizer.Token token = tokenizer.next();
+ if (token.getType() != HeaderTokenizer.Token.ATOM) {
+ throw new ParseException("Invalid content type");
+ }
+
+ _major = token.getValue();
+
+ // the MIME type must be major/minor
+ token = tokenizer.next();
+ if (token.getType() != '/') {
+ throw new ParseException("Invalid content type");
+ }
+
+
+ // this must also be an atom. Content types are not permitted to be wild cards.
+ token = tokenizer.next();
+ if (token.getType() != HeaderTokenizer.Token.ATOM) {
+ throw new ParseException("Invalid content type");
+ }
+
+ _minor = token.getValue();
+
+ // the remainder is parameters, which ParameterList will take care of parsing.
+ String remainder = tokenizer.getRemainder();
+ if (remainder != null) {
+ _list = new ParameterList(remainder);
+ }
+ }
+
+ public String getPrimaryType() {
+ return _major;
+ }
+
+ public String getSubType() {
+ return _minor;
+ }
+
+ public String getBaseType() {
+ return _major + "/" + _minor;
+ }
+
+ public String getParameter(String name) {
+ return (_list == null ? null : _list.get(name));
+ }
+
+ public ParameterList getParameterList() {
+ return _list;
+ }
+
+ public void setPrimaryType(String major) {
+ _major = major;
+ }
+
+ public void setSubType(String minor) {
+ _minor = minor;
+ }
+
+ public void setParameter(String name, String value) {
+ if (_list == null) {
+ _list = new ParameterList();
+ }
+ _list.set(name, value);
+ }
+
+ public void setParameterList(ParameterList list) {
+ _list = list;
+ }
+
+ public String toString() {
+ if (_major == null || _minor == null) {
+ return null;
+ }
+
+ // We need to format this as if we're doing it to set into the Content-Type
+ // header. So the parameter list gets added on as if the header name was
+ // also included.
+ String baseType = getBaseType();
+ if (_list != null) {
+ baseType += _list.toString(baseType.length() + "Content-Type: ".length());
+ }
+
+ return baseType;
+ }
+
+ public boolean match(ContentType other) {
+ return _major.equalsIgnoreCase(other._major)
+ && (_minor.equalsIgnoreCase(other._minor)
+ || _minor.equals("*")
+ || other._minor.equals("*"));
+ }
+
+ public boolean match(String contentType) {
+ try {
+ return match(new ContentType(contentType));
+ } catch (ParseException e) {
+ return false;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/HeaderTokenizer.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/HeaderTokenizer.java
new file mode 100644
index 00000000..3a4ca738
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/HeaderTokenizer.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+/**
+ * @version $Rev: 729233 $ $Date: 2008-12-23 23:08:45 -0600 (Tue, 23 Dec 2008) $
+ */
+public class HeaderTokenizer {
+ public static class Token {
+ // Constant values from J2SE 1.4 API Docs (Constant values)
+ public static final int ATOM = -1;
+ public static final int COMMENT = -3;
+ public static final int EOF = -4;
+ public static final int QUOTEDSTRING = -2;
+ private int _type;
+ private String _value;
+
+ public Token(int type, String value) {
+ _type = type;
+ _value = value;
+ }
+
+ public int getType() {
+ return _type;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+ }
+
+ private static final Token EOF = new Token(Token.EOF, null);
+ // characters not allowed in MIME
+ public static final String MIME = "()<>@,;:\\\"\t []/?=";
+ // charaters not allowed in RFC822
+ public static final String RFC822 = "()<>@,;:\\\"\t .[]";
+ private static final String WHITE = " \t\n\r";
+ private String _delimiters;
+ private String _header;
+ private boolean _skip;
+ private int pos;
+
+ public HeaderTokenizer(String header) {
+ this(header, RFC822);
+ }
+
+ public HeaderTokenizer(String header, String delimiters) {
+ this(header, delimiters, true);
+ }
+
+ public HeaderTokenizer(String header,
+ String delimiters,
+ boolean skipComments) {
+ _skip = skipComments;
+ _header = header;
+ _delimiters = delimiters;
+ }
+
+ public String getRemainder() {
+ return _header.substring(pos);
+ }
+
+ public Token next() throws ParseException {
+ return readToken();
+ }
+
+ public Token peek() throws ParseException {
+ int start = pos;
+ try {
+ return readToken();
+ } finally {
+ pos = start;
+ }
+ }
+
+ /**
+ * Read an ATOM token from the parsed header.
+ *
+ * @return A token containing the value of the atom token.
+ */
+ private Token readAtomicToken() {
+ // skip to next delimiter
+ int start = pos;
+ while (++pos < _header.length()) {
+ // break on the first non-atom character.
+ char ch = _header.charAt(pos);
+ if (_delimiters.indexOf(_header.charAt(pos)) != -1 || ch < 32 || ch >= 127) {
+ break;
+ }
+ }
+
+ return new Token(Token.ATOM, _header.substring(start, pos));
+ }
+
+ /**
+ * Read the next token from the header.
+ *
+ * @return The next token from the header. White space is skipped, and comment
+ * tokens are also skipped if indicated.
+ * @exception ParseException
+ */
+ private Token readToken() throws ParseException {
+ if (pos >= _header.length()) {
+ return EOF;
+ } else {
+ char c = _header.charAt(pos);
+ // comment token...read and skip over this
+ if (c == '(') {
+ Token comment = readComment();
+ if (_skip) {
+ return readToken();
+ } else {
+ return comment;
+ }
+ // quoted literal
+ } else if (c == '\"') {
+ return readQuotedString();
+ // white space, eat this and find a real token.
+ } else if (WHITE.indexOf(c) != -1) {
+ eatWhiteSpace();
+ return readToken();
+ // either a CTL or special. These characters have a self-defining token type.
+ } else if (c < 32 || c >= 127 || _delimiters.indexOf(c) != -1) {
+ pos++;
+ return new Token((int)c, String.valueOf(c));
+ } else {
+ // start of an atom, parse it off.
+ return readAtomicToken();
+ }
+ }
+ }
+
+ /**
+ * Extract a substring from the header string and apply any
+ * escaping/folding rules to the string.
+ *
+ * @param start The starting offset in the header.
+ * @param end The header end offset + 1.
+ *
+ * @return The processed string value.
+ * @exception ParseException
+ */
+ private String getEscapedValue(int start, int end) throws ParseException {
+ StringBuffer value = new StringBuffer();
+
+ for (int i = start; i < end; i++) {
+ char ch = _header.charAt(i);
+ // is this an escape character?
+ if (ch == '\\') {
+ i++;
+ if (i == end) {
+ throw new ParseException("Invalid escape character");
+ }
+ value.append(_header.charAt(i));
+ }
+ // line breaks are ignored, except for naked '\n' characters, which are consider
+ // parts of linear whitespace.
+ else if (ch == '\r') {
+ // see if this is a CRLF sequence, and skip the second if it is.
+ if (i < end - 1 && _header.charAt(i + 1) == '\n') {
+ i++;
+ }
+ }
+ else {
+ // just append the ch value.
+ value.append(ch);
+ }
+ }
+ return value.toString();
+ }
+
+ /**
+ * Read a comment from the header, applying nesting and escape
+ * rules to the content.
+ *
+ * @return A comment token with the token value.
+ * @exception ParseException
+ */
+ private Token readComment() throws ParseException {
+ int start = pos + 1;
+ int nesting = 1;
+
+ boolean requiresEscaping = false;
+
+ // skip to end of comment/string
+ while (++pos < _header.length()) {
+ char ch = _header.charAt(pos);
+ if (ch == ')') {
+ nesting--;
+ if (nesting == 0) {
+ break;
+ }
+ }
+ else if (ch == '(') {
+ nesting++;
+ }
+ else if (ch == '\\') {
+ pos++;
+ requiresEscaping = true;
+ }
+ // we need to process line breaks also
+ else if (ch == '\r') {
+ requiresEscaping = true;
+ }
+ }
+
+ if (nesting != 0) {
+ throw new ParseException("Unbalanced comments");
+ }
+
+ String value;
+ if (requiresEscaping) {
+ value = getEscapedValue(start, pos);
+ }
+ else {
+ value = _header.substring(start, pos++);
+ }
+ return new Token(Token.COMMENT, value);
+ }
+
+ /**
+ * Parse out a quoted string from the header, applying escaping
+ * rules to the value.
+ *
+ * @return The QUOTEDSTRING token with the value.
+ * @exception ParseException
+ */
+ private Token readQuotedString() throws ParseException {
+ int start = pos+1;
+ boolean requiresEscaping = false;
+
+ // skip to end of comment/string
+ while (++pos < _header.length()) {
+ char ch = _header.charAt(pos);
+ if (ch == '"') {
+ String value;
+ if (requiresEscaping) {
+ value = getEscapedValue(start, pos++);
+ }
+ else {
+ value = _header.substring(start, pos++);
+ }
+ return new Token(Token.QUOTEDSTRING, value);
+ }
+ else if (ch == '\\') {
+ pos++;
+ requiresEscaping = true;
+ }
+ // we need to process line breaks also
+ else if (ch == '\r') {
+ requiresEscaping = true;
+ }
+ }
+
+ throw new ParseException("Missing '\"'");
+ }
+
+ /**
+ * Skip white space in the token string.
+ */
+ private void eatWhiteSpace() {
+ // skip to end of whitespace
+ while (++pos < _header.length()
+ && WHITE.indexOf(_header.charAt(pos)) != -1)
+ ;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/InternetAddress.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/InternetAddress.java
new file mode 100644
index 00000000..708c5d24
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/InternetAddress.java
@@ -0,0 +1,560 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.UnsupportedEncodingException;
+
+import javax.mail.Address;
+import javax.mail.Session;
+
+/**
+ * A representation of an Internet email address as specified by RFC822 in
+ * conjunction with a human-readable personal name that can be encoded as
+ * specified by RFC2047.
+ * A typical address is "user@host.domain" and personal name "Joe User"
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class InternetAddress extends Address implements Cloneable {
+ /**
+ * The address in RFC822 format.
+ */
+ protected String address;
+
+ /**
+ * The personal name in RFC2047 format.
+ * Subclasses must ensure that this field is updated if the personal field
+ * is updated; alternatively, it can be invalidated by setting to null
+ * which will cause it to be recomputed.
+ */
+ protected String encodedPersonal;
+
+ /**
+ * The personal name as a Java String.
+ * Subclasses must ensure that this field is updated if the encodedPersonal field
+ * is updated; alternatively, it can be invalidated by setting to null
+ * which will cause it to be recomputed.
+ */
+ protected String personal;
+
+ public InternetAddress() {
+ }
+
+ public InternetAddress(String address) throws AddressException {
+ this(address, true);
+ }
+
+ public InternetAddress(String address, boolean strict) throws AddressException {
+ // use the parse method to process the address. This has the wierd side effect of creating a new
+ // InternetAddress instance to create an InternetAddress, but these are lightweight objects and
+ // we need access to multiple pieces of data from the parsing process.
+ AddressParser parser = new AddressParser(address, strict ? AddressParser.STRICT : AddressParser.NONSTRICT);
+
+ InternetAddress parsedAddress = parser.parseAddress();
+ // copy the important information, which right now is just the address and
+ // personal info.
+ this.address = parsedAddress.address;
+ this.personal = parsedAddress.personal;
+ this.encodedPersonal = parsedAddress.encodedPersonal;
+ }
+
+ public InternetAddress(String address, String personal) throws UnsupportedEncodingException {
+ this(address, personal, null);
+ }
+
+ public InternetAddress(String address, String personal, String charset) throws UnsupportedEncodingException {
+ this.address = address;
+ setPersonal(personal, charset);
+ }
+
+ /**
+ * Clone this object.
+ *
+ * @return a copy of this object as created by Object.clone()
+ */
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new Error();
+ }
+ }
+
+ /**
+ * Return the type of this address.
+ *
+ * @return the type of this address; always "rfc822"
+ */
+ public String getType() {
+ return "rfc822";
+ }
+
+ /**
+ * Set the address.
+ * No validation is performed; validate() can be used to check if it is valid.
+ *
+ * @param address the address to set
+ */
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ /**
+ * Set the personal name.
+ * The name is first checked to see if it can be encoded; if this fails then an
+ * UnsupportedEncodingException is thrown and no fields are modified.
+ *
+ * @param name the new personal name
+ * @param charset the charset to use; see {@link MimeUtility#encodeWord(String, String, String) MimeUtilityencodeWord}
+ * @throws UnsupportedEncodingException if the name cannot be encoded
+ */
+ public void setPersonal(String name, String charset) throws UnsupportedEncodingException {
+ personal = name;
+ if (name != null) {
+ encodedPersonal = MimeUtility.encodeWord(name, charset, null);
+ }
+ else {
+ encodedPersonal = null;
+ }
+ }
+
+ /**
+ * Set the personal name.
+ * The name is first checked to see if it can be encoded using {@link MimeUtility#encodeWord(String)}; if this fails then an
+ * UnsupportedEncodingException is thrown and no fields are modified.
+ *
+ * @param name the new personal name
+ * @throws UnsupportedEncodingException if the name cannot be encoded
+ */
+ public void setPersonal(String name) throws UnsupportedEncodingException {
+ personal = name;
+ if (name != null) {
+ encodedPersonal = MimeUtility.encodeWord(name);
+ }
+ else {
+ encodedPersonal = null;
+ }
+ }
+
+ /**
+ * Return the address.
+ *
+ * @return the address
+ */
+ public String getAddress() {
+ return address;
+ }
+
+ /**
+ * Return the personal name.
+ * If the personal field is null, then an attempt is made to decode the encodedPersonal
+ * field using {@link MimeUtility#decodeWord(String)}; if this is sucessful, then
+ * the personal field is updated with that value and returned; if there is a problem
+ * decoding the text then the raw value from encodedPersonal is returned.
+ *
+ * @return the personal name
+ */
+ public String getPersonal() {
+ if (personal == null && encodedPersonal != null) {
+ try {
+ personal = MimeUtility.decodeWord(encodedPersonal);
+ } catch (ParseException e) {
+ return encodedPersonal;
+ } catch (UnsupportedEncodingException e) {
+ return encodedPersonal;
+ }
+ }
+ return personal;
+ }
+
+ /**
+ * Return the encoded form of the personal name.
+ * If the encodedPersonal field is null, then an attempt is made to encode the
+ * personal field using {@link MimeUtility#encodeWord(String)}; if this is
+ * successful then the encodedPersonal field is updated with that value and returned;
+ * if there is a problem encoding the text then null is returned.
+ *
+ * @return the encoded form of the personal name
+ */
+ private String getEncodedPersonal() {
+ if (encodedPersonal == null && personal != null) {
+ try {
+ encodedPersonal = MimeUtility.encodeWord(personal);
+ } catch (UnsupportedEncodingException e) {
+ // as we could not encode this, return null
+ return null;
+ }
+ }
+ return encodedPersonal;
+ }
+
+
+ /**
+ * Return a string representation of this address using only US-ASCII characters.
+ *
+ * @return a string representation of this address
+ */
+ public String toString() {
+ // group addresses are always returned without modification.
+ if (isGroup()) {
+ return address;
+ }
+
+ // if we have personal information, then we need to return this in the route-addr form:
+ // "personal ". If there is no personal information, then we typically return
+ // the address without the angle brackets. However, if the address contains anything other
+ // than atoms, '@', and '.' (e.g., uses domain literals, has specified routes, or uses
+ // quoted strings in the local-part), we bracket the address.
+ String p = getEncodedPersonal();
+ if (p == null) {
+ return formatAddress(address);
+ }
+ else {
+ StringBuffer buf = new StringBuffer(p.length() + 8 + address.length() + 3);
+ buf.append(AddressParser.quoteString(p));
+ buf.append(" <").append(address).append(">");
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Check the form of an address, and enclose it within brackets
+ * if they are required for this address form.
+ *
+ * @param a The source address.
+ *
+ * @return A formatted address, which can be the original address string.
+ */
+ private String formatAddress(String a)
+ {
+ // this could be a group address....we don't muck with those.
+ if (address.endsWith(";") && address.indexOf(":") > 0) {
+ return address;
+ }
+
+ if (AddressParser.containsCharacters(a, "()<>,;:\"[]")) {
+ StringBuffer buf = new StringBuffer(address.length() + 3);
+ buf.append("<").append(address).append(">");
+ return buf.toString();
+ }
+ return address;
+ }
+
+ /**
+ * Return a string representation of this address using Unicode characters.
+ *
+ * @return a string representation of this address
+ */
+ public String toUnicodeString() {
+ // group addresses are always returned without modification.
+ if (isGroup()) {
+ return address;
+ }
+
+ // if we have personal information, then we need to return this in the route-addr form:
+ // "personal ". If there is no personal information, then we typically return
+ // the address without the angle brackets. However, if the address contains anything other
+ // than atoms, '@', and '.' (e.g., uses domain literals, has specified routes, or uses
+ // quoted strings in the local-part), we bracket the address.
+
+ // NB: The difference between toString() and toUnicodeString() is the use of getPersonal()
+ // vs. getEncodedPersonal() for the personal portion. If the personal information contains only
+ // ASCII-7 characters, these are the same.
+ String p = getPersonal();
+ if (p == null) {
+ return formatAddress(address);
+ }
+ else {
+ StringBuffer buf = new StringBuffer(p.length() + 8 + address.length() + 3);
+ buf.append(AddressParser.quoteString(p));
+ buf.append(" <").append(address).append(">");
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Compares two addresses for equality.
+ * We define this as true if the other object is an InternetAddress
+ * and the two values returned by getAddress() are equal in a
+ * case-insensitive comparison.
+ *
+ * @param o the other object
+ * @return true if the addresses are the same
+ */
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof InternetAddress)) return false;
+
+ InternetAddress other = (InternetAddress) o;
+ String myAddress = getAddress();
+ return myAddress == null ? (other.getAddress() == null) : myAddress.equalsIgnoreCase(other.getAddress());
+ }
+
+ /**
+ * Return the hashCode for this address.
+ * We define this to be the hashCode of the address after conversion to lowercase.
+ *
+ * @return a hashCode for this address
+ */
+ public int hashCode() {
+ return (address == null) ? 0 : address.toLowerCase().hashCode();
+ }
+
+ /**
+ * Return true is this address is an RFC822 group address in the format
+ * phrase ":" [#mailbox] ";"
.
+ * We check this by using the presense of a ':' character in the address, and a
+ * ';' as the very last character.
+ *
+ * @return true is this address represents a group
+ */
+ public boolean isGroup() {
+ if (address == null) {
+ return false;
+ }
+
+ return address.endsWith(";") && address.indexOf(":") > 0;
+ }
+
+ /**
+ * Return the members of a group address.
+ *
+ * If strict is true and the address does not contain an initial phrase then an AddressException is thrown.
+ * Otherwise the phrase is skipped and the remainder of the address is checked to see if it is a group.
+ * If it is, the content and strict flag are passed to parseHeader to extract the list of addresses;
+ * if it is not a group then null is returned.
+ *
+ * @param strict whether strict RFC822 checking should be performed
+ * @return an array of InternetAddress objects for the group members, or null if this address is not a group
+ * @throws AddressException if there was a problem parsing the header
+ */
+ public InternetAddress[] getGroup(boolean strict) throws AddressException {
+ if (address == null) {
+ return null;
+ }
+
+ // create an address parser and use it to extract the group information.
+ AddressParser parser = new AddressParser(address, strict ? AddressParser.STRICT : AddressParser.NONSTRICT);
+ return parser.extractGroupList();
+ }
+
+ /**
+ * Return an InternetAddress representing the current user.
+ *
+ * If session is not null, we first look for an address specified in its
+ * "mail.from" property; if this is not set, we look at its "mail.user"
+ * and "mail.host" properties and if both are not null then an address of
+ * the form "${mail.user}@${mail.host}" is created.
+ * If this fails to give an address, then an attempt is made to create
+ * an address by combining the value of the "user.name" System property
+ * with the value returned from InetAddress.getLocalHost().getHostName().
+ * Any SecurityException raised accessing the system property or any
+ * UnknownHostException raised getting the hostname are ignored.
+ *
+ * Finally, an attempt is made to convert the value obtained above to
+ * an InternetAddress. If this fails, then null is returned.
+ *
+ * @param session used to obtain mail properties
+ * @return an InternetAddress for the current user, or null if it cannot be determined
+ */
+ public static InternetAddress getLocalAddress(Session session) {
+ String host = null;
+ String user = null;
+
+ // ok, we have several steps for resolving this. To start with, we could have a from address
+ // configured already, which will be a full InternetAddress string. If we don't have that, then
+ // we need to resolve a user and host to compose an address from.
+ if (session != null) {
+ String address = session.getProperty("mail.from");
+ // if we got this, we can skip out now
+ if (address != null) {
+ try {
+ return new InternetAddress(address);
+ } catch (AddressException e) {
+ // invalid address on the from...treat this as an error and return null.
+ return null;
+ }
+ }
+
+ // now try for user and host information. We have both session and system properties to check here.
+ // we'll just handle the session ones here, and check the system ones below if we're missing information.
+ user = session.getProperty("mail.user");
+ host = session.getProperty("mail.host");
+ }
+
+ try {
+
+ // if either user or host is null, then we check non-session sources for the information.
+ if (user == null) {
+ user = System.getProperty("user.name");
+ }
+
+ if (host == null) {
+ host = "localhost";
+ }
+
+ if (user != null && host != null) {
+ // if we have both a user and host, we can create a local address
+ return new InternetAddress(user + '@' + host);
+ }
+ } catch (AddressException e) {
+ // ignore
+ } catch (SecurityException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ /**
+ * Convert the supplied addresses into a single String of comma-separated text as
+ * produced by {@link InternetAddress#toString() toString()}.
+ * No line-break detection is performed.
+ *
+ * @param addresses the array of addresses to convert
+ * @return a one-line String of comma-separated addresses
+ */
+ public static String toString(Address[] addresses) {
+ if (addresses == null || addresses.length == 0) {
+ return null;
+ }
+ if (addresses.length == 1) {
+ return addresses[0].toString();
+ } else {
+ StringBuffer buf = new StringBuffer(addresses.length * 32);
+ buf.append(addresses[0].toString());
+ for (int i = 1; i < addresses.length; i++) {
+ buf.append(", ");
+ buf.append(addresses[i].toString());
+ }
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Convert the supplies addresses into a String of comma-separated text,
+ * inserting line-breaks between addresses as needed to restrict the line
+ * length to 72 characters. Splits will only be introduced between addresses
+ * so an address longer than 71 characters will still be placed on a single
+ * line.
+ *
+ * @param addresses the array of addresses to convert
+ * @param used the starting column
+ * @return a String of comma-separated addresses with optional line breaks
+ */
+ public static String toString(Address[] addresses, int used) {
+ if (addresses == null || addresses.length == 0) {
+ return null;
+ }
+ if (addresses.length == 1) {
+ String s = addresses[0].toString();
+ if (used + s.length() > 72) {
+ s = "\r\n " + s;
+ }
+ return s;
+ } else {
+ StringBuffer buf = new StringBuffer(addresses.length * 32);
+ for (int i = 0; i < addresses.length; i++) {
+ String s = addresses[i].toString();
+ if (i == 0) {
+ if (used + s.length() + 1 > 72) {
+ buf.append("\r\n ");
+ used = 2;
+ }
+ } else {
+ if (used + s.length() + 1 > 72) {
+ buf.append(",\r\n ");
+ used = 2;
+ } else {
+ buf.append(", ");
+ used += 2;
+ }
+ }
+ buf.append(s);
+ used += s.length();
+ }
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Parse addresses out of the string with basic checking.
+ *
+ * @param addresses the addresses to parse
+ * @return an array of InternetAddresses parsed from the string
+ * @throws AddressException if addresses checking fails
+ */
+ public static InternetAddress[] parse(String addresses) throws AddressException {
+ return parse(addresses, true);
+ }
+
+ /**
+ * Parse addresses out of the string.
+ *
+ * @param addresses the addresses to parse
+ * @param strict if true perform detailed checking, if false just perform basic checking
+ * @return an array of InternetAddresses parsed from the string
+ * @throws AddressException if address checking fails
+ */
+ public static InternetAddress[] parse(String addresses, boolean strict) throws AddressException {
+ return parse(addresses, strict ? AddressParser.STRICT : AddressParser.NONSTRICT);
+ }
+
+ /**
+ * Parse addresses out of the string.
+ *
+ * @param addresses the addresses to parse
+ * @param strict if true perform detailed checking, if false perform little checking
+ * @return an array of InternetAddresses parsed from the string
+ * @throws AddressException if address checking fails
+ */
+ public static InternetAddress[] parseHeader(String addresses, boolean strict) throws AddressException {
+ return parse(addresses, strict ? AddressParser.STRICT : AddressParser.PARSE_HEADER);
+ }
+
+ /**
+ * Parse addresses with increasing degrees of RFC822 compliance checking.
+ *
+ * @param addresses the string to parse
+ * @param level The required strictness level.
+ *
+ * @return an array of InternetAddresses parsed from the string
+ * @throws AddressException
+ * if address checking fails
+ */
+ private static InternetAddress[] parse(String addresses, int level) throws AddressException {
+ // create a parser and have it extract the list using the requested strictness leve.
+ AddressParser parser = new AddressParser(addresses, level);
+ return parser.parseAddressList();
+ }
+
+ /**
+ * Validate the address portion of an internet address to ensure
+ * validity. Throws an AddressException if any validity
+ * problems are encountered.
+ *
+ * @exception AddressException
+ */
+ public void validate() throws AddressException {
+
+ // create a parser using the strictest validation level.
+ AddressParser parser = new AddressParser(formatAddress(address), AddressParser.STRICT);
+ parser.validateAddress();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/InternetHeaders.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/InternetHeaders.java
new file mode 100644
index 00000000..635cf108
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/InternetHeaders.java
@@ -0,0 +1,724 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.mail.Address;
+import javax.mail.Header;
+import javax.mail.MessagingException;
+
+/**
+ * Class that represents the RFC822 headers associated with a message.
+ *
+ * @version $Rev: 702234 $ $Date: 2008-10-06 14:25:41 -0500 (Mon, 06 Oct 2008) $
+ */
+public class InternetHeaders {
+ // the list of headers (to preserve order);
+ protected List headers = new ArrayList();
+
+ private transient String lastHeaderName;
+
+ /**
+ * Create an empty InternetHeaders
+ */
+ public InternetHeaders() {
+ // these are created in the preferred order of the headers.
+ addHeader("Return-Path", null);
+ addHeader("Received", null);
+ addHeader("Resent-Date", null);
+ addHeader("Resent-From", null);
+ addHeader("Resent-Sender", null);
+ addHeader("Resent-To", null);
+ addHeader("Resent-Cc", null);
+ addHeader("Resent-Bcc", null);
+ addHeader("Resent-Message-Id", null);
+ addHeader("Date", null);
+ addHeader("From", null);
+ addHeader("Sender", null);
+ addHeader("Reply-To", null);
+ addHeader("To", null);
+ addHeader("Cc", null);
+ addHeader("Bcc", null);
+ addHeader("Message-Id", null);
+ addHeader("In-Reply-To", null);
+ addHeader("References", null);
+ addHeader("Subject", null);
+ addHeader("Comments", null);
+ addHeader("Keywords", null);
+ addHeader("Errors-To", null);
+ addHeader("MIME-Version", null);
+ addHeader("Content-Type", null);
+ addHeader("Content-Transfer-Encoding", null);
+ addHeader("Content-MD5", null);
+ // the following is a special marker used to identify new header insertion points.
+ addHeader(":", null);
+ addHeader("Content-Length", null);
+ addHeader("Status", null);
+ }
+
+ /**
+ * Create a new InternetHeaders initialized by reading headers from the
+ * stream.
+ *
+ * @param in
+ * the RFC822 input stream to load from
+ * @throws MessagingException
+ * if there is a problem pasring the stream
+ */
+ public InternetHeaders(InputStream in) throws MessagingException {
+ load(in);
+ }
+
+ /**
+ * Read and parse the supplied stream and add all headers to the current
+ * set.
+ *
+ * @param in
+ * the RFC822 input stream to load from
+ * @throws MessagingException
+ * if there is a problem pasring the stream
+ */
+ public void load(InputStream in) throws MessagingException {
+ try {
+ StringBuffer buffer = new StringBuffer(128);
+ String line;
+ // loop until we hit the end or a null line
+ while ((line = readLine(in)) != null) {
+ // lines beginning with white space get special handling
+ if (line.startsWith(" ") || line.startsWith("\t")) {
+ // this gets handled using the logic defined by
+ // the addHeaderLine method. If this line is a continuation, but
+ // there's nothing before it, just call addHeaderLine to add it
+ // to the last header in the headers list
+ if (buffer.length() == 0) {
+ addHeaderLine(line);
+ }
+ else {
+ // preserve the line break and append the continuation
+ buffer.append("\r\n");
+ buffer.append(line);
+ }
+ }
+ else {
+ // if we have a line pending in the buffer, flush it
+ if (buffer.length() > 0) {
+ addHeaderLine(buffer.toString());
+ buffer.setLength(0);
+ }
+ // add this to the accumulator
+ buffer.append(line);
+ }
+ }
+
+ // if we have a line pending in the buffer, flush it
+ if (buffer.length() > 0) {
+ addHeaderLine(buffer.toString());
+ }
+ } catch (IOException e) {
+ throw new MessagingException("Error loading headers", e);
+ }
+ }
+
+
+ /**
+ * Read a single line from the input stream
+ *
+ * @param in The source stream for the line
+ *
+ * @return The string value of the line (without line separators)
+ */
+ private String readLine(InputStream in) throws IOException {
+ StringBuffer buffer = new StringBuffer(128);
+
+ int c;
+
+ while ((c = in.read()) != -1) {
+ // a linefeed is a terminator, always.
+ if (c == '\n') {
+ break;
+ }
+ // just ignore the CR. The next character SHOULD be an NL. If not, we're
+ // just going to discard this
+ else if (c == '\r') {
+ continue;
+ }
+ else {
+ // just add to the buffer
+ buffer.append((char)c);
+ }
+ }
+
+ // no characters found...this was either an eof or a null line.
+ if (buffer.length() == 0) {
+ return null;
+ }
+
+ return buffer.toString();
+ }
+
+
+ /**
+ * Return all the values for the specified header.
+ *
+ * @param name
+ * the header to return
+ * @return the values for that header, or null if the header is not present
+ */
+ public String[] getHeader(String name) {
+ List accumulator = new ArrayList();
+
+ for (int i = 0; i < headers.size(); i++) {
+ InternetHeader header = (InternetHeader)headers.get(i);
+ if (header.getName().equalsIgnoreCase(name) && header.getValue() != null) {
+ accumulator.add(header.getValue());
+ }
+ }
+
+ // this is defined as returning null of nothing is found.
+ if (accumulator.isEmpty()) {
+ return null;
+ }
+
+ // convert this to an array.
+ return (String[])accumulator.toArray(new String[accumulator.size()]);
+ }
+
+ /**
+ * Return the values for the specified header as a single String. If the
+ * header has more than one value then all values are concatenated together
+ * separated by the supplied delimiter.
+ *
+ * @param name
+ * the header to return
+ * @param delimiter
+ * the delimiter used in concatenation
+ * @return the header as a single String
+ */
+ public String getHeader(String name, String delimiter) {
+ // get all of the headers with this name
+ String[] matches = getHeader(name);
+
+ // no match? return a null.
+ if (matches == null) {
+ return null;
+ }
+
+ // a null delimiter means just return the first one. If there's only one item, this is easy too.
+ if (matches.length == 1 || delimiter == null) {
+ return matches[0];
+ }
+
+ // perform the concatenation
+ StringBuffer result = new StringBuffer(matches[0]);
+
+ for (int i = 1; i < matches.length; i++) {
+ result.append(delimiter);
+ result.append(matches[i]);
+ }
+
+ return result.toString();
+ }
+
+
+ /**
+ * Set the value of the header to the supplied value; any existing headers
+ * are removed.
+ *
+ * @param name
+ * the name of the header
+ * @param value
+ * the new value
+ */
+ public void setHeader(String name, String value) {
+ // look for a header match
+ for (int i = 0; i < headers.size(); i++) {
+ InternetHeader header = (InternetHeader)headers.get(i);
+ // found a matching header
+ if (name.equalsIgnoreCase(header.getName())) {
+ // we update both the name and the value for a set so that
+ // the header ends up with the same case as what is getting set
+ header.setValue(value);
+ header.setName(name);
+ // remove all of the headers from this point
+ removeHeaders(name, i + 1);
+ return;
+ }
+ }
+
+ // doesn't exist, so process as an add.
+ addHeader(name, value);
+ }
+
+
+ /**
+ * Remove all headers with the given name, starting with the
+ * specified start position.
+ *
+ * @param name The target header name.
+ * @param pos The position of the first header to examine.
+ */
+ private void removeHeaders(String name, int pos) {
+ // now go remove all other instances of this header
+ for (int i = pos; i < headers.size(); i++) {
+ InternetHeader header = (InternetHeader)headers.get(i);
+ // found a matching header
+ if (name.equalsIgnoreCase(header.getName())) {
+ // remove this item, and back up
+ headers.remove(i);
+ i--;
+ }
+ }
+ }
+
+
+ /**
+ * Find a header in the current list by name, returning the index.
+ *
+ * @param name The target name.
+ *
+ * @return The index of the header in the list. Returns -1 for a not found
+ * condition.
+ */
+ private int findHeader(String name) {
+ return findHeader(name, 0);
+ }
+
+
+ /**
+ * Find a header in the current list, beginning with the specified
+ * start index.
+ *
+ * @param name The target header name.
+ * @param start The search start index.
+ *
+ * @return The index of the first matching header. Returns -1 if the
+ * header is not located.
+ */
+ private int findHeader(String name, int start) {
+ for (int i = start; i < headers.size(); i++) {
+ InternetHeader header = (InternetHeader)headers.get(i);
+ // found a matching header
+ if (name.equalsIgnoreCase(header.getName())) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Add a new value to the header with the supplied name.
+ *
+ * @param name
+ * the name of the header to add a new value for
+ * @param value
+ * another value
+ */
+ public void addHeader(String name, String value) {
+ InternetHeader newHeader = new InternetHeader(name, value);
+
+ // The javamail spec states that "Recieved" headers need to be added in reverse order.
+ // Return-Path is permitted before Received, so handle it the same way.
+ if (name.equalsIgnoreCase("Received") || name.equalsIgnoreCase("Return-Path")) {
+ // see if we have one of these already
+ int pos = findHeader(name);
+
+ // either insert before an existing header, or insert at the very beginning
+ if (pos != -1) {
+ // this could be a placeholder header with a null value. If it is, just update
+ // the value. Otherwise, insert in front of the existing header.
+ InternetHeader oldHeader = (InternetHeader)headers.get(pos);
+ if (oldHeader.getValue() == null) {
+ oldHeader.setValue(value);
+ }
+ else {
+ headers.add(pos, newHeader);
+ }
+ }
+ else {
+ // doesn't exist, so insert at the beginning
+ headers.add(0, newHeader);
+ }
+ }
+ // normal insertion
+ else {
+ // see if we have one of these already
+ int pos = findHeader(name);
+
+ // either insert before an existing header, or insert at the very beginning
+ if (pos != -1) {
+ InternetHeader oldHeader = (InternetHeader)headers.get(pos);
+ // if the existing header is a place holder, we can just update the value
+ if (oldHeader.getValue() == null) {
+ oldHeader.setValue(value);
+ }
+ else {
+ // we have at least one existing header with this name. We need to find the last occurrance,
+ // and insert after that spot.
+
+ int lastPos = findHeader(name, pos + 1);
+
+ while (lastPos != -1) {
+ pos = lastPos;
+ lastPos = findHeader(name, pos + 1);
+ }
+
+ // ok, we have the insertion position
+ headers.add(pos + 1, newHeader);
+ }
+ }
+ else {
+ // find the insertion marker. If that is missing somehow, insert at the end.
+ pos = findHeader(":");
+ if (pos == -1) {
+ pos = headers.size();
+ }
+ headers.add(pos, newHeader);
+ }
+ }
+ }
+
+
+ /**
+ * Remove all header entries with the supplied name
+ *
+ * @param name
+ * the header to remove
+ */
+ public void removeHeader(String name) {
+ // the first occurrance of a header is just zeroed out.
+ int pos = findHeader(name);
+
+ if (pos != -1) {
+ InternetHeader oldHeader = (InternetHeader)headers.get(pos);
+ // keep the header in the list, but with a null value
+ oldHeader.setValue(null);
+ // now remove all other headers with this name
+ removeHeaders(name, pos + 1);
+ }
+ }
+
+
+ /**
+ * Return all headers.
+ *
+ * @return an Enumeration containing all headers
+ */
+ public Enumeration getAllHeaders() {
+ List result = new ArrayList();
+
+ for (int i = 0; i < headers.size(); i++) {
+ InternetHeader header = (InternetHeader)headers.get(i);
+ // we only include headers with real values, no placeholders
+ if (header.getValue() != null) {
+ result.add(header);
+ }
+ }
+ // just return a list enumerator for the header list.
+ return Collections.enumeration(result);
+ }
+
+
+ /**
+ * Test if a given header name is a match for any header in the
+ * given list.
+ *
+ * @param name The name of the current tested header.
+ * @param names The list of names to match against.
+ *
+ * @return True if this is a match for any name in the list, false
+ * for a complete mismatch.
+ */
+ private boolean matchHeader(String name, String[] names) {
+ // the list of names is not required, so treat this as if it
+ // was an empty list and we didn't get a match.
+ if (names == null) {
+ return false;
+ }
+
+ for (int i = 0; i < names.length; i++) {
+ if (name.equalsIgnoreCase(names[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Return all matching Header objects.
+ */
+ public Enumeration getMatchingHeaders(String[] names) {
+ List result = new ArrayList();
+
+ for (int i = 0; i < headers.size(); i++) {
+ InternetHeader header = (InternetHeader)headers.get(i);
+ // we only include headers with real values, no placeholders
+ if (header.getValue() != null) {
+ // only add the matching ones
+ if (matchHeader(header.getName(), names)) {
+ result.add(header);
+ }
+ }
+ }
+ return Collections.enumeration(result);
+ }
+
+
+ /**
+ * Return all non matching Header objects.
+ */
+ public Enumeration getNonMatchingHeaders(String[] names) {
+ List result = new ArrayList();
+
+ for (int i = 0; i < headers.size(); i++) {
+ InternetHeader header = (InternetHeader)headers.get(i);
+ // we only include headers with real values, no placeholders
+ if (header.getValue() != null) {
+ // only add the non-matching ones
+ if (!matchHeader(header.getName(), names)) {
+ result.add(header);
+ }
+ }
+ }
+ return Collections.enumeration(result);
+ }
+
+
+ /**
+ * Add an RFC822 header line to the header store. If the line starts with a
+ * space or tab (a continuation line), add it to the last header line in the
+ * list. Otherwise, append the new header line to the list.
+ *
+ * Note that RFC822 headers can only contain US-ASCII characters
+ *
+ * @param line
+ * raw RFC822 header line
+ */
+ public void addHeaderLine(String line) {
+ // null lines are a nop
+ if (line.length() == 0) {
+ return;
+ }
+
+ // we need to test the first character to see if this is a continuation whitespace
+ char ch = line.charAt(0);
+
+ // tabs and spaces are special. This is a continuation of the last header in the list.
+ if (ch == ' ' || ch == '\t') {
+ int size = headers.size();
+ // it's possible that we have a leading blank line.
+ if (size > 0) {
+ InternetHeader header = (InternetHeader)headers.get(size - 1);
+ header.appendValue(line);
+ }
+ }
+ else {
+ // this just gets appended to the end, preserving the addition order.
+ headers.add(new InternetHeader(line));
+ }
+ }
+
+
+ /**
+ * Return all the header lines as an Enumeration of Strings.
+ */
+ public Enumeration getAllHeaderLines() {
+ return new HeaderLineEnumeration(getAllHeaders());
+ }
+
+ /**
+ * Return all matching header lines as an Enumeration of Strings.
+ */
+ public Enumeration getMatchingHeaderLines(String[] names) {
+ return new HeaderLineEnumeration(getMatchingHeaders(names));
+ }
+
+ /**
+ * Return all non-matching header lines.
+ */
+ public Enumeration getNonMatchingHeaderLines(String[] names) {
+ return new HeaderLineEnumeration(getNonMatchingHeaders(names));
+ }
+
+
+ /**
+ * Set an internet header from a list of addresses. The
+ * first address item is set, followed by a series of addHeaders().
+ *
+ * @param name The name to set.
+ * @param addresses The list of addresses to set.
+ */
+ void setHeader(String name, Address[] addresses) {
+ // if this is empty, then we need to replace this
+ if (addresses.length == 0) {
+ removeHeader(name);
+ } else {
+
+ // replace the first header
+ setHeader(name, addresses[0].toString());
+
+ // now add the rest as extra headers.
+ for (int i = 1; i < addresses.length; i++) {
+ Address address = addresses[i];
+ addHeader(name, address.toString());
+ }
+ }
+ }
+
+
+ /**
+ * Write out the set of headers, except for any
+ * headers specified in the optional ignore list.
+ *
+ * @param out The output stream.
+ * @param ignore The optional ignore list.
+ *
+ * @exception IOException
+ */
+ void writeTo(OutputStream out, String[] ignore) throws IOException {
+ if (ignore == null) {
+ // write out all header lines with non-null values
+ for (int i = 0; i < headers.size(); i++) {
+ InternetHeader header = (InternetHeader)headers.get(i);
+ // we only include headers with real values, no placeholders
+ if (header.getValue() != null) {
+ header.writeTo(out);
+ }
+ }
+ }
+ else {
+ // write out all matching header lines with non-null values
+ for (int i = 0; i < headers.size(); i++) {
+ InternetHeader header = (InternetHeader)headers.get(i);
+ // we only include headers with real values, no placeholders
+ if (header.getValue() != null) {
+ if (!matchHeader(header.getName(), ignore)) {
+ header.writeTo(out);
+ }
+ }
+ }
+ }
+ }
+
+ protected static final class InternetHeader extends Header {
+
+ public InternetHeader(String h) {
+ // initialize with null values, which we'll update once we parse the string
+ super("", "");
+ int separator = h.indexOf(':');
+ // no separator, then we take this as a name with a null string value.
+ if (separator == -1) {
+ name = h.trim();
+ }
+ else {
+ name = h.substring(0, separator);
+ // step past the separator. Now we need to remove any leading white space characters.
+ separator++;
+
+ while (separator < h.length()) {
+ char ch = h.charAt(separator);
+ if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
+ break;
+ }
+ separator++;
+ }
+
+ value = h.substring(separator);
+ }
+ }
+
+ public InternetHeader(String name, String value) {
+ super(name, value);
+ }
+
+
+ /**
+ * Package scope method for setting the header value.
+ *
+ * @param value The new header value.
+ */
+ void setValue(String value) {
+ this.value = value;
+ }
+
+
+ /**
+ * Package scope method for setting the name value.
+ *
+ * @param name The new header name
+ */
+ void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Package scope method for extending a header value.
+ *
+ * @param value The appended header value.
+ */
+ void appendValue(String value) {
+ if (this.value == null) {
+ this.value = value;
+ }
+ else {
+ this.value = this.value + "\r\n" + value;
+ }
+ }
+
+ void writeTo(OutputStream out) throws IOException {
+ out.write(name.getBytes());
+ out.write(':');
+ out.write(' ');
+ out.write(value.getBytes());
+ out.write('\r');
+ out.write('\n');
+ }
+ }
+
+ private static class HeaderLineEnumeration implements Enumeration {
+ private Enumeration headers;
+
+ public HeaderLineEnumeration(Enumeration headers) {
+ this.headers = headers;
+ }
+
+ public boolean hasMoreElements() {
+ return headers.hasMoreElements();
+ }
+
+ public Object nextElement() {
+ Header h = (Header) headers.nextElement();
+ return h.getName() + ": " + h.getValue();
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/MailDateFormat.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/MailDateFormat.java
new file mode 100644
index 00000000..9e081d99
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/MailDateFormat.java
@@ -0,0 +1,611 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Formats ths date as specified by
+ * draft-ietf-drums-msg-fmt-08 dated January 26, 2000
+ * which supercedes RFC822.
+ *
+ *
+ * The format used is EEE, d MMM yyyy HH:mm:ss Z
and
+ * locale is always US-ASCII.
+ *
+ * @version $Rev: 628009 $ $Date: 2008-02-15 04:53:02 -0600 (Fri, 15 Feb 2008) $
+ */
+public class MailDateFormat extends SimpleDateFormat {
+ public MailDateFormat() {
+ super("EEE, d MMM yyyy HH:mm:ss Z (z)", Locale.US);
+ }
+
+ public StringBuffer format(Date date, StringBuffer buffer, FieldPosition position) {
+ return super.format(date, buffer, position);
+ }
+
+ /**
+ * Parse a Mail date into a Date object. This uses fairly
+ * lenient rules for the format because the Mail standards
+ * for dates accept multiple formats.
+ *
+ * @param string The input string.
+ * @param position The position argument.
+ *
+ * @return The Date object with the information inside.
+ */
+ public Date parse(String string, ParsePosition position) {
+ MailDateParser parser = new MailDateParser(string, position);
+ try {
+ return parser.parse(isLenient());
+ } catch (ParseException e) {
+ e.printStackTrace();
+ // just return a null for any parsing errors
+ return null;
+ }
+ }
+
+ /**
+ * The calendar cannot be set
+ * @param calendar
+ * @throws UnsupportedOperationException
+ */
+ public void setCalendar(Calendar calendar) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * The format cannot be set
+ * @param format
+ * @throws UnsupportedOperationException
+ */
+ public void setNumberFormat(NumberFormat format) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ // utility class for handling date parsing issues
+ class MailDateParser {
+ // our list of defined whitespace characters
+ static final String whitespace = " \t\r\n";
+
+ // current parsing position
+ int current;
+ // our end parsing position
+ int endOffset;
+ // the date source string
+ String source;
+ // The parsing position. We update this as we move along and
+ // also for any parsing errors
+ ParsePosition pos;
+
+ public MailDateParser(String source, ParsePosition pos)
+ {
+ this.source = source;
+ this.pos = pos;
+ // we start using the providing parsing index.
+ this.current = pos.getIndex();
+ this.endOffset = source.length();
+ }
+
+ /**
+ * Parse the timestamp, returning a date object.
+ *
+ * @param lenient The lenient setting from the Formatter object.
+ *
+ * @return A Date object based off of parsing the date string.
+ * @exception ParseException
+ */
+ public Date parse(boolean lenient) throws ParseException {
+ // we just skip over any next date format, which means scanning ahead until we
+ // find the first numeric character
+ locateNumeric();
+ // the day can be either 1 or two digits
+ int day = parseNumber(1, 2);
+ // step over the delimiter
+ skipDateDelimiter();
+ // parse off the month (which is in character format)
+ int month = parseMonth();
+ // step over the delimiter
+ skipDateDelimiter();
+ // now pull of the year, which can be either 2-digit or 4-digit
+ int year = parseYear();
+ // white space is required here
+ skipRequiredWhiteSpace();
+ // accept a 1 or 2 digit hour
+ int hour = parseNumber(1, 2);
+ skipRequiredChar(':');
+ // the minutes must be two digit
+ int minutes = parseNumber(2, 2);
+
+ // the seconds are optional, but the ":" tells us if they are to
+ // be expected.
+ int seconds = 0;
+ if (skipOptionalChar(':')) {
+ seconds = parseNumber(2, 2);
+ }
+ // skip over the white space
+ skipWhiteSpace();
+ // and finally the timezone information
+ int offset = parseTimeZone();
+
+ // set the index of how far we've parsed this
+ pos.setIndex(current);
+
+ // create a calendar for creating the date
+ Calendar greg = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
+ // we inherit the leniency rules
+ greg.setLenient(lenient);
+ greg.set(year, month, day, hour, minutes, seconds);
+ // now adjust by the offset. This seems a little strange, but we
+ // need to negate the offset because this is a UTC calendar, so we need to
+ // apply the reverse adjustment. for example, for the EST timezone, the offset
+ // value will be -300 (5 hours). If the time was 15:00:00, the UTC adjusted time
+ // needs to be 20:00:00, so we subract -300 minutes.
+ greg.add(Calendar.MINUTE, -offset);
+ // now return this timestamp.
+ return greg.getTime();
+ }
+
+
+ /**
+ * Skip over a position where there's a required value
+ * expected.
+ *
+ * @param ch The required character.
+ *
+ * @exception ParseException
+ */
+ private void skipRequiredChar(char ch) throws ParseException {
+ if (current >= endOffset) {
+ parseError("Delimiter '" + ch + "' expected");
+ }
+ if (source.charAt(current) != ch) {
+ parseError("Delimiter '" + ch + "' expected");
+ }
+ current++;
+ }
+
+
+ /**
+ * Skip over a position where iff the position matches the
+ * character
+ *
+ * @param ch The required character.
+ *
+ * @return true if the character was there, false otherwise.
+ * @exception ParseException
+ */
+ private boolean skipOptionalChar(char ch) {
+ if (current >= endOffset) {
+ return false;
+ }
+ if (source.charAt(current) != ch) {
+ return false;
+ }
+ current++;
+ return true;
+ }
+
+
+ /**
+ * Skip over any white space characters until we find
+ * the next real bit of information. Will scan completely to the
+ * end, if necessary.
+ */
+ private void skipWhiteSpace() {
+ while (current < endOffset) {
+ // if this is not in the white space list, then success.
+ if (whitespace.indexOf(source.charAt(current)) < 0) {
+ return;
+ }
+ current++;
+ }
+
+ // everything used up, just return
+ }
+
+
+ /**
+ * Skip over any non-white space characters until we find
+ * either a whitespace char or the end of the data.
+ */
+ private void skipNonWhiteSpace() {
+ while (current < endOffset) {
+ // if this is not in the white space list, then success.
+ if (whitespace.indexOf(source.charAt(current)) >= 0) {
+ return;
+ }
+ current++;
+ }
+
+ // everything used up, just return
+ }
+
+
+ /**
+ * Skip over any white space characters until we find
+ * the next real bit of information. Will scan completely to the
+ * end, if necessary.
+ */
+ private void skipRequiredWhiteSpace() throws ParseException {
+ int start = current;
+
+ while (current < endOffset) {
+ // if this is not in the white space list, then success.
+ if (whitespace.indexOf(source.charAt(current)) < 0) {
+ // we must have at least one white space character
+ if (start == current) {
+ parseError("White space character expected");
+ }
+ return;
+ }
+ current++;
+ }
+ // everything used up, just return, but make sure we had at least one
+ // white space
+ if (start == current) {
+ parseError("White space character expected");
+ }
+ }
+
+ private void parseError(String message) throws ParseException {
+ // we've got an error, set the index to the end.
+ pos.setErrorIndex(current);
+ throw new ParseException(message, current);
+ }
+
+
+ /**
+ * Locate an expected numeric field.
+ *
+ * @exception ParseException
+ */
+ private void locateNumeric() throws ParseException {
+ while (current < endOffset) {
+ // found a digit? we're done
+ if (Character.isDigit(source.charAt(current))) {
+ return;
+ }
+ current++;
+ }
+ // we've got an error, set the index to the end.
+ parseError("Number field expected");
+ }
+
+
+ /**
+ * Parse out an expected numeric field.
+ *
+ * @param minDigits The minimum number of digits we expect in this filed.
+ * @param maxDigits The maximum number of digits expected. Parsing will
+ * stop at the first non-digit character. An exception will
+ * be thrown if the field contained more than maxDigits
+ * in it.
+ *
+ * @return The parsed numeric value.
+ * @exception ParseException
+ */
+ private int parseNumber(int minDigits, int maxDigits) throws ParseException {
+ int start = current;
+ int accumulator = 0;
+ while (current < endOffset) {
+ char ch = source.charAt(current);
+ // if this is not a digit character, then quit
+ if (!Character.isDigit(ch)) {
+ break;
+ }
+ // add the digit value into the accumulator
+ accumulator = accumulator * 10 + Character.digit(ch, 10);
+ current++;
+ }
+
+ int fieldLength = current - start;
+ if (fieldLength < minDigits || fieldLength > maxDigits) {
+ parseError("Invalid number field");
+ }
+
+ return accumulator;
+ }
+
+ /**
+ * Skip a delimiter between the date portions of the
+ * string. The IMAP internal date format uses "-", so
+ * we either accept a single "-" or any number of white
+ * space characters (at least one required).
+ *
+ * @exception ParseException
+ */
+ private void skipDateDelimiter() throws ParseException {
+ if (current >= endOffset) {
+ parseError("Invalid date field delimiter");
+ }
+
+ if (source.charAt(current) == '-') {
+ current++;
+ }
+ else {
+ // must be at least a single whitespace character
+ skipRequiredWhiteSpace();
+ }
+ }
+
+
+ /**
+ * Parse a character month name into the date month
+ * offset.
+ *
+ * @return
+ * @exception ParseException
+ */
+ private int parseMonth() throws ParseException {
+ if ((endOffset - current) < 3) {
+ parseError("Invalid month");
+ }
+
+ int monthOffset = 0;
+ String month = source.substring(current, current + 3).toLowerCase();
+
+ if (month.equals("jan")) {
+ monthOffset = 0;
+ }
+ else if (month.equals("feb")) {
+ monthOffset = 1;
+ }
+ else if (month.equals("mar")) {
+ monthOffset = 2;
+ }
+ else if (month.equals("apr")) {
+ monthOffset = 3;
+ }
+ else if (month.equals("may")) {
+ monthOffset = 4;
+ }
+ else if (month.equals("jun")) {
+ monthOffset = 5;
+ }
+ else if (month.equals("jul")) {
+ monthOffset = 6;
+ }
+ else if (month.equals("aug")) {
+ monthOffset = 7;
+ }
+ else if (month.equals("sep")) {
+ monthOffset = 8;
+ }
+ else if (month.equals("oct")) {
+ monthOffset = 9;
+ }
+ else if (month.equals("nov")) {
+ monthOffset = 10;
+ }
+ else if (month.equals("dec")) {
+ monthOffset = 11;
+ }
+ else {
+ parseError("Invalid month");
+ }
+
+ // ok, this is valid. Update the position and return it
+ current += 3;
+ return monthOffset;
+ }
+
+ /**
+ * Parse off a year field that might be expressed as
+ * either 2 or 4 digits.
+ *
+ * @return The numeric value of the year.
+ * @exception ParseException
+ */
+ private int parseYear() throws ParseException {
+ // the year is between 2 to 4 digits
+ int year = parseNumber(2, 4);
+
+ // the two digit years get some sort of adjustment attempted.
+ if (year < 50) {
+ year += 2000;
+ }
+ else if (year < 100) {
+ year += 1990;
+ }
+ return year;
+ }
+
+
+ /**
+ * Parse all of the different timezone options.
+ *
+ * @return The timezone offset.
+ * @exception ParseException
+ */
+ private int parseTimeZone() throws ParseException {
+ if (current >= endOffset) {
+ parseError("Missing time zone");
+ }
+
+ // get the first non-blank. If this is a sign character, this
+ // is a zone offset.
+ char sign = source.charAt(current);
+
+ if (sign == '-' || sign == '+') {
+ // need to step over the sign character
+ current++;
+ // a numeric timezone is always a 4 digit number, but
+ // expressed as minutes/seconds. I'm too lazy to write a
+ // different parser that will bound on just a couple of characters, so
+ // we'll grab this as a single value and adjust
+ int zoneInfo = parseNumber(4, 4);
+
+ int offset = (zoneInfo / 100) * 60 + (zoneInfo % 100);
+ // negate this, if we have a negativeo offset
+ if (sign == '-') {
+ offset = -offset;
+ }
+ return offset;
+ }
+ else {
+ // need to parse this out using the obsolete zone names. This will be
+ // either a 3-character code (defined set), or a single character military
+ // zone designation.
+ int start = current;
+ skipNonWhiteSpace();
+ String name = source.substring(start, current).toUpperCase();
+
+ if (name.length() == 1) {
+ return militaryZoneOffset(name);
+ }
+ else if (name.length() <= 3) {
+ return namedZoneOffset(name);
+ }
+ else {
+ parseError("Invalid time zone");
+ }
+ return 0;
+ }
+ }
+
+
+ /**
+ * Parse the obsolete mail timezone specifiers. The
+ * allowed set of timezones are terribly US centric.
+ * That's the spec. The preferred timezone form is
+ * the +/-mmss form.
+ *
+ * @param name The input name.
+ *
+ * @return The standard timezone offset for the specifier.
+ * @exception ParseException
+ */
+ private int namedZoneOffset(String name) throws ParseException {
+
+ // NOTE: This is "UT", NOT "UTC"
+ if (name.equals("UT")) {
+ return 0;
+ }
+ else if (name.equals("GMT")) {
+ return 0;
+ }
+ else if (name.equals("EST")) {
+ return -300;
+ }
+ else if (name.equals("EDT")) {
+ return -240;
+ }
+ else if (name.equals("CST")) {
+ return -360;
+ }
+ else if (name.equals("CDT")) {
+ return -300;
+ }
+ else if (name.equals("MST")) {
+ return -420;
+ }
+ else if (name.equals("MDT")) {
+ return -360;
+ }
+ else if (name.equals("PST")) {
+ return -480;
+ }
+ else if (name.equals("PDT")) {
+ return -420;
+ }
+ else {
+ parseError("Invalid time zone");
+ return 0;
+ }
+ }
+
+
+ /**
+ * Parse a single-character military timezone.
+ *
+ * @param name The one-character name.
+ *
+ * @return The offset corresponding to the military designation.
+ */
+ private int militaryZoneOffset(String name) throws ParseException {
+ switch (Character.toUpperCase(name.charAt(0))) {
+ case 'A':
+ return 60;
+ case 'B':
+ return 120;
+ case 'C':
+ return 180;
+ case 'D':
+ return 240;
+ case 'E':
+ return 300;
+ case 'F':
+ return 360;
+ case 'G':
+ return 420;
+ case 'H':
+ return 480;
+ case 'I':
+ return 540;
+ case 'K':
+ return 600;
+ case 'L':
+ return 660;
+ case 'M':
+ return 720;
+ case 'N':
+ return -60;
+ case 'O':
+ return -120;
+ case 'P':
+ return -180;
+ case 'Q':
+ return -240;
+ case 'R':
+ return -300;
+ case 'S':
+ return -360;
+ case 'T':
+ return -420;
+ case 'U':
+ return -480;
+ case 'V':
+ return -540;
+ case 'W':
+ return -600;
+ case 'X':
+ return -660;
+ case 'Y':
+ return -720;
+ case 'Z':
+ return 0;
+ default:
+ parseError("Invalid time zone");
+ return 0;
+ }
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeBodyPart.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeBodyPart.java
new file mode 100644
index 00000000..f32e37a0
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeBodyPart.java
@@ -0,0 +1,685 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Enumeration;
+
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Part;
+import javax.mail.internet.HeaderTokenizer.Token;
+import javax.swing.text.AbstractDocument.Content;
+
+import org.apache.geronimo.mail.util.ASCIIUtil;
+import org.apache.geronimo.mail.util.SessionUtil;
+
+/**
+ * @version $Rev: 689126 $ $Date: 2008-08-26 11:17:53 -0500 (Tue, 26 Aug 2008) $
+ */
+public class MimeBodyPart extends BodyPart implements MimePart {
+ // constants for accessed properties
+ private static final String MIME_DECODEFILENAME = "mail.mime.decodefilename";
+ private static final String MIME_ENCODEFILENAME = "mail.mime.encodefilename";
+ private static final String MIME_SETDEFAULTTEXTCHARSET = "mail.mime.setdefaulttextcharset";
+ private static final String MIME_SETCONTENTTYPEFILENAME = "mail.mime.setcontenttypefilename";
+
+
+ /**
+ * The {@link DataHandler} for this Message's content.
+ */
+ protected DataHandler dh;
+ /**
+ * This message's content (unless sourced from a SharedInputStream).
+ */
+ protected byte content[];
+ /**
+ * If the data for this message was supplied by a {@link SharedInputStream}
+ * then this is another such stream representing the content of this message;
+ * if this field is non-null, then {@link #content} will be null.
+ */
+ protected InputStream contentStream;
+ /**
+ * This message's headers.
+ */
+ protected InternetHeaders headers;
+
+ public MimeBodyPart() {
+ headers = new InternetHeaders();
+ }
+
+ public MimeBodyPart(InputStream in) throws MessagingException {
+ headers = new InternetHeaders(in);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int count;
+ try {
+ while((count = in.read(buffer, 0, 1024)) > 0) {
+ baos.write(buffer, 0, count);
+ }
+ } catch (IOException e) {
+ throw new MessagingException(e.toString(),e);
+ }
+ content = baos.toByteArray();
+ }
+
+ public MimeBodyPart(InternetHeaders headers, byte[] content) throws MessagingException {
+ this.headers = headers;
+ this.content = content;
+ }
+
+ /**
+ * Return the content size of this message. This is obtained
+ * either from the size of the content field (if available) or
+ * from the contentStream, IFF the contentStream returns a positive
+ * size. Returns -1 if the size is not available.
+ *
+ * @return Size of the content in bytes.
+ * @exception MessagingException
+ */
+ public int getSize() throws MessagingException {
+ if (content != null) {
+ return content.length;
+ }
+ if (contentStream != null) {
+ try {
+ int size = contentStream.available();
+ if (size > 0) {
+ return size;
+ }
+ } catch (IOException e) {
+ }
+ }
+ return -1;
+ }
+
+ public int getLineCount() throws MessagingException {
+ return -1;
+ }
+
+ public String getContentType() throws MessagingException {
+ String value = getSingleHeader("Content-Type");
+ if (value == null) {
+ value = "text/plain";
+ }
+ return value;
+ }
+
+ /**
+ * Tests to see if this message has a mime-type match with the
+ * given type name.
+ *
+ * @param type The tested type name.
+ *
+ * @return If this is a type match on the primary and secondare portion of the types.
+ * @exception MessagingException
+ */
+ public boolean isMimeType(String type) throws MessagingException {
+ return new ContentType(getContentType()).match(type);
+ }
+
+ /**
+ * Retrieve the message "Content-Disposition" header field.
+ * This value represents how the part should be represented to
+ * the user.
+ *
+ * @return The string value of the Content-Disposition field.
+ * @exception MessagingException
+ */
+ public String getDisposition() throws MessagingException {
+ String disp = getSingleHeader("Content-Disposition");
+ if (disp != null) {
+ return new ContentDisposition(disp).getDisposition();
+ }
+ return null;
+ }
+
+ /**
+ * Set a new dispostion value for the "Content-Disposition" field.
+ * If the new value is null, the header is removed.
+ *
+ * @param disposition
+ * The new disposition value.
+ *
+ * @exception MessagingException
+ */
+ public void setDisposition(String disposition) throws MessagingException {
+ if (disposition == null) {
+ removeHeader("Content-Disposition");
+ }
+ else {
+ // the disposition has parameters, which we'll attempt to preserve in any existing header.
+ String currentHeader = getSingleHeader("Content-Disposition");
+ if (currentHeader != null) {
+ ContentDisposition content = new ContentDisposition(currentHeader);
+ content.setDisposition(disposition);
+ setHeader("Content-Disposition", content.toString());
+ }
+ else {
+ // set using the raw string.
+ setHeader("Content-Disposition", disposition);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the current value of the "Content-Transfer-Encoding"
+ * header. Returns null if the header does not exist.
+ *
+ * @return The current header value or null.
+ * @exception MessagingException
+ */
+ public String getEncoding() throws MessagingException {
+ // this might require some parsing to sort out.
+ String encoding = getSingleHeader("Content-Transfer-Encoding");
+ if (encoding != null) {
+ // we need to parse this into ATOMs and other constituent parts. We want the first
+ // ATOM token on the string.
+ HeaderTokenizer tokenizer = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
+
+ Token token = tokenizer.next();
+ while (token.getType() != Token.EOF) {
+ // if this is an ATOM type, return it.
+ if (token.getType() == Token.ATOM) {
+ return token.getValue();
+ }
+ }
+ // not ATOMs found, just return the entire header value....somebody might be able to make sense of
+ // this.
+ return encoding;
+ }
+ // no header, nothing to return.
+ return null;
+ }
+
+
+ /**
+ * Retrieve the value of the "Content-ID" header. Returns null
+ * if the header does not exist.
+ *
+ * @return The current header value or null.
+ * @exception MessagingException
+ */
+ public String getContentID() throws MessagingException {
+ return getSingleHeader("Content-ID");
+ }
+
+ public void setContentID(String cid) throws MessagingException {
+ setOrRemoveHeader("Content-ID", cid);
+ }
+
+ public String getContentMD5() throws MessagingException {
+ return getSingleHeader("Content-MD5");
+ }
+
+ public void setContentMD5(String md5) throws MessagingException {
+ setHeader("Content-MD5", md5);
+ }
+
+ public String[] getContentLanguage() throws MessagingException {
+ return getHeader("Content-Language");
+ }
+
+ public void setContentLanguage(String[] languages) throws MessagingException {
+ if (languages == null) {
+ removeHeader("Content-Language");
+ } else if (languages.length == 1) {
+ setHeader("Content-Language", languages[0]);
+ } else {
+ StringBuffer buf = new StringBuffer(languages.length * 20);
+ buf.append(languages[0]);
+ for (int i = 1; i < languages.length; i++) {
+ buf.append(',').append(languages[i]);
+ }
+ setHeader("Content-Language", buf.toString());
+ }
+ }
+
+ public String getDescription() throws MessagingException {
+ String description = getSingleHeader("Content-Description");
+ if (description != null) {
+ try {
+ // this could be both folded and encoded. Return this to usable form.
+ return MimeUtility.decodeText(MimeUtility.unfold(description));
+ } catch (UnsupportedEncodingException e) {
+ // ignore
+ }
+ }
+ // return the raw version for any errors.
+ return description;
+ }
+
+ public void setDescription(String description) throws MessagingException {
+ setDescription(description, null);
+ }
+
+ public void setDescription(String description, String charset) throws MessagingException {
+ if (description == null) {
+ removeHeader("Content-Description");
+ }
+ else {
+ try {
+ setHeader("Content-Description", MimeUtility.fold(21, MimeUtility.encodeText(description, charset, null)));
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException(e.getMessage(), e);
+ }
+ }
+ }
+
+ public String getFileName() throws MessagingException {
+ // see if there is a disposition. If there is, parse off the filename parameter.
+ String disposition = getSingleHeader("Content-Disposition");
+ String filename = null;
+
+ if (disposition != null) {
+ filename = new ContentDisposition(disposition).getParameter("filename");
+ }
+
+ // if there's no filename on the disposition, there might be a name parameter on a
+ // Content-Type header.
+ if (filename == null) {
+ String type = getSingleHeader("Content-Type");
+ if (type != null) {
+ try {
+ filename = new ContentType(type).getParameter("name");
+ } catch (ParseException e) {
+ }
+ }
+ }
+ // if we have a name, we might need to decode this if an additional property is set.
+ if (filename != null && SessionUtil.getBooleanProperty(MIME_DECODEFILENAME, false)) {
+ try {
+ filename = MimeUtility.decodeText(filename);
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException("Unable to decode filename", e);
+ }
+ }
+
+ return filename;
+ }
+
+
+ public void setFileName(String name) throws MessagingException {
+ // there's an optional session property that requests file name encoding...we need to process this before
+ // setting the value.
+ if (name != null && SessionUtil.getBooleanProperty(MIME_ENCODEFILENAME, false)) {
+ try {
+ name = MimeUtility.encodeText(name);
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException("Unable to encode filename", e);
+ }
+ }
+
+ // get the disposition string.
+ String disposition = getDisposition();
+ // if not there, then this is an attachment.
+ if (disposition == null) {
+ disposition = Part.ATTACHMENT;
+ }
+
+ // now create a disposition object and set the parameter.
+ ContentDisposition contentDisposition = new ContentDisposition(disposition);
+ contentDisposition.setParameter("filename", name);
+
+ // serialize this back out and reset.
+ setHeader("Content-Disposition", contentDisposition.toString());
+
+ // The Sun implementation appears to update the Content-type name parameter too, based on
+ // another system property
+ if (SessionUtil.getBooleanProperty(MIME_SETCONTENTTYPEFILENAME, true)) {
+ ContentType type = new ContentType(getContentType());
+ type.setParameter("name", name);
+ setHeader("Content-Type", type.toString());
+ }
+ }
+
+ public InputStream getInputStream() throws MessagingException, IOException {
+ return getDataHandler().getInputStream();
+ }
+
+ protected InputStream getContentStream() throws MessagingException {
+ if (contentStream != null) {
+ return contentStream;
+ }
+
+ if (content != null) {
+ return new ByteArrayInputStream(content);
+ } else {
+ throw new MessagingException("No content");
+ }
+ }
+
+ public InputStream getRawInputStream() throws MessagingException {
+ return getContentStream();
+ }
+
+ public synchronized DataHandler getDataHandler() throws MessagingException {
+ if (dh == null) {
+ dh = new DataHandler(new MimePartDataSource(this));
+ }
+ return dh;
+ }
+
+ public Object getContent() throws MessagingException, IOException {
+ return getDataHandler().getContent();
+ }
+
+ public void setDataHandler(DataHandler handler) throws MessagingException {
+ dh = handler;
+ // if we have a handler override, then we need to invalidate any content
+ // headers that define the types. This information will be derived from the
+ // data heander unless subsequently overridden.
+ removeHeader("Content-Type");
+ removeHeader("Content-Transfer-Encoding");
+
+ }
+
+ public void setContent(Object content, String type) throws MessagingException {
+ // Multipart content needs to be handled separately.
+ if (content instanceof Multipart) {
+ setContent((Multipart)content);
+ }
+ else {
+ setDataHandler(new DataHandler(content, type));
+ }
+
+ }
+
+ public void setText(String text) throws MessagingException {
+ setText(text, null);
+ }
+
+ public void setText(String text, String charset) throws MessagingException {
+ // the default subtype is plain text.
+ setText(text, charset, "plain");
+ }
+
+
+ public void setText(String text, String charset, String subtype) throws MessagingException {
+ // we need to sort out the character set if one is not provided.
+ if (charset == null) {
+ // if we have non us-ascii characters here, we need to adjust this.
+ if (!ASCIIUtil.isAscii(text)) {
+ charset = MimeUtility.getDefaultMIMECharset();
+ }
+ else {
+ charset = "us-ascii";
+ }
+ }
+ setContent(text, "text/plain; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME));
+ }
+
+ public void setContent(Multipart part) throws MessagingException {
+ setDataHandler(new DataHandler(part, part.getContentType()));
+ part.setParent(this);
+ }
+
+ public void writeTo(OutputStream out) throws IOException, MessagingException {
+ headers.writeTo(out, null);
+ // add the separater between the headers and the data portion.
+ out.write('\r');
+ out.write('\n');
+ // we need to process this using the transfer encoding type
+ OutputStream encodingStream = MimeUtility.encode(out, getEncoding());
+ getDataHandler().writeTo(encodingStream);
+ encodingStream.flush();
+ }
+
+ public String[] getHeader(String name) throws MessagingException {
+ return headers.getHeader(name);
+ }
+
+ public String getHeader(String name, String delimiter) throws MessagingException {
+ return headers.getHeader(name, delimiter);
+ }
+
+ public void setHeader(String name, String value) throws MessagingException {
+ headers.setHeader(name, value);
+ }
+
+ /**
+ * Conditionally set or remove a named header. If the new value
+ * is null, the header is removed.
+ *
+ * @param name The header name.
+ * @param value The new header value. A null value causes the header to be
+ * removed.
+ *
+ * @exception MessagingException
+ */
+ private void setOrRemoveHeader(String name, String value) throws MessagingException {
+ if (value == null) {
+ headers.removeHeader(name);
+ }
+ else {
+ headers.setHeader(name, value);
+ }
+ }
+
+ public void addHeader(String name, String value) throws MessagingException {
+ headers.addHeader(name, value);
+ }
+
+ public void removeHeader(String name) throws MessagingException {
+ headers.removeHeader(name);
+ }
+
+ public Enumeration getAllHeaders() throws MessagingException {
+ return headers.getAllHeaders();
+ }
+
+ public Enumeration getMatchingHeaders(String[] name) throws MessagingException {
+ return headers.getMatchingHeaders(name);
+ }
+
+ public Enumeration getNonMatchingHeaders(String[] name) throws MessagingException {
+ return headers.getNonMatchingHeaders(name);
+ }
+
+ public void addHeaderLine(String line) throws MessagingException {
+ headers.addHeaderLine(line);
+ }
+
+ public Enumeration getAllHeaderLines() throws MessagingException {
+ return headers.getAllHeaderLines();
+ }
+
+ public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
+ return headers.getMatchingHeaderLines(names);
+ }
+
+ public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
+ return headers.getNonMatchingHeaderLines(names);
+ }
+
+ protected void updateHeaders() throws MessagingException {
+ DataHandler handler = getDataHandler();
+
+ try {
+ // figure out the content type. If not set, we'll need to figure this out.
+ String type = dh.getContentType();
+ // parse this content type out so we can do matches/compares.
+ ContentType content = new ContentType(type);
+
+ // we might need to reconcile the content type and our explicitly set type
+ String explicitType = getSingleHeader("Content-Type");
+ // is this a multipart content?
+ if (content.match("multipart/*")) {
+ // the content is suppose to be a MimeMultipart. Ping it to update it's headers as well.
+ try {
+ MimeMultipart part = (MimeMultipart)handler.getContent();
+ part.updateHeaders();
+ } catch (ClassCastException e) {
+ throw new MessagingException("Message content is not MimeMultipart", e);
+ }
+ }
+ else if (!content.match("message/rfc822")) {
+ // simple part, we need to update the header type information
+ // if no encoding is set yet, figure this out from the data handler.
+ if (getSingleHeader("Content-Transfer-Encoding") == null) {
+ setHeader("Content-Transfer-Encoding", MimeUtility.getEncoding(handler));
+ }
+
+ // is a content type header set? Check the property to see if we need to set this.
+ if (explicitType == null) {
+ if (SessionUtil.getBooleanProperty(MIME_SETDEFAULTTEXTCHARSET, true)) {
+ // is this a text type? Figure out the encoding and make sure it is set.
+ if (content.match("text/*")) {
+ // the charset should be specified as a parameter on the MIME type. If not there,
+ // try to figure one out.
+ if (content.getParameter("charset") == null) {
+
+ String encoding = getEncoding();
+ // if we're sending this as 7-bit ASCII, our character set need to be
+ // compatible.
+ if (encoding != null && encoding.equalsIgnoreCase("7bit")) {
+ content.setParameter("charset", "us-ascii");
+ }
+ else {
+ // get the global default.
+ content.setParameter("charset", MimeUtility.getDefaultMIMECharset());
+ }
+ // replace the datasource provided type
+ type = content.toString();
+ }
+ }
+ }
+ }
+ }
+
+ // if we don't have a content type header, then create one.
+ if (explicitType == null) {
+ // get the disposition header, and if it is there, copy the filename parameter into the
+ // name parameter of the type.
+ String disp = getHeader("Content-Disposition", null);
+ if (disp != null) {
+ // parse up the string value of the disposition
+ ContentDisposition disposition = new ContentDisposition(disp);
+ // now check for a filename value
+ String filename = disposition.getParameter("filename");
+ // copy and rename the parameter, if it exists.
+ if (filename != null) {
+ content.setParameter("name", filename);
+ // and update the string version
+ type = content.toString();
+ }
+ }
+ // set the header with the updated content type information.
+ setHeader("Content-Type", type);
+ }
+
+ } catch (IOException e) {
+ throw new MessagingException("Error updating message headers", e);
+ }
+ }
+
+ private String getSingleHeader(String name) throws MessagingException {
+ String[] values = getHeader(name);
+ if (values == null || values.length == 0) {
+ return null;
+ } else {
+ return values[0];
+ }
+ }
+
+
+ /**
+ * Attach a file to this body part from a File object.
+ *
+ * @param file The source File object.
+ *
+ * @exception IOException
+ * @exception MessagingException
+ */
+ public void attachFile(File file) throws IOException, MessagingException {
+ FileDataSource dataSource = new FileDataSource(file);
+ setDataHandler(new DataHandler(dataSource));
+ setFileName(dataSource.getName());
+ }
+
+
+ /**
+ * Attach a file to this body part from a file name.
+ *
+ * @param file The source file name.
+ *
+ * @exception IOException
+ * @exception MessagingException
+ */
+ public void attachFile(String file) throws IOException, MessagingException {
+ // just create a File object and attach.
+ attachFile(new File(file));
+ }
+
+
+ /**
+ * Save the body part content to a given target file.
+ *
+ * @param file The File object used to store the information.
+ *
+ * @exception IOException
+ * @exception MessagingException
+ */
+ public void saveFile(File file) throws IOException, MessagingException {
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
+ // we need to read the data in to write it out (sigh).
+ InputStream in = getInputStream();
+ try {
+ byte[] buffer = new byte[8192];
+ int length;
+ while ((length = in.read(buffer)) > 0) {
+ out.write(buffer, 0, length);
+ }
+ }
+ finally {
+ // make sure all of the streams are closed before we return
+ if (in != null) {
+ in.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
+
+ /**
+ * Save the body part content to a given target file.
+ *
+ * @param file The file name used to store the information.
+ *
+ * @exception IOException
+ * @exception MessagingException
+ */
+ public void saveFile(String file) throws IOException, MessagingException {
+ saveFile(new File(file));
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeMessage.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeMessage.java
new file mode 100644
index 00000000..6dc9f3ea
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeMessage.java
@@ -0,0 +1,1644 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectStreamException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.activation.DataHandler;
+import javax.mail.Address;
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Part;
+import javax.mail.Session;
+import javax.mail.internet.HeaderTokenizer.Token;
+
+import org.apache.geronimo.mail.util.ASCIIUtil;
+import org.apache.geronimo.mail.util.SessionUtil;
+
+/**
+ * @version $Rev: 702800 $ $Date: 2008-10-08 05:38:14 -0500 (Wed, 08 Oct 2008) $
+ */
+public class MimeMessage extends Message implements MimePart {
+ private static final String MIME_ADDRESS_STRICT = "mail.mime.address.strict";
+ private static final String MIME_DECODEFILENAME = "mail.mime.decodefilename";
+ private static final String MIME_ENCODEFILENAME = "mail.mime.encodefilename";
+
+ private static final String MAIL_ALTERNATES = "mail.alternates";
+ private static final String MAIL_REPLYALLCC = "mail.replyallcc";
+
+ // static used to ensure message ID uniqueness
+ private static int messageID = 0;
+
+
+ /**
+ * Extends {@link javax.mail.Message.RecipientType} to support addition recipient types.
+ */
+ public static class RecipientType extends Message.RecipientType {
+ /**
+ * Recipient type for Usenet news.
+ */
+ public static final RecipientType NEWSGROUPS = new RecipientType("Newsgroups");
+
+ protected RecipientType(String type) {
+ super(type);
+ }
+
+ /**
+ * Ensure the singleton is returned.
+ *
+ * @return resolved object
+ */
+ protected Object readResolve() throws ObjectStreamException {
+ if (this.type.equals("Newsgroups")) {
+ return NEWSGROUPS;
+ } else {
+ return super.readResolve();
+ }
+ }
+ }
+
+ /**
+ * The {@link DataHandler} for this Message's content.
+ */
+ protected DataHandler dh;
+ /**
+ * This message's content (unless sourced from a SharedInputStream).
+ */
+ protected byte[] content;
+ /**
+ * If the data for this message was supplied by a {@link SharedInputStream}
+ * then this is another such stream representing the content of this message;
+ * if this field is non-null, then {@link #content} will be null.
+ */
+ protected InputStream contentStream;
+ /**
+ * This message's headers.
+ */
+ protected InternetHeaders headers;
+ /**
+ * This message's flags.
+ */
+ protected Flags flags;
+ /**
+ * Flag indicating that the message has been modified; set to true when
+ * an empty message is created or when {@link #saveChanges()} is called.
+ */
+ protected boolean modified;
+ /**
+ * Flag indicating that the message has been saved.
+ */
+ protected boolean saved;
+
+ private final MailDateFormat dateFormat = new MailDateFormat();
+
+ /**
+ * Create a new MimeMessage.
+ * An empty message is created, with empty {@link #headers} and empty {@link #flags}.
+ * The {@link #modified} flag is set.
+ *
+ * @param session the session for this message
+ */
+ public MimeMessage(Session session) {
+ super(session);
+ headers = new InternetHeaders();
+ flags = new Flags();
+ // empty messages are modified, because the content is not there, and require saving before use.
+ modified = true;
+ saved = false;
+ }
+
+ /**
+ * Create a MimeMessage by reading an parsing the data from the supplied stream.
+ *
+ * @param session the session for this message
+ * @param in the stream to load from
+ * @throws MessagingException if there is a problem reading or parsing the stream
+ */
+ public MimeMessage(Session session, InputStream in) throws MessagingException {
+ this(session);
+ parse(in);
+ // this message is complete, so marked as unmodified.
+ modified = false;
+ // and no saving required
+ saved = true;
+ }
+
+ /**
+ * Copy a MimeMessage.
+ *
+ * @param message the message to copy
+ * @throws MessagingException is there was a problem copying the message
+ */
+ public MimeMessage(MimeMessage message) throws MessagingException {
+ super(message.session);
+ // get a copy of the source message flags
+ flags = message.getFlags();
+ // this is somewhat difficult to do. There's a lot of data in both the superclass and this
+ // class that needs to undergo a "deep cloning" operation. These operations don't really exist
+ // on the objects in question, so the only solution I can come up with is to serialize the
+ // message data of the source object using the write() method, then reparse the data in this
+ // object. I've not found a lot of uses for this particular constructor, so perhaps that's not
+ // really all that bad of a solution.
+
+ // serialize this out to an in-memory stream.
+ ByteArrayOutputStream copy = new ByteArrayOutputStream();
+
+ try {
+ // write this out the stream.
+ message.writeTo(copy);
+ copy.close();
+ // I think this ends up creating a new array for the data, but I'm not aware of any more
+ // efficient options.
+ ByteArrayInputStream inData = new ByteArrayInputStream(copy.toByteArray());
+ // now reparse this message into this object.
+ inData.close();
+ parse (inData);
+ // writing out the source data requires saving it, so we should consider this one saved also.
+ saved = true;
+ // this message is complete, so marked as unmodified.
+ modified = false;
+ } catch (IOException e) {
+ // I'm not sure ByteArrayInput/OutputStream actually throws IOExceptions or not, but the method
+ // signatures declare it, so we need to deal with it. Turning it into a messaging exception
+ // should fit the bill.
+ throw new MessagingException("Error copying MimeMessage data", e);
+ }
+ }
+
+ /**
+ * Create an new MimeMessage in the supplied {@link Folder} and message number.
+ *
+ * @param folder the Folder that contains the new message
+ * @param number the message number of the new message
+ */
+ protected MimeMessage(Folder folder, int number) {
+ super(folder, number);
+ headers = new InternetHeaders();
+ flags = new Flags();
+ // saving primarly involves updates to the message header. Since we're taking the header info
+ // from a message store in this context, we mark the message as saved.
+ saved = true;
+ // we've not filled in the content yet, so this needs to be marked as modified
+ modified = true;
+ }
+
+ /**
+ * Create a MimeMessage by reading an parsing the data from the supplied stream.
+ *
+ * @param folder the folder for this message
+ * @param in the stream to load from
+ * @param number the message number of the new message
+ * @throws MessagingException if there is a problem reading or parsing the stream
+ */
+ protected MimeMessage(Folder folder, InputStream in, int number) throws MessagingException {
+ this(folder, number);
+ parse(in);
+ // this message is complete, so marked as unmodified.
+ modified = false;
+ // and no saving required
+ saved = true;
+ }
+
+
+ /**
+ * Create a MimeMessage with the supplied headers and content.
+ *
+ * @param folder the folder for this message
+ * @param headers the headers for the new message
+ * @param content the content of the new message
+ * @param number the message number of the new message
+ * @throws MessagingException if there is a problem reading or parsing the stream
+ */
+ protected MimeMessage(Folder folder, InternetHeaders headers, byte[] content, int number) throws MessagingException {
+ this(folder, number);
+ this.headers = headers;
+ this.content = content;
+ // this message is complete, so marked as unmodified.
+ modified = false;
+ }
+
+ /**
+ * Parse the supplied stream and initialize {@link #headers} and {@link #content} appropriately.
+ *
+ * @param in the stream to read
+ * @throws MessagingException if there was a problem parsing the stream
+ */
+ protected void parse(InputStream in) throws MessagingException {
+ in = new BufferedInputStream(in);
+ // create the headers first from the stream. Note: We need to do this
+ // by calling createInternetHeaders because subclasses might wish to add
+ // additional headers to the set initialized from the stream.
+ headers = createInternetHeaders(in);
+
+ // now we need to get the rest of the content as a byte array...this means reading from the current
+ // position in the stream until the end and writing it to an accumulator ByteArrayOutputStream.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ byte buffer[] = new byte[1024];
+ int count;
+ while ((count = in.read(buffer, 0, 1024)) != -1) {
+ baos.write(buffer, 0, count);
+ }
+ } catch (Exception e) {
+ throw new MessagingException(e.toString(), e);
+ }
+ // and finally extract the content as a byte array.
+ content = baos.toByteArray();
+ }
+
+ /**
+ * Get the message "From" addresses. This looks first at the
+ * "From" headers, and no "From" header is found, the "Sender"
+ * header is checked. Returns null if not found.
+ *
+ * @return An array of addresses identifying the message from target. Returns
+ * null if this is not resolveable from the headers.
+ * @exception MessagingException
+ */
+ public Address[] getFrom() throws MessagingException {
+ // strict addressing controls this.
+ boolean strict = isStrictAddressing();
+ Address[] result = getHeaderAsInternetAddresses("From", strict);
+ if (result == null) {
+ result = getHeaderAsInternetAddresses("Sender", strict);
+ }
+ return result;
+ }
+
+ /**
+ * Set the current message "From" recipient. This replaces any
+ * existing "From" header. If the address is null, the header is
+ * removed.
+ *
+ * @param address The new "From" target.
+ *
+ * @exception MessagingException
+ */
+ public void setFrom(Address address) throws MessagingException {
+ setHeader("From", address);
+ }
+
+ /**
+ * Set the "From" header using the value returned by {@link InternetAddress#getLocalAddress(javax.mail.Session)}.
+ *
+ * @throws MessagingException if there was a problem setting the header
+ */
+ public void setFrom() throws MessagingException {
+ InternetAddress address = InternetAddress.getLocalAddress(session);
+ // no local address resolvable? This is an error.
+ if (address == null) {
+ throw new MessagingException("No local address defined");
+ }
+ setFrom(address);
+ }
+
+ /**
+ * Add a set of addresses to the existing From header.
+ *
+ * @param addresses The list to add.
+ *
+ * @exception MessagingException
+ */
+ public void addFrom(Address[] addresses) throws MessagingException {
+ addHeader("From", addresses);
+ }
+
+ /**
+ * Return the "Sender" header as an address.
+ *
+ * @return the "Sender" header as an address, or null if not present
+ * @throws MessagingException if there was a problem parsing the header
+ */
+ public Address getSender() throws MessagingException {
+ Address[] addrs = getHeaderAsInternetAddresses("Sender", isStrictAddressing());
+ return addrs != null && addrs.length > 0 ? addrs[0] : null;
+ }
+
+ /**
+ * Set the "Sender" header. If the address is null, this
+ * will remove the current sender header.
+ *
+ * @param address the new Sender address
+ *
+ * @throws MessagingException
+ * if there was a problem setting the header
+ */
+ public void setSender(Address address) throws MessagingException {
+ setHeader("Sender", address);
+ }
+
+ /**
+ * Gets the recipients by type. Returns null if there are no
+ * headers of the specified type. Acceptable RecipientTypes are:
+ *
+ * javax.mail.Message.RecipientType.TO
+ * javax.mail.Message.RecipientType.CC
+ * javax.mail.Message.RecipientType.BCC
+ * javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
+ *
+ * @param type The message RecipientType identifier.
+ *
+ * @return The array of addresses for the specified recipient types.
+ * @exception MessagingException
+ */
+ public Address[] getRecipients(Message.RecipientType type) throws MessagingException {
+ // is this a NEWSGROUP request? We need to handle this as a special case here, because
+ // this needs to return NewsAddress instances instead of InternetAddress items.
+ if (type == RecipientType.NEWSGROUPS) {
+ return getHeaderAsNewsAddresses(getHeaderForRecipientType(type));
+ }
+ // the other types are all internet addresses.
+ return getHeaderAsInternetAddresses(getHeaderForRecipientType(type), isStrictAddressing());
+ }
+
+ /**
+ * Retrieve all of the recipients defined for this message. This
+ * returns a merged array of all possible message recipients
+ * extracted from the headers. The relevant header types are:
+ *
+ *
+ * javax.mail.Message.RecipientType.TO
+ * javax.mail.Message.RecipientType.CC
+ * javax.mail.Message.RecipientType.BCC
+ * javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
+ *
+ * @return An array of all target message recipients.
+ * @exception MessagingException
+ */
+ public Address[] getAllRecipients() throws MessagingException {
+ List recipients = new ArrayList();
+ addRecipientsToList(recipients, RecipientType.TO);
+ addRecipientsToList(recipients, RecipientType.CC);
+ addRecipientsToList(recipients, RecipientType.BCC);
+ addRecipientsToList(recipients, RecipientType.NEWSGROUPS);
+
+ // this is supposed to return null if nothing is there.
+ if (recipients.isEmpty()) {
+ return null;
+ }
+ return (Address[]) recipients.toArray(new Address[recipients.size()]);
+ }
+
+ /**
+ * Utility routine to merge different recipient types into a
+ * single list.
+ *
+ * @param list The accumulator list.
+ * @param type The recipient type to extract.
+ *
+ * @exception MessagingException
+ */
+ private void addRecipientsToList(List list, Message.RecipientType type) throws MessagingException {
+
+ Address[] recipients;
+ if (type == RecipientType.NEWSGROUPS) {
+ recipients = getHeaderAsNewsAddresses(getHeaderForRecipientType(type));
+ }
+ else {
+ recipients = getHeaderAsInternetAddresses(getHeaderForRecipientType(type), isStrictAddressing());
+ }
+ if (recipients != null) {
+ list.addAll(Arrays.asList(recipients));
+ }
+ }
+
+ /**
+ * Set a recipients list for a particular recipient type. If the
+ * list is null, the corresponding header is removed.
+ *
+ * @param type The type of recipient to set.
+ * @param addresses The list of addresses.
+ *
+ * @exception MessagingException
+ */
+ public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
+ setHeader(getHeaderForRecipientType(type), InternetAddress.toString(addresses, 0));
+ }
+
+ /**
+ * Set a recipient field to a string address (which may be a
+ * list or group type).
+ *
+ * If the address is null, the field is removed.
+ *
+ * @param type The type of recipient to set.
+ * @param address The address string.
+ *
+ * @exception MessagingException
+ */
+ public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
+ setOrRemoveHeader(getHeaderForRecipientType(type), address);
+ }
+
+
+ /**
+ * Add a list of addresses to a target recipient list.
+ *
+ * @param type The target recipient type.
+ * @param address An array of addresses to add.
+ *
+ * @exception MessagingException
+ */
+ public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
+ addHeader(getHeaderForRecipientType(type), address);
+ }
+
+ /**
+ * Add an address to a target recipient list by string name.
+ *
+ * @param type The target header type.
+ * @param address The address to add.
+ *
+ * @exception MessagingException
+ */
+ public void addRecipients(Message.RecipientType type, String address) throws MessagingException {
+ addHeader(getHeaderForRecipientType(type), address);
+ }
+
+ /**
+ * Get the ReplyTo address information. The headers are parsed
+ * using the "mail.mime.address.strict" setting. If the "Reply-To" header does
+ * not have any addresses, then the value of the "From" field is used.
+ *
+ * @return An array of addresses obtained from parsing the header.
+ * @exception MessagingException
+ */
+ public Address[] getReplyTo() throws MessagingException {
+ Address[] addresses = getHeaderAsInternetAddresses("Reply-To", isStrictAddressing());
+ if (addresses == null) {
+ addresses = getFrom();
+ }
+ return addresses;
+ }
+
+ /**
+ * Set the Reply-To field to the provided list of addresses. If
+ * the address list is null, the header is removed.
+ *
+ * @param address The new field value.
+ *
+ * @exception MessagingException
+ */
+ public void setReplyTo(Address[] address) throws MessagingException {
+ setHeader("Reply-To", address);
+ }
+
+ /**
+ * Returns the value of the "Subject" header. If the subject
+ * is encoded as an RFC 2047 value, the value is decoded before
+ * return. If decoding fails, the raw string value is
+ * returned.
+ *
+ * @return The String value of the subject field.
+ * @exception MessagingException
+ */
+ public String getSubject() throws MessagingException {
+ String subject = getSingleHeader("Subject");
+ if (subject == null) {
+ return null;
+ } else {
+ try {
+ // this needs to be unfolded before decodeing.
+ return MimeUtility.decodeText(MimeUtility.unfold(subject));
+ } catch (UnsupportedEncodingException e) {
+ // ignored.
+ }
+ }
+
+ return subject;
+ }
+
+ /**
+ * Set the value for the "Subject" header. If the subject
+ * contains non US-ASCII characters, it is encoded in RFC 2047
+ * fashion.
+ *
+ * If the subject value is null, the Subject field is removed.
+ *
+ * @param subject The new subject value.
+ *
+ * @exception MessagingException
+ */
+ public void setSubject(String subject) throws MessagingException {
+ // just set this using the default character set.
+ setSubject(subject, null);
+ }
+
+ public void setSubject(String subject, String charset) throws MessagingException {
+ // standard null removal (yada, yada, yada....)
+ if (subject == null) {
+ removeHeader("Subject");
+ }
+ else {
+ try {
+ String s = MimeUtility.fold(9, MimeUtility.encodeText(subject, charset, null));
+ // encode this, and then fold to fit the line lengths.
+ setHeader("Subject", MimeUtility.fold(9, MimeUtility.encodeText(subject, charset, null)));
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException("Encoding error", e);
+ }
+ }
+ }
+
+ /**
+ * Get the value of the "Date" header field. Returns null if
+ * if the field is absent or the date is not in a parseable format.
+ *
+ * @return A Date object parsed according to RFC 822.
+ * @exception MessagingException
+ */
+ public Date getSentDate() throws MessagingException {
+ String value = getSingleHeader("Date");
+ if (value == null) {
+ return null;
+ }
+ try {
+ return dateFormat.parse(value);
+ } catch (java.text.ParseException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Set the message sent date. This updates the "Date" header.
+ * If the provided date is null, the header is removed.
+ *
+ * @param sent The new sent date value.
+ *
+ * @exception MessagingException
+ */
+ public void setSentDate(Date sent) throws MessagingException {
+ setOrRemoveHeader("Date", dateFormat.format(sent));
+ }
+
+ /**
+ * Get the message received date. The Sun implementation is
+ * documented as always returning null, so this one does too.
+ *
+ * @return Always returns null.
+ * @exception MessagingException
+ */
+ public Date getReceivedDate() throws MessagingException {
+ return null;
+ }
+
+ /**
+ * Return the content size of this message. This is obtained
+ * either from the size of the content field (if available) or
+ * from the contentStream, IFF the contentStream returns a positive
+ * size. Returns -1 if the size is not available.
+ *
+ * @return Size of the content in bytes.
+ * @exception MessagingException
+ */
+ public int getSize() throws MessagingException {
+ if (content != null) {
+ return content.length;
+ }
+ if (contentStream != null) {
+ try {
+ int size = contentStream.available();
+ if (size > 0) {
+ return size;
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Retrieve the line count for the current message. Returns
+ * -1 if the count cannot be determined.
+ *
+ * The Sun implementation always returns -1, so this version
+ * does too.
+ *
+ * @return The content line count (always -1 in this implementation).
+ * @exception MessagingException
+ */
+ public int getLineCount() throws MessagingException {
+ return -1;
+ }
+
+ /**
+ * Returns the current content type (defined in the "Content-Type"
+ * header. If not available, "text/plain" is the default.
+ *
+ * @return The String name of the message content type.
+ * @exception MessagingException
+ */
+ public String getContentType() throws MessagingException {
+ String value = getSingleHeader("Content-Type");
+ if (value == null) {
+ value = "text/plain";
+ }
+ return value;
+ }
+
+
+ /**
+ * Tests to see if this message has a mime-type match with the
+ * given type name.
+ *
+ * @param type The tested type name.
+ *
+ * @return If this is a type match on the primary and secondare portion of the types.
+ * @exception MessagingException
+ */
+ public boolean isMimeType(String type) throws MessagingException {
+ return new ContentType(getContentType()).match(type);
+ }
+
+ /**
+ * Retrieve the message "Content-Disposition" header field.
+ * This value represents how the part should be represented to
+ * the user.
+ *
+ * @return The string value of the Content-Disposition field.
+ * @exception MessagingException
+ */
+ public String getDisposition() throws MessagingException {
+ String disp = getSingleHeader("Content-Disposition");
+ if (disp != null) {
+ return new ContentDisposition(disp).getDisposition();
+ }
+ return null;
+ }
+
+
+ /**
+ * Set a new dispostion value for the "Content-Disposition" field.
+ * If the new value is null, the header is removed.
+ *
+ * @param disposition
+ * The new disposition value.
+ *
+ * @exception MessagingException
+ */
+ public void setDisposition(String disposition) throws MessagingException {
+ if (disposition == null) {
+ removeHeader("Content-Disposition");
+ }
+ else {
+ // the disposition has parameters, which we'll attempt to preserve in any existing header.
+ String currentHeader = getSingleHeader("Content-Disposition");
+ if (currentHeader != null) {
+ ContentDisposition content = new ContentDisposition(currentHeader);
+ content.setDisposition(disposition);
+ setHeader("Content-Disposition", content.toString());
+ }
+ else {
+ // set using the raw string.
+ setHeader("Content-Disposition", disposition);
+ }
+ }
+ }
+
+ /**
+ * Decode the Content-Transfer-Encoding header to determine
+ * the transfer encoding type.
+ *
+ * @return The string name of the required encoding.
+ * @exception MessagingException
+ */
+ public String getEncoding() throws MessagingException {
+ // this might require some parsing to sort out.
+ String encoding = getSingleHeader("Content-Transfer-Encoding");
+ if (encoding != null) {
+ // we need to parse this into ATOMs and other constituent parts. We want the first
+ // ATOM token on the string.
+ HeaderTokenizer tokenizer = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
+
+ Token token = tokenizer.next();
+ while (token.getType() != Token.EOF) {
+ // if this is an ATOM type, return it.
+ if (token.getType() == Token.ATOM) {
+ return token.getValue();
+ }
+ }
+ // not ATOMs found, just return the entire header value....somebody might be able to make sense of
+ // this.
+ return encoding;
+ }
+ // no header, nothing to return.
+ return null;
+ }
+
+ /**
+ * Retrieve the value of the "Content-ID" header. Returns null
+ * if the header does not exist.
+ *
+ * @return The current header value or null.
+ * @exception MessagingException
+ */
+ public String getContentID() throws MessagingException {
+ return getSingleHeader("Content-ID");
+ }
+
+ public void setContentID(String cid) throws MessagingException {
+ setOrRemoveHeader("Content-ID", cid);
+ }
+
+ public String getContentMD5() throws MessagingException {
+ return getSingleHeader("Content-MD5");
+ }
+
+ public void setContentMD5(String md5) throws MessagingException {
+ setOrRemoveHeader("Content-MD5", md5);
+ }
+
+ public String getDescription() throws MessagingException {
+ String description = getSingleHeader("Content-Description");
+ if (description != null) {
+ try {
+ // this could be both folded and encoded. Return this to usable form.
+ return MimeUtility.decodeText(MimeUtility.unfold(description));
+ } catch (UnsupportedEncodingException e) {
+ // ignore
+ }
+ }
+ // return the raw version for any errors.
+ return description;
+ }
+
+ public void setDescription(String description) throws MessagingException {
+ setDescription(description, null);
+ }
+
+ public void setDescription(String description, String charset) throws MessagingException {
+ if (description == null) {
+ removeHeader("Content-Description");
+ }
+ else {
+ try {
+ setHeader("Content-Description", MimeUtility.fold(21, MimeUtility.encodeText(description, charset, null)));
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException(e.getMessage(), e);
+ }
+ }
+
+ }
+
+ public String[] getContentLanguage() throws MessagingException {
+ return getHeader("Content-Language");
+ }
+
+ public void setContentLanguage(String[] languages) throws MessagingException {
+ if (languages == null) {
+ removeHeader("Content-Language");
+ } else if (languages.length == 1) {
+ setHeader("Content-Language", languages[0]);
+ } else {
+ StringBuffer buf = new StringBuffer(languages.length * 20);
+ buf.append(languages[0]);
+ for (int i = 1; i < languages.length; i++) {
+ buf.append(',').append(languages[i]);
+ }
+ setHeader("Content-Language", buf.toString());
+ }
+ }
+
+ public String getMessageID() throws MessagingException {
+ return getSingleHeader("Message-ID");
+ }
+
+ public String getFileName() throws MessagingException {
+ // see if there is a disposition. If there is, parse off the filename parameter.
+ String disposition = getDisposition();
+ String filename = null;
+
+ if (disposition != null) {
+ filename = new ContentDisposition(disposition).getParameter("filename");
+ }
+
+ // if there's no filename on the disposition, there might be a name parameter on a
+ // Content-Type header.
+ if (filename == null) {
+ String type = getContentType();
+ if (type != null) {
+ try {
+ filename = new ContentType(type).getParameter("name");
+ } catch (ParseException e) {
+ }
+ }
+ }
+ // if we have a name, we might need to decode this if an additional property is set.
+ if (filename != null && SessionUtil.getBooleanProperty(session, MIME_DECODEFILENAME, false)) {
+ try {
+ filename = MimeUtility.decodeText(filename);
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException("Unable to decode filename", e);
+ }
+ }
+
+ return filename;
+ }
+
+
+ public void setFileName(String name) throws MessagingException {
+ // there's an optional session property that requests file name encoding...we need to process this before
+ // setting the value.
+ if (name != null && SessionUtil.getBooleanProperty(session, MIME_ENCODEFILENAME, false)) {
+ try {
+ name = MimeUtility.encodeText(name);
+ } catch (UnsupportedEncodingException e) {
+ throw new MessagingException("Unable to encode filename", e);
+ }
+ }
+
+ // get the disposition string.
+ String disposition = getDisposition();
+ // if not there, then this is an attachment.
+ if (disposition == null) {
+ disposition = Part.ATTACHMENT;
+ }
+ // now create a disposition object and set the parameter.
+ ContentDisposition contentDisposition = new ContentDisposition(disposition);
+ contentDisposition.setParameter("filename", name);
+
+ // serialize this back out and reset.
+ setDisposition(contentDisposition.toString());
+ }
+
+ public InputStream getInputStream() throws MessagingException, IOException {
+ return getDataHandler().getInputStream();
+ }
+
+ protected InputStream getContentStream() throws MessagingException {
+ if (contentStream != null) {
+ return contentStream;
+ }
+
+ if (content != null) {
+ return new ByteArrayInputStream(content);
+ } else {
+ throw new MessagingException("No content");
+ }
+ }
+
+ public InputStream getRawInputStream() throws MessagingException {
+ return getContentStream();
+ }
+
+ public synchronized DataHandler getDataHandler() throws MessagingException {
+ if (dh == null) {
+ dh = new DataHandler(new MimePartDataSource(this));
+ }
+ return dh;
+ }
+
+ public Object getContent() throws MessagingException, IOException {
+ return getDataHandler().getContent();
+ }
+
+ public void setDataHandler(DataHandler handler) throws MessagingException {
+ dh = handler;
+ // if we have a handler override, then we need to invalidate any content
+ // headers that define the types. This information will be derived from the
+ // data heander unless subsequently overridden.
+ removeHeader("Content-Type");
+ removeHeader("Content-Transfer-Encoding");
+ }
+
+ public void setContent(Object content, String type) throws MessagingException {
+ setDataHandler(new DataHandler(content, type));
+ }
+
+ public void setText(String text) throws MessagingException {
+ setText(text, null, "plain");
+ }
+
+ public void setText(String text, String charset) throws MessagingException {
+ setText(text, charset, "plain");
+ }
+
+
+ public void setText(String text, String charset, String subtype) throws MessagingException {
+ // we need to sort out the character set if one is not provided.
+ if (charset == null) {
+ // if we have non us-ascii characters here, we need to adjust this.
+ if (!ASCIIUtil.isAscii(text)) {
+ charset = MimeUtility.getDefaultMIMECharset();
+ }
+ else {
+ charset = "us-ascii";
+ }
+ }
+ setContent(text, "text/" + subtype + "; charset=" + MimeUtility.quote(charset, HeaderTokenizer.MIME));
+ }
+
+ public void setContent(Multipart part) throws MessagingException {
+ setDataHandler(new DataHandler(part, part.getContentType()));
+ part.setParent(this);
+ }
+
+ public Message reply(boolean replyToAll) throws MessagingException {
+ // create a new message in this session.
+ MimeMessage reply = createMimeMessage(session);
+
+ // get the header and add the "Re:" bit, if necessary.
+ String newSubject = getSubject();
+ if (newSubject != null) {
+ // check to see if it already begins with "Re: " (in any case).
+ // Add one on if we don't have it yet.
+ if (!newSubject.regionMatches(true, 0, "Re: ", 0, 4)) {
+ newSubject = "Re: " + newSubject;
+ }
+ reply.setSubject(newSubject);
+ }
+
+ // if this message has a message ID, then add a In-Reply-To and References
+ // header to the reply message
+ String messageID = getSingleHeader("Message-ID");
+ if (messageID != null) {
+ // this one is just set unconditionally
+ reply.setHeader("In-Reply-To", messageID);
+ // we might already have a references header. If so, then add our message id
+ // on the the end
+ String references = getSingleHeader("References");
+ if (references == null) {
+ references = messageID;
+ }
+ else {
+ references = references + " " + messageID;
+ }
+ // and this is a replacement for whatever might be there.
+ reply.setHeader("References", MimeUtility.fold("References: ".length(), references));
+ }
+
+ Address[] toRecipients = getReplyTo();
+
+ // set the target recipients the replyTo value
+ reply.setRecipients(Message.RecipientType.TO, getReplyTo());
+
+ // need to reply to everybody? More things to add.
+ if (replyToAll) {
+ // when replying, we want to remove "duplicates" in the final list.
+
+ HashMap masterList = new HashMap();
+
+ // reply to all implies add the local sender. Add this to the list if resolveable.
+ InternetAddress localMail = InternetAddress.getLocalAddress(session);
+ if (localMail != null) {
+ masterList.put(localMail.getAddress(), localMail);
+ }
+ // see if we have some local aliases to deal with.
+ String alternates = session.getProperty(MAIL_ALTERNATES);
+ if (alternates != null) {
+ // parse this string list and merge with our set.
+ Address[] alternateList = InternetAddress.parse(alternates, false);
+ mergeAddressList(masterList, alternateList);
+ }
+
+ // the master list now contains an a list of addresses we will exclude from
+ // the addresses. From this point on, we're going to prune any additional addresses
+ // against this list, AND add any new addresses to the list
+
+ // now merge in the main recipients, and merge in the other recipents as well
+ Address[] toList = pruneAddresses(masterList, getRecipients(Message.RecipientType.TO));
+ if (toList.length != 0) {
+ // now check to see what sort of reply we've been asked to send.
+ // if replying to all as a CC, then we need to add to the CC list, otherwise they are
+ // TO recipients.
+ if (SessionUtil.getBooleanProperty(session, MAIL_REPLYALLCC, false)) {
+ reply.addRecipients(Message.RecipientType.CC, toList);
+ }
+ else {
+ reply.addRecipients(Message.RecipientType.TO, toList);
+ }
+ }
+ // and repeat for the CC list.
+ toList = pruneAddresses(masterList, getRecipients(Message.RecipientType.CC));
+ if (toList.length != 0) {
+ reply.addRecipients(Message.RecipientType.CC, toList);
+ }
+
+ // a news group list is separate from the normal addresses. We just take these recepients
+ // asis without trying to prune duplicates.
+ toList = getRecipients(RecipientType.NEWSGROUPS);
+ if (toList != null && toList.length != 0) {
+ reply.addRecipients(RecipientType.NEWSGROUPS, toList);
+ }
+ }
+
+ // this is a bit of a pain. We can't set the flags here by specifying the system flag, we need to
+ // construct a flag item instance inorder to set it.
+
+ // this is an answered email.
+ setFlags(new Flags(Flags.Flag.ANSWERED), true);
+ // all done, return the constructed Message object.
+ return reply;
+ }
+
+
+ /**
+ * Merge a set of addresses into a master accumulator list, eliminating
+ * duplicates.
+ *
+ * @param master The set of addresses we've accumulated so far.
+ * @param list The list of addresses to merge in.
+ */
+ private void mergeAddressList(Map master, Address[] list) {
+ // make sure we have a list.
+ if (list == null) {
+ return;
+ }
+ for (int i = 0; i < list.length; i++) {
+ InternetAddress address = (InternetAddress)list[i];
+
+ // if not in the master list already, add it now.
+ if (!master.containsKey(address.getAddress())) {
+ master.put(address.getAddress(), address);
+ }
+ }
+ }
+
+
+ /**
+ * Prune a list of addresses against our master address list,
+ * returning the "new" addresses. The master list will be
+ * updated with this new set of addresses.
+ *
+ * @param master The master address list of addresses we've seen before.
+ * @param list The new list of addresses to prune.
+ *
+ * @return An array of addresses pruned of any duplicate addresses.
+ */
+ private Address[] pruneAddresses(Map master, Address[] list) {
+ // return an empy array if we don't get an input list.
+ if (list == null) {
+ return new Address[0];
+ }
+
+ // optimistically assume there are no addresses to eliminate (common).
+ ArrayList prunedList = new ArrayList(list.length);
+ for (int i = 0; i < list.length; i++) {
+ InternetAddress address = (InternetAddress)list[i];
+
+ // if not in the master list, this is a new one. Add to both the master list and
+ // the pruned list.
+ if (!master.containsKey(address.getAddress())) {
+ master.put(address.getAddress(), address);
+ prunedList.add(address);
+ }
+ }
+ // convert back to list form.
+ return (Address[])prunedList.toArray(new Address[0]);
+ }
+
+
+ /**
+ * Write the message out to a stream in RFC 822 format.
+ *
+ * @param out The target output stream.
+ *
+ * @exception MessagingException
+ * @exception IOException
+ */
+ public void writeTo(OutputStream out) throws MessagingException, IOException {
+ writeTo(out, null);
+ }
+
+ /**
+ * Write the message out to a target output stream, excluding the
+ * specified message headers.
+ *
+ * @param out The target output stream.
+ * @param ignoreHeaders
+ * An array of header types to ignore. This can be null, which means
+ * write out all headers.
+ *
+ * @exception MessagingException
+ * @exception IOException
+ */
+ public void writeTo(OutputStream out, String[] ignoreHeaders) throws MessagingException, IOException {
+ // make sure everything is saved before we write
+ if (!saved) {
+ saveChanges();
+ }
+
+ // write out the headers first
+ headers.writeTo(out, ignoreHeaders);
+ // add the separater between the headers and the data portion.
+ out.write('\r');
+ out.write('\n');
+
+ // if the modfied flag, we don't have current content, so the data handler needs to
+ // take care of writing this data out.
+ if (modified) {
+ OutputStream encoderStream = MimeUtility.encode(out, getEncoding());
+ dh.writeTo(encoderStream);
+ encoderStream.flush();
+ } else {
+ // if we have content directly, we can write this out now.
+ if (content != null) {
+ out.write(content);
+ }
+ else {
+ // see if we can get a content stream for this message. We might have had one
+ // explicitly set, or a subclass might override the get method to provide one.
+ InputStream in = getContentStream();
+
+ byte[] buffer = new byte[8192];
+ int length = in.read(buffer);
+ // copy the data stream-to-stream.
+ while (length > 0) {
+ out.write(buffer, 0, length);
+ length = in.read(buffer);
+ }
+ in.close();
+ }
+ }
+
+ // flush any data we wrote out, but do not close the stream. That's the caller's duty.
+ out.flush();
+ }
+
+
+ /**
+ * Retrieve all headers that match a given name.
+ *
+ * @param name The target name.
+ *
+ * @return The set of headers that match the given name. These headers
+ * will be the decoded() header values if these are RFC 2047
+ * encoded.
+ * @exception MessagingException
+ */
+ public String[] getHeader(String name) throws MessagingException {
+ return headers.getHeader(name);
+ }
+
+ /**
+ * Get all headers that match a particular name, as a single string.
+ * Individual headers are separated by the provided delimiter. If
+ * the delimiter is null, only the first header is returned.
+ *
+ * @param name The source header name.
+ * @param delimiter The delimiter string to be used between headers. If null, only
+ * the first is returned.
+ *
+ * @return The headers concatenated as a single string.
+ * @exception MessagingException
+ */
+ public String getHeader(String name, String delimiter) throws MessagingException {
+ return headers.getHeader(name, delimiter);
+ }
+
+ /**
+ * Set a new value for a named header.
+ *
+ * @param name The name of the target header.
+ * @param value The new value for the header.
+ *
+ * @exception MessagingException
+ */
+ public void setHeader(String name, String value) throws MessagingException {
+ headers.setHeader(name, value);
+ }
+
+ /**
+ * Conditionally set or remove a named header. If the new value
+ * is null, the header is removed.
+ *
+ * @param name The header name.
+ * @param value The new header value. A null value causes the header to be
+ * removed.
+ *
+ * @exception MessagingException
+ */
+ private void setOrRemoveHeader(String name, String value) throws MessagingException {
+ if (value == null) {
+ headers.removeHeader(name);
+ }
+ else {
+ headers.setHeader(name, value);
+ }
+ }
+
+ /**
+ * Add a new value to an existing header. The added value is
+ * created as an additional header of the same type and value.
+ *
+ * @param name The name of the target header.
+ * @param value The removed header.
+ *
+ * @exception MessagingException
+ */
+ public void addHeader(String name, String value) throws MessagingException {
+ headers.addHeader(name, value);
+ }
+
+ /**
+ * Remove a header with the given name.
+ *
+ * @param name The name of the removed header.
+ *
+ * @exception MessagingException
+ */
+ public void removeHeader(String name) throws MessagingException {
+ headers.removeHeader(name);
+ }
+
+ /**
+ * Retrieve the complete list of message headers, as an enumeration.
+ *
+ * @return An Enumeration of the message headers.
+ * @exception MessagingException
+ */
+ public Enumeration getAllHeaders() throws MessagingException {
+ return headers.getAllHeaders();
+ }
+
+ public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
+ return headers.getMatchingHeaders(names);
+ }
+
+ public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
+ return headers.getNonMatchingHeaders(names);
+ }
+
+ public void addHeaderLine(String line) throws MessagingException {
+ headers.addHeaderLine(line);
+ }
+
+ public Enumeration getAllHeaderLines() throws MessagingException {
+ return headers.getAllHeaderLines();
+ }
+
+ public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
+ return headers.getMatchingHeaderLines(names);
+ }
+
+ public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
+ return headers.getNonMatchingHeaderLines(names);
+ }
+
+
+ /**
+ * Return a copy the flags associated with this message.
+ *
+ * @return a copy of the flags for this message
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public synchronized Flags getFlags() throws MessagingException {
+ return (Flags) flags.clone();
+ }
+
+
+ /**
+ * Check whether the supplied flag is set.
+ * The default implementation checks the flags returned by {@link #getFlags()}.
+ *
+ * @param flag the flags to check for
+ * @return true if the flags is set
+ * @throws MessagingException if there was a problem accessing the Store
+ */
+ public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
+ return flags.contains(flag);
+ }
+
+ /**
+ * Set or clear a flag value.
+ *
+ * @param flags The set of flags to effect.
+ * @param set The value to set the flag to (true or false).
+ *
+ * @exception MessagingException
+ */
+ public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
+ if (set) {
+ flags.add(flag);
+ }
+ else {
+ flags.remove(flag);
+ }
+ }
+
+ /**
+ * Saves any changes on this message. When called, the modified
+ * and saved flags are set to true and updateHeaders() is called
+ * to force updates.
+ *
+ * @exception MessagingException
+ */
+ public void saveChanges() throws MessagingException {
+ // setting modified invalidates the current content.
+ modified = true;
+ saved = true;
+ // update message headers from the content.
+ updateHeaders();
+ }
+
+ /**
+ * Update the internet headers so that they make sense. This
+ * will attempt to make sense of the message content type
+ * given the state of the content.
+ *
+ * @exception MessagingException
+ */
+ protected void updateHeaders() throws MessagingException {
+
+ DataHandler handler = getDataHandler();
+
+ try {
+ // figure out the content type. If not set, we'll need to figure this out.
+ String type = dh.getContentType();
+ // we might need to reconcile the content type and our explicitly set type
+ String explicitType = getSingleHeader("Content-Type");
+ // parse this content type out so we can do matches/compares.
+ ContentType content = new ContentType(type);
+
+ // is this a multipart content?
+ if (content.match("multipart/*")) {
+ // the content is suppose to be a MimeMultipart. Ping it to update it's headers as well.
+ try {
+ MimeMultipart part = (MimeMultipart)handler.getContent();
+ part.updateHeaders();
+ } catch (ClassCastException e) {
+ throw new MessagingException("Message content is not MimeMultipart", e);
+ }
+ }
+ else if (!content.match("message/rfc822")) {
+ // simple part, we need to update the header type information
+ // if no encoding is set yet, figure this out from the data handler content.
+ if (getSingleHeader("Content-Transfer-Encoding") == null) {
+ setHeader("Content-Transfer-Encoding", MimeUtility.getEncoding(handler));
+ }
+
+ // is a content type header set? Check the property to see if we need to set this.
+ if (explicitType == null) {
+ if (SessionUtil.getBooleanProperty(session, "MIME_MAIL_SETDEFAULTTEXTCHARSET", true)) {
+ // is this a text type? Figure out the encoding and make sure it is set.
+ if (content.match("text/*")) {
+ // the charset should be specified as a parameter on the MIME type. If not there,
+ // try to figure one out.
+ if (content.getParameter("charset") == null) {
+
+ String encoding = getEncoding();
+ // if we're sending this as 7-bit ASCII, our character set need to be
+ // compatible.
+ if (encoding != null && encoding.equalsIgnoreCase("7bit")) {
+ content.setParameter("charset", "us-ascii");
+ }
+ else {
+ // get the global default.
+ content.setParameter("charset", MimeUtility.getDefaultMIMECharset());
+ }
+ // replace the original type string
+ type = content.toString();
+ }
+ }
+ }
+ }
+ }
+
+ // if we don't have a content type header, then create one.
+ if (explicitType == null) {
+ // get the disposition header, and if it is there, copy the filename parameter into the
+ // name parameter of the type.
+ String disp = getSingleHeader("Content-Disposition");
+ if (disp != null) {
+ // parse up the string value of the disposition
+ ContentDisposition disposition = new ContentDisposition(disp);
+ // now check for a filename value
+ String filename = disposition.getParameter("filename");
+ // copy and rename the parameter, if it exists.
+ if (filename != null) {
+ content.setParameter("name", filename);
+ // set the header with the updated content type information.
+ type = content.toString();
+ }
+ }
+ // if no header has been set, then copy our current type string (which may
+ // have been modified above)
+ setHeader("Content-Type", type);
+ }
+
+ // make sure we set the MIME version
+ setHeader("MIME-Version", "1.0");
+ // new javamail 1.4 requirement.
+ updateMessageID();
+
+ } catch (IOException e) {
+ throw new MessagingException("Error updating message headers", e);
+ }
+ }
+
+
+ /**
+ * Create a new set of internet headers from the
+ * InputStream
+ *
+ * @param in The header source.
+ *
+ * @return A new InternetHeaders object containing the
+ * appropriate headers.
+ * @exception MessagingException
+ */
+ protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
+ // internet headers has a constructor for just this purpose
+ return new InternetHeaders(in);
+ }
+
+ /**
+ * Convert a header into an array of NewsAddress items.
+ *
+ * @param header The name of the source header.
+ *
+ * @return The parsed array of addresses.
+ * @exception MessagingException
+ */
+ private Address[] getHeaderAsNewsAddresses(String header) throws MessagingException {
+ // NB: We're using getHeader() here to allow subclasses an opportunity to perform lazy loading
+ // of the headers.
+ String mergedHeader = getHeader(header, ",");
+ if (mergedHeader != null) {
+ return NewsAddress.parse(mergedHeader);
+ }
+ return null;
+ }
+
+ private Address[] getHeaderAsInternetAddresses(String header, boolean strict) throws MessagingException {
+ // NB: We're using getHeader() here to allow subclasses an opportunity to perform lazy loading
+ // of the headers.
+ String mergedHeader = getHeader(header, ",");
+
+ if (mergedHeader != null) {
+ return InternetAddress.parseHeader(mergedHeader, strict);
+ }
+ return null;
+ }
+
+ /**
+ * Check to see if we require strict addressing on parsing
+ * internet headers.
+ *
+ * @return The current value of the "mail.mime.address.strict" session
+ * property, or true, if the property is not set.
+ */
+ private boolean isStrictAddressing() {
+ return SessionUtil.getBooleanProperty(session, MIME_ADDRESS_STRICT, true);
+ }
+
+ /**
+ * Set a named header to the value of an address field.
+ *
+ * @param header The header name.
+ * @param address The address value. If the address is null, the header is removed.
+ *
+ * @exception MessagingException
+ */
+ private void setHeader(String header, Address address) throws MessagingException {
+ if (address == null) {
+ removeHeader(header);
+ }
+ else {
+ setHeader(header, address.toString());
+ }
+ }
+
+ /**
+ * Set a header to a list of addresses.
+ *
+ * @param header The header name.
+ * @param addresses An array of addresses to set the header to. If null, the
+ * header is removed.
+ */
+ private void setHeader(String header, Address[] addresses) {
+ if (addresses == null) {
+ headers.removeHeader(header);
+ }
+ else {
+ headers.setHeader(header, addresses);
+ }
+ }
+
+ private void addHeader(String header, Address[] addresses) throws MessagingException {
+ headers.addHeader(header, InternetAddress.toString(addresses));
+ }
+
+ private String getHeaderForRecipientType(Message.RecipientType type) throws MessagingException {
+ if (RecipientType.TO == type) {
+ return "To";
+ } else if (RecipientType.CC == type) {
+ return "Cc";
+ } else if (RecipientType.BCC == type) {
+ return "Bcc";
+ } else if (RecipientType.NEWSGROUPS == type) {
+ return "Newsgroups";
+ } else {
+ throw new MessagingException("Unsupported recipient type: " + type.toString());
+ }
+ }
+
+ /**
+ * Utility routine to get a header as a single string value
+ * rather than an array of headers.
+ *
+ * @param name The name of the header.
+ *
+ * @return The single string header value. If multiple headers exist,
+ * the additional ones are ignored.
+ * @exception MessagingException
+ */
+ private String getSingleHeader(String name) throws MessagingException {
+ String[] values = getHeader(name);
+ if (values == null || values.length == 0) {
+ return null;
+ } else {
+ return values[0];
+ }
+ }
+
+ /**
+ * Update the message identifier after headers have been updated.
+ *
+ * The default message id is composed of the following items:
+ *
+ * 1) A newly created object's hash code.
+ * 2) A uniqueness counter
+ * 3) The current time in milliseconds
+ * 4) The string JavaMail
+ * 5) The user's local address as returned by InternetAddress.getLocalAddress().
+ *
+ * @exception MessagingException
+ */
+ protected void updateMessageID() throws MessagingException {
+ StringBuffer id = new StringBuffer();
+
+ id.append('<');
+ id.append(new Object().hashCode());
+ id.append('.');
+ id.append(messageID++);
+ id.append(System.currentTimeMillis());
+ id.append('.');
+ id.append("JavaMail.");
+
+ // get the local address and apply a suitable default.
+
+ InternetAddress localAddress = InternetAddress.getLocalAddress(session);
+ if (localAddress != null) {
+ id.append(localAddress.getAddress());
+ }
+ else {
+ id.append("javamailuser@localhost");
+ }
+ id.append('>');
+
+ setHeader("Message-ID", id.toString());
+ }
+
+ /**
+ * Method used to create a new MimeMessage instance. This method
+ * is used whenever the MimeMessage class needs to create a new
+ * Message instance (e.g, reply()). This method allows subclasses
+ * to override the class of message that gets created or set
+ * default values, if needed.
+ *
+ * @param session The session associated with this message.
+ *
+ * @return A newly create MimeMessage instance.
+ * @throws javax.mail.MessagingException if the MimeMessage could not be created
+ */
+ protected MimeMessage createMimeMessage(Session session) throws javax.mail.MessagingException {
+ return new MimeMessage(session);
+ }
+
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeMultipart.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeMultipart.java
new file mode 100644
index 00000000..12c37851
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeMultipart.java
@@ -0,0 +1,655 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import java.util.Arrays;
+
+import javax.activation.DataSource;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.MultipartDataSource;
+
+import org.apache.geronimo.mail.util.SessionUtil;
+
+/**
+ * @version $Rev: 689486 $ $Date: 2008-08-27 09:11:03 -0500 (Wed, 27 Aug 2008) $
+ */
+public class MimeMultipart extends Multipart {
+ private static final String MIME_IGNORE_MISSING_BOUNDARY = "mail.mime.multipart.ignoremissingendboundary";
+
+ /**
+ * DataSource that provides our InputStream.
+ */
+ protected DataSource ds;
+ /**
+ * Indicates if the data has been parsed.
+ */
+ protected boolean parsed = true;
+
+ // the content type information
+ private transient ContentType type;
+
+ // indicates if we've seen the final boundary line when parsing.
+ private boolean complete = true;
+
+ // MIME multipart preable text that can appear before the first boundary line.
+ private String preamble = null;
+
+ /**
+ * Create an empty MimeMultipart with content type "multipart/mixed"
+ */
+ public MimeMultipart() {
+ this("mixed");
+ }
+
+ /**
+ * Create an empty MimeMultipart with the subtype supplied.
+ *
+ * @param subtype the subtype
+ */
+ public MimeMultipart(String subtype) {
+ type = new ContentType("multipart", subtype, null);
+ type.setParameter("boundary", getBoundary());
+ contentType = type.toString();
+ }
+
+ /**
+ * Create a MimeMultipart from the supplied DataSource.
+ *
+ * @param dataSource the DataSource to use
+ * @throws MessagingException
+ */
+ public MimeMultipart(DataSource dataSource) throws MessagingException {
+ ds = dataSource;
+ if (dataSource instanceof MultipartDataSource) {
+ super.setMultipartDataSource((MultipartDataSource) dataSource);
+ parsed = true;
+ } else {
+ // We keep the original, provided content type string so that we
+ // don't end up changing quoting/formatting of the header unless
+ // changes are made to the content type. James is somewhat dependent
+ // on that behavior.
+ contentType = ds.getContentType();
+ type = new ContentType(contentType);
+ parsed = false;
+ }
+ }
+
+ public void setSubType(String subtype) throws MessagingException {
+ type.setSubType(subtype);
+ contentType = type.toString();
+ }
+
+ public int getCount() throws MessagingException {
+ parse();
+ return super.getCount();
+ }
+
+ public synchronized BodyPart getBodyPart(int part) throws MessagingException {
+ parse();
+ return super.getBodyPart(part);
+ }
+
+ public BodyPart getBodyPart(String cid) throws MessagingException {
+ parse();
+ for (int i = 0; i < parts.size(); i++) {
+ MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
+ if (cid.equals(bodyPart.getContentID())) {
+ return bodyPart;
+ }
+ }
+ return null;
+ }
+
+ protected void updateHeaders() throws MessagingException {
+ parse();
+ for (int i = 0; i < parts.size(); i++) {
+ MimeBodyPart bodyPart = (MimeBodyPart) parts.get(i);
+ bodyPart.updateHeaders();
+ }
+ }
+
+ private static byte[] dash = { '-', '-' };
+ private static byte[] crlf = { 13, 10 };
+
+ public void writeTo(OutputStream out) throws IOException, MessagingException {
+ parse();
+ String boundary = type.getParameter("boundary");
+ byte[] bytes = boundary.getBytes();
+
+ if (preamble != null) {
+ byte[] preambleBytes = preamble.getBytes();
+ // write this out, followed by a line break.
+ out.write(preambleBytes);
+ out.write(crlf);
+ }
+
+ for (int i = 0; i < parts.size(); i++) {
+ BodyPart bodyPart = (BodyPart) parts.get(i);
+ out.write(dash);
+ out.write(bytes);
+ out.write(crlf);
+ bodyPart.writeTo(out);
+ out.write(crlf);
+ }
+ out.write(dash);
+ out.write(bytes);
+ out.write(dash);
+ out.write(crlf);
+ out.flush();
+ }
+
+ protected void parse() throws MessagingException {
+ if (parsed) {
+ return;
+ }
+
+ try {
+ ContentType cType = new ContentType(contentType);
+ InputStream is = new BufferedInputStream(ds.getInputStream());
+ BufferedInputStream pushbackInStream = null;
+ String boundaryString = cType.getParameter("boundary");
+ byte[] boundary = null;
+ if (boundaryString == null) {
+ pushbackInStream = new BufferedInputStream(is, 1200);
+ // read until we find something that looks like a boundary string
+ boundary = readTillFirstBoundary(pushbackInStream);
+ }
+ else {
+ boundary = ("--" + boundaryString).getBytes();
+ pushbackInStream = new BufferedInputStream(is, boundary.length + 1000);
+ readTillFirstBoundary(pushbackInStream, boundary);
+ }
+
+ while (true) {
+ MimeBodyPartInputStream partStream;
+ partStream = new MimeBodyPartInputStream(pushbackInStream, boundary);
+ addBodyPart(new MimeBodyPart(partStream));
+
+ // terminated by an EOF rather than a proper boundary?
+ if (!partStream.boundaryFound) {
+ if (!SessionUtil.getBooleanProperty(MIME_IGNORE_MISSING_BOUNDARY, true)) {
+ throw new MessagingException("Missing Multi-part end boundary");
+ }
+ complete = false;
+ }
+ // if we hit the final boundary, stop processing this
+ if (partStream.finalBoundaryFound) {
+ break;
+ }
+ }
+ } catch (Exception e){
+ throw new MessagingException(e.toString(),e);
+ }
+ parsed = true;
+ }
+
+ /**
+ * Move the read pointer to the begining of the first part
+ * read till the end of first boundary. Any data read before this point are
+ * saved as the preamble.
+ *
+ * @param pushbackInStream
+ * @param boundary
+ * @throws MessagingException
+ */
+ private byte[] readTillFirstBoundary(BufferedInputStream pushbackInStream) throws MessagingException {
+ ByteArrayOutputStream preambleStream = new ByteArrayOutputStream();
+
+ try {
+ while (true) {
+ // read the next line
+ byte[] line = readLine(pushbackInStream);
+ // hit an EOF?
+ if (line == null) {
+ throw new MessagingException("Unexpected End of Stream while searching for first Mime Boundary");
+ }
+ // if this looks like a boundary, then make it so
+ if (line.length > 2 && line[0] == '-' && line[1] == '-') {
+ // save the preamble, if there is one.
+ byte[] preambleBytes = preambleStream.toByteArray();
+ if (preambleBytes.length > 0) {
+ preamble = new String(preambleBytes);
+ }
+ return stripLinearWhiteSpace(line);
+ }
+ else {
+ // this is part of the preamble.
+ preambleStream.write(line);
+ preambleStream.write('\r');
+ preambleStream.write('\n');
+ }
+ }
+ } catch (IOException ioe) {
+ throw new MessagingException(ioe.toString(), ioe);
+ }
+ }
+
+
+ /**
+ * Scan a line buffer stripping off linear whitespace
+ * characters, returning a new array without the
+ * characters, if possible.
+ *
+ * @param line The source line buffer.
+ *
+ * @return A byte array with white space characters removed,
+ * if necessary.
+ */
+ private byte[] stripLinearWhiteSpace(byte[] line) {
+ int index = line.length - 1;
+ // if the last character is not a space or tab, we
+ // can use this unchanged
+ if (line[index] != ' ' && line[index] != '\t') {
+ return line;
+ }
+ // scan backwards for the first non-white space
+ for (; index > 0; index--) {
+ if (line[index] != ' ' && line[index] != '\t') {
+ break;
+ }
+ }
+ // make a shorter copy of this
+ byte[] newLine = new byte[index + 1];
+ System.arraycopy(line, 0, newLine, 0, index + 1);
+ return newLine;
+ }
+
+ /**
+ * Move the read pointer to the begining of the first part
+ * read till the end of first boundary. Any data read before this point are
+ * saved as the preamble.
+ *
+ * @param pushbackInStream
+ * @param boundary
+ * @throws MessagingException
+ */
+ private void readTillFirstBoundary(BufferedInputStream pushbackInStream, byte[] boundary) throws MessagingException {
+ ByteArrayOutputStream preambleStream = new ByteArrayOutputStream();
+
+ try {
+ while (true) {
+ // read the next line
+ byte[] line = readLine(pushbackInStream);
+ // hit an EOF?
+ if (line == null) {
+ throw new MessagingException("Unexpected End of Stream while searching for first Mime Boundary");
+ }
+
+ // apply the boundary comparison rules to this
+ if (compareBoundary(line, boundary)) {
+ // save the preamble, if there is one.
+ byte[] preambleBytes = preambleStream.toByteArray();
+ if (preambleBytes.length > 0) {
+ preamble = new String(preambleBytes);
+ }
+ return;
+ }
+
+ // this is part of the preamble.
+ preambleStream.write(line);
+ preambleStream.write('\r');
+ preambleStream.write('\n');
+ }
+ } catch (IOException ioe) {
+ throw new MessagingException(ioe.toString(), ioe);
+ }
+ }
+
+
+ /**
+ * Peform a boundary comparison, taking into account
+ * potential linear white space
+ *
+ * @param line The line to compare.
+ * @param boundary The boundary we're searching for
+ *
+ * @return true if this is a valid boundary line, false for
+ * any mismatches.
+ */
+ private boolean compareBoundary(byte[] line, byte[] boundary) {
+ // if the line is too short, this is an easy failure
+ if (line.length < boundary.length) {
+ return false;
+ }
+
+ // this is the most common situation
+ if (line.length == boundary.length) {
+ return Arrays.equals(line, boundary);
+ }
+ // the line might have linear white space after the boundary portions
+ for (int i = 0; i < boundary.length; i++) {
+ // fail on any mismatch
+ if (line[i] != boundary[i]) {
+ return false;
+ }
+ }
+ // everything after the boundary portion must be linear whitespace
+ for (int i = boundary.length; i < line.length; i++) {
+ // fail on any mismatch
+ if (line[i] != ' ' && line[i] != '\t') {
+ return false;
+ }
+ }
+ // these are equivalent
+ return true;
+ }
+
+ /**
+ * Read a single line of data from the input stream,
+ * returning it as an array of bytes.
+ *
+ * @param in The source input stream.
+ *
+ * @return A byte array containing the line data. Returns
+ * null if there's nothing left in the stream.
+ * @exception MessagingException
+ */
+ private byte[] readLine(BufferedInputStream in) throws IOException
+ {
+ ByteArrayOutputStream line = new ByteArrayOutputStream();
+
+ while (in.available() > 0) {
+ int value = in.read();
+ if (value == -1) {
+ // if we have nothing in the accumulator, signal an EOF back
+ if (line.size() == 0) {
+ return null;
+ }
+ break;
+ }
+ else if (value == '\r') {
+ in.mark(10);
+ value = in.read();
+ // we expect to find a linefeed after the carriage return, but
+ // some things play loose with the rules.
+ if (value != '\n') {
+ in.reset();
+ }
+ break;
+ }
+ else if (value == '\n') {
+ // naked linefeed, allow that
+ break;
+ }
+ else {
+ // write this to the line
+ line.write((byte)value);
+ }
+ }
+ // return this as an array of bytes
+ return line.toByteArray();
+ }
+
+
+ protected InternetHeaders createInternetHeaders(InputStream in) throws MessagingException {
+ return new InternetHeaders(in);
+ }
+
+ protected MimeBodyPart createMimeBodyPart(InternetHeaders headers, byte[] data) throws MessagingException {
+ return new MimeBodyPart(headers, data);
+ }
+
+ protected MimeBodyPart createMimeBodyPart(InputStream in) throws MessagingException {
+ return new MimeBodyPart(in);
+ }
+
+ // static used to track boudary value allocations to help ensure uniqueness.
+ private static int part;
+
+ private synchronized static String getBoundary() {
+ int i;
+ synchronized(MimeMultipart.class) {
+ i = part++;
+ }
+ StringBuffer buf = new StringBuffer(64);
+ buf.append("----=_Part_").append(i).append('_').append((new Object()).hashCode()).append('.').append(System.currentTimeMillis());
+ return buf.toString();
+ }
+
+ private class MimeBodyPartInputStream extends InputStream {
+ BufferedInputStream inStream;
+ public boolean boundaryFound = false;
+ byte[] boundary;
+ public boolean finalBoundaryFound = false;
+
+ public MimeBodyPartInputStream(BufferedInputStream inStream, byte[] boundary) {
+ super();
+ this.inStream = inStream;
+ this.boundary = boundary;
+ }
+
+ /**
+ * The base reading method for reading one character
+ * at a time.
+ *
+ * @return The read character, or -1 if an EOF was encountered.
+ * @exception IOException
+ */
+ public int read() throws IOException {
+ if (boundaryFound) {
+ return -1;
+ }
+
+ // read the next value from stream
+ int firstChar = inStream.read();
+ // premature end? Handle it like a boundary located
+ if (firstChar == -1) {
+ boundaryFound = true;
+ // also mark this as the end
+ finalBoundaryFound = true;
+ return -1;
+ }
+
+ // we first need to look for a line boundary. If we find a boundary, it can be followed by the
+ // boundary marker, so we need to remember what sort of thing we found, then read ahead looking
+ // for the part boundary.
+
+ // NB:, we only handle [\r]\n--boundary marker[--]
+ // we need to at least accept what most mail servers would consider an
+ // invalid format using just '\n'
+ if (firstChar != '\r' && firstChar != '\n') {
+ // not a \r, just return the byte as is
+ return firstChar;
+ }
+ // we might need to rewind to this point. The padding is to allow for
+ // line terminators and linear whitespace on the boundary lines
+ inStream.mark(boundary.length + 1000);
+ // we need to keep track of the first read character in case we need to
+ // rewind back to the mark point
+ int value = firstChar;
+ // if this is a '\r', then we require the '\n'
+ if (value == '\r') {
+ // now scan ahead for the second character
+ value = inStream.read();
+ if (value != '\n') {
+ // only a \r, so this can't be a boundary. Return the
+ // \r as if it was data, after first resetting
+ inStream.reset();
+ return '\r';
+ }
+ }
+
+ value = inStream.read();
+ // if the next character is not a boundary start, we
+ // need to handle this as a normal line end
+ if ((byte) value != boundary[0]) {
+ // just reset and return the first character as data
+ inStream.reset();
+ return firstChar;
+ }
+
+ // we're here because we found a "\r\n-" sequence, which is a potential
+ // boundary marker. Read the individual characters of the next line until
+ // we have a mismatch
+
+ // read value is the first byte of the boundary. Start matching the
+ // next characters to find a boundary
+ int boundaryIndex = 0;
+ while ((boundaryIndex < boundary.length) && ((byte) value == boundary[boundaryIndex])) {
+ value = inStream.read();
+ boundaryIndex++;
+ }
+ // if we didn't match all the way, we need to push back what we've read and
+ // return the EOL character
+ if (boundaryIndex != boundary.length) {
+ // Boundary not found. Restoring bytes skipped.
+ // just reset and return the first character as data
+ inStream.reset();
+ return firstChar;
+ }
+
+ // The full boundary sequence should be \r\n--boundary string[--]\r\n
+ // if the last character we read was a '-', check for the end terminator
+ if (value == '-') {
+ value = inStream.read();
+ // crud, we have a bad boundary terminator. We need to unwind this all the way
+ // back to the lineend and pretend none of this ever happened
+ if (value != '-') {
+ // Boundary not found. Restoring bytes skipped.
+ // just reset and return the first character as data
+ inStream.reset();
+ return firstChar;
+ }
+ // on the home stretch, but we need to verify the LWSP/EOL sequence
+ value = inStream.read();
+ // first skip over the linear whitespace
+ while (value == ' ' || value == '\t') {
+ value = inStream.read();
+ }
+
+ // We've matched the final boundary, skipped any whitespace, but
+ // we've hit the end of the stream. This is highly likely when
+ // we have nested multiparts, since the linend terminator for the
+ // final boundary marker is eated up as the start of the outer
+ // boundary marker. No CRLF sequence here is ok.
+ if (value == -1) {
+ // we've hit the end of times...
+ finalBoundaryFound = true;
+ // we have a boundary, so return this as an EOF condition
+ boundaryFound = true;
+ return -1;
+ }
+
+ // this must be a CR or a LF...which leaves us even more to push back and forget
+ if (value != '\r' && value != '\n') {
+ // Boundary not found. Restoring bytes skipped.
+ // just reset and return the first character as data
+ inStream.reset();
+ return firstChar;
+ }
+
+ // if this is carriage return, check for a linefeed
+ if (value == '\r') {
+ // last check, this must be a line feed
+ value = inStream.read();
+ if (value != '\n') {
+ // SO CLOSE!
+ // Boundary not found. Restoring bytes skipped.
+ // just reset and return the first character as data
+ inStream.reset();
+ return firstChar;
+ }
+ }
+
+ // we've hit the end of times...
+ finalBoundaryFound = true;
+ }
+ else {
+ // first skip over the linear whitespace
+ while (value == ' ' || value == '\t') {
+ value = inStream.read();
+ }
+ // this must be a CR or a LF...which leaves us even more to push back and forget
+ if (value != '\r' && value != '\n') {
+ // Boundary not found. Restoring bytes skipped.
+ // just reset and return the first character as data
+ inStream.reset();
+ return firstChar;
+ }
+
+ // if this is carriage return, check for a linefeed
+ if (value == '\r') {
+ // last check, this must be a line feed
+ value = inStream.read();
+ if (value != '\n') {
+ // SO CLOSE!
+ // Boundary not found. Restoring bytes skipped.
+ // just reset and return the first character as data
+ inStream.reset();
+ return firstChar;
+ }
+ }
+ }
+ // we have a boundary, so return this as an EOF condition
+ boundaryFound = true;
+ return -1;
+ }
+ }
+
+
+ /**
+ * Return true if the final boundary line for this multipart was
+ * seen when parsing the data.
+ *
+ * @return
+ * @exception MessagingException
+ */
+ public boolean isComplete() throws MessagingException {
+ // make sure we've parsed this
+ parse();
+ return complete;
+ }
+
+
+ /**
+ * Returns the preamble text that appears before the first bady
+ * part of a MIME multi part. The preamble is optional, so this
+ * might be null.
+ *
+ * @return The preamble text string.
+ * @exception MessagingException
+ */
+ public String getPreamble() throws MessagingException {
+ parse();
+ return preamble;
+ }
+
+ /**
+ * Set the message preamble text. This will be written before
+ * the first boundary of a multi-part message.
+ *
+ * @param preamble The new boundary text. This is complete lines of text, including
+ * new lines.
+ *
+ * @exception MessagingException
+ */
+ public void setPreamble(String preamble) throws MessagingException {
+ this.preamble = preamble;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/MimePart.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimePart.java
new file mode 100644
index 00000000..3eea101d
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimePart.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 javax.mail.internet;
+
+import java.util.Enumeration;
+import javax.mail.MessagingException;
+import javax.mail.Part;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public interface MimePart extends Part {
+ public abstract void addHeaderLine(String line) throws MessagingException;
+
+ public abstract Enumeration getAllHeaderLines() throws MessagingException;
+
+ public abstract String getContentID() throws MessagingException;
+
+ public abstract String[] getContentLanguage() throws MessagingException;
+
+ public abstract String getContentMD5() throws MessagingException;
+
+ public abstract String getEncoding() throws MessagingException;
+
+ public abstract String getHeader(String header, String delimiter)
+ throws MessagingException;
+
+ public abstract Enumeration getMatchingHeaderLines(String[] names)
+ throws MessagingException;
+
+ public abstract Enumeration getNonMatchingHeaderLines(String[] names)
+ throws MessagingException;
+
+ public abstract void setContentLanguage(String[] languages)
+ throws MessagingException;
+
+ public abstract void setContentMD5(String content)
+ throws MessagingException;
+
+ public abstract void setText(String text) throws MessagingException;
+
+ public abstract void setText(String text, String charset)
+ throws MessagingException;
+
+ public abstract void setText(String text, String charset, String subType)
+ throws MessagingException;
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/MimePartDataSource.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimePartDataSource.java
new file mode 100644
index 00000000..d549b88b
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimePartDataSource.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.UnknownServiceException;
+import javax.activation.DataSource;
+import javax.mail.MessageAware;
+import javax.mail.MessageContext;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 702432 $ $Date: 2008-10-07 06:18:08 -0500 (Tue, 07 Oct 2008) $
+ */
+public class MimePartDataSource implements DataSource, MessageAware {
+ // the part that provides the data form this data source.
+ protected MimePart part;
+
+ public MimePartDataSource(MimePart part) {
+ this.part = part;
+ }
+
+ public InputStream getInputStream() throws IOException {
+ try {
+ InputStream stream;
+ if (part instanceof MimeMessage) {
+ stream = ((MimeMessage) part).getContentStream();
+ } else if (part instanceof MimeBodyPart) {
+ stream = ((MimeBodyPart) part).getContentStream();
+ } else {
+ throw new MessagingException("Unknown part");
+ }
+ return checkPartEncoding(part, stream);
+ } catch (MessagingException e) {
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ }
+ }
+
+
+ /**
+ * For a given part, decide it the data stream requires
+ * wrappering with a stream for decoding a particular
+ * encoding.
+ *
+ * @param part The part we're extracting.
+ * @param stream The raw input stream for the part.
+ *
+ * @return An input stream configured for reading the
+ * source part and decoding it into raw bytes.
+ */
+ private InputStream checkPartEncoding(MimePart part, InputStream stream) throws MessagingException {
+ String encoding = part.getEncoding();
+ // if nothing is specified, there's nothing to do
+ if (encoding == null) {
+ return stream;
+ }
+ // now screen out the ones that never need decoding
+ encoding = encoding.toLowerCase();
+ if (encoding.equals("7bit") || encoding.equals("8bit") || encoding.equals("binary")) {
+ return stream;
+ }
+ // now we need to check the content type to prevent
+ // MultiPart types from getting decoded, since the part is just an envelope around other
+ // parts
+ String contentType = part.getContentType();
+ if (contentType != null) {
+ try {
+ ContentType type = new ContentType(contentType);
+ // no decoding done here
+ if (type.match("multipart/*")) {
+ return stream;
+ }
+ } catch (ParseException e) {
+ // ignored....bad content type means we handle as a normal part
+ }
+ }
+ // ok, wrap this is a decoding stream if required
+ return MimeUtility.decode(stream, encoding);
+ }
+
+
+ public OutputStream getOutputStream() throws IOException {
+ throw new UnknownServiceException();
+ }
+
+ public String getContentType() {
+ try {
+ return part.getContentType();
+ } catch (MessagingException e) {
+ return null;
+ }
+ }
+
+ public String getName() {
+ return "";
+ }
+
+ public synchronized MessageContext getMessageContext() {
+ return new MessageContext(part);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeUtility.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeUtility.java
new file mode 100644
index 00000000..797eeaa4
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/MimeUtility.java
@@ -0,0 +1,1386 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.mail.MessagingException;
+
+import org.apache.geronimo.mail.util.ASCIIUtil;
+import org.apache.geronimo.mail.util.Base64;
+import org.apache.geronimo.mail.util.Base64DecoderStream;
+import org.apache.geronimo.mail.util.Base64Encoder;
+import org.apache.geronimo.mail.util.Base64EncoderStream;
+import org.apache.geronimo.mail.util.QuotedPrintableDecoderStream;
+import org.apache.geronimo.mail.util.QuotedPrintableEncoderStream;
+import org.apache.geronimo.mail.util.QuotedPrintableEncoder;
+import org.apache.geronimo.mail.util.QuotedPrintable;
+import org.apache.geronimo.mail.util.SessionUtil;
+import org.apache.geronimo.mail.util.UUDecoderStream;
+import org.apache.geronimo.mail.util.UUEncoderStream;
+
+// encodings include "base64", "quoted-printable", "7bit", "8bit" and "binary".
+// In addition, "uuencode" is also supported. The
+
+/**
+ * @version $Rev: 627556 $ $Date: 2008-02-13 12:27:22 -0600 (Wed, 13 Feb 2008) $
+ */
+public class MimeUtility {
+
+ private static final String MIME_FOLDENCODEDWORDS = "mail.mime.foldencodedwords";
+ private static final String MIME_DECODE_TEXT_STRICT = "mail.mime.decodetext.strict";
+ private static final String MIME_FOLDTEXT = "mail.mime.foldtext";
+ private static final int FOLD_THRESHOLD = 76;
+
+ private MimeUtility() {
+ }
+
+ public static final int ALL = -1;
+
+ private static String defaultJavaCharset;
+ private static String escapedChars = "\"\\\r\n";
+ private static String linearWhiteSpace = " \t\r\n";
+
+ private static String QP_WORD_SPECIALS = "=_?\"#$%&'(),.:;<>@[\\]^`{|}~";
+ private static String QP_TEXT_SPECIALS = "=_?";
+
+ // the javamail spec includes the ability to map java encoding names to MIME-specified names. Normally,
+ // these values are loaded from a character mapping file.
+ private static Map java2mime;
+ private static Map mime2java;
+
+ static {
+ // we need to load the mapping tables used by javaCharset() and mimeCharset().
+ loadCharacterSetMappings();
+ }
+
+ public static InputStream decode(InputStream in, String encoding) throws MessagingException {
+ encoding = encoding.toLowerCase();
+
+ // some encodies are just pass-throughs, with no real decoding.
+ if (encoding.equals("binary") || encoding.equals("7bit") || encoding.equals("8bit")) {
+ return in;
+ }
+ else if (encoding.equals("base64")) {
+ return new Base64DecoderStream(in);
+ }
+ // UUEncode is known by a couple historical extension names too.
+ else if (encoding.equals("uuencode") || encoding.equals("x-uuencode") || encoding.equals("x-uue")) {
+ return new UUDecoderStream(in);
+ }
+ else if (encoding.equals("quoted-printable")) {
+ return new QuotedPrintableDecoderStream(in);
+ }
+ else {
+ throw new MessagingException("Unknown encoding " + encoding);
+ }
+ }
+
+ /**
+ * Decode a string of text obtained from a mail header into
+ * it's proper form. The text generally will consist of a
+ * string of tokens, some of which may be encoded using
+ * base64 encoding.
+ *
+ * @param text The text to decode.
+ *
+ * @return The decoded test string.
+ * @exception UnsupportedEncodingException
+ */
+ public static String decodeText(String text) throws UnsupportedEncodingException {
+ // if the text contains any encoded tokens, those tokens will be marked with "=?". If the
+ // source string doesn't contain that sequent, no decoding is required.
+ if (text.indexOf("=?") < 0) {
+ return text;
+ }
+
+ // we have two sets of rules we can apply.
+ if (!SessionUtil.getBooleanProperty(MIME_DECODE_TEXT_STRICT, true)) {
+ return decodeTextNonStrict(text);
+ }
+
+ int offset = 0;
+ int endOffset = text.length();
+
+ int startWhiteSpace = -1;
+ int endWhiteSpace = -1;
+
+ StringBuffer decodedText = new StringBuffer(text.length());
+
+ boolean previousTokenEncoded = false;
+
+ while (offset < endOffset) {
+ char ch = text.charAt(offset);
+
+ // is this a whitespace character?
+ if (linearWhiteSpace.indexOf(ch) != -1) {
+ startWhiteSpace = offset;
+ while (offset < endOffset) {
+ // step over the white space characters.
+ ch = text.charAt(offset);
+ if (linearWhiteSpace.indexOf(ch) != -1) {
+ offset++;
+ }
+ else {
+ // record the location of the first non lwsp and drop down to process the
+ // token characters.
+ endWhiteSpace = offset;
+ break;
+ }
+ }
+ }
+ else {
+ // we have a word token. We need to scan over the word and then try to parse it.
+ int wordStart = offset;
+
+ while (offset < endOffset) {
+ // step over the white space characters.
+ ch = text.charAt(offset);
+ if (linearWhiteSpace.indexOf(ch) == -1) {
+ offset++;
+ }
+ else {
+ break;
+ }
+
+ //NB: Trailing whitespace on these header strings will just be discarded.
+ }
+ // pull out the word token.
+ String word = text.substring(wordStart, offset);
+ // is the token encoded? decode the word
+ if (word.startsWith("=?")) {
+ try {
+ // if this gives a parsing failure, treat it like a non-encoded word.
+ String decodedWord = decodeWord(word);
+
+ // are any whitespace characters significant? Append 'em if we've got 'em.
+ if (!previousTokenEncoded) {
+ if (startWhiteSpace != -1) {
+ decodedText.append(text.substring(startWhiteSpace, endWhiteSpace));
+ startWhiteSpace = -1;
+ }
+ }
+ // this is definitely a decoded token.
+ previousTokenEncoded = true;
+ // and add this to the text.
+ decodedText.append(decodedWord);
+ // we continue parsing from here...we allow parsing errors to fall through
+ // and get handled as normal text.
+ continue;
+
+ } catch (ParseException e) {
+ }
+ }
+ // this is a normal token, so it doesn't matter what the previous token was. Add the white space
+ // if we have it.
+ if (startWhiteSpace != -1) {
+ decodedText.append(text.substring(startWhiteSpace, endWhiteSpace));
+ startWhiteSpace = -1;
+ }
+ // this is not a decoded token.
+ previousTokenEncoded = false;
+ decodedText.append(word);
+ }
+ }
+
+ return decodedText.toString();
+ }
+
+
+ /**
+ * Decode a string of text obtained from a mail header into
+ * it's proper form. The text generally will consist of a
+ * string of tokens, some of which may be encoded using
+ * base64 encoding. This is for non-strict decoded for mailers that
+ * violate the RFC 2047 restriction that decoded tokens must be delimited
+ * by linear white space. This will scan tokens looking for inner tokens
+ * enclosed in "=?" -- "?=" pairs.
+ *
+ * @param text The text to decode.
+ *
+ * @return The decoded test string.
+ * @exception UnsupportedEncodingException
+ */
+ private static String decodeTextNonStrict(String text) throws UnsupportedEncodingException {
+ int offset = 0;
+ int endOffset = text.length();
+
+ int startWhiteSpace = -1;
+ int endWhiteSpace = -1;
+
+ StringBuffer decodedText = new StringBuffer(text.length());
+
+ boolean previousTokenEncoded = false;
+
+ while (offset < endOffset) {
+ char ch = text.charAt(offset);
+
+ // is this a whitespace character?
+ if (linearWhiteSpace.indexOf(ch) != -1) {
+ startWhiteSpace = offset;
+ while (offset < endOffset) {
+ // step over the white space characters.
+ ch = text.charAt(offset);
+ if (linearWhiteSpace.indexOf(ch) != -1) {
+ offset++;
+ }
+ else {
+ // record the location of the first non lwsp and drop down to process the
+ // token characters.
+ endWhiteSpace = offset;
+ break;
+ }
+ }
+ }
+ else {
+ // we're at the start of a word token. We potentially need to break this up into subtokens
+ int wordStart = offset;
+
+ while (offset < endOffset) {
+ // step over the white space characters.
+ ch = text.charAt(offset);
+ if (linearWhiteSpace.indexOf(ch) == -1) {
+ offset++;
+ }
+ else {
+ break;
+ }
+
+ //NB: Trailing whitespace on these header strings will just be discarded.
+ }
+ // pull out the word token.
+ String word = text.substring(wordStart, offset);
+
+ int decodeStart = 0;
+
+ // now scan and process each of the bits within here.
+ while (decodeStart < word.length()) {
+ int tokenStart = word.indexOf("=?", decodeStart);
+ if (tokenStart == -1) {
+ // this is a normal token, so it doesn't matter what the previous token was. Add the white space
+ // if we have it.
+ if (startWhiteSpace != -1) {
+ decodedText.append(text.substring(startWhiteSpace, endWhiteSpace));
+ startWhiteSpace = -1;
+ }
+ // this is not a decoded token.
+ previousTokenEncoded = false;
+ decodedText.append(word.substring(decodeStart));
+ // we're finished.
+ break;
+ }
+ // we have something to process
+ else {
+ // we might have a normal token preceeding this.
+ if (tokenStart != decodeStart) {
+ // this is a normal token, so it doesn't matter what the previous token was. Add the white space
+ // if we have it.
+ if (startWhiteSpace != -1) {
+ decodedText.append(text.substring(startWhiteSpace, endWhiteSpace));
+ startWhiteSpace = -1;
+ }
+ // this is not a decoded token.
+ previousTokenEncoded = false;
+ decodedText.append(word.substring(decodeStart, tokenStart));
+ }
+
+ // now find the end marker.
+ int tokenEnd = word.indexOf("?=", tokenStart);
+ // sigh, an invalid token. Treat this as plain text.
+ if (tokenEnd == -1) {
+ // this is a normal token, so it doesn't matter what the previous token was. Add the white space
+ // if we have it.
+ if (startWhiteSpace != -1) {
+ decodedText.append(text.substring(startWhiteSpace, endWhiteSpace));
+ startWhiteSpace = -1;
+ }
+ // this is not a decoded token.
+ previousTokenEncoded = false;
+ decodedText.append(word.substring(tokenStart));
+ // we're finished.
+ break;
+ }
+ else {
+ // update our ticker
+ decodeStart = tokenEnd + 2;
+
+ String token = word.substring(tokenStart, tokenEnd);
+ try {
+ // if this gives a parsing failure, treat it like a non-encoded word.
+ String decodedWord = decodeWord(token);
+
+ // are any whitespace characters significant? Append 'em if we've got 'em.
+ if (!previousTokenEncoded) {
+ if (startWhiteSpace != -1) {
+ decodedText.append(text.substring(startWhiteSpace, endWhiteSpace));
+ startWhiteSpace = -1;
+ }
+ }
+ // this is definitely a decoded token.
+ previousTokenEncoded = true;
+ // and add this to the text.
+ decodedText.append(decodedWord);
+ // we continue parsing from here...we allow parsing errors to fall through
+ // and get handled as normal text.
+ continue;
+
+ } catch (ParseException e) {
+ }
+ // this is a normal token, so it doesn't matter what the previous token was. Add the white space
+ // if we have it.
+ if (startWhiteSpace != -1) {
+ decodedText.append(text.substring(startWhiteSpace, endWhiteSpace));
+ startWhiteSpace = -1;
+ }
+ // this is not a decoded token.
+ previousTokenEncoded = false;
+ decodedText.append(token);
+ }
+ }
+ }
+ }
+ }
+
+ return decodedText.toString();
+ }
+
+ /**
+ * Parse a string using the RFC 2047 rules for an "encoded-word"
+ * type. This encoding has the syntax:
+ *
+ * encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
+ *
+ * @param word The possibly encoded word value.
+ *
+ * @return The decoded word.
+ * @exception ParseException
+ * @exception UnsupportedEncodingException
+ */
+ public static String decodeWord(String word) throws ParseException, UnsupportedEncodingException {
+ // encoded words start with the characters "=?". If this not an encoded word, we throw a
+ // ParseException for the caller.
+
+ if (!word.startsWith("=?")) {
+ throw new ParseException("Invalid RFC 2047 encoded-word: " + word);
+ }
+
+ int charsetPos = word.indexOf('?', 2);
+ if (charsetPos == -1) {
+ throw new ParseException("Missing charset in RFC 2047 encoded-word: " + word);
+ }
+
+ // pull out the character set information (this is the MIME name at this point).
+ String charset = word.substring(2, charsetPos).toLowerCase();
+
+ // now pull out the encoding token the same way.
+ int encodingPos = word.indexOf('?', charsetPos + 1);
+ if (encodingPos == -1) {
+ throw new ParseException("Missing encoding in RFC 2047 encoded-word: " + word);
+ }
+
+ String encoding = word.substring(charsetPos + 1, encodingPos);
+
+ // and finally the encoded text.
+ int encodedTextPos = word.indexOf("?=", encodingPos + 1);
+ if (encodedTextPos == -1) {
+ throw new ParseException("Missing encoded text in RFC 2047 encoded-word: " + word);
+ }
+
+ String encodedText = word.substring(encodingPos + 1, encodedTextPos);
+
+ // seems a bit silly to encode a null string, but easy to deal with.
+ if (encodedText.length() == 0) {
+ return "";
+ }
+
+ try {
+ // the decoder writes directly to an output stream.
+ ByteArrayOutputStream out = new ByteArrayOutputStream(encodedText.length());
+
+ byte[] encodedData = encodedText.getBytes("US-ASCII");
+
+ // Base64 encoded?
+ if (encoding.equals("B")) {
+ Base64.decode(encodedData, out);
+ }
+ // maybe quoted printable.
+ else if (encoding.equals("Q")) {
+ QuotedPrintableEncoder dataEncoder = new QuotedPrintableEncoder();
+ dataEncoder.decodeWord(encodedData, out);
+ }
+ else {
+ throw new UnsupportedEncodingException("Unknown RFC 2047 encoding: " + encoding);
+ }
+ // get the decoded byte data and convert into a string.
+ byte[] decodedData = out.toByteArray();
+ return new String(decodedData, javaCharset(charset));
+ } catch (IOException e) {
+ throw new UnsupportedEncodingException("Invalid RFC 2047 encoding");
+ }
+
+ }
+
+ /**
+ * Wrap an encoder around a given output stream.
+ *
+ * @param out The output stream to wrap.
+ * @param encoding The name of the encoding.
+ *
+ * @return A instance of FilterOutputStream that manages on the fly
+ * encoding for the requested encoding type.
+ * @exception MessagingException
+ */
+ public static OutputStream encode(OutputStream out, String encoding) throws MessagingException {
+ // no encoding specified, so assume it goes out unchanged.
+ if (encoding == null) {
+ return out;
+ }
+
+ encoding = encoding.toLowerCase();
+
+ // some encodies are just pass-throughs, with no real decoding.
+ if (encoding.equals("binary") || encoding.equals("7bit") || encoding.equals("8bit")) {
+ return out;
+ }
+ else if (encoding.equals("base64")) {
+ return new Base64EncoderStream(out);
+ }
+ // UUEncode is known by a couple historical extension names too.
+ else if (encoding.equals("uuencode") || encoding.equals("x-uuencode") || encoding.equals("x-uue")) {
+ return new UUEncoderStream(out);
+ }
+ else if (encoding.equals("quoted-printable")) {
+ return new QuotedPrintableEncoderStream(out);
+ }
+ else {
+ throw new MessagingException("Unknown encoding " + encoding);
+ }
+ }
+
+ /**
+ * Wrap an encoder around a given output stream.
+ *
+ * @param out The output stream to wrap.
+ * @param encoding The name of the encoding.
+ * @param filename The filename of the data being sent (only used for UUEncode).
+ *
+ * @return A instance of FilterOutputStream that manages on the fly
+ * encoding for the requested encoding type.
+ * @exception MessagingException
+ */
+ public static OutputStream encode(OutputStream out, String encoding, String filename) throws MessagingException {
+ encoding = encoding.toLowerCase();
+
+ // some encodies are just pass-throughs, with no real decoding.
+ if (encoding.equals("binary") || encoding.equals("7bit") || encoding.equals("8bit")) {
+ return out;
+ }
+ else if (encoding.equals("base64")) {
+ return new Base64EncoderStream(out);
+ }
+ // UUEncode is known by a couple historical extension names too.
+ else if (encoding.equals("uuencode") || encoding.equals("x-uuencode") || encoding.equals("x-uue")) {
+ return new UUEncoderStream(out, filename);
+ }
+ else if (encoding.equals("quoted-printable")) {
+ return new QuotedPrintableEncoderStream(out);
+ }
+ else {
+ throw new MessagingException("Unknown encoding " + encoding);
+ }
+ }
+
+
+ public static String encodeText(String word) throws UnsupportedEncodingException {
+ return encodeText(word, null, null);
+ }
+
+ public static String encodeText(String word, String charset, String encoding) throws UnsupportedEncodingException {
+ return encodeWord(word, charset, encoding, false);
+ }
+
+ public static String encodeWord(String word) throws UnsupportedEncodingException {
+ return encodeWord(word, null, null);
+ }
+
+ public static String encodeWord(String word, String charset, String encoding) throws UnsupportedEncodingException {
+ return encodeWord(word, charset, encoding, true);
+ }
+
+
+ private static String encodeWord(String word, String charset, String encoding, boolean encodingWord) throws UnsupportedEncodingException {
+
+ // figure out what we need to encode this.
+ String encoder = ASCIIUtil.getTextTransferEncoding(word);
+ // all ascii? We can return this directly,
+ if (encoder.equals("7bit")) {
+ return word;
+ }
+
+ // if not given a charset, use the default.
+ if (charset == null) {
+ charset = getDefaultMIMECharset();
+ }
+
+ // sort out the encoder. If not explicitly given, use the best guess we've already established.
+ if (encoding != null) {
+ if (encoding.equalsIgnoreCase("B")) {
+ encoder = "base64";
+ }
+ else if (encoding.equalsIgnoreCase("Q")) {
+ encoder = "quoted-printable";
+ }
+ else {
+ throw new UnsupportedEncodingException("Unknown transfer encoding: " + encoding);
+ }
+ }
+
+ try {
+
+ // we'll format this directly into the string buffer
+ StringBuffer result = new StringBuffer();
+
+ // this is the maximum size of a segment of encoded data, which is based off
+ // of a 75 character size limit and all of the encoding overhead elements.
+ int sizeLimit = 75 - 7 - charset.length();
+
+ // now do the appropriate encoding work
+ if (encoder.equals("base64")) {
+ Base64Encoder dataEncoder = new Base64Encoder();
+ // this may recurse on the encoding if the string is too long. The left-most will not
+ // get a segment delimiter
+ encodeBase64(word, result, sizeLimit, charset, dataEncoder, true, SessionUtil.getBooleanProperty(MIME_FOLDENCODEDWORDS, false));
+ }
+ else {
+ QuotedPrintableEncoder dataEncoder = new QuotedPrintableEncoder();
+ encodeQuotedPrintable(word, result, sizeLimit, charset, dataEncoder, true,
+ SessionUtil.getBooleanProperty(MIME_FOLDENCODEDWORDS, false), encodingWord ? QP_WORD_SPECIALS : QP_TEXT_SPECIALS);
+ }
+ return result.toString();
+ } catch (IOException e) {
+ throw new UnsupportedEncodingException("Invalid encoding");
+ }
+ }
+
+
+ /**
+ * Encode a string into base64 encoding, taking into
+ * account the maximum segment length.
+ *
+ * @param data The string data to encode.
+ * @param out The output buffer used for the result.
+ * @param sizeLimit The maximum amount of encoded data we're allowed
+ * to have in a single encoded segment.
+ * @param charset The character set marker that needs to be added to the
+ * encoding header.
+ * @param encoder The encoder instance we're using.
+ * @param firstSegment
+ * If true, this is the first (left-most) segment in the
+ * data. Used to determine if segment delimiters need to
+ * be added between sections.
+ * @param foldSegments
+ * Indicates the type of delimiter to use (blank or newline sequence).
+ */
+ static private void encodeBase64(String data, StringBuffer out, int sizeLimit, String charset, Base64Encoder encoder, boolean firstSegment, boolean foldSegments) throws IOException
+ {
+ // this needs to be converted into the appropriate transfer encoding.
+ byte [] bytes = data.getBytes(javaCharset(charset));
+
+ int estimatedSize = encoder.estimateEncodedLength(bytes);
+
+ // if the estimated encoding size is over our segment limit, split the string in half and
+ // recurse. Eventually we'll reach a point where things are small enough.
+ if (estimatedSize > sizeLimit) {
+ // the first segment indicator travels with the left half.
+ encodeBase64(data.substring(0, data.length() / 2), out, sizeLimit, charset, encoder, firstSegment, foldSegments);
+ // the second half can never be the first segment
+ encodeBase64(data.substring(data.length() / 2), out, sizeLimit, charset, encoder, false, foldSegments);
+ }
+ else
+ {
+ // if this is not the first sement of the encoding, we need to add either a blank or
+ // a newline sequence to the data
+ if (!firstSegment) {
+ if (foldSegments) {
+ out.append("\r\n");
+ }
+ else {
+ out.append(' ');
+ }
+ }
+ // do the encoding of the segment.
+ encoder.encodeWord(bytes, out, charset);
+ }
+ }
+
+
+ /**
+ * Encode a string into quoted printable encoding, taking into
+ * account the maximum segment length.
+ *
+ * @param data The string data to encode.
+ * @param out The output buffer used for the result.
+ * @param sizeLimit The maximum amount of encoded data we're allowed
+ * to have in a single encoded segment.
+ * @param charset The character set marker that needs to be added to the
+ * encoding header.
+ * @param encoder The encoder instance we're using.
+ * @param firstSegment
+ * If true, this is the first (left-most) segment in the
+ * data. Used to determine if segment delimiters need to
+ * be added between sections.
+ * @param foldSegments
+ * Indicates the type of delimiter to use (blank or newline sequence).
+ */
+ static private void encodeQuotedPrintable(String data, StringBuffer out, int sizeLimit, String charset, QuotedPrintableEncoder encoder,
+ boolean firstSegment, boolean foldSegments, String specials) throws IOException
+ {
+ // this needs to be converted into the appropriate transfer encoding.
+ byte [] bytes = data.getBytes(javaCharset(charset));
+
+ int estimatedSize = encoder.estimateEncodedLength(bytes, specials);
+
+ // if the estimated encoding size is over our segment limit, split the string in half and
+ // recurse. Eventually we'll reach a point where things are small enough.
+ if (estimatedSize > sizeLimit) {
+ // the first segment indicator travels with the left half.
+ encodeQuotedPrintable(data.substring(0, data.length() / 2), out, sizeLimit, charset, encoder, firstSegment, foldSegments, specials);
+ // the second half can never be the first segment
+ encodeQuotedPrintable(data.substring(data.length() / 2), out, sizeLimit, charset, encoder, false, foldSegments, specials);
+ }
+ else
+ {
+ // if this is not the first sement of the encoding, we need to add either a blank or
+ // a newline sequence to the data
+ if (!firstSegment) {
+ if (foldSegments) {
+ out.append("\r\n");
+ }
+ else {
+ out.append(' ');
+ }
+ }
+ // do the encoding of the segment.
+ encoder.encodeWord(bytes, out, charset, specials);
+ }
+ }
+
+
+ /**
+ * Examine the content of a data source and decide what type
+ * of transfer encoding should be used. For text streams,
+ * we'll decided between 7bit, quoted-printable, and base64.
+ * For binary content types, we'll use either 7bit or base64.
+ *
+ * @param handler The DataHandler associated with the content.
+ *
+ * @return The string name of an encoding used to transfer the content.
+ */
+ public static String getEncoding(DataHandler handler) {
+
+
+ // if this handler has an associated data source, we can read directly from the
+ // data source to make this judgment. This is generally MUCH faster than asking the
+ // DataHandler to write out the data for us.
+ DataSource ds = handler.getDataSource();
+ if (ds != null) {
+ return getEncoding(ds);
+ }
+
+ try {
+ // get a parser that allows us to make comparisons.
+ ContentType content = new ContentType(handler.getContentType());
+
+ // The only access to the content bytes at this point is by asking the handler to write
+ // the information out to a stream. We're going to pipe this through a special stream
+ // that examines the bytes as they go by.
+ ContentCheckingOutputStream checker = new ContentCheckingOutputStream();
+
+ handler.writeTo(checker);
+
+ // figure this out based on whether we believe this to be a text type or not.
+ if (content.match("text/*")) {
+ return checker.getTextTransferEncoding();
+ }
+ else {
+ return checker.getBinaryTransferEncoding();
+ }
+
+ } catch (Exception e) {
+ // any unexpected I/O exceptions we'll force to a "safe" fallback position.
+ return "base64";
+ }
+ }
+
+
+ /**
+ * Determine the what transfer encoding should be used for
+ * data retrieved from a DataSource.
+ *
+ * @param source The DataSource for the transmitted data.
+ *
+ * @return The string name of the encoding form that should be used for
+ * the data.
+ */
+ public static String getEncoding(DataSource source) {
+ InputStream in = null;
+
+ try {
+ // get a parser that allows us to make comparisons.
+ ContentType content = new ContentType(source.getContentType());
+
+ // we're probably going to have to scan the data.
+ in = source.getInputStream();
+
+ if (!content.match("text/*")) {
+ // Not purporting to be a text type? Examine the content to see we might be able to
+ // at least pretend it is an ascii type.
+ return ASCIIUtil.getBinaryTransferEncoding(in);
+ }
+ else {
+ return ASCIIUtil.getTextTransferEncoding(in);
+ }
+ } catch (Exception e) {
+ // this was a problem...not sure what makes sense here, so we'll assume it's binary
+ // and we need to transfer this using Base64 encoding.
+ return "base64";
+ } finally {
+ // make sure we close the stream
+ try {
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException e) {
+ }
+ }
+ }
+
+
+ /**
+ * Quote a "word" value. If the word contains any character from
+ * the specified "specials" list, this value is returned as a
+ * quoted strong. Otherwise, it is returned unchanged (an "atom").
+ *
+ * @param word The word requiring quoting.
+ * @param specials The set of special characters that can't appear in an unquoted
+ * string.
+ *
+ * @return The quoted value. This will be unchanged if the word doesn't contain
+ * any of the designated special characters.
+ */
+ public static String quote(String word, String specials) {
+ int wordLength = word.length();
+ boolean requiresQuoting = false;
+ // scan the string looking for problem characters
+ for (int i =0; i < wordLength; i++) {
+ char ch = word.charAt(i);
+ // special escaped characters require escaping, which also implies quoting.
+ if (escapedChars.indexOf(ch) >= 0) {
+ return quoteAndEscapeString(word);
+ }
+ // now check for control characters or the designated special characters.
+ if (ch < 32 || ch >= 127 || specials.indexOf(ch) >= 0) {
+ // we know this requires quoting, but we still need to scan the entire string to
+ // see if contains chars that require escaping. Just go ahead and treat it as if it does.
+ return quoteAndEscapeString(word);
+ }
+ }
+ return word;
+ }
+
+ /**
+ * Take a string and return it as a formatted quoted string, with
+ * all characters requiring escaping handled properly.
+ *
+ * @param word The string to quote.
+ *
+ * @return The quoted string.
+ */
+ private static String quoteAndEscapeString(String word) {
+ int wordLength = word.length();
+ // allocate at least enough for the string and two quotes plus a reasonable number of escaped chars.
+ StringBuffer buffer = new StringBuffer(wordLength + 10);
+ // add the leading quote.
+ buffer.append('"');
+
+ for (int i = 0; i < wordLength; i++) {
+ char ch = word.charAt(i);
+ // is this an escaped char?
+ if (escapedChars.indexOf(ch) >= 0) {
+ // add the escape marker before appending.
+ buffer.append('\\');
+ }
+ buffer.append(ch);
+ }
+ // now the closing quote
+ buffer.append('"');
+ return buffer.toString();
+ }
+
+ /**
+ * Translate a MIME standard character set name into the Java
+ * equivalent.
+ *
+ * @param charset The MIME standard name.
+ *
+ * @return The Java equivalent for this name.
+ */
+ public static String javaCharset(String charset) {
+ // nothing in, nothing out.
+ if (charset == null) {
+ return null;
+ }
+
+ String mappedCharset = (String)mime2java.get(charset.toLowerCase());
+ // if there is no mapping, then the original name is used. Many of the MIME character set
+ // names map directly back into Java. The reverse isn't necessarily true.
+ return mappedCharset == null ? charset : mappedCharset;
+ }
+
+ /**
+ * Map a Java character set name into the MIME equivalent.
+ *
+ * @param charset The java character set name.
+ *
+ * @return The MIME standard equivalent for this character set name.
+ */
+ public static String mimeCharset(String charset) {
+ // nothing in, nothing out.
+ if (charset == null) {
+ return null;
+ }
+
+ String mappedCharset = (String)java2mime.get(charset.toLowerCase());
+ // if there is no mapping, then the original name is used. Many of the MIME character set
+ // names map directly back into Java. The reverse isn't necessarily true.
+ return mappedCharset == null ? charset : mappedCharset;
+ }
+
+
+ /**
+ * Get the default character set to use, in Java name format.
+ * This either be the value set with the mail.mime.charset
+ * system property or obtained from the file.encoding system
+ * property. If neither of these is set, we fall back to
+ * 8859_1 (basically US-ASCII).
+ *
+ * @return The character string value of the default character set.
+ */
+ public static String getDefaultJavaCharset() {
+ String charset = SessionUtil.getProperty("mail.mime.charset");
+ if (charset != null) {
+ return javaCharset(charset);
+ }
+ return SessionUtil.getProperty("file.encoding", "8859_1");
+ }
+
+ /**
+ * Get the default character set to use, in MIME name format.
+ * This either be the value set with the mail.mime.charset
+ * system property or obtained from the file.encoding system
+ * property. If neither of these is set, we fall back to
+ * 8859_1 (basically US-ASCII).
+ *
+ * @return The character string value of the default character set.
+ */
+ static String getDefaultMIMECharset() {
+ // if the property is specified, this can be used directly.
+ String charset = SessionUtil.getProperty("mail.mime.charset");
+ if (charset != null) {
+ return charset;
+ }
+
+ // get the Java-defined default and map back to a MIME name.
+ return mimeCharset(SessionUtil.getProperty("file.encoding", "8859_1"));
+ }
+
+
+ /**
+ * Load the default mapping tables used by the javaCharset()
+ * and mimeCharset() methods. By default, these tables are
+ * loaded from the /META-INF/javamail.charset.map file. If
+ * something goes wrong loading that file, we configure things
+ * with a default mapping table (which just happens to mimic
+ * what's in the default mapping file).
+ */
+ static private void loadCharacterSetMappings() {
+ java2mime = new HashMap();
+ mime2java = new HashMap();
+
+
+ // normally, these come from a character map file contained in the jar file.
+ try {
+ InputStream map = javax.mail.internet.MimeUtility.class.getResourceAsStream("/META-INF/javamail.charset.map");
+
+ if (map != null) {
+ // get a reader for this so we can load.
+ BufferedReader reader = new BufferedReader(new InputStreamReader(map));
+
+ readMappings(reader, java2mime);
+ readMappings(reader, mime2java);
+ }
+ } catch (Exception e) {
+ }
+
+ // if any sort of error occurred reading the preferred file version, we could end up with empty
+ // mapping tables. This could cause all sorts of difficulty, so ensure they are populated with at
+ // least a reasonable set of defaults.
+
+ // these mappings echo what's in the default file.
+ if (java2mime.isEmpty()) {
+ java2mime.put("8859_1", "ISO-8859-1");
+ java2mime.put("iso8859_1", "ISO-8859-1");
+ java2mime.put("iso8859-1", "ISO-8859-1");
+
+ java2mime.put("8859_2", "ISO-8859-2");
+ java2mime.put("iso8859_2", "ISO-8859-2");
+ java2mime.put("iso8859-2", "ISO-8859-2");
+
+ java2mime.put("8859_3", "ISO-8859-3");
+ java2mime.put("iso8859_3", "ISO-8859-3");
+ java2mime.put("iso8859-3", "ISO-8859-3");
+
+ java2mime.put("8859_4", "ISO-8859-4");
+ java2mime.put("iso8859_4", "ISO-8859-4");
+ java2mime.put("iso8859-4", "ISO-8859-4");
+
+ java2mime.put("8859_5", "ISO-8859-5");
+ java2mime.put("iso8859_5", "ISO-8859-5");
+ java2mime.put("iso8859-5", "ISO-8859-5");
+
+ java2mime.put ("8859_6", "ISO-8859-6");
+ java2mime.put("iso8859_6", "ISO-8859-6");
+ java2mime.put("iso8859-6", "ISO-8859-6");
+
+ java2mime.put("8859_7", "ISO-8859-7");
+ java2mime.put("iso8859_7", "ISO-8859-7");
+ java2mime.put("iso8859-7", "ISO-8859-7");
+
+ java2mime.put("8859_8", "ISO-8859-8");
+ java2mime.put("iso8859_8", "ISO-8859-8");
+ java2mime.put("iso8859-8", "ISO-8859-8");
+
+ java2mime.put("8859_9", "ISO-8859-9");
+ java2mime.put("iso8859_9", "ISO-8859-9");
+ java2mime.put("iso8859-9", "ISO-8859-9");
+
+ java2mime.put("sjis", "Shift_JIS");
+ java2mime.put ("jis", "ISO-2022-JP");
+ java2mime.put("iso2022jp", "ISO-2022-JP");
+ java2mime.put("euc_jp", "euc-jp");
+ java2mime.put("koi8_r", "koi8-r");
+ java2mime.put("euc_cn", "euc-cn");
+ java2mime.put("euc_tw", "euc-tw");
+ java2mime.put("euc_kr", "euc-kr");
+ }
+
+ if (mime2java.isEmpty ()) {
+ mime2java.put("iso-2022-cn", "ISO2022CN");
+ mime2java.put("iso-2022-kr", "ISO2022KR");
+ mime2java.put("utf-8", "UTF8");
+ mime2java.put("utf8", "UTF8");
+ mime2java.put("ja_jp.iso2022-7", "ISO2022JP");
+ mime2java.put("ja_jp.eucjp", "EUCJIS");
+ mime2java.put ("euc-kr", "KSC5601");
+ mime2java.put("euckr", "KSC5601");
+ mime2java.put("us-ascii", "ISO-8859-1");
+ mime2java.put("x-us-ascii", "ISO-8859-1");
+ }
+ }
+
+
+ /**
+ * Read a section of a character map table and populate the
+ * target mapping table with the information. The table end
+ * is marked by a line starting with "--" and also ending with
+ * "--". Blank lines and comment lines (beginning with '#') are
+ * ignored.
+ *
+ * @param reader The source of the file information.
+ * @param table The mapping table used to store the information.
+ */
+ static private void readMappings(BufferedReader reader, Map table) throws IOException {
+ // process lines to the EOF or the end of table marker.
+ while (true) {
+ String line = reader.readLine();
+ // no line returned is an EOF
+ if (line == null) {
+ return;
+ }
+
+ // trim so we're not messed up by trailing blanks
+ line = line.trim();
+
+ if (line.length() == 0 || line.startsWith("#")) {
+ continue;
+ }
+
+ // stop processing if this is the end-of-table marker.
+ if (line.startsWith("--") && line.endsWith("--")) {
+ return;
+ }
+
+ // we allow either blanks or tabs as token delimiters.
+ StringTokenizer tokenizer = new StringTokenizer(line, " \t");
+
+ try {
+ String from = tokenizer.nextToken().toLowerCase();
+ String to = tokenizer.nextToken();
+
+ table.put(from, to);
+ } catch (NoSuchElementException e) {
+ // just ignore the line if invalid.
+ }
+ }
+ }
+
+
+ /**
+ * Perform RFC 2047 text folding on a string of text.
+ *
+ * @param used The amount of text already "used up" on this line. This is
+ * typically the length of a message header that this text
+ * get getting added to.
+ * @param s The text to fold.
+ *
+ * @return The input text, with linebreaks inserted at appropriate fold points.
+ */
+ public static String fold(int used, String s) {
+ // if folding is disable, unfolding is also. Return the string unchanged.
+ if (!SessionUtil.getBooleanProperty(MIME_FOLDTEXT, true)) {
+ return s;
+ }
+
+ int end;
+
+ // now we need to strip off any trailing "whitespace", where whitespace is blanks, tabs,
+ // and line break characters.
+ for (end = s.length() - 1; end >= 0; end--) {
+ int ch = s.charAt(end);
+ if (ch != ' ' && ch != '\t' ) {
+ break;
+ }
+ }
+
+ // did we actually find something to remove? Shorten the String to the trimmed length
+ if (end != s.length() - 1) {
+ s = s.substring(0, end + 1);
+ }
+
+ // does the string as it exists now not require folding? We can just had that back right off.
+ if (s.length() + used <= FOLD_THRESHOLD) {
+ return s;
+ }
+
+ // get a buffer for the length of the string, plus room for a few line breaks.
+ // these are soft line breaks, so we generally need more that just the line breaks (an escape +
+ // CR + LF + leading space on next line);
+ StringBuffer newString = new StringBuffer(s.length() + 8);
+
+
+ // now keep chopping this down until we've accomplished what we need.
+ while (used + s.length() > FOLD_THRESHOLD) {
+ int breakPoint = -1;
+ char breakChar = 0;
+
+ // now scan for the next place where we can break.
+ for (int i = 0; i < s.length(); i++) {
+ // have we passed the fold limit?
+ if (used + i > FOLD_THRESHOLD) {
+ // if we've already seen a blank, then stop now. Otherwise
+ // we keep going until we hit a fold point.
+ if (breakPoint != -1) {
+ break;
+ }
+ }
+ char ch = s.charAt(i);
+
+ // a white space character?
+ if (ch == ' ' || ch == '\t') {
+ // this might be a run of white space, so skip over those now.
+ breakPoint = i;
+ // we need to maintain the same character type after the inserted linebreak.
+ breakChar = ch;
+ i++;
+ while (i < s.length()) {
+ ch = s.charAt(i);
+ if (ch != ' ' && ch != '\t') {
+ break;
+ }
+ i++;
+ }
+ }
+ // found an embedded new line. Escape this so that the unfolding process preserves it.
+ else if (ch == '\n') {
+ newString.append('\\');
+ newString.append('\n');
+ }
+ else if (ch == '\r') {
+ newString.append('\\');
+ newString.append('\n');
+ i++;
+ // if this is a CRLF pair, add the second char also
+ if (i < s.length() && s.charAt(i) == '\n') {
+ newString.append('\r');
+ }
+ }
+
+ }
+ // no fold point found, we punt, append the remainder and leave.
+ if (breakPoint == -1) {
+ newString.append(s);
+ return newString.toString();
+ }
+ newString.append(s.substring(0, breakPoint));
+ newString.append("\r\n");
+ newString.append(breakChar);
+ // chop the string
+ s = s.substring(breakPoint + 1);
+ // start again, and we've used the first char of the limit already with the whitespace char.
+ used = 1;
+ }
+
+ // add on the remainder, and return
+ newString.append(s);
+ return newString.toString();
+ }
+
+ /**
+ * Unfold a folded string. The unfolding process will remove
+ * any line breaks that are not escaped and which are also followed
+ * by whitespace characters.
+ *
+ * @param s The folded string.
+ *
+ * @return A new string with unfolding rules applied.
+ */
+ public static String unfold(String s) {
+ // if folding is disable, unfolding is also. Return the string unchanged.
+ if (!SessionUtil.getBooleanProperty(MIME_FOLDTEXT, true)) {
+ return s;
+ }
+
+ // if there are no line break characters in the string, we can just return this.
+ if (s.indexOf('\n') < 0 && s.indexOf('\r') < 0) {
+ return s;
+ }
+
+ // we need to scan and fix things up.
+ int length = s.length();
+
+ StringBuffer newString = new StringBuffer(length);
+
+ // scan the entire string
+ for (int i = 0; i < length; i++) {
+ char ch = s.charAt(i);
+
+ // we have a backslash. In folded strings, escape characters are only processed as such if
+ // they preceed line breaks. Otherwise, we leave it be.
+ if (ch == '\\') {
+ // escape at the very end? Just add the character.
+ if (i == length - 1) {
+ newString.append(ch);
+ }
+ else {
+ int nextChar = s.charAt(i + 1);
+
+ // naked newline? Add the new line to the buffer, and skip the escape char.
+ if (nextChar == '\n') {
+ newString.append('\n');
+ i++;
+ }
+ else if (nextChar == '\r') {
+ // just the CR left? Add it, removing the escape.
+ if (i == length - 2 || s.charAt(i + 2) != '\r') {
+ newString.append('\r');
+ i++;
+ }
+ else {
+ // toss the escape, add both parts of the CRLF, and skip over two chars.
+ newString.append('\r');
+ newString.append('\n');
+ i += 2;
+ }
+ }
+ else {
+ // an escape for another purpose, just copy it over.
+ newString.append(ch);
+ }
+ }
+ }
+ // we have an unescaped line break
+ else if (ch == '\n' || ch == '\r') {
+ // remember the position in case we need to backtrack.
+ int lineBreak = i;
+ boolean CRLF = false;
+
+ if (ch == '\r') {
+ // check to see if we need to step over this.
+ if (i < length - 1 && s.charAt(i + 1) == '\n') {
+ i++;
+ // flag the type so we know what we might need to preserve.
+ CRLF = true;
+ }
+ }
+
+ // get a temp position scanner.
+ int scan = i + 1;
+
+ // does a blank follow this new line? we need to scrap the new line and reduce the leading blanks
+ // down to a single blank.
+ if (scan < length && s.charAt(scan) == ' ') {
+ // add the character
+ newString.append(' ');
+
+ // scan over the rest of the blanks
+ i = scan + 1;
+ while (i < length && s.charAt(i) == ' ') {
+ i++;
+ }
+ // we'll increment down below, so back up to the last blank as the current char.
+ i--;
+ }
+ else {
+ // we must keep this line break. Append the appropriate style.
+ if (CRLF) {
+ newString.append("\r\n");
+ }
+ else {
+ newString.append(ch);
+ }
+ }
+ }
+ else {
+ // just a normal, ordinary character
+ newString.append(ch);
+ }
+ }
+ return newString.toString();
+ }
+}
+
+
+/**
+ * Utility class for examining content information written out
+ * by a DataHandler object. This stream gathers statistics on
+ * the stream so it can make transfer encoding determinations.
+ */
+class ContentCheckingOutputStream extends OutputStream {
+ private int asciiChars = 0;
+ private int nonAsciiChars = 0;
+ private boolean containsLongLines = false;
+ private boolean containsMalformedEOL = false;
+ private int previousChar = 0;
+ private int span = 0;
+
+ ContentCheckingOutputStream() {
+ }
+
+ public void write(byte[] data) throws IOException {
+ write(data, 0, data.length);
+ }
+
+ public void write(byte[] data, int offset, int length) throws IOException {
+ for (int i = 0; i < length; i++) {
+ write(data[offset + i]);
+ }
+ }
+
+ public void write(int ch) {
+ // we found a linebreak. Reset the line length counters on either one. We don't
+ // really need to validate here.
+ if (ch == '\n' || ch == '\r') {
+ // we found a newline, this is only valid if the previous char was the '\r'
+ if (ch == '\n') {
+ // malformed linebreak? force this to base64 encoding.
+ if (previousChar != '\r') {
+ containsMalformedEOL = true;
+ }
+ }
+ // hit a line end, reset our line length counter
+ span = 0;
+ }
+ else {
+ span++;
+ // the text has long lines, we can't transfer this as unencoded text.
+ if (span > 998) {
+ containsLongLines = true;
+ }
+
+ // non-ascii character, we have to transfer this in binary.
+ if (!ASCIIUtil.isAscii(ch)) {
+ nonAsciiChars++;
+ }
+ else {
+ asciiChars++;
+ }
+ }
+ previousChar = ch;
+ }
+
+
+ public String getBinaryTransferEncoding() {
+ if (nonAsciiChars != 0 || containsLongLines || containsMalformedEOL) {
+ return "base64";
+ }
+ else {
+ return "7bit";
+ }
+ }
+
+ public String getTextTransferEncoding() {
+ // looking good so far, only valid chars here.
+ if (nonAsciiChars == 0) {
+ // does this contain long text lines? We need to use a Q-P encoding which will
+ // be only slightly longer, but handles folding the longer lines.
+ if (containsLongLines) {
+ return "quoted-printable";
+ }
+ else {
+ // ideal! Easiest one to handle.
+ return "7bit";
+ }
+ }
+ else {
+ // mostly characters requiring encoding? Base64 is our best bet.
+ if (nonAsciiChars > asciiChars) {
+ return "base64";
+ }
+ else {
+ // Q-P encoding will use fewer bytes than the full Base64.
+ return "quoted-printable";
+ }
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/NewsAddress.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/NewsAddress.java
new file mode 100644
index 00000000..e20947c1
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/NewsAddress.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.mail.Address;
+
+// Not used. import sun.security.provider.Sun;
+
+/**
+ * A representation of an RFC1036 Internet newsgroup address.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class NewsAddress extends Address {
+ /**
+ * The host for this newsgroup
+ */
+ protected String host;
+
+ /**
+ * The name of this newsgroup
+ */
+ protected String newsgroup;
+
+ public NewsAddress() {
+ }
+
+ public NewsAddress(String newsgroup) {
+ this.newsgroup = newsgroup;
+ }
+
+ public NewsAddress(String newsgroup, String host) {
+ this.newsgroup = newsgroup;
+ this.host = host;
+ }
+
+ /**
+ * The type of this address; always "news".
+ * @return "news"
+ */
+ public String getType() {
+ return "news";
+ }
+
+ public void setNewsgroup(String newsgroup) {
+ this.newsgroup = newsgroup;
+ }
+
+ public String getNewsgroup() {
+ return newsgroup;
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public String toString() {
+ // Sun impl only appears to return the newsgroup name, no host.
+ return newsgroup;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NewsAddress)) return false;
+
+ final NewsAddress newsAddress = (NewsAddress) o;
+
+ if (host != null ? !host.equals(newsAddress.host) : newsAddress.host != null) return false;
+ if (newsgroup != null ? !newsgroup.equals(newsAddress.newsgroup) : newsAddress.newsgroup != null) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = (host != null ? host.toLowerCase().hashCode() : 0);
+ result = 29 * result + (newsgroup != null ? newsgroup.hashCode() : 0);
+ return result;
+ }
+
+ /**
+ * Parse a comma-spearated list of addresses.
+ *
+ * @param addresses the list to parse
+ * @return the array of extracted addresses
+ * @throws AddressException if one of the addresses is invalid
+ */
+ public static NewsAddress[] parse(String addresses) throws AddressException {
+ List result = new ArrayList();
+ StringTokenizer tokenizer = new StringTokenizer(addresses, ",");
+ while (tokenizer.hasMoreTokens()) {
+ String address = tokenizer.nextToken().trim();
+ int index = address.indexOf('@');
+ if (index == -1) {
+ result.add(new NewsAddress(address));
+ } else {
+ String newsgroup = address.substring(0, index).trim();
+ String host = address.substring(index+1).trim();
+ result.add(new NewsAddress(newsgroup, host));
+ }
+ }
+ return (NewsAddress[]) result.toArray(new NewsAddress[result.size()]);
+ }
+
+ /**
+ * Convert the supplied addresses to a comma-separated String.
+ * If addresses is null, returns null; if empty, returns an empty string.
+ *
+ * @param addresses the addresses to convert
+ * @return a comma-separated list of addresses
+ */
+ public static String toString(Address[] addresses) {
+ if (addresses == null) {
+ return null;
+ }
+ if (addresses.length == 0) {
+ return "";
+ }
+
+ StringBuffer result = new StringBuffer(addresses.length * 32);
+ result.append(addresses[0]);
+ for (int i = 1; i < addresses.length; i++) {
+ result.append(',').append(addresses[i].toString());
+ }
+ return result.toString();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/ParameterList.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/ParameterList.java
new file mode 100644
index 00000000..b9237d77
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/ParameterList.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;// Represents lists in things like
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.geronimo.mail.util.ASCIIUtil;
+import org.apache.geronimo.mail.util.RFC2231Encoder;
+import org.apache.geronimo.mail.util.SessionUtil;
+
+// Content-Type: text/plain;charset=klingon
+//
+// The ;charset=klingon is the parameter list, may have more of them with ';'
+
+/**
+ * @version $Rev: 669445 $ $Date: 2008-06-19 05:48:18 -0500 (Thu, 19 Jun 2008) $
+ */
+public class ParameterList {
+ private static final String MIME_ENCODEPARAMETERS = "mail.mime.encodeparameters";
+ private static final String MIME_DECODEPARAMETERS = "mail.mime.decodeparameters";
+ private static final String MIME_DECODEPARAMETERS_STRICT = "mail.mime.decodeparameters.strict";
+ private static final String MIME_FOLDTEXT = "mail.mime.foldtext";
+
+ private static final int HEADER_SIZE_LIMIT = 76;
+
+ private Map _parameters = new HashMap();
+
+ private boolean encodeParameters = false;
+ private boolean decodeParameters = false;
+ private boolean decodeParametersStrict = false;
+ private boolean foldText = true;
+
+ public ParameterList() {
+ // figure out how parameter handling is to be performed.
+ getInitialProperties();
+ }
+
+ public ParameterList(String list) throws ParseException {
+ // figure out how parameter handling is to be performed.
+ getInitialProperties();
+ // get a token parser for the type information
+ HeaderTokenizer tokenizer = new HeaderTokenizer(list, HeaderTokenizer.MIME);
+ while (true) {
+ HeaderTokenizer.Token token = tokenizer.next();
+
+ switch (token.getType()) {
+ // the EOF token terminates parsing.
+ case HeaderTokenizer.Token.EOF:
+ return;
+
+ // each new parameter is separated by a semicolon, including the first, which separates
+ // the parameters from the main part of the header.
+ case ';':
+ // the next token needs to be a parameter name
+ token = tokenizer.next();
+ // allow a trailing semicolon on the parameters.
+ if (token.getType() == HeaderTokenizer.Token.EOF) {
+ return;
+ }
+
+ if (token.getType() != HeaderTokenizer.Token.ATOM) {
+ throw new ParseException("Invalid parameter name: " + token.getValue());
+ }
+
+ // get the parameter name as a lower case version for better mapping.
+ String name = token.getValue().toLowerCase();
+
+ token = tokenizer.next();
+
+ // parameters are name=value, so we must have the "=" here.
+ if (token.getType() != '=') {
+ throw new ParseException("Missing '='");
+ }
+
+ // now the value, which may be an atom or a literal
+ token = tokenizer.next();
+
+ if (token.getType() != HeaderTokenizer.Token.ATOM && token.getType() != HeaderTokenizer.Token.QUOTEDSTRING) {
+ throw new ParseException("Invalid parameter value: " + token.getValue());
+ }
+
+ String value = token.getValue();
+ String decodedValue = null;
+
+ // we might have to do some additional decoding. A name that ends with "*"
+ // is marked as being encoded, so if requested, we decode the value.
+ if (decodeParameters && name.endsWith("*")) {
+ // the name needs to be pruned of the marker, and we need to decode the value.
+ name = name.substring(0, name.length() - 1);
+ // get a new decoder
+ RFC2231Encoder decoder = new RFC2231Encoder(HeaderTokenizer.MIME);
+
+ try {
+ // decode the value
+ decodedValue = decoder.decode(value);
+ } catch (Exception e) {
+ // if we're doing things strictly, then raise a parsing exception for errors.
+ // otherwise, leave the value in its current state.
+ if (decodeParametersStrict) {
+ throw new ParseException("Invalid RFC2231 encoded parameter");
+ }
+ }
+ _parameters.put(name, new ParameterValue(name, decodedValue, value));
+ }
+ else {
+ _parameters.put(name, new ParameterValue(name, value));
+ }
+
+ break;
+
+ default:
+ throw new ParseException("Missing ';'");
+
+ }
+ }
+ }
+
+ /**
+ * Get the initial parameters that control parsing and values.
+ * These parameters are controlled by System properties.
+ */
+ private void getInitialProperties() {
+ decodeParameters = SessionUtil.getBooleanProperty(MIME_DECODEPARAMETERS, false);
+ decodeParametersStrict = SessionUtil.getBooleanProperty(MIME_DECODEPARAMETERS_STRICT, false);
+ encodeParameters = SessionUtil.getBooleanProperty(MIME_ENCODEPARAMETERS, true);
+ foldText = SessionUtil.getBooleanProperty(MIME_FOLDTEXT, true);
+ }
+
+ public int size() {
+ return _parameters.size();
+ }
+
+ public String get(String name) {
+ ParameterValue value = (ParameterValue)_parameters.get(name.toLowerCase());
+ if (value != null) {
+ return value.value;
+ }
+ return null;
+ }
+
+ public void set(String name, String value) {
+ name = name.toLowerCase();
+ _parameters.put(name, new ParameterValue(name, value));
+ }
+
+ public void set(String name, String value, String charset) {
+ name = name.toLowerCase();
+ // only encode if told to and this contains non-ASCII charactes.
+ if (encodeParameters && !ASCIIUtil.isAscii(value)) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ try {
+ RFC2231Encoder encoder = new RFC2231Encoder(HeaderTokenizer.MIME);
+
+ // extract the bytes using the given character set and encode
+ byte[] valueBytes = value.getBytes(MimeUtility.javaCharset(charset));
+
+ // the string format is charset''data
+ out.write(charset.getBytes());
+ out.write('\'');
+ out.write('\'');
+ encoder.encode(valueBytes, 0, valueBytes.length, out);
+
+ // default in case there is an exception
+ _parameters.put(name, new ParameterValue(name, value, new String(out.toByteArray())));
+ return;
+
+ } catch (Exception e) {
+ // just fall through and set the value directly if there is an error
+ }
+ }
+ // default in case there is an exception
+ _parameters.put(name, new ParameterValue(name, value));
+ }
+
+ public void remove(String name) {
+ _parameters.remove(name);
+ }
+
+ public Enumeration getNames() {
+ return Collections.enumeration(_parameters.keySet());
+ }
+
+ public String toString() {
+ // we need to perform folding, but out starting point is 0.
+ return toString(0);
+ }
+
+ public String toString(int used) {
+ StringBuffer stringValue = new StringBuffer();
+
+ Iterator values = _parameters.values().iterator();
+
+ while (values.hasNext()) {
+ ParameterValue parm = (ParameterValue)values.next();
+ // get the values we're going to encode in here.
+ String name = parm.getEncodedName();
+ String value = parm.toString();
+
+ // add the semicolon separator. We also add a blank so that folding/unfolding rules can be used.
+ stringValue.append("; ");
+ used += 2;
+
+ // N.B.(schwardo): I added this foldText check.
+ // MimeUtility.fold() checks the same property below -- I
+ // believe this code should be checking it as well.
+ if (foldText) {
+ // too big for the current header line?
+ if ((used + name.length() + value.length() + 1) > HEADER_SIZE_LIMIT) {
+ // and a CRLF-combo combo.
+ stringValue.append("\r\n\t");
+ // reset the counter for a fresh line
+ // note we use use 8 because we're using a rather than a blank
+ used = 8;
+ }
+ }
+ // now add the keyword/value pair.
+ stringValue.append(name);
+ stringValue.append("=");
+
+ used += name.length() + 1;
+
+ // we're not out of the woods yet. It is possible that the keyword/value pair by itself might
+ // be too long for a single line. If that's the case, the we need to fold the value, if possible
+ if (used + value.length() > HEADER_SIZE_LIMIT) {
+ String foldedValue = MimeUtility.fold(used, value);
+
+ stringValue.append(foldedValue);
+
+ // now we need to sort out how much of the current line is in use.
+ int lastLineBreak = foldedValue.lastIndexOf('\n');
+
+ if (lastLineBreak != -1) {
+ used = foldedValue.length() - lastLineBreak + 1;
+ }
+ else {
+ used += foldedValue.length();
+ }
+ }
+ else {
+ // no folding required, just append.
+ stringValue.append(value);
+ used += value.length();
+ }
+ }
+
+ return stringValue.toString();
+ }
+
+
+ /**
+ * Utility class for representing parameter values in the list.
+ */
+ class ParameterValue {
+ public String name; // the name of the parameter
+ public String value; // the original set value
+ public String encodedValue; // an encoded value, if encoding is requested.
+
+ public ParameterValue(String name, String value) {
+ this.name = name;
+ this.value = value;
+ this.encodedValue = null;
+ }
+
+ public ParameterValue(String name, String value, String encodedValue) {
+ this.name = name;
+ this.value = value;
+ this.encodedValue = encodedValue;
+ }
+
+ public String toString() {
+ if (encodedValue != null) {
+ return MimeUtility.quote(encodedValue, HeaderTokenizer.MIME);
+ }
+ return MimeUtility.quote(value, HeaderTokenizer.MIME);
+ }
+
+ public String getEncodedName() {
+ if (encodedValue != null) {
+ return name + "*";
+ }
+ return name;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/ParseException.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/ParseException.java
new file mode 100644
index 00000000..62d8f72c
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/ParseException.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class ParseException extends MessagingException {
+ public ParseException() {
+ super();
+ }
+
+ public ParseException(String message) {
+ super(message);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/PreencodedMimeBodyPart.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/PreencodedMimeBodyPart.java
new file mode 100644
index 00000000..07344408
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/PreencodedMimeBodyPart.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+
+
+public class PreencodedMimeBodyPart extends MimeBodyPart {
+ // the defined transfer encoding
+ private String transferEncoding;
+
+
+ /**
+ * Create a new body part with the specified MIME transfer encoding.
+ *
+ * @param encoding The content encoding.
+ */
+ public PreencodedMimeBodyPart(String encoding) {
+ transferEncoding = encoding;
+ }
+
+
+ /**
+ * Retieve the defined encoding for this body part.
+ *
+ * @return
+ * @exception MessagingException
+ */
+ public String getEncoding() throws MessagingException {
+ return transferEncoding;
+ }
+
+ /**
+ * Write the body part content to the stream without applying
+ * and additional encodings.
+ *
+ * @param out The target output stream.
+ *
+ * @exception IOException
+ * @exception MessagingException
+ */
+ public void writeTo(OutputStream out) throws IOException, MessagingException {
+ headers.writeTo(out, null);
+ // add the separater between the headers and the data portion.
+ out.write('\r');
+ out.write('\n');
+ // write this out without getting an encoding stream
+ getDataHandler().writeTo(out);
+ out.flush();
+ }
+
+
+ /**
+ * Override of update headers to ensure the transfer encoding
+ * is forced to the correct type.
+ *
+ * @exception MessagingException
+ */
+ protected void updateHeaders() throws MessagingException {
+ super.updateHeaders();
+ setHeader("Content-Transfer-Encoding", transferEncoding);
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/internet/SharedInputStream.java b/external/geronimo_javamail/src/main/java/javax/mail/internet/SharedInputStream.java
new file mode 100644
index 00000000..a03f29aa
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/internet/SharedInputStream.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.InputStream;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public interface SharedInputStream {
+ public abstract long getPosition();
+
+ public abstract InputStream newStream(long start, long end);
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/AddressStringTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/AddressStringTerm.java
new file mode 100644
index 00000000..0035299d
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/AddressStringTerm.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 javax.mail.search;
+
+import javax.mail.Address;
+
+/**
+ * A Term that compares two Addresses as Strings.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class AddressStringTerm extends StringTerm {
+ /**
+ * Constructor.
+ * @param pattern the pattern to be compared
+ */
+ protected AddressStringTerm(String pattern) {
+ super(pattern);
+ }
+
+ /**
+ * Tests if the patterm associated with this Term is a substring of
+ * the address in the supplied object.
+ *
+ * @param address
+ * @return
+ */
+ protected boolean match(Address address) {
+ return match(address.toString());
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/AddressTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/AddressTerm.java
new file mode 100644
index 00000000..6bf424dd
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/AddressTerm.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import javax.mail.Address;
+
+/**
+ * Term that compares two addresses.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class AddressTerm extends SearchTerm {
+ /**
+ * The address.
+ */
+ protected Address address;
+
+ /**
+ * Constructor taking the address for this term.
+ * @param address the address
+ */
+ protected AddressTerm(Address address) {
+ this.address = address;
+ }
+
+ /**
+ * Return the address of this term.
+ *
+ * @return the addre4ss
+ */
+ public Address getAddress() {
+ return address;
+ }
+
+ /**
+ * Match to the supplied address.
+ *
+ * @param address the address to match with
+ * @return true if the addresses match
+ */
+ protected boolean match(Address address) {
+ return this.address.equals(address);
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other instanceof AddressTerm == false) return false;
+
+ return address.equals(((AddressTerm) other).address);
+ }
+
+ public int hashCode() {
+ return address.hashCode();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/AndTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/AndTerm.java
new file mode 100644
index 00000000..c6a66e90
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/AndTerm.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import java.util.Arrays;
+import javax.mail.Message;
+
+/**
+ * Term that implements a logical AND across terms.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public final class AndTerm extends SearchTerm {
+ /**
+ * Terms to which the AND operator should be applied.
+ */
+ protected SearchTerm[] terms;
+
+ /**
+ * Constructor for performing a binary AND.
+ *
+ * @param a the first term
+ * @param b the second ter,
+ */
+ public AndTerm(SearchTerm a, SearchTerm b) {
+ terms = new SearchTerm[]{a, b};
+ }
+
+ /**
+ * Constructor for performing and AND across an arbitraty number of terms.
+ * @param terms the terms to AND together
+ */
+ public AndTerm(SearchTerm[] terms) {
+ this.terms = terms;
+ }
+
+ /**
+ * Return the terms.
+ * @return the terms
+ */
+ public SearchTerm[] getTerms() {
+ return terms;
+ }
+
+ /**
+ * Match by applying the terms, in order, to the Message and performing an AND operation
+ * to the result. Comparision will stop immediately if one of the terms returns false.
+ *
+ * @param message the Message to apply the terms to
+ * @return true if all terms match
+ */
+ public boolean match(Message message) {
+ for (int i = 0; i < terms.length; i++) {
+ SearchTerm term = terms[i];
+ if (!term.match(message)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other instanceof AndTerm == false) return false;
+ return Arrays.equals(terms, ((AndTerm) other).terms);
+ }
+
+ public int hashCode() {
+ int hash = 0;
+ for (int i = 0; i < terms.length; i++) {
+ hash = hash * 37 + terms[i].hashCode();
+ }
+ return hash;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/BodyTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/BodyTerm.java
new file mode 100644
index 00000000..f40825b9
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/BodyTerm.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import java.io.IOException;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Part;
+import javax.mail.Multipart;
+import javax.mail.BodyPart;
+
+/**
+ * Term that matches on a message body. All {@link javax.mail.BodyPart parts} that have
+ * a MIME type of "text/*" are searched.
+ *
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class BodyTerm extends StringTerm {
+ public BodyTerm(String pattern) {
+ super(pattern);
+ }
+
+ public boolean match(Message message) {
+ try {
+ return matchPart(message);
+ } catch (IOException e) {
+ return false;
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+
+ private boolean matchPart(Part part) throws MessagingException, IOException {
+ if (part.isMimeType("multipart/*")) {
+ Multipart mp = (Multipart) part.getContent();
+ int count = mp.getCount();
+ for (int i=0; i < count; i++) {
+ BodyPart bp = mp.getBodyPart(i);
+ if (matchPart(bp)) {
+ return true;
+ }
+ }
+ return false;
+ } else if (part.isMimeType("text/*")) {
+ String content = (String) part.getContent();
+ return super.match(content);
+ } else if (part.isMimeType("message/rfc822")) {
+ // nested messages need recursion
+ return matchPart((Part)part.getContent());
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/ComparisonTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/ComparisonTerm.java
new file mode 100644
index 00000000..c6e98e82
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/ComparisonTerm.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 javax.mail.search;
+
+/**
+ * Base for comparison terms.
+ *
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public abstract class ComparisonTerm extends SearchTerm {
+ public static final int LE = 1;
+ public static final int LT = 2;
+ public static final int EQ = 3;
+ public static final int NE = 4;
+ public static final int GT = 5;
+ public static final int GE = 6;
+
+ protected int comparison;
+
+ public ComparisonTerm() {
+ }
+
+ public boolean equals(Object other) {
+ if (!(other instanceof ComparisonTerm)) {
+ return false;
+ }
+ return comparison == ((ComparisonTerm)other).comparison;
+ }
+
+ public int hashCode() {
+ return comparison;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/DateTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/DateTerm.java
new file mode 100644
index 00000000..ff3ccd4c
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/DateTerm.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import java.util.Date;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public abstract class DateTerm extends ComparisonTerm {
+ protected Date date;
+
+ protected DateTerm(int comparison, Date date) {
+ super();
+ this.comparison = comparison;
+ this.date = date;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public int getComparison() {
+ return comparison;
+ }
+
+ protected boolean match(Date match) {
+ long matchTime = match.getTime();
+ long mytime = date.getTime();
+ switch (comparison) {
+ case EQ:
+ return matchTime == mytime;
+ case NE:
+ return matchTime != mytime;
+ case LE:
+ return matchTime <= mytime;
+ case LT:
+ return matchTime < mytime;
+ case GT:
+ return matchTime > mytime;
+ case GE:
+ return matchTime >= mytime;
+ default:
+ return false;
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other instanceof DateTerm == false) return false;
+ final DateTerm term = (DateTerm) other;
+ return this.comparison == term.comparison && this.date.equals(term.date);
+ }
+
+ public int hashCode() {
+ return date.hashCode() + super.hashCode();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/FlagTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/FlagTerm.java
new file mode 100644
index 00000000..ef1420c9
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/FlagTerm.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import javax.mail.Flags;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * Term for matching message {@link Flags}.
+ *
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class FlagTerm extends SearchTerm {
+ /**
+ * If true, test that all flags are set; if false, test that all flags are clear.
+ */
+ protected boolean set;
+ /**
+ * The flags to test.
+ */
+ protected Flags flags;
+
+ /**
+ * @param flags the flags to test
+ * @param set test for set or clear; {@link #set}
+ */
+ public FlagTerm(Flags flags, boolean set) {
+ this.set = set;
+ this.flags = flags;
+ }
+
+ public Flags getFlags() {
+ return flags;
+ }
+
+ public boolean getTestSet() {
+ return set;
+ }
+
+ public boolean match(Message message) {
+ try {
+ Flags msgFlags = message.getFlags();
+ if (set) {
+ return msgFlags.contains(flags);
+ } else {
+ // yuk - I wish we could get at the internal state of the Flags
+ Flags.Flag[] system = flags.getSystemFlags();
+ for (int i = 0; i < system.length; i++) {
+ Flags.Flag flag = system[i];
+ if (msgFlags.contains(flag)) {
+ return false;
+ }
+ }
+ String[] user = flags.getUserFlags();
+ for (int i = 0; i < user.length; i++) {
+ String flag = user[i];
+ if (msgFlags.contains(flag)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other instanceof FlagTerm == false) return false;
+ final FlagTerm otherFlags = (FlagTerm) other;
+ return otherFlags.set == this.set && otherFlags.flags.equals(flags);
+ }
+
+ public int hashCode() {
+ return set ? flags.hashCode() : ~flags.hashCode();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/FromStringTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/FromStringTerm.java
new file mode 100644
index 00000000..9cdc037d
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/FromStringTerm.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class FromStringTerm extends AddressStringTerm {
+ public FromStringTerm(String string) {
+ super(string);
+ }
+
+ public boolean match(Message message) {
+ try {
+ Address from[] = message.getFrom();
+ if (from == null) {
+ return false;
+ }
+
+ for (int i = 0; i < from.length; i++) {
+ if (match(from[i])){
+ return true;
+ }
+ }
+ return false;
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/FromTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/FromTerm.java
new file mode 100644
index 00000000..0c2577b0
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/FromTerm.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 javax.mail.search;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class FromTerm extends AddressTerm {
+ public FromTerm(Address match) {
+ super(match);
+ }
+
+ public boolean match(Message message) {
+ try {
+ Address from[] = message.getFrom();
+ if (from == null) {
+ return false;
+ }
+ for (int i = 0; i < from.length; i++) {
+ if (match(from[i])) {
+ return true;
+ }
+ }
+ return false;
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/HeaderTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/HeaderTerm.java
new file mode 100644
index 00000000..21d0d8f3
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/HeaderTerm.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class HeaderTerm extends StringTerm {
+ protected String headerName;
+
+ public HeaderTerm(String header, String pattern) {
+ super(pattern);
+ this.headerName = header;
+ }
+
+ public String getHeaderName() {
+ return headerName;
+ }
+
+ public boolean match(Message message) {
+ try {
+ String values[] = message.getHeader(headerName);
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ String value = values[i];
+ if (match(value)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other instanceof HeaderTerm == false) return false;
+ // we need to compare with more than just the header name.
+ return headerName.equalsIgnoreCase(((HeaderTerm) other).headerName) && super.equals(other);
+ }
+
+ public int hashCode() {
+ return headerName.toLowerCase().hashCode() + super.hashCode();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/IntegerComparisonTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/IntegerComparisonTerm.java
new file mode 100644
index 00000000..b95f7d03
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/IntegerComparisonTerm.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+/**
+ * A Term that provides comparisons for integers.
+ *
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public abstract class IntegerComparisonTerm extends ComparisonTerm {
+ protected int number;
+
+ protected IntegerComparisonTerm(int comparison, int number) {
+ super();
+ this.comparison = comparison;
+ this.number = number;
+ }
+
+ public int getNumber() {
+ return number;
+ }
+
+ public int getComparison() {
+ return comparison;
+ }
+
+ protected boolean match(int match) {
+ switch (comparison) {
+ case EQ:
+ return match == number;
+ case NE:
+ return match != number;
+ case GT:
+ return match > number;
+ case GE:
+ return match >= number;
+ case LT:
+ return match < number;
+ case LE:
+ return match <= number;
+ default:
+ return false;
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other instanceof IntegerComparisonTerm == false) return false;
+ final IntegerComparisonTerm term = (IntegerComparisonTerm) other;
+ return this.comparison == term.comparison && this.number == term.number;
+ }
+
+ public int hashCode() {
+ return number + super.hashCode();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/MessageIDTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/MessageIDTerm.java
new file mode 100644
index 00000000..1f9a3152
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/MessageIDTerm.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class MessageIDTerm extends StringTerm {
+ public MessageIDTerm(String id) {
+ super(id);
+ }
+
+ public boolean match(Message message) {
+ try {
+ String values[] = message.getHeader("Message-ID");
+ if (values != null) {
+ for (int i = 0; i < values.length; i++) {
+ String value = values[i];
+ if (match(value)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (!(other instanceof MessageIDTerm)) {
+ return false;
+ }
+ return super.equals(other);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/MessageNumberTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/MessageNumberTerm.java
new file mode 100644
index 00000000..2a567804
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/MessageNumberTerm.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 javax.mail.search;
+
+import javax.mail.Message;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class MessageNumberTerm extends IntegerComparisonTerm {
+ public MessageNumberTerm(int number) {
+ super(EQ, number);
+ }
+
+ public boolean match(Message message) {
+ return match(message.getMessageNumber());
+ }
+
+ public boolean equals(Object other) {
+ if (!(other instanceof MessageNumberTerm)) {
+ return false;
+ }
+ return super.equals(other);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/NotTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/NotTerm.java
new file mode 100644
index 00000000..826c6ed7
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/NotTerm.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+
+/**
+ * Term that implements a logical negation.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public final class NotTerm extends SearchTerm {
+ protected SearchTerm term;
+
+ public NotTerm(SearchTerm term) {
+ this.term = term;
+ }
+
+ public SearchTerm getTerm() {
+ return term;
+ }
+
+ public boolean match(Message message) {
+ return !term.match(message);
+ }
+
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other instanceof NotTerm == false) return false;
+ return term.equals(((NotTerm) other).term);
+ }
+
+ public int hashCode() {
+ return term.hashCode();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/OrTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/OrTerm.java
new file mode 100644
index 00000000..ce1a7358
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/OrTerm.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 javax.mail.search;
+
+import java.util.Arrays;
+import javax.mail.Message;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public final class OrTerm extends SearchTerm {
+ protected SearchTerm[] terms;
+
+ public OrTerm(SearchTerm a, SearchTerm b) {
+ terms = new SearchTerm[]{a, b};
+ }
+
+ public OrTerm(SearchTerm[] terms) {
+ this.terms = terms;
+ }
+
+ public SearchTerm[] getTerms() {
+ return terms;
+ }
+
+ public boolean match(Message message) {
+ for (int i = 0; i < terms.length; i++) {
+ SearchTerm term = terms[i];
+ if (term.match(message)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other instanceof OrTerm == false) return false;
+ return Arrays.equals(terms, ((OrTerm) other).terms);
+ }
+
+ public int hashCode() {
+ int hash = 0;
+ for (int i = 0; i < terms.length; i++) {
+ hash = hash * 37 + terms[i].hashCode();
+ }
+ return hash;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/ReceivedDateTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/ReceivedDateTerm.java
new file mode 100644
index 00000000..7581b21d
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/ReceivedDateTerm.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import java.util.Date;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class ReceivedDateTerm extends DateTerm {
+ public ReceivedDateTerm(int comparison, Date date) {
+ super(comparison, date);
+ }
+
+ public boolean match(Message message) {
+ try {
+ Date date = message.getReceivedDate();
+ if (date == null) {
+ return false;
+ }
+
+ return match(date);
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+
+
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other instanceof ReceivedDateTerm == false) return false;
+ return super.equals(other);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/RecipientStringTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/RecipientStringTerm.java
new file mode 100644
index 00000000..54ce8413
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/RecipientStringTerm.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 javax.mail.search;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class RecipientStringTerm extends AddressStringTerm {
+ private Message.RecipientType type;
+
+ public RecipientStringTerm(Message.RecipientType type, String pattern) {
+ super(pattern);
+ this.type = type;
+ }
+
+ public Message.RecipientType getRecipientType() {
+ return type;
+ }
+
+ public boolean match(Message message) {
+ try {
+ Address from[] = message.getRecipients(type);
+ if (from == null) {
+ return false;
+ }
+ for (int i = 0; i < from.length; i++) {
+ Address address = from[i];
+ if (match(address)) {
+ return true;
+ }
+ }
+ return false;
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (other == this) return true;
+ if (other instanceof RecipientStringTerm == false) return false;
+ final RecipientStringTerm otherTerm = (RecipientStringTerm) other;
+ return this.pattern.equals(otherTerm.pattern) && this.type == otherTerm.type;
+ }
+
+ public int hashCode() {
+ return pattern.hashCode() + type.hashCode();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/RecipientTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/RecipientTerm.java
new file mode 100644
index 00000000..cd67741c
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/RecipientTerm.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class RecipientTerm extends AddressTerm {
+ protected Message.RecipientType type;
+
+ public RecipientTerm(Message.RecipientType type, Address address) {
+ super(address);
+ this.type = type;
+ }
+
+ public Message.RecipientType getRecipientType() {
+ return type;
+ }
+
+ public boolean match(Message message) {
+ try {
+ Address from[] = message.getRecipients(type);
+ if (from == null) {
+ return false;
+ }
+ for (int i = 0; i < from.length; i++) {
+ Address address = from[i];
+ if (match(address)) {
+ return true;
+ }
+ }
+ return false;
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other instanceof RecipientTerm == false) return false;
+
+ final RecipientTerm recipientTerm = (RecipientTerm) other;
+ return address.equals(recipientTerm.address) && type == recipientTerm.type;
+ }
+
+ public int hashCode() {
+ return address.hashCode() + type.hashCode();
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/SearchException.java b/external/geronimo_javamail/src/main/java/javax/mail/search/SearchException.java
new file mode 100644
index 00000000..8e9a8850
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/SearchException.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class SearchException extends MessagingException {
+ public SearchException() {
+ super();
+ }
+
+ public SearchException(String message) {
+ super(message);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/SearchTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/SearchTerm.java
new file mode 100644
index 00000000..cca43b8a
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/SearchTerm.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import java.io.Serializable;
+import javax.mail.Message;
+
+/**
+ * Base class for Terms in a parse tree used to represent a search condition.
+ *
+ * This class is Serializable to allow for the short term persistence of
+ * searches between Sessions; this is not intended for long-term persistence.
+ *
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public abstract class SearchTerm implements Serializable {
+ /**
+ * Checks a matching criteria defined by the concrete subclass of this Term.
+ *
+ * @param message the message to apply the matching criteria to
+ * @return true if the matching criteria is met
+ */
+ public abstract boolean match(Message message);
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/SentDateTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/SentDateTerm.java
new file mode 100644
index 00000000..4f0adae3
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/SentDateTerm.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import java.util.Date;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class SentDateTerm extends DateTerm {
+ public SentDateTerm(int comparison, Date date) {
+ super(comparison, date);
+ }
+
+ public boolean match(Message message) {
+ try {
+ Date date = message.getSentDate();
+ if (date == null) {
+ return false;
+ }
+
+ return match(message.getSentDate());
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other instanceof SentDateTerm == false) return false;
+ return super.equals(other);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/SizeTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/SizeTerm.java
new file mode 100644
index 00000000..8d41e6e5
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/SizeTerm.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class SizeTerm extends IntegerComparisonTerm {
+ public SizeTerm(int comparison, int size) {
+ super(comparison, size);
+ }
+
+ public boolean match(Message message) {
+ try {
+ return match(message.getSize());
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other instanceof SizeTerm == false) return false;
+ return super.equals(other);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/StringTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/StringTerm.java
new file mode 100644
index 00000000..18e6f2d8
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/StringTerm.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.search;
+
+/**
+ * A Term that provides matching criteria for Strings.
+ *
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public abstract class StringTerm extends SearchTerm {
+ /**
+ * If true, case should be ignored during matching.
+ */
+ protected boolean ignoreCase;
+
+ /**
+ * The pattern associated with this term.
+ */
+ protected String pattern;
+
+ /**
+ * Constructor specifying a pattern.
+ * Defaults to case insensitive matching.
+ * @param pattern the pattern for this term
+ */
+ protected StringTerm(String pattern) {
+ this(pattern, true);
+ }
+
+ /**
+ * Constructor specifying pattern and case sensitivity.
+ * @param pattern the pattern for this term
+ * @param ignoreCase if true, case should be ignored during matching
+ */
+ protected StringTerm(String pattern, boolean ignoreCase) {
+ this.pattern = pattern;
+ this.ignoreCase = ignoreCase;
+ }
+
+ /**
+ * Return the pattern associated with this term.
+ * @return the pattern associated with this term
+ */
+ public String getPattern() {
+ return pattern;
+ }
+
+ /**
+ * Indicate if case should be ignored when matching.
+ * @return if true, case should be ignored during matching
+ */
+ public boolean getIgnoreCase() {
+ return ignoreCase;
+ }
+
+ /**
+ * Determine if the pattern associated with this term is a substring of the
+ * supplied String. If ignoreCase is true then case will be ignored.
+ *
+ * @param match the String to compare to
+ * @return true if this patter is a substring of the supplied String
+ */
+ protected boolean match(String match) {
+ int matchLength = pattern.length();
+ int length = match.length() - matchLength;
+
+ for (int i = 0; i <= length; i++) {
+ if (match.regionMatches(ignoreCase, i, pattern, 0, matchLength)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other instanceof StringTerm == false) return false;
+
+ StringTerm term = (StringTerm)other;
+
+ if (ignoreCase) {
+ return term.pattern.equalsIgnoreCase(pattern) && term.ignoreCase == ignoreCase;
+ }
+ else {
+ return term.pattern.equals(pattern) && term.ignoreCase == ignoreCase;
+ }
+ }
+
+ public int hashCode() {
+ return pattern.hashCode() + (ignoreCase ? 32 : 79);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/search/SubjectTerm.java b/external/geronimo_javamail/src/main/java/javax/mail/search/SubjectTerm.java
new file mode 100644
index 00000000..7e9b8dc7
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/search/SubjectTerm.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 javax.mail.search;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+
+/**
+ * @version $Rev: 593593 $ $Date: 2007-11-09 11:04:20 -0600 (Fri, 09 Nov 2007) $
+ */
+public final class SubjectTerm extends StringTerm {
+ public SubjectTerm(String subject) {
+ super(subject);
+ }
+
+ public boolean match(Message message) {
+ try {
+ String subject = message.getSubject();
+ if (subject == null) {
+ return false;
+ }
+ return match(subject);
+ } catch (MessagingException e) {
+ return false;
+ }
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other instanceof SubjectTerm == false) return false;
+ return super.equals(other);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/util/ByteArrayDataSource.java b/external/geronimo_javamail/src/main/java/javax/mail/util/ByteArrayDataSource.java
new file mode 100644
index 00000000..0763589b
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/util/ByteArrayDataSource.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.activation.DataSource;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.ParseException;
+import javax.mail.internet.MimeUtility;
+
+
+/**
+ * An activation DataSource object that sources the data from
+ * a byte[] array.
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class ByteArrayDataSource implements DataSource {
+ // the data source
+ private byte[] source;
+ // the content MIME type
+ private String contentType;
+ // the name information (defaults to a null string)
+ private String name = "";
+
+
+ /**
+ * Create a ByteArrayDataSource from an input stream.
+ *
+ * @param in The source input stream.
+ * @param type The MIME-type of the data.
+ *
+ * @exception IOException
+ */
+ public ByteArrayDataSource(InputStream in, String type) throws IOException {
+ ByteArrayOutputStream sink = new ByteArrayOutputStream();
+
+ // ok, how I wish you could just pipe an input stream into an output stream :-)
+ byte[] buffer = new byte[8192];
+ int bytesRead;
+
+ while ((bytesRead = in.read(buffer)) > 0) {
+ sink.write(buffer, 0, bytesRead);
+ }
+
+ source = sink.toByteArray();
+ contentType = type;
+ }
+
+
+ /**
+ * Create a ByteArrayDataSource directly from a byte array.
+ *
+ * @param data The source byte array (not copied).
+ * @param type The content MIME-type.
+ */
+ public ByteArrayDataSource(byte[] data, String type) {
+ source = data;
+ contentType = type;
+ }
+
+ /**
+ * Create a ByteArrayDataSource from a string value. If the
+ * type information includes a charset parameter, that charset
+ * is used to extract the bytes. Otherwise, the default Java
+ * char set is used.
+ *
+ * @param data The source data string.
+ * @param type The MIME type information.
+ *
+ * @exception IOException
+ */
+ public ByteArrayDataSource(String data, String type) throws IOException {
+ String charset = null;
+ try {
+ // the charset can be encoded in the content type, which we parse using
+ // the ContentType class.
+ ContentType content = new ContentType(type);
+ charset = content.getParameter("charset");
+ } catch (ParseException e) {
+ // ignored...just use the default if this fails
+ }
+ if (charset == null) {
+ charset = MimeUtility.getDefaultJavaCharset();
+ }
+ else {
+ // the type information encodes a MIME charset, which may need mapping to a Java one.
+ charset = MimeUtility.javaCharset(charset);
+ }
+
+ // get the source using the specified charset
+ source = data.getBytes(charset);
+ contentType = type;
+ }
+
+
+ /**
+ * Create an input stream for this data. A new input stream
+ * is created each time.
+ *
+ * @return An InputStream for reading the encapsulated data.
+ * @exception IOException
+ */
+ public InputStream getInputStream() throws IOException {
+ return new ByteArrayInputStream(source);
+ }
+
+
+ /**
+ * Open an output stream for the DataSource. This is not
+ * supported by this DataSource, so an IOException is always
+ * throws.
+ *
+ * @return Nothing...an IOException is always thrown.
+ * @exception IOException
+ */
+ public OutputStream getOutputStream() throws IOException {
+ throw new IOException("Writing to a ByteArrayDataSource is not supported");
+ }
+
+
+ /**
+ * Get the MIME content type information for this DataSource.
+ *
+ * @return The MIME content type string.
+ */
+ public String getContentType() {
+ return contentType;
+ }
+
+
+ /**
+ * Retrieve the DataSource name. If not explicitly set, this
+ * returns "".
+ *
+ * @return The currently set DataSource name.
+ */
+ public String getName() {
+ return name;
+ }
+
+
+ /**
+ * Set a new DataSource name.
+ *
+ * @param name The new name.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/util/SharedByteArrayInputStream.java b/external/geronimo_javamail/src/main/java/javax/mail/util/SharedByteArrayInputStream.java
new file mode 100644
index 00000000..98dba0ac
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/util/SharedByteArrayInputStream.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.mail.internet.SharedInputStream;
+
+public class SharedByteArrayInputStream extends ByteArrayInputStream implements SharedInputStream {
+
+ /**
+ * Position within shared buffer that this stream starts at.
+ */
+ protected int start;
+
+ /**
+ * Create a SharedByteArrayInputStream that shares the entire
+ * buffer.
+ *
+ * @param buf The input data.
+ */
+ public SharedByteArrayInputStream(byte[] buf) {
+ this(buf, 0, buf.length);
+ }
+
+
+ /**
+ * Create a SharedByteArrayInputStream using a subset of the
+ * array data.
+ *
+ * @param buf The source data array.
+ * @param offset The starting offset within the array.
+ * @param length The length of data to use.
+ */
+ public SharedByteArrayInputStream(byte[] buf, int offset, int length) {
+ super(buf, offset, length);
+ start = offset;
+ }
+
+
+ /**
+ * Get the position within the output stream, adjusted by the
+ * starting offset.
+ *
+ * @return The adjusted position within the stream.
+ */
+ public long getPosition() {
+ return pos - start;
+ }
+
+
+ /**
+ * Create a new input stream from this input stream, accessing
+ * a subset of the data. Think of it as a substring operation
+ * for a stream.
+ *
+ * The starting offset must be non-negative. The end offset can
+ * by -1, which means use the remainder of the stream.
+ *
+ * @param offset The starting offset.
+ * @param end The end offset (which can be -1).
+ *
+ * @return An InputStream configured to access the indicated data subrange.
+ */
+ public InputStream newStream(long offset, long end) {
+ if (offset < 0) {
+ throw new IllegalArgumentException("Starting position must be non-negative");
+ }
+ if (end == -1) {
+ end = count - start;
+ }
+ return new SharedByteArrayInputStream(buf, start + (int)offset, (int)(end - offset));
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/javax/mail/util/SharedFileInputStream.java b/external/geronimo_javamail/src/main/java/javax/mail/util/SharedFileInputStream.java
new file mode 100644
index 00000000..270d15a7
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/javax/mail/util/SharedFileInputStream.java
@@ -0,0 +1,587 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.util;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+import javax.mail.internet.SharedInputStream;
+
+public class SharedFileInputStream extends BufferedInputStream implements SharedInputStream {
+
+
+ // This initial size isn't documented, but bufsize is 2048 after initialization for the
+ // Sun implementation.
+ private static final int DEFAULT_BUFFER_SIZE = 2048;
+
+ // the shared file information, used to synchronize opens/closes of the base file.
+ private SharedFileSource source;
+
+ /**
+ * The file offset that is the first byte in the read buffer.
+ */
+ protected long bufpos;
+
+ /**
+ * The normal size of the read buffer.
+ */
+ protected int bufsize;
+
+ /**
+ * The size of the file subset represented by this stream instance.
+ */
+ protected long datalen;
+
+ /**
+ * The source of the file data. This is shared across multiple
+ * instances.
+ */
+ protected RandomAccessFile in;
+
+ /**
+ * The starting position of data represented by this stream relative
+ * to the start of the file data. This stream instance represents
+ * data in the range start to (start + datalen - 1).
+ */
+ protected long start;
+
+
+ /**
+ * Construct a SharedFileInputStream from a file name, using the default buffer size.
+ *
+ * @param file The name of the file.
+ *
+ * @exception IOException
+ */
+ public SharedFileInputStream(String file) throws IOException {
+ this(file, DEFAULT_BUFFER_SIZE);
+ }
+
+
+ /**
+ * Construct a SharedFileInputStream from a File object, using the default buffer size.
+ *
+ * @param file The name of the file.
+ *
+ * @exception IOException
+ */
+ public SharedFileInputStream(File file) throws IOException {
+ this(file, DEFAULT_BUFFER_SIZE);
+ }
+
+
+ /**
+ * Construct a SharedFileInputStream from a file name, with a given initial buffer size.
+ *
+ * @param file The name of the file.
+ * @param bufferSize The initial buffer size.
+ *
+ * @exception IOException
+ */
+ public SharedFileInputStream(String file, int bufferSize) throws IOException {
+ // I'm not sure this is correct or not. The SharedFileInputStream spec requires this
+ // be a subclass of BufferedInputStream. The BufferedInputStream constructor takes a stream,
+ // which we're not really working from at this point. Using null seems to work so far.
+ super(null);
+ init(new File(file), bufferSize);
+ }
+
+
+ /**
+ * Construct a SharedFileInputStream from a File object, with a given initial buffer size.
+ *
+ * @param file The name of the file.
+ * @param bufferSize The initial buffer size.
+ *
+ * @exception IOException
+ */
+ public SharedFileInputStream(File file, int bufferSize) throws IOException {
+ // I'm not sure this is correct or not. The SharedFileInputStream spec requires this
+ // be a subclass of BufferedInputStream. The BufferedInputStream constructor takes a stream,
+ // which we're not really working from at this point. Using null seems to work so far.
+ super(null);
+ init(file, bufferSize);
+ }
+
+
+ /**
+ * Private constructor used to spawn off a shared instance
+ * of this stream.
+ *
+ * @param source The internal class object that manages the shared resources of
+ * the stream.
+ * @param start The starting offset relative to the beginning of the file.
+ * @param len The length of file data in this shared instance.
+ * @param bufsize The initial buffer size (same as the spawning parent.
+ */
+ private SharedFileInputStream(SharedFileSource source, long start, long len, int bufsize) {
+ super(null);
+ this.source = source;
+ in = source.open();
+ this.start = start;
+ bufpos = start;
+ datalen = len;
+ this.bufsize = bufsize;
+ buf = new byte[bufsize];
+ // other fields such as pos and count initialized by the super class constructor.
+ }
+
+
+ /**
+ * Shared initializtion routine for the constructors.
+ *
+ * @param file The file we're accessing.
+ * @param bufferSize The initial buffer size to use.
+ *
+ * @exception IOException
+ */
+ private void init(File file, int bufferSize) throws IOException {
+ if (bufferSize <= 0) {
+ throw new IllegalArgumentException("Buffer size must be positive");
+ }
+ // create a random access file for accessing the data, then create an object that's used to share
+ // instances of the same stream.
+ source = new SharedFileSource(file);
+ // we're opening the first one.
+ in = source.open();
+ // this represents the entire file, for now.
+ start = 0;
+ // use the current file length for the bounds
+ datalen = in.length();
+ // now create our buffer version
+ bufsize = bufferSize;
+ bufpos = 0;
+ // NB: this is using the super class protected variable.
+ buf = new byte[bufferSize];
+ }
+
+
+ /**
+ * Check to see if we need to read more data into our buffer.
+ *
+ * @return False if there's not valid data in the buffer (generally means
+ * an EOF condition).
+ * @exception IOException
+ */
+ private boolean checkFill() throws IOException {
+ // if we have data in the buffer currently, just return
+ if (pos < count) {
+ return true;
+ }
+
+ // ugh, extending BufferedInputStream also means supporting mark positions. That complicates everything.
+ // life is so much easier if marks are not used....
+ if (markpos < 0) {
+ // reset back to the buffer position
+ pos = 0;
+ // this will be the new position within the file once we're read some data.
+ bufpos += count;
+ }
+ else {
+ // we have marks to worry about....damn.
+ // if we have room in the buffer to read more data, then we will. Otherwise, we need to see
+ // if it's possible to shift the data in the buffer or extend the buffer (up to the mark limit).
+ if (pos >= buf.length) {
+ // the mark position is not at the beginning of the buffer, so just shuffle the bytes, leaving
+ // us room to read more data.
+ if (markpos > 0) {
+ // this is the size of the data we need to keep.
+ int validSize = pos - markpos;
+ // perform the shift operation.
+ System.arraycopy(buf, markpos, buf, 0, validSize);
+ // now adjust the positional markers for this shift.
+ pos = validSize;
+ bufpos += markpos;
+ markpos = 0;
+ }
+ // the mark is at the beginning, and we've used up the buffer. See if we're allowed to
+ // extend this.
+ else if (buf.length < marklimit) {
+ // try to double this, but throttle to the mark limit
+ int newSize = Math.min(buf.length * 2, marklimit);
+
+ byte[] newBuffer = new byte[newSize];
+ System.arraycopy(buf, 0, newBuffer, 0, buf.length);
+
+ // replace the old buffer. Note that all other positional markers remain the same here.
+ buf = newBuffer;
+ }
+ // we've got further than allowed, so invalidate the mark, and just reset the buffer
+ else {
+ markpos = -1;
+ pos = 0;
+ bufpos += count;
+ }
+ }
+ }
+
+ // if we're past our designated end, force an eof.
+ if (bufpos + pos >= start + datalen) {
+ // make sure we zero the count out, otherwise we'll reuse this data
+ // if called again.
+ count = pos;
+ return false;
+ }
+
+ // seek to the read location start. Note this is a shared file, so this assumes all of the methods
+ // doing buffer fills will be synchronized.
+ int fillLength = buf.length - pos;
+
+ // we might be working with a subset of the file data, so normal eof processing might not apply.
+ // we need to limit how much we read to the data length.
+ if (bufpos - start + pos + fillLength > datalen) {
+ fillLength = (int)(datalen - (bufpos - start + pos));
+ }
+
+ // finally, try to read more data into the buffer.
+ fillLength = source.read(bufpos + pos, buf, pos, fillLength);
+
+ // we weren't able to read anything, count this as an eof failure.
+ if (fillLength <= 0) {
+ // make sure we zero the count out, otherwise we'll reuse this data
+ // if called again.
+ count = pos;
+ return false;
+ }
+
+ // set the new buffer count
+ count = fillLength + pos;
+
+ // we have data in the buffer.
+ return true;
+ }
+
+
+ /**
+ * Return the number of bytes available for reading without
+ * blocking for a long period.
+ *
+ * @return For this stream, this is the number of bytes between the
+ * current read position and the indicated end of the file.
+ * @exception IOException
+ */
+ public synchronized int available() throws IOException {
+ checkOpen();
+
+ // this is backed by a file, which doesn't really block. We can return all the way to the
+ // marked data end, if necessary
+ long endMarker = start + datalen;
+ return (int)(endMarker - (bufpos + pos));
+ }
+
+
+ /**
+ * Return the current read position of the stream.
+ *
+ * @return The current position relative to the beginning of the stream.
+ * This is not the position relative to the start of the file, since
+ * the stream starting position may be other than the beginning.
+ */
+ public long getPosition() {
+ checkOpenRuntime();
+
+ return bufpos + pos - start;
+ }
+
+
+ /**
+ * Mark the current position for retracing.
+ *
+ * @param readlimit The limit for the distance the read position can move from
+ * the mark position before the mark is reset.
+ */
+ public synchronized void mark(int readlimit) {
+ checkOpenRuntime();
+ marklimit = readlimit;
+ markpos = pos;
+ }
+
+
+ /**
+ * Read a single byte of data from the input stream.
+ *
+ * @return The read byte. Returns -1 if an eof condition has been hit.
+ * @exception IOException
+ */
+ public synchronized int read() throws IOException {
+ checkOpen();
+
+ // check to see if we can fill more data
+ if (!checkFill()) {
+ return -1;
+ }
+
+ // return the current byte...anded to prevent sign extension.
+ return buf[pos++] & 0xff;
+ }
+
+
+ /**
+ * Read multiple bytes of data and place them directly into
+ * a byte-array buffer.
+ *
+ * @param buffer The target buffer.
+ * @param offset The offset within the buffer to place the data.
+ * @param length The length to attempt to read.
+ *
+ * @return The number of bytes actually read. Returns -1 for an EOF
+ * condition.
+ * @exception IOException
+ */
+ public synchronized int read(byte buffer[], int offset, int length) throws IOException {
+ checkOpen();
+
+ // asked to read nothing? That's what we'll do.
+ if (length == 0) {
+ return 0;
+ }
+
+
+ int returnCount = 0;
+ while (length > 0) {
+ // check to see if we can/must fill more data
+ if (!checkFill()) {
+ // we've hit the end, but if we've read data, then return that.
+ if (returnCount > 0) {
+ return returnCount;
+ }
+ // trun eof.
+ return -1;
+ }
+
+ int available = count - pos;
+ int given = Math.min(available, length);
+
+ System.arraycopy(buf, pos, buffer, offset, given);
+
+ // now adjust all of our positions and counters
+ pos += given;
+ length -= given;
+ returnCount += given;
+ offset += given;
+ }
+ // return the accumulated count.
+ return returnCount;
+ }
+
+
+ /**
+ * Skip the read pointer ahead a given number of bytes.
+ *
+ * @param n The number of bytes to skip.
+ *
+ * @return The number of bytes actually skipped.
+ * @exception IOException
+ */
+ public synchronized long skip(long n) throws IOException {
+ checkOpen();
+
+ // nothing to skip, so don't skip
+ if (n <= 0) {
+ return 0;
+ }
+
+ // see if we need to fill more data, and potentially shift the mark positions
+ if (!checkFill()) {
+ return 0;
+ }
+
+ long available = count - pos;
+
+ // the skipped contract allows skipping within the current buffer bounds, so cap it there.
+ long skipped = available < n ? available : n;
+ pos += skipped;
+ return skipped;
+ }
+
+ /**
+ * Reset the mark position.
+ *
+ * @exception IOException
+ */
+ public synchronized void reset() throws IOException {
+ checkOpen();
+ if (markpos < 0) {
+ throw new IOException("Resetting to invalid mark position");
+ }
+ // if we have a markpos, it will still be in the buffer bounds.
+ pos = markpos;
+ }
+
+
+ /**
+ * Indicates the mark() operation is supported.
+ *
+ * @return Always returns true.
+ */
+ public boolean markSupported() {
+ return true;
+ }
+
+
+ /**
+ * Close the stream. This does not close the source file until
+ * the last shared instance is closed.
+ *
+ * @exception IOException
+ */
+ public void close() throws IOException {
+ // already closed? This is not an error
+ if (in == null) {
+ return;
+ }
+
+ try {
+ // perform a close on the source version.
+ source.close();
+ } finally {
+ in = null;
+ }
+ }
+
+
+ /**
+ * Create a new stream from this stream, using the given
+ * start offset and length.
+ *
+ * @param offset The offset relative to the start of this stream instance.
+ * @param end The end offset of the substream. If -1, the end of the parent stream is used.
+ *
+ * @return A new SharedFileInputStream object sharing the same source
+ * input file.
+ */
+ public InputStream newStream(long offset, long end) {
+ checkOpenRuntime();
+
+ if (offset < 0) {
+ throw new IllegalArgumentException("Start position is less than 0");
+ }
+ // the default end position is the datalen of the one we're spawning from.
+ if (end == -1) {
+ end = datalen;
+ }
+
+ // create a new one using the private constructor
+ return new SharedFileInputStream(source, start + (int)offset, (int)(end - offset), bufsize);
+ }
+
+
+ /**
+ * Check if the file is open and throw an IOException if not.
+ *
+ * @exception IOException
+ */
+ private void checkOpen() throws IOException {
+ if (in == null) {
+ throw new IOException("Stream has been closed");
+ }
+ }
+
+
+ /**
+ * Check if the file is open and throw an IOException if not. This version is
+ * used because several API methods are not defined as throwing IOException, so
+ * checkOpen() can't be used. The Sun implementation just throws RuntimeExceptions
+ * in those methods, hence 2 versions.
+ *
+ * @exception RuntimeException
+ */
+ private void checkOpenRuntime() {
+ if (in == null) {
+ throw new RuntimeException("Stream has been closed");
+ }
+ }
+
+
+ /**
+ * Internal class used to manage resources shared between the
+ * ShareFileInputStream instances.
+ */
+ class SharedFileSource {
+ // the file source
+ public RandomAccessFile source;
+ // the shared instance count for this file (open instances)
+ public int instanceCount = 0;
+
+ public SharedFileSource(File file) throws IOException {
+ source = new RandomAccessFile(file, "r");
+ }
+
+ /**
+ * Open the shared stream to keep track of open instances.
+ */
+ public synchronized RandomAccessFile open() {
+ instanceCount++;
+ return source;
+ }
+
+ /**
+ * Process a close request for this stream. If there are multiple
+ * instances using this underlying stream, the stream will not
+ * be closed.
+ *
+ * @exception IOException
+ */
+ public synchronized void close() throws IOException {
+ if (instanceCount > 0) {
+ instanceCount--;
+ // if the last open instance, close the real source file.
+ if (instanceCount == 0) {
+ source.close();
+ }
+ }
+ }
+
+ /**
+ * Read a buffer of data from the shared file.
+ *
+ * @param position The position to read from.
+ * @param buf The target buffer for storing the read data.
+ * @param offset The starting offset within the buffer.
+ * @param length The length to attempt to read.
+ *
+ * @return The number of bytes actually read.
+ * @exception IOException
+ */
+ public synchronized int read(long position, byte[] buf, int offset, int length) throws IOException {
+ // seek to the read location start. Note this is a shared file, so this assumes all of the methods
+ // doing buffer fills will be synchronized.
+ source.seek(position);
+ return source.read(buf, offset, length);
+ }
+
+
+ /**
+ * Ensure the stream is closed when this shared object is finalized.
+ *
+ * @exception Throwable
+ */
+ protected void finalize() throws Throwable {
+ super.finalize();
+ if (instanceCount > 0) {
+ source.close();
+ }
+ }
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java
new file mode 100644
index 00000000..7f42f61d
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.handlers;
+
+import javax.activation.ActivationDataFlavor;
+
+public class HtmlHandler extends TextHandler {
+ public HtmlHandler() {
+ super(new ActivationDataFlavor(java.lang.String.class, "text/html", "HTML String"));
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java
new file mode 100644
index 00000000..6e9e8c60
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.handlers;
+
+import javax.activation.ActivationDataFlavor;
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+import javax.mail.internet.ContentType;
+import javax.mail.Message;
+import javax.mail.MessageAware;
+import javax.mail.MessageContext;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeUtility;
+import javax.mail.internet.ParseException;
+import java.awt.datatransfer.DataFlavor;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+
+public class MessageHandler implements DataContentHandler {
+ /**
+ * Field dataFlavor
+ */
+ ActivationDataFlavor dataFlavor;
+
+ public MessageHandler(){
+ dataFlavor = new ActivationDataFlavor(java.lang.String.class, "message/rfc822", "Text");
+ }
+
+
+ /**
+ * Method getDF
+ *
+ * @return dataflavor
+ */
+ protected ActivationDataFlavor getDF() {
+ return dataFlavor;
+ }
+
+ /**
+ * Method getTransferDataFlavors
+ *
+ * @return dataflavors
+ */
+ public DataFlavor[] getTransferDataFlavors() {
+ return (new DataFlavor[]{dataFlavor});
+ }
+
+ /**
+ * Method getTransferData
+ *
+ * @param dataflavor
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getTransferData(DataFlavor dataflavor, DataSource datasource)
+ throws IOException {
+ if (getDF().equals(dataflavor)) {
+ return getContent(datasource);
+ }
+ return null;
+ }
+
+ /**
+ * Method getContent
+ *
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getContent(DataSource datasource) throws IOException {
+
+ try {
+ // if this is a proper message, it implements the MessageAware interface. We need this to
+ // get the associated session.
+ if (datasource instanceof MessageAware) {
+ MessageContext context = ((MessageAware)datasource).getMessageContext();
+ // construct a mime message instance from the stream, associating it with the
+ // data source session.
+ return new MimeMessage(context.getSession(), datasource.getInputStream());
+ }
+ } catch (MessagingException e) {
+ // we need to transform any exceptions into an IOException.
+ throw new IOException("Exception writing MimeMultipart: " + e.toString());
+ }
+ return null;
+ }
+
+ /**
+ * Method writeTo
+ *
+ * @param object
+ * @param s
+ * @param outputstream
+ * @throws IOException
+ */
+ public void writeTo(Object object, String s, OutputStream outputstream) throws IOException {
+ // proper message type?
+ if (object instanceof Message) {
+ try {
+ ((Message)object).writeTo(outputstream);
+ } catch (MessagingException e) {
+ throw new IOException("Error parsing message: " + e.toString());
+ }
+ }
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java
new file mode 100644
index 00000000..9133179e
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.handlers;
+
+import javax.activation.ActivationDataFlavor;
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+import java.awt.datatransfer.DataFlavor;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.MessagingException;
+
+public class MultipartHandler implements DataContentHandler {
+ /**
+ * Field dataFlavor
+ */
+ ActivationDataFlavor dataFlavor;
+
+ public MultipartHandler(){
+ dataFlavor = new ActivationDataFlavor(javax.mail.internet.MimeMultipart.class, "multipart/mixed", "Multipart");
+ }
+
+ /**
+ * Constructor TextHandler
+ *
+ * @param dataFlavor
+ */
+ public MultipartHandler(ActivationDataFlavor dataFlavor) {
+ this.dataFlavor = dataFlavor;
+ }
+
+ /**
+ * Method getDF
+ *
+ * @return dataflavor
+ */
+ protected ActivationDataFlavor getDF() {
+ return dataFlavor;
+ }
+
+ /**
+ * Method getTransferDataFlavors
+ *
+ * @return dataflavors
+ */
+ public DataFlavor[] getTransferDataFlavors() {
+ return (new DataFlavor[]{dataFlavor});
+ }
+
+ /**
+ * Method getTransferData
+ *
+ * @param dataflavor
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getTransferData(DataFlavor dataflavor, DataSource datasource)
+ throws IOException {
+ if (getDF().equals(dataflavor)) {
+ return getContent(datasource);
+ }
+ return null;
+ }
+
+ /**
+ * Method getContent
+ *
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getContent(DataSource datasource) throws IOException {
+ try {
+ return new MimeMultipart(datasource);
+ } catch (MessagingException e) {
+ // if there is a syntax error from the datasource parsing, the content is
+ // just null.
+ return null;
+ }
+ }
+
+ /**
+ * Method writeTo
+ *
+ * @param object
+ * @param s
+ * @param outputstream
+ * @throws IOException
+ */
+ public void writeTo(Object object, String s, OutputStream outputstream) throws IOException {
+ // if this object is a MimeMultipart, then delegate to the part.
+ if (object instanceof MimeMultipart) {
+ try {
+ ((MimeMultipart)object).writeTo(outputstream);
+ } catch (MessagingException e) {
+ // we need to transform any exceptions into an IOException.
+ throw new IOException("Exception writing MimeMultipart: " + e.toString());
+ }
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java
new file mode 100644
index 00000000..ee548eea
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.handlers;
+
+import java.awt.datatransfer.DataFlavor;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+
+import javax.activation.ActivationDataFlavor;
+import javax.activation.DataContentHandler;
+import javax.activation.DataSource;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.MimeUtility;
+import javax.mail.internet.ParseException;
+
+public class TextHandler implements DataContentHandler {
+ /**
+ * Field dataFlavor
+ */
+ ActivationDataFlavor dataFlavor;
+
+ public TextHandler(){
+ dataFlavor = new ActivationDataFlavor(java.lang.String.class, "text/plain", "Text String");
+ }
+
+ /**
+ * Constructor TextHandler
+ *
+ * @param dataFlavor
+ */
+ public TextHandler(ActivationDataFlavor dataFlavor) {
+ this.dataFlavor = dataFlavor;
+ }
+
+ /**
+ * Method getDF
+ *
+ * @return dataflavor
+ */
+ protected ActivationDataFlavor getDF() {
+ return dataFlavor;
+ }
+
+ /**
+ * Method getTransferDataFlavors
+ *
+ * @return dataflavors
+ */
+ public DataFlavor[] getTransferDataFlavors() {
+ return (new DataFlavor[]{dataFlavor});
+ }
+
+ /**
+ * Method getTransferData
+ *
+ * @param dataflavor
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getTransferData(DataFlavor dataflavor, DataSource datasource)
+ throws IOException {
+ if (getDF().equals(dataflavor)) {
+ return getContent(datasource);
+ }
+ return null;
+ }
+
+ /**
+ * Method getContent
+ *
+ * @param datasource
+ * @return
+ * @throws IOException
+ */
+ public Object getContent(DataSource datasource) throws IOException {
+ InputStream is = datasource.getInputStream();
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ int count;
+ byte[] buffer = new byte[1000];
+
+ try {
+ while ((count = is.read(buffer, 0, buffer.length)) > 0) {
+ os.write(buffer, 0, count);
+ }
+ } finally {
+ is.close();
+ }
+ try {
+ return os.toString(getCharSet(datasource.getContentType()));
+ } catch (ParseException e) {
+ throw new UnsupportedEncodingException(e.getMessage());
+ }
+ }
+
+
+ /**
+ * Write an object of "our" type out to the provided
+ * output stream. The content type might modify the
+ * result based on the content type parameters.
+ *
+ * @param object The object to write.
+ * @param contentType
+ * The content mime type, including parameters.
+ * @param outputstream
+ * The target output stream.
+ *
+ * @throws IOException
+ */
+ public void writeTo(Object object, String contentType, OutputStream outputstream)
+ throws IOException {
+ OutputStreamWriter os;
+ try {
+ String charset = getCharSet(contentType);
+ os = new OutputStreamWriter(outputstream, charset);
+ } catch (Exception ex) {
+ throw new UnsupportedEncodingException(ex.toString());
+ }
+ String content = (String) object;
+ os.write(content, 0, content.length());
+ os.flush();
+ }
+
+ /**
+ * get the character set from content type
+ * @param contentType
+ * @return
+ * @throws ParseException
+ */
+ protected String getCharSet(String contentType) throws ParseException {
+ ContentType type = new ContentType(contentType);
+ String charset = type.getParameter("charset");
+ if (charset == null) {
+ charset = "us-ascii";
+ }
+ return MimeUtility.javaCharset(charset);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java
new file mode 100644
index 00000000..2aa7203f
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.handlers;
+
+import javax.activation.ActivationDataFlavor;
+
+public class XMLHandler extends TextHandler {
+ public XMLHandler() {
+ super(new ActivationDataFlavor(java.lang.String.class, "text/xml", "XML String"));
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java
new file mode 100644
index 00000000..0653c942
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java
@@ -0,0 +1,246 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * Set of utility classes for handling common encoding-related
+ * manipulations.
+ */
+public class ASCIIUtil {
+
+ /**
+ * Test to see if this string contains only US-ASCII (i.e., 7-bit
+ * ASCII) charactes.
+ *
+ * @param s The test string.
+ *
+ * @return true if this is a valid 7-bit ASCII encoding, false if it
+ * contains any non-US ASCII characters.
+ */
+ static public boolean isAscii(String s) {
+ for (int i = 0; i < s.length(); i++) {
+ if (!isAscii(s.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Test to see if a given character can be considered "valid" ASCII.
+ * The excluded characters are the control characters less than
+ * 32, 8-bit characters greater than 127, EXCEPT the CR, LF and
+ * tab characters ARE considered value (all less than 32).
+ *
+ * @param ch The test character.
+ *
+ * @return true if this character meets the "ascii-ness" criteria, false
+ * otherwise.
+ */
+ static public boolean isAscii(int ch) {
+ // these are explicitly considered valid.
+ if (ch == '\r' || ch == '\n' || ch == '\t') {
+ return true;
+ }
+
+ // anything else outside the range is just plain wrong.
+ if (ch >= 127 || ch < 32) {
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Examine a stream of text and make a judgement on what encoding
+ * type should be used for the text. Ideally, we want to use 7bit
+ * encoding to determine this, but we may need to use either quoted-printable
+ * or base64. The choice is made on the ratio of 7-bit characters to non-7bit.
+ *
+ * @param content An input stream for the content we're examining.
+ *
+ * @exception IOException
+ */
+ public static String getTextTransferEncoding(InputStream content) throws IOException {
+
+ // for efficiency, we'll read in blocks.
+ BufferedInputStream in = new BufferedInputStream(content, 4096);
+
+ int span = 0; // span of characters without a line break.
+ boolean containsLongLines = false;
+ int asciiChars = 0;
+ int nonAsciiChars = 0;
+
+ while (true) {
+ int ch = in.read();
+ // if we hit an EOF here, go decide what type we've actually found.
+ if (ch == -1) {
+ break;
+ }
+
+ // we found a linebreak. Reset the line length counters on either one. We don't
+ // really need to validate here.
+ if (ch == '\n' || ch == '\r') {
+ // hit a line end, reset our line length counter
+ span = 0;
+ }
+ else {
+ span++;
+ // the text has long lines, we can't transfer this as unencoded text.
+ if (span > 998) {
+ containsLongLines = true;
+ }
+
+ // non-ascii character, we have to transfer this in binary.
+ if (!isAscii(ch)) {
+ nonAsciiChars++;
+ }
+ else {
+ asciiChars++;
+ }
+ }
+ }
+
+ // looking good so far, only valid chars here.
+ if (nonAsciiChars == 0) {
+ // does this contain long text lines? We need to use a Q-P encoding which will
+ // be only slightly longer, but handles folding the longer lines.
+ if (containsLongLines) {
+ return "quoted-printable";
+ }
+ else {
+ // ideal! Easiest one to handle.
+ return "7bit";
+ }
+ }
+ else {
+ // mostly characters requiring encoding? Base64 is our best bet.
+ if (nonAsciiChars > asciiChars) {
+ return "base64";
+ }
+ else {
+ // Q-P encoding will use fewer bytes than the full Base64.
+ return "quoted-printable";
+ }
+ }
+ }
+
+
+ /**
+ * Examine a stream of text and make a judgement on what encoding
+ * type should be used for the text. Ideally, we want to use 7bit
+ * encoding to determine this, but we may need to use either quoted-printable
+ * or base64. The choice is made on the ratio of 7-bit characters to non-7bit.
+ *
+ * @param content A string for the content we're examining.
+ */
+ public static String getTextTransferEncoding(String content) {
+
+ int asciiChars = 0;
+ int nonAsciiChars = 0;
+
+ for (int i = 0; i < content.length(); i++) {
+ int ch = content.charAt(i);
+
+ // non-ascii character, we have to transfer this in binary.
+ if (!isAscii(ch)) {
+ nonAsciiChars++;
+ }
+ else {
+ asciiChars++;
+ }
+ }
+
+ // looking good so far, only valid chars here.
+ if (nonAsciiChars == 0) {
+ // ideal! Easiest one to handle.
+ return "7bit";
+ }
+ else {
+ // mostly characters requiring encoding? Base64 is our best bet.
+ if (nonAsciiChars > asciiChars) {
+ return "base64";
+ }
+ else {
+ // Q-P encoding will use fewer bytes than the full Base64.
+ return "quoted-printable";
+ }
+ }
+ }
+
+
+ /**
+ * Determine if the transfer encoding looks like it might be
+ * valid ascii text, and thus transferable as 7bit code. In
+ * order for this to be true, all characters must be valid
+ * 7-bit ASCII code AND all line breaks must be properly formed
+ * (JUST '\r\n' sequences). 7-bit transfers also
+ * typically have a line limit of 1000 bytes (998 + the CRLF), so any
+ * stretch of charactes longer than that will also force Base64 encoding.
+ *
+ * @param content An input stream for the content we're examining.
+ *
+ * @exception IOException
+ */
+ public static String getBinaryTransferEncoding(InputStream content) throws IOException {
+
+ // for efficiency, we'll read in blocks.
+ BufferedInputStream in = new BufferedInputStream(content, 4096);
+
+ int previousChar = 0;
+ int span = 0; // span of characters without a line break.
+
+ while (true) {
+ int ch = in.read();
+ // if we hit an EOF here, we've only found valid text so far, so we can transfer this as
+ // 7-bit ascii.
+ if (ch == -1) {
+ return "7bit";
+ }
+
+ // we found a newline, this is only valid if the previous char was the '\r'
+ if (ch == '\n') {
+ // malformed linebreak? force this to base64 encoding.
+ if (previousChar != '\r') {
+ return "base64";
+ }
+ // hit a line end, reset our line length counter
+ span = 0;
+ }
+ else {
+ span++;
+ // the text has long lines, we can't transfer this as unencoded text.
+ if (span > 998) {
+ return "base64";
+ }
+
+ // non-ascii character, we have to transfer this in binary.
+ if (!isAscii(ch)) {
+ return "base64";
+ }
+ }
+ previousChar = ch;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64.java
new file mode 100644
index 00000000..a6f65201
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class Base64
+{
+ private static final Encoder encoder = new Base64Encoder();
+
+ /**
+ * encode the input data producing a base 64 encoded byte array.
+ *
+ * @return a byte array containing the base 64 encoded data.
+ */
+ public static byte[] encode(
+ byte[] data)
+ {
+ // just forward to the general array encoder.
+ return encode(data, 0, data.length);
+ }
+
+ /**
+ * encode the input data producing a base 64 encoded byte array.
+ *
+ * @param data The data array to encode.
+ * @param offset The starting offset within the data array.
+ * @param length The length of the data to encode.
+ *
+ * @return a byte array containing the base 64 encoded data.
+ */
+ public static byte[] encode(
+ byte[] data,
+ int offset,
+ int length)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.encode(data, 0, data.length, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception encoding base64 string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * Encode the byte data to base 64 writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ byte[] data,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.encode(data, 0, data.length, out);
+ }
+
+ /**
+ * Encode the byte data to base 64 writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.encode(data, off, length, out);
+ }
+
+ /**
+ * decode the base 64 encoded input data. It is assumed the input data is valid.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ byte[] data)
+ {
+ // just decode the entire array of data.
+ return decode(data, 0, data.length);
+ }
+
+
+ /**
+ * decode the base 64 encoded input data. It is assumed the input data is valid.
+ *
+ * @param data The data array to decode.
+ * @param offset The offset of the data array.
+ * @param length The length of data to decode.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ byte[] data,
+ int offset,
+ int length)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, offset, length, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception decoding base64 string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the base 64 encoded String data - whitespace will be ignored.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ String data)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception decoding base64 string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the base 64 encoded String data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int decode(
+ String data,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.decode(data, out);
+ }
+
+ /**
+ * decode the base 64 encoded String data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @param data The array data to decode.
+ * @param out The output stream for the data.
+ *
+ * @return the number of bytes produced.
+ * @exception IOException
+ */
+ public static int decode(byte [] data, OutputStream out) throws IOException
+ {
+ return encoder.decode(data, 0, data.length, out);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java
new file mode 100644
index 00000000..eb767ff4
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.FilterInputStream;
+
+/**
+ * An implementation of a FilterInputStream that decodes the
+ * stream data in BASE64 encoding format. This version does the
+ * decoding "on the fly" rather than decoding a single block of
+ * data. Since this version is intended for use by the MimeUtilty class,
+ * it also handles line breaks in the encoded data.
+ */
+public class Base64DecoderStream extends FilterInputStream {
+
+ static protected final String MAIL_BASE64_IGNOREERRORS = "mail.mime.base64.ignoreerrors";
+
+ // number of decodeable units we'll try to process at one time. We'll attempt to read that much
+ // data from the input stream and decode in blocks.
+ static protected final int BUFFERED_UNITS = 2000;
+
+ // our decoder for processing the data
+ protected Base64Encoder decoder = new Base64Encoder();
+
+ // can be overridden by a system property.
+ protected boolean ignoreErrors = false;
+
+ // buffer for reading in chars for decoding (which can support larger bulk reads)
+ protected byte[] encodedChars = new byte[BUFFERED_UNITS * 4];
+ // a buffer for one decoding unit's worth of data (3 bytes). This is the minimum amount we
+ // can read at one time.
+ protected byte[] decodedChars = new byte[BUFFERED_UNITS * 3];
+ // count of characters in the buffer
+ protected int decodedCount = 0;
+ // index of the next decoded character
+ protected int decodedIndex = 0;
+
+
+ public Base64DecoderStream(InputStream in) {
+ super(in);
+ // make sure we get the ignore errors flag
+ ignoreErrors = SessionUtil.getBooleanProperty(MAIL_BASE64_IGNOREERRORS, false);
+ }
+
+ /**
+ * Test for the existance of decoded characters in our buffer
+ * of decoded data.
+ *
+ * @return True if we currently have buffered characters.
+ */
+ private boolean dataAvailable() {
+ return decodedCount != 0;
+ }
+
+ /**
+ * Get the next buffered decoded character.
+ *
+ * @return The next decoded character in the buffer.
+ */
+ private byte getBufferedChar() {
+ decodedCount--;
+ return decodedChars[decodedIndex++];
+ }
+
+ /**
+ * Decode a requested number of bytes of data into a buffer.
+ *
+ * @return true if we were able to obtain more data, false otherwise.
+ */
+ private boolean decodeStreamData() throws IOException {
+ decodedIndex = 0;
+
+ // fill up a data buffer with input data
+ int readCharacters = fillEncodedBuffer();
+
+ if (readCharacters > 0) {
+ decodedCount = decoder.decode(encodedChars, 0, readCharacters, decodedChars);
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Retrieve a single byte from the decoded characters buffer.
+ *
+ * @return The decoded character or -1 if there was an EOF condition.
+ */
+ private int getByte() throws IOException {
+ if (!dataAvailable()) {
+ if (!decodeStreamData()) {
+ return -1;
+ }
+ }
+ decodedCount--;
+ // we need to ensure this doesn't get sign extended
+ return decodedChars[decodedIndex++] & 0xff;
+ }
+
+ private int getBytes(byte[] data, int offset, int length) throws IOException {
+
+ int readCharacters = 0;
+ while (length > 0) {
+ // need data? Try to get some
+ if (!dataAvailable()) {
+ // if we can't get this, return a count of how much we did get (which may be -1).
+ if (!decodeStreamData()) {
+ return readCharacters > 0 ? readCharacters : -1;
+ }
+ }
+
+ // now copy some of the data from the decoded buffer to the target buffer
+ int copyCount = Math.min(decodedCount, length);
+ System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount);
+ decodedIndex += copyCount;
+ decodedCount -= copyCount;
+ offset += copyCount;
+ length -= copyCount;
+ readCharacters += copyCount;
+ }
+ return readCharacters;
+ }
+
+
+ /**
+ * Fill our buffer of input characters for decoding from the
+ * stream. This will attempt read a full buffer, but will
+ * terminate on an EOF or read error. This will filter out
+ * non-Base64 encoding chars and will only return a valid
+ * multiple of 4 number of bytes.
+ *
+ * @return The count of characters read.
+ */
+ private int fillEncodedBuffer() throws IOException
+ {
+ int readCharacters = 0;
+
+ while (true) {
+ // get the next character from the stream
+ int ch = in.read();
+ // did we hit an EOF condition?
+ if (ch == -1) {
+ // now check to see if this is normal, or potentially an error
+ // if we didn't get characters as a multiple of 4, we may need to complain about this.
+ if ((readCharacters % 4) != 0) {
+ // the error checking can be turned off...normally it isn't
+ if (!ignoreErrors) {
+ throw new IOException("Base64 encoding error, data truncated");
+ }
+ // we're ignoring errors, so round down to a multiple and return that.
+ return (readCharacters / 4) * 4;
+ }
+ // return the count.
+ return readCharacters;
+ }
+ // if this character is valid in a Base64 stream, copy it to the buffer.
+ else if (decoder.isValidBase64(ch)) {
+ encodedChars[readCharacters++] = (byte)ch;
+ // if we've filled up the buffer, time to quit.
+ if (readCharacters >= encodedChars.length) {
+ return readCharacters;
+ }
+ }
+
+ // we're filtering out whitespace and CRLF characters, so just ignore these
+ }
+ }
+
+
+ // in order to function as a filter, these streams need to override the different
+ // read() signature.
+
+ public int read() throws IOException
+ {
+ return getByte();
+ }
+
+
+ public int read(byte [] buffer, int offset, int length) throws IOException {
+ return getBytes(buffer, offset, length);
+ }
+
+
+ public boolean markSupported() {
+ return false;
+ }
+
+
+ public int available() throws IOException {
+ return ((in.available() / 4) * 3) + decodedCount;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java
new file mode 100644
index 00000000..e86af8e0
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64Encoder.java
@@ -0,0 +1,657 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+public class Base64Encoder
+ implements Encoder
+{
+ protected final byte[] encodingTable =
+ {
+ (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+ (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+ (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+ (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+ (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+ (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+ (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+ (byte)'v',
+ (byte)'w', (byte)'x', (byte)'y', (byte)'z',
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6',
+ (byte)'7', (byte)'8', (byte)'9',
+ (byte)'+', (byte)'/'
+ };
+
+ protected byte padding = (byte)'=';
+
+ /*
+ * set up the decoding table.
+ */
+ protected final byte[] decodingTable = new byte[256];
+
+ protected void initialiseDecodingTable()
+ {
+ for (int i = 0; i < encodingTable.length; i++)
+ {
+ decodingTable[encodingTable[i]] = (byte)i;
+ }
+ }
+
+ public Base64Encoder()
+ {
+ initialiseDecodingTable();
+ }
+
+ /**
+ * encode the input data producing a base 64 output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public int encode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ int modulus = length % 3;
+ int dataLength = (length - modulus);
+ int a1, a2, a3;
+
+ for (int i = off; i < off + dataLength; i += 3)
+ {
+ a1 = data[i] & 0xff;
+ a2 = data[i + 1] & 0xff;
+ a3 = data[i + 2] & 0xff;
+
+ out.write(encodingTable[(a1 >>> 2) & 0x3f]);
+ out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
+ out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
+ out.write(encodingTable[a3 & 0x3f]);
+ }
+
+ /*
+ * process the tail end.
+ */
+ int b1, b2, b3;
+ int d1, d2;
+
+ switch (modulus)
+ {
+ case 0: /* nothing left to do */
+ break;
+ case 1:
+ d1 = data[off + dataLength] & 0xff;
+ b1 = (d1 >>> 2) & 0x3f;
+ b2 = (d1 << 4) & 0x3f;
+
+ out.write(encodingTable[b1]);
+ out.write(encodingTable[b2]);
+ out.write(padding);
+ out.write(padding);
+ break;
+ case 2:
+ d1 = data[off + dataLength] & 0xff;
+ d2 = data[off + dataLength + 1] & 0xff;
+
+ b1 = (d1 >>> 2) & 0x3f;
+ b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
+ b3 = (d2 << 2) & 0x3f;
+
+ out.write(encodingTable[b1]);
+ out.write(encodingTable[b2]);
+ out.write(encodingTable[b3]);
+ out.write(padding);
+ break;
+ }
+
+ return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4);
+ }
+
+ private boolean ignore(
+ char c)
+ {
+ return (c == '\n' || c =='\r' || c == '\t' || c == ' ');
+ }
+
+ /**
+ * decode the base 64 encoded byte data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @return the number of bytes produced.
+ */
+ public int decode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ byte[] bytes;
+ byte b1, b2, b3, b4;
+ int outLen = 0;
+
+ int end = off + length;
+
+ while (end > 0)
+ {
+ if (!ignore((char)data[end - 1]))
+ {
+ break;
+ }
+
+ end--;
+ }
+
+ int i = off;
+ int finish = end - 4;
+
+ while (i < finish)
+ {
+ while ((i < finish) && ignore((char)data[i]))
+ {
+ i++;
+ }
+
+ b1 = decodingTable[data[i++]];
+
+ while ((i < finish) && ignore((char)data[i]))
+ {
+ i++;
+ }
+
+ b2 = decodingTable[data[i++]];
+
+ while ((i < finish) && ignore((char)data[i]))
+ {
+ i++;
+ }
+
+ b3 = decodingTable[data[i++]];
+
+ while ((i < finish) && ignore((char)data[i]))
+ {
+ i++;
+ }
+
+ b4 = decodingTable[data[i++]];
+
+ out.write((b1 << 2) | (b2 >> 4));
+ out.write((b2 << 4) | (b3 >> 2));
+ out.write((b3 << 6) | b4);
+
+ outLen += 3;
+ }
+
+ if (data[end - 2] == padding)
+ {
+ b1 = decodingTable[data[end - 4]];
+ b2 = decodingTable[data[end - 3]];
+
+ out.write((b1 << 2) | (b2 >> 4));
+
+ outLen += 1;
+ }
+ else if (data[end - 1] == padding)
+ {
+ b1 = decodingTable[data[end - 4]];
+ b2 = decodingTable[data[end - 3]];
+ b3 = decodingTable[data[end - 2]];
+
+ out.write((b1 << 2) | (b2 >> 4));
+ out.write((b2 << 4) | (b3 >> 2));
+
+ outLen += 2;
+ }
+ else
+ {
+ b1 = decodingTable[data[end - 4]];
+ b2 = decodingTable[data[end - 3]];
+ b3 = decodingTable[data[end - 2]];
+ b4 = decodingTable[data[end - 1]];
+
+ out.write((b1 << 2) | (b2 >> 4));
+ out.write((b2 << 4) | (b3 >> 2));
+ out.write((b3 << 6) | b4);
+
+ outLen += 3;
+ }
+
+ return outLen;
+ }
+
+ /**
+ * decode the base 64 encoded String data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @return the number of bytes produced.
+ */
+ public int decode(
+ String data,
+ OutputStream out)
+ throws IOException
+ {
+ byte[] bytes;
+ byte b1, b2, b3, b4;
+ int length = 0;
+
+ int end = data.length();
+
+ while (end > 0)
+ {
+ if (!ignore(data.charAt(end - 1)))
+ {
+ break;
+ }
+
+ end--;
+ }
+
+ int i = 0;
+ int finish = end - 4;
+
+ while (i < finish)
+ {
+ while ((i < finish) && ignore(data.charAt(i)))
+ {
+ i++;
+ }
+
+ b1 = decodingTable[data.charAt(i++)];
+
+ while ((i < finish) && ignore(data.charAt(i)))
+ {
+ i++;
+ }
+ b2 = decodingTable[data.charAt(i++)];
+
+ while ((i < finish) && ignore(data.charAt(i)))
+ {
+ i++;
+ }
+ b3 = decodingTable[data.charAt(i++)];
+
+ while ((i < finish) && ignore(data.charAt(i)))
+ {
+ i++;
+ }
+ b4 = decodingTable[data.charAt(i++)];
+
+ out.write((b1 << 2) | (b2 >> 4));
+ out.write((b2 << 4) | (b3 >> 2));
+ out.write((b3 << 6) | b4);
+
+ length += 3;
+ }
+
+ if (data.charAt(end - 2) == padding)
+ {
+ b1 = decodingTable[data.charAt(end - 4)];
+ b2 = decodingTable[data.charAt(end - 3)];
+
+ out.write((b1 << 2) | (b2 >> 4));
+
+ length += 1;
+ }
+ else if (data.charAt(end - 1) == padding)
+ {
+ b1 = decodingTable[data.charAt(end - 4)];
+ b2 = decodingTable[data.charAt(end - 3)];
+ b3 = decodingTable[data.charAt(end - 2)];
+
+ out.write((b1 << 2) | (b2 >> 4));
+ out.write((b2 << 4) | (b3 >> 2));
+
+ length += 2;
+ }
+ else
+ {
+ b1 = decodingTable[data.charAt(end - 4)];
+ b2 = decodingTable[data.charAt(end - 3)];
+ b3 = decodingTable[data.charAt(end - 2)];
+ b4 = decodingTable[data.charAt(end - 1)];
+
+ out.write((b1 << 2) | (b2 >> 4));
+ out.write((b2 << 4) | (b3 >> 2));
+ out.write((b3 << 6) | b4);
+
+ length += 3;
+ }
+
+ return length;
+ }
+
+ /**
+ * decode the base 64 encoded byte data writing it to the provided byte array buffer.
+ *
+ * @return the number of bytes produced.
+ */
+ public int decode(byte[] data, int off, int length, byte[] out) throws IOException
+ {
+ byte[] bytes;
+ byte b1, b2, b3, b4;
+ int outLen = 0;
+
+ int end = off + length;
+
+ while (end > 0)
+ {
+ if (!ignore((char)data[end - 1]))
+ {
+ break;
+ }
+
+ end--;
+ }
+
+ int i = off;
+ int finish = end - 4;
+
+ while (i < finish)
+ {
+ while ((i < finish) && ignore((char)data[i]))
+ {
+ i++;
+ }
+
+ b1 = decodingTable[data[i++]];
+
+ while ((i < finish) && ignore((char)data[i]))
+ {
+ i++;
+ }
+
+ b2 = decodingTable[data[i++]];
+
+ while ((i < finish) && ignore((char)data[i]))
+ {
+ i++;
+ }
+
+ b3 = decodingTable[data[i++]];
+
+ while ((i < finish) && ignore((char)data[i]))
+ {
+ i++;
+ }
+
+ b4 = decodingTable[data[i++]];
+
+ out[outLen++] = (byte)((b1 << 2) | (b2 >> 4));
+ out[outLen++] = (byte)((b2 << 4) | (b3 >> 2));
+ out[outLen++] = (byte)((b3 << 6) | b4);
+ }
+
+ if (data[end - 2] == padding)
+ {
+ b1 = decodingTable[data[end - 4]];
+ b2 = decodingTable[data[end - 3]];
+
+ out[outLen++] = (byte)((b1 << 2) | (b2 >> 4));
+ }
+ else if (data[end - 1] == padding)
+ {
+ b1 = decodingTable[data[end - 4]];
+ b2 = decodingTable[data[end - 3]];
+ b3 = decodingTable[data[end - 2]];
+
+ out[outLen++] = (byte)((b1 << 2) | (b2 >> 4));
+ out[outLen++] = (byte)((b2 << 4) | (b3 >> 2));
+ }
+ else
+ {
+ b1 = decodingTable[data[end - 4]];
+ b2 = decodingTable[data[end - 3]];
+ b3 = decodingTable[data[end - 2]];
+ b4 = decodingTable[data[end - 1]];
+
+ out[outLen++] = (byte)((b1 << 2) | (b2 >> 4));
+ out[outLen++] = (byte)((b2 << 4) | (b3 >> 2));
+ out[outLen++] = (byte)((b3 << 6) | b4);
+ }
+
+ return outLen;
+ }
+
+ /**
+ * Test if a character is a valid Base64 encoding character. This
+ * must be either a valid digit or the padding character ("=").
+ *
+ * @param ch The test character.
+ *
+ * @return true if this is valid in Base64 encoded data, false otherwise.
+ */
+ public boolean isValidBase64(int ch) {
+ // 'A' has the value 0 in the decoding table, so we need a special one for that
+ return ch == padding || ch == 'A' || decodingTable[ch] != 0;
+ }
+
+
+ /**
+ * Perform RFC-2047 word encoding using Base64 data encoding.
+ *
+ * @param in The source for the encoded data.
+ * @param charset The charset tag to be added to each encoded data section.
+ * @param out The output stream where the encoded data is to be written.
+ * @param fold Controls whether separate sections of encoded data are separated by
+ * linebreaks or whitespace.
+ *
+ * @exception IOException
+ */
+ public void encodeWord(InputStream in, String charset, OutputStream out, boolean fold) throws IOException
+ {
+ PrintStream writer = new PrintStream(out);
+
+ // encoded words are restricted to 76 bytes, including the control adornments.
+ int limit = 75 - 7 - charset.length();
+ boolean firstLine = true;
+ StringBuffer encodedString = new StringBuffer(76);
+
+ while (true) {
+ // encode the next segment.
+ encode(in, encodedString, limit);
+ // if we're out of data, nothing will be encoded.
+ if (encodedString.length() == 0) {
+ break;
+ }
+
+ // if we have more than one segment, we need to insert separators. Depending on whether folding
+ // was requested, this is either a blank or a linebreak.
+ if (!firstLine) {
+ if (fold) {
+ writer.print("\r\n");
+ }
+ else {
+ writer.print(" ");
+ }
+ }
+
+ // add the encoded word header
+ writer.print("=?");
+ writer.print(charset);
+ writer.print("?B?");
+ // the data
+ writer.print(encodedString.toString());
+ // and the word terminator.
+ writer.print("?=");
+ writer.flush();
+
+ // reset our string buffer for the next segment.
+ encodedString.setLength(0);
+ // we need a delimiter after this
+ firstLine = false;
+ }
+ }
+
+
+ /**
+ * Perform RFC-2047 word encoding using Base64 data encoding.
+ *
+ * @param in The source for the encoded data.
+ * @param charset The charset tag to be added to each encoded data section.
+ * @param out The output stream where the encoded data is to be written.
+ * @param fold Controls whether separate sections of encoded data are separated by
+ * linebreaks or whitespace.
+ *
+ * @exception IOException
+ */
+ public void encodeWord(byte[] data, StringBuffer out, String charset) throws IOException
+ {
+ // append the word header
+ out.append("=?");
+ out.append(charset);
+ out.append("?B?");
+ // add on the encodeded data
+ encodeWordData(data, out);
+ // the end of the encoding marker
+ out.append("?=");
+ }
+
+ /**
+ * encode the input data producing a base 64 output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public void encodeWordData(byte[] data, StringBuffer out)
+ {
+ int modulus = data.length % 3;
+ int dataLength = (data.length - modulus);
+ int a1, a2, a3;
+
+ for (int i = 0; i < dataLength; i += 3)
+ {
+ a1 = data[i] & 0xff;
+ a2 = data[i + 1] & 0xff;
+ a3 = data[i + 2] & 0xff;
+
+ out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
+ out.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
+ out.append((char)encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
+ out.append((char)encodingTable[a3 & 0x3f]);
+ }
+
+ /*
+ * process the tail end.
+ */
+ int b1, b2, b3;
+ int d1, d2;
+
+ switch (modulus)
+ {
+ case 0: /* nothing left to do */
+ break;
+ case 1:
+ d1 = data[dataLength] & 0xff;
+ b1 = (d1 >>> 2) & 0x3f;
+ b2 = (d1 << 4) & 0x3f;
+
+ out.append((char)encodingTable[b1]);
+ out.append((char)encodingTable[b2]);
+ out.append((char)padding);
+ out.append((char)padding);
+ break;
+ case 2:
+ d1 = data[dataLength] & 0xff;
+ d2 = data[dataLength + 1] & 0xff;
+
+ b1 = (d1 >>> 2) & 0x3f;
+ b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
+ b3 = (d2 << 2) & 0x3f;
+
+ out.append((char)encodingTable[b1]);
+ out.append((char)encodingTable[b2]);
+ out.append((char)encodingTable[b3]);
+ out.append((char)padding);
+ break;
+ }
+ }
+
+
+ /**
+ * encode the input data producing a base 64 output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public void encode(InputStream in, StringBuffer out, int limit) throws IOException
+ {
+ int count = limit / 4;
+ byte [] inBuffer = new byte[3];
+
+ while (count-- > 0) {
+
+ int readCount = in.read(inBuffer);
+ // did we get a full triplet? that's an easy encoding.
+ if (readCount == 3) {
+ int a1 = inBuffer[0] & 0xff;
+ int a2 = inBuffer[1] & 0xff;
+ int a3 = inBuffer[2] & 0xff;
+
+ out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
+ out.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
+ out.append((char)encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
+ out.append((char)encodingTable[a3 & 0x3f]);
+
+ }
+ else if (readCount <= 0) {
+ // eof condition, don'e entirely.
+ return;
+ }
+ else if (readCount == 1) {
+ int a1 = inBuffer[0] & 0xff;
+ out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
+ out.append((char)encodingTable[(a1 << 4) & 0x3f]);
+ out.append((char)padding);
+ out.append((char)padding);
+ return;
+ }
+ else if (readCount == 2) {
+ int a1 = inBuffer[0] & 0xff;
+ int a2 = inBuffer[1] & 0xff;
+
+ out.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
+ out.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
+ out.append((char)encodingTable[(a2 << 2) & 0x3f]);
+ out.append((char)padding);
+ return;
+ }
+ }
+ }
+
+
+ /**
+ * Estimate the final encoded size of a segment of data.
+ * This is used to ensure that the encoded blocks do
+ * not get split across a unicode character boundary and
+ * that the encoding will fit within the bounds of
+ * a mail header line.
+ *
+ * @param data The data we're anticipating encoding.
+ *
+ * @return The size of the byte data in encoded form.
+ */
+ public int estimateEncodedLength(byte[] data)
+ {
+ return ((data.length + 2) / 3) * 4;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64EncoderStream.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64EncoderStream.java
new file mode 100644
index 00000000..0aa33652
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Base64EncoderStream.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.FilterOutputStream;
+
+/**
+ * An implementation of a FilterOutputStream that encodes the
+ * stream data in BASE64 encoding format. This version does the
+ * encoding "on the fly" rather than encoding a single block of
+ * data. Since this version is intended for use by the MimeUtilty class,
+ * it also handles line breaks in the encoded data.
+ */
+public class Base64EncoderStream extends FilterOutputStream {
+
+ // our Filtered stream writes everything out as byte data. This allows the CRLF sequence to
+ // be written with a single call.
+ protected static final byte[] CRLF = { '\r', '\n' };
+
+ // our hex encoder utility class.
+ protected Base64Encoder encoder = new Base64Encoder();
+
+ // our default for line breaks
+ protected static final int DEFAULT_LINEBREAK = 76;
+
+ // Data can only be written out in complete units of 3 bytes encoded as 4. Therefore, we need to buffer
+ // as many as 2 bytes to fill out an encoding unit.
+
+ // the buffered byte count
+ protected int bufferedBytes = 0;
+
+ // we'll encode this part once it is filled up.
+ protected byte[] buffer = new byte[3];
+
+
+ // the size we process line breaks at. If this is Integer.MAX_VALUE, no line breaks are handled.
+ protected int lineBreak;
+
+ // the number of encoded characters we've written to the stream, which determines where we
+ // insert line breaks.
+ protected int outputCount;
+
+ /**
+ * Create a Base64 encoder stream that wraps a specifed stream
+ * using the default line break size.
+ *
+ * @param out The wrapped output stream.
+ */
+ public Base64EncoderStream(OutputStream out) {
+ this(out, DEFAULT_LINEBREAK);
+ }
+
+
+ public Base64EncoderStream(OutputStream out, int lineBreak) {
+ super(out);
+ // lines are processed only in multiple of 4, so round this down.
+ this.lineBreak = (lineBreak / 4) * 4 ;
+ }
+
+ // in order for this to work, we need to override the 3 different signatures for write
+
+ public void write(int ch) throws IOException {
+ // store this in the buffer.
+ buffer[bufferedBytes++] = (byte)ch;
+ // if the buffer is filled, encode these bytes
+ if (bufferedBytes == 3) {
+ // check for room in the current line for this character
+ checkEOL(4);
+ // write these directly to the stream.
+ encoder.encode(buffer, 0, 3, out);
+ bufferedBytes = 0;
+ // and update the line length checkers
+ updateLineCount(4);
+ }
+ }
+
+ public void write(byte [] data) throws IOException {
+ write(data, 0, data.length);
+ }
+
+ public void write(byte [] data, int offset, int length) throws IOException {
+ // if we have something in the buffer, we need to write enough bytes out to flush
+ // those into the output stream AND continue on to finish off a line. Once we're done there
+ // we can write additional data out in complete blocks.
+ while ((bufferedBytes > 0 || outputCount > 0) && length > 0) {
+ write(data[offset++]);
+ length--;
+ }
+
+ if (length > 0) {
+ // no linebreaks requested? YES!!!!!, we can just dispose of the lot with one call.
+ if (lineBreak == Integer.MAX_VALUE) {
+ encoder.encode(data, offset, length, out);
+ }
+ else {
+ // calculate the size of a segment we can encode directly as a line.
+ int segmentSize = (lineBreak / 4) * 3;
+
+ // write this out a block at a time, with separators between.
+ while (length > segmentSize) {
+ // encode a segment
+ encoder.encode(data, offset, segmentSize, out);
+ // write an EOL marker
+ out.write(CRLF);
+ offset += segmentSize;
+ length -= segmentSize;
+ }
+
+ // any remainder we write out a byte at a time to manage the groupings and
+ // the line count appropriately.
+ if (length > 0) {
+ while (length > 0) {
+ write(data[offset++]);
+ length--;
+ }
+ }
+ }
+ }
+ }
+
+ public void close() throws IOException {
+ flush();
+ out.close();
+ }
+
+ public void flush() throws IOException {
+ if (bufferedBytes > 0) {
+ encoder.encode(buffer, 0, bufferedBytes, out);
+ bufferedBytes = 0;
+ }
+ }
+
+
+ /**
+ * Check for whether we're about the reach the end of our
+ * line limit for an update that's about to occur. If we will
+ * overflow, then a line break is inserted.
+ *
+ * @param required The space required for this pending write.
+ *
+ * @exception IOException
+ */
+ private void checkEOL(int required) throws IOException {
+ if (lineBreak != Integer.MAX_VALUE) {
+ // if this write would exceed the line maximum, add a linebreak to the stream.
+ if (outputCount + required > lineBreak) {
+ out.write(CRLF);
+ outputCount = 0;
+ }
+ }
+ }
+
+ /**
+ * Update the counter of characters on the current working line.
+ * This is conditional if we're not working with a line limit.
+ *
+ * @param added The number of characters just added.
+ */
+ private void updateLineCount(int added) {
+ if (lineBreak != Integer.MAX_VALUE) {
+ outputCount += added;
+ }
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Encoder.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Encoder.java
new file mode 100644
index 00000000..462d7647
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Encoder.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Encode and decode byte arrays (typically from binary to 7-bit ASCII
+ * encodings).
+ */
+public interface Encoder
+{
+ int encode(byte[] data, int off, int length, OutputStream out) throws IOException;
+
+ int decode(byte[] data, int off, int length, OutputStream out) throws IOException;
+
+ int decode(String data, OutputStream out) throws IOException;
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Hex.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Hex.java
new file mode 100644
index 00000000..cc9d6df5
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/Hex.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class Hex
+{
+ private static final Encoder encoder = new HexEncoder();
+
+ /**
+ * encode the input data producing a Hex encoded byte array.
+ *
+ * @return a byte array containing the Hex encoded data.
+ */
+ public static byte[] encode(
+ byte[] data)
+ {
+ return encode(data, 0, data.length);
+ }
+
+ /**
+ * encode the input data producing a Hex encoded byte array.
+ *
+ * @return a byte array containing the Hex encoded data.
+ */
+ public static byte[] encode(
+ byte[] data,
+ int off,
+ int length)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.encode(data, off, length, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception encoding Hex string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * Hex encode the byte data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ byte[] data,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.encode(data, 0, data.length, out);
+ }
+
+ /**
+ * Hex encode the byte data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.encode(data, 0, data.length, out);
+ }
+
+ /**
+ * decode the Hex encoded input data. It is assumed the input data is valid.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ byte[] data)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, 0, data.length, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception decoding Hex string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the Hex encoded String data - whitespace will be ignored.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ String data)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception decoding Hex string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the Hex encoded String data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int decode(
+ String data,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.decode(data, out);
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/HexEncoder.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/HexEncoder.java
new file mode 100644
index 00000000..cb46a0f6
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/HexEncoder.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class HexEncoder
+ implements Encoder
+{
+ protected final byte[] encodingTable =
+ {
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7',
+ (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f'
+ };
+
+ /*
+ * set up the decoding table.
+ */
+ protected final byte[] decodingTable = new byte[128];
+
+ protected void initialiseDecodingTable()
+ {
+ for (int i = 0; i < encodingTable.length; i++)
+ {
+ decodingTable[encodingTable[i]] = (byte)i;
+ }
+
+ decodingTable['A'] = decodingTable['a'];
+ decodingTable['B'] = decodingTable['b'];
+ decodingTable['C'] = decodingTable['c'];
+ decodingTable['D'] = decodingTable['d'];
+ decodingTable['E'] = decodingTable['e'];
+ decodingTable['F'] = decodingTable['f'];
+ }
+
+ public HexEncoder()
+ {
+ initialiseDecodingTable();
+ }
+
+ /**
+ * encode the input data producing a Hex output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public int encode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ for (int i = off; i < (off + length); i++)
+ {
+ int v = data[i] & 0xff;
+
+ out.write(encodingTable[(v >>> 4)]);
+ out.write(encodingTable[v & 0xf]);
+ }
+
+ return length * 2;
+ }
+
+ private boolean ignore(
+ char c)
+ {
+ return (c == '\n' || c =='\r' || c == '\t' || c == ' ');
+ }
+
+ /**
+ * decode the Hex encoded byte data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @return the number of bytes produced.
+ */
+ public int decode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ byte[] bytes;
+ byte b1, b2;
+ int outLen = 0;
+
+ int end = off + length;
+
+ while (end > 0)
+ {
+ if (!ignore((char)data[end - 1]))
+ {
+ break;
+ }
+
+ end--;
+ }
+
+ int i = off;
+ while (i < end)
+ {
+ while (i < end && ignore((char)data[i]))
+ {
+ i++;
+ }
+
+ b1 = decodingTable[data[i++]];
+
+ while (i < end && ignore((char)data[i]))
+ {
+ i++;
+ }
+
+ b2 = decodingTable[data[i++]];
+
+ out.write((b1 << 4) | b2);
+
+ outLen++;
+ }
+
+ return outLen;
+ }
+
+ /**
+ * decode the Hex encoded String data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @return the number of bytes produced.
+ */
+ public int decode(
+ String data,
+ OutputStream out)
+ throws IOException
+ {
+ byte[] bytes;
+ byte b1, b2, b3, b4;
+ int length = 0;
+
+ int end = data.length();
+
+ while (end > 0)
+ {
+ if (!ignore(data.charAt(end - 1)))
+ {
+ break;
+ }
+
+ end--;
+ }
+
+ int i = 0;
+ while (i < end)
+ {
+ while (i < end && ignore(data.charAt(i)))
+ {
+ i++;
+ }
+
+ b1 = decodingTable[data.charAt(i++)];
+
+ while (i < end && ignore(data.charAt(i)))
+ {
+ i++;
+ }
+
+ b2 = decodingTable[data.charAt(i++)];
+
+ out.write((b1 << 4) | b2);
+
+ length++;
+ }
+
+ return length;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintable.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintable.java
new file mode 100644
index 00000000..9996f6d0
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintable.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class QuotedPrintable {
+ // NOTE: the QuotedPrintableEncoder class needs to keep some active state about what's going on with
+ // respect to line breaks and significant white spaces. This makes it difficult to keep one static
+ // instance of the decode around for reuse.
+
+
+ /**
+ * encode the input data producing a Q-P encoded byte array.
+ *
+ * @return a byte array containing the Q-P encoded data.
+ */
+ public static byte[] encode(
+ byte[] data)
+ {
+ return encode(data, 0, data.length);
+ }
+
+ /**
+ * encode the input data producing a Q-P encoded byte array.
+ *
+ * @return a byte array containing the Q-P encoded data.
+ */
+ public static byte[] encode(
+ byte[] data,
+ int off,
+ int length)
+ {
+ QuotedPrintableEncoder encoder = new QuotedPrintableEncoder();
+
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.encode(data, off, length, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception encoding Q-P encoded string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * Q-P encode the byte data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ byte[] data,
+ OutputStream out)
+ throws IOException
+ {
+ QuotedPrintableEncoder encoder = new QuotedPrintableEncoder();
+
+ return encoder.encode(data, 0, data.length, out);
+ }
+
+ /**
+ * Q-P encode the byte data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ QuotedPrintableEncoder encoder = new QuotedPrintableEncoder();
+ return encoder.encode(data, 0, data.length, out);
+ }
+
+ /**
+ * decode the Q-P encoded input data. It is assumed the input data is valid.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ byte[] data)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ QuotedPrintableEncoder encoder = new QuotedPrintableEncoder();
+ try
+ {
+ encoder.decode(data, 0, data.length, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception decoding Q-P encoded string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the UUEncided String data.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ String data)
+ {
+ QuotedPrintableEncoder encoder = new QuotedPrintableEncoder();
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception decoding Q-P encoded string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the Q-P encoded encoded String data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int decode(
+ String data,
+ OutputStream out)
+ throws IOException
+ {
+ QuotedPrintableEncoder encoder = new QuotedPrintableEncoder();
+ return encoder.decode(data, out);
+ }
+
+ /**
+ * decode the base Q-P encoded String data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @param data The array data to decode.
+ * @param out The output stream for the data.
+ *
+ * @return the number of bytes produced.
+ * @exception IOException
+ */
+ public static int decode(byte [] data, OutputStream out) throws IOException
+ {
+ QuotedPrintableEncoder encoder = new QuotedPrintableEncoder();
+ return encoder.decode(data, 0, data.length, out);
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableDecoderStream.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableDecoderStream.java
new file mode 100644
index 00000000..75696104
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableDecoderStream.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * An implementation of a FilterOutputStream that decodes the
+ * stream data in Q-P encoding format. This version does the
+ * decoding "on the fly" rather than decoding a single block of
+ * data. Since this version is intended for use by the MimeUtilty class,
+ * it also handles line breaks in the encoded data.
+ */
+public class QuotedPrintableDecoderStream extends FilterInputStream {
+ // our decoder for processing the data
+ protected QuotedPrintableEncoder decoder;
+
+
+ /**
+ * Stream constructor.
+ *
+ * @param in The InputStream this stream is filtering.
+ */
+ public QuotedPrintableDecoderStream(InputStream in) {
+ super(in);
+ decoder = new QuotedPrintableEncoder();
+ }
+
+ // in order to function as a filter, these streams need to override the different
+ // read() signatures.
+
+
+ /**
+ * Read a single byte from the stream.
+ *
+ * @return The next byte of the stream. Returns -1 for an EOF condition.
+ * @exception IOException
+ */
+ public int read() throws IOException
+ {
+ // just get a single byte from the decoder
+ return decoder.decode(in);
+ }
+
+
+ /**
+ * Read a buffer of data from the input stream.
+ *
+ * @param buffer The target byte array the data is placed into.
+ * @param offset The starting offset for the read data.
+ * @param length How much data is requested.
+ *
+ * @return The number of bytes of data read.
+ * @exception IOException
+ */
+ public int read(byte [] buffer, int offset, int length) throws IOException {
+
+ for (int i = 0; i < length; i++) {
+ int ch = decoder.decode(in);
+ if (ch == -1) {
+ return i == 0 ? -1 : i;
+ }
+ buffer[offset + i] = (byte)ch;
+ }
+
+ return length;
+ }
+
+
+ /**
+ * Indicate whether this stream supports the mark() operation.
+ *
+ * @return Always returns false.
+ */
+ public boolean markSupported() {
+ return false;
+ }
+
+
+ /**
+ * Give an estimate of how much additional data is available
+ * from this stream.
+ *
+ * @return Always returns -1.
+ * @exception IOException
+ */
+ public int available() throws IOException {
+ // this is almost impossible to determine at this point
+ return -1;
+ }
+}
+
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java
new file mode 100644
index 00000000..217c6dda
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoder.java
@@ -0,0 +1,777 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PushbackInputStream;
+import java.io.UnsupportedEncodingException;
+
+public class QuotedPrintableEncoder implements Encoder {
+
+ static protected final byte[] encodingTable =
+ {
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7',
+ (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F'
+ };
+
+ /*
+ * set up the decoding table.
+ */
+ static protected final byte[] decodingTable = new byte[128];
+
+ static {
+ // initialize the decoding table
+ for (int i = 0; i < encodingTable.length; i++)
+ {
+ decodingTable[encodingTable[i]] = (byte)i;
+ }
+ }
+
+
+ // default number of characters we will write per line.
+ static private final int DEFAULT_CHARS_PER_LINE = 76;
+
+ // the output stream we're wrapped around
+ protected OutputStream out;
+ // the number of bytes written;
+ protected int bytesWritten = 0;
+ // number of bytes written on the current line
+ protected int lineCount = 0;
+ // line length we're dealing with
+ protected int lineLength;
+ // number of deferred whitespace characters in decode mode.
+ protected int deferredWhitespace = 0;
+
+ protected int cachedCharacter = -1;
+
+ // indicates whether the last character was a '\r', potentially part of a CRLF sequence.
+ protected boolean lastCR = false;
+ // remember whether last character was a white space.
+ protected boolean lastWhitespace = false;
+
+ public QuotedPrintableEncoder() {
+ this(null, DEFAULT_CHARS_PER_LINE);
+ }
+
+ public QuotedPrintableEncoder(OutputStream out) {
+ this(out, DEFAULT_CHARS_PER_LINE);
+ }
+
+ public QuotedPrintableEncoder(OutputStream out, int lineLength) {
+ this.out = out;
+ this.lineLength = lineLength;
+ }
+
+ private void checkDeferred(int ch) throws IOException {
+ // was the last character we looked at a whitespace? Try to decide what to do with it now.
+ if (lastWhitespace) {
+ // if this whitespace is at the end of the line, write it out encoded
+ if (ch == '\r' || ch == '\n') {
+ writeEncodedCharacter(' ');
+ }
+ else {
+ // we can write this out without encoding.
+ writeCharacter(' ');
+ }
+ // we always turn this off.
+ lastWhitespace = false;
+ }
+ // deferred carriage return?
+ else if (lastCR) {
+ // if the char following the CR was not a new line, write an EOL now.
+ if (ch != '\n') {
+ writeEOL();
+ }
+ // we always turn this off too
+ lastCR = false;
+ }
+ }
+
+
+ /**
+ * encode the input data producing a UUEncoded output stream.
+ *
+ * @param data The array of byte data.
+ * @param off The starting offset within the data.
+ * @param length Length of the data to encode.
+ *
+ * @return the number of bytes produced.
+ */
+ public int encode(byte[] data, int off, int length) throws IOException {
+ int endOffset = off + length;
+
+ while (off < endOffset) {
+ // get the character
+ byte ch = data[off++];
+
+ // handle the encoding of this character.
+ encode(ch);
+ }
+
+ return bytesWritten;
+ }
+
+
+ public void encode(int ch) throws IOException {
+ // make sure this is just a single byte value.
+ ch = ch &0xFF;
+
+ // see if we had to defer handling of a whitespace or '\r' character, and handle it if necessary.
+ checkDeferred(ch);
+ // different characters require special handling.
+ switch (ch) {
+ // spaces require special handling. If the next character is a line terminator, then
+ // the space needs to be encoded.
+ case ' ':
+ {
+ // at this point, we don't know whether this needs encoding or not. If the next
+ // character is a linend, it gets encoded. If anything else, we just write it as is.
+ lastWhitespace = true;
+ // turn off any CR flags.
+ lastCR = false;
+ break;
+ }
+
+ // carriage return, which may be part of a CRLF sequence.
+ case '\r':
+ {
+ // just flag this until we see the next character.
+ lastCR = true;
+ break;
+ }
+
+ // a new line character...we need to check to see if it was paired up with a '\r' char.
+ case '\n':
+ {
+ // we always write this out for a newline. We defer CRs until we see if the LF follows.
+ writeEOL();
+ break;
+ }
+
+ // an '=' is the escape character for an encoded character, so it must also
+ // be written encoded.
+ case '=':
+ {
+ writeEncodedCharacter(ch);
+ break;
+ }
+
+ // all other characters. If outside the printable character range, write it encoded.
+ default:
+ {
+ if (ch < 32 || ch >= 127) {
+ writeEncodedCharacter(ch);
+ }
+ else {
+ writeCharacter(ch);
+ }
+ break;
+ }
+ }
+ }
+
+
+ /**
+ * encode the input data producing a UUEncoded output stream.
+ *
+ * @param data The array of byte data.
+ * @param off The starting offset within the data.
+ * @param length Length of the data to encode.
+ *
+ * @return the number of bytes produced.
+ */
+ public int encode(byte[] data, int off, int length, String specials) throws IOException {
+ int endOffset = off + length;
+
+ while (off < endOffset) {
+ // get the character
+ byte ch = data[off++];
+
+ // handle the encoding of this character.
+ encode(ch, specials);
+ }
+
+ return bytesWritten;
+ }
+
+
+ /**
+ * encode the input data producing a UUEncoded output stream.
+ *
+ * @param data The array of byte data.
+ * @param off The starting offset within the data.
+ * @param length Length of the data to encode.
+ *
+ * @return the number of bytes produced.
+ */
+ public int encode(PushbackInputStream in, StringBuffer out, String specials, int limit) throws IOException {
+ int count = 0;
+
+ while (count < limit) {
+ int ch = in.read();
+
+ if (ch == -1) {
+ return count;
+ }
+ // make sure this is just a single byte value.
+ ch = ch &0xFF;
+
+ // spaces require special handling. If the next character is a line terminator, then
+ // the space needs to be encoded.
+ if (ch == ' ') {
+ // blanks get translated into underscores, because the encoded tokens can't have embedded blanks.
+ out.append('_');
+ count++;
+ }
+ // non-ascii chars and the designated specials all get encoded.
+ else if (ch < 32 || ch >= 127 || specials.indexOf(ch) != -1) {
+ // we need at least 3 characters to write this out, so we need to
+ // forget we saw this one and try in the next segment.
+ if (count + 3 > limit) {
+ in.unread(ch);
+ return count;
+ }
+ out.append('=');
+ out.append((char)encodingTable[ch >> 4]);
+ out.append((char)encodingTable[ch & 0x0F]);
+ count += 3;
+ }
+ else {
+ // good character, just use unchanged.
+ out.append((char)ch);
+ count++;
+ }
+ }
+ return count;
+ }
+
+
+ /**
+ * Specialized version of the decoder that handles encoding of
+ * RFC 2047 encoded word values. This has special handling for
+ * certain characters, but less special handling for blanks and
+ * linebreaks.
+ *
+ * @param ch
+ * @param specials
+ *
+ * @exception IOException
+ */
+ public void encode(int ch, String specials) throws IOException {
+ // make sure this is just a single byte value.
+ ch = ch &0xFF;
+
+ // spaces require special handling. If the next character is a line terminator, then
+ // the space needs to be encoded.
+ if (ch == ' ') {
+ // blanks get translated into underscores, because the encoded tokens can't have embedded blanks.
+ writeCharacter('_');
+ }
+ // non-ascii chars and the designated specials all get encoded.
+ else if (ch < 32 || ch >= 127 || specials.indexOf(ch) != -1) {
+ writeEncodedCharacter(ch);
+ }
+ else {
+ // good character, just use unchanged.
+ writeCharacter(ch);
+ }
+ }
+
+
+ /**
+ * encode the input data producing a UUEncoded output stream.
+ *
+ * @param data The array of byte data.
+ * @param off The starting offset within the data.
+ * @param length Length of the data to encode.
+ * @param out The output stream the encoded data is written to.
+ *
+ * @return the number of bytes produced.
+ */
+ public int encode(byte[] data, int off, int length, OutputStream out) throws IOException {
+ // make sure we're writing to the correct stream
+ this.out = out;
+ bytesWritten = 0;
+
+ // do the actual encoding
+ return encode(data, off, length);
+ }
+
+
+ /**
+ * decode the uuencoded byte data writing it to the given output stream
+ *
+ * @param data The array of byte data to decode.
+ * @param off Starting offset within the array.
+ * @param length The length of data to encode.
+ * @param out The output stream used to return the decoded data.
+ *
+ * @return the number of bytes produced.
+ * @exception IOException
+ */
+ public int decode(byte[] data, int off, int length, OutputStream out) throws IOException {
+ // make sure we're writing to the correct stream
+ this.out = out;
+
+ int endOffset = off + length;
+ int bytesWritten = 0;
+
+ while (off < endOffset) {
+ byte ch = data[off++];
+
+ // space characters are a pain. We need to scan ahead until we find a non-space character.
+ // if the character is a line terminator, we need to discard the blanks.
+ if (ch == ' ') {
+ int trailingSpaces = 1;
+ // scan forward, counting the characters.
+ while (off < endOffset && data[off] == ' ') {
+ // step forward and count this.
+ off++;
+ trailingSpaces++;
+ }
+ // is this a lineend at the current location?
+ if (off >= endOffset || data[off] == '\r' || data[off] == '\n') {
+ // go to the next one
+ continue;
+ }
+ else {
+ // make sure we account for the spaces in the output count.
+ bytesWritten += trailingSpaces;
+ // write out the blank characters we counted and continue with the non-blank.
+ while (trailingSpaces-- > 0) {
+ out.write(' ');
+ }
+ }
+ }
+ else if (ch == '=') {
+ // we found an encoded character. Reduce the 3 char sequence to one.
+ // but first, make sure we have two characters to work with.
+ if (off + 1 >= endOffset) {
+ throw new IOException("Invalid quoted printable encoding");
+ }
+ // convert the two bytes back from hex.
+ byte b1 = data[off++];
+ byte b2 = data[off++];
+
+ // we've found an encoded carriage return. The next char needs to be a newline
+ if (b1 == '\r') {
+ if (b2 != '\n') {
+ throw new IOException("Invalid quoted printable encoding");
+ }
+ // this was a soft linebreak inserted by the encoding. We just toss this away
+ // on decode.
+ }
+ else {
+ // this is a hex pair we need to convert back to a single byte.
+ b1 = decodingTable[b1];
+ b2 = decodingTable[b2];
+ out.write((b1 << 4) | b2);
+ // 3 bytes in, one byte out
+ bytesWritten++;
+ }
+ }
+ else {
+ // simple character, just write it out.
+ out.write(ch);
+ bytesWritten++;
+ }
+ }
+
+ return bytesWritten;
+ }
+
+ /**
+ * Decode a byte array of data.
+ *
+ * @param data The data array.
+ * @param out The output stream target for the decoded data.
+ *
+ * @return The number of bytes written to the stream.
+ * @exception IOException
+ */
+ public int decodeWord(byte[] data, OutputStream out) throws IOException {
+ return decodeWord(data, 0, data.length, out);
+ }
+
+
+ /**
+ * decode the uuencoded byte data writing it to the given output stream
+ *
+ * @param data The array of byte data to decode.
+ * @param off Starting offset within the array.
+ * @param length The length of data to encode.
+ * @param out The output stream used to return the decoded data.
+ *
+ * @return the number of bytes produced.
+ * @exception IOException
+ */
+ public int decodeWord(byte[] data, int off, int length, OutputStream out) throws IOException {
+ // make sure we're writing to the correct stream
+ this.out = out;
+
+ int endOffset = off + length;
+ int bytesWritten = 0;
+
+ while (off < endOffset) {
+ byte ch = data[off++];
+
+ // space characters were translated to '_' on encode, so we need to translate them back.
+ if (ch == '_') {
+ out.write(' ');
+ }
+ else if (ch == '=') {
+ // we found an encoded character. Reduce the 3 char sequence to one.
+ // but first, make sure we have two characters to work with.
+ if (off + 1 >= endOffset) {
+ throw new IOException("Invalid quoted printable encoding");
+ }
+ // convert the two bytes back from hex.
+ byte b1 = data[off++];
+ byte b2 = data[off++];
+
+ // we've found an encoded carriage return. The next char needs to be a newline
+ if (b1 == '\r') {
+ if (b2 != '\n') {
+ throw new IOException("Invalid quoted printable encoding");
+ }
+ // this was a soft linebreak inserted by the encoding. We just toss this away
+ // on decode.
+ }
+ else {
+ // this is a hex pair we need to convert back to a single byte.
+ byte c1 = decodingTable[b1];
+ byte c2 = decodingTable[b2];
+ out.write((c1 << 4) | c2);
+ // 3 bytes in, one byte out
+ bytesWritten++;
+ }
+ }
+ else {
+ // simple character, just write it out.
+ out.write(ch);
+ bytesWritten++;
+ }
+ }
+
+ return bytesWritten;
+ }
+
+
+ /**
+ * decode the UUEncoded String data writing it to the given output stream.
+ *
+ * @param data The String data to decode.
+ * @param out The output stream to write the decoded data to.
+ *
+ * @return the number of bytes produced.
+ * @exception IOException
+ */
+ public int decode(String data, OutputStream out) throws IOException {
+ try {
+ // just get the byte data and decode.
+ byte[] bytes = data.getBytes("US-ASCII");
+ return decode(bytes, 0, bytes.length, out);
+ } catch (UnsupportedEncodingException e) {
+ throw new IOException("Invalid UUEncoding");
+ }
+ }
+
+ private void checkLineLength(int required) throws IOException {
+ // if we're at our line length limit, write out a soft line break and reset.
+ if ((lineCount + required) >= lineLength ) {
+ out.write('=');
+ out.write('\r');
+ out.write('\n');
+ bytesWritten += 3;
+ lineCount = 0;
+ }
+ }
+
+
+ public void writeEncodedCharacter(int ch) throws IOException {
+ // we need 3 characters for an encoded value
+ checkLineLength(3);
+ out.write('=');
+ out.write(encodingTable[ch >> 4]);
+ out.write(encodingTable[ch & 0x0F]);
+ lineCount += 3;
+ bytesWritten += 3;
+ }
+
+
+ public void writeCharacter(int ch) throws IOException {
+ // we need 3 characters for an encoded value
+ checkLineLength(1);
+ out.write(ch);
+ lineCount++;
+ bytesWritten++;
+ }
+
+
+ public void writeEOL() throws IOException {
+ out.write('\r');
+ out.write('\n');
+ lineCount = 0;
+ bytesWritten += 3;
+ }
+
+
+ public int decode(InputStream in) throws IOException {
+
+ // we potentially need to scan over spans of whitespace characters to determine if they're real
+ // we just return blanks until the count goes to zero.
+ if (deferredWhitespace > 0) {
+ deferredWhitespace--;
+ return ' ';
+ }
+
+ // we may have needed to scan ahead to find the first non-blank character, which we would store here.
+ // hand that back once we're done with the blanks.
+ if (cachedCharacter != -1) {
+ int result = cachedCharacter;
+ cachedCharacter = -1;
+ return result;
+ }
+
+ int ch = in.read();
+
+ // reflect back an EOF condition.
+ if (ch == -1) {
+ return -1;
+ }
+
+ // space characters are a pain. We need to scan ahead until we find a non-space character.
+ // if the character is a line terminator, we need to discard the blanks.
+ if (ch == ' ') {
+ // scan forward, counting the characters.
+ while ((ch = in.read()) == ' ') {
+ deferredWhitespace++;
+ }
+
+ // is this a lineend at the current location?
+ if (ch == -1 || ch == '\r' || ch == '\n') {
+ // those blanks we so zealously counted up don't really exist. Clear out the counter.
+ deferredWhitespace = 0;
+ // return the real significant character now.
+ return ch;
+ }
+ // remember this character for later, after we've used up the deferred blanks.
+ cachedCharacter = decodeNonspaceChar(in, ch);
+ // return this space. We did not include this one in the deferred count, so we're right in sync.
+ return ' ';
+ }
+ return decodeNonspaceChar(in, ch);
+ }
+
+ private int decodeNonspaceChar(InputStream in, int ch) throws IOException {
+ if (ch == '=') {
+ int b1 = in.read();
+ // we need to get two characters after the quotation marker
+ if (b1 == -1) {
+ throw new IOException("Truncated quoted printable data");
+ }
+ int b2 = in.read();
+ // we need to get two characters after the quotation marker
+ if (b2 == -1) {
+ throw new IOException("Truncated quoted printable data");
+ }
+
+ // we've found an encoded carriage return. The next char needs to be a newline
+ if (b1 == '\r') {
+ if (b2 != '\n') {
+ throw new IOException("Invalid quoted printable encoding");
+ }
+ // this was a soft linebreak inserted by the encoding. We just toss this away
+ // on decode. We need to return something, so recurse and decode the next.
+ return decode(in);
+ }
+ else {
+ // this is a hex pair we need to convert back to a single byte.
+ b1 = decodingTable[b1];
+ b2 = decodingTable[b2];
+ return (b1 << 4) | b2;
+ }
+ }
+ else {
+ return ch;
+ }
+ }
+
+
+ /**
+ * Perform RFC-2047 word encoding using Q-P data encoding.
+ *
+ * @param in The source for the encoded data.
+ * @param charset The charset tag to be added to each encoded data section.
+ * @param specials The set of special characters that we require to encoded.
+ * @param out The output stream where the encoded data is to be written.
+ * @param fold Controls whether separate sections of encoded data are separated by
+ * linebreaks or whitespace.
+ *
+ * @exception IOException
+ */
+ public void encodeWord(InputStream in, String charset, String specials, OutputStream out, boolean fold) throws IOException
+ {
+ // we need to scan ahead in a few places, which may require pushing characters back on to the stream.
+ // make sure we have a stream where this is possible.
+ PushbackInputStream inStream = new PushbackInputStream(in);
+ PrintStream writer = new PrintStream(out);
+
+ // segments of encoded data are limited to 75 byes, including the control sections.
+ int limit = 75 - 7 - charset.length();
+ boolean firstLine = true;
+ StringBuffer encodedString = new StringBuffer(76);
+
+ while (true) {
+
+ // encode another segment of data.
+ encode(inStream, encodedString, specials, limit);
+ // nothing encoded means we've hit the end of the data.
+ if (encodedString.length() == 0) {
+ break;
+ }
+ // if we have more than one segment, we need to insert separators. Depending on whether folding
+ // was requested, this is either a blank or a linebreak.
+ if (!firstLine) {
+ if (fold) {
+ writer.print("\r\n");
+ }
+ else {
+ writer.print(" ");
+ }
+ }
+
+ // add the encoded word header
+ writer.print("=?");
+ writer.print(charset);
+ writer.print("?Q?");
+ // the data
+ writer.print(encodedString.toString());
+ // and the terminator mark
+ writer.print("?=");
+ writer.flush();
+
+ // we reset the string buffer and reuse it.
+ encodedString.setLength(0);
+ // we need a delimiter between sections from this point on.
+ firstLine = false;
+ }
+ }
+
+
+ /**
+ * Perform RFC-2047 word encoding using Base64 data encoding.
+ *
+ * @param in The source for the encoded data.
+ * @param charset The charset tag to be added to each encoded data section.
+ * @param out The output stream where the encoded data is to be written.
+ * @param fold Controls whether separate sections of encoded data are separated by
+ * linebreaks or whitespace.
+ *
+ * @exception IOException
+ */
+ public void encodeWord(byte[] data, StringBuffer out, String charset, String specials) throws IOException
+ {
+ // append the word header
+ out.append("=?");
+ out.append(charset);
+ out.append("?Q?");
+ // add on the encodeded data
+ encodeWordData(data, out, specials);
+ // the end of the encoding marker
+ out.append("?=");
+ }
+
+
+ /**
+ * Perform RFC-2047 word encoding using Q-P data encoding.
+ *
+ * @param in The source for the encoded data.
+ * @param charset The charset tag to be added to each encoded data section.
+ * @param specials The set of special characters that we require to encoded.
+ * @param out The output stream where the encoded data is to be written.
+ * @param fold Controls whether separate sections of encoded data are separated by
+ * linebreaks or whitespace.
+ *
+ * @exception IOException
+ */
+ public void encodeWordData(byte[] data, StringBuffer out, String specials) throws IOException {
+ for (int i = 0; i < data.length; i++) {
+ int ch = data[i] & 0xff; ;
+
+ // spaces require special handling. If the next character is a line terminator, then
+ // the space needs to be encoded.
+ if (ch == ' ') {
+ // blanks get translated into underscores, because the encoded tokens can't have embedded blanks.
+ out.append('_');
+ }
+ // non-ascii chars and the designated specials all get encoded.
+ else if (ch < 32 || ch >= 127 || specials.indexOf(ch) != -1) {
+ out.append('=');
+ out.append((char)encodingTable[ch >> 4]);
+ out.append((char)encodingTable[ch & 0x0F]);
+ }
+ else {
+ // good character, just use unchanged.
+ out.append((char)ch);
+ }
+ }
+ }
+
+
+ /**
+ * Estimate the final encoded size of a segment of data.
+ * This is used to ensure that the encoded blocks do
+ * not get split across a unicode character boundary and
+ * that the encoding will fit within the bounds of
+ * a mail header line.
+ *
+ * @param data The data we're anticipating encoding.
+ *
+ * @return The size of the byte data in encoded form.
+ */
+ public int estimateEncodedLength(byte[] data, String specials)
+ {
+ int count = 0;
+
+ for (int i = 0; i < data.length; i++) {
+ // make sure this is just a single byte value.
+ int ch = data[i] & 0xff;
+
+ // non-ascii chars and the designated specials all get encoded.
+ if (ch < 32 || ch >= 127 || specials.indexOf(ch) != -1) {
+ // Q encoding translates a single char into 3 characters
+ count += 3;
+ }
+ else {
+ // non-encoded character
+ count++;
+ }
+ }
+ return count;
+ }
+}
+
+
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoderStream.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoderStream.java
new file mode 100644
index 00000000..5c9e48df
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/QuotedPrintableEncoderStream.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.FilterOutputStream;
+
+/**
+ * An implementation of a FilterOutputStream that encodes the
+ * stream data in Q-P encoding format. This version does the
+ * encoding "on the fly" rather than encoding a single block of
+ * data. Since this version is intended for use by the MimeUtilty class,
+ * it also handles line breaks in the encoded data.
+ */
+public class QuotedPrintableEncoderStream extends FilterOutputStream {
+ // our hex encoder utility class.
+ protected QuotedPrintableEncoder encoder;
+
+ // our default for line breaks
+ protected static final int DEFAULT_LINEBREAK = 76;
+
+ // the instance line break value
+ protected int lineBreak;
+
+ /**
+ * Create a Base64 encoder stream that wraps a specifed stream
+ * using the default line break size.
+ *
+ * @param out The wrapped output stream.
+ */
+ public QuotedPrintableEncoderStream(OutputStream out) {
+ this(out, DEFAULT_LINEBREAK);
+ }
+
+
+ public QuotedPrintableEncoderStream(OutputStream out, int lineBreak) {
+ super(out);
+ // lines are processed only in multiple of 4, so round this down.
+ this.lineBreak = (lineBreak / 4) * 4 ;
+
+ // create an encoder configured to this amount
+ encoder = new QuotedPrintableEncoder(out, this.lineBreak);
+ }
+
+
+ public void write(int ch) throws IOException {
+ // have the encoder do the heavy lifting here.
+ encoder.encode(ch);
+ }
+
+ public void write(byte [] data) throws IOException {
+ write(data, 0, data.length);
+ }
+
+ public void write(byte [] data, int offset, int length) throws IOException {
+ // the encoder does the heavy lifting here.
+ encoder.encode(data, offset, length);
+ }
+
+ public void close() throws IOException {
+ out.close();
+ }
+
+ public void flush() throws IOException {
+ out.flush();
+ }
+}
+
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/RFC2231Encoder.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/RFC2231Encoder.java
new file mode 100644
index 00000000..e90c80e2
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/RFC2231Encoder.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import javax.mail.internet.MimeUtility;
+
+/**
+ * Encoder for RFC2231 encoded parameters
+ *
+ * RFC2231 string are encoded as
+ *
+ * charset'language'encoded-text
+ *
+ * and
+ *
+ * encoded-text = *(char / hexchar)
+ *
+ * where
+ *
+ * char is any ASCII character in the range 33-126, EXCEPT
+ * the characters "%" and " ".
+ *
+ * hexchar is an ASCII "%" followed by two upper case
+ * hexadecimal digits.
+ */
+
+public class RFC2231Encoder implements Encoder
+{
+ protected final byte[] encodingTable =
+ {
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7',
+ (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F'
+ };
+
+ protected String DEFAULT_SPECIALS = " *'%";
+ protected String specials = DEFAULT_SPECIALS;
+
+ /*
+ * set up the decoding table.
+ */
+ protected final byte[] decodingTable = new byte[128];
+
+ protected void initialiseDecodingTable()
+ {
+ for (int i = 0; i < encodingTable.length; i++)
+ {
+ decodingTable[encodingTable[i]] = (byte)i;
+ }
+ }
+
+ public RFC2231Encoder()
+ {
+ this(null);
+ }
+
+ public RFC2231Encoder(String specials)
+ {
+ if (specials != null) {
+ this.specials = DEFAULT_SPECIALS + specials;
+ }
+ initialiseDecodingTable();
+ }
+
+
+ /**
+ * encode the input data producing an RFC2231 output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public int encode(byte[] data, int off, int length, OutputStream out) throws IOException {
+
+ int bytesWritten = 0;
+ for (int i = off; i < (off + length); i++)
+ {
+ int ch = data[i] & 0xff;
+ // character tha must be encoded? Prefix with a '%' and encode in hex.
+ if (ch <= 32 || ch >= 127 || specials.indexOf(ch) != -1) {
+ out.write((byte)'%');
+ out.write(encodingTable[ch >> 4]);
+ out.write(encodingTable[ch & 0xf]);
+ bytesWritten += 3;
+ }
+ else {
+ // add unchanged.
+ out.write((byte)ch);
+ bytesWritten++;
+ }
+ }
+
+ return bytesWritten;
+ }
+
+
+ /**
+ * decode the RFC2231 encoded byte data writing it to the given output stream
+ *
+ * @return the number of bytes produced.
+ */
+ public int decode(byte[] data, int off, int length, OutputStream out) throws IOException {
+ int outLen = 0;
+ int end = off + length;
+
+ int i = off;
+ while (i < end)
+ {
+ byte v = data[i++];
+ // a percent is a hex character marker, need to decode a hex value.
+ if (v == '%') {
+ byte b1 = decodingTable[data[i++]];
+ byte b2 = decodingTable[data[i++]];
+ out.write((b1 << 4) | b2);
+ }
+ else {
+ // copied over unchanged.
+ out.write(v);
+ }
+ // always just one byte added
+ outLen++;
+ }
+
+ return outLen;
+ }
+
+ /**
+ * decode the RFC2231 encoded String data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public int decode(String data, OutputStream out) throws IOException
+ {
+ int length = 0;
+ int end = data.length();
+
+ int i = 0;
+ while (i < end)
+ {
+ char v = data.charAt(i++);
+ if (v == '%') {
+ byte b1 = decodingTable[data.charAt(i++)];
+ byte b2 = decodingTable[data.charAt(i++)];
+
+ out.write((b1 << 4) | b2);
+ }
+ else {
+ out.write((byte)v);
+ }
+ length++;
+ }
+
+ return length;
+ }
+
+
+ /**
+ * Encode a string as an RFC2231 encoded parameter, using the
+ * given character set and language.
+ *
+ * @param charset The source character set (the MIME version).
+ * @param language The encoding language.
+ * @param data The data to encode.
+ *
+ * @return The encoded string.
+ */
+ public String encode(String charset, String language, String data) throws IOException {
+
+ byte[] bytes = null;
+ try {
+ // the charset we're adding is the MIME-defined name. We need the java version
+ // in order to extract the bytes.
+ bytes = data.getBytes(MimeUtility.javaCharset(charset));
+ } catch (UnsupportedEncodingException e) {
+ // we have a translation problem here.
+ return null;
+ }
+
+ StringBuffer result = new StringBuffer();
+
+ // append the character set, if we have it.
+ if (charset != null) {
+ result.append(charset);
+ }
+ // the field marker is required.
+ result.append("'");
+
+ // and the same for the language.
+ if (language != null) {
+ result.append(language);
+ }
+ // the field marker is required.
+ result.append("'");
+
+ // wrap an output stream around our buffer for the decoding
+ OutputStream out = new StringBufferOutputStream(result);
+
+ // encode the data stream
+ encode(bytes, 0, bytes.length, out);
+
+ // finis!
+ return result.toString();
+ }
+
+
+ /**
+ * Decode an RFC2231 encoded string.
+ *
+ * @param data The data to decode.
+ *
+ * @return The decoded string.
+ * @exception IOException
+ * @exception UnsupportedEncodingException
+ */
+ public String decode(String data) throws IOException, UnsupportedEncodingException {
+ // get the end of the language field
+ int charsetEnd = data.indexOf('\'');
+ // uh oh, might not be there
+ if (charsetEnd == -1) {
+ throw new IOException("Missing charset in RFC2231 encoded value");
+ }
+
+ String charset = data.substring(0, charsetEnd);
+
+ // now pull out the language the same way
+ int languageEnd = data.indexOf('\'', charsetEnd + 1);
+ if (languageEnd == -1) {
+ throw new IOException("Missing language in RFC2231 encoded value");
+ }
+
+ String language = data.substring(charsetEnd + 1, languageEnd);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream(data.length());
+
+ // decode the data
+ decode(data.substring(languageEnd + 1), out);
+
+ byte[] bytes = out.toByteArray();
+ // build a new string from this using the java version of the encoded charset.
+ return new String(bytes, 0, bytes.length, MimeUtility.javaCharset(charset));
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/SessionUtil.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/SessionUtil.java
new file mode 100644
index 00000000..64db0845
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/SessionUtil.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.security.Security;
+
+import javax.mail.Session;
+
+/**
+ * Simple utility class for managing session properties.
+ */
+public class SessionUtil {
+
+ /**
+ * Get a property associated with this mail session. Returns
+ * the provided default if it doesn't exist.
+ *
+ * @param session The attached session.
+ * @param name The name of the property.
+ *
+ * @return The property value (returns null if the property has not been set).
+ */
+ static public String getProperty(Session session, String name) {
+ // occasionally, we get called with a null session if an object is not attached to
+ // a session. In that case, treat this like an unknown parameter.
+ if (session == null) {
+ return null;
+ }
+
+ return session.getProperty(name);
+ }
+
+
+ /**
+ * Get a property associated with this mail session. Returns
+ * the provided default if it doesn't exist.
+ *
+ * @param session The attached session.
+ * @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).
+ */
+ static public String getProperty(Session session, String name, String defaultValue) {
+ String result = getProperty(session, name);
+ if (result == null) {
+ return defaultValue;
+ }
+ return result;
+ }
+
+
+ /**
+ * Process a session property as a boolean value, returning
+ * either true or false.
+ *
+ * @param session The source session.
+ * @param name
+ *
+ * @return True if the property value is "true". Returns false for any
+ * other value (including null).
+ */
+ static public boolean isPropertyTrue(Session session, String name) {
+ String property = getProperty(session, name);
+ if (property != null) {
+ return property.equals("true");
+ }
+ return false;
+ }
+
+ /**
+ * Process a session property as a boolean value, returning
+ * either true or false.
+ *
+ * @param session The source session.
+ * @param name
+ *
+ * @return True if the property value is "false". Returns false for
+ * other value (including null).
+ */
+ static public boolean isPropertyFalse(Session session, String name) {
+ String property = getProperty(session, name);
+ if (property != null) {
+ return property.equals("false");
+ }
+ 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 session The source session.
+ * @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.
+ */
+ static public int getIntProperty(Session session, String name, int defaultValue) {
+ String result = getProperty(session, 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 a boolean value. Returns
+ * the default value if the property doesn't exist or it doesn't have a valid boolean value.
+ *
+ * @param session The source session.
+ * @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.
+ */
+ static public boolean getBooleanProperty(Session session, String name, boolean defaultValue) {
+ String result = getProperty(session, name);
+ if (result != null) {
+ return Boolean.valueOf(result).booleanValue();
+ }
+ // return default value if it doesn't exist is isn't convertable.
+ return defaultValue;
+ }
+
+
+ /**
+ * Get a system property associated with this mail session as a boolean value. Returns
+ * the default value if the property doesn't exist or it doesn't have a valid boolean 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.
+ */
+ static public boolean getBooleanProperty(String name, boolean defaultValue) {
+ try {
+ String result = System.getProperty(name);
+ if (result != null) {
+ return Boolean.valueOf(result).booleanValue();
+ }
+ } catch (SecurityException e) {
+ // we can't access the property, so for all intents, it doesn't exist.
+ }
+ // return default value if it doesn't exist is isn't convertable.
+ return defaultValue;
+ }
+
+
+ /**
+ * Get a system property associated with this mail session as a boolean value. Returns
+ * the default value if the property 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
+ */
+ static public String getProperty(String name, String defaultValue) {
+ try {
+ String result = System.getProperty(name);
+ if (result != null) {
+ return result;
+ }
+ } catch (SecurityException e) {
+ // we can't access the property, so for all intents, it doesn't exist.
+ }
+ // return default value if it doesn't exist is isn't convertable.
+ return defaultValue;
+ }
+
+
+ /**
+ * Get a system property associated with this mail session as a boolean value. Returns
+ * the default value if the property doesn't exist.
+ *
+ * @param name The name of the property.
+ *
+ * @return The property value
+ */
+ static public String getProperty(String name) {
+ try {
+ return System.getProperty(name);
+ } catch (SecurityException e) {
+ // we can't access the property, so for all intents, it doesn't exist.
+ }
+ // return null if we got an exception.
+ return null;
+ }
+}
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/StringBufferOutputStream.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/StringBufferOutputStream.java
new file mode 100644
index 00000000..bb5fd1d4
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/StringBufferOutputStream.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An implementation of an OutputStream that writes the data directly
+ * out to a StringBuffer object. Useful for applications where an
+ * intermediate ByteArrayOutputStream is required to append generated
+ * characters to a StringBuffer;
+ */
+public class StringBufferOutputStream extends OutputStream {
+
+ // the target buffer
+ protected StringBuffer buffer;
+
+ /**
+ * Create an output stream that writes to the target StringBuffer
+ *
+ * @param out The wrapped output stream.
+ */
+ public StringBufferOutputStream(StringBuffer out) {
+ buffer = 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 {
+ // just append the character
+ buffer.append((char)ch);
+ }
+}
+
+
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUDecoderStream.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUDecoderStream.java
new file mode 100644
index 00000000..985dcb78
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUDecoderStream.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * An implementation of a FilterOutputStream that decodes the
+ * stream data in UU encoding format. This version does the
+ * decoding "on the fly" rather than decoding a single block of
+ * data. Since this version is intended for use by the MimeUtilty class,
+ * it also handles line breaks in the encoded data.
+ */
+public class UUDecoderStream extends FilterInputStream {
+ // maximum number of chars that can appear in a single line
+ protected static final int MAX_CHARS_PER_LINE = 45;
+
+ // our decoder for processing the data
+ protected UUEncoder decoder = new UUEncoder();
+
+ // a buffer for one decoding unit's worth of data (45 bytes).
+ protected byte[] decodedChars;
+ // count of characters in the buffer
+ protected int decodedCount = 0;
+ // index of the next decoded character
+ protected int decodedIndex = 0;
+
+ // indicates whether we've already processed the "begin" prefix.
+ protected boolean beginRead = false;
+
+
+ public UUDecoderStream(InputStream in) {
+ super(in);
+ }
+
+
+ /**
+ * Test for the existance of decoded characters in our buffer
+ * of decoded data.
+ *
+ * @return True if we currently have buffered characters.
+ */
+ private boolean dataAvailable() {
+ return decodedCount != 0;
+ }
+
+ /**
+ * Get the next buffered decoded character.
+ *
+ * @return The next decoded character in the buffer.
+ */
+ private byte getBufferedChar() {
+ decodedCount--;
+ return decodedChars[decodedIndex++];
+ }
+
+ /**
+ * Decode a requested number of bytes of data into a buffer.
+ *
+ * @return true if we were able to obtain more data, false otherwise.
+ */
+ private boolean decodeStreamData() throws IOException {
+ decodedIndex = 0;
+
+ // fill up a data buffer with input data
+ return fillEncodedBuffer() != -1;
+ }
+
+
+ /**
+ * Retrieve a single byte from the decoded characters buffer.
+ *
+ * @return The decoded character or -1 if there was an EOF condition.
+ */
+ private int getByte() throws IOException {
+ if (!dataAvailable()) {
+ if (!decodeStreamData()) {
+ return -1;
+ }
+ }
+ decodedCount--;
+ return decodedChars[decodedIndex++];
+ }
+
+ private int getBytes(byte[] data, int offset, int length) throws IOException {
+
+ int readCharacters = 0;
+ while (length > 0) {
+ // need data? Try to get some
+ if (!dataAvailable()) {
+ // if we can't get this, return a count of how much we did get (which may be -1).
+ if (!decodeStreamData()) {
+ return readCharacters > 0 ? readCharacters : -1;
+ }
+ }
+
+ // now copy some of the data from the decoded buffer to the target buffer
+ int copyCount = Math.min(decodedCount, length);
+ System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount);
+ decodedIndex += copyCount;
+ decodedCount -= copyCount;
+ offset += copyCount;
+ length -= copyCount;
+ readCharacters += copyCount;
+ }
+ return readCharacters;
+ }
+
+ /**
+ * Verify that the first line of the buffer is a valid begin
+ * marker.
+ *
+ * @exception IOException
+ */
+ private void checkBegin() throws IOException {
+ // we only do this the first time we're requested to read from the stream.
+ if (beginRead) {
+ return;
+ }
+
+ // we might have to skip over lines to reach the marker. If we hit the EOF without finding
+ // the begin, that's an error.
+ while (true) {
+ String line = readLine();
+ if (line == null) {
+ throw new IOException("Missing UUEncode begin command");
+ }
+
+ // is this our begin?
+ if (line.regionMatches(true, 0, "begin ", 0, 6)) {
+ // This is the droid we're looking for.....
+ beginRead = true;
+ return;
+ }
+ }
+ }
+
+
+ /**
+ * Read a line of data. Returns null if there is an EOF.
+ *
+ * @return The next line read from the stream. Returns null if we
+ * hit the end of the stream.
+ * @exception IOException
+ */
+ protected String readLine() throws IOException {
+ decodedIndex = 0;
+ // get an accumulator for the data
+ StringBuffer buffer = new StringBuffer();
+
+ // now process a character at a time.
+ int ch = in.read();
+ while (ch != -1) {
+ // a naked new line completes the line.
+ if (ch == '\n') {
+ break;
+ }
+ // a carriage return by itself is ignored...we're going to assume that this is followed
+ // by a new line because we really don't have the capability of pushing this back .
+ else if (ch == '\r') {
+ ;
+ }
+ else {
+ // add this to our buffer
+ buffer.append((char)ch);
+ }
+ ch = in.read();
+ }
+
+ // if we didn't get any data at all, return nothing
+ if (ch == -1 && buffer.length() == 0) {
+ return null;
+ }
+ // convert this into a string.
+ return buffer.toString();
+ }
+
+
+ /**
+ * Fill our buffer of input characters for decoding from the
+ * stream. This will attempt read a full buffer, but will
+ * terminate on an EOF or read error. This will filter out
+ * non-Base64 encoding chars and will only return a valid
+ * multiple of 4 number of bytes.
+ *
+ * @return The count of characters read.
+ */
+ private int fillEncodedBuffer() throws IOException
+ {
+ checkBegin();
+ // reset the buffer position
+ decodedIndex = 0;
+
+ while (true) {
+
+ // we read these as character lines. We need to be looking for the "end" marker for the
+ // end of the data.
+ String line = readLine();
+
+ // this should NOT be happening....
+ if (line == null) {
+ throw new IOException("Missing end in UUEncoded data");
+ }
+
+ // Is this the end marker? EOF baby, EOF!
+ if (line.equalsIgnoreCase("end")) {
+ // this indicates we got nuttin' more to do.
+ return -1;
+ }
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream(MAX_CHARS_PER_LINE);
+
+ byte [] lineBytes;
+ try {
+ lineBytes = line.getBytes("US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new IOException("Invalid UUEncoding");
+ }
+
+ // decode this line
+ decodedCount = decoder.decode(lineBytes, 0, lineBytes.length, out);
+
+ // not just a zero-length line?
+ if (decodedCount != 0) {
+ // get the resulting byte array
+ decodedChars = out.toByteArray();
+ return decodedCount;
+ }
+ }
+ }
+
+
+ // in order to function as a filter, these streams need to override the different
+ // read() signature.
+
+ public int read() throws IOException
+ {
+ return getByte();
+ }
+
+
+ public int read(byte [] buffer, int offset, int length) throws IOException {
+ return getBytes(buffer, offset, length);
+ }
+
+
+ public boolean markSupported() {
+ return false;
+ }
+
+
+ public int available() throws IOException {
+ return ((in.available() / 4) * 3) + decodedCount;
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUEncode.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUEncode.java
new file mode 100644
index 00000000..dbaad7c8
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUEncode.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class UUEncode {
+ private static final Encoder encoder = new UUEncoder();
+
+ /**
+ * encode the input data producing a UUEncoded byte array.
+ *
+ * @return a byte array containing the UUEncoded data.
+ */
+ public static byte[] encode(
+ byte[] data)
+ {
+ return encode(data, 0, data.length);
+ }
+
+ /**
+ * encode the input data producing a UUEncoded byte array.
+ *
+ * @return a byte array containing the UUEncoded data.
+ */
+ public static byte[] encode(
+ byte[] data,
+ int off,
+ int length)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.encode(data, off, length, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception encoding UUEncoded string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * UUEncode the byte data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ byte[] data,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.encode(data, 0, data.length, out);
+ }
+
+ /**
+ * UUEncode the byte data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.encode(data, 0, data.length, out);
+ }
+
+ /**
+ * decode the UUEncoded input data. It is assumed the input data is valid.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ byte[] data)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, 0, data.length, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception decoding UUEncoded string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the UUEncided String data.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ String data)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception decoding UUEncoded string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the UUEncoded encoded String data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int decode(
+ String data,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.decode(data, out);
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUEncoder.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUEncoder.java
new file mode 100644
index 00000000..b2c514eb
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUEncoder.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+public class UUEncoder implements Encoder {
+
+ // this is the maximum number of chars allowed per line, since we have to include a uuencoded length at
+ // the start of each line.
+ static private final int MAX_CHARS_PER_LINE = 45;
+
+
+ public UUEncoder()
+ {
+ }
+
+ /**
+ * encode the input data producing a UUEncoded output stream.
+ *
+ * @param data The array of byte data.
+ * @param off The starting offset within the data.
+ * @param length Length of the data to encode.
+ * @param out The output stream the encoded data is written to.
+ *
+ * @return the number of bytes produced.
+ */
+ public int encode(byte[] data, int off, int length, OutputStream out) throws IOException
+ {
+ int byteCount = 0;
+
+ while (true) {
+ // keep writing complete lines until we've exhausted the data.
+ if (length > MAX_CHARS_PER_LINE) {
+ // encode another line and adjust the length and position
+ byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
+ length -= MAX_CHARS_PER_LINE;
+ off += MAX_CHARS_PER_LINE;
+ }
+ else {
+ // last line. Encode the partial and quit
+ byteCount += encodeLine(data, off, MAX_CHARS_PER_LINE, out);
+ break;
+ }
+ }
+ return byteCount;
+ }
+
+
+ /**
+ * Encode a single line of data (less than or equal to 45 characters).
+ *
+ * @param data The array of byte data.
+ * @param off The starting offset within the data.
+ * @param length Length of the data to encode.
+ * @param out The output stream the encoded data is written to.
+ *
+ * @return The number of bytes written to the output stream.
+ * @exception IOException
+ */
+ private int encodeLine(byte[] data, int offset, int length, OutputStream out) throws IOException {
+ // write out the number of characters encoded in this line.
+ out.write((byte)((length & 0x3F) + ' '));
+ byte a;
+ byte b;
+ byte c;
+
+ // count the bytes written...we add 2, one for the length and 1 for the linend terminator.
+ int bytesWritten = 2;
+
+ for (int i = 0; i < length;) {
+ // set the padding defauls
+ b = 1;
+ c = 1;
+ // get the next 3 bytes (if we have them)
+ a = data[offset + i++];
+ if (i < length) {
+ b = data[offset + i++];
+ if (i < length) {
+ c = data[offset + i++];
+ }
+ }
+
+ byte d1 = (byte)(((a >>> 2) & 0x3F) + ' ');
+ byte d2 = (byte)(((( a << 4) & 0x30) | ((b >>> 4) & 0x0F)) + ' ');
+ byte d3 = (byte)((((b << 2) & 0x3C) | ((c >>> 6) & 0x3)) + ' ');
+ byte d4 = (byte)((c & 0x3F) + ' ');
+
+ out.write(d1);
+ out.write(d2);
+ out.write(d3);
+ out.write(d4);
+
+ bytesWritten += 4;
+ }
+
+ // terminate with a linefeed alone
+ out.write('\n');
+
+ return bytesWritten;
+ }
+
+
+ /**
+ * decode the uuencoded byte data writing it to the given output stream
+ *
+ * @param data The array of byte data to decode.
+ * @param off Starting offset within the array.
+ * @param length The length of data to encode.
+ * @param out The output stream used to return the decoded data.
+ *
+ * @return the number of bytes produced.
+ * @exception IOException
+ */
+ public int decode(byte[] data, int off, int length, OutputStream out) throws IOException
+ {
+ int bytesWritten = 0;
+
+ while (length > 0) {
+ int lineOffset = off;
+
+ // scan forward looking for a EOL terminator for the next line of data.
+ while (length > 0 && data[off] != '\n') {
+ off++;
+ length--;
+ }
+
+ // go decode this line of data
+ bytesWritten += decodeLine(data, lineOffset, off - lineOffset, out);
+
+ // the offset was left pointing at the EOL character, so step over that one before
+ // scanning again.
+ off++;
+ length--;
+ }
+ return bytesWritten;
+ }
+
+
+ /**
+ * decode a single line of uuencoded byte data writing it to the given output stream
+ *
+ * @param data The array of byte data to decode.
+ * @param off Starting offset within the array.
+ * @param length The length of data to decode (length does NOT include the terminating new line).
+ * @param out The output stream used to return the decoded data.
+ *
+ * @return the number of bytes produced.
+ * @exception IOException
+ */
+ private int decodeLine(byte[] data, int off, int length, OutputStream out) throws IOException {
+ int count = data[off++];
+
+ // obtain and validate the count
+ if (count < ' ') {
+ throw new IOException("Invalid UUEncode line length");
+ }
+
+ count = (count - ' ') & 0x3F;
+
+ // get the rounded count of characters that should have been used to encode this. The + 1 is for the
+ // length encoded at the beginning
+ int requiredLength = (((count * 8) + 5) / 6) + 1;
+ if (length < requiredLength) {
+ throw new IOException("UUEncoded data and length do not match");
+ }
+
+ int bytesWritten = 0;
+ // now decode the bytes.
+ while (bytesWritten < count) {
+ // even one byte of data requires two bytes to encode, so we should have that.
+ byte a = (byte)((data[off++] - ' ') & 0x3F);
+ byte b = (byte)((data[off++] - ' ') & 0x3F);
+ byte c = 0;
+ byte d = 0;
+
+ // do the first byte
+ byte first = (byte)(((a << 2) & 0xFC) | ((b >>> 4) & 3));
+ out.write(first);
+ bytesWritten++;
+
+ // still have more bytes to decode? do the second byte of the second. That requires
+ // a third byte from the data.
+ if (bytesWritten < count) {
+ c = (byte)((data[off++] - ' ') & 0x3F);
+ byte second = (byte)(((b << 4) & 0xF0) | ((c >>> 2) & 0x0F));
+ out.write(second);
+ bytesWritten++;
+
+ // need the third one?
+ if (bytesWritten < count) {
+ d = (byte)((data[off++] - ' ') & 0x3F);
+ byte third = (byte)(((c << 6) & 0xC0) | (d & 0x3F));
+ out.write(third);
+ bytesWritten++;
+ }
+ }
+ }
+ return bytesWritten;
+ }
+
+
+ /**
+ * decode the UUEncoded String data writing it to the given output stream.
+ *
+ * @param data The String data to decode.
+ * @param out The output stream to write the decoded data to.
+ *
+ * @return the number of bytes produced.
+ * @exception IOException
+ */
+ public int decode(String data, OutputStream out) throws IOException
+ {
+ try {
+ // just get the byte data and decode.
+ byte[] bytes = data.getBytes("US-ASCII");
+ return decode(bytes, 0, bytes.length, out);
+ } catch (UnsupportedEncodingException e) {
+ throw new IOException("Invalid UUEncoding");
+ }
+ }
+}
+
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUEncoderStream.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUEncoderStream.java
new file mode 100644
index 00000000..f557d7ea
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/UUEncoderStream.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * An implementation of a FilterOutputStream that encodes the
+ * stream data in UUencoding format. This version does the
+ * encoding "on the fly" rather than encoding a single block of
+ * data. Since this version is intended for use by the MimeUtilty class,
+ * it also handles line breaks in the encoded data.
+ */
+public class UUEncoderStream extends FilterOutputStream {
+
+ // default values included on the "begin" prefix of the data stream.
+ protected static final int DEFAULT_MODE = 644;
+ protected static final String DEFAULT_NAME = "encoder.buf";
+
+ protected static final int MAX_CHARS_PER_LINE = 45;
+
+ // the configured name on the "begin" command.
+ protected String name;
+ // the configured mode for the "begin" command.
+ protected int mode;
+
+ // since this is a filtering stream, we need to wait until we have the first byte written for encoding
+ // to write out the "begin" marker. A real pain, but necessary.
+ protected boolean beginWritten = false;
+
+
+ // our encoder utility class.
+ protected UUEncoder encoder = new UUEncoder();
+
+ // Data is generally written out in 45 character lines, so we're going to buffer that amount before
+ // asking the encoder to process this.
+
+ // the buffered byte count
+ protected int bufferedBytes = 0;
+
+ // we'll encode this part once it is filled up.
+ protected byte[] buffer = new byte[45];
+
+ /**
+ * Create a Base64 encoder stream that wraps a specifed stream
+ * using the default line break size.
+ *
+ * @param out The wrapped output stream.
+ */
+ public UUEncoderStream(OutputStream out) {
+ this(out, DEFAULT_NAME, DEFAULT_MODE);
+ }
+
+
+ /**
+ * Create a Base64 encoder stream that wraps a specifed stream
+ * using the default line break size.
+ *
+ * @param out The wrapped output stream.
+ * @param name The filename placed on the "begin" command.
+ */
+ public UUEncoderStream(OutputStream out, String name) {
+ this(out, name, DEFAULT_MODE);
+ }
+
+
+ public UUEncoderStream(OutputStream out, String name, int mode) {
+ super(out);
+ // fill in the name and mode information.
+ this.name = name;
+ this.mode = mode;
+ }
+
+
+ private void checkBegin() throws IOException {
+ if (!beginWritten) {
+ // grumble...OutputStream doesn't directly support writing String data. We'll wrap this in
+ // a PrintStream() to accomplish the task of writing the begin command.
+
+ PrintStream writer = new PrintStream(out);
+ // write out the stream with a CRLF marker
+ writer.print("begin " + mode + " " + name + "\r\n");
+ writer.flush();
+ beginWritten = true;
+ }
+ }
+
+ private void writeEnd() throws IOException {
+ PrintStream writer = new PrintStream(out);
+ // write out the stream with a CRLF marker
+ writer.print("\nend\r\n");
+ writer.flush();
+ }
+
+ private void flushBuffer() throws IOException {
+ // make sure we've written the begin marker first
+ checkBegin();
+ // ask the encoder to encode and write this out.
+ if (bufferedBytes != 0) {
+ encoder.encode(buffer, 0, bufferedBytes, out);
+ // reset the buffer count
+ bufferedBytes = 0;
+ }
+ }
+
+ private int bufferSpace() {
+ return MAX_CHARS_PER_LINE - bufferedBytes;
+ }
+
+ private boolean isBufferFull() {
+ return bufferedBytes >= MAX_CHARS_PER_LINE;
+ }
+
+
+ // in order for this to work, we need to override the 3 different signatures for write
+
+ public void write(int ch) throws IOException {
+ // store this in the buffer.
+ buffer[bufferedBytes++] = (byte)ch;
+
+ // if we filled this up, time to encode and write to the output stream.
+ if (isBufferFull()) {
+ flushBuffer();
+ }
+ }
+
+ public void write(byte [] data) throws IOException {
+ write(data, 0, data.length);
+ }
+
+ public void write(byte [] data, int offset, int length) throws IOException {
+ // first check to see how much space we have left in the buffer, and copy that over
+ int copyBytes = Math.min(bufferSpace(), length);
+
+ System.arraycopy(buffer, bufferedBytes, data, offset, copyBytes);
+ bufferedBytes += copyBytes;
+ offset += copyBytes;
+ length -= copyBytes;
+
+ // if we filled this up, time to encode and write to the output stream.
+ if (isBufferFull()) {
+ flushBuffer();
+ }
+
+ // we've flushed the leading part up to the line break. Now if we have complete lines
+ // of data left, we can have the encoder process all of these lines directly.
+ if (length >= MAX_CHARS_PER_LINE) {
+ int fullLinesLength = (length / MAX_CHARS_PER_LINE) * MAX_CHARS_PER_LINE;
+ // ask the encoder to encode and write this out.
+ encoder.encode(data, offset, fullLinesLength, out);
+ offset += fullLinesLength;
+ length -= fullLinesLength;
+ }
+
+ // ok, now we're down to a potential trailing bit we need to move into the
+ // buffer for later processing.
+
+ if (length > 0) {
+ System.arraycopy(buffer, 0, data, offset, length);
+ bufferedBytes += length;
+ offset += length;
+ length -= length;
+ }
+ }
+
+ public void flush() throws IOException {
+ // flush any unencoded characters we're holding.
+ flushBuffer();
+ // write out the data end marker
+ writeEnd();
+ // and flush the output stream too so that this data is available.
+ out.flush();
+ }
+
+ public void close() throws IOException {
+ // flush all of the streams and close the target output stream.
+ flush();
+ out.close();
+ }
+
+}
+
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/XText.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/XText.java
new file mode 100644
index 00000000..398749fa
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/XText.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Encoder for RFC1891 xtext.
+ *
+ * xtext strings are defined as
+ *
+ * xtext = *( xchar / hexchar )
+ *
+ * where
+ *
+ * xchar is any ASCII character in the range 33-126, EXCEPT
+ * the characters "+" and "=".
+ *
+ * hexchar is an ASCII "+" followed by two upper case
+ * hexadecimal digits.
+ */
+public class XText
+{
+ private static final Encoder encoder = new XTextEncoder();
+
+ /**
+ * encode the input data producing an xtext encoded byte array.
+ *
+ * @return a byte array containing the xtext encoded data.
+ */
+ public static byte[] encode(
+ byte[] data)
+ {
+ return encode(data, 0, data.length);
+ }
+
+ /**
+ * encode the input data producing an xtext encoded byte array.
+ *
+ * @return a byte array containing the xtext encoded data.
+ */
+ public static byte[] encode(
+ byte[] data,
+ int off,
+ int length)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.encode(data, off, length, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception encoding xtext string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * xtext encode the byte data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ byte[] data,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.encode(data, 0, data.length, out);
+ }
+
+ /**
+ * extext encode the byte data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int encode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.encode(data, 0, data.length, out);
+ }
+
+ /**
+ * decode the xtext encoded input data. It is assumed the input data is valid.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ byte[] data)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, 0, data.length, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception decoding xtext string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the xtext encoded String data - whitespace will be ignored.
+ *
+ * @return a byte array representing the decoded data.
+ */
+ public static byte[] decode(
+ String data)
+ {
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ try
+ {
+ encoder.decode(data, bOut);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("exception decoding xtext string: " + e);
+ }
+
+ return bOut.toByteArray();
+ }
+
+ /**
+ * decode the xtext encoded String data writing it to the given output stream,
+ * whitespace characters will be ignored.
+ *
+ * @return the number of bytes produced.
+ */
+ public static int decode(
+ String data,
+ OutputStream out)
+ throws IOException
+ {
+ return encoder.decode(data, out);
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/XTextEncoder.java b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/XTextEncoder.java
new file mode 100644
index 00000000..45bb9080
--- /dev/null
+++ b/external/geronimo_javamail/src/main/java/org/apache/geronimo/mail/util/XTextEncoder.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.geronimo.mail.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class XTextEncoder
+ implements Encoder
+{
+ protected final byte[] encodingTable =
+ {
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7',
+ (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F'
+ };
+
+ /*
+ * set up the decoding table.
+ */
+ protected final byte[] decodingTable = new byte[128];
+
+ protected void initialiseDecodingTable()
+ {
+ for (int i = 0; i < encodingTable.length; i++)
+ {
+ decodingTable[encodingTable[i]] = (byte)i;
+ }
+ }
+
+ public XTextEncoder()
+ {
+ initialiseDecodingTable();
+ }
+
+ /**
+ * encode the input data producing an XText output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public int encode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ int bytesWritten = 0;
+
+ for (int i = off; i < (off + length); i++)
+ {
+ int v = data[i] & 0xff;
+ // character tha must be encoded? Prefix with a '+' and encode in hex.
+ if (v < 33 || v > 126 || v == '+' || v == '+') {
+ out.write((byte)'+');
+ out.write(encodingTable[(v >>> 4)]);
+ out.write(encodingTable[v & 0xf]);
+ bytesWritten += 3;
+ }
+ else {
+ // add unchanged.
+ out.write((byte)v);
+ bytesWritten++;
+ }
+ }
+
+ return bytesWritten;
+ }
+
+
+ /**
+ * decode the xtext encoded byte data writing it to the given output stream
+ *
+ * @return the number of bytes produced.
+ */
+ public int decode(
+ byte[] data,
+ int off,
+ int length,
+ OutputStream out)
+ throws IOException
+ {
+ byte[] bytes;
+ byte b1, b2;
+ int outLen = 0;
+
+ int end = off + length;
+
+ int i = off;
+ while (i < end)
+ {
+ byte v = data[i++];
+ // a plus is a hex character marker, need to decode a hex value.
+ if (v == '+') {
+ b1 = decodingTable[data[i++]];
+ b2 = decodingTable[data[i++]];
+ out.write((b1 << 4) | b2);
+ }
+ else {
+ // copied over unchanged.
+ out.write(v);
+ }
+ // always just one byte added
+ outLen++;
+ }
+
+ return outLen;
+ }
+
+ /**
+ * decode the xtext encoded String data writing it to the given output stream.
+ *
+ * @return the number of bytes produced.
+ */
+ public int decode(
+ String data,
+ OutputStream out)
+ throws IOException
+ {
+ byte[] bytes;
+ byte b1, b2, b3, b4;
+ int length = 0;
+
+ int end = data.length();
+
+ int i = 0;
+ while (i < end)
+ {
+ char v = data.charAt(i++);
+ if (v == '+') {
+ b1 = decodingTable[data.charAt(i++)];
+ b2 = decodingTable[data.charAt(i++)];
+
+ out.write((b1 << 4) | b2);
+ }
+ else {
+ out.write((byte)v);
+ }
+ length++;
+ }
+
+ return length;
+ }
+}
+
diff --git a/external/geronimo_javamail/src/main/resources/META-INF/default.address.map b/external/geronimo_javamail/src/main/resources/META-INF/default.address.map
new file mode 100644
index 00000000..33daec8c
--- /dev/null
+++ b/external/geronimo_javamail/src/main/resources/META-INF/default.address.map
@@ -0,0 +1,27 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements. See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership. The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT 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: 438769 $ $Date: 2006-08-30 21:03:44 -0700 (Wed, 30 Aug 2006) $
+##
+
+# only the single mapping for smtp is defined.
+rfc822=smtp
+
+
diff --git a/external/geronimo_javamail/src/main/resources/META-INF/javamail.charset.map b/external/geronimo_javamail/src/main/resources/META-INF/javamail.charset.map
new file mode 100644
index 00000000..accff5f4
--- /dev/null
+++ b/external/geronimo_javamail/src/main/resources/META-INF/javamail.charset.map
@@ -0,0 +1,78 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements. See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership. The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+## KIND, either express or implied. See the License for the
+## specific language governing permissions and limitations
+## under the License.
+##
+
+##
+## $Rev: 438769 $ $Date: 2006-08-30 21:03:44 -0700 (Wed, 30 Aug 2006) $
+##
+
+### Character set mapping table loaded and used by the javax.mail.internet.MimeUtility class.
+
+### java character sets to MIME character set map. This must be the first table.
+
+8859_1 ISO-8859-1
+iso8859_1 ISO-8859-1
+
+8859_2 ISO-8859-2
+iso8859_2 ISO-8859-2
+
+8859_3 ISO-8859-3
+iso8859_3 ISO-8859-3
+
+8859_4 ISO-8859-4
+iso8859_4 ISO-8859-4
+
+8859_5 ISO-8859-5
+iso8859_5 ISO-8859-5
+
+8859_6 ISO-8859-6
+iso8859_6 ISO-8859-6
+
+8859_7 ISO-8859-7
+iso8859_7 ISO-8859-7
+
+8859_8 ISO-8859-8
+iso8859_8 ISO-8859-8
+
+8859_9 ISO-8859-9
+iso8859_9 ISO-8859-9
+
+SJIS Shift_JIS
+MS932 Shift_JIS
+JIS ISO-2022-JP
+ISO2022JP ISO-2022-JP
+EUC_JP euc-jp
+KOI8_R koi8-r
+EUC_CN euc-cn
+EUC_TW euc-tw
+EUC_KR euc-kr
+
+--Table terminator. The "--" at the beginning and end are required --
+
+#### MIME to java character set map
+
+iso-2022-cn ISO2022CN
+iso-2022-kr ISO2022KR
+utf-8 UTF8
+utf8 UTF8
+ja_jp.iso2022-7 ISO2022JP
+ja_jp.eucjp EUCJIS
+euc-kr KSC5601
+euckr KSC5601
+us-ascii ISO-8859-1
+x-us-ascii ISO-8859-1
diff --git a/external/geronimo_javamail/src/main/resources/META-INF/mailcap b/external/geronimo_javamail/src/main/resources/META-INF/mailcap
new file mode 100644
index 00000000..281e90d8
--- /dev/null
+++ b/external/geronimo_javamail/src/main/resources/META-INF/mailcap
@@ -0,0 +1,28 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements. See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership. The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License. You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT 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: 438769 $ $Date: 2006-08-30 21:03:44 -0700 (Wed, 30 Aug 2006) $
+##
+
+text/plain;; x-java-content-handler=org.apache.geronimo.mail.handlers.TextHandler
+text/xml;; x-java-content-handler=org.apache.geronimo.mail.handlers.XMLHandler
+text/html;; x-java-content-handler=org.apache.geronimo.mail.handlers.HtmlHandler
+message/rfc822;; x-java-content-handler=org.apache.geronimo.mail.handlers.MessageHandler
+multipart/*;; x-java-content-handler=org.apache.geronimo.mail.handlers.MultipartHandler; x-java-fallback-entry=true
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/AllTests.java b/external/geronimo_javamail/src/test/java/javax/mail/AllTests.java
new file mode 100644
index 00000000..906cc18a
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/AllTests.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import javax.mail.event.AllEventTests;
+import javax.mail.internet.AllInternetTests;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * @version $Revision $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class AllTests {
+ public static Test suite() {
+ TestSuite suite = new TestSuite("Test for javax.mail");
+ //$JUnit-BEGIN$
+ suite.addTest(new TestSuite(FlagsTest.class));
+ suite.addTest(new TestSuite(HeaderTest.class));
+ suite.addTest(new TestSuite(MessagingExceptionTest.class));
+ suite.addTest(new TestSuite(URLNameTest.class));
+ suite.addTest(new TestSuite(PasswordAuthenticationTest.class));
+ suite.addTest(new TestSuite(SessionTest.class));
+ suite.addTest(AllEventTests.suite());
+ suite.addTest(AllInternetTests.suite());
+ //$JUnit-END$
+ return suite;
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/EventQueueTest.java b/external/geronimo_javamail/src/test/java/javax/mail/EventQueueTest.java
new file mode 100644
index 00000000..ca633475
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/EventQueueTest.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.util.Vector;
+
+import javax.mail.MessagingException;
+import javax.mail.event.FolderEvent;
+import javax.mail.event.FolderListener;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 582780 $ $Date: 2007-10-08 06:17:15 -0500 (Mon, 08 Oct 2007) $
+ */
+public class EventQueueTest extends TestCase {
+ protected EventQueue queue;
+
+ public void setUp() throws Exception {
+ queue = new EventQueue();
+ }
+
+ public void tearDown() throws Exception {
+ queue.stop();
+ }
+
+ public void testEvent() {
+ doEventTests(FolderEvent.CREATED);
+ doEventTests(FolderEvent.RENAMED);
+ doEventTests(FolderEvent.DELETED);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private void doEventTests(int type) {
+
+ // These tests are essentially the same as the
+ // folder event tests, but done using the asynchronous
+ // event queue.
+ FolderEvent event = new FolderEvent(this, null, type);
+ assertEquals(this, event.getSource());
+ assertEquals(type, event.getType());
+ FolderListenerTest listener = new FolderListenerTest();
+ Vector listeners = new Vector();
+ listeners.add(listener);
+ queue.queueEvent(event, listeners);
+ // we need to make sure the queue thread has a chance to dispatch
+ // this before we check.
+ try {
+ Thread.currentThread().sleep(1000);
+ } catch (InterruptedException e ) {
+ }
+ assertEquals("Unexpcted method dispatched", type, listener.getState());
+ }
+
+ public static class FolderListenerTest implements FolderListener {
+ private int state = 0;
+ public void folderCreated(FolderEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = FolderEvent.CREATED;
+ }
+ public void folderDeleted(FolderEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = FolderEvent.DELETED;
+ }
+ public void folderRenamed(FolderEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = FolderEvent.RENAMED;
+ }
+ public int getState() {
+ return state;
+ }
+ }
+}
+
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/FlagsTest.java b/external/geronimo_javamail/src/test/java/javax/mail/FlagsTest.java
new file mode 100644
index 00000000..3174f9cd
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/FlagsTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class FlagsTest extends TestCase {
+ @SuppressWarnings("rawtypes") // Legacy
+ private List flagtypes;
+ private Flags flags;
+ /**
+ * Constructor for FlagsTest.
+ * @param arg0
+ */
+ public FlagsTest(String name) {
+ super(name);
+ }
+ /*
+ * @see TestCase#setUp()
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"}) // Legacy
+ protected void setUp() throws Exception {
+ super.setUp();
+ flags = new Flags();
+ flagtypes = new LinkedList();
+ flagtypes.add(Flags.Flag.ANSWERED);
+ flagtypes.add(Flags.Flag.DELETED);
+ flagtypes.add(Flags.Flag.DRAFT);
+ flagtypes.add(Flags.Flag.FLAGGED);
+ flagtypes.add(Flags.Flag.RECENT);
+ flagtypes.add(Flags.Flag.SEEN);
+ Collections.shuffle(flagtypes);
+ }
+ public void testHashCode() {
+ int before = flags.hashCode();
+ flags.add("Test");
+ assertTrue(
+ "Before: " + before + ", now " + flags.hashCode(),
+ flags.hashCode() != before);
+ assertTrue(flags.hashCode() != 0);
+ }
+ /*
+ * Test for void add(Flag)
+ */
+ public void testAddAndRemoveFlag() {
+ @SuppressWarnings("rawtypes") Iterator it = flagtypes.iterator();
+ while (it.hasNext()) {
+ Flags.Flag flag = (Flags.Flag) it.next();
+ assertFalse(flags.contains(flag));
+ flags.add(flag);
+ assertTrue(flags.contains(flag));
+ }
+ it = flagtypes.iterator();
+ while (it.hasNext()) {
+ Flags.Flag flag = (Flags.Flag) it.next();
+ flags.remove(flag);
+ assertFalse(flags.contains(flag));
+ }
+ }
+ /*
+ * Test for void add(String)
+ */
+ public void testAddString() {
+ assertFalse(flags.contains("Frog"));
+ flags.add("Frog");
+ assertTrue(flags.contains("Frog"));
+ flags.remove("Frog");
+ assertFalse(flags.contains("Frog"));
+ }
+ /*
+ * Test for void add(Flags)
+ */
+ public void testAddFlags() {
+ Flags other = new Flags();
+ other.add("Stuff");
+ other.add(Flags.Flag.RECENT);
+ flags.add(other);
+ assertTrue(flags.contains("Stuff"));
+ assertTrue(flags.contains(Flags.Flag.RECENT));
+ assertTrue(flags.contains(other));
+ assertTrue(flags.contains(flags));
+ flags.add("Thing");
+ assertTrue(flags.contains("Thing"));
+ flags.remove(other);
+ assertFalse(flags.contains("Stuff"));
+ assertFalse(flags.contains(Flags.Flag.RECENT));
+ assertFalse(flags.contains(other));
+ assertTrue(flags.contains("Thing"));
+ }
+ /*
+ * Test for boolean equals(Object)
+ */
+ public void testEqualsObject() {
+ Flags other = new Flags();
+ other.add("Stuff");
+ other.add(Flags.Flag.RECENT);
+ flags.add(other);
+ assertEquals(flags, other);
+ }
+ public void testGetSystemFlags() {
+ flags.add("Stuff");
+ flags.add("Another");
+ flags.add(Flags.Flag.FLAGGED);
+ flags.add(Flags.Flag.RECENT);
+ Flags.Flag[] array = flags.getSystemFlags();
+ assertEquals(2, array.length);
+ assertTrue(
+ (array[0] == Flags.Flag.FLAGGED && array[1] == Flags.Flag.RECENT)
+ || (array[0] == Flags.Flag.RECENT
+ && array[1] == Flags.Flag.FLAGGED));
+ }
+ public void testGetUserFlags() {
+ final String stuff = "Stuff";
+ final String another = "Another";
+ flags.add(stuff);
+ flags.add(another);
+ flags.add(Flags.Flag.FLAGGED);
+ flags.add(Flags.Flag.RECENT);
+ String[] array = flags.getUserFlags();
+ assertEquals(2, array.length);
+ assertTrue(
+ (array[0] == stuff && array[1] == another)
+ || (array[0] == another && array[1] == stuff));
+ }
+ public void testClone() throws CloneNotSupportedException {
+ flags.add("Thing");
+ flags.add(Flags.Flag.RECENT);
+ Flags other = (Flags) flags.clone();
+ assertTrue(other != flags);
+ assertEquals(other, flags);
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/HeaderTest.java b/external/geronimo_javamail/src/test/java/javax/mail/HeaderTest.java
new file mode 100644
index 00000000..f81d902f
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/HeaderTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class HeaderTest extends TestCase {
+ public HeaderTest(String name) {
+ super(name);
+ }
+ public void testHeader() {
+ Header header = new Header("One", "Two");
+ assertEquals("One", header.getName());
+ assertEquals("Two", header.getValue());
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/MessageContextTest.java b/external/geronimo_javamail/src/test/java/javax/mail/MessageContextTest.java
new file mode 100644
index 00000000..be2ce6f9
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/MessageContextTest.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+import javax.activation.DataHandler;
+import javax.mail.internet.MimeMessage;
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class MessageContextTest extends TestCase {
+ public void testNothing() {
+ }
+ /*
+ public void testMessageContext() {
+ Part p;
+ MessageContext mc;
+ p = new TestPart();
+ mc = new MessageContext(p);
+ assertSame(p, mc.getPart());
+ assertNull(mc.getMessage());
+ assertNull(mc.getSession());
+
+ Session s = Session.getDefaultInstance(null);
+ MimeMessage m = new MimeMessage(s);
+ p = new TestMultipart(m);
+ mc = new MessageContext(p);
+ assertSame(p, mc.getPart());
+ assertSame(m,mc.getMessage());
+ assertSame(s,mc.getSession());
+
+ }
+ private static class TestMultipart extends Multipart implements Part {
+ public TestMultipart(Part p) {
+ parent = p;
+ }
+ public void writeTo(OutputStream out) throws IOException, MessagingException {
+ }
+ public void addHeader(String name, String value) throws MessagingException {
+ }
+ public Enumeration getAllHeaders() throws MessagingException {
+ return null;
+ }
+ public Object getContent() throws IOException, MessagingException {
+ return null;
+ }
+ public DataHandler getDataHandler() throws MessagingException {
+ return null;
+ }
+ public String getDescription() throws MessagingException {
+ return null;
+ }
+ public String getDisposition() throws MessagingException {
+ return null;
+ }
+ public String getFileName() throws MessagingException {
+ return null;
+ }
+ public String[] getHeader(String name) throws MessagingException {
+ return null;
+ }
+ public InputStream getInputStream() throws IOException, MessagingException {
+ return null;
+ }
+ public int getLineCount() throws MessagingException {
+ return 0;
+ }
+ public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
+ return null;
+ }
+ public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
+ return null;
+ }
+ public int getSize() throws MessagingException {
+ return 0;
+ }
+ public boolean isMimeType(String mimeType) throws MessagingException {
+ return false;
+ }
+ public void removeHeader(String name) throws MessagingException {
+ }
+ public void setContent(Multipart content) throws MessagingException {
+ }
+ public void setContent(Object content, String type) throws MessagingException {
+ }
+ public void setDataHandler(DataHandler handler) throws MessagingException {
+ }
+ public void setDescription(String description) throws MessagingException {
+ }
+ public void setDisposition(String disposition) throws MessagingException {
+ }
+ public void setFileName(String name) throws MessagingException {
+ }
+ public void setHeader(String name, String value) throws MessagingException {
+ }
+ public void setText(String content) throws MessagingException {
+ }
+ }
+ private static class TestBodyPart extends BodyPart {
+ public TestBodyPart(Multipart p) {
+ super();
+ parent = p;
+ }
+ public void addHeader(String name, String value)
+ throws MessagingException {
+ }
+ public Enumeration getAllHeaders() throws MessagingException {
+ return null;
+ }
+ public Object getContent() throws IOException, MessagingException {
+ return null;
+ }
+ public String getContentType() throws MessagingException {
+ return null;
+ }
+ public DataHandler getDataHandler() throws MessagingException {
+ return null;
+ }
+ public String getDescription() throws MessagingException {
+ return null;
+ }
+ public String getDisposition() throws MessagingException {
+ return null;
+ }
+ public String getFileName() throws MessagingException {
+ return null;
+ }
+ public String[] getHeader(String name) throws MessagingException {
+ return null;
+ }
+ public InputStream getInputStream()
+ throws IOException, MessagingException {
+ return null;
+ }
+ public int getLineCount() throws MessagingException {
+ return 0;
+ }
+ public Enumeration getMatchingHeaders(String[] names)
+ throws MessagingException {
+ return null;
+ }
+ public Enumeration getNonMatchingHeaders(String[] names)
+ throws MessagingException {
+ return null;
+ }
+ public int getSize() throws MessagingException {
+ return 0;
+ }
+ public boolean isMimeType(String mimeType) throws MessagingException {
+ return false;
+ }
+ public void removeHeader(String name) throws MessagingException {
+ }
+ public void setContent(Multipart content) throws MessagingException {
+ }
+ public void setContent(Object content, String type)
+ throws MessagingException {
+ }
+ public void setDataHandler(DataHandler handler)
+ throws MessagingException {
+ }
+ public void setDescription(String description)
+ throws MessagingException {
+ }
+ public void setDisposition(String disposition)
+ throws MessagingException {
+ }
+ public void setFileName(String name) throws MessagingException {
+ }
+ public void setHeader(String name, String value)
+ throws MessagingException {
+ }
+ public void setText(String content) throws MessagingException {
+ }
+ public void writeTo(OutputStream out)
+ throws IOException, MessagingException {
+ }
+ }
+ private static class TestPart implements Part {
+ public void addHeader(String name, String value)
+ throws MessagingException {
+ }
+ public Enumeration getAllHeaders() throws MessagingException {
+ return null;
+ }
+ public Object getContent() throws IOException, MessagingException {
+ return null;
+ }
+ public String getContentType() throws MessagingException {
+ return null;
+ }
+ public DataHandler getDataHandler() throws MessagingException {
+ return null;
+ }
+ public String getDescription() throws MessagingException {
+ return null;
+ }
+ public String getDisposition() throws MessagingException {
+ return null;
+ }
+ public String getFileName() throws MessagingException {
+ return null;
+ }
+ public String[] getHeader(String name) throws MessagingException {
+ return null;
+ }
+ public InputStream getInputStream()
+ throws IOException, MessagingException {
+ return null;
+ }
+ public int getLineCount() throws MessagingException {
+ return 0;
+ }
+ public Enumeration getMatchingHeaders(String[] names)
+ throws MessagingException {
+ return null;
+ }
+ public Enumeration getNonMatchingHeaders(String[] names)
+ throws MessagingException {
+ return null;
+ }
+ public int getSize() throws MessagingException {
+ return 0;
+ }
+ public boolean isMimeType(String mimeType) throws MessagingException {
+ return false;
+ }
+ public void removeHeader(String name) throws MessagingException {
+ }
+ public void setContent(Multipart content) throws MessagingException {
+ }
+ public void setContent(Object content, String type)
+ throws MessagingException {
+ }
+ public void setDataHandler(DataHandler handler)
+ throws MessagingException {
+ }
+ public void setDescription(String description)
+ throws MessagingException {
+ }
+ public void setDisposition(String disposition)
+ throws MessagingException {
+ }
+ public void setFileName(String name) throws MessagingException {
+ }
+ public void setHeader(String name, String value)
+ throws MessagingException {
+ }
+ public void setText(String content) throws MessagingException {
+ }
+ public void writeTo(OutputStream out)
+ throws IOException, MessagingException {
+ }
+ }
+ */
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/MessagingExceptionTest.java b/external/geronimo_javamail/src/test/java/javax/mail/MessagingExceptionTest.java
new file mode 100644
index 00000000..1295179d
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/MessagingExceptionTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class MessagingExceptionTest extends TestCase {
+ private RuntimeException d;
+ private MessagingException c;
+ private MessagingException b;
+ private MessagingException a;
+ public MessagingExceptionTest(String name) {
+ super(name);
+ }
+ protected void setUp() throws Exception {
+ super.setUp();
+ a = new MessagingException("A");
+ b = new MessagingException("B");
+ c = new MessagingException("C");
+ d = new RuntimeException("D");
+ }
+ public void testMessagingExceptionString() {
+ assertEquals("A", a.getMessage());
+ }
+ public void testNextException() {
+ assertTrue(a.setNextException(b));
+ assertEquals(b, a.getNextException());
+ assertTrue(a.setNextException(c));
+ assertEquals(b, a.getNextException());
+ assertEquals(c, b.getNextException());
+ String message = a.getMessage();
+ int ap = message.indexOf("A");
+ int bp = message.indexOf("B");
+ int cp = message.indexOf("C");
+ assertTrue("A does not contain 'A'", ap != -1);
+ assertTrue("B does not contain 'B'", bp != -1);
+ assertTrue("C does not contain 'C'", cp != -1);
+ }
+ public void testNextExceptionWrong() {
+ assertTrue(a.setNextException(d));
+ assertFalse(a.setNextException(b));
+ }
+ public void testNextExceptionWrong2() {
+ assertTrue(a.setNextException(d));
+ assertFalse(a.setNextException(b));
+ }
+ public void testMessagingExceptionStringException() {
+ MessagingException x = new MessagingException("X", a);
+ assertEquals("X (javax.mail.MessagingException: A)", x.getMessage());
+ assertEquals(a, x.getNextException());
+ assertEquals(a, x.getCause());
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/PasswordAuthenticationTest.java b/external/geronimo_javamail/src/test/java/javax/mail/PasswordAuthenticationTest.java
new file mode 100644
index 00000000..8b8d09f5
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/PasswordAuthenticationTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class PasswordAuthenticationTest extends TestCase {
+ public PasswordAuthenticationTest(String name) {
+ super(name);
+ }
+ public void testPA() {
+ String user = String.valueOf(System.currentTimeMillis());
+ String password = "JobbyJobbyJobby" + user;
+ PasswordAuthentication pa = new PasswordAuthentication(user, password);
+ assertEquals(user, pa.getUserName());
+ assertEquals(password, pa.getPassword());
+ }
+ public void testPasswordAuthentication() {
+ PasswordAuthentication pa = new PasswordAuthentication("Alex", "xelA");
+ assertEquals("Alex", pa.getUserName());
+ assertEquals("xelA", pa.getPassword());
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/QuotaTest.java b/external/geronimo_javamail/src/test/java/javax/mail/QuotaTest.java
new file mode 100644
index 00000000..3fc6c389
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/QuotaTest.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 javax.mail;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class QuotaTest extends TestCase {
+
+ public void testQuota() throws MessagingException {
+ Quota quota = new Quota("Fred");
+
+ assertEquals("Fred", quota.quotaRoot);
+ assertNull(quota.resources);
+
+ quota.setResourceLimit("Storage", 20000);
+
+ assertNotNull(quota.resources);
+ assertTrue(quota.resources.length == 1);
+ assertEquals("Storage", quota.resources[0].name);
+ assertEquals(0, quota.resources[0].usage);
+ assertEquals(20000, quota.resources[0].limit);
+
+ quota.setResourceLimit("Storage", 30000);
+
+ assertNotNull(quota.resources);
+ assertTrue(quota.resources.length == 1);
+ assertEquals("Storage", quota.resources[0].name);
+ assertEquals(0, quota.resources[0].usage);
+ assertEquals(30000, quota.resources[0].limit);
+
+ quota.setResourceLimit("Folders", 5);
+
+ assertNotNull(quota.resources);
+ assertTrue(quota.resources.length == 2);
+ assertEquals("Storage", quota.resources[0].name);
+ assertEquals(0, quota.resources[0].usage);
+ assertEquals(30000, quota.resources[0].limit);
+
+ assertEquals("Folders", quota.resources[1].name);
+ assertEquals(0, quota.resources[1].usage);
+ assertEquals(5, quota.resources[1].limit);
+ }
+
+}
+
+
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/SessionTest.java b/external/geronimo_javamail/src/test/java/javax/mail/SessionTest.java
new file mode 100644
index 00000000..800e3eb9
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/SessionTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.lang.InterruptedException;
+import java.lang.Thread;
+import java.util.Properties;
+
+import javax.mail.MessagingException;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class SessionTest extends TestCase {
+ public class MailThread extends Thread {
+ public volatile boolean success;
+ private final Session session;
+
+ MailThread(Session session) {
+ success = true;
+ this.session = session;
+ }
+
+ public void run() {
+ try {
+ for (int i = 0; i < 1000; i++) {
+ InternetAddress addr = new InternetAddress("person@example.com",
+ "Me");
+ Transport t = session.getTransport(addr);
+ }
+ } catch (Exception e) {
+ success = false;
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void testAddProvider() throws MessagingException {
+ Properties props = System.getProperties();
+ // Get a Session object
+ Session mailSession = Session.getDefaultInstance(props, null);
+
+ mailSession.addProvider(new Provider(Provider.Type.TRANSPORT, "foo", NullTransport.class.getName(), "Apache", "Java 1.4 Test"));
+
+ // retrieve the transport
+ Transport trans = mailSession.getTransport("foo");
+
+ assertTrue(trans instanceof NullTransport);
+
+ mailSession.setProtocolForAddress("foo", "foo");
+
+ trans = mailSession.getTransport(new FooAddress());
+
+ assertTrue(trans instanceof NullTransport);
+ }
+
+ public void testConcurrentTransport() throws InterruptedException {
+ int kThreads = 1000;
+ Properties props = new Properties();
+ Session session = Session.getDefaultInstance(props, null);
+ session.addProvider(new Provider(Provider.Type.TRANSPORT, "smtp", NullTransport.class.getName(), "Apache", "Java 1.4 Test"));
+ MailThread threads[] = new MailThread[kThreads];
+ for (int i = 0; i < kThreads; i++) {
+ threads[i] = new MailThread(session);
+ threads[i].start();
+ }
+ for (int i = 0; i < kThreads; i++) {
+ threads[i].join();
+ assertTrue(threads[i].success);
+ }
+ }
+
+ static public class NullTransport extends Transport {
+ public NullTransport(Session session, URLName urlName) {
+ super(session, urlName);
+ }
+
+ public void sendMessage(Message message, Address[] addresses) throws MessagingException {
+ // do nothing
+ }
+
+ protected boolean protocolConnect(String host, int port, String user, String password) throws MessagingException {
+ return true; // always connect
+ }
+
+ }
+
+ static public class FooAddress extends Address {
+ public FooAddress() {
+ }
+
+ public String getType() {
+ return "foo";
+ }
+
+ public String toString() {
+ return "yada";
+ }
+
+
+ public boolean equals(Object other) {
+ return true;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/SimpleFolder.java b/external/geronimo_javamail/src/test/java/javax/mail/SimpleFolder.java
new file mode 100644
index 00000000..6ada7f8b
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/SimpleFolder.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class SimpleFolder extends Folder {
+ private static final Message[] MESSAGE_ARRAY = new Message[0];
+ @SuppressWarnings("rawtypes") private List _messages = new LinkedList();
+ private String _name;
+ public SimpleFolder(Store store) {
+ this(store, "SimpleFolder");
+ }
+ SimpleFolder(Store store, String name) {
+ super(store);
+ _name = name;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#appendMessages(javax.mail.Message[])
+ */
+ @SuppressWarnings("unchecked") // Legacy
+ public void appendMessages(Message[] messages) throws MessagingException {
+ for (int i = 0; i < messages.length; i++) {
+ Message message = messages[i];
+ _messages.add(message);
+ }
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#close(boolean)
+ */
+ public void close(boolean expunge) throws MessagingException {
+ if (expunge) {
+ expunge();
+ }
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#create(int)
+ */
+ public boolean create(int type) throws MessagingException {
+ if (type == HOLDS_MESSAGES) {
+ return true;
+ } else {
+ throw new MessagingException("Cannot create folders that hold folders");
+ }
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#delete(boolean)
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"}) // Legacy
+ public boolean delete(boolean recurse) throws MessagingException {
+ _messages = new LinkedList();
+ return true;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#exists()
+ */
+ public boolean exists() throws MessagingException {
+ return true;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#expunge()
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"}) // Legacy
+ public Message[] expunge() throws MessagingException {
+ @SuppressWarnings("rawtypes") Iterator it = _messages.iterator();
+
+ @SuppressWarnings("rawtypes") List result = new LinkedList();
+ while (it.hasNext()) {
+ Message message = (Message) it.next();
+ if (message.isSet(Flags.Flag.DELETED)) {
+ it.remove();
+ result.add(message);
+ }
+ }
+ // run through and renumber the messages
+ for (int i = 0; i < _messages.size(); i++) {
+ Message message = (Message) _messages.get(i);
+ message.setMessageNumber(i);
+ }
+ return (Message[]) result.toArray(MESSAGE_ARRAY);
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#getFolder(java.lang.String)
+ */
+ public Folder getFolder(String name) throws MessagingException {
+ return null;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#getFullName()
+ */
+ public String getFullName() {
+ return getName();
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#getMessage(int)
+ */
+ public Message getMessage(int id) throws MessagingException {
+ return (Message) _messages.get(id);
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#getMessageCount()
+ */
+ public int getMessageCount() throws MessagingException {
+ return _messages.size();
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#getName()
+ */
+ public String getName() {
+ return _name;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#getParent()
+ */
+ public Folder getParent() throws MessagingException {
+ return null;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#getPermanentFlags()
+ */
+ public Flags getPermanentFlags() {
+ return null;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#getSeparator()
+ */
+ public char getSeparator() throws MessagingException {
+ return '/';
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#getType()
+ */
+ public int getType() throws MessagingException {
+ return HOLDS_MESSAGES;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#hasNewMessages()
+ */
+ public boolean hasNewMessages() throws MessagingException {
+ return false;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#isOpen()
+ */
+ public boolean isOpen() {
+ return true;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#list(java.lang.String)
+ */
+ public Folder[] list(String pattern) throws MessagingException {
+ return null;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#open(int)
+ */
+ public void open(int mode) throws MessagingException {
+ if (mode != HOLDS_MESSAGES) {
+ throw new MessagingException("SimpleFolder can only be opened with HOLDS_MESSAGES");
+ }
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Folder#renameTo(javax.mail.Folder)
+ */
+ public boolean renameTo(Folder newName) throws MessagingException {
+ _name = newName.getName();
+ return true;
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/SimpleTextMessage.java b/external/geronimo_javamail/src/test/java/javax/mail/SimpleTextMessage.java
new file mode 100644
index 00000000..fd66f2b5
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/SimpleTextMessage.java
@@ -0,0 +1,352 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import javax.activation.DataHandler;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class SimpleTextMessage extends Message {
+ public static final Address[] ADDRESS_ARRAY = new Address[0];
+ @SuppressWarnings("rawtypes") private List _bcc = new LinkedList();
+ @SuppressWarnings("rawtypes") private List _cc = new LinkedList();
+ private String _description;
+ private Flags _flags = new Flags();
+ @SuppressWarnings("rawtypes") private List _from = new LinkedList();
+ private Date _received;
+ private Date _sent;
+ private String _subject;
+ private String _text;
+ @SuppressWarnings("rawtypes") private List _to = new LinkedList();
+ /**
+ * @param folder
+ * @param number
+ */
+ public SimpleTextMessage(Folder folder, int number) {
+ super(folder, number);
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#addFrom(javax.mail.Address[])
+ */
+ @SuppressWarnings("unchecked") // Legacy
+ public void addFrom(Address[] addresses) throws MessagingException {
+ _from.addAll(Arrays.asList(addresses));
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#addHeader(java.lang.String, java.lang.String)
+ */
+ public void addHeader(String name, String value)
+ throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#addRecipients(javax.mail.Message.RecipientType, javax.mail.Address[])
+ */
+ @SuppressWarnings("unchecked") // Legacy
+ public void addRecipients(RecipientType type, Address[] addresses)
+ throws MessagingException {
+ getList(type).addAll(Arrays.asList(addresses));
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getAllHeaders()
+ */
+ @SuppressWarnings("rawtypes")
+ public Enumeration getAllHeaders() throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getContent()
+ */
+ public Object getContent() throws IOException, MessagingException {
+ return _text;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getContentType()
+ */
+ public String getContentType() throws MessagingException {
+ return "text/plain";
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getDataHandler()
+ */
+ public DataHandler getDataHandler() throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getDescription()
+ */
+ public String getDescription() throws MessagingException {
+ return _description;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getDisposition()
+ */
+ public String getDisposition() throws MessagingException {
+ return Part.INLINE;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getFileName()
+ */
+ public String getFileName() throws MessagingException {
+ return null;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#getFlags()
+ */
+ public Flags getFlags() throws MessagingException {
+ return _flags;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#getFrom()
+ */
+ @SuppressWarnings("unchecked") // Legacy
+ public Address[] getFrom() throws MessagingException {
+ return (Address[]) _from.toArray(ADDRESS_ARRAY);
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getHeader(java.lang.String)
+ */
+ public String[] getHeader(String name) throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getInputStream()
+ */
+ public InputStream getInputStream()
+ throws IOException, MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getLineCount()
+ */
+ public int getLineCount() throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ @SuppressWarnings("rawtypes")
+ private List getList(RecipientType type) throws MessagingException {
+ @SuppressWarnings("rawtypes") List list;
+ if (type == RecipientType.TO) {
+ list = _to;
+ } else if (type == RecipientType.CC) {
+ list = _cc;
+ } else if (type == RecipientType.BCC) {
+ list = _bcc;
+ } else {
+ throw new MessagingException("Address type not understood");
+ }
+ return list;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getMatchingHeaders(java.lang.String[])
+ */
+ @SuppressWarnings("rawtypes")
+ public Enumeration getMatchingHeaders(String[] names)
+ throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getNonMatchingHeaders(java.lang.String[])
+ */
+ @SuppressWarnings("rawtypes")
+ public Enumeration getNonMatchingHeaders(String[] names)
+ throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#getReceivedDate()
+ */
+ public Date getReceivedDate() throws MessagingException {
+ return _received;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#getRecipients(javax.mail.Message.RecipientType)
+ */
+ @SuppressWarnings("unchecked") // Legacy
+ public Address[] getRecipients(RecipientType type)
+ throws MessagingException {
+ return (Address[]) getList(type).toArray(ADDRESS_ARRAY);
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#getSentDate()
+ */
+ public Date getSentDate() throws MessagingException {
+ return _sent;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#getSize()
+ */
+ public int getSize() throws MessagingException {
+ return _text.length();
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#getSubject()
+ */
+ public String getSubject() throws MessagingException {
+ return _subject;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#isMimeType(java.lang.String)
+ */
+ public boolean isMimeType(String mimeType) throws MessagingException {
+ return mimeType.equals("text/plain") || mimeType.equals("text/*");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#removeHeader(java.lang.String)
+ */
+ public void removeHeader(String name) throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#reply(boolean)
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"}) // Legacy
+ public Message reply(boolean replyToAll) throws MessagingException {
+ try {
+ SimpleTextMessage replyx = (SimpleTextMessage) this.clone();
+ replyx._to = new LinkedList(_from);
+ if (replyToAll) {
+ replyx._to.addAll(_cc);
+ }
+ return replyx;
+ } catch (CloneNotSupportedException e) {
+ throw new MessagingException(e.getMessage());
+ }
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#saveChanges()
+ */
+ public void saveChanges() throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#setContent(javax.mail.Multipart)
+ */
+ public void setContent(Multipart content) throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#setContent(java.lang.Object, java.lang.String)
+ */
+ public void setContent(Object content, String type)
+ throws MessagingException {
+ setText((String) content);
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#setDataHandler(javax.activation.DataHandler)
+ */
+ public void setDataHandler(DataHandler handler) throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#setDescription(java.lang.String)
+ */
+ public void setDescription(String description) throws MessagingException {
+ _description = description;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#setDisposition(java.lang.String)
+ */
+ public void setDisposition(String disposition) throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#setFileName(java.lang.String)
+ */
+ public void setFileName(String name) throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#setFlags(javax.mail.Flags, boolean)
+ */
+ public void setFlags(Flags flags, boolean set) throws MessagingException {
+ if (set) {
+ _flags.add(flags);
+ } else {
+ _flags.remove(flags);
+ }
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#setFrom()
+ */
+ public void setFrom() throws MessagingException {
+ setFrom(new InternetAddress("root@localhost"));
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#setFrom(javax.mail.Address)
+ */
+ @SuppressWarnings("unchecked") // Legacy
+ public void setFrom(Address address) throws MessagingException {
+ _from.clear();
+ _from.add(address);
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#setHeader(java.lang.String, java.lang.String)
+ */
+ public void setHeader(String name, String value)
+ throws MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#setRecipients(javax.mail.Message.RecipientType, javax.mail.Address[])
+ */
+ @SuppressWarnings("unchecked") // Legacy
+ public void setRecipients(RecipientType type, Address[] addresses)
+ throws MessagingException {
+ @SuppressWarnings("rawtypes")
+ List list = getList(type);
+ list.clear();
+ list.addAll(Arrays.asList(addresses));
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#setSentDate(java.util.Date)
+ */
+ public void setSentDate(Date sent) throws MessagingException {
+ _sent = sent;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Message#setSubject(java.lang.String)
+ */
+ public void setSubject(String subject) throws MessagingException {
+ _subject = subject;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#setText(java.lang.String)
+ */
+ public void setText(String content) throws MessagingException {
+ _text = content;
+ }
+ /* (non-Javadoc)
+ * @see javax.mail.Part#writeTo(java.io.OutputStream)
+ */
+ public void writeTo(OutputStream out)
+ throws IOException, MessagingException {
+ throw new UnsupportedOperationException("Method not implemented");
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/TestData.java b/external/geronimo_javamail/src/test/java/javax/mail/TestData.java
new file mode 100644
index 00000000..5ae4c3b3
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/TestData.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import javax.mail.internet.MimeMessage;
+
+public class TestData {
+ public static Store getTestStore() {
+ return new Store(
+ getTestSession(),
+ new URLName("http://alex@test.com")) {
+ public Folder getDefaultFolder() throws MessagingException {
+ return getTestFolder();
+ }
+ public Folder getFolder(String name) throws MessagingException {
+ if (name.equals("test")) {
+ return getTestFolder();
+ } else {
+ return null;
+ }
+ }
+ public Folder getFolder(URLName name) throws MessagingException {
+ return getTestFolder();
+ }
+ };
+ }
+ public static Session getTestSession() {
+ return Session.getDefaultInstance(System.getProperties());
+ }
+ public static Folder getTestFolder() {
+ return new Folder(getTestStore()) {
+ public void appendMessages(Message[] messages)
+ throws MessagingException {
+ }
+ public void close(boolean expunge) throws MessagingException {
+ }
+ public boolean create(int type) throws MessagingException {
+ return false;
+ }
+ public boolean delete(boolean recurse) throws MessagingException {
+ return false;
+ }
+ public boolean exists() throws MessagingException {
+ return false;
+ }
+ public Message[] expunge() throws MessagingException {
+ return null;
+ }
+ public Folder getFolder(String name) throws MessagingException {
+ return null;
+ }
+ public String getFullName() {
+ return null;
+ }
+ public Message getMessage(int id) throws MessagingException {
+ return null;
+ }
+ public int getMessageCount() throws MessagingException {
+ return 0;
+ }
+ public String getName() {
+ return null;
+ }
+ public Folder getParent() throws MessagingException {
+ return null;
+ }
+ public Flags getPermanentFlags() {
+ return null;
+ }
+ public char getSeparator() throws MessagingException {
+ return 0;
+ }
+ public int getType() throws MessagingException {
+ return 0;
+ }
+ public boolean hasNewMessages() throws MessagingException {
+ return false;
+ }
+ public boolean isOpen() {
+ return false;
+ }
+ public Folder[] list(String pattern) throws MessagingException {
+ return null;
+ }
+ public void open(int mode) throws MessagingException {
+ }
+ public boolean renameTo(Folder newName) throws MessagingException {
+ return false;
+ }
+ };
+ }
+ public static Transport getTestTransport() {
+ return new Transport(
+ getTestSession(),
+ new URLName("http://host.name")) {
+ public void sendMessage(Message message, Address[] addresses)
+ throws MessagingException {
+ // TODO Auto-generated method stub
+ }
+ };
+ }
+ public static Message getMessage() {
+ return new MimeMessage(getTestFolder(), 1) {
+ };
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/URLNameTest.java b/external/geronimo_javamail/src/test/java/javax/mail/URLNameTest.java
new file mode 100644
index 00000000..339a1711
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/URLNameTest.java
@@ -0,0 +1,391 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 593290 $ $Date: 2007-11-08 14:18:29 -0600 (Thu, 08 Nov 2007) $
+ */
+public class URLNameTest extends TestCase {
+ public URLNameTest(String name) {
+ super(name);
+ }
+
+ public void testURLNameString() {
+ String s;
+ URLName name;
+
+ s = "http://www.apache.org";
+ name = new URLName(s);
+ assertEquals(s, name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertNull(name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ assertEquals(new URL(s), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ s = "http://www.apache.org/file/file1#ref";
+ name = new URLName(s);
+ assertEquals(s, name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/file1", name.getFile());
+ assertEquals("ref", name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ assertEquals(new URL(s), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ s = "http://www.apache.org/file/";
+ name = new URLName(s);
+ assertEquals(s, name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/", name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ assertEquals(new URL(s), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ s = "http://john@www.apache.org/file/";
+ name = new URLName(s);
+ assertEquals(s, name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/", name.getFile());
+ assertNull(name.getRef());
+ assertEquals("john", name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ assertEquals(new URL(s), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ s = "http://john:doe@www.apache.org/file/";
+ name = new URLName(s);
+ assertEquals(s, name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/", name.getFile());
+ assertNull(name.getRef());
+ assertEquals("john", name.getUsername());
+ assertEquals("doe", name.getPassword());
+ try {
+ assertEquals(new URL(s), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ s = "http://john%40gmail.com:doe@www.apache.org/file/";
+ name = new URLName(s);
+ assertEquals(s, name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/", name.getFile());
+ assertNull(name.getRef());
+ assertEquals("john@gmail.com", name.getUsername());
+ assertEquals("doe", name.getPassword());
+ try {
+ assertEquals(new URL(s), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ s = "file/file2";
+ name = new URLName(s);
+ assertNull(name.getProtocol());
+ assertNull(name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/file2", name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ name.getURL();
+ fail();
+ } catch (MalformedURLException e) {
+ // OK
+ }
+
+ name = new URLName((String) null);
+ assertNull( name.getProtocol());
+ assertNull(name.getHost());
+ assertEquals(-1, name.getPort());
+ assertNull(name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ name.getURL();
+ fail();
+ } catch (MalformedURLException e) {
+ // OK
+ }
+
+ name = new URLName("");
+ assertNull( name.getProtocol());
+ assertNull(name.getHost());
+ assertEquals(-1, name.getPort());
+ assertNull(name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ name.getURL();
+ fail();
+ } catch (MalformedURLException e) {
+ // OK
+ }
+ }
+
+ public void testURLNameAll() {
+ URLName name;
+ name = new URLName(null, null, -1, null, null, null);
+ assertNull(name.getProtocol());
+ assertNull(name.getHost());
+ assertEquals(-1, name.getPort());
+ assertNull(name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ name.getURL();
+ fail();
+ } catch (MalformedURLException e) {
+ // OK
+ }
+
+ name = new URLName("", "", -1, "", "", "");
+ assertNull(name.getProtocol());
+ assertNull(name.getHost());
+ assertEquals(-1, name.getPort());
+ assertNull(name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ name.getURL();
+ fail();
+ } catch (MalformedURLException e) {
+ // OK
+ }
+
+ name = new URLName("http", "www.apache.org", -1, null, null, null);
+ assertEquals("http://www.apache.org", name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertNull(name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ assertEquals(new URL("http://www.apache.org"), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ name = new URLName("http", "www.apache.org", 8080, "", "", "");
+ assertEquals("http://www.apache.org:8080", name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(8080, name.getPort());
+ assertNull(name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ assertEquals(new URL("http://www.apache.org:8080"), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ name = new URLName("http", "www.apache.org", -1, "file/file2", "", "");
+ assertEquals("http://www.apache.org/file/file2", name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/file2", name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ assertEquals(new URL("http://www.apache.org/file/file2"), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ name = new URLName("http", "www.apache.org", -1, "file/file2", "john", "");
+ assertEquals("http://john@www.apache.org/file/file2", name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/file2", name.getFile());
+ assertNull(name.getRef());
+ assertEquals("john", name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ assertEquals(new URL("http://john@www.apache.org/file/file2"), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ name = new URLName("http", "www.apache.org", -1, "file/file2", "john", "doe");
+ assertEquals("http://john:doe@www.apache.org/file/file2", name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/file2", name.getFile());
+ assertNull(name.getRef());
+ assertEquals("john", name.getUsername());
+ assertEquals("doe", name.getPassword());
+ try {
+ assertEquals(new URL("http://john:doe@www.apache.org/file/file2"), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ name = new URLName("http", "www.apache.org", -1, "file/file2", "john@gmail.com", "doe");
+ assertEquals("http://john%40gmail.com:doe@www.apache.org/file/file2", name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/file2", name.getFile());
+ assertNull(name.getRef());
+ assertEquals("john@gmail.com", name.getUsername());
+ assertEquals("doe", name.getPassword());
+ try {
+ assertEquals(new URL("http://john%40gmail.com:doe@www.apache.org/file/file2"), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+
+ name = new URLName("http", "www.apache.org", -1, "file/file2", "", "doe");
+ assertEquals("http://www.apache.org/file/file2", name.toString());
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("file/file2", name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ assertEquals(new URL("http://www.apache.org/file/file2"), name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+ }
+
+ public void testURLNameURL() throws MalformedURLException {
+ URL url;
+ URLName name;
+
+ url = new URL("http://www.apache.org");
+ name = new URLName(url);
+ assertEquals("http", name.getProtocol());
+ assertEquals("www.apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertNull(name.getFile());
+ assertNull(name.getRef());
+ assertNull(name.getUsername());
+ assertNull(name.getPassword());
+ try {
+ assertEquals(url, name.getURL());
+ } catch (MalformedURLException e) {
+ fail();
+ }
+ }
+
+ public void testEquals() throws MalformedURLException {
+ URLName name1 = new URLName("http://www.apache.org");
+ assertEquals(name1, new URLName("http://www.apache.org"));
+ assertEquals(name1, new URLName(new URL("http://www.apache.org")));
+ assertEquals(name1, new URLName("http", "www.apache.org", -1, null, null, null));
+ assertEquals(name1, new URLName("http://www.apache.org#foo")); // wierd but ref is not part of the equals contract
+ assertTrue(!name1.equals(new URLName("http://www.apache.org:8080")));
+ assertTrue(!name1.equals(new URLName("http://cvs.apache.org")));
+ assertTrue(!name1.equals(new URLName("https://www.apache.org")));
+
+ name1 = new URLName("http://john:doe@www.apache.org");
+ assertEquals(name1, new URLName(new URL("http://john:doe@www.apache.org")));
+ assertEquals(name1, new URLName("http", "www.apache.org", -1, null, "john", "doe"));
+ assertTrue(!name1.equals(new URLName("http://john:xxx@www.apache.org")));
+ assertTrue(!name1.equals(new URLName("http://xxx:doe@www.apache.org")));
+ assertTrue(!name1.equals(new URLName("http://www.apache.org")));
+
+ assertEquals(new URLName("http://john@www.apache.org"), new URLName("http", "www.apache.org", -1, null, "john", null));
+ assertEquals(new URLName("http://www.apache.org"), new URLName("http", "www.apache.org", -1, null, null, "doe"));
+ }
+
+ public void testHashCode() {
+ URLName name1 = new URLName("http://www.apache.org/file");
+ URLName name2 = new URLName("http://www.apache.org/file#ref");
+ assertTrue(name1.equals(name2));
+ assertTrue(name1.hashCode() == name2.hashCode());
+ }
+
+ public void testNullProtocol() {
+ URLName name1 = new URLName(null, "www.apache.org", -1, null, null, null);
+ assertTrue(!name1.equals(name1));
+ }
+
+ public void testOpaqueSchemes() {
+ String s;
+ URLName name;
+
+ // not strictly opaque but no protocol handler installed
+ s = "foo://jdoe@apache.org/INBOX";
+ name = new URLName(s);
+ assertEquals(s, name.toString());
+ assertEquals("foo", name.getProtocol());
+ assertEquals("apache.org", name.getHost());
+ assertEquals(-1, name.getPort());
+ assertEquals("INBOX", name.getFile());
+ assertNull(name.getRef());
+ assertEquals("jdoe", name.getUsername());
+ assertNull(name.getPassword());
+
+ // TBD as I am not sure what other URL formats to use
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/event/AllEventTests.java b/external/geronimo_javamail/src/test/java/javax/mail/event/AllEventTests.java
new file mode 100644
index 00000000..179b26bc
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/event/AllEventTests.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class AllEventTests {
+ public static Test suite() {
+ TestSuite suite = new TestSuite("Test for javax.mail.event");
+ //$JUnit-BEGIN$
+ suite.addTest(new TestSuite(ConnectionEventTest.class));
+ suite.addTest(new TestSuite(FolderEventTest.class));
+ suite.addTest(new TestSuite(MessageChangedEventTest.class));
+ suite.addTest(new TestSuite(StoreEventTest.class));
+ suite.addTest(new TestSuite(MessageCountEventTest.class));
+ suite.addTest(new TestSuite(TransportEventTest.class));
+ //$JUnit-END$
+ return suite;
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/event/ConnectionEventTest.java b/external/geronimo_javamail/src/test/java/javax/mail/event/ConnectionEventTest.java
new file mode 100644
index 00000000..95ddf71f
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/event/ConnectionEventTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class ConnectionEventTest extends TestCase {
+ public static class ConnectionListenerTest implements ConnectionListener {
+ private int state = 0;
+ public void closed(ConnectionEvent event) {
+ if (state != 0) {
+ fail("Recycled ConnectionListener");
+ }
+ state = ConnectionEvent.CLOSED;
+ }
+ public void disconnected(ConnectionEvent event) {
+ if (state != 0) {
+ fail("Recycled ConnectionListener");
+ }
+ state = ConnectionEvent.DISCONNECTED;
+ }
+ public int getState() {
+ return state;
+ }
+ public void opened(ConnectionEvent event) {
+ if (state != 0) {
+ fail("Recycled ConnectionListener");
+ }
+ state = ConnectionEvent.OPENED;
+ }
+ }
+ public ConnectionEventTest(String name) {
+ super(name);
+ }
+ private void doEventTests(int type) {
+ ConnectionEvent event = new ConnectionEvent(this, type);
+ assertEquals(this, event.getSource());
+ assertEquals(type, event.getType());
+ ConnectionListenerTest listener = new ConnectionListenerTest();
+ event.dispatch(listener);
+ assertEquals("Unexpcted method dispatched", type, listener.getState());
+ }
+ public void testEvent() {
+ doEventTests(ConnectionEvent.CLOSED);
+ doEventTests(ConnectionEvent.OPENED);
+ doEventTests(ConnectionEvent.DISCONNECTED);
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/event/FolderEventTest.java b/external/geronimo_javamail/src/test/java/javax/mail/event/FolderEventTest.java
new file mode 100644
index 00000000..878e34c1
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/event/FolderEventTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class FolderEventTest extends TestCase {
+ public FolderEventTest(String name) {
+ super(name);
+ }
+ public void testEvent() {
+ doEventTests(FolderEvent.CREATED);
+ doEventTests(FolderEvent.RENAMED);
+ doEventTests(FolderEvent.DELETED);
+ }
+ private void doEventTests(int type) {
+ FolderEvent event = new FolderEvent(this, null, type);
+ assertEquals(this, event.getSource());
+ assertEquals(type, event.getType());
+ FolderListenerTest listener = new FolderListenerTest();
+ event.dispatch(listener);
+ assertEquals("Unexpcted method dispatched", type, listener.getState());
+ }
+ public static class FolderListenerTest implements FolderListener {
+ private int state = 0;
+ public void folderCreated(FolderEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = FolderEvent.CREATED;
+ }
+ public void folderDeleted(FolderEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = FolderEvent.DELETED;
+ }
+ public void folderRenamed(FolderEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = FolderEvent.RENAMED;
+ }
+ public int getState() {
+ return state;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/event/MessageChangedEventTest.java b/external/geronimo_javamail/src/test/java/javax/mail/event/MessageChangedEventTest.java
new file mode 100644
index 00000000..0245ef05
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/event/MessageChangedEventTest.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class MessageChangedEventTest extends TestCase {
+ public MessageChangedEventTest(String name) {
+ super(name);
+ }
+ public void testEvent() {
+ doEventTests(MessageChangedEvent.ENVELOPE_CHANGED);
+ doEventTests(MessageChangedEvent.FLAGS_CHANGED);
+ }
+ private void doEventTests(int type) {
+ MessageChangedEvent event = new MessageChangedEvent(this, type, null);
+ assertEquals(this, event.getSource());
+ assertEquals(type, event.getMessageChangeType());
+ MessageChangedListenerTest listener = new MessageChangedListenerTest();
+ event.dispatch(listener);
+ assertEquals("Unexpcted method dispatched", type, listener.getState());
+ }
+ public static class MessageChangedListenerTest
+ implements MessageChangedListener {
+ private int state = 0;
+ public void messageChanged(MessageChangedEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = event.getMessageChangeType();
+ }
+ public int getState() {
+ return state;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/event/MessageCountEventTest.java b/external/geronimo_javamail/src/test/java/javax/mail/event/MessageCountEventTest.java
new file mode 100644
index 00000000..ca2e1668
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/event/MessageCountEventTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import javax.mail.Folder;
+import javax.mail.TestData;
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class MessageCountEventTest extends TestCase {
+ public MessageCountEventTest(String name) {
+ super(name);
+ }
+ public void testEvent() {
+ doEventTests(MessageCountEvent.ADDED);
+ doEventTests(MessageCountEvent.REMOVED);
+ try {
+ doEventTests(-12345);
+ fail("Expected exception due to invalid type -12345");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ private void doEventTests(int type) {
+ Folder folder = TestData.getTestFolder();
+ MessageCountEvent event =
+ new MessageCountEvent(folder, type, false, null);
+ assertEquals(folder, event.getSource());
+ assertEquals(type, event.getType());
+ MessageCountListenerTest listener = new MessageCountListenerTest();
+ event.dispatch(listener);
+ assertEquals("Unexpcted method dispatched", type, listener.getState());
+ }
+ public static class MessageCountListenerTest
+ implements MessageCountListener {
+ private int state = 0;
+ public void messagesAdded(MessageCountEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = MessageCountEvent.ADDED;
+ }
+ public void messagesRemoved(MessageCountEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = MessageCountEvent.REMOVED;
+ }
+ public int getState() {
+ return state;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/event/StoreEventTest.java b/external/geronimo_javamail/src/test/java/javax/mail/event/StoreEventTest.java
new file mode 100644
index 00000000..bc1c5e0b
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/event/StoreEventTest.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 javax.mail.event;
+
+import javax.mail.Store;
+import javax.mail.TestData;
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class StoreEventTest extends TestCase {
+ public StoreEventTest(String name) {
+ super(name);
+ }
+ public void testEvent() {
+ doEventTests(StoreEvent.ALERT);
+ doEventTests(StoreEvent.NOTICE);
+ try {
+ StoreEvent event = new StoreEvent(null, -12345, "Hello World");
+ fail(
+ "Expected exception due to invalid type "
+ + event.getMessageType());
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ private void doEventTests(int type) {
+ Store source = TestData.getTestStore();
+ StoreEvent event = new StoreEvent(source, type, "Hello World");
+ assertEquals(source, event.getSource());
+ assertEquals("Hello World", event.getMessage());
+ assertEquals(type, event.getMessageType());
+ StoreListenerTest listener = new StoreListenerTest();
+ event.dispatch(listener);
+ assertEquals("Unexpcted method dispatched", type, listener.getState());
+ }
+ public static class StoreListenerTest implements StoreListener {
+ private int state = 0;
+ public void notification(StoreEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = event.getMessageType();
+ }
+ public int getState() {
+ return state;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/event/TransportEventTest.java b/external/geronimo_javamail/src/test/java/javax/mail/event/TransportEventTest.java
new file mode 100644
index 00000000..d6f7231a
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/event/TransportEventTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.event;
+
+import javax.mail.Address;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.TestData;
+import javax.mail.Transport;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class TransportEventTest extends TestCase {
+ public TransportEventTest(String name) {
+ super(name);
+ }
+ public void testEvent() throws AddressException {
+ doEventTests(TransportEvent.MESSAGE_DELIVERED);
+ doEventTests(TransportEvent.MESSAGE_PARTIALLY_DELIVERED);
+ doEventTests(TransportEvent.MESSAGE_NOT_DELIVERED);
+ }
+ private void doEventTests(int type) throws AddressException {
+ Folder folder = TestData.getTestFolder();
+ Message message = TestData.getMessage();
+ Transport transport = TestData.getTestTransport();
+ Address[] sent = new Address[] { new InternetAddress("alex@here.com")};
+ Address[] empty = new Address[0];
+ TransportEvent event =
+ new TransportEvent(transport, type, sent, empty, empty, message);
+ assertEquals(transport, event.getSource());
+ assertEquals(type, event.getType());
+ TransportListenerTest listener = new TransportListenerTest();
+ event.dispatch(listener);
+ assertEquals("Unexpcted method dispatched", type, listener.getState());
+ }
+ public static class TransportListenerTest implements TransportListener {
+ private int state = 0;
+ public void messageDelivered(TransportEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = TransportEvent.MESSAGE_DELIVERED;
+ }
+ public void messagePartiallyDelivered(TransportEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = TransportEvent.MESSAGE_PARTIALLY_DELIVERED;
+ }
+ public void messageNotDelivered(TransportEvent event) {
+ if (state != 0) {
+ fail("Recycled Listener");
+ }
+ state = TransportEvent.MESSAGE_NOT_DELIVERED;
+ }
+ public int getState() {
+ return state;
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/AllInternetTests.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/AllInternetTests.java
new file mode 100644
index 00000000..545d06ab
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/AllInternetTests.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class AllInternetTests {
+ public static Test suite() {
+ TestSuite suite = new TestSuite("Test for javax.mail.internet");
+ //$JUnit-BEGIN$
+ suite.addTest(new TestSuite(ContentTypeTest.class));
+ suite.addTest(new TestSuite(ParameterListTest.class));
+ suite.addTest(new TestSuite(InternetAddressTest.class));
+ //$JUnit-END$
+ return suite;
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/ContentDispositionTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/ContentDispositionTest.java
new file mode 100644
index 00000000..7da6ae98
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/ContentDispositionTest.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class ContentDispositionTest extends TestCase {
+
+ public ContentDispositionTest(String name) {
+ super(name);
+ }
+
+ public void testContentDisposition() throws ParseException {
+ ContentDisposition c;
+ c = new ContentDisposition();
+ assertNotNull(c.getParameterList());
+ assertNull(c.getParameterList().get("nothing"));
+ assertNull(c.getDisposition());
+ assertNull(c.toString());
+ c.setDisposition("inline");
+ assertEquals("inline",c.getDisposition());
+ c.setParameter("file","file.txt");
+ assertEquals("file.txt",c.getParameterList().get("file"));
+ assertEquals("inline; file=file.txt",c.toString());
+ c = new ContentDisposition("inline");
+ assertEquals(0,c.getParameterList().size());
+ assertEquals("inline",c.getDisposition());
+ c = new ContentDisposition("inline",new ParameterList(";charset=us-ascii;content-type=\"text/plain\""));
+ assertEquals("inline",c.getDisposition());
+ assertEquals("us-ascii",c.getParameter("charset"));
+ assertEquals("text/plain",c.getParameter("content-type"));
+ c = new ContentDisposition("attachment;content-type=\"text/html\";charset=UTF-8");
+ assertEquals("attachment",c.getDisposition());
+ assertEquals("UTF-8",c.getParameter("charset"));
+ assertEquals("text/html",c.getParameter("content-type"));
+ }
+
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/ContentTypeTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/ContentTypeTest.java
new file mode 100644
index 00000000..02be10eb
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/ContentTypeTest.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 729233 $ $Date: 2008-12-23 23:08:45 -0600 (Tue, 23 Dec 2008) $
+ */
+public class ContentTypeTest extends TestCase {
+ public ContentTypeTest(String arg0) {
+ super(arg0);
+ }
+ public void testContentType() throws ParseException {
+ ContentType type = new ContentType();
+ assertNull(type.getPrimaryType());
+ assertNull(type.getSubType());
+ assertNull(type.getParameter("charset"));
+ }
+
+ public void testContentTypeStringStringParameterList() throws ParseException {
+ ContentType type;
+ ParameterList list = new ParameterList(";charset=us-ascii");
+ type = new ContentType("text", "plain", list);
+ assertEquals("text", type.getPrimaryType());
+ assertEquals("plain", type.getSubType());
+ assertEquals("text/plain", type.getBaseType());
+ ParameterList parameterList = type.getParameterList();
+ assertEquals("us-ascii", parameterList.get("charset"));
+ assertEquals("us-ascii", type.getParameter("charset"));
+
+ }
+
+ public void testContentTypeString() throws ParseException {
+ ContentType type;
+ type = new ContentType("text/plain");
+ assertEquals("text", type.getPrimaryType());
+ assertEquals("plain", type.getSubType());
+ assertEquals("text/plain", type.getBaseType());
+ assertNotNull(type.getParameterList());
+ assertNull(type.getParameter("charset"));
+ type = new ContentType("image/audio;charset=us-ascii");
+ ParameterList parameterList = type.getParameterList();
+ assertEquals("image", type.getPrimaryType());
+ assertEquals("audio", type.getSubType());
+ assertEquals("image/audio", type.getBaseType());
+ assertEquals("us-ascii", parameterList.get("charset"));
+ assertEquals("us-ascii", type.getParameter("charset"));
+ }
+ public void testGetPrimaryType() throws ParseException {
+ }
+ public void testGetSubType() throws ParseException {
+ }
+ public void testGetBaseType() throws ParseException {
+ }
+ public void testGetParameter() throws ParseException {
+ }
+ public void testGetParameterList() throws ParseException {
+ }
+ public void testSetPrimaryType() throws ParseException {
+ ContentType type = new ContentType("text/plain");
+ type.setPrimaryType("binary");
+ assertEquals("binary", type.getPrimaryType());
+ assertEquals("plain", type.getSubType());
+ assertEquals("binary/plain", type.getBaseType());
+ }
+ public void testSetSubType() throws ParseException {
+ ContentType type = new ContentType("text/plain");
+ type.setSubType("html");
+ assertEquals("text", type.getPrimaryType());
+ assertEquals("html", type.getSubType());
+ assertEquals("text/html", type.getBaseType());
+ }
+ public void testSetParameter() throws ParseException {
+ }
+ public void testSetParameterList() throws ParseException {
+ }
+ public void testToString() throws ParseException {
+ ContentType type = new ContentType("text/plain");
+ assertEquals("text/plain", type.toString());
+ type.setParameter("foo", "bar");
+ assertEquals("text/plain; foo=bar", type.toString());
+ type.setParameter("bar", "me@apache.org");
+ assertTrue(
+ type.toString().equals("text/plain; bar=\"me@apache.org\"; foo=bar")
+ || type.toString().equals("text/plain; foo=bar; bar=\"me@apache.org\""));
+ }
+ public void testMatchContentType() throws ParseException {
+ ContentType type = new ContentType("text/plain");
+
+ ContentType test = new ContentType("text/plain");
+
+ assertTrue(type.match(test));
+
+ test = new ContentType("TEXT/plain");
+ assertTrue(type.match(test));
+ assertTrue(test.match(type));
+
+ test = new ContentType("text/PLAIN");
+ assertTrue(type.match(test));
+ assertTrue(test.match(type));
+
+ test = new ContentType("text/*");
+ assertTrue(type.match(test));
+ assertTrue(test.match(type));
+
+ test = new ContentType("text/xml");
+ assertFalse(type.match(test));
+ assertFalse(test.match(type));
+
+ test = new ContentType("binary/plain");
+ assertFalse(type.match(test));
+ assertFalse(test.match(type));
+
+ test = new ContentType("*/plain");
+ assertFalse(type.match(test));
+ assertFalse(test.match(type));
+ }
+ public void testMatchString() throws ParseException {
+ ContentType type = new ContentType("text/plain");
+ assertTrue(type.match("text/plain"));
+ assertTrue(type.match("TEXT/plain"));
+ assertTrue(type.match("text/PLAIN"));
+ assertTrue(type.match("TEXT/PLAIN"));
+ assertTrue(type.match("TEXT/*"));
+
+ assertFalse(type.match("text/xml"));
+ assertFalse(type.match("binary/plain"));
+ assertFalse(type.match("*/plain"));
+ assertFalse(type.match(""));
+ assertFalse(type.match("text/plain/yada"));
+ }
+
+ public void testSOAP12ContentType() throws ParseException {
+ ContentType type = new ContentType("multipart/related; type=\"application/xop+xml\"; start=\"\"; start-info=\"application/soap+xml; action=\\\"urn:upload\\\"\"; boundary=\"----=_Part_10_5804917.1223557742343\"");
+ assertEquals("multipart/related", type.getBaseType());
+ assertEquals("application/xop+xml", type.getParameter("type"));
+ assertEquals("", type.getParameter("start"));
+ assertEquals("application/soap+xml; action=\"urn:upload\"", type.getParameter("start-info"));
+ assertEquals("----=_Part_10_5804917.1223557742343", type.getParameter("boundary"));
+ }
+
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/HeaderTokenizerTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/HeaderTokenizerTest.java
new file mode 100644
index 00000000..46ad0cc9
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/HeaderTokenizerTest.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import javax.mail.internet.HeaderTokenizer.Token;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class HeaderTokenizerTest extends TestCase {
+ public void testTokenizer() throws ParseException {
+ Token t;
+ HeaderTokenizer ht;
+ ht =
+ new HeaderTokenizer("To: \"Geronimo List\" , \n\r Geronimo User ");
+ validateToken(ht.peek(), Token.ATOM, "To");
+ validateToken(ht.next(), Token.ATOM, "To");
+ validateToken(ht.peek(), ':', ":");
+ validateToken(ht.next(), ':', ":");
+ validateToken(ht.next(), Token.QUOTEDSTRING, "Geronimo List");
+ validateToken(ht.next(), '<', "<");
+ validateToken(ht.next(), Token.ATOM, "geronimo-dev");
+ validateToken(ht.next(), '@', "@");
+ validateToken(ht.next(), Token.ATOM, "apache");
+ validateToken(ht.next(), '.', ".");
+ validateToken(ht.next(), Token.ATOM, "org");
+ validateToken(ht.next(), '>', ">");
+ validateToken(ht.next(), ',', ",");
+ validateToken(ht.next(), Token.ATOM, "Geronimo");
+ validateToken(ht.next(), Token.ATOM, "User");
+ validateToken(ht.next(), '<', "<");
+ validateToken(ht.next(), Token.ATOM, "geronimo-user");
+ validateToken(ht.next(), '@', "@");
+ validateToken(ht.next(), Token.ATOM, "apache");
+ validateToken(ht.next(), '.', ".");
+ assertEquals("org>", ht.getRemainder());
+ validateToken(ht.peek(), Token.ATOM, "org");
+ validateToken(ht.next(), Token.ATOM, "org");
+ validateToken(ht.next(), '>', ">");
+ assertEquals(Token.EOF, ht.next().getType());
+ ht = new HeaderTokenizer(" ");
+ assertEquals(Token.EOF, ht.next().getType());
+ ht = new HeaderTokenizer("J2EE");
+ validateToken(ht.next(), Token.ATOM, "J2EE");
+ assertEquals(Token.EOF, ht.next().getType());
+ // test comments
+ doComment(true);
+ doComment(false);
+ }
+
+ public void testErrors() throws ParseException {
+ checkParseError("(Geronimo");
+ checkParseError("((Geronimo)");
+ checkParseError("\"Geronimo");
+ checkParseError("\"Geronimo\\");
+ }
+
+
+ public void testQuotedLiteral() throws ParseException {
+ checkTokenParse("\"\"", Token.QUOTEDSTRING, "");
+ checkTokenParse("\"\\\"\"", Token.QUOTEDSTRING, "\"");
+ checkTokenParse("\"\\\"\"", Token.QUOTEDSTRING, "\"");
+ checkTokenParse("\"A\r\nB\"", Token.QUOTEDSTRING, "AB");
+ checkTokenParse("\"A\nB\"", Token.QUOTEDSTRING, "A\nB");
+ }
+
+
+ public void testComment() throws ParseException {
+ checkTokenParse("()", Token.COMMENT, "");
+ checkTokenParse("(())", Token.COMMENT, "()");
+ checkTokenParse("(Foo () Bar)", Token.COMMENT, "Foo () Bar");
+ checkTokenParse("(\"Foo () Bar)", Token.COMMENT, "\"Foo () Bar");
+ checkTokenParse("(\\()", Token.COMMENT, "(");
+ checkTokenParse("(Foo \r\n Bar)", Token.COMMENT, "Foo Bar");
+ checkTokenParse("(Foo \n Bar)", Token.COMMENT, "Foo \n Bar");
+ }
+
+ public void checkTokenParse(String text, int type, String value) throws ParseException {
+ HeaderTokenizer ht;
+ ht = new HeaderTokenizer(text, HeaderTokenizer.RFC822, false);
+ validateToken(ht.next(), type, value);
+ }
+
+
+ public void checkParseError(String text) throws ParseException {
+ Token t;
+ HeaderTokenizer ht;
+
+ ht = new HeaderTokenizer(text);
+ doNextError(ht);
+ ht = new HeaderTokenizer(text);
+ doPeekError(ht);
+ }
+
+ public void doNextError(HeaderTokenizer ht) {
+ try {
+ ht.next();
+ fail("Expected ParseException");
+ } catch (ParseException e) {
+ }
+ }
+
+ public void doPeekError(HeaderTokenizer ht) {
+ try {
+ ht.peek();
+ fail("Expected ParseException");
+ } catch (ParseException e) {
+ }
+ }
+
+
+ public void doComment(boolean ignore) throws ParseException {
+ HeaderTokenizer ht;
+ Token t;
+ ht =
+ new HeaderTokenizer(
+ "Apache(Geronimo)J2EE",
+ HeaderTokenizer.RFC822,
+ ignore);
+ validateToken(ht.next(), Token.ATOM, "Apache");
+ if (!ignore) {
+ validateToken(ht.next(), Token.COMMENT, "Geronimo");
+ }
+ validateToken(ht.next(), Token.ATOM, "J2EE");
+ assertEquals(Token.EOF, ht.next().getType());
+
+ ht =
+ new HeaderTokenizer(
+ "Apache(Geronimo (Project))J2EE",
+ HeaderTokenizer.RFC822,
+ ignore);
+ validateToken(ht.next(), Token.ATOM, "Apache");
+ if (!ignore) {
+ validateToken(ht.next(), Token.COMMENT, "Geronimo (Project)");
+ }
+ validateToken(ht.next(), Token.ATOM, "J2EE");
+ assertEquals(Token.EOF, ht.next().getType());
+ }
+
+ private void validateToken(HeaderTokenizer.Token token, int type, String value) {
+ assertEquals(token.getType(), type);
+ assertEquals(token.getValue(), value);
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/InternetAddressTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/InternetAddressTest.java
new file mode 100644
index 00000000..617c7be8
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/InternetAddressTest.java
@@ -0,0 +1,546 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import junit.framework.TestCase;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Properties;
+
+import javax.mail.Session;
+
+/**
+ * @version $Rev: 669901 $ $Date: 2008-06-20 09:01:53 -0500 (Fri, 20 Jun 2008) $
+ */
+public class InternetAddressTest extends TestCase {
+ private InternetAddress address;
+
+ public void testQuotedLiterals() throws Exception {
+ parseHeaderTest("\"Foo\t\n\\\\\\\"\" ", true, "foo@apache.org", "Foo\t\n\\\"", "\"Foo\t\n\\\\\\\"\" ", false);
+ parseHeaderTest("<\"@,:;<>.[]()\"@apache.org>", true, "\"@,:;<>.[]()\"@apache.org", null, "<\"@,:;<>.[]()\"@apache.org>", false);
+ parseHeaderTest("<\"\\F\\o\\o\"@apache.org>", true, "\"Foo\"@apache.org", null, "<\"Foo\"@apache.org>", false);
+ parseHeaderErrorTest("\"Foo ", true);
+ parseHeaderErrorTest("\"Foo\r\" ", true);
+ }
+
+ public void testDomainLiterals() throws Exception {
+ parseHeaderTest("", true, "foo@[apache].org", null, "", false);
+ parseHeaderTest(".,:;\"\\\\].org>", true, "foo@[@()<>.,:;\"\\\\].org", null, ".,:;\"\\\\].org>", false);
+ parseHeaderTest("", true, "foo@[\\[\\]].org", null, "", false);
+ parseHeaderErrorTest("", true);
+ parseHeaderErrorTest("", true);
+ parseHeaderErrorTest("", true);
+ }
+
+ public void testComments() throws Exception {
+ parseHeaderTest("Foo Bar (Fred) ", true, "foo@apache.org", "Foo Bar (Fred)", "\"Foo Bar (Fred)\" ", false);
+ parseHeaderTest("(Fred) foo@apache.org", true, "foo@apache.org", "Fred", "Fred ", false);
+ parseHeaderTest("(\\(Fred\\)) foo@apache.org", true, "foo@apache.org", "(Fred)", "\"(Fred)\" ", false);
+ parseHeaderTest("(Fred (Jones)) foo@apache.org", true, "foo@apache.org", "Fred (Jones)", "\"Fred (Jones)\" ", false);
+ parseHeaderErrorTest("(Fred foo@apache.org", true);
+ parseHeaderErrorTest("(Fred\r) foo@apache.org", true);
+ }
+
+ public void testParseHeader() throws Exception {
+ parseHeaderTest("<@apache.org,@apache.net:foo@apache.org>", false, "@apache.org,@apache.net:foo@apache.org", null, "<@apache.org,@apache.net:foo@apache.org>", false);
+ parseHeaderTest("<@apache.org:foo@apache.org>", false, "@apache.org:foo@apache.org", null, "<@apache.org:foo@apache.org>", false);
+ parseHeaderTest("Foo Bar:;", false, "Foo Bar:;", null, "Foo Bar:;", true);
+ parseHeaderTest("\"\\\"Foo Bar\" ", false, "foo.bar@apache.org", "\"Foo Bar", "\"\\\"Foo Bar\" ", false);
+ parseHeaderTest("\"Foo Bar\" ", false, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseHeaderTest("(Foo) (Bar) foo.bar@apache.org", false, "foo.bar@apache.org", "Foo", "Foo ", false);
+ parseHeaderTest("", false, "foo@apache.org", null, "foo@apache.org", false);
+ parseHeaderTest("Foo Bar ", false, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseHeaderTest("foo", false, "foo", null, "foo", false);
+ parseHeaderTest("\"foo\"", false, "\"foo\"", null, "<\"foo\">", false);
+ parseHeaderTest("foo@apache.org", false, "foo@apache.org", null, "foo@apache.org", false);
+ parseHeaderTest("\"foo\"@apache.org", false, "\"foo\"@apache.org", null, "<\"foo\"@apache.org>", false);
+ parseHeaderTest("foo@[apache].org", false, "foo@[apache].org", null, "", false);
+ parseHeaderTest("foo@[apache].[org]", false, "foo@[apache].[org]", null, "", false);
+ parseHeaderTest("foo.bar@apache.org", false, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseHeaderTest("(Foo Bar) ", false, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseHeaderTest("(Foo) (Bar) ", false, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseHeaderTest("\"Foo\" Bar ", false, "foo.bar@apache.org", "\"Foo\" Bar", "\"\\\"Foo\\\" Bar\" ", false);
+ parseHeaderTest("(Foo Bar) foo.bar@apache.org", false, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseHeaderTest("apache.org", false, "apache.org", null, "apache.org", false);
+ }
+
+ public void testValidate() throws Exception {
+ validateTest("@apache.org,@apache.net:foo@apache.org");
+ validateTest("@apache.org:foo@apache.org");
+ validateTest("Foo Bar:;");
+ validateTest("foo.bar@apache.org");
+ validateTest("bar@apache.org");
+ validateTest("foo");
+ validateTest("foo.bar");
+ validateTest("\"foo\"");
+ validateTest("\"foo\"@apache.org");
+ validateTest("foo@[apache].org");
+ validateTest("foo@[apache].[org]");
+ }
+
+ public void testStrictParseHeader() throws Exception {
+ parseHeaderTest("<@apache.org,@apache.net:foo@apache.org>", true, "@apache.org,@apache.net:foo@apache.org", null, "<@apache.org,@apache.net:foo@apache.org>", false);
+ parseHeaderTest("<@apache.org:foo@apache.org>", true, "@apache.org:foo@apache.org", null, "<@apache.org:foo@apache.org>", false);
+ parseHeaderTest("Foo Bar:;", true, "Foo Bar:;", null, "Foo Bar:;", true);
+ parseHeaderTest("\"\\\"Foo Bar\" ", true, "foo.bar@apache.org", "\"Foo Bar", "\"\\\"Foo Bar\" ", false);
+ parseHeaderTest("\"Foo Bar\" ", true, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseHeaderTest("(Foo) (Bar) foo.bar@apache.org", true, "foo.bar@apache.org", "Foo", "Foo ", false);
+ parseHeaderTest("", true, "foo@apache.org", null, "foo@apache.org", false);
+ parseHeaderTest("Foo Bar ", true, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseHeaderTest("foo", true, "foo", null, "foo", false);
+ parseHeaderTest("\"foo\"", true, "\"foo\"", null, "<\"foo\">", false);
+ parseHeaderTest("foo@apache.org", true, "foo@apache.org", null, "foo@apache.org", false);
+ parseHeaderTest("\"foo\"@apache.org", true, "\"foo\"@apache.org", null, "<\"foo\"@apache.org>", false);
+ parseHeaderTest("foo@[apache].org", true, "foo@[apache].org", null, "", false);
+ parseHeaderTest("foo@[apache].[org]", true, "foo@[apache].[org]", null, "", false);
+ parseHeaderTest("foo.bar@apache.org", true, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseHeaderTest("(Foo Bar) ", true, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseHeaderTest("(Foo) (Bar) ", true, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseHeaderTest("\"Foo\" Bar ", true, "foo.bar@apache.org", "\"Foo\" Bar", "\"\\\"Foo\\\" Bar\" ", false);
+ parseHeaderTest("(Foo Bar) foo.bar@apache.org", true, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseHeaderTest("apache.org", true, "apache.org", null, "apache.org", false);
+ }
+
+ public void testParse() throws Exception {
+ parseTest("<@apache.org,@apache.net:foo@apache.org>", false, "@apache.org,@apache.net:foo@apache.org", null, "<@apache.org,@apache.net:foo@apache.org>", false);
+ parseTest("<@apache.org:foo@apache.org>", false, "@apache.org:foo@apache.org", null, "<@apache.org:foo@apache.org>", false);
+ parseTest("Foo Bar:;", false, "Foo Bar:;", null, "Foo Bar:;", true);
+ parseTest("\"\\\"Foo Bar\" ", false, "foo.bar@apache.org", "\"Foo Bar", "\"\\\"Foo Bar\" ", false);
+ parseTest("\"Foo Bar\" ", false, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseTest("(Foo) (Bar) foo.bar@apache.org", false, "foo.bar@apache.org", "Foo", "Foo ", false);
+ parseTest("", false, "foo@apache.org", null, "foo@apache.org", false);
+ parseTest("Foo Bar ", false, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseTest("foo", false, "foo", null, "foo", false);
+ parseTest("\"foo\"", false, "\"foo\"", null, "<\"foo\">", false);
+ parseTest("foo@apache.org", false, "foo@apache.org", null, "foo@apache.org", false);
+ parseTest("\"foo\"@apache.org", false, "\"foo\"@apache.org", null, "<\"foo\"@apache.org>", false);
+ parseTest("foo@[apache].org", false, "foo@[apache].org", null, "", false);
+ parseTest("foo@[apache].[org]", false, "foo@[apache].[org]", null, "", false);
+ parseTest("foo.bar@apache.org", false, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseTest("(Foo Bar) ", false, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseTest("(Foo) (Bar) ", false, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseTest("\"Foo\" Bar ", false, "foo.bar@apache.org", "\"Foo\" Bar", "\"\\\"Foo\\\" Bar\" ", false);
+ parseTest("(Foo Bar) foo.bar@apache.org", false, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseTest("apache.org", false, "apache.org", null, "apache.org", false);
+ }
+
+ public void testDefaultParse() throws Exception {
+ parseDefaultTest("<@apache.org,@apache.net:foo@apache.org>", "@apache.org,@apache.net:foo@apache.org", null, "<@apache.org,@apache.net:foo@apache.org>", false);
+ parseDefaultTest("<@apache.org:foo@apache.org>", "@apache.org:foo@apache.org", null, "<@apache.org:foo@apache.org>", false);
+ parseDefaultTest("Foo Bar:;", "Foo Bar:;", null, "Foo Bar:;", true);
+ parseDefaultTest("\"\\\"Foo Bar\" ", "foo.bar@apache.org", "\"Foo Bar", "\"\\\"Foo Bar\" ", false);
+ parseDefaultTest("\"Foo Bar\" ", "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseDefaultTest("(Foo) (Bar) foo.bar@apache.org", "foo.bar@apache.org", "Foo", "Foo ", false);
+ parseDefaultTest("", "foo@apache.org", null, "foo@apache.org", false);
+ parseDefaultTest("Foo Bar ", "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseDefaultTest("foo", "foo", null, "foo", false);
+ parseDefaultTest("\"foo\"", "\"foo\"", null, "<\"foo\">", false);
+ parseDefaultTest("foo@apache.org", "foo@apache.org", null, "foo@apache.org", false);
+ parseDefaultTest("\"foo\"@apache.org", "\"foo\"@apache.org", null, "<\"foo\"@apache.org>", false);
+ parseDefaultTest("foo@[apache].org", "foo@[apache].org", null, "", false);
+ parseDefaultTest("foo@[apache].[org]", "foo@[apache].[org]", null, "", false);
+ parseDefaultTest("foo.bar@apache.org", "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseDefaultTest("(Foo Bar) ", "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseDefaultTest("(Foo) (Bar) ", "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseDefaultTest("\"Foo\" Bar ", "foo.bar@apache.org", "\"Foo\" Bar", "\"\\\"Foo\\\" Bar\" ", false);
+ parseDefaultTest("(Foo Bar) foo.bar@apache.org", "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseDefaultTest("apache.org", "apache.org", null, "apache.org", false);
+ }
+
+ public void testStrictParse() throws Exception {
+ parseTest("<@apache.org,@apache.net:foo@apache.org>", true, "@apache.org,@apache.net:foo@apache.org", null, "<@apache.org,@apache.net:foo@apache.org>", false);
+ parseTest("<@apache.org:foo@apache.org>", true, "@apache.org:foo@apache.org", null, "<@apache.org:foo@apache.org>", false);
+ parseTest("Foo Bar:;", true, "Foo Bar:;", null, "Foo Bar:;", true);
+ parseTest("\"\\\"Foo Bar\" ", true, "foo.bar@apache.org", "\"Foo Bar", "\"\\\"Foo Bar\" ", false);
+ parseTest("\"Foo Bar\" ", true, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseTest("(Foo) (Bar) foo.bar@apache.org", true, "foo.bar@apache.org", "Foo", "Foo ", false);
+ parseTest("", true, "foo@apache.org", null, "foo@apache.org", false);
+ parseTest("Foo Bar ", true, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseTest("foo", true, "foo", null, "foo", false);
+ parseTest("\"foo\"", true, "\"foo\"", null, "<\"foo\">", false);
+ parseTest("foo@apache.org", true, "foo@apache.org", null, "foo@apache.org", false);
+ parseTest("\"foo\"@apache.org", true, "\"foo\"@apache.org", null, "<\"foo\"@apache.org>", false);
+ parseTest("foo@[apache].org", true, "foo@[apache].org", null, "", false);
+ parseTest("foo@[apache].[org]", true, "foo@[apache].[org]", null, "", false);
+ parseTest("foo.bar@apache.org", true, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseTest("(Foo Bar) ", true, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseTest("(Foo) (Bar) ", true, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ parseTest("\"Foo\" Bar ", true, "foo.bar@apache.org", "\"Foo\" Bar", "\"\\\"Foo\\\" Bar\" ", false);
+ parseTest("(Foo Bar) foo.bar@apache.org", true, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ parseTest("apache.org", true, "apache.org", null, "apache.org", false);
+ }
+
+ public void testConstructor() throws Exception {
+ constructorTest("(Foo) (Bar) foo.bar@apache.org", false, "foo.bar@apache.org", "Foo", "Foo ", false);
+ constructorTest("<@apache.org,@apache.net:foo@apache.org>", false, "@apache.org,@apache.net:foo@apache.org", null, "<@apache.org,@apache.net:foo@apache.org>", false);
+ constructorTest("<@apache.org:foo@apache.org>", false, "@apache.org:foo@apache.org", null, "<@apache.org:foo@apache.org>", false);
+ constructorTest("Foo Bar:;", false, "Foo Bar:;", null, "Foo Bar:;", true);
+ constructorTest("\"\\\"Foo Bar\" ", false, "foo.bar@apache.org", "\"Foo Bar", "\"\\\"Foo Bar\" ", false);
+ constructorTest("\"Foo Bar\" ", false, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ constructorTest("", false, "foo@apache.org", null, "foo@apache.org", false);
+ constructorTest("Foo Bar ", false, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ constructorTest("foo", false, "foo", null, "foo", false);
+ constructorTest("\"foo\"", false, "\"foo\"", null, "<\"foo\">", false);
+ constructorTest("foo@apache.org", false, "foo@apache.org", null, "foo@apache.org", false);
+ constructorTest("\"foo\"@apache.org", false, "\"foo\"@apache.org", null, "<\"foo\"@apache.org>", false);
+ constructorTest("foo@[apache].org", false, "foo@[apache].org", null, "", false);
+ constructorTest("foo@[apache].[org]", false, "foo@[apache].[org]", null, "", false);
+ constructorTest("foo.bar@apache.org", false, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ constructorTest("(Foo Bar) ", false, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ constructorTest("(Foo) (Bar) ", false, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ constructorTest("\"Foo\" Bar ", false, "foo.bar@apache.org", "\"Foo\" Bar", "\"\\\"Foo\\\" Bar\" ", false);
+ constructorTest("(Foo Bar) foo.bar@apache.org", false, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ constructorTest("apache.org", false, "apache.org", null, "apache.org", false);
+ }
+
+ public void testDefaultConstructor() throws Exception {
+ constructorDefaultTest("<@apache.org,@apache.net:foo@apache.org>", "@apache.org,@apache.net:foo@apache.org", null, "<@apache.org,@apache.net:foo@apache.org>", false);
+ constructorDefaultTest("<@apache.org:foo@apache.org>", "@apache.org:foo@apache.org", null, "<@apache.org:foo@apache.org>", false);
+ constructorDefaultTest("Foo Bar:;", "Foo Bar:;", null, "Foo Bar:;", true);
+ constructorDefaultTest("\"\\\"Foo Bar\" ", "foo.bar@apache.org", "\"Foo Bar", "\"\\\"Foo Bar\" ", false);
+ constructorDefaultTest("\"Foo Bar\" ", "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ constructorDefaultTest("(Foo) (Bar) foo.bar@apache.org", "foo.bar@apache.org", "Foo", "Foo ", false);
+ constructorDefaultTest("", "foo@apache.org", null, "foo@apache.org", false);
+ constructorDefaultTest("Foo Bar ", "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ constructorDefaultTest("foo", "foo", null, "foo", false);
+ constructorDefaultTest("\"foo\"", "\"foo\"", null, "<\"foo\">", false);
+ constructorDefaultTest("foo@apache.org", "foo@apache.org", null, "foo@apache.org", false);
+ constructorDefaultTest("\"foo\"@apache.org", "\"foo\"@apache.org", null, "<\"foo\"@apache.org>", false);
+ constructorDefaultTest("foo@[apache].org", "foo@[apache].org", null, "", false);
+ constructorDefaultTest("foo@[apache].[org]", "foo@[apache].[org]", null, "", false);
+ constructorDefaultTest("foo.bar@apache.org", "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ constructorDefaultTest("(Foo Bar) ", "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ constructorDefaultTest("(Foo) (Bar) ", "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ constructorDefaultTest("\"Foo\" Bar ", "foo.bar@apache.org", "\"Foo\" Bar", "\"\\\"Foo\\\" Bar\" ", false);
+ constructorDefaultTest("(Foo Bar) foo.bar@apache.org", "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ constructorDefaultTest("apache.org", "apache.org", null, "apache.org", false);
+ }
+
+ public void testStrictConstructor() throws Exception {
+ constructorTest("<@apache.org,@apache.net:foo@apache.org>", true, "@apache.org,@apache.net:foo@apache.org", null, "<@apache.org,@apache.net:foo@apache.org>", false);
+ constructorTest("<@apache.org:foo@apache.org>", true, "@apache.org:foo@apache.org", null, "<@apache.org:foo@apache.org>", false);
+ constructorTest("Foo Bar:;", true, "Foo Bar:;", null, "Foo Bar:;", true);
+ constructorTest("\"\\\"Foo Bar\" ", true, "foo.bar@apache.org", "\"Foo Bar", "\"\\\"Foo Bar\" ", false);
+ constructorTest("\"Foo Bar\" ", true, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ constructorTest("(Foo) (Bar) foo.bar@apache.org", true, "foo.bar@apache.org", "Foo", "Foo ", false);
+ constructorTest("", true, "foo@apache.org", null, "foo@apache.org", false);
+ constructorTest("Foo Bar ", true, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ constructorTest("foo", true, "foo", null, "foo", false);
+ constructorTest("\"foo\"", true, "\"foo\"", null, "<\"foo\">", false);
+ constructorTest("foo@apache.org", true, "foo@apache.org", null, "foo@apache.org", false);
+ constructorTest("\"foo\"@apache.org", true, "\"foo\"@apache.org", null, "<\"foo\"@apache.org>", false);
+ constructorTest("foo@[apache].org", true, "foo@[apache].org", null, "", false);
+ constructorTest("foo@[apache].[org]", true, "foo@[apache].[org]", null, "", false);
+ constructorTest("foo.bar@apache.org", true, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ constructorTest("(Foo Bar) ", true, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ constructorTest("(Foo) (Bar) ", true, "foo.bar@apache.org", null, "foo.bar@apache.org", false);
+ constructorTest("\"Foo\" Bar ", true, "foo.bar@apache.org", "\"Foo\" Bar", "\"\\\"Foo\\\" Bar\" ", false);
+ constructorTest("(Foo Bar) foo.bar@apache.org", true, "foo.bar@apache.org", "Foo Bar", "Foo Bar ", false);
+ constructorTest("apache.org", true, "apache.org", null, "apache.org", false);
+ }
+
+ public void testParseHeaderList() throws Exception {
+
+ InternetAddress[] addresses = InternetAddress.parseHeader("foo@apache.org,bar@apache.org", true);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", null, "foo@apache.org", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+ addresses = InternetAddress.parseHeader("Foo ,,Bar ", true);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", "Foo", "Foo ", false);
+ validateAddress(addresses[1], "bar@apache.org", "Bar", "Bar ", false);
+
+ addresses = InternetAddress.parseHeader("foo@apache.org, bar@apache.org", true);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", null, "foo@apache.org", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+ addresses = InternetAddress.parseHeader("Foo , Bar ", true);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", "Foo", "Foo ", false);
+ validateAddress(addresses[1], "bar@apache.org", "Bar", "Bar ", false);
+
+
+ addresses = InternetAddress.parseHeader("Foo ,(yada),Bar ", true);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", "Foo", "Foo ", false);
+ validateAddress(addresses[1], "bar@apache.org", "Bar", "Bar ", false);
+ }
+
+ public void testParseHeaderErrors() throws Exception {
+ parseHeaderErrorTest("foo@apache.org bar@apache.org", true);
+ parseHeaderErrorTest("Foo foo@apache.org", true);
+ parseHeaderErrorTest("Foo foo@apache.org", true);
+ parseHeaderErrorTest("Foo ,bar@apache.org;", true, "Foo Bar:,bar@apache.org;", null, "Foo Bar:,bar@apache.org;", true);
+ parseHeaderTest("Foo Bar:Foo ,bar@apache.org;", true, "Foo Bar:Foo,bar@apache.org;", null, "Foo Bar:Foo,bar@apache.org;", true);
+ parseHeaderTest("Foo:,,bar@apache.org;", true, "Foo:,,bar@apache.org;", null, "Foo:,,bar@apache.org;", true);
+ parseHeaderTest("Foo:foo,bar;", true, "Foo:foo,bar;", null, "Foo:foo,bar;", true);
+ parseHeaderTest("Foo:;", true, "Foo:;", null, "Foo:;", true);
+ parseHeaderTest("\"Foo\":foo@apache.org;", true, "\"Foo\":foo@apache.org;", null, "\"Foo\":foo@apache.org;", true);
+
+ parseHeaderErrorTest("Foo:foo@apache.org,bar@apache.org", true);
+ parseHeaderErrorTest("Foo:foo@apache.org,Bar:bar@apache.org;;", true);
+ parseHeaderErrorTest(":foo@apache.org;", true);
+ parseHeaderErrorTest("Foo Bar:,bar@apache.org;", true);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", null, "foo@apache.org", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+ addresses = getGroup("Foo:,,bar@apache.org;", true);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", null, "foo@apache.org", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+ addresses = getGroup("Foo:Foo ,bar@apache.org;", true);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", "Foo", "Foo ", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+ addresses = getGroup("Foo:Foo <@apache.org:foo@apache.org>,bar@apache.org;", true);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "@apache.org:foo@apache.org", "Foo", "Foo <@apache.org:foo@apache.org>", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+
+ addresses = getGroup("Foo:;", true);
+ assertTrue("Expecting 0 addresses", addresses.length == 0);
+
+ addresses = getGroup("Foo:foo@apache.org;", false);
+ assertTrue("Expecting 1 address", addresses.length == 1);
+ validateAddress(addresses[0], "foo@apache.org", null, "foo@apache.org", false);
+
+ addresses = getGroup("Foo:foo@apache.org,bar@apache.org;", false);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", null, "foo@apache.org", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+ addresses = getGroup("Foo:,bar@apache.org;", false);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", null, "foo@apache.org", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+ addresses = getGroup("Foo:,,bar@apache.org;", false);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", null, "foo@apache.org", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+ addresses = getGroup("Foo:Foo ,bar@apache.org;", false);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "foo@apache.org", "Foo", "Foo ", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+ addresses = getGroup("Foo:Foo <@apache.org:foo@apache.org>,bar@apache.org;", false);
+ assertTrue("Expecting 2 addresses", addresses.length == 2);
+ validateAddress(addresses[0], "@apache.org:foo@apache.org", "Foo", "Foo <@apache.org:foo@apache.org>", false);
+ validateAddress(addresses[1], "bar@apache.org", null, "bar@apache.org", false);
+
+
+ addresses = getGroup("Foo:;", false);
+ assertTrue("Expecting 0 addresses", addresses.length == 0);
+ }
+
+
+ public void testLocalAddress() throws Exception {
+ System.getProperties().remove("user.name");
+
+ assertNull(InternetAddress.getLocalAddress(null));
+ System.setProperty("user.name", "dev");
+
+ InternetAddress localHost = null;
+ String user = null;
+ String host = "localhost";
+ try {
+ user = System.getProperty("user.name");
+ localHost = new InternetAddress(user + "@" + host);
+ } catch (SecurityException e) {
+ // ignore
+ }
+
+ assertEquals(InternetAddress.getLocalAddress(null), localHost);
+
+ Properties props = new Properties();
+ Session session = Session.getInstance(props, null);
+
+ assertEquals(InternetAddress.getLocalAddress(session), localHost);
+
+ props.put("mail.host", "apache.org");
+ session = Session.getInstance(props, null);
+
+ assertEquals(InternetAddress.getLocalAddress(session), new InternetAddress(user + "@apache.org"));
+
+ props.put("mail.user", "user");
+ props.remove("mail.host");
+
+ session = Session.getInstance(props, null);
+ assertEquals(InternetAddress.getLocalAddress(session), new InternetAddress("user@" + host));
+
+ props.put("mail.host", "apache.org");
+ session = Session.getInstance(props, null);
+
+ assertEquals(InternetAddress.getLocalAddress(session), new InternetAddress("user@apache.org"));
+
+ props.put("mail.from", "tester@incubator.apache.org");
+ session = Session.getInstance(props, null);
+
+ assertEquals(InternetAddress.getLocalAddress(session), new InternetAddress("tester@incubator.apache.org"));
+ }
+
+ private InternetAddress[] getGroup(String address, boolean strict) throws AddressException
+ {
+ InternetAddress group = new InternetAddress(address);
+ return group.getGroup(strict);
+ }
+
+
+ protected void setUp() throws Exception {
+ address = new InternetAddress();
+ }
+
+ private void parseHeaderTest(String address, boolean strict, String resultAddr, String personal, String toString, boolean group) throws Exception
+ {
+ InternetAddress[] addresses = InternetAddress.parseHeader(address, strict);
+ assertTrue(addresses.length == 1);
+ validateAddress(addresses[0], resultAddr, personal, toString, group);
+ }
+
+ private void parseHeaderErrorTest(String address, boolean strict) throws Exception
+ {
+ try {
+ InternetAddress.parseHeader(address, strict);
+ fail("Expected AddressException");
+ } catch (AddressException e) {
+ }
+ }
+
+ private void constructorTest(String address, boolean strict, String resultAddr, String personal, String toString, boolean group) throws Exception
+ {
+ validateAddress(new InternetAddress(address, strict), resultAddr, personal, toString, group);
+ }
+
+ private void constructorDefaultTest(String address, String resultAddr, String personal, String toString, boolean group) throws Exception
+ {
+ validateAddress(new InternetAddress(address), resultAddr, personal, toString, group);
+ }
+
+ private void constructorErrorTest(String address, boolean strict) throws Exception
+ {
+ try {
+ InternetAddress foo = new InternetAddress(address, strict);
+ fail("Expected AddressException");
+ } catch (AddressException e) {
+ }
+ }
+
+ private void parseTest(String address, boolean strict, String resultAddr, String personal, String toString, boolean group) throws Exception
+ {
+ InternetAddress[] addresses = InternetAddress.parse(address, strict);
+ assertTrue(addresses.length == 1);
+ validateAddress(addresses[0], resultAddr, personal, toString, group);
+ }
+
+ private void parseErrorTest(String address, boolean strict) throws Exception
+ {
+ try {
+ InternetAddress.parse(address, strict);
+ fail("Expected AddressException");
+ } catch (AddressException e) {
+ }
+ }
+
+ private void parseDefaultTest(String address, String resultAddr, String personal, String toString, boolean group) throws Exception
+ {
+ InternetAddress[] addresses = InternetAddress.parse(address);
+ assertTrue(addresses.length == 1);
+ validateAddress(addresses[0], resultAddr, personal, toString, group);
+ }
+
+ private void parseDefaultErrorTest(String address) throws Exception
+ {
+ try {
+ InternetAddress.parse(address);
+ fail("Expected AddressException");
+ } catch (AddressException e) {
+ }
+ }
+
+ private void validateTest(String address) throws Exception {
+ InternetAddress test = new InternetAddress();
+ test.setAddress(address);
+ test.validate();
+ }
+
+ private void validateErrorTest(String address) throws Exception {
+ InternetAddress test = new InternetAddress();
+ test.setAddress(address);
+ try {
+ test.validate();
+ fail("Expected AddressException");
+ } catch (AddressException e) {
+ }
+ }
+
+
+ private void validateAddress(InternetAddress a, String address, String personal, String toString, boolean group)
+ {
+ assertEquals("Invalid address:", a.getAddress(), address);
+ if (personal == null) {
+ assertNull("Personal must be null", a.getPersonal());
+ }
+ else {
+ assertEquals("Invalid Personal:", a.getPersonal(), personal);
+ }
+ assertEquals("Invalid string value:", a.toString(), toString);
+ assertTrue("Incorrect group value:", group == a.isGroup());
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/InternetHeadersTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/InternetHeadersTest.java
new file mode 100644
index 00000000..38a7802b
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/InternetHeadersTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.ByteArrayInputStream;
+
+import javax.mail.MessagingException;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class InternetHeadersTest extends TestCase {
+ private InternetHeaders headers;
+
+ public void testLoadSingleHeader() throws MessagingException {
+ String stream = "content-type: text/plain\r\n\r\n";
+ headers.load(new ByteArrayInputStream(stream.getBytes()));
+ String[] header = headers.getHeader("content-type");
+ assertNotNull(header);
+ assertEquals("text/plain", header[0]);
+ }
+
+ protected void setUp() throws Exception {
+ headers = new InternetHeaders();
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/MailDateFormatTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/MailDateFormatTest.java
new file mode 100644
index 00000000..1824bf17
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/MailDateFormatTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.SimpleTimeZone;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 628009 $ $Date: 2008-02-15 04:53:02 -0600 (Fri, 15 Feb 2008) $
+ */
+public class MailDateFormatTest extends TestCase {
+ public void testMailDateFormat() throws ParseException {
+ MailDateFormat mdf = new MailDateFormat();
+ Date date = mdf.parse("Wed, 27 Aug 2003 13:43:38 +0100 (BST)");
+ // don't we just love the Date class?
+ Calendar cal = Calendar.getInstance(new SimpleTimeZone(+1 * 60 * 60 * 1000, "BST"), Locale.getDefault());
+ cal.setTime(date);
+ assertEquals(2003, cal.get(Calendar.YEAR));
+ assertEquals(Calendar.AUGUST, cal.get(Calendar.MONTH));
+ assertEquals(27, cal.get(Calendar.DAY_OF_MONTH));
+ assertEquals(Calendar.WEDNESDAY, cal.get(Calendar.DAY_OF_WEEK));
+ assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
+ assertEquals(43, cal.get(Calendar.MINUTE));
+ assertEquals(38, cal.get(Calendar.SECOND));
+
+ date = mdf.parse("Wed, 27-Aug-2003 13:43:38 +0100");
+ // don't we just love the Date class?
+ cal = Calendar.getInstance(new SimpleTimeZone(+1 * 60 * 60 * 1000, "BST"), Locale.getDefault());
+ cal.setTime(date);
+ assertEquals(2003, cal.get(Calendar.YEAR));
+ assertEquals(Calendar.AUGUST, cal.get(Calendar.MONTH));
+ assertEquals(27, cal.get(Calendar.DAY_OF_MONTH));
+ assertEquals(Calendar.WEDNESDAY, cal.get(Calendar.DAY_OF_WEEK));
+ assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
+ assertEquals(43, cal.get(Calendar.MINUTE));
+ assertEquals(38, cal.get(Calendar.SECOND));
+
+ date = mdf.parse("27-Aug-2003 13:43:38 EST");
+ // don't we just love the Date class?
+ cal = Calendar.getInstance(new SimpleTimeZone(-5 * 60 * 60 * 1000, "EST"), Locale.getDefault());
+ cal.setTime(date);
+ assertEquals(2003, cal.get(Calendar.YEAR));
+ assertEquals(Calendar.AUGUST, cal.get(Calendar.MONTH));
+ assertEquals(27, cal.get(Calendar.DAY_OF_MONTH));
+ assertEquals(Calendar.WEDNESDAY, cal.get(Calendar.DAY_OF_WEEK));
+ assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
+ assertEquals(43, cal.get(Calendar.MINUTE));
+ assertEquals(38, cal.get(Calendar.SECOND));
+
+ date = mdf.parse("27 Aug 2003 13:43 EST");
+ // don't we just love the Date class?
+ cal = Calendar.getInstance(new SimpleTimeZone(-5 * 60 * 60 * 1000, "EST"), Locale.getDefault());
+ cal.setTime(date);
+ assertEquals(2003, cal.get(Calendar.YEAR));
+ assertEquals(Calendar.AUGUST, cal.get(Calendar.MONTH));
+ assertEquals(27, cal.get(Calendar.DAY_OF_MONTH));
+ assertEquals(Calendar.WEDNESDAY, cal.get(Calendar.DAY_OF_WEEK));
+ assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
+ assertEquals(43, cal.get(Calendar.MINUTE));
+ assertEquals(00, cal.get(Calendar.SECOND));
+
+ date = mdf.parse("27 Aug 03 13:43 EST");
+ // don't we just love the Date class?
+ cal = Calendar.getInstance(new SimpleTimeZone(-5 * 60 * 60 * 1000, "EST"), Locale.getDefault());
+ cal.setTime(date);
+ assertEquals(2003, cal.get(Calendar.YEAR));
+ assertEquals(Calendar.AUGUST, cal.get(Calendar.MONTH));
+ assertEquals(27, cal.get(Calendar.DAY_OF_MONTH));
+ assertEquals(Calendar.WEDNESDAY, cal.get(Calendar.DAY_OF_WEEK));
+ assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
+ assertEquals(43, cal.get(Calendar.MINUTE));
+ assertEquals(00, cal.get(Calendar.SECOND));
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeBodyPartTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeBodyPartTest.java
new file mode 100644
index 00000000..b46d2375
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeBodyPartTest.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.UnsupportedEncodingException;
+import javax.mail.MessagingException;
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class MimeBodyPartTest extends TestCase {
+
+ File basedir = new File(System.getProperty("basedir", "."));
+ File testInput = new File(basedir, "src/test/resources/test.dat");
+
+ public void testGetSize() throws MessagingException {
+ MimeBodyPart part = new MimeBodyPart();
+ assertEquals(-1, part.getSize());
+
+ part = new MimeBodyPart(new InternetHeaders(), new byte[] {'a', 'b', 'c'});
+ assertEquals(3, part.getSize());
+ }
+
+ public void testGetLineCount() throws MessagingException {
+ MimeBodyPart part = new MimeBodyPart();
+ assertEquals(-1, part.getLineCount());
+
+ part = new MimeBodyPart(new InternetHeaders(), new byte[] {'a', 'b', 'c'});
+ assertEquals(-1, part.getLineCount());
+ }
+
+
+ public void testGetContentType() throws MessagingException {
+ MimeBodyPart part = new MimeBodyPart();
+ assertEquals("text/plain", part.getContentType());
+
+ part.setHeader("Content-Type", "text/xml");
+ assertEquals("text/xml", part.getContentType());
+
+ part = new MimeBodyPart();
+ part.setText("abc");
+ assertEquals("text/plain", part.getContentType());
+ }
+
+
+ public void testIsMimeType() throws MessagingException {
+ MimeBodyPart part = new MimeBodyPart();
+ assertTrue(part.isMimeType("text/plain"));
+ assertTrue(part.isMimeType("text/*"));
+
+ part.setHeader("Content-Type", "text/xml");
+ assertTrue(part.isMimeType("text/xml"));
+ assertTrue(part.isMimeType("text/*"));
+ }
+
+
+ public void testGetDisposition() throws MessagingException {
+ MimeBodyPart part = new MimeBodyPart();
+ assertNull(part.getDisposition());
+
+ part.setDisposition("inline");
+ assertEquals("inline", part.getDisposition());
+ }
+
+
+ public void testSetDescription() throws MessagingException, UnsupportedEncodingException {
+ MimeBodyPart part = new MimeBodyPart();
+
+ String simpleSubject = "Yada, yada";
+
+ String complexSubject = "Yada, yada\u0081";
+
+ String mungedSubject = "Yada, yada\u003F";
+
+ part.setDescription(simpleSubject);
+ assertEquals(part.getDescription(), simpleSubject);
+
+ part.setDescription(complexSubject, "UTF-8");
+ assertEquals(part.getDescription(), complexSubject);
+ assertEquals(part.getHeader("Content-Description", null), MimeUtility.encodeText(complexSubject, "UTF-8", null));
+
+ part.setDescription(null);
+ assertNull(part.getDescription());
+ }
+
+ public void testSetFileName() throws Exception {
+ MimeBodyPart part = new MimeBodyPart();
+ part.setFileName("test.dat");
+
+ assertEquals("test.dat", part.getFileName());
+
+ ContentDisposition disp = new ContentDisposition(part.getHeader("Content-Disposition", null));
+ assertEquals("test.dat", disp.getParameter("filename"));
+
+ ContentType type = new ContentType(part.getHeader("Content-Type", null));
+ assertEquals("test.dat", type.getParameter("name"));
+
+ MimeBodyPart part2 = new MimeBodyPart();
+
+ part2.setHeader("Content-Type", type.toString());
+
+ assertEquals("test.dat", part2.getFileName());
+ part2.setHeader("Content-Type", null);
+ part2.setHeader("Content-Disposition", disp.toString());
+ assertEquals("test.dat", part2.getFileName());
+ }
+
+
+ public void testAttachments() throws Exception {
+ MimeBodyPart part = new MimeBodyPart();
+
+ byte[] testData = getFileData(testInput);
+
+ part.attachFile(testInput);
+ assertEquals(part.getFileName(), testInput.getName());
+
+ part.updateHeaders();
+
+ File temp1 = File.createTempFile("MIME", ".dat");
+ temp1.deleteOnExit();
+
+ part.saveFile(temp1);
+
+ byte[] tempData = getFileData(temp1);
+
+ compareFileData(testData, tempData);
+
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ part.writeTo(out);
+
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+
+ MimeBodyPart part2 = new MimeBodyPart(in);
+
+ temp1 = File.createTempFile("MIME", ".dat");
+ temp1.deleteOnExit();
+
+ part2.saveFile(temp1);
+
+ tempData = getFileData(temp1);
+
+ compareFileData(testData, tempData);
+
+
+ part = new MimeBodyPart();
+
+ part.attachFile(testInput.getPath());
+ assertEquals(part.getFileName(), testInput.getName());
+
+ part.updateHeaders();
+
+ temp1 = File.createTempFile("MIME", ".dat");
+ temp1.deleteOnExit();
+
+ part.saveFile(temp1.getPath());
+
+ tempData = getFileData(temp1);
+
+ compareFileData(testData, tempData);
+
+ out = new ByteArrayOutputStream();
+ part.writeTo(out);
+
+ in = new ByteArrayInputStream(out.toByteArray());
+
+ part2 = new MimeBodyPart(in);
+
+ temp1 = File.createTempFile("MIME", ".dat");
+ temp1.deleteOnExit();
+
+ part2.saveFile(temp1.getPath());
+
+ tempData = getFileData(temp1);
+
+ compareFileData(testData, tempData);
+ }
+
+ private byte[] getFileData(File source) throws Exception {
+ FileInputStream testIn = new FileInputStream(source);
+
+ byte[] testData = new byte[(int)source.length()];
+
+ testIn.read(testData);
+ testIn.close();
+ return testData;
+ }
+
+ private void compareFileData(byte[] file1, byte [] file2) {
+ assertEquals(file1.length, file2.length);
+ for (int i = 0; i < file1.length; i++) {
+ assertEquals(file1[i], file2[i]);
+ }
+ }
+
+
+
+ class TestMimeBodyPart extends MimeBodyPart {
+ public TestMimeBodyPart() {
+ super();
+ }
+
+
+ public void updateHeaders() throws MessagingException {
+ super.updateHeaders();
+ }
+ }
+}
+
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeMessageTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeMessageTest.java
new file mode 100644
index 00000000..65617d4c
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeMessageTest.java
@@ -0,0 +1,417 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import javax.activation.CommandMap;
+import javax.activation.MailcapCommandMap;
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 627556 $ $Date: 2008-02-13 12:27:22 -0600 (Wed, 13 Feb 2008) $
+ */
+public class MimeMessageTest extends TestCase {
+ private CommandMap defaultMap;
+ private Session session;
+
+ public void testWriteTo() throws MessagingException, IOException {
+ MimeMessage msg = new MimeMessage(session);
+ msg.setSender(new InternetAddress("foo"));
+ msg.setHeader("foo", "bar");
+ MimeMultipart mp = new MimeMultipart();
+ MimeBodyPart part1 = new MimeBodyPart();
+ part1.setHeader("foo", "bar");
+ part1.setContent("Hello World", "text/plain");
+ mp.addBodyPart(part1);
+ MimeBodyPart part2 = new MimeBodyPart();
+ part2.setContent("Hello Again", "text/plain");
+ mp.addBodyPart(part2);
+ msg.setContent(mp);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ msg.writeTo(out);
+
+ InputStream in = new ByteArrayInputStream(out.toByteArray());
+
+ MimeMessage newMessage = new MimeMessage(session, in);
+
+ assertEquals("foo", ((InternetAddress) newMessage.getSender()).getAddress());
+
+ String[] headers = newMessage.getHeader("foo");
+ assertTrue(headers.length == 1);
+ assertEquals("bar", headers[0]);
+
+ newMessage = new MimeMessage(msg);
+
+ assertEquals("foo", ((InternetAddress) newMessage.getSender()).getAddress());
+ assertEquals("bar", newMessage.getHeader("foo")[0]);
+ }
+
+
+ public void testFrom() throws MessagingException {
+ MimeMessage msg = new MimeMessage(session);
+
+ InternetAddress dev = new InternetAddress("geronimo-dev@apache.org");
+ InternetAddress user = new InternetAddress("geronimo-user@apache.org");
+
+ msg.setSender(dev);
+
+ Address[] from = msg.getFrom();
+ assertTrue(from.length == 1);
+ assertEquals(from[0], dev);
+
+ msg.setFrom(user);
+ from = msg.getFrom();
+ assertTrue(from.length == 1);
+ assertEquals(from[0], user);
+
+ msg.addFrom(new Address[] { dev });
+ from = msg.getFrom();
+ assertTrue(from.length == 2);
+ assertEquals(from[0], user);
+ assertEquals(from[1], dev);
+
+ msg.setFrom();
+ InternetAddress local = InternetAddress.getLocalAddress(session);
+ from = msg.getFrom();
+
+ assertTrue(from.length == 1);
+ assertEquals(local, from[0]);
+
+ msg.setFrom(null);
+ from = msg.getFrom();
+
+ assertTrue(from.length == 1);
+ assertEquals(dev, from[0]);
+
+ msg.setSender(null);
+ from = msg.getFrom();
+ assertNull(from);
+ }
+
+
+ public void testSender() throws MessagingException {
+ MimeMessage msg = new MimeMessage(session);
+
+ InternetAddress dev = new InternetAddress("geronimo-dev@apache.org");
+ InternetAddress user = new InternetAddress("geronimo-user@apache.org");
+
+ msg.setSender(dev);
+
+ Address[] from = msg.getFrom();
+ assertTrue(from.length == 1);
+ assertEquals(from[0], dev);
+
+ assertEquals(msg.getSender(), dev);
+
+ msg.setSender(null);
+ assertNull(msg.getSender());
+ }
+
+ public void testGetAllRecipients() throws MessagingException {
+ MimeMessage msg = new MimeMessage(session);
+
+ InternetAddress dev = new InternetAddress("geronimo-dev@apache.org");
+ InternetAddress user = new InternetAddress("geronimo-user@apache.org");
+ InternetAddress user1 = new InternetAddress("geronimo-user1@apache.org");
+ InternetAddress user2 = new InternetAddress("geronimo-user2@apache.org");
+ NewsAddress group = new NewsAddress("comp.lang.rexx");
+
+ Address[] recipients = msg.getAllRecipients();
+ assertNull(recipients);
+
+ msg.setRecipients(Message.RecipientType.TO, new Address[] { dev });
+
+ recipients = msg.getAllRecipients();
+ assertTrue(recipients.length == 1);
+ assertEquals(recipients[0], dev);
+
+ msg.addRecipients(Message.RecipientType.BCC, new Address[] { user });
+
+ recipients = msg.getAllRecipients();
+ assertTrue(recipients.length == 2);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user);
+
+ msg.addRecipients(Message.RecipientType.CC, new Address[] { user1, user2} );
+
+ recipients = msg.getAllRecipients();
+ assertTrue(recipients.length == 4);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user1);
+ assertEquals(recipients[2], user2);
+ assertEquals(recipients[3], user);
+
+
+ msg.addRecipients(MimeMessage.RecipientType.NEWSGROUPS, new Address[] { group } );
+
+ recipients = msg.getAllRecipients();
+ assertTrue(recipients.length == 5);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user1);
+ assertEquals(recipients[2], user2);
+ assertEquals(recipients[3], user);
+ assertEquals(recipients[4], group);
+
+ msg.setRecipients(Message.RecipientType.CC, (String)null);
+
+ recipients = msg.getAllRecipients();
+
+ assertTrue(recipients.length == 3);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user);
+ assertEquals(recipients[2], group);
+ }
+
+ public void testGetRecipients() throws MessagingException {
+ doRecipientTest(Message.RecipientType.TO);
+ doRecipientTest(Message.RecipientType.CC);
+ doRecipientTest(Message.RecipientType.BCC);
+ doNewsgroupRecipientTest(MimeMessage.RecipientType.NEWSGROUPS);
+ }
+
+ private void doRecipientTest(Message.RecipientType type) throws MessagingException {
+ MimeMessage msg = new MimeMessage(session);
+
+ InternetAddress dev = new InternetAddress("geronimo-dev@apache.org");
+ InternetAddress user = new InternetAddress("geronimo-user@apache.org");
+
+ Address[] recipients = msg.getRecipients(type);
+ assertNull(recipients);
+
+ msg.setRecipients(type, "geronimo-dev@apache.org");
+ recipients = msg.getRecipients(type);
+ assertTrue(recipients.length == 1);
+ assertEquals(recipients[0], dev);
+
+ msg.addRecipients(type, "geronimo-user@apache.org");
+
+ recipients = msg.getRecipients(type);
+ assertTrue(recipients.length == 2);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user);
+
+ msg.setRecipients(type, (String)null);
+
+ recipients = msg.getRecipients(type);
+ assertNull(recipients);
+
+ msg.setRecipients(type, new Address[] { dev });
+ recipients = msg.getRecipients(type);
+ assertTrue(recipients.length == 1);
+ assertEquals(recipients[0], dev);
+
+ msg.addRecipients(type, new Address[] { user });
+
+ recipients = msg.getRecipients(type);
+ assertTrue(recipients.length == 2);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user);
+
+ msg.setRecipients(type, (Address[])null);
+
+ recipients = msg.getRecipients(type);
+ assertNull(recipients);
+
+ msg.setRecipients(type, new Address[] { dev, user });
+
+ recipients = msg.getRecipients(type);
+ assertTrue(recipients.length == 2);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user);
+ }
+
+
+ private void doNewsgroupRecipientTest(Message.RecipientType type) throws MessagingException {
+ MimeMessage msg = new MimeMessage(session);
+
+ Address dev = new NewsAddress("geronimo-dev");
+ Address user = new NewsAddress("geronimo-user");
+
+ Address[] recipients = msg.getRecipients(type);
+ assertNull(recipients);
+
+ msg.setRecipients(type, "geronimo-dev");
+ recipients = msg.getRecipients(type);
+ assertTrue(recipients.length == 1);
+ assertEquals(recipients[0], dev);
+
+ msg.addRecipients(type, "geronimo-user");
+
+ recipients = msg.getRecipients(type);
+ assertTrue(recipients.length == 2);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user);
+
+ msg.setRecipients(type, (String)null);
+
+ recipients = msg.getRecipients(type);
+ assertNull(recipients);
+
+ msg.setRecipients(type, new Address[] { dev });
+ recipients = msg.getRecipients(type);
+ assertTrue(recipients.length == 1);
+ assertEquals(recipients[0], dev);
+
+ msg.addRecipients(type, new Address[] { user });
+
+ recipients = msg.getRecipients(type);
+ assertTrue(recipients.length == 2);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user);
+
+ msg.setRecipients(type, (Address[])null);
+
+ recipients = msg.getRecipients(type);
+ assertNull(recipients);
+
+ msg.setRecipients(type, new Address[] { dev, user });
+
+ recipients = msg.getRecipients(type);
+ assertTrue(recipients.length == 2);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user);
+ }
+
+ public void testReplyTo() throws MessagingException {
+ MimeMessage msg = new MimeMessage(session);
+
+ InternetAddress dev = new InternetAddress("geronimo-dev@apache.org");
+ InternetAddress user = new InternetAddress("geronimo-user@apache.org");
+
+ msg.setReplyTo(new Address[] { dev });
+
+ Address[] recipients = msg.getReplyTo();
+ assertTrue(recipients.length == 1);
+ assertEquals(recipients[0], dev);
+
+ msg.setReplyTo(new Address[] { dev, user });
+
+ recipients = msg.getReplyTo();
+ assertTrue(recipients.length == 2);
+ assertEquals(recipients[0], dev);
+ assertEquals(recipients[1], user);
+
+ msg.setReplyTo(null);
+
+ recipients = msg.getReplyTo();
+ assertNull(recipients);
+ }
+
+
+ public void testSetSubject() throws MessagingException {
+ MimeMessage msg = new MimeMessage(session);
+
+ String simpleSubject = "Yada, yada";
+
+ String complexSubject = "Yada, yada\u0081";
+
+ String mungedSubject = "Yada, yada\u003F";
+
+ msg.setSubject(simpleSubject);
+ assertEquals(msg.getSubject(), simpleSubject);
+
+ msg.setSubject(complexSubject, "UTF-8");
+ assertEquals(msg.getSubject(), complexSubject);
+
+ msg.setSubject(null);
+ assertNull(msg.getSubject());
+ }
+
+
+ public void testSetDescription() throws MessagingException {
+ MimeMessage msg = new MimeMessage(session);
+
+ String simpleSubject = "Yada, yada";
+
+ String complexSubject = "Yada, yada\u0081";
+
+ String mungedSubject = "Yada, yada\u003F";
+
+ msg.setDescription(simpleSubject);
+ assertEquals(msg.getDescription(), simpleSubject);
+
+ msg.setDescription(complexSubject, "UTF-8");
+ assertEquals(msg.getDescription(), complexSubject);
+
+ msg.setDescription(null);
+ assertNull(msg.getDescription());
+ }
+
+
+ public void testGetContentType() throws MessagingException {
+ MimeMessage msg = new MimeMessage(session);
+ assertEquals("text/plain", msg.getContentType());
+
+ msg.setHeader("Content-Type", "text/xml");
+ assertEquals("text/xml", msg.getContentType());
+ }
+
+
+ public void testSetText() throws MessagingException {
+ MimeMessage msg = new MimeMessage(session);
+
+ msg.setText("Yada, yada");
+ msg.saveChanges();
+ ContentType type = new ContentType(msg.getContentType());
+ assertTrue(type.match("text/plain"));
+
+ msg = new MimeMessage(session);
+ msg.setText("Yada, yada", "UTF-8");
+ msg.saveChanges();
+ type = new ContentType(msg.getContentType());
+ assertTrue(type.match("text/plain"));
+ assertEquals("UTF-8", type.getParameter("charset"));
+
+ msg = new MimeMessage(session);
+ msg.setText("Yada, yada", "UTF-8", "xml");
+ msg.saveChanges();
+ type = new ContentType(msg.getContentType());
+ assertTrue(type.match("text/xml"));
+ assertEquals("UTF-8", type.getParameter("charset"));
+ }
+
+
+
+ protected void setUp() throws Exception {
+ defaultMap = CommandMap.getDefaultCommandMap();
+ MailcapCommandMap myMap = new MailcapCommandMap();
+ myMap.addMailcap("text/plain;; x-java-content-handler=" + MimeMultipartTest.DummyTextHandler.class.getName());
+ myMap.addMailcap("multipart/*;; x-java-content-handler=" + MimeMultipartTest.DummyMultipartHandler.class.getName());
+ CommandMap.setDefaultCommandMap(myMap);
+ Properties props = new Properties();
+ props.put("mail.user", "tester");
+ props.put("mail.host", "apache.org");
+
+ session = Session.getInstance(props);
+ }
+
+ protected void tearDown() throws Exception {
+ CommandMap.setDefaultCommandMap(defaultMap);
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeMultipartTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeMultipartTest.java
new file mode 100644
index 00000000..66921572
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeMultipartTest.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Properties;
+import javax.activation.CommandMap;
+import javax.activation.DataContentHandler;
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.activation.MailcapCommandMap;
+import javax.mail.BodyPart;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 646017 $ $Date: 2008-04-08 13:01:42 -0500 (Tue, 08 Apr 2008) $
+ */
+public class MimeMultipartTest extends TestCase {
+ private CommandMap defaultMap;
+
+ public void testWriteTo() throws MessagingException, IOException, Exception {
+ writeToSetUp();
+
+ MimeMultipart mp = new MimeMultipart();
+ MimeBodyPart part1 = new MimeBodyPart();
+ part1.setHeader("foo", "bar");
+ part1.setContent("Hello World", "text/plain");
+ mp.addBodyPart(part1);
+ MimeBodyPart part2 = new MimeBodyPart();
+ part2.setContent("Hello Again", "text/plain");
+ mp.addBodyPart(part2);
+ mp.writeTo(System.out);
+
+ writeToTearDown();
+ }
+
+ public void testPreamble() throws MessagingException, IOException {
+ Properties props = new Properties();
+ Session session = Session.getDefaultInstance(props);
+ MimeMessage message = new MimeMessage(session);
+ message.setFrom(new InternetAddress("rickmcg@gmail.com"));
+ message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("rick@us.ibm.com"));
+ message.setSubject("test subject");
+
+ BodyPart messageBodyPart1 = new MimeBodyPart();
+ messageBodyPart1.setHeader("Content-Type", "text/xml");
+ messageBodyPart1.setHeader("Content-Transfer-Encoding", "binary");
+ messageBodyPart1.setText("This is a test");
+
+ MimeMultipart multipart = new MimeMultipart();
+ multipart.addBodyPart(messageBodyPart1);
+ multipart.setPreamble("This is a preamble");
+
+ assertEquals("This is a preamble", multipart.getPreamble());
+
+ message.setContent(multipart);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ message.writeTo(out);
+ out.writeTo(System.out);
+
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+
+ MimeMessage newMessage = new MimeMessage(session, in);
+ assertEquals("This is a preamble\r\n", ((MimeMultipart)newMessage.getContent()).getPreamble());
+ }
+
+ public void testMIMEWriting() throws IOException, MessagingException {
+ File basedir = new File(System.getProperty("basedir", "."));
+ File testInput = new File(basedir, "src/test/resources/wmtom.bin");
+ FileInputStream inStream = new FileInputStream(testInput);
+ Properties props = new Properties();
+ javax.mail.Session session = javax.mail.Session
+ .getInstance(props, null);
+ MimeMessage mimeMessage = new MimeMessage(session, inStream);
+ DataHandler dh = mimeMessage.getDataHandler();
+ MimeMultipart multiPart = new MimeMultipart(dh.getDataSource());
+ MimeBodyPart mimeBodyPart0 = (MimeBodyPart) multiPart.getBodyPart(0);
+ Object object0 = mimeBodyPart0.getContent();
+ assertNotNull(object0);
+ MimeBodyPart mimeBodyPart1 = (MimeBodyPart) multiPart.getBodyPart(1);
+ Object object1 = mimeBodyPart1.getContent();
+ assertNotNull(object1);
+ assertEquals(2, multiPart.getCount());
+ }
+
+ protected void writeToSetUp() throws Exception {
+ defaultMap = CommandMap.getDefaultCommandMap();
+ MailcapCommandMap myMap = new MailcapCommandMap();
+ myMap.addMailcap("text/plain;; x-java-content-handler=" + DummyTextHandler.class.getName());
+ myMap.addMailcap("multipart/*;; x-java-content-handler=" + DummyMultipartHandler.class.getName());
+ CommandMap.setDefaultCommandMap(myMap);
+ }
+
+ protected void writeToTearDown() throws Exception {
+ CommandMap.setDefaultCommandMap(defaultMap);
+ }
+
+ public static class DummyTextHandler implements DataContentHandler {
+ public DataFlavor[] getTransferDataFlavors() {
+ return new DataFlavor[0]; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Object getTransferData(DataFlavor df, DataSource ds) throws UnsupportedFlavorException, IOException {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Object getContent(DataSource ds) throws IOException {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void writeTo(Object obj, String mimeType, OutputStream os) throws IOException {
+ os.write(((String)obj).getBytes());
+ }
+ }
+
+ public static class DummyMultipartHandler implements DataContentHandler {
+ public DataFlavor[] getTransferDataFlavors() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object getTransferData(DataFlavor df, DataSource ds) throws UnsupportedFlavorException, IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object getContent(DataSource ds) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void writeTo(Object obj, String mimeType, OutputStream os) throws IOException {
+ MimeMultipart mp = (MimeMultipart) obj;
+ try {
+ mp.writeTo(os);
+ } catch (MessagingException e) {
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ }
+ }
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeTest.java
new file mode 100644
index 00000000..6a9e9f7c
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeTest.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 javax.mail.internet;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.mail.Session;
+
+import junit.framework.TestCase;
+
+public class MimeTest extends TestCase {
+
+ public void testWriteRead() throws Exception {
+ Session session = Session.getDefaultInstance(new Properties(), null);
+ MimeMessage mime = new MimeMessage(session);
+ MimeMultipart parts = new MimeMultipart("related; type=\"text/xml\"; start=\"\"");
+ MimeBodyPart xmlPart = new MimeBodyPart();
+ xmlPart.setContentID("");
+ xmlPart.setDataHandler(new DataHandler(new ByteArrayDataSource(" ".getBytes(), "text/xml")));
+ parts.addBodyPart(xmlPart);
+ MimeBodyPart jpegPart = new MimeBodyPart();
+ jpegPart.setContentID("");
+ jpegPart.setDataHandler(new DataHandler(new ByteArrayDataSource(new byte[] { 0, 1, 2, 3, 4, 5 }, "image/jpeg")));
+ parts.addBodyPart(jpegPart);
+ mime.setContent(parts);
+ mime.setHeader("Content-Type", parts.getContentType());
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mime.writeTo(baos);
+
+ MimeMessage mime2 = new MimeMessage(session, new ByteArrayInputStream(baos.toByteArray()));
+ assertTrue(mime2.getContent() instanceof MimeMultipart);
+ MimeMultipart parts2 = (MimeMultipart) mime2.getContent();
+ assertEquals(mime.getContentType(), mime2.getContentType());
+ assertEquals(parts.getCount(), parts2.getCount());
+ assertTrue(parts2.getBodyPart(0) instanceof MimeBodyPart);
+ assertTrue(parts2.getBodyPart(1) instanceof MimeBodyPart);
+
+ MimeBodyPart xmlPart2 = (MimeBodyPart) parts2.getBodyPart(0);
+ assertEquals(xmlPart.getContentID(), xmlPart2.getContentID());
+ ByteArrayOutputStream xmlBaos = new ByteArrayOutputStream();
+ copyInputStream(xmlPart.getDataHandler().getInputStream(), xmlBaos);
+ ByteArrayOutputStream xmlBaos2 = new ByteArrayOutputStream();
+ copyInputStream(xmlPart2.getDataHandler().getInputStream(), xmlBaos2);
+ assertEquals(xmlBaos.toString(), xmlBaos2.toString());
+
+ MimeBodyPart jpegPart2 = (MimeBodyPart) parts2.getBodyPart(1);
+ assertEquals(jpegPart.getContentID(), jpegPart2.getContentID());
+ ByteArrayOutputStream jpegBaos = new ByteArrayOutputStream();
+ copyInputStream(jpegPart.getDataHandler().getInputStream(), jpegBaos);
+ ByteArrayOutputStream jpegBaos2 = new ByteArrayOutputStream();
+ copyInputStream(jpegPart2.getDataHandler().getInputStream(), jpegBaos2);
+ assertEquals(jpegBaos.toString(), jpegBaos2.toString());
+ }
+
+ public static class ByteArrayDataSource implements DataSource {
+ private byte[] data;
+ private String type;
+ private String name = "unused";
+
+ public ByteArrayDataSource(byte[] data, String type) {
+ this.data = data;
+ this.type = type;
+ }
+
+ public InputStream getInputStream() throws IOException {
+ if (data == null) throw new IOException("no data");
+ return new ByteArrayInputStream(data);
+ }
+
+ public OutputStream getOutputStream() throws IOException {
+ throw new IOException("getOutputStream() not supported");
+ }
+
+ public String getContentType() {
+ return type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+ public static void copyInputStream(InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, len);
+ }
+ in.close();
+ out.close();
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeUtilityTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeUtilityTest.java
new file mode 100644
index 00000000..f54d8130
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/MimeUtilityTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Properties;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.mail.Session;
+import javax.mail.util.ByteArrayDataSource;
+
+import junit.framework.TestCase;
+
+public class MimeUtilityTest extends TestCase {
+
+ private byte[] encodeBytes = new byte[] { 32, 104, -61, -87, 33, 32, -61, -96, -61, -88, -61, -76, 117, 32, 33, 33, 33 };
+
+ public void testEncodeDecode() throws Exception {
+
+ byte [] data = new byte[256];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (byte)i;
+ }
+
+ // different lengths test boundary conditions
+ doEncodingTest(data, 256, "uuencode");
+ doEncodingTest(data, 255, "uuencode");
+ doEncodingTest(data, 254, "uuencode");
+
+ doEncodingTest(data, 256, "binary");
+ doEncodingTest(data, 256, "7bit");
+ doEncodingTest(data, 256, "8bit");
+ doEncodingTest(data, 256, "base64");
+ doEncodingTest(data, 255, "base64");
+ doEncodingTest(data, 254, "base64");
+
+ doEncodingTest(data, 256, "x-uuencode");
+ doEncodingTest(data, 256, "x-uue");
+ doEncodingTest(data, 256, "quoted-printable");
+ doEncodingTest(data, 255, "quoted-printable");
+ doEncodingTest(data, 254, "quoted-printable");
+ }
+
+
+ public void testFoldUnfold() throws Exception {
+ doFoldTest(0, "This is a short string", "This is a short string");
+ doFoldTest(0, "The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog.",
+ "The quick brown fox jumped over the lazy dog. The quick brown fox jumped\r\n over the lazy dog. The quick brown fox jumped over the lazy dog.");
+ doFoldTest(50, "The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog.",
+ "The quick brown fox jumped\r\n over the lazy dog. The quick brown fox jumped over the lazy dog. The quick\r\n brown fox jumped over the lazy dog.");
+ doFoldTest(20, "======================================================================================================================= break should be here",
+ "=======================================================================================================================\r\n break should be here");
+ }
+
+
+ public void doEncodingTest(byte[] data, int length, String encoding) throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ OutputStream encoder = MimeUtility.encode(out, encoding);
+
+ encoder.write(data, 0, length);
+ encoder.flush();
+
+ byte[] encodedData = out.toByteArray();
+
+ ByteArrayInputStream in = new ByteArrayInputStream(encodedData);
+
+ InputStream decoder = MimeUtility.decode(in, encoding);
+
+ byte[] decodedData = new byte[length];
+
+ int count = decoder.read(decodedData);
+
+ assertEquals(length, count);
+
+ for (int i = 0; i < length; i++) {
+ assertEquals(data[i], decodedData[i]);
+ }
+ }
+
+
+ public void doFoldTest(int used, String source, String folded) throws Exception {
+ String newFolded = MimeUtility.fold(used, source);
+ String newUnfolded = MimeUtility.unfold(newFolded);
+
+ assertEquals(folded, newFolded);
+ assertEquals(source, newUnfolded);
+ }
+
+
+ public void testEncodeWord() throws Exception {
+ assertEquals("abc", MimeUtility.encodeWord("abc"));
+
+ String encodeString = new String(encodeBytes, "UTF-8");
+ // default code page dependent, hard to directly test the encoded results
+ // The following disabled because it will not succeed on all locales because the
+ // code points used in the test string won't round trip properly for all code pages.
+ // assertEquals(encodeString, MimeUtility.decodeWord(MimeUtility.encodeWord(encodeString)));
+
+ String encoded = MimeUtility.encodeWord(encodeString, "UTF-8", "Q");
+ assertEquals("=?UTF-8?Q?_h=C3=A9!_=C3=A0=C3=A8=C3=B4u_!!!?=", encoded);
+ assertEquals(encodeString, MimeUtility.decodeWord(encoded));
+
+ encoded = MimeUtility.encodeWord(encodeString, "UTF-8", "B");
+ assertEquals("=?UTF-8?B?IGjDqSEgw6DDqMO0dSAhISE=?=", encoded);
+ assertEquals(encodeString, MimeUtility.decodeWord(encoded));
+ }
+
+
+ public void testEncodeText() throws Exception {
+ assertEquals("abc", MimeUtility.encodeWord("abc"));
+
+ String encodeString = new String(encodeBytes, "UTF-8");
+ // default code page dependent, hard to directly test the encoded results
+ // The following disabled because it will not succeed on all locales because the
+ // code points used in the test string won't round trip properly for all code pages.
+ // assertEquals(encodeString, MimeUtility.decodeText(MimeUtility.encodeText(encodeString)));
+
+ String encoded = MimeUtility.encodeText(encodeString, "UTF-8", "Q");
+ assertEquals("=?UTF-8?Q?_h=C3=A9!_=C3=A0=C3=A8=C3=B4u_!!!?=", encoded);
+ assertEquals(encodeString, MimeUtility.decodeText(encoded));
+
+ encoded = MimeUtility.encodeText(encodeString, "UTF-8", "B");
+ assertEquals("=?UTF-8?B?IGjDqSEgw6DDqMO0dSAhISE=?=", encoded);
+ assertEquals(encodeString, MimeUtility.decodeText(encoded));
+
+ // this has multiple byte characters and is longer than the 76 character grouping, so this
+ // hits a lot of different boundary conditions
+ String subject = "\u03a0\u03a1\u03a2\u03a3\u03a4\u03a5\u03a6\u03a7 \u03a8\u03a9\u03aa\u03ab \u03ac\u03ad\u03ae\u03af\u03b0 \u03b1\u03b2\u03b3\u03b4\u03b5 \u03b6\u03b7\u03b8\u03b9\u03ba \u03bb\u03bc\u03bd\u03be\u03bf\u03c0 \u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7 \u03c8\u03c9\u03ca\u03cb\u03cd\u03ce \u03cf\u03d0\u03d1\u03d2";
+ encoded = MimeUtility.encodeText(subject, "utf-8", "Q");
+ assertEquals(subject, MimeUtility.decodeText(encoded));
+
+ encoded = MimeUtility.encodeText(subject, "utf-8", "B");
+ assertEquals(subject, MimeUtility.decodeText(encoded));
+ }
+
+
+ public void testGetEncoding() throws Exception {
+ ByteArrayDataSource source = new ByteArrayDataSource(new byte[] { 'a', 'b', 'c'}, "text/plain");
+
+ assertEquals("7bit", MimeUtility.getEncoding(source));
+
+ source = new ByteArrayDataSource(new byte[] { 'a', 'b', (byte)0x81}, "text/plain");
+
+ assertEquals("quoted-printable", MimeUtility.getEncoding(source));
+
+ source = new ByteArrayDataSource(new byte[] { 'a', (byte)0x82, (byte)0x81}, "text/plain");
+
+ assertEquals("base64", MimeUtility.getEncoding(source));
+
+
+ source = new ByteArrayDataSource(new byte[] { 'a', 'b', 'c'}, "application/binary");
+
+ assertEquals("7bit", MimeUtility.getEncoding(source));
+
+ source = new ByteArrayDataSource(new byte[] { 'a', 'b', (byte)0x81}, "application/binary");
+
+ assertEquals("base64", MimeUtility.getEncoding(source));
+
+ source = new ByteArrayDataSource(new byte[] { 'a', (byte)0x82, (byte)0x81}, "application/binary");
+
+ assertEquals("base64", MimeUtility.getEncoding(source));
+ }
+
+
+ public void testQuote() throws Exception {
+ assertEquals("abc", MimeUtility.quote("abc", "&*%"));
+ assertEquals("\"abc&\"", MimeUtility.quote("abc&", "&*%"));
+ assertEquals("\"abc\\\"\"", MimeUtility.quote("abc\"", "&*%"));
+ assertEquals("\"abc\\\\\"", MimeUtility.quote("abc\\", "&*%"));
+ assertEquals("\"abc\\\r\"", MimeUtility.quote("abc\r", "&*%"));
+ assertEquals("\"abc\\\n\"", MimeUtility.quote("abc\n", "&*%"));
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/NewsAddressTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/NewsAddressTest.java
new file mode 100644
index 00000000..2b666e87
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/NewsAddressTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class NewsAddressTest extends TestCase {
+ public void testNewsAddress() throws AddressException {
+ NewsAddress na = new NewsAddress("geronimo-dev", "news.apache.org");
+ assertEquals("geronimo-dev", na.getNewsgroup());
+ assertEquals("news.apache.org", na.getHost());
+ assertEquals("news", na.getType());
+ assertEquals("geronimo-dev", na.toString());
+ NewsAddress[] nas =
+ NewsAddress.parse(
+ "geronimo-dev@news.apache.org, geronimo-user@news.apache.org");
+ assertEquals(2, nas.length);
+ assertEquals("geronimo-dev", nas[0].getNewsgroup());
+ assertEquals("news.apache.org", nas[0].getHost());
+ assertEquals("geronimo-user", nas[1].getNewsgroup());
+ assertEquals("news.apache.org", nas[1].getHost());
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/ParameterListTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/ParameterListTest.java
new file mode 100644
index 00000000..b6dd1e16
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/ParameterListTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.internet;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class ParameterListTest extends TestCase {
+ public ParameterListTest(String arg0) {
+ super(arg0);
+ }
+ public void testParameters() throws ParseException {
+ ParameterList list =
+ new ParameterList(";thing=value;thong=vulue;thung=git");
+ assertEquals("value", list.get("thing"));
+ assertEquals("vulue", list.get("thong"));
+ assertEquals("git", list.get("thung"));
+ }
+
+ public void testQuotedParameter() throws ParseException {
+ ParameterList list = new ParameterList(";foo=one;bar=\"two\"");
+ assertEquals("one", list.get("foo"));
+ assertEquals("two", list.get("bar"));
+ }
+
+ public void testEncodeDecode() throws Exception {
+
+ System.setProperty("mail.mime.encodeparameters", "true");
+ System.setProperty("mail.mime.decodeparameters", "true");
+
+ String value = " '*% abc \u0081\u0082\r\n\t";
+ String encodedTest = "; one*=UTF-8''%20%27%2A%25%20abc%20%C2%81%C2%82%0D%0A%09";
+
+ ParameterList list = new ParameterList();
+ list.set("one", value, "UTF-8");
+
+ assertEquals(value, list.get("one"));
+
+ String encoded = list.toString();
+
+ assertEquals(encoded, encodedTest);
+
+ ParameterList list2 = new ParameterList(encoded);
+ assertEquals(value, list.get("one"));
+ assertEquals(list2.toString(), encodedTest);
+ }
+
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/internet/PreencodedMimeBodyPartTest.java b/external/geronimo_javamail/src/test/java/javax/mail/internet/PreencodedMimeBodyPartTest.java
new file mode 100644
index 00000000..a5f17d50
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/internet/PreencodedMimeBodyPartTest.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 javax.mail.internet;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+
+import javax.mail.MessagingException;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class PreencodedMimeBodyPartTest extends TestCase {
+
+ public void testEncoding() throws Exception {
+ PreencodedMimeBodyPart part = new PreencodedMimeBodyPart("base64");
+ assertEquals("base64", part.getEncoding());
+ }
+
+ public void testUpdateHeaders() throws Exception {
+ TestBodyPart part = new TestBodyPart("base64");
+
+ part.updateHeaders();
+
+ assertEquals("base64", part.getHeader("Content-Transfer-Encoding", null));
+ }
+
+ public void testWriteTo() throws Exception {
+ PreencodedMimeBodyPart part = new PreencodedMimeBodyPart("binary");
+
+ byte[] content = new byte[] { 81, 82, 83, 84, 85, 86 };
+
+ part.setContent(new String(content, "UTF-8"), "text/plain; charset=\"UTF-8\"");
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ part.writeTo(out);
+
+ byte[] data = out.toByteArray();
+
+ // we need to scan forward to the actual content and verify it has been written without additional
+ // encoding. Our marker is a "crlfcrlf" sequence.
+
+
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] == '\r') {
+ if (data[i + 1] == '\n' && data[i + 2] == '\r' && data[i + 3] == '\n') {
+ for (int j = 0; j < content.length; j++) {
+ assertEquals(data[i + 4 + j], content[j]);
+ }
+
+ }
+ }
+ }
+ }
+
+
+ public class TestBodyPart extends PreencodedMimeBodyPart {
+
+ public TestBodyPart(String encoding) {
+ super(encoding);
+ }
+
+ public void updateHeaders() throws MessagingException {
+ super.updateHeaders();
+ }
+ }
+}
+
+
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/util/ByteArrayDataSourceTest.java b/external/geronimo_javamail/src/test/java/javax/mail/util/ByteArrayDataSourceTest.java
new file mode 100644
index 00000000..49615498
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/util/ByteArrayDataSourceTest.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 javax.mail.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class ByteArrayDataSourceTest extends TestCase {
+ public ByteArrayDataSourceTest(String arg0) {
+ super(arg0);
+ }
+
+ public void testByteArray() throws Exception {
+ doDataSourceTest(new ByteArrayDataSource("0123456789", "text/plain"), "text/plain");
+ doDataSourceTest(new ByteArrayDataSource("0123456789".getBytes(), "text/xml"), "text/xml");
+ ByteArrayInputStream in = new ByteArrayInputStream("0123456789".getBytes());
+
+ doDataSourceTest(new ByteArrayDataSource(in, "text/html"), "text/html");
+
+ try {
+ ByteArrayDataSource source = new ByteArrayDataSource("01234567890", "text/plain");
+ source.getOutputStream();
+ fail();
+ } catch (IOException e) {
+ }
+
+ ByteArrayDataSource source = new ByteArrayDataSource("01234567890", "text/plain");
+ assertEquals("", source.getName());
+
+ source.setName("fred");
+ assertEquals("fred", source.getName());
+ }
+
+
+ private void doDataSourceTest(ByteArrayDataSource source, String type) throws Exception {
+ assertEquals(type, source.getContentType());
+
+ InputStream in = source.getInputStream();
+ byte[] bytes = new byte[10];
+
+ int count = in.read(bytes);
+
+ assertEquals(count, bytes.length);
+ assertEquals("0123456789", new String(bytes));
+ }
+}
+
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/util/SharedByteArrayInputStreamTest.java b/external/geronimo_javamail/src/test/java/javax/mail/util/SharedByteArrayInputStreamTest.java
new file mode 100644
index 00000000..5779aac2
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/util/SharedByteArrayInputStreamTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.util;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class SharedByteArrayInputStreamTest extends TestCase {
+ private String testString = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ private byte[] testData = testString.getBytes();
+
+
+
+ public SharedByteArrayInputStreamTest(String arg0) {
+ super(arg0);
+ }
+
+ public void testInput() throws Exception {
+ SharedByteArrayInputStream in = new SharedByteArrayInputStream(testData);
+
+ assertEquals('0', in.read());
+
+ assertEquals(1, in.getPosition());
+
+ byte[] bytes = new byte[10];
+
+ assertEquals(10, in.read(bytes));
+ assertEquals("123456789a", new String(bytes));
+ assertEquals(11, in.getPosition());
+
+ assertEquals(5, in.read(bytes, 5, 5));
+ assertEquals("12345bcdef", new String(bytes));
+ assertEquals(16, in.getPosition());
+
+ assertEquals(5, in.skip(5));
+ assertEquals(21, in.getPosition());
+ assertEquals('l', in.read());
+
+ while (in.read() != 'Z') {
+ }
+
+ assertEquals(-1, in.read());
+ }
+
+
+ public void testNewStream() throws Exception {
+ SharedByteArrayInputStream in = new SharedByteArrayInputStream(testData);
+
+ SharedByteArrayInputStream sub = (SharedByteArrayInputStream)in.newStream(10, 10 + 26);
+
+ assertEquals(0, sub.getPosition());
+
+ assertEquals('0', in.read());
+ assertEquals('a', sub.read());
+
+ sub.skip(1);
+ assertEquals(2, sub.getPosition());
+
+ while (sub.read() != 'z') {
+ }
+
+ assertEquals(-1, sub.read());
+
+ SharedByteArrayInputStream sub2 = (SharedByteArrayInputStream)sub.newStream(5, 10);
+
+ assertEquals(0, sub2.getPosition());
+ assertEquals('f', sub2.read());
+ }
+}
diff --git a/external/geronimo_javamail/src/test/java/javax/mail/util/SharedFileInputStreamTest.java b/external/geronimo_javamail/src/test/java/javax/mail/util/SharedFileInputStreamTest.java
new file mode 100644
index 00000000..e8950edb
--- /dev/null
+++ b/external/geronimo_javamail/src/test/java/javax/mail/util/SharedFileInputStreamTest.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package javax.mail.util;
+
+import java.io.File;
+import java.io.IOException;
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $
+ */
+public class SharedFileInputStreamTest extends TestCase {
+
+ File basedir = new File(System.getProperty("basedir", "."));
+ File testInput = new File(basedir, "src/test/resources/test.dat");
+
+ public SharedFileInputStreamTest(String arg0) {
+ super(arg0);
+ }
+
+ public void testInput() throws Exception {
+ doTestInput(new SharedFileInputStream(testInput));
+ doTestInput(new SharedFileInputStream(testInput.getPath()));
+
+ doTestInput(new SharedFileInputStream(testInput, 16));
+ doTestInput(new SharedFileInputStream(testInput.getPath(), 16));
+ }
+
+
+ public void doTestInput(SharedFileInputStream in) throws Exception {
+ assertEquals('0', in.read());
+
+ assertEquals(1, in.getPosition());
+
+ byte[] bytes = new byte[10];
+
+ assertEquals(10, in.read(bytes));
+ assertEquals("123456789a", new String(bytes));
+ assertEquals(11, in.getPosition());
+
+ assertEquals(5, in.read(bytes, 5, 5));
+ assertEquals("12345bcdef", new String(bytes));
+ assertEquals(16, in.getPosition());
+
+ assertEquals(5, in.skip(5));
+ assertEquals(21, in.getPosition());
+ assertEquals('l', in.read());
+
+ while (in.read() != '\n' ) {
+ }
+
+ assertEquals(-1, in.read());
+
+ in.close();
+ }
+
+
+ public void testNewStream() throws Exception {
+ SharedFileInputStream in = new SharedFileInputStream(testInput);
+
+ SharedFileInputStream sub = (SharedFileInputStream)in.newStream(10, 10 + 26);
+
+ assertEquals(0, sub.getPosition());
+
+ assertEquals('0', in.read());
+ assertEquals('a', sub.read());
+
+ sub.skip(1);
+ assertEquals(2, sub.getPosition());
+
+ while (sub.read() != 'z') {
+ }
+
+ assertEquals(-1, sub.read());
+
+ SharedFileInputStream sub2 = (SharedFileInputStream)sub.newStream(5, 10);
+
+ sub.close(); // should not close in or sub2
+
+ assertEquals(0, sub2.getPosition());
+ assertEquals('f', sub2.read());
+
+ assertEquals('1', in.read()); // should still work
+
+ sub2.close();
+
+ assertEquals('2', in.read()); // should still work
+
+ in.close();
+ }
+
+
+ public void testMark() throws Exception {
+ doMarkTest(new SharedFileInputStream(testInput, 10));
+
+ SharedFileInputStream in = new SharedFileInputStream(testInput, 10);
+
+ SharedFileInputStream sub = (SharedFileInputStream)in.newStream(5, -1);
+ doMarkTest(sub);
+ }
+
+
+ private void doMarkTest(SharedFileInputStream in) throws Exception {
+ assertTrue(in.markSupported());
+
+ byte[] buffer = new byte[60];
+
+ in.read();
+ in.read();
+ in.mark(50);
+
+ int markSpot = in.read();
+
+ in.read(buffer, 0, 20);
+
+ in.reset();
+
+ assertEquals(markSpot, in.read());
+ in.read(buffer, 0, 40);
+ in.reset();
+ assertEquals(markSpot, in.read());
+
+ in.read(buffer, 0, 51);
+
+ try {
+ in.reset();
+ fail();
+ } catch (IOException e) {
+ }
+ }
+}
+
diff --git a/external/geronimo_javamail/src/test/resources/test.dat b/external/geronimo_javamail/src/test/resources/test.dat
new file mode 100644
index 00000000..d70c47e0
--- /dev/null
+++ b/external/geronimo_javamail/src/test/resources/test.dat
@@ -0,0 +1 @@
+0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
diff --git a/external/geronimo_javamail/src/test/resources/wmtom.bin b/external/geronimo_javamail/src/test/resources/wmtom.bin
new file mode 100644
index 00000000..01f9c8c6
--- /dev/null
+++ b/external/geronimo_javamail/src/test/resources/wmtom.bin
@@ -0,0 +1,21 @@
+----MIMEBoundary258DE2D105298B756D
+content-type:application/xop+xml; charset=utf-8; type="application/soap+xml"
+content-transfer-encoding:binary
+content-id:<0.15B50EF49317518B01@apache.org>
+
+http://localhost:8070/axis2/services/MTOMService/mtomSample
+----MIMEBoundary258DE2D105298B756D
+content-id:<11.BBFC8D48A21258EBBD@apache.org>
+content-type:application/octet-stream
+content-transfer-encoding:binary
+
+saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda
+
+----MIMEBoundary258DE2D105298B756D
+content-id:<2.CB365E36E21BD6491A@apache.org>
+content-type:application/octet-stream
+content-transfer-encoding:binary
+
+saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda saminda
+
+----MIMEBoundary258DE2D105298B756D--
diff --git a/mvnw b/mvnw
index 95b507f8..ac8e247e 100755
--- a/mvnw
+++ b/mvnw
@@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
-# https://www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
@@ -16,274 +16,235 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-# Copyright 2021 Google
-
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Apache Maven Wrapper startup batch script, version 3.1.1
-#
-# Required ENV vars:
-# ------------------
-# JAVA_HOME - location of a JDK home dir
+# Apache Maven Wrapper startup batch script, version 3.3.1
#
# Optional ENV vars
# -----------------
-# MAVEN_OPTS - parameters passed to the Java VM when running Maven
-# e.g. to debug Maven itself, use
-# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
-if [ -z "$MAVEN_SKIP_RC" ] ; then
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
- if [ -f /usr/local/etc/mavenrc ] ; then
- . /usr/local/etc/mavenrc
- fi
-
- if [ -f /etc/mavenrc ] ; then
- . /etc/mavenrc
- fi
-
- if [ -f "$HOME/.mavenrc" ] ; then
- . "$HOME/.mavenrc"
- fi
-
-fi
-
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-mingw=false
-case "`uname`" in
- CYGWIN*) cygwin=true ;;
- MINGW*) mingw=true;;
- Darwin*) darwin=true
- # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
- # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
- if [ -z "$JAVA_HOME" ]; then
- if [ -x "/usr/libexec/java_home" ]; then
- JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME
- else
- JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
- fi
- fi
- ;;
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
esac
-if [ -z "$JAVA_HOME" ] ; then
- if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
- fi
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
-fi
-
-# For Mingw, ensure paths are in UNIX format before anything is touched
-if $mingw ; then
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
-fi
-
-if [ -z "$JAVA_HOME" ]; then
- javaExecutable="`which javac`"
- if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
- # readlink(1) is not available as standard on Solaris 10.
- readLink=`which readlink`
- if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
- if $darwin ; then
- javaHome="`dirname \"$javaExecutable\"`"
- javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
- else
- javaExecutable="`readlink -f \"$javaExecutable\"`"
- fi
- javaHome="`dirname \"$javaExecutable\"`"
- javaHome=`expr "$javaHome" : '\(.*\)/bin'`
- JAVA_HOME="$javaHome"
- export JAVA_HOME
- fi
- fi
-fi
-
-if [ -z "$JAVACMD" ] ; then
- if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
fi
else
- JAVACMD="`\\unset -f command; \\command -v java`"
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
fi
-fi
-
-if [ ! -x "$JAVACMD" ] ; then
- echo "Error: JAVA_HOME is not defined correctly." >&2
- echo " We cannot execute $JAVACMD" >&2
- exit 1
-fi
+}
-if [ -z "$JAVA_HOME" ] ; then
- echo "Warning: JAVA_HOME environment variable is not set."
-fi
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
-# traverses directory structure from process work directory to filesystem root
-# first directory with .mvn subdirectory is considered project base directory
-find_maven_basedir() {
- if [ -z "$1" ]
- then
- echo "Path not specified to find_maven_basedir"
- return 1
- fi
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
- basedir="$1"
- wdir="$1"
- while [ "$wdir" != '/' ] ; do
- if [ -d "$wdir"/.mvn ] ; then
- basedir=$wdir
- break
- fi
- # workaround for JBEAP-8937 (on Solaris 10/Sparc)
- if [ -d "${wdir}" ]; then
- wdir=`cd "$wdir/.."; pwd`
- fi
- # end of workaround
- done
- printf '%s' "$(cd "$basedir"; pwd)"
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
}
-# concatenates all lines of a file
-concat_lines() {
- if [ -f "$1" ]; then
- echo "$(tr -s '\n' ' ' < "$1")"
- fi
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl="${value-}" ;;
+ distributionSha256Sum) distributionSha256Sum="${value-}" ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_HOME="$HOME/.m2/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
-BASE_DIR=$(find_maven_basedir "$(dirname $0)")
-if [ -z "$BASE_DIR" ]; then
- exit 1;
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
fi
-MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
-if [ "$MVNW_VERBOSE" = true ]; then
- echo $MAVEN_PROJECTBASEDIR
-fi
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
-##########################################################################################
-# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-# This allows using the maven wrapper in projects that prohibit checking in binary data.
-##########################################################################################
-if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found .mvn/wrapper/maven-wrapper.jar"
- fi
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
- fi
- if [ -n "$MVNW_REPOURL" ]; then
- wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
- else
- wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
- fi
- while IFS="=" read key value; do
- case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;;
- esac
- done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Downloading from: $wrapperUrl"
- fi
- wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
- if $cygwin; then
- wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
- fi
+ die "cannot create temp dir"
+fi
- if command -v wget > /dev/null; then
- QUIET="--quiet"
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found wget ... using wget"
- QUIET=""
- fi
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- wget $QUIET "$wrapperUrl" -O "$wrapperJarPath"
- else
- wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath"
- fi
- [ $? -eq 0 ] || rm -f "$wrapperJarPath"
- elif command -v curl > /dev/null; then
- QUIET="--silent"
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found curl ... using curl"
- QUIET=""
- fi
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L
- else
- curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L
- fi
- [ $? -eq 0 ] || rm -f "$wrapperJarPath"
- else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Falling back to using Java to download"
- fi
- javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
- javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class"
- # For Cygwin, switch paths to Windows format before running javac
- if $cygwin; then
- javaSource=`cygpath --path --windows "$javaSource"`
- javaClass=`cygpath --path --windows "$javaClass"`
- fi
- if [ -e "$javaSource" ]; then
- if [ ! -e "$javaClass" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Compiling MavenWrapperDownloader.java ..."
- fi
- # Compiling the Java class
- ("$JAVA_HOME/bin/javac" "$javaSource")
- fi
- if [ -e "$javaClass" ]; then
- # Running the downloader
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Running MavenWrapperDownloader.java ..."
- fi
- ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
- fi
- fi
- fi
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
fi
-##########################################################################################
-# End of extension
-##########################################################################################
-MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
- [ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
-# Provide a "standardized" way to retrieve the CLI args that will
-# work with both Windows and non-Windows executions.
-MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
-export MAVEN_CMD_LINE_ARGS
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
-WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
-exec "$JAVACMD" \
- $MAVEN_OPTS \
- $MAVEN_DEBUG_OPTS \
- -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
- ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
+clean || :
+exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
index fa1d4c77..7b0c0943 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -1,198 +1,146 @@
-@REM
-@REM Copyright 2021 Google LLC
-@REM
-@REM Licensed under the Apache License, Version 2.0 (the "License");
-@REM you may not use this file except in compliance with the License.
-@REM You may obtain a copy of the License at
-@REM
-@REM https://www.apache.org/licenses/LICENSE-2.0
-@REM
-@REM Unless required by applicable law or agreed to in writing, software
-@REM distributed under the License is distributed on an "AS IS" BASIS,
-@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@REM See the License for the specific language governing permissions and
-@REM limitations under the License.
-@REM
-
+<# : batch portion
@REM ----------------------------------------------------------------------------
-@REM Maven2 Start Up Batch script
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
@REM
-@REM Required ENV vars:
-@REM JAVA_HOME - location of a JDK home dir
+@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
-@REM Optional ENV vars
-@REM M2_HOME - location of maven2's installed home dir
-@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
-@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
-@REM e.g. to debug Maven itself, use
-@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
@REM ----------------------------------------------------------------------------
-@REM Apache Maven Wrapper startup batch script, version 3.1.1
-@REM
-@REM Required ENV vars:
-@REM JAVA_HOME - location of a JDK home dir
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.1
@REM
@REM Optional ENV vars
-@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
-@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
-@REM e.g. to debug Maven itself, use
-@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
-@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
-@echo off
-@REM set title of command window
-title %0
-@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
-@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
-
-@REM set %HOME% to equivalent of $HOME
-if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
-
-@REM Execute a user defined script before this one
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
-@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
-if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
-:skipRcPre
-
-@setlocal
-
-set ERROR_CODE=0
-
-@REM To isolate internal variables from possible post scripts, we use another setlocal
-@setlocal
-
-@REM ==== START VALIDATION ====
-if not "%JAVA_HOME%" == "" goto OkJHome
-
-echo.
-echo Error: JAVA_HOME not found in your environment. >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-:OkJHome
-if exist "%JAVA_HOME%\bin\java.exe" goto init
-
-echo.
-echo Error: JAVA_HOME is set to an invalid directory. >&2
-echo JAVA_HOME = "%JAVA_HOME%" >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-@REM ==== END VALIDATION ====
-
-:init
-
-@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
-@REM Fallback to current working directory if not found.
-
-set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
-IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
-
-set EXEC_DIR=%CD%
-set WDIR=%EXEC_DIR%
-:findBaseDir
-IF EXIST "%WDIR%"\.mvn goto baseDirFound
-cd ..
-IF "%WDIR%"=="%CD%" goto baseDirNotFound
-set WDIR=%CD%
-goto findBaseDir
-
-:baseDirFound
-set MAVEN_PROJECTBASEDIR=%WDIR%
-cd "%EXEC_DIR%"
-goto endDetectBaseDir
-
-:baseDirNotFound
-set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
-cd "%EXEC_DIR%"
-
-:endDetectBaseDir
-
-IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
-
-@setlocal EnableExtensions EnableDelayedExpansion
-for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
-@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
-
-:endReadAdditionalConfig
-
-SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
-set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
-
-FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
-
-@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
-if exist %WRAPPER_JAR% (
- if "%MVNW_VERBOSE%" == "true" (
- echo Found %WRAPPER_JAR%
- )
-) else (
- if not "%MVNW_REPOURL%" == "" (
- SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
- )
- if "%MVNW_VERBOSE%" == "true" (
- echo Couldn't find %WRAPPER_JAR%, downloading it ...
- echo Downloading from: %WRAPPER_URL%
- )
-
- powershell -Command "&{"^
- "$webclient = new-object System.Net.WebClient;"^
- "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
- "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
- "}"^
- "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
- "}"
- if "%MVNW_VERBOSE%" == "true" (
- echo Finished downloading %WRAPPER_JAR%
- )
-)
-@REM End of extension
-
-@REM Provide a "standardized" way to retrieve the CLI args that will
-@REM work with both Windows and non-Windows executions.
-set MAVEN_CMD_LINE_ARGS=%*
-
-%MAVEN_JAVA_EXE% ^
- %JVM_CONFIG_MAVEN_PROPS% ^
- %MAVEN_OPTS% ^
- %MAVEN_DEBUG_OPTS% ^
- -classpath %WRAPPER_JAR% ^
- "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
- %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
-if ERRORLEVEL 1 goto error
-goto end
-
-:error
-set ERROR_CODE=1
-
-:end
-@endlocal & set ERROR_CODE=%ERROR_CODE%
-
-if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
-@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
-if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
-:skipRcPost
-
-@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%"=="on" pause
-
-if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
-
-cmd /C exit /B %ERROR_CODE%
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/pom.xml b/pom.xml
index 0321501d..b9bb8d98 100644
--- a/pom.xml
+++ b/pom.xml
@@ -914,7 +914,7 @@
org.spdx
spdx-maven-plugin
- 0.7.2
+ 0.7.3
build-spdx