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

+ * This class is Serializable but compatibility is not guaranteed across releases. + * + * @version $Rev: 467553 $ $Date: 2006-10-24 23:01:51 -0500 (Tue, 24 Oct 2006) $ + */ +public class Flags implements Cloneable, Serializable { + public static final class Flag { + /** + * Flag that indicates that the message has been replied to; has a bit value of 1. + */ + public static final Flag ANSWERED = new Flag(1); + /** + * Flag that indicates that the message has been marked for deletion and + * should be removed on a subsequent expunge operation; has a bit value of 2. + */ + public static final Flag DELETED = new Flag(2); + /** + * Flag that indicates that the message is a draft; has a bit value of 4. + */ + public static final Flag DRAFT = new Flag(4); + /** + * Flag that indicates that the message has been flagged; has a bit value of 8. + */ + public static final Flag FLAGGED = new Flag(8); + /** + * Flag that indicates that the message has been delivered since the last time + * this folder was opened; has a bit value of 16. + */ + public static final Flag RECENT = new Flag(16); + /** + * Flag that indicates that the message has been viewed; has a bit value of 32. + * This flag is set by the {@link Message#getInputStream()} and {@link Message#getContent()} + * methods. + */ + public static final Flag SEEN = new Flag(32); + /** + * Flags that indicates if this folder supports user-defined flags; has a bit value of 0x80000000. + */ + public static final Flag USER = new Flag(0x80000000); + + private final int mask; + + private Flag(int mask) { + this.mask = mask; + } + } + + // the Serialized form of this class required the following two fields to be persisted + // this leads to a specific type of implementation + private int system_flags; + private final Hashtable user_flags; + + /** + * Construct a Flags instance with no flags set. + */ + public Flags() { + user_flags = new Hashtable(); + } + + /** + * Construct a Flags instance with a supplied system flag set. + * @param flag the system flag to set + */ + public Flags(Flag flag) { + system_flags = flag.mask; + user_flags = new Hashtable(); + } + + /** + * Construct a Flags instance with a same flags set. + * @param flags the instance to copy + */ + public Flags(Flags flags) { + system_flags = flags.system_flags; + user_flags = new Hashtable(flags.user_flags); + } + + /** + * Construct a Flags instance with the supplied user flags set. + * Question: should this automatically set the USER system flag? + * @param name the user flag to set + */ + public Flags(String name) { + user_flags = new Hashtable(); + user_flags.put(name.toLowerCase(), name); + } + + /** + * Set a system flag. + * @param flag the system flag to set + */ + public void add(Flag flag) { + system_flags |= flag.mask; + } + + /** + * Set all system and user flags from the supplied Flags. + * Question: do we need to check compatibility of USER flags? + * @param flags the Flags to add + */ + public void add(Flags flags) { + system_flags |= flags.system_flags; + user_flags.putAll(flags.user_flags); + } + + /** + * Set a user flag. + * Question: should this fail if the USER system flag is not set? + * @param name the user flag to set + */ + public void add(String name) { + user_flags.put(name.toLowerCase(), name); + } + + /** + * Return a copy of this instance. + * @return a copy of this instance + */ + public Object clone() { + return new Flags(this); + } + + /** + * See if the supplied system flags are set + * @param flag the system flags to check for + * @return true if the flags are set + */ + public boolean contains(Flag flag) { + return (system_flags & flag.mask) != 0; + } + + /** + * See if all of the supplied Flags are set + * @param flags the flags to check for + * @return true if all the supplied system and user flags are set + */ + public boolean contains(Flags flags) { + return ((system_flags & flags.system_flags) == flags.system_flags) + && user_flags.keySet().containsAll(flags.user_flags.keySet()); + } + + /** + * See if the supplied user flag is set + * @param name the user flag to check for + * @return true if the flag is set + */ + public boolean contains(String name) { + return user_flags.containsKey(name.toLowerCase()); + } + + /** + * Equality is defined as true if the other object is a instanceof Flags with the + * same system and user flags set (using a case-insensitive name comparison for user flags). + * @param other the instance to compare against + * @return true if the two instance are the same + */ + public boolean equals(Object other) { + if (other == this) return true; + if (other instanceof Flags == false) return false; + final Flags flags = (Flags) other; + return system_flags == flags.system_flags && user_flags.keySet().equals(flags.user_flags.keySet()); + } + + /** + * Calculate a hashCode for this instance + * @return a hashCode for this instance + */ + public int hashCode() { + return system_flags ^ user_flags.keySet().hashCode(); + } + + /** + * Return a list of {@link Flags.Flag Flags} containing the system flags that have been set + * @return the system flags that have been set + */ + public Flag[] getSystemFlags() { + // assumption: it is quicker to calculate the size than it is to reallocate the array + int size = 0; + if ((system_flags & Flag.ANSWERED.mask) != 0) size += 1; + if ((system_flags & Flag.DELETED.mask) != 0) size += 1; + if ((system_flags & Flag.DRAFT.mask) != 0) size += 1; + if ((system_flags & Flag.FLAGGED.mask) != 0) size += 1; + if ((system_flags & Flag.RECENT.mask) != 0) size += 1; + if ((system_flags & Flag.SEEN.mask) != 0) size += 1; + if ((system_flags & Flag.USER.mask) != 0) size += 1; + Flag[] result = new Flag[size]; + if ((system_flags & Flag.USER.mask) != 0) result[--size] = Flag.USER; + if ((system_flags & Flag.SEEN.mask) != 0) result[--size] = Flag.SEEN; + if ((system_flags & Flag.RECENT.mask) != 0) result[--size] = Flag.RECENT; + if ((system_flags & Flag.FLAGGED.mask) != 0) result[--size] = Flag.FLAGGED; + if ((system_flags & Flag.DRAFT.mask) != 0) result[--size] = Flag.DRAFT; + if ((system_flags & Flag.DELETED.mask) != 0) result[--size] = Flag.DELETED; + if ((system_flags & Flag.ANSWERED.mask) != 0) result[--size] = Flag.ANSWERED; + return result; + } + + /** + * Return a list of user flags that have been set + * @return a list of user flags + */ + public String[] getUserFlags() { + return (String[]) user_flags.values().toArray(new String[user_flags.values().size()]); + } + + /** + * Unset the supplied system flag. + * Question: what happens if we unset the USER flags and user flags are set? + * @param flag the flag to clear + */ + public void remove(Flag flag) { + system_flags &= ~flag.mask; + } + + /** + * Unset all flags from the supplied instance. + * @param flags the flags to clear + */ + public void remove(Flags flags) { + system_flags &= ~flags.system_flags; + user_flags.keySet().removeAll(flags.user_flags.keySet()); + } + + /** + * Unset the supplied user flag. + * @param name the flag to clear + */ + public void remove(String name) { + user_flags.remove(name.toLowerCase()); + } +} diff --git a/external/geronimo_javamail/src/main/java/javax/mail/Folder.java b/external/geronimo_javamail/src/main/java/javax/mail/Folder.java new file mode 100644 index 00000000..25193d9b --- /dev/null +++ b/external/geronimo_javamail/src/main/java/javax/mail/Folder.java @@ -0,0 +1,747 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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; + +import javax.mail.Flags.Flag; +import javax.mail.event.ConnectionEvent; +import javax.mail.event.ConnectionListener; +import javax.mail.event.FolderEvent; +import javax.mail.event.FolderListener; +import javax.mail.event.MailEvent; +import javax.mail.event.MessageChangedEvent; +import javax.mail.event.MessageChangedListener; +import javax.mail.event.MessageCountEvent; +import javax.mail.event.MessageCountListener; +import javax.mail.search.SearchTerm; + +/** + * An abstract representation of a folder in a mail system; subclasses would + * implement Folders for each supported protocol. + *

+ * Depending on protocol and implementation, folders may contain other folders, messages, + * or both as indicated by the {@link Folder#HOLDS_FOLDERS} and {@link Folder#HOLDS_MESSAGES} flags. + * If the immplementation supports hierarchical folders, the format of folder names is + * implementation dependent; however, components of the name are separated by the + * delimiter character returned by {@link Folder#getSeparator()}. + *

+ * The case-insensitive folder name "INBOX" is reserved to refer to the primary folder + * for the current user on the current server; not all stores will provide an INBOX + * and it may not be available at all times. + * + * @version $Rev: 582780 $ $Date: 2007-10-08 06:17:15 -0500 (Mon, 08 Oct 2007) $ + */ +public abstract class Folder { + /** + * Flag that indicates that a folder can contain messages. + */ + public static final int HOLDS_MESSAGES = 1; + /** + * Flag that indicates that a folder can contain other folders. + */ + public static final int HOLDS_FOLDERS = 2; + + /** + * Flag indicating that this folder cannot be modified. + */ + public static final int READ_ONLY = 1; + /** + * Flag indictaing that this folder can be modified. + * Question: what does it mean if both are set? + */ + public static final int READ_WRITE = 2; + + /** + * The store that this folder is part of. + */ + protected Store store; + /** + * The current mode of this folder. + * When open, this can be {@link #READ_ONLY} or {@link #READ_WRITE}; + * otherwise is set to -1. + */ + protected int mode = -1; + + private final ArrayList connectionListeners = new ArrayList(2); + private final ArrayList folderListeners = new ArrayList(2); + private final ArrayList messageChangedListeners = new ArrayList(2); + private final ArrayList messageCountListeners = new ArrayList(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; + + /** + * Constructor that initializes the Store. + * + * @param store the store that this folder is part of + */ + protected Folder(Store store) { + this.store = store; + } + + /** + * Return the name of this folder. + * This can be invoked when the folder is closed. + * + * @return this folder's name + */ + public abstract String getName(); + + /** + * Return the full absolute name of this folder. + * This can be invoked when the folder is closed. + * + * @return the full name of this folder + */ + public abstract String getFullName(); + + /** + * Return the URLName for this folder, which includes the location of the store. + * + * @return the URLName for this folder + * @throws MessagingException + */ + public URLName getURLName() throws MessagingException { + URLName baseURL = store.getURLName(); + return new URLName(baseURL.getProtocol(), baseURL.getHost(), baseURL.getPort(), + getFullName(), baseURL.getUsername(), null); + } + + /** + * Return the store that this folder is part of. + * + * @return the store this folder is part of + */ + public Store getStore() { + return store; + } + + /** + * Return the parent for this folder; if the folder is at the root of a heirarchy + * this returns null. + * This can be invoked when the folder is closed. + * + * @return this folder's parent + * @throws MessagingException + */ + public abstract Folder getParent() throws MessagingException; + + /** + * Check to see if this folder physically exists in the store. + * This can be invoked when the folder is closed. + * + * @return true if the folder really exists + * @throws MessagingException if there was a problem accessing the store + */ + public abstract boolean exists() throws MessagingException; + + /** + * Return a list of folders from this Folder's namespace that match the supplied pattern. + * Patterns may contain the following wildcards: + *

+ * This can be invoked when the folder is closed. + * + * @param pattern the pattern to search for + * @return a, possibly empty, array containing Folders that matched the pattern + * @throws MessagingException if there was a problem accessing the store + */ + public abstract Folder[] list(String pattern) throws MessagingException; + + /** + * Return a list of folders to which the user is subscribed and which match the supplied pattern. + * If the store does not support the concept of subscription then this should match against + * all folders; the default implementation of this method achieves this by defaulting to the + * {@link #list(String)} method. + * + * @param pattern the pattern to search for + * @return a, possibly empty, array containing subscribed Folders that matched the pattern + * @throws MessagingException if there was a problem accessing the store + */ + public Folder[] listSubscribed(String pattern) throws MessagingException { + return list(pattern); + } + + /** + * Convenience method that invokes {@link #list(String)} with the pattern "%". + * + * @return a, possibly empty, array of subfolders + * @throws MessagingException if there was a problem accessing the store + */ + public Folder[] list() throws MessagingException { + return list("%"); + } + + /** + * Convenience method that invokes {@link #listSubscribed(String)} with the pattern "%". + * + * @return a, possibly empty, array of subscribed subfolders + * @throws MessagingException if there was a problem accessing the store + */ + public Folder[] listSubscribed() throws MessagingException { + return listSubscribed("%"); + } + + /** + * Return the character used by this folder's Store to separate path components. + * + * @return the name separater character + * @throws MessagingException if there was a problem accessing the store + */ + public abstract char getSeparator() throws MessagingException; + + /** + * Return the type of this folder, indicating whether it can contain subfolders, + * messages, or both. The value returned is a bitmask with the appropriate bits set. + * + * @return the type of this folder + * @throws MessagingException if there was a problem accessing the store + * @see #HOLDS_FOLDERS + * @see #HOLDS_MESSAGES + */ + public abstract int getType() throws MessagingException; + + /** + * Create a new folder capable of containing subfoldera and/or messages as + * determined by the type parameter. Any hierarchy defined by the folder + * name will be recursively created. + * If the folder was sucessfully created, a {@link FolderEvent#CREATED CREATED FolderEvent} + * is sent to all FolderListeners registered with this Folder or with the Store. + * + * @param type the type, indicating if this folder should contain subfolders, messages or both + * @return true if the folder was sucessfully created + * @throws MessagingException if there was a problem accessing the store + */ + public abstract boolean create(int type) throws MessagingException; + + /** + * Determine if the user is subscribed to this Folder. The default implementation in + * this class always returns true. + * + * @return true is the user is subscribed to this Folder + */ + public boolean isSubscribed() { + return true; + } + + /** + * Set the user's subscription to this folder. + * Not all Stores support subscription; the default implementation in this class + * always throws a MethodNotSupportedException + * + * @param subscribed whether to subscribe to this Folder + * @throws MessagingException if there was a problem accessing the store + * @throws MethodNotSupportedException if the Store does not support subscription + */ + public void setSubscribed(boolean subscribed) throws MessagingException { + throw new MethodNotSupportedException(); + } + + /** + * Check to see if this Folder conatins messages with the {@link Flag.RECENT} flag set. + * This can be used when the folder is closed to perform a light-weight check for new mail; + * to perform an incremental check for new mail the folder must be opened. + * + * @return true if the Store has recent messages + * @throws MessagingException if there was a problem accessing the store + */ + public abstract boolean hasNewMessages() throws MessagingException; + + /** + * Get the Folder determined by the supplied name; if the name is relative + * then it is interpreted relative to this folder. This does not check that + * the named folder actually exists. + * + * @param name the name of the folder to return + * @return the named folder + * @throws MessagingException if there was a problem accessing the store + */ + public abstract Folder getFolder(String name) throws MessagingException; + + /** + * Delete this folder and possibly any subfolders. This operation can only be + * performed on a closed folder. + * If recurse is true, then all subfolders are deleted first, then any messages in + * this folder are removed and it is finally deleted; {@link FolderEvent#DELETED} + * events are sent as appropriate. + * If recurse is false, then the behaviour depends on the folder type and store + * implementation as followd: + * + * FolderEvents are sent to all listeners registered with this folder or + * with the Store. + * + * @param recurse whether subfolders should be recursively deleted as well + * @return true if the delete operation succeeds + * @throws MessagingException if there was a problem accessing the store + */ + public abstract boolean delete(boolean recurse) throws MessagingException; + + /** + * Rename this folder; the folder must be closed. + * If the rename is successfull, a {@link FolderEvent#RENAMED} event is sent to + * all listeners registered with this folder or with the store. + * + * @param newName the new name for this folder + * @return true if the rename succeeded + * @throws MessagingException if there was a problem accessing the store + */ + public abstract boolean renameTo(Folder newName) throws MessagingException; + + /** + * Open this folder; the folder must be able to contain messages and + * must currently be closed. If the folder is opened successfully then + * a {@link ConnectionEvent#OPENED} event is sent to listeners registered + * with this Folder. + *

+ * Whether the Store allows multiple connections or if it allows multiple + * writers is implementation defined. + * + * @param mode READ_ONLY or READ_WRITE + * @throws MessagingException if there was a problem accessing the store + */ + public abstract void open(int mode) throws MessagingException; + + /** + * Close this folder; it must already be open. + * A {@link ConnectionEvent#CLOSED} event is sent to all listeners registered + * with this folder. + * + * @param expunge whether to expunge all deleted messages + * @throws MessagingException if there was a problem accessing the store; the folder is still closed + */ + public abstract void close(boolean expunge) throws MessagingException; + + /** + * Indicates that the folder has been opened. + * + * @return true if the folder is open + */ + public abstract boolean isOpen(); + + /** + * Return the mode of this folder ass passed to {@link #open(int)}, or -1 if + * the folder is closed. + * + * @return the mode this folder was opened with + */ + public int getMode() { + return mode; + } + + /** + * Get the flags supported by this folder. + * + * @return the flags supported by this folder, or null if unknown + * @see Flags + */ + public abstract Flags getPermanentFlags(); + + /** + * Return the number of messages this folder contains. + * If this operation is invoked on a closed folder, the implementation + * may choose to return -1 to avoid the expense of opening the folder. + * + * @return the number of messages, or -1 if unknown + * @throws MessagingException if there was a problem accessing the store + */ + public abstract int getMessageCount() throws MessagingException; + + /** + * Return the numbew of messages in this folder that have the {@link Flag.RECENT} flag set. + * If this operation is invoked on a closed folder, the implementation + * may choose to return -1 to avoid the expense of opening the folder. + * The default implmentation of this method iterates over all messages + * in the folder; subclasses should override if possible to provide a more + * efficient implementation. + * + * @return the number of new messages, or -1 if unknown + * @throws MessagingException if there was a problem accessing the store + */ + public int getNewMessageCount() throws MessagingException { + return getCount(Flags.Flag.RECENT, true); + } + + /** + * Return the numbew of messages in this folder that do not have the {@link Flag.SEEN} flag set. + * If this operation is invoked on a closed folder, the implementation + * may choose to return -1 to avoid the expense of opening the folder. + * The default implmentation of this method iterates over all messages + * in the folder; subclasses should override if possible to provide a more + * efficient implementation. + * + * @return the number of new messages, or -1 if unknown + * @throws MessagingException if there was a problem accessing the store + */ + public int getUnreadMessageCount() throws MessagingException { + return getCount(Flags.Flag.SEEN, false); + } + + /** + * Return the numbew of messages in this folder that have the {@link Flag.DELETED} flag set. + * If this operation is invoked on a closed folder, the implementation + * may choose to return -1 to avoid the expense of opening the folder. + * The default implmentation of this method iterates over all messages + * in the folder; subclasses should override if possible to provide a more + * efficient implementation. + * + * @return the number of new messages, or -1 if unknown + * @throws MessagingException if there was a problem accessing the store + */ + public int getDeletedMessageCount() throws MessagingException { + return getCount(Flags.Flag.DELETED, true); + } + + private int getCount(Flag flag, boolean value) throws MessagingException { + if (!isOpen()) { + return -1; + } + Message[] messages = getMessages(); + int total = 0; + for (int i = 0; i < messages.length; i++) { + if (messages[i].getFlags().contains(flag) == value) { + total++; + } + } + return total; + } + + /** + * Retrieve the message with the specified index in this Folder; + * messages indices start at 1 not zero. + * Clients should note that the index for a specific message may change + * if the folder is expunged; {@link Message} objects should be used as + * references instead. + * + * @param index the index of the message to fetch + * @return the message + * @throws MessagingException if there was a problem accessing the store + */ + public abstract Message getMessage(int index) throws MessagingException; + + /** + * Retrieve messages with index between start and end inclusive + * + * @param start index of first message + * @param end index of last message + * @return an array of messages from start to end inclusive + * @throws MessagingException if there was a problem accessing the store + */ + public Message[] getMessages(int start, int end) throws MessagingException { + Message[] result = new Message[end - start + 1]; + for (int i = 0; i < result.length; i++) { + result[i] = getMessage(start++); + } + return result; + } + + /** + * Retrieve messages with the specified indices. + * + * @param ids the indices of the messages to fetch + * @return the specified messages + * @throws MessagingException if there was a problem accessing the store + */ + public Message[] getMessages(int ids[]) throws MessagingException { + Message[] result = new Message[ids.length]; + for (int i = 0; i < ids.length; i++) { + result[i] = getMessage(ids[i]); + } + return result; + } + + /** + * Retrieve all messages. + * + * @return all messages in this folder + * @throws MessagingException if there was a problem accessing the store + */ + public Message[] getMessages() throws MessagingException { + return getMessages(1, getMessageCount()); + } + + /** + * Append the supplied messages to this folder. A {@link MessageCountEvent} is sent + * to all listeners registered with this folder when all messages have been appended. + * If the array contains a previously expunged message, it must be re-appended to the Store + * and implementations must not abort this operation. + * + * @param messages the messages to append + * @throws MessagingException if there was a problem accessing the store + */ + public abstract void appendMessages(Message[] messages) throws MessagingException; + + /** + * Hint to the store to prefetch information on the supplied messaged. + * Subclasses should override this method to provide an efficient implementation; + * the default implementation in this class simply returns. + * + * @param messages messages for which information should be fetched + * @param profile the information to fetch + * @throws MessagingException if there was a problem accessing the store + * @see FetchProfile + */ + public void fetch(Message[] messages, FetchProfile profile) throws MessagingException { + return; + } + + /** + * Set flags on the messages to the supplied value; all messages must belong to this folder. + * This method may be overridden by subclasses that can optimize the setting + * of flags on multiple messages at once; the default implementation simply calls + * {@link Message#setFlags(Flags, boolean)} for each supplied messages. + * + * @param messages whose flags should be set + * @param flags the set of flags to modify + * @param value the value the flags should be set to + * @throws MessagingException if there was a problem accessing the store + */ + public void setFlags(Message[] messages, Flags flags, boolean value) throws MessagingException { + for (int i = 0; i < messages.length; i++) { + Message message = messages[i]; + message.setFlags(flags, value); + } + } + + /** + * Set flags on a range of messages to the supplied value. + * This method may be overridden by subclasses that can optimize the setting + * of flags on multiple messages at once; the default implementation simply + * gets each message and then calls {@link Message#setFlags(Flags, boolean)}. + * + * @param start first message end set + * @param end last message end set + * @param flags the set of flags end modify + * @param value the value the flags should be set end + * @throws MessagingException if there was a problem accessing the store + */ + public void setFlags(int start, int end, Flags flags, boolean value) throws MessagingException { + for (int i = start; i <= end; i++) { + Message message = getMessage(i); + message.setFlags(flags, value); + } + } + + /** + * Set flags on a set of messages to the supplied value. + * This method may be overridden by subclasses that can optimize the setting + * of flags on multiple messages at once; the default implementation simply + * gets each message and then calls {@link Message#setFlags(Flags, boolean)}. + * + * @param ids the indexes of the messages to set + * @param flags the set of flags end modify + * @param value the value the flags should be set end + * @throws MessagingException if there was a problem accessing the store + */ + public void setFlags(int ids[], Flags flags, boolean value) throws MessagingException { + for (int i = 0; i < ids.length; i++) { + Message message = getMessage(ids[i]); + message.setFlags(flags, value); + } + } + + /** + * Copy the specified messages to another folder. + * The default implementation simply appends the supplied messages to the + * target folder using {@link #appendMessages(Message[])}. + * @param messages the messages to copy + * @param folder the folder to copy to + * @throws MessagingException if there was a problem accessing the store + */ + public void copyMessages(Message[] messages, Folder folder) throws MessagingException { + folder.appendMessages(messages); + } + + /** + * Permanently delete all supplied messages that have the DELETED flag set from the Store. + * The original message indices of all messages actually deleted are returned and a + * {@link MessageCountEvent} event is sent to all listeners with this folder. The expunge + * may cause the indices of all messaged that remain in the folder to change. + * + * @return the original indices of messages that were actually deleted + * @throws MessagingException if there was a problem accessing the store + */ + public abstract Message[] expunge() throws MessagingException; + + /** + * Search this folder for messages matching the supplied search criteria. + * The default implementation simply invoke 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: + *

+ * + * @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