add net-mail, based on jakarta mail and eclipse angus mail

This commit is contained in:
Jörg Prante 2024-07-30 17:54:09 +02:00
parent 3189083512
commit ee2b2e7db7
438 changed files with 95445 additions and 3 deletions

View file

@ -1,5 +1,6 @@
dependencies {
testImplementation testLibs.junit.jupiter.api
testImplementation testLibs.junit.jupiter.params
testImplementation testLibs.hamcrest
testRuntimeOnly testLibs.junit.jupiter.engine
testRuntimeOnly testLibs.junit.jupiter.platform.launcher

14
net-mail/build.gradle Normal file
View file

@ -0,0 +1,14 @@
dependencies {
api project(':net-security-auth')
}
def moduleName = 'org.xbib.net.mail.test'
def patchArgs = ['--patch-module', "$moduleName=" + files(sourceSets.test.resources.srcDirs).asPath ]
tasks.named('compileTestJava') {
options.compilerArgs += patchArgs
}
tasks.named('test') {
jvmArgs += patchArgs
}

View file

@ -0,0 +1,219 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
/**
* The ActivationDataFlavor class is similar to the JDK's
* <code>java.awt.datatransfer.DataFlavor</code> class. It allows
* Jakarta Activation to
* set all three values stored by the DataFlavor class via a new
* constructor. It also contains improved MIME parsing in the <code>equals
* </code> method. Except for the improved parsing, its semantics are
* identical to that of the JDK's DataFlavor class.
*/
public class ActivationDataFlavor {
/*
* Raison d'etre:
*
* The DataFlavor class included in JDK 1.1 has several limitations
* including poor MIME type parsing, and the limitation of
* only supporting serialized objects and InputStreams as
* representation objects. This class 'fixes' that.
*/
private final String mimeType;
private MimeType mimeObject = null;
private String humanPresentableName;
private Class<?> representationClass = null;
/**
* Construct an ActivationDataFlavor that represents an arbitrary
* Java object.
* <p>
* The returned ActivationDataFlavor will have the following
* characteristics:
* <p>
* representationClass = representationClass
* mimeType = mimeType
* humanName = humanName
*
* @param representationClass the class used in this ActivationDataFlavor
* @param mimeType the MIME type of the data represented by this class
* @param humanPresentableName the human presentable name of the flavor
*/
public ActivationDataFlavor(Class<?> representationClass,
String mimeType, String humanPresentableName) {
this.mimeType = mimeType;
this.humanPresentableName = humanPresentableName;
this.representationClass = representationClass;
}
/**
* Construct an ActivationDataFlavor that represents a MimeType.
* <p>
* The returned ActivationDataFlavor will have the following
* characteristics:
* <p>
* If the mimeType is "application/x-java-serialized-object;
* class=", the result is the same as calling new
* ActivationDataFlavor(Class.forName()) as above.
* <p>
* otherwise:
* <p>
* representationClass = InputStream<p>
* mimeType = mimeType
*
* @param representationClass the class used in this ActivationDataFlavor
* @param humanPresentableName the human presentable name of the flavor
*/
public ActivationDataFlavor(Class<?> representationClass,
String humanPresentableName) {
this.mimeType = "application/x-java-serialized-object";
this.representationClass = representationClass;
this.humanPresentableName = humanPresentableName;
}
/**
* Construct an ActivationDataFlavor that represents a MimeType.
* <p>
* The returned ActivationDataFlavor will have the following
* characteristics:
* <p>
* If the mimeType is "application/x-java-serialized-object; class=",
* the result is the same as calling new
* ActivationDataFlavor(Class.forName()) as above, otherwise:
* <p>
* representationClass = InputStream<br>
* mimeType = mimeType
*
* @param mimeType the MIME type of the data represented by this class
* @param humanPresentableName the human presentable name of the flavor
*/
public ActivationDataFlavor(String mimeType, String humanPresentableName) {
this.mimeType = mimeType;
try {
this.representationClass = Class.forName("java.io.InputStream");
} catch (ClassNotFoundException ex) {
// XXX - should never happen, ignore it
}
this.humanPresentableName = humanPresentableName;
}
/**
* Return the MIME type for this ActivationDataFlavor.
*
* @return the MIME type
*/
public String getMimeType() {
return mimeType;
}
/**
* Return the representation class.
*
* @return the representation class
*/
public Class<?> getRepresentationClass() {
return representationClass;
}
/**
* Return the Human Presentable name.
*
* @return the human presentable name
*/
public String getHumanPresentableName() {
return humanPresentableName;
}
/**
* Set the human presentable name.
*
* @param humanPresentableName the name to set
*/
public void setHumanPresentableName(String humanPresentableName) {
this.humanPresentableName = humanPresentableName;
}
/**
* Compares the ActivationDataFlavor passed in with this
* ActivationDataFlavor; calls the <code>isMimeTypeEqual</code> method.
*
* @param dataFlavor the ActivationDataFlavor to compare with
* @return true if the MIME type and representation class
* are the same
*/
public boolean equals(ActivationDataFlavor dataFlavor) {
return (isMimeTypeEqual(dataFlavor.mimeType) &&
dataFlavor.getRepresentationClass() == representationClass);
}
/**
* @param o the <code>Object</code> to compare with
* @return true if the object is also an ActivationDataFlavor
* and is equal to this
*/
@Override
public boolean equals(Object o) {
return ((o instanceof ActivationDataFlavor) &&
equals((ActivationDataFlavor) o));
}
/**
* Returns hash code for this <code>ActivationDataFlavor</code>.
* For two equal <code>ActivationDataFlavor</code>s, hash codes are equal.
* For the <code>String</code>
* that matches <code>ActivationDataFlavor.equals(String)</code>, it is not
* guaranteed that <code>ActivationDataFlavor</code>'s hash code is equal
* to the hash code of the <code>String</code>.
*
* @return a hash code for this <code>ActivationDataFlavor</code>
*/
public int hashCode() {
int total = 0;
if (representationClass != null) {
total += representationClass.hashCode();
}
// XXX - MIME type equality is too complicated so we don't
// include it in the hashCode
return total;
}
/**
* Is the string representation of the MIME type passed in equivalent
* to the MIME type of this ActivationDataFlavor. <p>
* <p>
* ActivationDataFlavor delegates the comparison of MIME types to
* the MimeType class included as part of Jakarta Activation.
*
* @param mimeType the MIME type
* @return true if the same MIME type
*/
public boolean isMimeTypeEqual(String mimeType) {
MimeType mt = null;
try {
if (mimeObject == null)
mimeObject = new MimeType(this.mimeType);
mt = new MimeType(mimeType);
} catch (MimeTypeParseException e) {
// something didn't parse, do a crude comparison
return this.mimeType.equalsIgnoreCase(mimeType);
}
return mimeObject.match(mt);
}
}

View file

@ -0,0 +1,170 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* The CommandInfo class is used by CommandMap implementations to
* describe the results of command requests. It provides the requestor
* with both the verb requested, as well as an instance of the
* bean. There is also a method that will return the name of the
* class that implements the command but <i>it is not guaranteed to
* return a valid value</i>. The reason for this is to allow CommandMap
* implmentations that subclass CommandInfo to provide special
* behavior. For example a CommandMap could dynamically generate
* JavaBeans. In this case, it might not be possible to create an
* object with all the correct state information solely from the class
* name.
*/
public class CommandInfo {
private String verb;
private String className;
/**
* The Constructor for CommandInfo.
*
* @param verb The command verb this CommandInfo decribes.
* @param className The command's fully qualified class name.
*/
public CommandInfo(String verb, String className) {
this.verb = verb;
this.className = className;
}
/**
* Return the command verb.
*
* @return the command verb.
*/
public String getCommandName() {
return verb;
}
/**
* Return the command's class name. <i>This method MAY return null in
* cases where a CommandMap subclassed CommandInfo for its
* own purposes.</i> In other words, it might not be possible to
* create the correct state in the command by merely knowing
* its class name. <b>DO NOT DEPEND ON THIS METHOD RETURNING
* A VALID VALUE!</b>
*
* @return The class name of the command, or <i>null</i>
*/
public String getCommandClass() {
return className;
}
/**
* Return the instantiated JavaBean component.
* <p>
* If the current runtime environment supports
* <code>Beans.instantiate</code>,
* use it to instantiate the JavaBeans component. Otherwise, use
* {@link Class#forName(String)} Class.forName}.
* <p>
* The component class needs to be public.
* On Java SE 9 and newer, if the component class is in a named module,
* it needs to be in an exported package.
* <p>
* If the bean implements the <code>jakarta.activation.CommandObject</code>
* interface, call its <code>setCommandContext</code> method.
* <p>
* If the DataHandler parameter is null, then the bean is
* instantiated with no data. NOTE: this may be useful
* if for some reason the DataHandler that is passed in
* throws IOExceptions when this method attempts to
* access its InputStream. It will allow the caller to
* retrieve a reference to the bean if it can be
* instantiated.
* <p>
* If the bean does NOT implement the CommandObject interface,
* this method will check if it implements the
* java.io.Externalizable interface. If it does, the bean's
* readExternal method will be called if an InputStream
* can be acquired from the DataHandler.
*
* @param dh The DataHandler that describes the data to be
* passed to the command.
* @param loader The ClassLoader to be used to instantiate the bean.
* @return The bean
* @throws IOException for failures reading data
* @throws ClassNotFoundException if command object class can't
* be found
* @see CommandObject
*/
public Object getCommandObject(DataHandler dh, ClassLoader loader)
throws IOException, ClassNotFoundException {
Object new_bean = null;
// try to instantiate the bean
new_bean = Beans.instantiate(loader, className);
// if we got one and it is a CommandObject
if (new_bean != null) {
if (new_bean instanceof CommandObject) {
((CommandObject) new_bean).setCommandContext(verb, dh);
}
}
return new_bean;
}
/**
* Helper class to invoke Beans.instantiate reflectively or the equivalent
* with core reflection when module java.desktop is not readable.
*/
private static final class Beans {
static final Method instantiateMethod;
static {
Method m;
try {
Class<?> c = Class.forName("java.beans.Beans");
m = c.getDeclaredMethod("instantiate", ClassLoader.class, String.class);
} catch (ClassNotFoundException | NoSuchMethodException e) {
m = null;
}
instantiateMethod = m;
}
/**
* Equivalent to invoking java.beans.Beans.instantiate(loader, cn)
*/
static Object instantiate(ClassLoader loader, String cn)
throws ClassNotFoundException {
if (instantiateMethod != null) {
// invoke Beans.instantiate
try {
return instantiateMethod.invoke(null, loader, cn);
} catch (InvocationTargetException | IllegalAccessException e) {
//
}
} else {
if (loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
Class<?> beanClass = Class.forName(cn, true, loader);
try {
return beanClass.getDeclaredConstructor().newInstance();
} catch (Exception ex) {
throw new ClassNotFoundException(beanClass + ": " + ex, ex);
}
}
return null;
}
}
}

View file

@ -0,0 +1,204 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.util.Map;
import java.util.WeakHashMap;
/**
* The CommandMap class provides an interface to a registry of
* command objects available in the system.
* Developers are expected to either use the CommandMap
* implementation included with this package (MailcapCommandMap) or
* develop their own. Note that some of the methods in this class are
* abstract.
*/
public abstract class CommandMap {
private static CommandMap defaultCommandMap = null;
private static Map<ClassLoader, CommandMap> map = new WeakHashMap<>();
/**
* Default (empty) constructor.
*/
protected CommandMap() {
}
/**
* Get the default CommandMap.
*
* <ul>
* <li> In cases where a CommandMap instance has been previously set
* to some value (via <i>setDefaultCommandMap</i>)
* return the CommandMap.
* <li>
* In cases where no CommandMap has been set, the CommandMap
* creates an instance of <code>MailcapCommandMap</code> and
* set that to the default, returning its value.
*
* </ul>
*
* @return the CommandMap
*/
public static synchronized CommandMap getDefaultCommandMap() {
if (defaultCommandMap != null)
return defaultCommandMap;
// fetch per-thread-context-class-loader default
ClassLoader tccl = Util.getContextClassLoader();
CommandMap def = map.get(tccl);
if (def == null) {
def = new MailcapCommandMap();
map.put(tccl, def);
}
return def;
}
/**
* Set the default CommandMap. Reset the CommandMap to the default by
* calling this method with <code>null</code>.
*
* @param commandMap The new default CommandMap.
*/
public static synchronized void setDefaultCommandMap(CommandMap commandMap) {
map.remove(Util.getContextClassLoader());
defaultCommandMap = commandMap;
}
/**
* Get the preferred command list from a MIME Type. The actual semantics
* are determined by the implementation of the CommandMap.
*
* @param mimeType the MIME type
* @return the CommandInfo classes that represent the command Beans.
*/
abstract public CommandInfo[] getPreferredCommands(String mimeType);
/**
* Get the preferred command list from a MIME Type. The actual semantics
* are determined by the implementation of the CommandMap. <p>
* <p>
* The <code>DataSource</code> provides extra information, such as
* the file name, that a CommandMap implementation may use to further
* refine the list of commands that are returned. The implementation
* in this class simply calls the <code>getPreferredCommands</code>
* method that ignores this argument.
*
* @param mimeType the MIME type
* @param ds a DataSource for the data
* @return the CommandInfo classes that represent the command Beans.
* @since JAF 1.1
*/
public CommandInfo[] getPreferredCommands(String mimeType, DataSource ds) {
return getPreferredCommands(mimeType);
}
/**
* Get all the available commands for this type. This method
* should return all the possible commands for this MIME type.
*
* @param mimeType the MIME type
* @return the CommandInfo objects representing all the commands.
*/
abstract public CommandInfo[] getAllCommands(String mimeType);
/**
* Get all the available commands for this type. This method
* should return all the possible commands for this MIME type. <p>
* <p>
* The <code>DataSource</code> provides extra information, such as
* the file name, that a CommandMap implementation may use to further
* refine the list of commands that are returned. The implementation
* in this class simply calls the <code>getAllCommands</code>
* method that ignores this argument.
*
* @param mimeType the MIME type
* @param ds a DataSource for the data
* @return the CommandInfo objects representing all the commands.
* @since JAF 1.1
*/
public CommandInfo[] getAllCommands(String mimeType, DataSource ds) {
return getAllCommands(mimeType);
}
/**
* Get the default command corresponding to the MIME type.
*
* @param mimeType the MIME type
* @param cmdName the command name
* @return the CommandInfo corresponding to the command.
*/
abstract public CommandInfo getCommand(String mimeType, String cmdName);
/**
* Get the default command corresponding to the MIME type. <p>
* <p>
* The <code>DataSource</code> provides extra information, such as
* the file name, that a CommandMap implementation may use to further
* refine the command that is chosen. The implementation
* in this class simply calls the <code>getCommand</code>
* method that ignores this argument.
*
* @param mimeType the MIME type
* @param cmdName the command name
* @param ds a DataSource for the data
* @return the CommandInfo corresponding to the command.
* @since JAF 1.1
*/
public CommandInfo getCommand(String mimeType, String cmdName,
DataSource ds) {
return getCommand(mimeType, cmdName);
}
/**
* Locate a DataContentHandler that corresponds to the MIME type.
* The mechanism and semantics for determining this are determined
* by the implementation of the particular CommandMap.
*
* @param mimeType the MIME type
* @return the DataContentHandler for the MIME type
*/
abstract public DataContentHandler createDataContentHandler(String
mimeType);
/**
* Locate a DataContentHandler that corresponds to the MIME type.
* The mechanism and semantics for determining this are determined
* by the implementation of the particular CommandMap. <p>
* <p>
* The <code>DataSource</code> provides extra information, such as
* the file name, that a CommandMap implementation may use to further
* refine the choice of DataContentHandler. The implementation
* in this class simply calls the <code>createDataContentHandler</code>
* method that ignores this argument.
*
* @param mimeType the MIME type
* @param ds a DataSource for the data
* @return the DataContentHandler for the MIME type
* @since JAF 1.1
*/
public DataContentHandler createDataContentHandler(String mimeType,
DataSource ds) {
return createDataContentHandler(mimeType);
}
/**
* Get all the MIME types known to this command map.
* If the command map doesn't support this operation,
* null is returned.
*
* @return array of MIME types as strings, or null if not supported
* @since JAF 1.1
*/
public String[] getMimeTypes() {
return null;
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.io.IOException;
/**
* JavaBeans components that are Jakarta Activation aware implement
* this interface to find out which command verb they're being asked
* to perform, and to obtain the DataHandler representing the
* data they should operate on. JavaBeans that don't implement
* this interface may be used as well. Such commands may obtain
* the data using the Externalizable interface, or using an
* application-specific method.
*/
public interface CommandObject {
/**
* Initialize the Command with the verb it is requested to handle
* and the DataHandler that describes the data it will
* operate on. <b>NOTE:</b> it is acceptable for the caller
* to pass <i>null</i> as the value for <code>DataHandler</code>.
*
* @param verb The Command Verb this object refers to.
* @param dh The DataHandler.
* @throws IOException for failures accessing data
*/
void setCommandContext(String verb, DataHandler dh)
throws IOException;
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.io.IOException;
import java.io.OutputStream;
/**
* The DataContentHandler interface is implemented by objects that can
* be used to extend the capabilities of the DataHandler's implementation
* of the Transferable interface. Through <code>DataContentHandlers</code>
* the framework can be extended to convert streams in to objects, and
* to write objects to streams. <p>
* <p>
* Applications don't generally call the methods in DataContentHandlers
* directly. Instead, an application calls the equivalent methods in
* DataHandler. The DataHandler will attempt to find an appropriate
* DataContentHandler that corresponds to its MIME type using the
* current DataContentHandlerFactory. The DataHandler then calls
* through to the methods in the DataContentHandler.
*/
public interface DataContentHandler {
/**
* Returns an array of ActivationDataFlavor objects indicating the flavors
* the data can be provided in. The array should be ordered according to
* preference for providing the data (from most richly descriptive to
* least descriptive).
*
* @return The ActivationDataFlavors.
*/
ActivationDataFlavor[] getTransferDataFlavors();
/**
* Returns an object which represents the data to be transferred.
* The class of the object returned is defined by the representation class
* of the flavor.
*
* @param df The ActivationDataFlavor representing the requested type.
* @param ds The DataSource representing the data to be converted.
* @return The constructed Object.
* @throws IOException if the handler doesn't
* support the requested flavor
* @throws IOException if the data can't be accessed
*/
Object getTransferData(ActivationDataFlavor df, DataSource ds)
throws IOException;
/**
* Return an object representing the data in its most preferred form.
* Generally this will be the form described by the first
* ActivationDataFlavor returned by the
* <code>getTransferDataFlavors</code> method.
*
* @param ds The DataSource representing the data to be converted.
* @return The constructed Object.
* @throws IOException if the data can't be accessed
*/
Object getContent(DataSource ds) throws IOException;
/**
* Convert the object to a byte stream of the specified MIME type
* and write it to the output stream.
*
* @param obj The object to be converted.
* @param mimeType The requested MIME type of the resulting byte stream.
* @param os The output stream into which to write the converted
* byte stream.
* @throws IOException errors writing to the stream
*/
void writeTo(Object obj, String mimeType, OutputStream os)
throws IOException;
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
/**
* This interface defines a factory for <code>DataContentHandlers</code>. An
* implementation of this interface should map a MIME type into an
* instance of DataContentHandler. The design pattern for classes implementing
* this interface is the same as for the ContentHandler mechanism used in
* <code>java.net.URL</code>.
*/
public interface DataContentHandlerFactory {
/**
* Creates a new DataContentHandler object for the MIME type.
*
* @param mimeType the MIME type to create the DataContentHandler for.
* @return The new <code>DataContentHandler</code>, or <i>null</i>
* if none are found.
*/
DataContentHandler createDataContentHandler(String mimeType);
}

View file

@ -0,0 +1,857 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.URL;
import java.nio.charset.Charset;
/**
* The DataHandler class provides a consistent interface to data
* available in many different sources and formats.
* It manages simple stream to string conversions and related operations
* using DataContentHandlers.
* It provides access to commands that can operate on the data.
* The commands are found using a CommandMap. <p>
*
* <b>DataHandler and CommandMaps</b><p>
* The DataHandler keeps track of the current CommandMap that it uses to
* service requests for commands (<code>getCommand</code>,
* <code>getAllCommands</code>, <code>getPreferredCommands</code>).
* Each instance of a DataHandler may have a CommandMap associated with
* it using the <code>setCommandMap</code> method. If a CommandMap was
* not set, DataHandler calls the <code>getDefaultCommandMap</code>
* method in CommandMap and uses the value it returns. See
* <i>CommandMap</i> for more information. <p>
*
* <b>DataHandler and URLs</b><p>
* The current DataHandler implementation creates a private
* instance of URLDataSource when it is constructed with a URL.
*
* @see CommandMap
* @see DataContentHandler
* @see DataSource
* @see URLDataSource
*/
public class DataHandler {
// our transfer flavors
private static final ActivationDataFlavor[] emptyFlavors =
new ActivationDataFlavor[0];
// our DataContentHandlerFactory
private static DataContentHandlerFactory factory = null;
// Use the datasource to indicate whether we were started via the
// DataSource constructor or the object constructor.
private DataSource dataSource = null;
private DataSource objDataSource = null;
// The Object and mimetype from the constructor (if passed in).
// object remains null if it was instantiated with a
// DataSource.
private Object object = null;
private String objectMimeType = null;
// Keep track of the CommandMap
private CommandMap currentCommandMap = null;
private ActivationDataFlavor[] transferFlavors = emptyFlavors;
// our DataContentHandler
private DataContentHandler dataContentHandler = null;
private DataContentHandler factoryDCH = null;
private DataContentHandlerFactory oldFactory = null;
// the short representation of the ContentType (sans params)
private String shortType = null;
/**
* Create a <code>DataHandler</code> instance referencing the
* specified DataSource. The data exists in a byte stream form.
* The DataSource will provide an InputStream to access the data.
*
* @param ds the DataSource
*/
public DataHandler(DataSource ds) {
// save a reference to the incoming DS
dataSource = ds;
oldFactory = factory; // keep track of the factory
}
/**
* Create a <code>DataHandler</code> instance representing an object
* of this MIME type. This constructor is
* used when the application already has an in-memory representation
* of the data in the form of a Java Object.
*
* @param obj the Java Object
* @param mimeType the MIME type of the object
*/
public DataHandler(Object obj, String mimeType) {
object = obj;
objectMimeType = mimeType;
oldFactory = factory; // keep track of the factory
}
/**
* Create a <code>DataHandler</code> instance referencing a URL.
* The DataHandler internally creates a <code>URLDataSource</code>
* instance to represent the URL.
*
* @param url a URL object
*/
public DataHandler(URL url) {
dataSource = new URLDataSource(url);
oldFactory = factory; // keep track of the factory
}
/**
* Sets the DataContentHandlerFactory. The DataContentHandlerFactory
* is called first to find DataContentHandlers.
* The DataContentHandlerFactory can only be set once.
* <p>
* If the DataContentHandlerFactory has already been set,
* this method throws an Error.
*
* @param newFactory the DataContentHandlerFactory
* @throws Error if the factory has already been defined.
* @see DataContentHandlerFactory
*/
public static synchronized void setDataContentHandlerFactory(
DataContentHandlerFactory newFactory) {
if (factory != null)
throw new Error("DataContentHandlerFactory already defined");
factory = newFactory;
}
/**
* Return the CommandMap for this instance of DataHandler.
*/
private synchronized CommandMap getCommandMap() {
if (currentCommandMap != null)
return currentCommandMap;
else
return CommandMap.getDefaultCommandMap();
}
/**
* Set the CommandMap for use by this DataHandler.
* Setting it to <code>null</code> causes the CommandMap to revert
* to the CommandMap returned by the
* <code>CommandMap.getDefaultCommandMap</code> method.
* Changing the CommandMap, or setting it to <code>null</code>,
* clears out any data cached from the previous CommandMap.
*
* @param commandMap the CommandMap to use in this DataHandler
* @see CommandMap#setDefaultCommandMap
*/
public synchronized void setCommandMap(CommandMap commandMap) {
if (commandMap != currentCommandMap || commandMap == null) {
// clear cached values...
transferFlavors = emptyFlavors;
dataContentHandler = null;
currentCommandMap = commandMap;
}
}
/**
* Return the DataSource associated with this instance
* of DataHandler.
* <p>
* For DataHandlers that have been instantiated with a DataSource,
* this method returns the DataSource that was used to create the
* DataHandler object. In other cases the DataHandler
* constructs a DataSource from the data used to construct
* the DataHandler. DataSources created for DataHandlers <b>not</b>
* instantiated with a DataSource are cached for performance
* reasons.
*
* @return a valid DataSource object for this DataHandler
*/
public DataSource getDataSource() {
if (dataSource == null) {
// create one on the fly
if (objDataSource == null)
objDataSource = new DataHandlerDataSource(this);
return objDataSource;
}
return dataSource;
}
/**
* Return the name of the data object. If this DataHandler
* was created with a DataSource, this method calls through
* to the <code>DataSource.getName</code> method, otherwise it
* returns <i>null</i>.
*
* @return the name of the object
*/
public String getName() {
if (dataSource != null)
return dataSource.getName();
else
return null;
}
/**
* Return the MIME type of this object as retrieved from
* the source object. Note that this is the <i>full</i>
* type with parameters.
*
* @return the MIME type
*/
public String getContentType() {
if (dataSource != null) // data source case
return dataSource.getContentType();
else
return objectMimeType; // obj/type case
}
/**
* Get the InputStream for this object. <p>
* <p>
* For DataHandlers instantiated with a DataSource, the DataHandler
* calls the <code>DataSource.getInputStream</code> method and
* returns the result to the caller.
* <p>
* For DataHandlers instantiated with an Object, the DataHandler
* first attempts to find a DataContentHandler for the Object. If
* the DataHandler can not find a DataContentHandler for this MIME
* type, it throws an UnsupportedDataTypeException. If it is
* successful, it creates a pipe and a thread. The thread uses the
* DataContentHandler's <code>writeTo</code> method to write the
* stream data into one end of the pipe. The other end of the pipe
* is returned to the caller. Because a thread is created to copy
* the data, IOExceptions that may occur during the copy can not be
* propagated back to the caller. The result is an empty stream.
*
* @return the InputStream representing this data
* @throws IOException if an I/O error occurs
* @see DataContentHandler#writeTo
* @see UnsupportedDataTypeException
*/
public InputStream getInputStream() throws IOException {
InputStream ins = null;
if (dataSource != null) {
ins = dataSource.getInputStream();
} else {
DataContentHandler dch = getDataContentHandler();
// we won't even try if we can't get a dch
if (dch == null)
throw new UnsupportedDataTypeException(
"no DCH for MIME type " + getBaseType());
if (dch instanceof ObjectDataContentHandler) {
if (((ObjectDataContentHandler) dch).getDCH() == null)
throw new UnsupportedDataTypeException(
"no object DCH for MIME type " + getBaseType());
}
// there is none but the default^^^^^^^^^^^^^^^^
final DataContentHandler fdch = dch;
// from bill s.
// ce n'est pas une pipe!
//
// NOTE: This block of code needs to throw exceptions, but
// can't because it is in another thread!!! ARG!
//
final PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pin = new PipedInputStream(pos);
new Thread(
new Runnable() {
public void run() {
try {
fdch.writeTo(object, objectMimeType, pos);
} catch (IOException e) {
} finally {
try {
pos.close();
} catch (IOException ie) {
}
}
}
},
"DataHandler.getInputStream").start();
ins = pin;
}
return ins;
}
/**
* Write the data to an <code>OutputStream</code>.<p>
* <p>
* If the DataHandler was created with a DataSource, writeTo
* retrieves the InputStream and copies the bytes from the
* InputStream to the OutputStream passed in.
* <p>
* If the DataHandler was created with an object, writeTo
* retrieves the DataContentHandler for the object's type.
* If the DataContentHandler was found, it calls the
* <code>writeTo</code> method on the <code>DataContentHandler</code>.
*
* @param os the OutputStream to write to
* @throws IOException if an I/O error occurs
*/
public void writeTo(OutputStream os) throws IOException {
// for the DataSource case
if (dataSource != null) {
InputStream is = null;
byte[] data = new byte[8 * 1024];
int bytes_read;
is = dataSource.getInputStream();
try {
while ((bytes_read = is.read(data)) > 0) {
os.write(data, 0, bytes_read);
}
} finally {
is.close();
is = null;
}
} else { // for the Object case
DataContentHandler dch = getDataContentHandler();
dch.writeTo(object, objectMimeType, os);
}
}
/**
* Get an OutputStream for this DataHandler to allow overwriting
* the underlying data.
* If the DataHandler was created with a DataSource, the
* DataSource's <code>getOutputStream</code> method is called.
* Otherwise, <code>null</code> is returned.
*
* @return the OutputStream
* @throws IOException for failures creating the OutputStream
* @see DataSource#getOutputStream
* @see URLDataSource
*/
public OutputStream getOutputStream() throws IOException {
if (dataSource != null)
return dataSource.getOutputStream();
else
return null;
}
/**
* Return the ActivationDataFlavors in which this data is available. <p>
* <p>
* Returns an array of ActivationDataFlavor objects indicating the flavors
* the data can be provided in. The array is usually ordered
* according to preference for providing the data, from most
* richly descriptive to least richly descriptive.<p>
* <p>
* The DataHandler attempts to find a DataContentHandler that
* corresponds to the MIME type of the data. If one is located,
* the DataHandler calls the DataContentHandler's
* <code>getTransferDataFlavors</code> method. <p>
* <p>
* If a DataContentHandler can <i>not</i> be located, and if the
* DataHandler was created with a DataSource (or URL), one
* ActivationDataFlavor is returned that represents this object's MIME type
* and the <code>java.io.InputStream</code> class. If the
* DataHandler was created with an object and a MIME type,
* getTransferDataFlavors returns one ActivationDataFlavor that represents
* this object's MIME type and the object's class.
*
* @return an array of data flavors in which this data can be transferred
* @see DataContentHandler#getTransferDataFlavors
*/
public synchronized ActivationDataFlavor[] getTransferDataFlavors() {
if (factory != oldFactory) // if the factory has changed, clear cache
transferFlavors = emptyFlavors;
// if it's not set, set it...
if (transferFlavors == emptyFlavors)
transferFlavors = getDataContentHandler().getTransferDataFlavors();
if (transferFlavors == emptyFlavors)
return transferFlavors; // no need to clone an empty array
else
return transferFlavors.clone();
}
/**
* Returns whether the specified data flavor is supported
* for this object.<p>
* <p>
* This method iterates through the ActivationDataFlavors returned from
* <code>getTransferDataFlavors</code>, comparing each with
* the specified flavor.
*
* @param flavor the requested flavor for the data
* @return true if the data flavor is supported
* @see DataHandler#getTransferDataFlavors
*/
public boolean isDataFlavorSupported(ActivationDataFlavor flavor) {
ActivationDataFlavor[] lFlavors = getTransferDataFlavors();
for (int i = 0; i < lFlavors.length; i++) {
if (lFlavors[i].equals(flavor))
return true;
}
return false;
}
/**
* Returns an object that represents the data to be
* transferred. The class of the object returned is defined by the
* representation class of the data flavor.<p>
*
* <b>For DataHandler's created with DataSources or URLs:</b><p>
* <p>
* The DataHandler attempts to locate a DataContentHandler
* for this MIME type. If one is found, the passed in ActivationDataFlavor
* and the type of the data are passed to its <code>getTransferData</code>
* method. If the DataHandler fails to locate a DataContentHandler
* and the flavor specifies this object's MIME type and the
* <code>java.io.InputStream</code> class, this object's InputStream
* is returned.
* Otherwise it throws an IOException. <p>
*
* <b>For DataHandler's created with Objects:</b><p>
* <p>
* The DataHandler attempts to locate a DataContentHandler
* for this MIME type. If one is found, the passed in ActivationDataFlavor
* and the type of the data are passed to its getTransferData
* method. If the DataHandler fails to locate a DataContentHandler
* and the flavor specifies this object's MIME type and its class,
* this DataHandler's referenced object is returned.
* Otherwise it throws an IOException.
*
* @param flavor the requested flavor for the data
* @return the object
* @throws IOException if the data could not be
* converted to the requested flavor
* @throws IOException if an I/O error occurs
* @see ActivationDataFlavor
*/
public Object getTransferData(ActivationDataFlavor flavor)
throws IOException {
return getDataContentHandler().getTransferData(flavor, dataSource);
}
/**
* Return the <i>preferred</i> commands for this type of data.
* This method calls the <code>getPreferredCommands</code> method
* in the CommandMap associated with this instance of DataHandler.
* This method returns an array that represents a subset of
* available commands. In cases where multiple commands for the
* MIME type represented by this DataHandler are present, the
* installed CommandMap chooses the appropriate commands.
*
* @return the CommandInfo objects representing the preferred commands
* @see CommandMap#getPreferredCommands
*/
public CommandInfo[] getPreferredCommands() {
if (dataSource != null)
return getCommandMap().getPreferredCommands(getBaseType(),
dataSource);
else
return getCommandMap().getPreferredCommands(getBaseType());
}
/**
* Return all the commands for this type of data.
* This method returns an array containing all commands
* for the type of data represented by this DataHandler. The
* MIME type for the underlying data represented by this DataHandler
* is used to call through to the <code>getAllCommands</code> method
* of the CommandMap associated with this DataHandler.
*
* @return the CommandInfo objects representing all the commands
* @see CommandMap#getAllCommands
*/
public CommandInfo[] getAllCommands() {
if (dataSource != null)
return getCommandMap().getAllCommands(getBaseType(), dataSource);
else
return getCommandMap().getAllCommands(getBaseType());
}
/**
* Get the command <i>cmdName</i>. Use the search semantics as
* defined by the CommandMap installed in this DataHandler. The
* MIME type for the underlying data represented by this DataHandler
* is used to call through to the <code>getCommand</code> method
* of the CommandMap associated with this DataHandler.
*
* @param cmdName the command name
* @return the CommandInfo corresponding to the command
* @see CommandMap#getCommand
*/
public CommandInfo getCommand(String cmdName) {
if (dataSource != null)
return getCommandMap().getCommand(getBaseType(), cmdName,
dataSource);
else
return getCommandMap().getCommand(getBaseType(), cmdName);
}
/**
* Return the data in its preferred Object form. <p>
* <p>
* If the DataHandler was instantiated with an object, return
* the object. <p>
* <p>
* If the DataHandler was instantiated with a DataSource,
* this method uses a DataContentHandler to return the content
* object for the data represented by this DataHandler. If no
* <code>DataContentHandler</code> can be found for the
* the type of this data, the DataHandler returns an
* InputStream for the data.
*
* @return the content.
* @throws IOException if an IOException occurs during
* this operation.
*/
public Object getContent() throws IOException {
if (object != null)
return object;
else
return getDataContentHandler().getContent(getDataSource());
}
/**
* A convenience method that takes a CommandInfo object
* and instantiates the corresponding command, usually
* a JavaBean component.
* <p>
* This method calls the CommandInfo's <code>getCommandObject</code>
* method with the <code>ClassLoader</code> used to load
* the <code>jakarta.activation.DataHandler</code> class itself.
*
* @param cmdinfo the CommandInfo corresponding to a command
* @return the instantiated command object
*/
public Object getBean(CommandInfo cmdinfo) {
Object bean = null;
try {
// make the bean
ClassLoader cld = null;
// First try the "application's" class loader.
cld = Util.getContextClassLoader();
if (cld == null)
cld = this.getClass().getClassLoader();
bean = cmdinfo.getCommandObject(this, cld);
} catch (IOException | ClassNotFoundException e) {
}
return bean;
}
/**
* Get the DataContentHandler for this DataHandler: <p>
* <p>
* If a DataContentHandlerFactory is set, use it.
* Otherwise look for an object to serve DCH in the
* following order: <p>
* <p>
* 1) if a factory is set, use it <p>
* 2) if a CommandMap is set, use it <p>
* 3) use the default CommandMap <p>
* <p>
* In any case, wrap the real DataContentHandler with one of our own
* to handle any missing cases, fill in defaults, and to ensure that
* we always have a non-null DataContentHandler.
*
* @return the requested DataContentHandler
*/
private synchronized DataContentHandler getDataContentHandler() {
// make sure the factory didn't change
if (factory != oldFactory) {
oldFactory = factory;
factoryDCH = null;
dataContentHandler = null;
transferFlavors = emptyFlavors;
}
if (dataContentHandler != null)
return dataContentHandler;
String simpleMT = getBaseType();
if (factoryDCH == null && factory != null)
factoryDCH = factory.createDataContentHandler(simpleMT);
if (factoryDCH != null)
dataContentHandler = factoryDCH;
if (dataContentHandler == null) {
if (dataSource != null)
dataContentHandler = getCommandMap().
createDataContentHandler(simpleMT, dataSource);
else
dataContentHandler = getCommandMap().
createDataContentHandler(simpleMT);
}
// getDataContentHandler always uses these 'wrapper' handlers
// to make sure it returns SOMETHING meaningful...
if (dataSource != null)
dataContentHandler = new DataSourceDataContentHandler(
dataContentHandler,
dataSource);
else
dataContentHandler = new ObjectDataContentHandler(
dataContentHandler,
object,
objectMimeType);
return dataContentHandler;
}
/**
* Use the MimeType class to extract the MIME type/subtype,
* ignoring the parameters. The type is cached.
*/
private synchronized String getBaseType() {
if (shortType == null) {
String ct = getContentType();
try {
MimeType mt = new MimeType(ct);
shortType = mt.getBaseType();
} catch (MimeTypeParseException e) {
shortType = ct;
}
}
return shortType;
}
}
/**
* The DataHanderDataSource class implements the
* DataSource interface when the DataHandler is constructed
* with an Object and a mimeType string.
*/
class DataHandlerDataSource implements DataSource {
DataHandler dataHandler = null;
/**
* The constructor.
*/
public DataHandlerDataSource(DataHandler dh) {
this.dataHandler = dh;
}
/**
* Returns an <code>InputStream</code> representing this object.
*
* @return the <code>InputStream</code>
*/
public InputStream getInputStream() throws IOException {
return dataHandler.getInputStream();
}
/**
* Returns the <code>OutputStream</code> for this object.
*
* @return the <code>OutputStream</code>
*/
public OutputStream getOutputStream() throws IOException {
return dataHandler.getOutputStream();
}
/**
* Returns the MIME type of the data represented by this object.
*
* @return the MIME type
*/
public String getContentType() {
return dataHandler.getContentType();
}
/**
* Returns the name of this object.
*
* @return the name of this object
*/
public String getName() {
return dataHandler.getName(); // what else would it be?
}
}
/*
* DataSourceDataContentHandler
*
* This is a <i>private</i> DataContentHandler that wraps the real
* DataContentHandler in the case where the DataHandler was instantiated
* with a DataSource.
*/
class DataSourceDataContentHandler implements DataContentHandler {
private DataSource ds = null;
private ActivationDataFlavor[] transferFlavors = null;
private DataContentHandler dch = null;
/**
* The constructor.
*/
public DataSourceDataContentHandler(DataContentHandler dch, DataSource ds) {
this.ds = ds;
this.dch = dch;
}
/**
* Return the ActivationDataFlavors for this
* <code>DataContentHandler</code>.
*
* @return the ActivationDataFlavors
*/
public ActivationDataFlavor[] getTransferDataFlavors() {
if (transferFlavors == null) {
if (dch != null) { // is there a dch?
transferFlavors = dch.getTransferDataFlavors();
} else {
transferFlavors = new ActivationDataFlavor[1];
transferFlavors[0] =
new ActivationDataFlavor(ds.getContentType(),
ds.getContentType());
}
}
return transferFlavors;
}
/**
* Return the Transfer Data of type ActivationDataFlavor from InputStream.
*
* @param df the ActivationDataFlavor
* @param ds the DataSource
* @return the constructed Object
*/
public Object getTransferData(ActivationDataFlavor df, DataSource ds)
throws IOException {
if (dch != null)
return dch.getTransferData(df, ds);
else if (df.equals(getTransferDataFlavors()[0])) // only have one now
return ds.getInputStream();
else
throw new IOException("Unsupported DataFlavor: " + df);
}
public Object getContent(DataSource ds) throws IOException {
if (dch != null)
return dch.getContent(ds);
else
return ds.getInputStream();
}
/**
* Write the object to the output stream.
*/
public void writeTo(Object obj, String mimeType, OutputStream os)
throws IOException {
if (dch != null)
dch.writeTo(obj, mimeType, os);
else
throw new UnsupportedDataTypeException(
"no DCH for content type " + ds.getContentType());
}
}
/*
* ObjectDataContentHandler
*
* This is a <i>private</i> DataContentHandler that wraps the real
* DataContentHandler in the case where the DataHandler was instantiated
* with an object.
*/
class ObjectDataContentHandler implements DataContentHandler {
private ActivationDataFlavor[] transferFlavors = null;
private Object obj;
private String mimeType;
private DataContentHandler dch = null;
/**
* The constructor.
*/
public ObjectDataContentHandler(DataContentHandler dch,
Object obj, String mimeType) {
this.obj = obj;
this.mimeType = mimeType;
this.dch = dch;
}
/**
* Return the DataContentHandler for this object.
* Used only by the DataHandler class.
*/
public DataContentHandler getDCH() {
return dch;
}
/**
* Return the ActivationDataFlavors for this
* <code>DataContentHandler</code>.
*
* @return the ActivationDataFlavors
*/
public synchronized ActivationDataFlavor[] getTransferDataFlavors() {
if (transferFlavors == null) {
if (dch != null) {
transferFlavors = dch.getTransferDataFlavors();
} else {
transferFlavors = new ActivationDataFlavor[1];
transferFlavors[0] = new ActivationDataFlavor(obj.getClass(),
mimeType, mimeType);
}
}
return transferFlavors;
}
/**
* Return the Transfer Data of type ActivationDataFlavor from InputStream.
*
* @param df the ActivationDataFlavor
* @param ds the DataSource
* @return the constructed Object
*/
public Object getTransferData(ActivationDataFlavor df, DataSource ds)
throws IOException {
if (dch != null)
return dch.getTransferData(df, ds);
else if (df.equals(getTransferDataFlavors()[0])) // only have one now
return obj;
else
throw new IOException("Unsupported DataFlavor: " + df);
}
public Object getContent(DataSource ds) {
return obj;
}
/**
* Write the object to the output stream.
*/
public void writeTo(Object obj, String mimeType, OutputStream os)
throws IOException {
if (dch != null)
dch.writeTo(obj, mimeType, os);
else if (obj instanceof byte[])
os.write((byte[]) obj);
else if (obj instanceof String) {
OutputStreamWriter osw = new OutputStreamWriter(os, Charset.defaultCharset());
osw.write((String) obj);
osw.flush();
} else
throw new UnsupportedDataTypeException(
"no object DCH for MIME type " + this.mimeType);
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* The DataSource interface provides Jakarta Activation
* with an abstraction of an arbitrary collection of data. It
* provides a type for that data as well as access
* to it in the form of <code>InputStreams</code> and
* <code>OutputStreams</code> where appropriate.
*/
public interface DataSource {
/**
* This method returns an <code>InputStream</code> representing
* the data and throws the appropriate exception if it can
* not do so. Note that a new <code>InputStream</code> object must be
* returned each time this method is called, and the stream must be
* positioned at the beginning of the data.
*
* @return an InputStream
* @throws IOException for failures creating the InputStream
*/
InputStream getInputStream() throws IOException;
/**
* This method returns an <code>OutputStream</code> where the
* data can be written and throws the appropriate exception if it can
* not do so. Note that a new <code>OutputStream</code> object must
* be returned each time this method is called, and the stream must
* be positioned at the location the data is to be written.
*
* @return an OutputStream
* @throws IOException for failures creating the OutputStream
*/
OutputStream getOutputStream() throws IOException;
/**
* This method returns the MIME type of the data in the form of a
* string. It should always return a valid type. It is suggested
* that getContentType return "application/octet-stream" if the
* DataSource implementation can not determine the data type.
*
* @return the MIME Type
*/
String getContentType();
/**
* Return the <i>name</i> of this object where the name of the object
* is dependant on the nature of the underlying objects. DataSources
* encapsulating files may choose to return the filename of the object.
* (Typically this would be the last component of the filename, not an
* entire pathname.)
*
* @return the name of the object.
*/
String getName();
}

View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2021, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
class FactoryFinder {
private static final Logger logger = Logger.getLogger("jakarta.activation");
private static final ServiceLoaderUtil.ExceptionHandler<RuntimeException> EXCEPTION_HANDLER =
new ServiceLoaderUtil.ExceptionHandler<>() {
@Override
public RuntimeException createException(Throwable throwable, String message) {
return new IllegalStateException(message, throwable);
}
};
/**
* Finds the implementation {@code Class} object for the given
* factory type.
* <p>
* This method is package private so that this code can be shared.
*
* @param factoryClass factory abstract class or interface to be found
* @return the {@code Class} object of the specified message factory;
* may not be {@code null}
* @throws IllegalStateException if there is no factory found
*/
static <T> T find(Class<T> factoryClass) throws RuntimeException {
for (ClassLoader l : getClassLoaders(Thread.class, FactoryFinder.class, System.class)) {
T f = find(factoryClass, l);
if (f != null) {
return f;
}
}
throw EXCEPTION_HANDLER.createException(null, "Provider for " + factoryClass.getName() + " cannot be found");
}
static <T> T find(Class<T> factoryClass, ClassLoader loader) throws RuntimeException {
String className = fromSystemProperty(factoryClass.getName());
if (className != null) {
T result = newInstance(className, factoryClass, loader);
if (result != null) {
return result;
}
}
return ServiceLoaderUtil.firstByServiceLoader(factoryClass, loader, logger, EXCEPTION_HANDLER);
}
private static <T> T newInstance(String className,
Class<? extends T> service, ClassLoader loader)
throws RuntimeException {
return ServiceLoaderUtil.newInstance(className, service, loader, EXCEPTION_HANDLER);
}
private static String fromSystemProperty(String factoryId) {
return getSystemProperty(factoryId);
}
private static String getSystemProperty(final String property) {
logger.log(Level.FINE, "Checking system property {0}", property);
String value = System.getProperty(property);
logFound(value);
return value;
}
private static void logFound(String value) {
if (value != null) {
logger.log(Level.FINE, " found {0}", value);
} else {
logger.log(Level.FINE, " not found");
}
}
private static ClassLoader[] getClassLoaders(final Class<?>... classes) {
ClassLoader[] loaders = new ClassLoader[classes.length];
int w = 0;
for (Class<?> k : classes) {
ClassLoader cl = null;
if (k == Thread.class) {
cl = Thread.currentThread().getContextClassLoader();
} else if (k == System.class) {
cl = ClassLoader.getSystemClassLoader();
} else {
cl = k.getClassLoader();
}
if (cl != null) {
loaders[w++] = cl;
}
}
if (loaders.length != w) {
loaders = Arrays.copyOf(loaders, w);
}
return loaders;
}
}

View file

@ -0,0 +1,167 @@
/*
* Copyright (c) 1997, 2023, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* The FileDataSource class implements a simple DataSource object
* that encapsulates a file. It provides data typing services via
* a FileTypeMap object. <p>
*
* <b>FileDataSource Typing Semantics</b><p>
* <p>
* The FileDataSource class delegates data typing of files
* to an object subclassed from the FileTypeMap class.
* The <code>setFileTypeMap</code> method can be used to explicitly
* set the FileTypeMap for an instance of FileDataSource. If no
* FileTypeMap is set, the FileDataSource will call the FileTypeMap's
* getDefaultFileTypeMap method to get the System's default FileTypeMap.
* <p>
* <b>API Note:</b>
* It is recommended to construct a {@code FileDataSource} using a {@code Path}
* instead of using a {@code File} since {@code Path} contains enhanced functionality.
*
* @see DataSource
* @see FileTypeMap
* @see MimetypesFileTypeMap
*/
public class FileDataSource implements DataSource {
// keep track of original 'ref' passed in, non-null
// one indicated which was passed in:
private Path _path = null;
private FileTypeMap typeMap = null;
/**
* Creates a FileDataSource from a File object. <i>Note:
* The file will not actually be opened until a method is
* called that requires the file to be opened.</i>
* <p>
* <b>API Note:</b>
* {@code FileDataSource(Path)} constructor should be preferred over this one.
*
* @param file the file
*/
public FileDataSource(File file) {
_path = file.toPath(); // save the file Object...
}
/**
* Creates a FileDataSource from a Path object. <i>Note: The file will not
* actually be opened until a method is called that requires the file to be
* opened.</i>
*
* @param path the file
*/
public FileDataSource(Path path) {
_path = path;
}
/**
* Creates a FileDataSource from
* the specified path name. <i>Note:
* The file will not actually be opened until a method is
* called that requires the file to be opened.</i>
*
* @param name the system-dependent file name.
*/
public FileDataSource(String name) {
this(Paths.get(name)); // use the file constructor
}
/**
* This method will return an InputStream representing the
* the data and will throw an IOException if it can
* not do so. This method will return a new
* instance of InputStream with each invocation.
*
* @return an InputStream
*/
public InputStream getInputStream() throws IOException {
return Files.newInputStream(_path);
}
/**
* This method will return an OutputStream representing the
* the data and will throw an IOException if it can
* not do so. This method will return a new instance of
* OutputStream with each invocation.
*
* @return an OutputStream
*/
public OutputStream getOutputStream() throws IOException {
return Files.newOutputStream(_path);
}
/**
* This method returns the MIME type of the data in the form of a
* string. This method uses the currently installed FileTypeMap. If
* there is no FileTypeMap explicitly set, the FileDataSource will
* call the <code>getDefaultFileTypeMap</code> method on
* FileTypeMap to acquire a default FileTypeMap. <i>Note: By
* default, the FileTypeMap used will be a MimetypesFileTypeMap.</i>
*
* @return the MIME Type
* @see FileTypeMap#getDefaultFileTypeMap
*/
public String getContentType() {
// check to see if the type map is null?
if (typeMap == null)
return FileTypeMap.getDefaultFileTypeMap().getContentType(_path);
else
return typeMap.getContentType(_path);
}
/**
* Return the <i>name</i> of this object. The FileDataSource
* will return the file name of the object.
*
* @return the name of the object.
* @see DataSource
*/
public String getName() {
return _path.getFileName().toString();
}
/**
* Return the File object that corresponds to this FileDataSource.
*
* @return the File object for the file represented by this object.
*/
public File getFile() {
return _path.toFile();
}
/**
* Return the Path object that corresponds to this FileDataSource.
*
* @return the Path object for the file represented by this object.
*/
public Path getPath() {
return _path;
}
/**
* Set the FileTypeMap to use with this FileDataSource
*
* @param map The FileTypeMap for this object.
*/
public void setFileTypeMap(FileTypeMap map) {
typeMap = map;
}
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (c) 1997, 2023, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.io.File;
import java.nio.file.Path;
import java.util.Map;
import java.util.WeakHashMap;
/**
* The FileTypeMap is an abstract class that provides a data typing
* interface for files. Implementations of this class will
* implement the getContentType methods which will derive a content
* type from a file name or a File object. FileTypeMaps could use any
* scheme to determine the data type, from examining the file extension
* of a file (like the MimetypesFileTypeMap) to opening the file and
* trying to derive its type from the contents of the file. The
* FileDataSource class uses the default FileTypeMap (a MimetypesFileTypeMap
* unless changed) to determine the content type of files.
*
* @see FileTypeMap
* @see FileDataSource
* @see MimetypesFileTypeMap
*/
public abstract class FileTypeMap {
private static FileTypeMap defaultMap = null;
private static Map<ClassLoader, FileTypeMap> map = new WeakHashMap<>();
/**
* The default constructor.
*/
public FileTypeMap() {
super();
}
/**
* Return the default FileTypeMap for the system.
* If setDefaultFileTypeMap was called, return
* that instance, otherwise return an instance of
* <code>MimetypesFileTypeMap</code>.
*
* @return The default FileTypeMap
* @see FileTypeMap#setDefaultFileTypeMap
*/
public static synchronized FileTypeMap getDefaultFileTypeMap() {
if (defaultMap != null) {
return defaultMap;
}
ClassLoader tccl = Util.getContextClassLoader();
FileTypeMap def = map.get(tccl);
if (def == null) {
def = new MimetypesFileTypeMap();
map.put(tccl, def);
}
return def;
}
/**
* Sets the default FileTypeMap for the system. This instance
* will be returned to callers of getDefaultFileTypeMap.
*
* @param fileTypeMap The FileTypeMap.
*/
public static synchronized void setDefaultFileTypeMap(FileTypeMap fileTypeMap) {
map.remove(Util.getContextClassLoader());
defaultMap = fileTypeMap;
}
/**
* Return the type of the file object. This method should
* always return a valid MIME type.
*
* @param file A file to be typed.
* @return The content type.
*/
abstract public String getContentType(File file);
/**
* Return the type of the file Path object. This method should
* always return a valid MIME type.
*
* @param path A file Path to be typed.
* @return The content type.
*/
abstract public String getContentType(Path path);
/**
* Return the type of the file passed in. This method should
* always return a valid MIME type.
*
* @param filename the pathname of the file.
* @return The content type.
*/
abstract public String getContentType(String filename);
}

View file

@ -0,0 +1,667 @@
/*
* Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import jakarta.activation.spi.MailcapRegistryProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.ServiceConfigurationError;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* MailcapCommandMap extends the CommandMap
* abstract class. It implements a CommandMap whose configuration
* is based on mailcap files
* (<A HREF="http://www.ietf.org/rfc/rfc1524.txt">RFC 1524</A>).
* The MailcapCommandMap can be configured both programmatically
* and via configuration files.
* <p>
* <b>Mailcap file search order:</b><p>
* The MailcapCommandMap looks in various places in the user's
* system for mailcap file entries. When requests are made
* to search for commands in the MailcapCommandMap, it searches
* mailcap files in the following order:
* <ol>
* <li> Programatically added entries to the MailcapCommandMap instance.
* <li> The file <code>.mailcap</code> in the user's home directory.
* <li> The file <code>mailcap</code> in the Java runtime.
* <li> The file or resources named <code>META-INF/mailcap</code>.
* <li> The file or resource named <code>META-INF/mailcap.default</code>
* (usually found only in the <code>activation.jar</code> file).
* </ol>
* <p>
* (The current implementation looks for the <code>mailcap</code> file
* in the Java runtime in the directory <code><i>java.home</i>/conf</code>
* if it exists, and otherwise in the directory
* <code><i>java.home</i>/lib</code>, where <i>java.home</i> is the value
* of the "java.home" System property. Note that the "conf" directory was
* introduced in JDK 9.)
* <p>
* <b>Mailcap file format:</b><p>
* <p>
* Mailcap files must conform to the mailcap
* file specification (RFC 1524, <i>A User Agent Configuration Mechanism
* For Multimedia Mail Format Information</i>).
* The file format consists of entries corresponding to
* particular MIME types. In general, the specification
* specifies <i>applications</i> for clients to use when they
* themselves cannot operate on the specified MIME type. The
* MailcapCommandMap extends this specification by using a parameter mechanism
* in mailcap files that allows JavaBeans(tm) components to be specified as
* corresponding to particular commands for a MIME type.<p>
* <p>
* When a mailcap file is
* parsed, the MailcapCommandMap recognizes certain parameter signatures,
* specifically those parameter names that begin with <code>x-java-</code>.
* The MailcapCommandMap uses this signature to find
* command entries for inclusion into its registries.
* Parameter names with the form <code>x-java-&lt;name&gt;</code>
* are read by the MailcapCommandMap as identifying a command
* with the name <i>name</i>. When the <i>name</i> is <code>
* content-handler</code> the MailcapCommandMap recognizes the class
* signified by this parameter as a <i>DataContentHandler</i>.
* All other commands are handled generically regardless of command
* name. The command implementation is specified by a fully qualified
* class name of a JavaBean(tm) component. For example; a command for viewing
* some data can be specified as: <code>x-java-view=com.foo.ViewBean</code>.<p>
* <p>
* When the command name is <code>fallback-entry</code>, the value of
* the command may be <code>true</code> or <code>false</code>. An
* entry for a MIME type that includes a parameter of
* <code>x-java-fallback-entry=true</code> defines fallback commands
* for that MIME type that will only be used if no non-fallback entry
* can be found. For example, an entry of the form <code>text/*; ;
* x-java-fallback-entry=true; x-java-view=com.sun.TextViewer</code>
* specifies a view command to be used for any text MIME type. This
* view command would only be used if a non-fallback view command for
* the MIME type could not be found.<p>
* <p>
* MailcapCommandMap aware mailcap files have the
* following general form:<p>
* <code>
* # Comments begin with a '#' and continue to the end of the line.<br>
* &lt;mime type&gt;; ; &lt;parameter list&gt;<br>
* # Where a parameter list consists of one or more parameters,<br>
* # where parameters look like: x-java-view=com.sun.TextViewer<br>
* # and a parameter list looks like: <br>
* text/plain; ; x-java-view=com.sun.TextViewer; x-java-edit=com.sun.TextEdit
* <br>
* # Note that mailcap entries that do not contain 'x-java' parameters<br>
* # and comply to RFC 1524 are simply ignored:<br>
* image/gif; /usr/dt/bin/sdtimage %s<br>
*
* </code>
*
* @author Bart Calder
* @author Bill Shannon
*/
public class MailcapCommandMap extends CommandMap {
private static final Logger logger = Logger.getLogger(MailcapCommandMap.class.getName());
private static final int PROG = 0; // programmatically added entries
private static final String confDir;
static {
String dir = null;
String home = System.getProperty("java.home");
String newdir = home + File.separator + "conf";
File conf = new File(newdir);
if (conf.exists())
dir = newdir + File.separator;
else
dir = home + File.separator + "lib" + File.separator;
confDir = dir;
}
/*
* We manage a collection of databases, searched in order.
*/
private MailcapRegistry[] DB;
/**
* The default Constructor.
*/
public MailcapCommandMap() {
super();
List<MailcapRegistry> dbv = new ArrayList<>(5); // usually 5 or less databases
MailcapRegistry mf = null;
dbv.add(null); // place holder for PROG entry
logger.log(Level.FINE, "MailcapCommandMap: load HOME");
String user_home = System.getProperty("user.home");
if (user_home != null) {
String path = user_home + File.separator + ".mailcap";
mf = loadFile(path);
if (mf != null)
dbv.add(mf);
}
logger.log(Level.FINE, "MailcapCommandMap: load SYS");
// check system's home
if (confDir != null) {
mf = loadFile(confDir + "mailcap");
if (mf != null)
dbv.add(mf);
}
logger.log(Level.FINE, "MailcapCommandMap: load JAR");
// load from the app's jar file
loadAllResources(dbv, "META-INF/mailcap");
logger.log(Level.FINE, "MailcapCommandMap: load DEF");
mf = loadResource("/META-INF/mailcap.default");
if (mf != null)
dbv.add(mf);
DB = new MailcapRegistry[dbv.size()];
DB = dbv.toArray(DB);
}
/**
* Constructor that allows the caller to specify the path
* of a <i>mailcap</i> file.
*
* @param fileName The name of the <i>mailcap</i> file to open
* @throws IOException if the file can't be accessed
*/
public MailcapCommandMap(String fileName) throws IOException {
this();
if (DB[PROG] == null) {
try {
DB[PROG] = getImplementation().getByFileName(fileName);
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
String message = "Cannot find or load an implementation for MailcapRegistryProvider. " +
"MailcapRegistry: can't load " + fileName;
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, message, e);
}
throw new IOException(message, e);
}
}
if (DB[PROG] != null && logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "MailcapCommandMap: load PROG from " + fileName);
}
}
/**
* Constructor that allows the caller to specify an <i>InputStream</i>
* containing a mailcap file.
*
* @param is InputStream of the <i>mailcap</i> file to open
*/
public MailcapCommandMap(InputStream is) {
this();
if (DB[PROG] == null) {
try {
DB[PROG] = getImplementation().getByInputStream(is);
} catch (IOException ex) {
// XXX - should throw it
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Cannot find or load an implementation for MailcapRegistryProvider." +
"MailcapRegistry: can't load InputStream", e);
}
}
}
if (DB[PROG] != null && logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "MailcapCommandMap: load PROG");
}
}
/**
* Load from the named resource.
*/
private MailcapRegistry loadResource(String name) {
try (InputStream clis = Util.getResourceAsStream(this.getClass(), name)) {
if (clis != null) {
MailcapRegistry mf = getImplementation().getByInputStream(clis);
logger.log(Level.FINE, "MailcapCommandMap: successfully loaded " +
"mailcap file: " + name);
return mf;
} else {
logger.log(Level.FINE, "MailcapCommandMap: not loading " +
"mailcap file: " + name);
}
} catch (IOException e) {
logger.log(Level.FINE, "MailcapCommandMap: can't load " + name, e);
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
logger.log(Level.FINE, "Cannot find or load an implementation for MailcapRegistryProvider. " +
"MailcapRegistry: can't load " + name, e);
}
return null;
}
/**
* Load all of the named resource.
*/
private void loadAllResources(List<MailcapRegistry> v, String name) {
boolean anyLoaded = false;
try {
URL[] urls;
ClassLoader cld = null;
// First try the "application's" class loader.
cld = Util.getContextClassLoader();
if (cld == null)
cld = this.getClass().getClassLoader();
if (cld != null)
urls = Util.getResources(cld, name);
else
urls = Util.getSystemResources(name);
if (urls != null) {
logger.log(Level.FINE, "MailcapCommandMap: getResources");
for (int i = 0; i < urls.length; i++) {
URL url = urls[i];
logger.log(Level.FINE, "MailcapCommandMap: URL " + url);
try (InputStream clis = Util.openStream(url)) {
if (clis != null) {
v.add(getImplementation().getByInputStream(clis));
anyLoaded = true;
logger.log(Level.FINE, "MailcapCommandMap: " +
"successfully loaded " +
"mailcap file from URL: " +
url);
} else {
logger.log(Level.FINE, "MailcapCommandMap: " +
"not loading mailcap " +
"file from URL: " + url);
}
} catch (IOException ioex) {
logger.log(Level.FINE, "MailcapCommandMap: can't load " +
url, ioex);
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
logger.log(Level.FINE, "Cannot find or load an implementation for MailcapRegistryProvider. " +
"MailcapRegistry: can't load " + name, e);
}
}
}
} catch (Exception ex) {
logger.log(Level.FINE, "MailcapCommandMap: can't load " + name, ex);
}
// if failed to load anything, fall back to old technique, just in case
if (!anyLoaded) {
logger.log(Level.FINE, "MailcapCommandMap: !anyLoaded");
MailcapRegistry mf = loadResource("/" + name);
if (mf != null)
v.add(mf);
}
}
/**
* Load from the named file.
*/
private MailcapRegistry loadFile(String name) {
MailcapRegistry mtf = null;
try {
mtf = getImplementation().getByFileName(name);
} catch (IOException e) {
logger.log(Level.FINE, "MailcapRegistry: can't load from file - " + name, e);
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
logger.log(Level.FINE, "Cannot find or load an implementation for MailcapRegistryProvider. " +
"MailcapRegistry: can't load " + name, e);
}
return mtf;
}
/**
* Get the preferred command list for a MIME Type. The MailcapCommandMap
* searches the mailcap files as described above under
* <i>Mailcap file search order</i>.<p>
* <p>
* The result of the search is a proper subset of available
* commands in all mailcap files known to this instance of
* MailcapCommandMap. The first entry for a particular command
* is considered the preferred command.
*
* @param mimeType the MIME type
* @return the CommandInfo objects representing the preferred commands.
*/
public synchronized CommandInfo[] getPreferredCommands(String mimeType) {
List<CommandInfo> cmdList = new ArrayList<>();
if (mimeType != null)
mimeType = mimeType.toLowerCase(Locale.ENGLISH);
for (int i = 0; i < DB.length; i++) {
if (DB[i] == null)
continue;
Map<String, List<String>> cmdMap = DB[i].getMailcapList(mimeType);
if (cmdMap != null)
appendPrefCmdsToList(cmdMap, cmdList);
}
// now add the fallback commands
for (int i = 0; i < DB.length; i++) {
if (DB[i] == null)
continue;
Map<String, List<String>> cmdMap = DB[i].getMailcapFallbackList(mimeType);
if (cmdMap != null)
appendPrefCmdsToList(cmdMap, cmdList);
}
CommandInfo[] cmdInfos = new CommandInfo[cmdList.size()];
cmdInfos = cmdList.toArray(cmdInfos);
return cmdInfos;
}
/**
* Put the commands that are in the hash table, into the list.
*/
private void appendPrefCmdsToList(Map<String, List<String>> cmdHash, List<CommandInfo> cmdList) {
Iterator<String> verb_enum = cmdHash.keySet().iterator();
while (verb_enum.hasNext()) {
String verb = verb_enum.next();
if (!checkForVerb(cmdList, verb)) {
List<String> cmdList2 = cmdHash.get(verb); // get the list
String className = cmdList2.get(0);
cmdList.add(new CommandInfo(verb, className));
}
}
}
/**
* Check the cmdList to see if this command exists, return
* true if the verb is there.
*/
private boolean checkForVerb(List<CommandInfo> cmdList, String verb) {
Iterator<CommandInfo> ee = cmdList.iterator();
while (ee.hasNext()) {
String enum_verb = (ee.next()).getCommandName();
if (enum_verb.equals(verb))
return true;
}
return false;
}
/**
* Get all the available commands in all mailcap files known to
* this instance of MailcapCommandMap for this MIME type.
*
* @param mimeType the MIME type
* @return the CommandInfo objects representing all the commands.
*/
public synchronized CommandInfo[] getAllCommands(String mimeType) {
List<CommandInfo> cmdList = new ArrayList<>();
if (mimeType != null)
mimeType = mimeType.toLowerCase(Locale.ENGLISH);
for (int i = 0; i < DB.length; i++) {
if (DB[i] == null)
continue;
Map<String, List<String>> cmdMap = DB[i].getMailcapList(mimeType);
if (cmdMap != null)
appendCmdsToList(cmdMap, cmdList);
}
// now add the fallback commands
for (int i = 0; i < DB.length; i++) {
if (DB[i] == null)
continue;
Map<String, List<String>> cmdMap = DB[i].getMailcapFallbackList(mimeType);
if (cmdMap != null)
appendCmdsToList(cmdMap, cmdList);
}
CommandInfo[] cmdInfos = new CommandInfo[cmdList.size()];
cmdInfos = cmdList.toArray(cmdInfos);
return cmdInfos;
}
/**
* Put the commands that are in the hash table, into the list.
*/
private void appendCmdsToList(Map<String, List<String>> typeHash, List<CommandInfo> cmdList) {
Iterator<String> verb_enum = typeHash.keySet().iterator();
while (verb_enum.hasNext()) {
String verb = verb_enum.next();
List<String> cmdList2 = typeHash.get(verb);
Iterator<String> cmd_enum = cmdList2.iterator();
while (cmd_enum.hasNext()) {
String cmd = cmd_enum.next();
cmdList.add(new CommandInfo(verb, cmd));
// cmdList.add(0, new CommandInfo(verb, cmd));
}
}
}
/**
* Get the command corresponding to <code>cmdName</code> for the MIME type.
*
* @param mimeType the MIME type
* @param cmdName the command name
* @return the CommandInfo object corresponding to the command.
*/
public synchronized CommandInfo getCommand(String mimeType,
String cmdName) {
if (mimeType != null)
mimeType = mimeType.toLowerCase(Locale.ENGLISH);
for (int i = 0; i < DB.length; i++) {
if (DB[i] == null)
continue;
Map<String, List<String>> cmdMap = DB[i].getMailcapList(mimeType);
if (cmdMap != null) {
// get the cmd list for the cmd
List<String> v = cmdMap.get(cmdName);
if (v != null) {
String cmdClassName = v.get(0);
if (cmdClassName != null)
return new CommandInfo(cmdName, cmdClassName);
}
}
}
// now try the fallback list
for (int i = 0; i < DB.length; i++) {
if (DB[i] == null)
continue;
Map<String, List<String>> cmdMap = DB[i].getMailcapFallbackList(mimeType);
if (cmdMap != null) {
// get the cmd list for the cmd
List<String> v = cmdMap.get(cmdName);
if (v != null) {
String cmdClassName = v.get(0);
if (cmdClassName != null)
return new CommandInfo(cmdName, cmdClassName);
}
}
}
return null;
}
/**
* Add entries to the registry. Programmatically
* added entries are searched before other entries.<p>
* <p>
* The string that is passed in should be in mailcap
* format.
*
* @param mail_cap a correctly formatted mailcap string
*/
public synchronized void addMailcap(String mail_cap) {
// check to see if one exists
logger.log(Level.FINE, "MailcapCommandMap: add to PROG");
try {
if (DB[PROG] == null) {
DB[PROG] = getImplementation().getInMemory();
}
DB[PROG].appendToMailcap(mail_cap);
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
logger.log(Level.FINE, "Cannot find or load an implementation for MailcapRegistryProvider. " +
"MailcapRegistry: can't load", e);
throw e;
}
}
/**
* Return the DataContentHandler for the specified MIME type.
*
* @param mimeType the MIME type
* @return the DataContentHandler
*/
public synchronized DataContentHandler createDataContentHandler(
String mimeType) {
logger.log(Level.FINE, "MailcapCommandMap: createDataContentHandler for " + mimeType);
if (mimeType != null)
mimeType = mimeType.toLowerCase(Locale.ENGLISH);
for (int i = 0; i < DB.length; i++) {
if (DB[i] == null)
continue;
logger.log(Level.FINE, " search DB #" + i);
Map<String, List<String>> cmdMap = DB[i].getMailcapList(mimeType);
if (cmdMap != null) {
List<String> v = cmdMap.get("content-handler");
if (v != null) {
String name = v.get(0);
DataContentHandler dch = getDataContentHandler(name);
if (dch != null)
return dch;
}
}
}
// now try the fallback entries
for (int i = 0; i < DB.length; i++) {
if (DB[i] == null)
continue;
logger.log(Level.FINE, " search fallback DB #" + i);
Map<String, List<String>> cmdMap = DB[i].getMailcapFallbackList(mimeType);
if (cmdMap != null) {
List<String> v = cmdMap.get("content-handler");
if (v != null) {
String name = v.get(0);
DataContentHandler dch = getDataContentHandler(name);
if (dch != null)
return dch;
}
}
}
return null;
}
private DataContentHandler getDataContentHandler(String name) {
logger.log(Level.FINE, " got content-handler");
logger.log(Level.FINE, " class " + name);
try {
ClassLoader cld = null;
// First try the "application's" class loader.
cld = Util.getContextClassLoader();
if (cld == null)
cld = this.getClass().getClassLoader();
Class<?> cl = null;
try {
cl = cld.loadClass(name);
} catch (Exception ex) {
// if anything goes wrong, do it the old way
cl = Class.forName(name);
}
if (cl != null) // XXX - always true?
return (DataContentHandler)
cl.getConstructor().newInstance();
} catch (ReflectiveOperationException e) {
logger.log(Level.FINE, "Can't load DCH " + name, e);
}
return null;
}
/**
* Get all the MIME types known to this command map.
*
* @return array of MIME types as strings
* @since JAF 1.1
*/
public synchronized String[] getMimeTypes() {
List<String> mtList = new ArrayList<>();
for (int i = 0; i < DB.length; i++) {
if (DB[i] == null)
continue;
String[] ts = DB[i].getMimeTypes();
if (ts != null) {
for (int j = 0; j < ts.length; j++) {
// eliminate duplicates
if (!mtList.contains(ts[j]))
mtList.add(ts[j]);
}
}
}
String[] mts = new String[mtList.size()];
mts = mtList.toArray(mts);
return mts;
}
/**
* Get the native commands for the given MIME type.
* Returns an array of strings where each string is
* an entire mailcap file entry. The application
* will need to parse the entry to extract the actual
* command as well as any attributes it needs. See
* <A HREF="http://www.ietf.org/rfc/rfc1524.txt">RFC 1524</A>
* for details of the mailcap entry syntax. Only mailcap
* entries that specify a view command for the specified
* MIME type are returned.
*
* @param mimeType the MIME type
* @return array of native command entries
* @since JAF 1.1
*/
public synchronized String[] getNativeCommands(String mimeType) {
List<String> cmdList = new ArrayList<>();
if (mimeType != null) {
mimeType = mimeType.toLowerCase(Locale.ENGLISH);
}
for (MailcapRegistry mailcapRegistry : DB) {
if (mailcapRegistry == null) {
continue;
}
String[] cmds = mailcapRegistry.getNativeCommands(mimeType);
if (cmds != null) {
for (String cmd : cmds) {
if (!cmdList.contains(cmd)) {
cmdList.add(cmd);
}
}
}
}
String[] cmds = new String[cmdList.size()];
cmds = cmdList.toArray(cmds);
return cmds;
}
private MailcapRegistryProvider getImplementation() {
return FactoryFinder.find(MailcapRegistryProvider.class);
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.util.List;
import java.util.Map;
/**
* The MailcapRegistry interface is implemented by objects that can
* be used to store and retrieve MailcapEntries.
* <p>
* Application must implement {@link jakarta.activation.spi.MailcapRegistryProvider}
* to create new instances of the MailcapRegistry. Implementation of the MailcapRegistry
* can store MailcapEntries in different ways and that storage must be accessible through the
* {@link jakarta.activation.spi.MailcapRegistryProvider} methods.
* Implementation of the MailcapRegistry must contain in-memory storage for MailcapEntries.
*/
public interface MailcapRegistry {
/**
* Get the Map of MailcapEntries based on the MIME type.
*
* <p>
* <strong>Semantics:</strong> First check for the literal mime type,
* if that fails looks for wildcard &lt;type&gt;/\* and return that.
* Return the list of all that hit.
*
* @param mime_type the MIME type
* @return the map of MailcapEntries
*/
Map<String, List<String>> getMailcapList(String mime_type);
/**
* Get the Map of fallback MailcapEntries based on the MIME type.
*
* <p>
* <strong>Semantics:</strong> First check for the literal mime type,
* if that fails looks for wildcard &lt;type&gt;/\* and return that.
* Return the list of all that hit.
*
* @param mime_type the MIME type
* @return the map of fallback MailcapEntries
*/
Map<String, List<String>> getMailcapFallbackList(String mime_type);
/**
* Return all the MIME types known to this mailcap file.
*
* @return a String array of the MIME types
*/
String[] getMimeTypes();
/**
* Return all the native comands for the given MIME type.
*
* @param mime_type the MIME type
* @return a String array of the commands
*/
String[] getNativeCommands(String mime_type);
/**
* appendToMailcap: Append to this Mailcap DB, use the mailcap
* format:
* Comment == "# <i>comment string</i>"
* Entry == "mimetype; javabeanclass"
* <p>
* Example:
* # this is a comment
* image/gif jaf.viewers.ImageViewer
*
* @param mail_cap the mailcap string
*/
void appendToMailcap(String mail_cap);
}

View file

@ -0,0 +1,275 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.util.Locale;
/**
* A Multipurpose Internet Mail Extension (MIME) type, as defined
* in RFC 2045 and 2046.
*/
public class MimeType {
/**
* A string that holds all the special chars.
*/
private static final String TSPECIALS = "()<>@,;:/[]?=\\\"";
private String primaryType;
private String subType;
private MimeTypeParameterList parameters;
/**
* Default constructor.
*/
public MimeType() {
primaryType = "application";
subType = "*";
parameters = new MimeTypeParameterList();
}
/**
* Constructor that builds a MimeType from a String.
*
* @param rawdata the MIME type string
* @throws MimeTypeParseException if the MIME type can't be parsed
*/
public MimeType(String rawdata) throws MimeTypeParseException {
parse(rawdata);
}
/**
* Constructor that builds a MimeType with the given primary and sub type
* but has an empty parameter list.
*
* @param primary the primary MIME type
* @param sub the MIME sub-type
* @throws MimeTypeParseException if the primary type or subtype
* is not a valid token
*/
public MimeType(String primary, String sub) throws MimeTypeParseException {
// check to see if primary is valid
if (isValidToken(primary)) {
primaryType = primary.toLowerCase(Locale.ENGLISH);
} else {
throw new MimeTypeParseException("Primary type is invalid.");
}
// check to see if sub is valid
if (isValidToken(sub)) {
subType = sub.toLowerCase(Locale.ENGLISH);
} else {
throw new MimeTypeParseException("Sub type is invalid.");
}
parameters = new MimeTypeParameterList();
}
/**
* Determine whether or not a given character belongs to a legal token.
*/
private static boolean isTokenChar(char c) {
return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0);
}
/**
* A routine for parsing the MIME type out of a String.
*/
private void parse(String rawdata) throws MimeTypeParseException {
int slashIndex = rawdata.indexOf('/');
int semIndex = rawdata.indexOf(';');
if ((slashIndex < 0) && (semIndex < 0)) {
// neither character is present, so treat it
// as an error
throw new MimeTypeParseException("Unable to find a sub type.");
} else if ((slashIndex < 0) && (semIndex >= 0)) {
// we have a ';' (and therefore a parameter list),
// but no '/' indicating a sub type is present
throw new MimeTypeParseException("Unable to find a sub type.");
} else if ((slashIndex >= 0) && (semIndex < 0)) {
// we have a primary and sub type but no parameter list
primaryType = rawdata.substring(0, slashIndex).trim().
toLowerCase(Locale.ENGLISH);
subType = rawdata.substring(slashIndex + 1).trim().
toLowerCase(Locale.ENGLISH);
parameters = new MimeTypeParameterList();
} else if (slashIndex < semIndex) {
// we have all three items in the proper sequence
primaryType = rawdata.substring(0, slashIndex).trim().
toLowerCase(Locale.ENGLISH);
subType = rawdata.substring(slashIndex + 1, semIndex).trim().
toLowerCase(Locale.ENGLISH);
parameters = new MimeTypeParameterList(rawdata.substring(semIndex));
} else {
// we have a ';' lexically before a '/' which means we
// have a primary type and a parameter list but no sub type
throw new MimeTypeParseException("Unable to find a sub type.");
}
// now validate the primary and sub types
// check to see if primary is valid
if (!isValidToken(primaryType))
throw new MimeTypeParseException("Primary type is invalid.");
// check to see if sub is valid
if (!isValidToken(subType))
throw new MimeTypeParseException("Sub type is invalid.");
}
/**
* Retrieve the primary type of this object.
*
* @return the primary MIME type
*/
public String getPrimaryType() {
return primaryType;
}
/**
* Set the primary type for this object to the given String.
*
* @param primary the primary MIME type
* @throws MimeTypeParseException if the primary type
* is not a valid token
*/
public void setPrimaryType(String primary) throws MimeTypeParseException {
// check to see if primary is valid
if (!isValidToken(primaryType))
throw new MimeTypeParseException("Primary type is invalid.");
primaryType = primary.toLowerCase(Locale.ENGLISH);
}
/**
* Retrieve the subtype of this object.
*
* @return the MIME subtype
*/
public String getSubType() {
return subType;
}
/**
* Set the subtype for this object to the given String.
*
* @param sub the MIME subtype
* @throws MimeTypeParseException if the subtype
* is not a valid token
*/
public void setSubType(String sub) throws MimeTypeParseException {
// check to see if sub is valid
if (!isValidToken(subType))
throw new MimeTypeParseException("Sub type is invalid.");
subType = sub.toLowerCase(Locale.ENGLISH);
}
/**
* Retrieve this object's parameter list.
*
* @return a MimeTypeParameterList object representing the parameters
*/
public MimeTypeParameterList getParameters() {
return parameters;
}
/**
* Retrieve the value associated with the given name, or null if there
* is no current association.
*
* @param name the parameter name
* @return the paramter's value
*/
public String getParameter(String name) {
return parameters.get(name);
}
/**
* Set the value to be associated with the given name, replacing
* any previous association.
*
* @param name the parameter name
* @param value the paramter's value
*/
public void setParameter(String name, String value) {
parameters.set(name, value);
}
/**
* Remove any value associated with the given name.
*
* @param name the parameter name
*/
public void removeParameter(String name) {
parameters.remove(name);
}
/**
* Return the String representation of this object.
*/
public String toString() {
return getBaseType() + parameters.toString();
}
/**
* Return a String representation of this object
* without the parameter list.
*
* @return the MIME type and sub-type
*/
public String getBaseType() {
return primaryType + "/" + subType;
}
/**
* Determine if the primary and sub type of this object is
* the same as what is in the given type.
*
* @param type the MimeType object to compare with
* @return true if they match
*/
public boolean match(MimeType type) {
return primaryType.equals(type.getPrimaryType())
&& (subType.equals("*")
|| type.getSubType().equals("*")
|| (subType.equals(type.getSubType())));
}
// below here be scary parsing related things
/**
* Determine if the primary and sub type of this object is
* the same as the content type described in rawdata.
*
* @param rawdata the MIME type string to compare with
* @return true if they match
* @throws MimeTypeParseException if the MIME type can't be parsed
*/
public boolean match(String rawdata) throws MimeTypeParseException {
return match(new MimeType(rawdata));
}
/**
* Determine whether or not a given string is a legal token.
*/
private boolean isValidToken(String s) {
int len = s.length();
if (len > 0) {
for (int i = 0; i < len; ++i) {
char c = s.charAt(i);
if (!isTokenChar(c)) {
return false;
}
}
return true;
} else {
return false;
}
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
/**
* Represents mapping between the file extension and the MIME type string.
*/
public class MimeTypeEntry {
private String type;
private String extension;
/**
* Create new {@code MimeTypeEntry}
*
* @param mime_type the MIME type string
* @param file_ext the file extension
*/
public MimeTypeEntry(String mime_type, String file_ext) {
type = mime_type;
extension = file_ext;
}
/**
* Get MIME type string
*
* @return the MIME type string
*/
public String getMIMEType() {
return type;
}
/**
* Get the file extension
*
* @return the file extension
*/
public String getFileExtension() {
return extension;
}
@Override
public String toString() {
return "MIMETypeEntry: " + type + ", " + extension;
}
}

View file

@ -0,0 +1,323 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;
/**
* A parameter list of a MimeType
* as defined in RFC 2045 and 2046. The Primary type of the
* object must already be stripped off.
*
* @see MimeType
*/
public class MimeTypeParameterList {
/**
* A string that holds all the special chars.
*/
private static final String TSPECIALS = "()<>@,;:/[]?=\\\"";
private Hashtable<String, String> parameters;
/**
* Default constructor.
*/
public MimeTypeParameterList() {
parameters = new Hashtable<>();
}
/**
* Constructs a new MimeTypeParameterList with the passed in data.
*
* @param parameterList an RFC 2045, 2046 compliant parameter list.
* @throws MimeTypeParseException if the MIME type can't be parsed
*/
public MimeTypeParameterList(String parameterList)
throws MimeTypeParseException {
parameters = new Hashtable<>();
// now parse rawdata
parse(parameterList);
}
/**
* Determine whether or not a given character belongs to a legal token.
*/
private static boolean isTokenChar(char c) {
return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0);
}
/**
* return the index of the first non white space character in
* rawdata at or after index i.
*/
private static int skipWhiteSpace(String rawdata, int i) {
int length = rawdata.length();
while ((i < length) && Character.isWhitespace(rawdata.charAt(i)))
i++;
return i;
}
/**
* A routine that knows how and when to quote and escape the given value.
*/
private static String quote(String value) {
boolean needsQuotes = false;
// check to see if we actually have to quote this thing
int length = value.length();
for (int i = 0; (i < length) && !needsQuotes; i++) {
needsQuotes = !isTokenChar(value.charAt(i));
}
if (needsQuotes) {
StringBuilder buffer = new StringBuilder();
buffer.ensureCapacity((int) (length * 1.5));
// add the initial quote
buffer.append('"');
// add the properly escaped text
for (int i = 0; i < length; ++i) {
char c = value.charAt(i);
if ((c == '\\') || (c == '"'))
buffer.append('\\');
buffer.append(c);
}
// add the closing quote
buffer.append('"');
return buffer.toString();
} else {
return value;
}
}
/**
* A routine that knows how to strip the quotes and
* escape sequences from the given value.
*/
private static String unquote(String value) {
int valueLength = value.length();
StringBuilder buffer = new StringBuilder();
buffer.ensureCapacity(valueLength);
boolean escaped = false;
for (int i = 0; i < valueLength; ++i) {
char currentChar = value.charAt(i);
if (!escaped && (currentChar != '\\')) {
buffer.append(currentChar);
} else if (escaped) {
buffer.append(currentChar);
escaped = false;
} else {
escaped = true;
}
}
return buffer.toString();
}
/**
* A routine for parsing the parameter list out of a String.
*
* @param parameterList an RFC 2045, 2046 compliant parameter list.
* @throws MimeTypeParseException if the MIME type can't be parsed
*/
private void parse(String parameterList) throws MimeTypeParseException {
if (parameterList == null)
return;
int length = parameterList.length();
if (length == 0)
return;
int i;
char c;
for (i = skipWhiteSpace(parameterList, 0);
i < length && (c = parameterList.charAt(i)) == ';';
i = skipWhiteSpace(parameterList, i)) {
int lastIndex;
String name;
String value;
// eat the ';'
i++;
// now parse the parameter name
// skip whitespace
i = skipWhiteSpace(parameterList, i);
// tolerate trailing semicolon, even though it violates the spec
if (i >= length)
return;
// find the end of the token char run
lastIndex = i;
while ((i < length) && isTokenChar(parameterList.charAt(i)))
i++;
name = parameterList.substring(lastIndex, i).
toLowerCase(Locale.ENGLISH);
// now parse the '=' that separates the name from the value
i = skipWhiteSpace(parameterList, i);
if (i >= length || parameterList.charAt(i) != '=')
throw new MimeTypeParseException(
"Couldn't find the '=' that separates a " +
"parameter name from its value.");
// eat it and parse the parameter value
i++;
i = skipWhiteSpace(parameterList, i);
if (i >= length)
throw new MimeTypeParseException(
"Couldn't find a value for parameter named " + name);
// now find out whether or not we have a quoted value
c = parameterList.charAt(i);
if (c == '"') {
// yup it's quoted so eat it and capture the quoted string
i++;
if (i >= length)
throw new MimeTypeParseException(
"Encountered unterminated quoted parameter value.");
lastIndex = i;
// find the next unescaped quote
while (i < length) {
c = parameterList.charAt(i);
if (c == '"')
break;
if (c == '\\') {
// found an escape sequence
// so skip this and the
// next character
i++;
}
i++;
}
if (c != '"')
throw new MimeTypeParseException(
"Encountered unterminated quoted parameter value.");
value = unquote(parameterList.substring(lastIndex, i));
// eat the quote
i++;
} else if (isTokenChar(c)) {
// nope it's an ordinary token so it
// ends with a non-token char
lastIndex = i;
while (i < length && isTokenChar(parameterList.charAt(i)))
i++;
value = parameterList.substring(lastIndex, i);
} else {
// it ain't a value
throw new MimeTypeParseException(
"Unexpected character encountered at index " + i);
}
// now put the data into the hashtable
parameters.put(name, value);
}
if (i < length) {
throw new MimeTypeParseException(
"More characters encountered in input than expected.");
}
}
/**
* Return the number of name-value pairs in this list.
*
* @return the number of parameters
*/
public int size() {
return parameters.size();
}
/**
* Determine whether or not this list is empty.
*
* @return true if there are no parameters
*/
public boolean isEmpty() {
return parameters.isEmpty();
}
/**
* Retrieve the value associated with the given name, or null if there
* is no current association.
*
* @param name the parameter name
* @return the parameter's value
*/
public String get(String name) {
return parameters.get(name.trim().toLowerCase(Locale.ENGLISH));
}
// below here be scary parsing related things
/**
* Set the value to be associated with the given name, replacing
* any previous association.
*
* @param name the parameter name
* @param value the parameter's value
*/
public void set(String name, String value) {
parameters.put(name.trim().toLowerCase(Locale.ENGLISH), value);
}
/**
* Remove any value associated with the given name.
*
* @param name the parameter name
*/
public void remove(String name) {
parameters.remove(name.trim().toLowerCase(Locale.ENGLISH));
}
/**
* Retrieve an enumeration of all the names in this list.
*
* @return an enumeration of all parameter names
*/
public Enumeration<String> getNames() {
return parameters.keys();
}
/**
* Return a string representation of this object.
*/
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.ensureCapacity(parameters.size() * 16);
// heuristic: 8 characters per field
Enumeration<String> keys = parameters.keys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
buffer.append("; ");
buffer.append(key);
buffer.append('=');
buffer.append(quote(parameters.get(key)));
}
return buffer.toString();
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
/**
* A class to encapsulate MimeType parsing related exceptions.
*/
@SuppressWarnings("serial")
public class MimeTypeParseException extends Exception {
/**
* Constructs a MimeTypeParseException with no specified detail message.
*/
public MimeTypeParseException() {
super();
}
/**
* Constructs a MimeTypeParseException with the specified detail message.
*
* @param s the detail message.
*/
public MimeTypeParseException(String s) {
super(s);
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
/**
* The MimeTypeRegistry interface is implemented by objects that can
* be used to store and retrieve MimeTypeEntries.
* <p>
* Application must implement {@link jakarta.activation.spi.MimeTypeRegistryProvider}
* to create new instances of the MimeTypeRegistry. Implementation of the MimeTypeRegistry
* can store MimeTypeEntries in different ways and that storage must be accessible through the
* {@link jakarta.activation.spi.MimeTypeRegistryProvider} methods.
* Implementation of the MimeTypeRegistry must contain in-memory storage for MimeTypeEntries.
*/
public interface MimeTypeRegistry {
/**
* get the MimeTypeEntry based on the file extension
*
* @param file_ext the file extension
* @return the MimeTypeEntry
*/
MimeTypeEntry getMimeTypeEntry(String file_ext);
/**
* Get the MIME type string corresponding to the file extension.
*
* @param file_ext the file extension
* @return the MIME type string
*/
default String getMIMETypeString(String file_ext) {
MimeTypeEntry entry = this.getMimeTypeEntry(file_ext);
if (entry != null) {
return entry.getMIMEType();
}
return null;
}
/**
* Appends string of entries to the types registry
*
* @param mime_types the mime.types string
*/
void appendToRegistry(String mime_types);
}

View file

@ -0,0 +1,350 @@
/*
* Copyright (c) 1997, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import jakarta.activation.spi.MimeTypeRegistryProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Path;
import java.util.NoSuchElementException;
import java.util.ServiceConfigurationError;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class extends FileTypeMap and provides data typing of files
* via their file extension. It uses the <code>.mime.types</code> format. <p>
*
* <b>MIME types file search order:</b><p>
* The MimetypesFileTypeMap looks in various places in the user's
* system for MIME types file entries. When requests are made
* to search for MIME types in the MimetypesFileTypeMap, it searches
* MIME types files in the following order:
* <ol>
* <li> Programmatically added entries to the MimetypesFileTypeMap instance.
* <li> The file <code>.mime.types</code> in the user's home directory.
* <li> The file <code>mime.types</code> in the Java runtime.
* <li> The file or resources named <code>META-INF/mime.types</code>.
* <li> The file or resource named <code>META-INF/mimetypes.default</code>
* (usually found only in the <code>activation.jar</code> file).
* </ol>
* <p>
* (The current implementation looks for the <code>mime.types</code> file
* in the Java runtime in the directory <code><i>java.home</i>/conf</code>
* if it exists, and otherwise in the directory
* <code><i>java.home</i>/lib</code>, where <i>java.home</i> is the value
* of the "java.home" System property. Note that the "conf" directory was
* introduced in JDK 9.)
* <p>
* <b>MIME types file format:</b><p>
*
* <code>
* # comments begin with a '#'<br>
* # the format is &lt;mime type&gt; &lt;space separated file extensions&gt;<br>
* # for example:<br>
* text/plain txt text TXT<br>
* # this would map file.txt, file.text, and file.TXT to<br>
* # the mime type "text/plain"<br>
* </code>
*
* @author Bart Calder
* @author Bill Shannon
*/
public class MimetypesFileTypeMap extends FileTypeMap {
private static final Logger logger = Logger.getLogger(MimetypesFileTypeMap.class.getName());
private static final int PROG = 0;
private static final String defaultType = "application/octet-stream";
private static final String confDir;
static {
String dir;
String home = System.getProperty("java.home");
String newdir = home + File.separator + "conf";
File conf = new File(newdir);
if (conf.exists()) {
dir = newdir + File.separator;
} else {
dir = home + File.separator + "lib" + File.separator;
}
confDir = dir;
}
private MimeTypeRegistry[] DB;
/**
* The default constructor.
*/
public MimetypesFileTypeMap() {
Vector<MimeTypeRegistry> dbv = new Vector<>(5);
MimeTypeRegistry mf;
dbv.addElement(null);
logger.log(Level.FINE, "MimetypesFileTypeMap: load HOME");
String user_home = System.getProperty("user.home");
if (user_home != null) {
String path = user_home + File.separator + ".mime.types";
mf = loadFile(path);
if (mf != null)
dbv.addElement(mf);
}
logger.log(Level.FINE, "MimetypesFileTypeMap: load SYS");
// check system's home
if (confDir != null) {
mf = loadFile(confDir + "mime.types");
if (mf != null)
dbv.addElement(mf);
}
logger.log(Level.FINE, "MimetypesFileTypeMap: load JAR");
// load from the app's jar file
loadAllResources(dbv, "META-INF/mime.types");
logger.log(Level.FINE, "MimetypesFileTypeMap: load DEF");
mf = loadResource("/META-INF/mimetypes.default");
if (mf != null)
dbv.addElement(mf);
DB = new MimeTypeRegistry[dbv.size()];
dbv.copyInto(DB);
}
/**
* Construct a MimetypesFileTypeMap with programmatic entries
* added from the named file.
*
* @param mimeTypeFileName the file name
* @throws IOException for errors reading the file
*/
public MimetypesFileTypeMap(String mimeTypeFileName) throws IOException {
this();
try {
DB[PROG] = getImplementation().getByFileName(mimeTypeFileName);
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
String errorMessage = "Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't load " + mimeTypeFileName;
logger.log(Level.FINE, errorMessage, e);
throw new IOException(errorMessage, e);
}
}
/**
* Construct a MimetypesFileTypeMap with programmatic entries
* added from the InputStream.
*
* @param is the input stream to read from
*/
public MimetypesFileTypeMap(InputStream is) {
this();
try {
DB[PROG] = getImplementation().getByInputStream(is);
} catch (IOException ex) {
// XXX - really should throw it
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
logger.log(Level.FINE, "Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't load InputStream", e);
}
}
/**
* Load from the named resource.
*/
private MimeTypeRegistry loadResource(String name) {
InputStream clis = null;
try {
clis = Util.getResourceAsStream(this.getClass(), name);
if (clis != null) {
MimeTypeRegistry mf = getImplementation().getByInputStream(clis);
logger.log(Level.FINE, "MimetypesFileTypeMap: successfully " +
"loaded mime types file: " + name);
return mf;
} else {
logger.log(Level.FINE, "MimetypesFileTypeMap: not loading " +
"mime types file: " + name);
}
} catch (IOException e) {
logger.log(Level.FINE, "MimetypesFileTypeMap: can't load " + name, e);
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
logger.log(Level.FINE, "Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't load " + name, e);
} finally {
try {
if (clis != null)
clis.close();
} catch (IOException ex) {
logger.log(Level.FINE, "InputStream cannot be close for " + name, ex);
}
}
return null;
}
/**
* Load all of the named resource.
*/
private void loadAllResources(Vector<MimeTypeRegistry> v, String name) {
boolean anyLoaded = false;
try {
URL[] urls;
ClassLoader cld = null;
// First try the "application's" class loader.
cld = Util.getContextClassLoader();
if (cld == null)
cld = this.getClass().getClassLoader();
if (cld != null)
urls = Util.getResources(cld, name);
else
urls = Util.getSystemResources(name);
if (urls != null) {
logger.log(Level.FINE, "MimetypesFileTypeMap: getResources");
for (int i = 0; i < urls.length; i++) {
URL url = urls[i];
InputStream clis = null;
logger.log(Level.FINE, "MimetypesFileTypeMap: URL " + url);
try {
clis = Util.openStream(url);
if (clis != null) {
v.addElement(
getImplementation().getByInputStream(clis)
);
anyLoaded = true;
logger.log(Level.FINE, "MimetypesFileTypeMap: " +
"successfully loaded " +
"mime types from URL: " + url);
} else {
logger.log(Level.FINE, "MimetypesFileTypeMap: " +
"not loading " +
"mime types from URL: " + url);
}
} catch (IOException ioex) {
logger.log(Level.FINE, "MimetypesFileTypeMap: can't load " +
url, ioex);
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
logger.log(Level.FINE, "Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't load " + url, e);
} finally {
try {
if (clis != null)
clis.close();
} catch (IOException cex) {
logger.log(Level.FINE, "InputStream cannot be close for " + name, cex);
}
}
}
}
} catch (Exception ex) {
logger.log(Level.FINE, "MimetypesFileTypeMap: can't load " + name, ex);
}
// if failed to load anything, fall back to old technique, just in case
if (!anyLoaded) {
logger.log(Level.FINE, "MimetypesFileTypeMap: !anyLoaded");
MimeTypeRegistry mf = loadResource("/" + name);
if (mf != null)
v.addElement(mf);
}
}
/**
* Load the named file.
*/
private MimeTypeRegistry loadFile(String name) {
MimeTypeRegistry mtf = null;
try {
mtf = getImplementation().getByFileName(name);
} catch (IOException e) {
logger.log(Level.FINE, "MimeTypeRegistry: can't load from file - " + name, e);
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
logger.log(Level.FINE, "Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't load " + name, e);
}
return mtf;
}
/**
* Prepend the MIME type values to the registry.
*
* @param mime_types A .mime.types formatted string of entries.
*/
public synchronized void addMimeTypes(String mime_types) {
try {
if (DB[PROG] == null) {
DB[PROG] = getImplementation().getInMemory();
}
DB[PROG].appendToRegistry(mime_types);
} catch (NoSuchElementException | IllegalStateException | ServiceConfigurationError e) {
logger.log(Level.FINE, "Cannot find or load an implementation for MimeTypeRegistryProvider." +
"MimeTypeRegistry: can't add " + mime_types, e);
throw e;
}
}
/**
* Return the MIME type of the <Code>File</Code> object.
* The implementation in this class calls
* <code>getContentType(f.getName())</code>.
*
* @param f the file
* @return the file's MIME type
*/
public String getContentType(File f) {
return this.getContentType(f.getName());
}
/**
* Return the MIME type of the <Code>Path</Code> object.
* The implementation in this class calls
* <code>getContentType(p.getFileName().toString())</code>.
*
* @param p the file <Code>Path</Code>
* @return the file's MIME type
*/
public String getContentType(Path p) {
return this.getContentType(p.getFileName().toString());
}
/**
* Return the MIME type based on the specified file name.
* The MIME type entries are searched as described above under
* <i>MIME types file search order</i>.
* If no entry is found, the type "application/octet-stream" is returned.
*
* @param filename the file name
* @return the file's MIME type
*/
public synchronized String getContentType(String filename) {
int dot_pos = filename.lastIndexOf("."); // period index
if (dot_pos < 0) {
return defaultType;
}
String file_ext = filename.substring(dot_pos + 1);
if (file_ext.isEmpty()) {
return defaultType;
}
for (MimeTypeRegistry mimeTypeRegistry : DB) {
if (mimeTypeRegistry == null) {
continue;
}
String result = mimeTypeRegistry.getMIMETypeString(file_ext);
if (result != null) {
return result;
}
}
return defaultType;
}
private MimeTypeRegistryProvider getImplementation() {
return FactoryFinder.find(MimeTypeRegistryProvider.class);
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2021, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Shared ServiceLoader/FactoryFinder Utils. JAF and MAIL use the same loading
* logic of thread context class loader, calling class loader, and finally the
* system class loader.
*
* @author Miroslav.Kos@oracle.com
*/
class ServiceLoaderUtil {
static <P, T extends Exception> P firstByServiceLoader(Class<P> spiClass,
ClassLoader loader,
Logger logger,
ExceptionHandler<T> handler) throws T {
logger.log(Level.FINE, "Using java.util.ServiceLoader to find {0}", spiClass.getName());
try {
ServiceLoader<P> serviceLoader = ServiceLoader.load(spiClass, loader);
for (P impl : serviceLoader) {
logger.log(Level.FINE, "ServiceProvider loading Facility used; returning object [{0}]", impl.getClass().getName());
return impl;
}
} catch (Throwable t) {
throw handler.createException(t, "Error while searching for service [" + spiClass.getName() + "]");
}
return null;
}
@SuppressWarnings({"unchecked"})
static <P> Class<P> nullSafeLoadClass(String className, ClassLoader classLoader) throws ClassNotFoundException {
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
return (Class<P>) Class.forName(className, false, classLoader);
}
static <P, T extends Exception> P newInstance(String className,
Class<P> service, ClassLoader classLoader,
final ExceptionHandler<T> handler) throws T {
try {
Class<P> cls = safeLoadClass(className, classLoader);
return service.cast(cls.getConstructor().newInstance());
} catch (ClassNotFoundException x) {
throw handler.createException(x, "Provider " + className + " not found");
} catch (Exception x) {
throw handler.createException(x, "Provider " + className + " could not be instantiated: " + x);
}
}
static <P> Class<P> safeLoadClass(String className,
ClassLoader classLoader) throws ClassNotFoundException {
return nullSafeLoadClass(className, classLoader);
}
static abstract class ExceptionHandler<T extends Exception> {
public abstract T createException(Throwable throwable, String message);
}
}

View file

@ -0,0 +1,121 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
/**
* The URLDataSource class provides an object that wraps a <code>URL</code>
* object in a DataSource interface. URLDataSource simplifies the handling
* of data described by URLs within Jakarta Activation
* because this class can be used to create new DataHandlers. <i>NOTE: The
* DataHandler object creates a URLDataSource internally,
* when it is constructed with a URL.</i>
*
* @see DataSource
* @see DataHandler
*/
public class URLDataSource implements DataSource {
private URL url = null;
private URLConnection url_conn = null;
/**
* URLDataSource constructor. The URLDataSource class will
* not open a connection to the URL until a method requiring it
* to do so is called.
*
* @param url The URL to be encapsulated in this object.
*/
public URLDataSource(URL url) {
this.url = url;
}
/**
* Returns the value of the URL content-type header field.
* It calls the URL's <code>URLConnection.getContentType</code> method
* after retrieving a URLConnection object.
* <i>Note: this method attempts to call the <code>openConnection</code>
* method on the URL. If this method fails, or if a content type is not
* returned from the URLConnection, getContentType returns
* "application/octet-stream" as the content type.</i>
*
* @return the content type.
*/
public String getContentType() {
String type = null;
try {
if (url_conn == null)
url_conn = url.openConnection();
} catch (IOException e) {
}
if (url_conn != null)
type = url_conn.getContentType();
if (type == null)
type = "application/octet-stream";
return type;
}
/**
* Calls the <code>getFile</code> method on the URL used to
* instantiate the object.
*
* @return the result of calling the URL's getFile method.
*/
public String getName() {
return url.getFile();
}
/**
* The getInputStream method from the URL. Calls the
* <code>openStream</code> method on the URL.
*
* @return the InputStream.
*/
public InputStream getInputStream() throws IOException {
return url.openStream();
}
/**
* The getOutputStream method from the URL. First an attempt is
* made to get the URLConnection object for the URL. If that
* succeeds, the getOutputStream method on the URLConnection
* is returned.
*
* @return the OutputStream.
*/
public OutputStream getOutputStream() throws IOException {
// get the url connection if it is available
url_conn = url.openConnection();
if (url_conn != null) {
url_conn.setDoOutput(true);
return url_conn.getOutputStream();
} else
return null;
}
/**
* Return the URL used to create this DataSource.
*
* @return The URL.
*/
public URL getURL() {
return url;
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.io.IOException;
/**
* Signals that the requested operation does not support the
* requested data type.
*
* @see DataHandler
*/
@SuppressWarnings("serial")
public class UnsupportedDataTypeException extends IOException {
/**
* Constructs an UnsupportedDataTypeException with no detail
* message.
*/
public UnsupportedDataTypeException() {
super();
}
/**
* Constructs an UnsupportedDataTypeException with the specified
* message.
*
* @param s The detail message.
*/
public UnsupportedDataTypeException(String s) {
super(s);
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
class Util {
private Util() {
// private constructor, can't create an instance
}
public static ClassLoader getContextClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
public static InputStream getResourceAsStream(final Class<?> c,
final String name) throws IOException {
return c.getResourceAsStream(name);
}
public static URL[] getResources(final ClassLoader cl, final String name) {
URL[] ret = null;
try {
List<URL> v = new ArrayList<>();
Enumeration<URL> e = cl.getResources(name);
while (e != null && e.hasMoreElements()) {
URL url = e.nextElement();
if (url != null)
v.add(url);
}
if (!v.isEmpty()) {
ret = new URL[v.size()];
ret = v.toArray(ret);
}
} catch (IOException ioex) {
//
}
return ret;
}
public static URL[] getSystemResources(final String name) {
URL[] ret = null;
try {
List<URL> v = new ArrayList<>();
Enumeration<URL> e = ClassLoader.getSystemResources(name);
while (e != null && e.hasMoreElements()) {
URL url = e.nextElement();
if (url != null)
v.add(url);
}
if (!v.isEmpty()) {
ret = new URL[v.size()];
ret = v.toArray(ret);
}
} catch (IOException ioex) {
//
}
return ret;
}
public static InputStream openStream(final URL url) throws IOException {
return url.openStream();
}
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* Jakarta Activation is used by Jakarta Mail to manage MIME data.
*/
package jakarta.activation;

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation.spi;
import jakarta.activation.MailcapRegistry;
import java.io.IOException;
import java.io.InputStream;
import java.util.NoSuchElementException;
import java.util.ServiceConfigurationError;
/**
* This interface defines a factory for <code>MailcapRegistry</code>. An
* implementation of this interface should provide instances of the MailcapRegistry
* based on the way how to access the storage for MailcapEntries.
* <p>
* Jakarta Activation uses Service Provider Interface and <code>ServiceLoader</code>
* to obtain an instance of the implementation of the <code>MailcapRegistryProvider</code>.
*/
public interface MailcapRegistryProvider {
/**
* Retrieve an instance of the MailcapRegistry based on the name of the file where the MailcapEntries are stored.
*
* @param name The name of the file that stores MailcapEntries.
* @return The instance of the <code>MailcapRegistry</code>, or <i>null</i> if none are found.
* @throws IOException If an instance of the MailcapRegistry class cannot be found or loaded.
*/
MailcapRegistry getByFileName(String name) throws IOException;
/**
* Retrieve an instance of the MailcapRegistry based on the InputStream
* that is used to read data from some named resource.
*
* @param inputStream InputStream for some resource that contains MailcapEntries.
* @return The instance of the <code>MailcapRegistry</code>, or <i>null</i> if none are found.
* @throws IOException If an instance of the MailcapRegistry class cannot be found or loaded.
*/
MailcapRegistry getByInputStream(InputStream inputStream) throws IOException;
/**
* Retrieve an instance of the in-memory implementation of the MailcapRegistry.
*
* @return In-memory implementation of the MailcapRegistry.
* @throws NoSuchElementException If no implementations were found.
* @throws ServiceConfigurationError If no implementations were loaded.
*/
MailcapRegistry getInMemory();
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package jakarta.activation.spi;
import jakarta.activation.MimeTypeRegistry;
import java.io.IOException;
import java.io.InputStream;
import java.util.NoSuchElementException;
import java.util.ServiceConfigurationError;
/**
* This interface defines a factory for <code>MimeTypeRegistry</code>. An
* implementation of this interface should provide instances of the MimeTypeRegistry
* based on the way how to access the storage for MimeTypeEntries.
* <p>
* Jakarta Activation uses Service Provider Interface and <code>ServiceLoader</code>
* to obtain an instance of the implementation of the <code>MimeTypeRegistryProvider</code>.
*/
public interface MimeTypeRegistryProvider {
/**
* Retrieve an instance of the MimeTypeRegistry based on the name of the file where the MimeTypeEntries are stored.
*
* @param name The name of the file that stores MimeTypeEntries.
* @return The instance of the <code>MimeTypeRegistry</code>, or <i>null</i> if none are found.
* @throws IOException If an instance of the MailcapRegistry class cannot be found or loaded.
*/
MimeTypeRegistry getByFileName(String name) throws IOException;
/**
* Retrieve an instance of the MimeTypeRegistry based on the InputStream
* that is used to read data from some named resource.
*
* @param inputStream InputStream for some resource that contains MimeTypeEntries.
* @return The instance of the <code>MimeTypeRegistry</code>, or <i>null</i> if none are found.
* @throws IOException If an instance of the MailcapRegistry class cannot be found or loaded.
*/
MimeTypeRegistry getByInputStream(InputStream inputStream) throws IOException;
/**
* Retrieve an instance of the in-memory implementation of the MimeTypeRegistry.
* Jakarta Activation can throw <code>NoSuchElementException</code> or <code>ServiceConfigurationError</code>
* if no implementations were found.
*
* @return In-memory implementation of the MimeTypeRegistry.
* @throws NoSuchElementException If no implementations were found.
* @throws ServiceConfigurationError If no implementations were loaded.
*/
MimeTypeRegistry getInMemory();
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* <p>Provides interfaces which implementations will be used as service providers for other services
* that used by Jakarta Activation.</p>
* <p>Implementation of Jakarta Activation must implement interfaces declared in this package.
* Jakarta Activation uses {@link java.util.ServiceLoader} class to discover
* and load implementations of the interfaces from this package using standard Java SPI mechanism.</p>
*/
package jakarta.activation.spi;

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* This abstract class models the addresses in a message.
* Subclasses provide specific implementations. Subclasses
* will typically be serializable so that (for example) the
* use of Address objects in search terms can be serialized
* along with the search terms.
*
* @author John Mani
* @author Bill Shannon
*/
public abstract class Address {
/**
* Creates a default {@code Address}.
*/
public Address() {
}
/**
* Return a type string that identifies this address type.
*
* @return address type
* @see jakarta.mail.internet.InternetAddress
*/
public abstract String getType();
/**
* Return a String representation of this address object.
*
* @return string representation of this address
*/
@Override
public abstract String toString();
/**
* The equality operator. Subclasses should provide an
* implementation of this method that supports value equality
* (do the two Address objects represent the same destination?),
* not object reference equality. A subclass must also provide
* a corresponding implementation of the <code>hashCode</code>
* method that preserves the general contract of
* <code>equals</code> and <code>hashCode</code> - objects that
* compare as equal must have the same hashCode.
*
* @param address Address object
*/
@Override
public abstract boolean equals(Object address);
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* This exception is thrown when the connect method on a Store or
* Transport object fails due to an authentication failure (e.g.,
* bad user name or password).
*
* @author Bill Shannon
*/
@SuppressWarnings("serial")
public class AuthenticationFailedException extends MessagingException {
/**
* Constructs an AuthenticationFailedException.
*/
public AuthenticationFailedException() {
super();
}
/**
* Constructs an AuthenticationFailedException with the specified
* detail message.
*
* @param message The detailed error message
*/
public AuthenticationFailedException(String message) {
super(message);
}
/**
* Constructs an AuthenticationFailedException with the specified
* detail message and embedded exception. The exception is chained
* to this exception.
*
* @param message The detailed error message
* @param e The embedded exception
* @since JavaMail 1.5
*/
public AuthenticationFailedException(String message, Exception e) {
super(message, e);
}
}

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import java.net.InetAddress;
/**
* The class Authenticator represents an object that knows how to obtain
* authentication for a network connection. Usually, it will do this
* by prompting the user for information.
* <p>
* Applications use this class by creating a subclass, and registering
* an instance of that subclass with the session when it is created.
* When authentication is required, the system will invoke a method
* on the subclass (like getPasswordAuthentication). The subclass's
* method can query about the authentication being requested with a
* number of inherited methods (getRequestingXXX()), and form an
* appropriate message for the user.
* <p>
* All methods that request authentication have a default implementation
* that fails.
*
* @author Bill Foote
* @author Bill Shannon
* @see java.net.Authenticator
* @see Session#getInstance(java.util.Properties,
* Authenticator)
* @see Session#getDefaultInstance(java.util.Properties,
* Authenticator)
* @see Session#requestPasswordAuthentication
* @see PasswordAuthentication
*/
public abstract class Authenticator {
private InetAddress requestingSite;
private int requestingPort;
private String requestingProtocol;
private String requestingPrompt;
private String requestingUserName;
/**
* Creates a default {@code Authenticator}.
* There are no abstract methods, but to be useful the user must subclass.
*
* @see #getPasswordAuthentication()
*/
public Authenticator() {
}
/**
* Ask the authenticator for a password.
* <p>
*
* @param addr The InetAddress of the site requesting authorization,
* or null if not known.
* @param port the port for the requested connection
* @param protocol The protocol that's requesting the connection
* (@see java.net.Authenticator.getProtocol())
* @param prompt A prompt string for the user
* @return The username/password, or null if one can't be gotten.
*/
final synchronized PasswordAuthentication requestPasswordAuthentication(
InetAddress addr, int port, String protocol,
String prompt, String defaultUserName) {
requestingSite = addr;
requestingPort = port;
requestingProtocol = protocol;
requestingPrompt = prompt;
requestingUserName = defaultUserName;
return getPasswordAuthentication();
}
/**
* @return the InetAddress of the site requesting authorization, or null
* if it's not available.
*/
protected final InetAddress getRequestingSite() {
return requestingSite;
}
/**
* @return the port for the requested connection
*/
protected final int getRequestingPort() {
return requestingPort;
}
/**
* Give the protocol that's requesting the connection. Often this
* will be based on a URLName.
*
* @return the protcol
* @see URLName#getProtocol
*/
protected final String getRequestingProtocol() {
return requestingProtocol;
}
/**
* @return the prompt string given by the requestor
*/
protected final String getRequestingPrompt() {
return requestingPrompt;
}
/**
* @return the default user name given by the requestor
*/
protected final String getDefaultUserName() {
return requestingUserName;
}
/**
* Called when password authentication is needed. Subclasses should
* override the default implementation, which returns null. <p>
* <p>
* Note that if this method uses a dialog to prompt the user for this
* information, the dialog needs to block until the user supplies the
* information. This method can not simply return after showing the
* dialog.
*
* @return The PasswordAuthentication collected from the
* user, or null if none is provided.
*/
protected PasswordAuthentication getPasswordAuthentication() {
return null;
}
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import jakarta.mail.util.StreamProvider;
/**
* This class models a Part that is contained within a Multipart.
* This is an abstract class. Subclasses provide actual implementations.<p>
* <p>
* BodyPart implements the Part interface. Thus, it contains a set of
* attributes and a "content".
*
* @author John Mani
* @author Bill Shannon
*/
public abstract class BodyPart implements Part {
/**
* Instance of stream provider.
*
* @since JavaMail 2.1
*/
protected final StreamProvider streamProvider = StreamProvider.provider();
/**
* The <code>Multipart</code> object containing this <code>BodyPart</code>,
* if known.
*
* @since JavaMail 1.1
*/
protected Multipart parent;
/**
* Creates a default {@code BodyPart}.
*/
public BodyPart() {
}
/**
* Return the containing <code>Multipart</code> object,
* or <code>null</code> if not known.
*
* @return the parent Multipart
*/
public Multipart getParent() {
return parent;
}
/**
* Set the parent of this <code>BodyPart</code> to be the specified
* <code>Multipart</code>. Normally called by <code>Multipart</code>'s
* <code>addBodyPart</code> method. <code>parent</code> may be
* <code>null</code> if the <code>BodyPart</code> is being removed
* from its containing <code>Multipart</code>.
*
* @since JavaMail 1.1
*/
void setParent(Multipart parent) {
this.parent = parent;
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* A {@link jakarta.activation.DataSource DataSource} that also implements
* <code>EncodingAware</code> may specify the Content-Transfer-Encoding
* to use for its data. Valid Content-Transfer-Encoding values specified
* by RFC 2045 are "7bit", "8bit", "quoted-printable", "base64", and "binary".
* <p>
* For example, a {@link jakarta.activation.FileDataSource FileDataSource}
* could be created that forces all files to be base64 encoded:
* <blockquote><pre>
* public class Base64FileDataSource extends FileDataSource
* implements EncodingAware {
* public Base64FileDataSource(File file) {
* super(file);
* }
*
* // implements EncodingAware.getEncoding()
* public String getEncoding() {
* return "base64";
* }
* }
* </pre></blockquote>
*
* @author Bill Shannon
* @since JavaMail 1.5
*/
public interface EncodingAware {
/**
* Return the MIME Content-Transfer-Encoding to use for this data,
* or null to indicate that an appropriate value should be chosen
* by the caller.
*
* @return the Content-Transfer-Encoding value, or null
*/
String getEncoding();
}

View file

@ -0,0 +1,159 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import jakarta.mail.event.MailEvent;
import java.util.EventListener;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Package private class used by Store & Folder to dispatch events.
* This class implements an event queue, and a dispatcher thread that
* dequeues and dispatches events from the queue.
*
* @author Bill Shannon
*/
class EventQueue implements Runnable {
private static WeakHashMap<ClassLoader, EventQueue> appq;
private volatile BlockingQueue<QueueElement> q;
private Executor executor;
/**
* Construct an EventQueue using the specified Executor.
* If the Executor is null, threads will be created as needed.
*/
EventQueue(Executor ex) {
this.executor = ex;
}
/**
* Create (if necessary) an application-scoped event queue.
* Application scoping is based on the thread's context class loader.
*/
static synchronized EventQueue getApplicationEventQueue(Executor ex) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (appq == null)
appq = new WeakHashMap<>();
EventQueue q = appq.get(cl);
if (q == null) {
q = new EventQueue(ex);
appq.put(cl, q);
}
return q;
}
/**
* Enqueue an event.
*/
synchronized void enqueue(MailEvent event,
Vector<? extends EventListener> vector) {
// if this is the first event, create the queue and start the event task
if (q == null) {
q = new LinkedBlockingQueue<>();
if (executor != null) {
executor.execute(this);
} else {
Thread qThread = new Thread(this, "Jakarta-Mail-EventQueue");
qThread.setDaemon(true); // not a user thread
qThread.start();
}
}
q.add(new QueueElement(event, vector));
}
/**
* Terminate the task running the queue, but only if there is a queue.
*/
synchronized void terminateQueue() {
if (q != null) {
Vector<EventListener> dummyListeners = new Vector<>();
dummyListeners.setSize(1); // need atleast one listener
q.add(new QueueElement(new TerminatorEvent(), dummyListeners));
q = null;
}
}
/**
* Pull events off the queue and dispatch them.
*/
@Override
public void run() {
BlockingQueue<QueueElement> bq = q;
if (bq == null)
return;
try {
loop:
for (; ; ) {
// block until an item is available
QueueElement qe = bq.take();
MailEvent e = qe.event;
Vector<? extends EventListener> v = qe.vector;
for (int i = 0; i < v.size(); i++)
try {
e.dispatch(v.elementAt(i));
} catch (Throwable t) {
if (t instanceof InterruptedException)
break loop;
// ignore anything else thrown by the listener
}
qe = null;
e = null;
v = null;
}
} catch (InterruptedException e) {
// just die
}
}
/**
* A special event that causes the queue processing task to terminate.
*/
@SuppressWarnings("serial")
static class TerminatorEvent extends MailEvent {
TerminatorEvent() {
super(new Object());
}
@Override
public void dispatch(Object listener) {
// Kill the event dispatching thread.
Thread.currentThread().interrupt();
}
}
/**
* A "struct" to put on the queue.
*/
static class QueueElement {
MailEvent event;
Vector<? extends EventListener> vector;
QueueElement(MailEvent event, Vector<? extends EventListener> vector) {
this.event = event;
this.vector = vector;
}
}
}

View file

@ -0,0 +1,223 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import java.util.Vector;
/**
* Clients use a FetchProfile to list the Message attributes that
* it wishes to prefetch from the server for a range of messages.<p>
* <p>
* Messages obtained from a Folder are light-weight objects that
* typically start off as empty references to the actual messages.
* Such a Message object is filled in "on-demand" when the appropriate
* get*() methods are invoked on that particular Message. Certain
* server-based message access protocols (Ex: IMAP) allow batch
* fetching of message attributes for a range of messages in a single
* request. Clients that want to use message attributes for a range of
* Messages (Example: to display the top-level headers in a headerlist)
* might want to use the optimization provided by such servers. The
* <code>FetchProfile</code> allows the client to indicate this desire
* to the server. <p>
* <p>
* Note that implementations are not obligated to support
* FetchProfiles, since there might be cases where the backend service
* does not allow easy, efficient fetching of such profiles. <p>
* <p>
* Sample code that illustrates the use of a FetchProfile is given
* below:
* <blockquote>
* <pre>
*
* Message[] msgs = folder.getMessages();
*
* FetchProfile fp = new FetchProfile();
* fp.add(FetchProfile.Item.ENVELOPE);
* fp.add("X-mailer");
* folder.fetch(msgs, fp);
*
* </pre></blockquote>
*
* @author John Mani
* @author Bill Shannon
* @see Folder#fetch
*/
public class FetchProfile {
private Vector<Item> specials; // specials
private Vector<String> headers; // vector of header names
/**
* Create an empty FetchProfile.
*/
public FetchProfile() {
specials = null;
headers = null;
}
/**
* Add the given special item as one of the attributes to
* be prefetched.
*
* @param item the special item to be fetched
* @see Item#ENVELOPE
* @see Item#CONTENT_INFO
* @see Item#FLAGS
*/
public void add(Item item) {
if (specials == null)
specials = new Vector<>();
specials.addElement(item);
}
/**
* Add the specified header-field to the list of attributes
* to be prefetched.
*
* @param headerName header to be prefetched
*/
public void add(String headerName) {
if (headers == null)
headers = new Vector<>();
headers.addElement(headerName);
}
/**
* Returns true if the fetch profile contains the given special item.
*
* @param item the Item to test
* @return true if the fetch profile contains the given special item
*/
public boolean contains(Item item) {
return specials != null && specials.contains(item);
}
/**
* Returns true if the fetch profile contains the given header name.
*
* @param headerName the header to test
* @return true if the fetch profile contains the given header name
*/
public boolean contains(String headerName) {
return headers != null && headers.contains(headerName);
}
/**
* Get the items set in this profile.
*
* @return items set in this profile
*/
public Item[] getItems() {
if (specials == null)
return new Item[0];
Item[] s = new Item[specials.size()];
specials.copyInto(s);
return s;
}
/**
* Get the names of the header-fields set in this profile.
*
* @return headers set in this profile
*/
public String[] getHeaderNames() {
if (headers == null)
return new String[0];
String[] s = new String[headers.size()];
headers.copyInto(s);
return s;
}
/**
* This inner class is the base class of all items that
* can be requested in a FetchProfile. The items currently
* defined here are <code>ENVELOPE</code>, <code>CONTENT_INFO</code>
* and <code>FLAGS</code>. The <code>UIDFolder</code> interface
* defines the <code>UID</code> Item as well. <p>
* <p>
* Note that this class only has a protected constructor, therby
* restricting new Item types to either this class or subclasses.
* This effectively implements a enumeration of allowed Item types.
*
* @see UIDFolder
*/
public static class Item {
/**
* This is the Envelope item. <p>
* <p>
* The Envelope is an aggregration of the common attributes
* of a Message. Implementations should include the following
* attributes: From, To, Cc, Bcc, ReplyTo, Subject and Date.
* More items may be included as well. <p>
* <p>
* For implementations of the IMAP4 protocol (RFC 2060), the
* Envelope should include the ENVELOPE data item. More items
* may be included too.
*/
public static final Item ENVELOPE = new Item("ENVELOPE");
/**
* This item is for fetching information about the
* content of the message. <p>
* <p>
* This includes all the attributes that describe the content
* of the message. Implementations should include the following
* attributes: ContentType, ContentDisposition,
* ContentDescription, Size and LineCount. Other items may be
* included as well.
*/
public static final Item CONTENT_INFO = new Item("CONTENT_INFO");
/**
* SIZE is a fetch profile item that can be included in a
* <code>FetchProfile</code> during a fetch request to a Folder.
* This item indicates that the sizes of the messages in the specified
* range should be prefetched.
*
* @since JavaMail 1.5
*/
public static final Item SIZE = new Item("SIZE");
/**
* This is the Flags item.
*/
public static final Item FLAGS = new Item("FLAGS");
private String name;
/**
* Constructor for an item. The name is used only for debugging.
*
* @param name the item name
*/
protected Item(String name) {
this.name = name;
}
/**
* Include the name in the toString return value for debugging.
*/
@Override
public String toString() {
return getClass().getName() + "[" + name + "]";
}
}
}

View file

@ -0,0 +1,667 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Vector;
/**
* The Flags class represents the set of flags on a Message. Flags
* are composed of predefined system flags, and user defined flags. <p>
* <p>
* A System flag is represented by the <code>Flags.Flag</code>
* inner class. A User defined flag is represented as a String.
* User flags are case-independent. <p>
* <p>
* A set of standard system flags are predefined. Most folder
* implementations are expected to support these flags. Some
* implementations may also support arbitrary user-defined flags. The
* <code>getPermanentFlags</code> method on a Folder returns a Flags
* object that holds all the flags that are supported by that folder
* implementation. <p>
* <p>
* A Flags object is serializable so that (for example) the
* use of Flags objects in search terms can be serialized
* along with the search terms. <p>
*
* <strong>Warning:</strong>
* Serialized objects of this class may not be compatible with future
* Jakarta Mail API releases. The current serialization support is
* appropriate for short term storage. <p>
* <p>
* The below code sample illustrates how to set, examine, and get the
* flags for a message.
* <pre>
*
* Message m = folder.getMessage(1);
* m.setFlag(Flags.Flag.DELETED, true); // set the DELETED flag
*
* // Check if DELETED flag is set on this message
* if (m.isSet(Flags.Flag.DELETED))
* System.out.println("DELETED message");
*
* // Examine ALL system flags for this message
* Flags flags = m.getFlags();
* Flags.Flag[] sf = flags.getSystemFlags();
* for (int i = 0; i &lt; sf.length; i++) {
* if (sf[i] == Flags.Flag.DELETED)
* System.out.println("DELETED message");
* else if (sf[i] == Flags.Flag.SEEN)
* System.out.println("SEEN message");
* ......
* ......
* }
* </pre>
*
* @author John Mani
* @author Bill Shannon
* @see Folder#getPermanentFlags
*/
public class Flags {
private final static int ANSWERED_BIT = 0x01;
private final static int DELETED_BIT = 0x02;
private final static int DRAFT_BIT = 0x04;
private final static int FLAGGED_BIT = 0x08;
private final static int RECENT_BIT = 0x10;
private final static int SEEN_BIT = 0x20;
private final static int USER_BIT = 0x80000000;
private int system_flags = 0;
// used as a case-independent Set that preserves the original case,
// the key is the lowercase flag name and the value is the original
private Hashtable<String, String> user_flags = null;
/**
* Construct an empty Flags object.
*/
public Flags() {
}
/**
* Construct a Flags object initialized with the given flags.
*
* @param flags the flags for initialization
*/
@SuppressWarnings("unchecked")
public Flags(Flags flags) {
this.system_flags = flags.system_flags;
if (flags.user_flags != null)
this.user_flags = (Hashtable<String, String>) flags.user_flags.clone();
}
/**
* Construct a Flags object initialized with the given system flag.
*
* @param flag the flag for initialization
*/
public Flags(Flag flag) {
this.system_flags |= flag.bit;
}
/**
* Construct a Flags object initialized with the given user flag.
*
* @param flag the flag for initialization
*/
public Flags(String flag) {
user_flags = new Hashtable<>(1);
user_flags.put(flag.toLowerCase(Locale.ENGLISH), flag);
}
/**
* Add the specified system flag to this Flags object.
*
* @param flag the flag to add
*/
public void add(Flag flag) {
system_flags |= flag.bit;
}
/**
* Add the specified user flag to this Flags object.
*
* @param flag the flag to add
*/
public void add(String flag) {
if (user_flags == null)
user_flags = new Hashtable<>(1);
user_flags.put(flag.toLowerCase(Locale.ENGLISH), flag);
}
/**
* Add all the flags in the given Flags object to this
* Flags object.
*
* @param f Flags object
*/
public void add(Flags f) {
system_flags |= f.system_flags; // add system flags
if (f.user_flags != null) { // add user-defined flags
if (user_flags == null)
user_flags = new Hashtable<>(1);
Enumeration<String> e = f.user_flags.keys();
while (e.hasMoreElements()) {
String s = e.nextElement();
user_flags.put(s, f.user_flags.get(s));
}
}
}
/**
* Remove the specified system flag from this Flags object.
*
* @param flag the flag to be removed
*/
public void remove(Flag flag) {
system_flags &= ~flag.bit;
}
/**
* Remove the specified user flag from this Flags object.
*
* @param flag the flag to be removed
*/
public void remove(String flag) {
if (user_flags != null)
user_flags.remove(flag.toLowerCase(Locale.ENGLISH));
}
/**
* Remove all flags in the given Flags object from this
* Flags object.
*
* @param f the flag to be removed
*/
public void remove(Flags f) {
system_flags &= ~f.system_flags; // remove system flags
if (f.user_flags != null) {
if (user_flags == null)
return;
Enumeration<String> e = f.user_flags.keys();
while (e.hasMoreElements())
user_flags.remove(e.nextElement());
}
}
/**
* Remove any flags <strong>not</strong> in the given Flags object.
* Useful for clearing flags not supported by a server. If the
* given Flags object includes the Flags.Flag.USER flag, all user
* flags in this Flags object are retained.
*
* @param f the flags to keep
* @return true if this Flags object changed
* @since JavaMail 1.6
*/
public boolean retainAll(Flags f) {
boolean changed = false;
int sf = system_flags & f.system_flags;
if (system_flags != sf) {
system_flags = sf;
changed = true;
}
// if we have user flags, and the USER flag is not set in "f",
// determine which user flags to clear
if (user_flags != null && (f.system_flags & USER_BIT) == 0) {
if (f.user_flags != null) {
Enumeration<String> e = user_flags.keys();
while (e.hasMoreElements()) {
String key = e.nextElement();
if (!f.user_flags.containsKey(key)) {
user_flags.remove(key);
changed = true;
}
}
} else {
// if anything in user_flags, throw them away
changed = user_flags.size() > 0;
user_flags = null;
}
}
return changed;
}
/**
* Check whether the specified system flag is present in this Flags object.
*
* @param flag the flag to test
* @return true of the given flag is present, otherwise false.
*/
public boolean contains(Flag flag) {
return (system_flags & flag.bit) != 0;
}
/**
* Check whether the specified user flag is present in this Flags object.
*
* @param flag the flag to test
* @return true of the given flag is present, otherwise false.
*/
public boolean contains(String flag) {
if (user_flags == null)
return false;
else
return user_flags.containsKey(flag.toLowerCase(Locale.ENGLISH));
}
/**
* Check whether all the flags in the specified Flags object are
* present in this Flags object.
*
* @param f the flags to test
* @return true if all flags in the given Flags object are present,
* otherwise false.
*/
public boolean contains(Flags f) {
// Check system flags
if ((f.system_flags & system_flags) != f.system_flags)
return false;
// Check user flags
if (f.user_flags != null) {
if (user_flags == null)
return false;
Enumeration<String> e = f.user_flags.keys();
while (e.hasMoreElements()) {
if (!user_flags.containsKey(e.nextElement()))
return false;
}
}
// If we've made it till here, return true
return true;
}
/**
* Check whether the two Flags objects are equal.
*
* @return true if they're equal
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Flags))
return false;
Flags f = (Flags) obj;
// Check system flags
if (f.system_flags != this.system_flags)
return false;
// Check user flags
int size = this.user_flags == null ? 0 : this.user_flags.size();
int fsize = f.user_flags == null ? 0 : f.user_flags.size();
if (size == 0 && fsize == 0)
return true;
if (f.user_flags != null && this.user_flags != null && fsize == size)
return user_flags.keySet().equals(f.user_flags.keySet());
return false;
}
/**
* Compute a hash code for this Flags object.
*
* @return the hash code
*/
@Override
public int hashCode() {
int hash = system_flags;
if (user_flags != null) {
Enumeration<String> e = user_flags.keys();
while (e.hasMoreElements())
hash += e.nextElement().hashCode();
}
return hash;
}
/**
* Return all the system flags in this Flags object. Returns
* an array of size zero if no flags are set.
*
* @return array of Flags.Flag objects representing system flags
*/
public Flag[] getSystemFlags() {
Vector<Flag> v = new Vector<>();
if ((system_flags & ANSWERED_BIT) != 0)
v.addElement(Flag.ANSWERED);
if ((system_flags & DELETED_BIT) != 0)
v.addElement(Flag.DELETED);
if ((system_flags & DRAFT_BIT) != 0)
v.addElement(Flag.DRAFT);
if ((system_flags & FLAGGED_BIT) != 0)
v.addElement(Flag.FLAGGED);
if ((system_flags & RECENT_BIT) != 0)
v.addElement(Flag.RECENT);
if ((system_flags & SEEN_BIT) != 0)
v.addElement(Flag.SEEN);
if ((system_flags & USER_BIT) != 0)
v.addElement(Flag.USER);
Flag[] f = new Flag[v.size()];
v.copyInto(f);
return f;
}
/**
* Return all the user flags in this Flags object. Returns
* an array of size zero if no flags are set.
*
* @return array of Strings, each String represents a flag.
*/
public String[] getUserFlags() {
Vector<String> v = new Vector<>();
if (user_flags != null) {
Enumeration<String> e = user_flags.elements();
while (e.hasMoreElements())
v.addElement(e.nextElement());
}
String[] f = new String[v.size()];
v.copyInto(f);
return f;
}
/**
* Clear all of the system flags.
*
* @since JavaMail 1.6
*/
public void clearSystemFlags() {
system_flags = 0;
}
/**
* Clear all of the user flags.
*
* @since JavaMail 1.6
*/
public void clearUserFlags() {
user_flags = null;
}
/**
* Returns a clone of this Flags object.
*/
@SuppressWarnings("unchecked")
@Override
public Object clone() {
Flags f = null;
try {
f = (Flags) super.clone();
} catch (CloneNotSupportedException cex) {
// ignore, can't happen
}
if (this.user_flags != null)
f.user_flags = (Hashtable<String, String>) this.user_flags.clone();
return f;
}
/**
* Return a string representation of this Flags object.
* Note that the exact format of the string is subject to change.
*/
public String toString() {
StringBuilder sb = new StringBuilder();
if ((system_flags & ANSWERED_BIT) != 0)
sb.append("\\Answered ");
if ((system_flags & DELETED_BIT) != 0)
sb.append("\\Deleted ");
if ((system_flags & DRAFT_BIT) != 0)
sb.append("\\Draft ");
if ((system_flags & FLAGGED_BIT) != 0)
sb.append("\\Flagged ");
if ((system_flags & RECENT_BIT) != 0)
sb.append("\\Recent ");
if ((system_flags & SEEN_BIT) != 0)
sb.append("\\Seen ");
if ((system_flags & USER_BIT) != 0)
sb.append("\\* ");
boolean first = true;
if (user_flags != null) {
Enumeration<String> e = user_flags.elements();
while (e.hasMoreElements()) {
if (first)
first = false;
else
sb.append(' ');
sb.append(e.nextElement());
}
}
if (first && sb.length() > 0)
sb.setLength(sb.length() - 1); // smash trailing space
return sb.toString();
}
/**
* This inner class represents an individual system flag. A set
* of standard system flag objects are predefined here.
*/
public static final class Flag {
/**
* This message has been answered. This flag is set by clients
* to indicate that this message has been answered to.
*/
public static final Flag ANSWERED = new Flag(ANSWERED_BIT);
/**
* This message is marked deleted. Clients set this flag to
* mark a message as deleted. The expunge operation on a folder
* removes all messages in that folder that are marked for deletion.
*/
public static final Flag DELETED = new Flag(DELETED_BIT);
/**
* This message is a draft. This flag is set by clients
* to indicate that the message is a draft message.
*/
public static final Flag DRAFT = new Flag(DRAFT_BIT);
/**
* This message is flagged. No semantic is defined for this flag.
* Clients alter this flag.
*/
public static final Flag FLAGGED = new Flag(FLAGGED_BIT);
/**
* This message is recent. Folder implementations set this flag
* to indicate that this message is new to this folder, that is,
* it has arrived since the last time this folder was opened. <p>
* <p>
* Clients cannot alter this flag.
*/
public static final Flag RECENT = new Flag(RECENT_BIT);
/**
* This message is seen. This flag is implicitly set by the
* implementation when this Message's content is returned
* to the client in some form. The <code>getInputStream</code>
* and <code>getContent</code> methods on Message cause this
* flag to be set. <p>
* <p>
* Clients can alter this flag.
*/
public static final Flag SEEN = new Flag(SEEN_BIT);
/**
* A special flag that indicates that this folder supports
* user defined flags. <p>
* <p>
* The implementation sets this flag. Clients cannot alter
* this flag but can use it to determine if a folder supports
* user defined flags by using
* <code>folder.getPermanentFlags().contains(Flags.Flag.USER)</code>.
*/
public static final Flag USER = new Flag(USER_BIT);
// flags are stored as bits for efficiency
private int bit;
private Flag(int bit) {
this.bit = bit;
}
}
/*
public static void main(String argv[]) throws Exception {
// a new flags object
Flags f1 = new Flags();
f1.add(Flags.Flag.DELETED);
f1.add(Flags.Flag.SEEN);
f1.add(Flags.Flag.RECENT);
f1.add(Flags.Flag.ANSWERED);
// check copy constructor with only system flags
Flags fc = new Flags(f1);
if (f1.equals(fc) && fc.equals(f1))
System.out.println("success");
else
System.out.println("fail");
// check clone with only system flags
fc = (Flags)f1.clone();
if (f1.equals(fc) && fc.equals(f1))
System.out.println("success");
else
System.out.println("fail");
// add a user flag and make sure it still works right
f1.add("MyFlag");
// shouldn't be equal here
if (!f1.equals(fc) && !fc.equals(f1))
System.out.println("success");
else
System.out.println("fail");
// check clone
fc = (Flags)f1.clone();
if (f1.equals(fc) && fc.equals(f1))
System.out.println("success");
else
System.out.println("fail");
// make sure user flag hash tables are separate
fc.add("AnotherFlag");
if (!f1.equals(fc) && !fc.equals(f1))
System.out.println("success");
else
System.out.println("fail");
// check copy constructor
fc = new Flags(f1);
if (f1.equals(fc) && fc.equals(f1))
System.out.println("success");
else
System.out.println("fail");
// another new flags object
Flags f2 = new Flags(Flags.Flag.ANSWERED);
f2.add("MyFlag");
if (f1.contains(Flags.Flag.DELETED))
System.out.println("success");
else
System.out.println("fail");
if (f1.contains(Flags.Flag.SEEN))
System.out.println("success");
else
System.out.println("fail");
if (f1.contains(Flags.Flag.RECENT))
System.out.println("success");
else
System.out.println("fail");
if (f1.contains("MyFlag"))
System.out.println("success");
else
System.out.println("fail");
if (f2.contains(Flags.Flag.ANSWERED))
System.out.println("success");
else
System.out.println("fail");
System.out.println("----------------");
String[] s = f1.getUserFlags();
for (int i = 0; i < s.length; i++)
System.out.println(s[i]);
System.out.println("----------------");
s = f2.getUserFlags();
for (int i = 0; i < s.length; i++)
System.out.println(s[i]);
System.out.println("----------------");
if (f1.contains(f2)) // this should be true
System.out.println("success");
else
System.out.println("fail");
if (!f2.contains(f1)) // this should be false
System.out.println("success");
else
System.out.println("fail");
Flags f3 = new Flags();
f3.add(Flags.Flag.DELETED);
f3.add(Flags.Flag.SEEN);
f3.add(Flags.Flag.RECENT);
f3.add(Flags.Flag.ANSWERED);
f3.add("ANOTHERFLAG");
f3.add("MYFLAG");
f1.add("AnotherFlag");
if (f1.equals(f3))
System.out.println("equals success");
else
System.out.println("fail");
if (f3.equals(f1))
System.out.println("equals success");
else
System.out.println("fail");
System.out.println("f1 hash code " + f1.hashCode());
System.out.println("f3 hash code " + f3.hashCode());
if (f1.hashCode() == f3.hashCode())
System.out.println("success");
else
System.out.println("fail");
}
****/
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* This exception is thrown when a method is invoked on a Messaging object
* and the Folder that owns that object has died due to some reason. <p>
* <p>
* Following the exception, the Folder is reset to the "closed" state.
* All messaging objects owned by the Folder should be considered invalid.
* The Folder can be reopened using the "open" method to reestablish the
* lost connection. <p>
* <p>
* The getMessage() method returns more detailed information about the
* error that caused this exception.
*
* @author John Mani
*/
@SuppressWarnings("serial")
public class FolderClosedException extends MessagingException {
transient private Folder folder;
/**
* Constructs a FolderClosedException.
*
* @param folder The Folder
*/
public FolderClosedException(Folder folder) {
this(folder, null);
}
/**
* Constructs a FolderClosedException with the specified
* detail message.
*
* @param folder The Folder
* @param message The detailed error message
*/
public FolderClosedException(Folder folder, String message) {
super(message);
this.folder = folder;
}
/**
* Constructs a FolderClosedException with the specified
* detail message and embedded exception. The exception is chained
* to this exception.
*
* @param folder The Folder
* @param message The detailed error message
* @param e The embedded exception
* @since JavaMail 1.5
*/
public FolderClosedException(Folder folder, String message, Exception e) {
super(message, e);
this.folder = folder;
}
/**
* Returns the dead Folder object
*
* @return the dead Folder object
*/
public Folder getFolder() {
return folder;
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* This exception is thrown by Folder methods, when those
* methods are invoked on a non existent folder.
*
* @author John Mani
*/
@SuppressWarnings("serial")
public class FolderNotFoundException extends MessagingException {
transient private Folder folder;
/**
* Constructs a FolderNotFoundException with no detail message.
*/
public FolderNotFoundException() {
super();
}
/**
* Constructs a FolderNotFoundException.
*
* @param folder The Folder
* @since JavaMail 1.2
*/
public FolderNotFoundException(Folder folder) {
super();
this.folder = folder;
}
/**
* Constructs a FolderNotFoundException with the specified
* detail message.
*
* @param folder The Folder
* @param s The detailed error message
* @since JavaMail 1.2
*/
public FolderNotFoundException(Folder folder, String s) {
super(s);
this.folder = folder;
}
/**
* Constructs a FolderNotFoundException with the specified
* detail message and embedded exception. The exception is chained
* to this exception.
*
* @param folder The Folder
* @param s The detailed error message
* @param e The embedded exception
* @since JavaMail 1.5
*/
public FolderNotFoundException(Folder folder, String s, Exception e) {
super(s, e);
this.folder = folder;
}
/**
* Constructs a FolderNotFoundException with the specified detail message
* and the specified folder.
*
* @param s The detail message
* @param folder The Folder
*/
public FolderNotFoundException(String s, Folder folder) {
super(s);
this.folder = folder;
}
/**
* Returns the offending Folder object.
*
* @return the Folder object. Note that the returned value can be
* <code>null</code>.
*/
public Folder getFolder() {
return folder;
}
}

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import java.util.Objects;
/**
* The Header class stores a name/value pair to represent headers.
*
* @author John Mani
*/
public class Header {
/**
* The name of the header.
*
* @since JavaMail 1.4
*/
protected String name;
/**
* The value of the header.
*
* @since JavaMail 1.4
*/
protected String value;
/**
* Construct a Header object.
*
* @param name name of the header
* @param value value of the header
*/
public Header(String name, String value) {
this.name = name;
this.value = value;
}
/**
* Returns the name of this header.
*
* @return name of the header
*/
public String getName() {
return name;
}
/**
* Returns the value of this header.
*
* @return value of the header
*/
public String getValue() {
return value;
}
@Override
public int hashCode() {
return Objects.hash(name, value);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj == null) {
return false;
} else if (getClass() != obj.getClass()) {
return false;
} else {
Header other = (Header) obj;
return Objects.equals(name, other.getName()) && Objects.equals(value, other.getValue());
}
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* The exception thrown when a write is attempted on a read-only attribute
* of any Messaging object.
*
* @author John Mani
*/
@SuppressWarnings("serial")
public class IllegalWriteException extends MessagingException {
/**
* Constructs an IllegalWriteException with no detail message.
*/
public IllegalWriteException() {
super();
}
/**
* Constructs an IllegalWriteException with the specified
* detail message.
*
* @param s The detailed error message
*/
public IllegalWriteException(String s) {
super(s);
}
/**
* Constructs an IllegalWriteException with the specified
* detail message and embedded exception. The exception is chained
* to this exception.
*
* @param s The detailed error message
* @param e The embedded exception
* @since JavaMail 1.5
*/
public IllegalWriteException(String s, Exception e) {
super(s, e);
}
}

View file

@ -0,0 +1,432 @@
/*
* Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A simplified logger used by Jakarta Mail to handle logging to a
* PrintStream and logging through a java.util.logging.Logger.
* If debug is set, messages are written to the PrintStream and
* prefixed by the specified prefix (which is not included in
* Logger messages).
* Messages are logged by the Logger based on the configuration
* of the logging system.
*/
/*
* It would be so much simpler to just subclass Logger and override
* the log(LogRecord) method, as the javadocs suggest, but that doesn't
* work because Logger makes the decision about whether to log the message
* or not before calling the log(LogRecord) method. Instead, we just
* provide the few log methods we need here.
*/
final class MailLogger {
/**
* For log messages.
*/
private final Logger logger;
/**
* For debug output.
*/
private final String prefix;
/**
* Produce debug output?
*/
private final boolean debug;
/**
* Stream for debug output.
*/
private final PrintStream out;
/**
* Construct a new MailLogger using the specified Logger name,
* debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
*
* @param name the Logger name
* @param prefix the prefix for debug output, or null for none
* @param debug if true, write to PrintStream
* @param out the PrintStream to write to
*/
public MailLogger(String name, String prefix, boolean debug,
PrintStream out) {
logger = Logger.getLogger(name);
this.prefix = prefix;
this.debug = debug;
this.out = out != null ? out : System.out;
}
/**
* Construct a new MailLogger using the specified class' package
* name as the Logger name,
* debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
*
* @param clazz the Logger name is the package name of this class
* @param prefix the prefix for debug output, or null for none
* @param debug if true, write to PrintStream
* @param out the PrintStream to write to
*/
public MailLogger(Class<?> clazz, String prefix, boolean debug,
PrintStream out) {
String name = packageOf(clazz);
logger = Logger.getLogger(name);
this.prefix = prefix;
this.debug = debug;
this.out = out != null ? out : System.out;
}
/**
* Construct a new MailLogger using the specified class' package
* name combined with the specified subname as the Logger name,
* debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
*
* @param clazz the Logger name is the package name of this class
* @param subname the Logger name relative to this Logger name
* @param prefix the prefix for debug output, or null for none
* @param debug if true, write to PrintStream
* @param out the PrintStream to write to
*/
public MailLogger(Class<?> clazz, String subname, String prefix, boolean debug,
PrintStream out) {
String name = packageOf(clazz) + "." + subname;
logger = Logger.getLogger(name);
this.prefix = prefix;
this.debug = debug;
this.out = out != null ? out : System.out;
}
/**
* Construct a new MailLogger using the specified Logger name and
* debug prefix (e.g., "DEBUG"). Get the debug flag and PrintStream
* from the Session.
*
* @param name the Logger name
* @param prefix the prefix for debug output, or null for none
* @param session where to get the debug flag and PrintStream
*/
@Deprecated
public MailLogger(String name, String prefix, Session session) {
this(name, prefix, session.getDebug(), session.getDebugOut());
}
/**
* Construct a new MailLogger using the specified class' package
* name as the Logger name and the specified
* debug prefix (e.g., "DEBUG"). Get the debug flag and PrintStream
* from the Session.
*
* @param clazz the Logger name is the package name of this class
* @param prefix the prefix for debug output, or null for none
* @param session where to get the debug flag and PrintStream
*/
@Deprecated
public MailLogger(Class<?> clazz, String prefix, Session session) {
this(clazz, prefix, session.getDebug(), session.getDebugOut());
}
/**
* Create a MailLogger that uses a Logger with the specified name
* and prefix. The new MailLogger uses the same debug flag and
* PrintStream as this MailLogger.
*
* @param name the Logger name
* @param prefix the prefix for debug output, or null for none
* @return a MailLogger for the given name and prefix.
*/
public MailLogger getLogger(String name, String prefix) {
return new MailLogger(name, prefix, debug, out);
}
/**
* Create a MailLogger using the specified class' package
* name as the Logger name and the specified prefix.
* The new MailLogger uses the same debug flag and
* PrintStream as this MailLogger.
*
* @param clazz the Logger name is the package name of this class
* @param prefix the prefix for debug output, or null for none
* @return a MailLogger for the given name and prefix.
*/
public MailLogger getLogger(Class<?> clazz, String prefix) {
return new MailLogger(clazz, prefix, debug, out);
}
/**
* Create a MailLogger that uses a Logger whose name is composed
* of this MailLogger's name plus the specified sub-name, separated
* by a dot. The new MailLogger uses the new prefix for debug output.
* This is used primarily by the protocol trace code that wants a
* different prefix (none).
*
* @param subname the Logger name relative to this Logger name
* @param prefix the prefix for debug output, or null for none
* @return a MailLogger for the given name and prefix.
*/
public MailLogger getSubLogger(String subname, String prefix) {
return new MailLogger(logger.getName() + "." + subname, prefix,
debug, out);
}
/**
* Create a MailLogger that uses a Logger whose name is composed
* of this MailLogger's name plus the specified sub-name, separated
* by a dot. The new MailLogger uses the new prefix for debug output.
* This is used primarily by the protocol trace code that wants a
* different prefix (none).
*
* @param subname the Logger name relative to this Logger name
* @param prefix the prefix for debug output, or null for none
* @param debug the debug flag for the sub-logger
* @return a MailLogger for the given name and prefix.
*/
public MailLogger getSubLogger(String subname, String prefix,
boolean debug) {
return new MailLogger(logger.getName() + "." + subname, prefix,
debug, out);
}
/**
* Log the message at the specified level.
*
* @param level the log level.
* @param msg the message.
*/
public void log(Level level, String msg) {
ifDebugOut(msg);
if (logger.isLoggable(level)) {
final StackTraceElement frame = inferCaller();
logger.logp(level, frame.getClassName(), frame.getMethodName(), msg);
}
}
/**
* Log the message at the specified level.
*
* @param level the log level.
* @param msg the message.
* @param param1 the additional parameter.
*/
public void log(Level level, String msg, Object param1) {
if (debug) {
msg = MessageFormat.format(msg, param1);
debugOut(msg);
}
if (logger.isLoggable(level)) {
final StackTraceElement frame = inferCaller();
logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, param1);
}
}
/**
* Log the message at the specified level.
*
* @param level the log level.
* @param msg the message.
* @param params the message parameters.
*/
public void log(Level level, String msg, Object... params) {
if (debug) {
msg = MessageFormat.format(msg, params);
debugOut(msg);
}
if (logger.isLoggable(level)) {
final StackTraceElement frame = inferCaller();
logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, params);
}
}
/**
* Log the message at the specified level using a format string.
*
* @param level the log level.
* @param msg the message format string.
* @param params the message parameters.
* @since JavaMail 1.5.4
*/
public void logf(Level level, String msg, Object... params) {
msg = String.format(msg, params);
ifDebugOut(msg);
logger.log(level, msg);
}
/**
* Log the message at the specified level.
*
* @param level the log level.
* @param msg the message.
* @param thrown the throwable to log.
*/
public void log(Level level, String msg, Throwable thrown) {
if (debug) {
if (thrown != null) {
debugOut(msg + ", THROW: ");
thrown.printStackTrace(out);
} else {
debugOut(msg);
}
}
if (logger.isLoggable(level)) {
final StackTraceElement frame = inferCaller();
logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, thrown);
}
}
/**
* Log a message at the CONFIG level.
*
* @param msg the message.
*/
public void config(String msg) {
log(Level.CONFIG, msg);
}
/**
* Log a message at the FINE level.
*
* @param msg the message.
*/
public void fine(String msg) {
log(Level.FINE, msg);
}
/**
* Log a message at the FINER level.
*
* @param msg the message.
*/
public void finer(String msg) {
log(Level.FINER, msg);
}
/**
* Log a message at the FINEST level.
*
* @param msg the message.
*/
public void finest(String msg) {
log(Level.FINEST, msg);
}
/**
* If "debug" is set, or our embedded Logger is loggable at the
* given level, return true.
*
* @param level the log level.
* @return true if loggable.
*/
public boolean isLoggable(Level level) {
return debug || logger.isLoggable(level);
}
/**
* Common code to conditionally log debug statements.
*
* @param msg the message to log.
*/
private void ifDebugOut(String msg) {
if (debug)
debugOut(msg);
}
/**
* Common formatting for debug output.
*
* @param msg the message to log.
*/
private void debugOut(String msg) {
if (prefix != null)
out.println(prefix + ": " + msg);
else
out.println(msg);
}
/**
* Return the package name of the class.
* Sometimes there will be no Package object for the class,
* e.g., if the class loader hasn't created one (see Class.getPackage()).
*
* @param clazz the class source.
* @return the package name or an empty string.
*/
private String packageOf(Class<?> clazz) {
Package p = clazz.getPackage();
if (p != null)
return p.getName(); // hopefully the common case
String cname = clazz.getName();
int i = cname.lastIndexOf('.');
if (i > 0)
return cname.substring(0, i);
// no package name, now what?
return "";
}
/**
* A disadvantage of not being able to use Logger directly in Jakarta Mail
* code is that the "source class" information that Logger guesses will
* always refer to this class instead of our caller. This method
* duplicates what Logger does to try to find *our* caller, so that
* Logger doesn't have to do it (and get the wrong answer), and because
* our caller is what's wanted.
*
* @return StackTraceElement that logged the message. Treat as read-only.
*/
private StackTraceElement inferCaller() {
// Get the stack trace.
StackTraceElement[] stack = (new Throwable()).getStackTrace();
// First, search back to a method in the Logger class.
int ix = 0;
while (ix < stack.length) {
StackTraceElement frame = stack[ix];
String cname = frame.getClassName();
if (isLoggerImplFrame(cname)) {
break;
}
ix++;
}
// Now search for the first frame before the "Logger" class.
while (ix < stack.length) {
StackTraceElement frame = stack[ix];
String cname = frame.getClassName();
if (!isLoggerImplFrame(cname)) {
// We've found the relevant frame.
return frame;
}
ix++;
}
// We haven't found a suitable frame, so just punt. This is
// OK as we are only committed to making a "best effort" here.
return new StackTraceElement(MailLogger.class.getName(), "log",
MailLogger.class.getName(), -1);
}
/**
* Frames to ignore as part of the MailLogger to JUL bridge.
*
* @param cname the class name.
* @return true if the class name is part of the MailLogger bridge.
*/
private boolean isLoggerImplFrame(String cname) {
return MailLogger.class.getName().equals(cname);
}
}

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation used by Jakarta EE applications to define a <code>MailSession</code>
* to be registered with JNDI. The <code>MailSession</code> may be configured
* by setting the annotation elements for commonly used <code>Session</code>
* properties. Additional standard and vendor-specific properties may be
* specified using the <code>properties</code> element.
* <p>
* The session will be registered under the name specified in the
* <code>name</code> element. It may be defined to be in any valid
* <code>Jakarta EE</code> namespace, and will determine the accessibility of
* the session from other components.
*
* @since JavaMail 1.5
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MailSessionDefinitions.class)
public @interface MailSessionDefinition {
/**
* Description of this mail session.
*
* @return the description
*/
String description() default "";
/**
* JNDI name by which the mail session will be registered.
*
* @return the JNDI name
*/
String name();
/**
* Store protocol name.
*
* @return the store protocol name
*/
String storeProtocol() default "";
/**
* Transport protocol name.
*
* @return the transport protocol name
*/
String transportProtocol() default "";
/**
* Host name for the mail server.
*
* @return the host name
*/
String host() default "";
/**
* User name to use for authentication.
*
* @return the user name
*/
String user() default "";
/**
* Password to use for authentication.
*
* @return the password
*/
String password() default "";
/**
* From address for the user.
*
* @return the from address
*/
String from() default "";
/**
* Properties to include in the Session.
* Properties are specified using the format:
* <i>propertyName=propertyValue</i> with one property per array element.
*
* @return the properties
*/
String[] properties() default {};
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Declares one or more <code>MailSessionDefinition</code> annotations.
*
* @see MailSessionDefinition
* @since JavaMail 1.5
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MailSessionDefinitions {
MailSessionDefinition[] value();
}

View file

@ -0,0 +1,678 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import jakarta.mail.search.SearchTerm;
import java.util.Date;
/**
* This class models an email message. This is an abstract class.
* Subclasses provide actual implementations. <p>
* <p>
* Message implements the Part interface. Message contains a set of
* attributes and a "content". Messages within a folder also have a
* set of flags that describe its state within the folder.<p>
* <p>
* Message defines some new attributes in addition to those defined
* in the <code>Part</code> interface. These attributes specify meta-data
* for the message - i.e., addressing and descriptive information about
* the message. <p>
* <p>
* Message objects are obtained either from a Folder or by constructing
* a new Message object of the appropriate subclass. Messages that have
* been received are normally retrieved from a folder named "INBOX". <p>
* <p>
* A Message object obtained from a folder is just a lightweight
* reference to the actual message. The Message is 'lazily' filled
* up (on demand) when each item is requested from the message. Note
* that certain folder implementations may return Message objects that
* are pre-filled with certain user-specified items.
* <p>
* To send a message, an appropriate subclass of Message (e.g.,
* MimeMessage) is instantiated, the attributes and content are
* filled in, and the message is sent using the <code>Transport.send</code>
* method.
*
* @author John Mani
* @author Bill Shannon
* @author Max Spivak
* @see Part
*/
public abstract class Message implements Part {
/**
* The number of this message within its folder, or zero if
* the message was not retrieved from a folder.
*/
protected int msgnum = 0;
/**
* True if this message has been expunged.
*/
protected boolean expunged = false;
/**
* The containing folder, if this message is obtained from a folder
*/
protected Folder folder = null;
/**
* The Session object for this Message
*/
protected Session session = null;
/**
* No-arg version of the constructor.
*/
protected Message() {
}
/**
* Constructor that takes a Folder and a message number.
* Used by Folder implementations.
*
* @param folder containing folder
* @param msgnum this message's sequence number within this folder
*/
protected Message(Folder folder, int msgnum) {
this.folder = folder;
this.msgnum = msgnum;
session = folder.store.session;
}
/**
* Constructor that takes a Session. Used for client created
* Message objects.
*
* @param session A Session object
*/
protected Message(Session session) {
this.session = session;
}
/**
* Return the Session used when this message was created.
*
* @return the message's Session
* @since JavaMail 1.5
*/
public Session getSession() {
return session;
}
/**
* Returns the "From" attribute. The "From" attribute contains
* the identity of the person(s) who wished this message to
* be sent. <p>
* <p>
* In certain implementations, this may be different
* from the entity that actually sent the message. <p>
* <p>
* This method returns <code>null</code> if this attribute
* is not present in this message. Returns an empty array if
* this attribute is present, but contains no addresses.
*
* @return array of Address objects
* @throws MessagingException for failures
*/
public abstract Address[] getFrom() throws MessagingException;
/**
* Set the "From" attribute in this Message.
*
* @param address the sender
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws MessagingException for other failures
*/
public abstract void setFrom(Address address)
throws MessagingException;
/**
* Set the "From" attribute in this Message. The value of this
* attribute is obtained from the property "mail.user". If this
* property is absent, the system property "user.name" is used.
*
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws MessagingException for other failures
*/
public abstract void setFrom() throws MessagingException;
/**
* Add these addresses to the existing "From" attribute
*
* @param addresses the senders
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws MessagingException for other failures
*/
public abstract void addFrom(Address[] addresses)
throws MessagingException;
/**
* Get all the recipient addresses of the given type. <p>
* <p>
* This method returns <code>null</code> if no recipients of
* the given type are present in this message. It may return an
* empty array if the header is present, but contains no addresses.
*
* @param type the recipient type
* @return array of Address objects
* @throws MessagingException for failures
* @see RecipientType#TO
* @see RecipientType#CC
* @see RecipientType#BCC
*/
public abstract Address[] getRecipients(RecipientType type)
throws MessagingException;
/**
* Get all the recipient addresses for the message.
* The default implementation extracts the TO, CC, and BCC
* recipients using the <code>getRecipients</code> method. <p>
* <p>
* This method returns <code>null</code> if none of the recipient
* headers are present in this message. It may Return an empty array
* if any recipient header is present, but contains no addresses.
*
* @return array of Address objects
* @throws MessagingException for failures
* @see RecipientType#TO
* @see RecipientType#CC
* @see RecipientType#BCC
* @see #getRecipients
*/
public Address[] getAllRecipients() throws MessagingException {
Address[] to = getRecipients(RecipientType.TO);
Address[] cc = getRecipients(RecipientType.CC);
Address[] bcc = getRecipients(RecipientType.BCC);
if (cc == null && bcc == null)
return to; // a common case
int numRecip =
(to != null ? to.length : 0) +
(cc != null ? cc.length : 0) +
(bcc != null ? bcc.length : 0);
Address[] addresses = new Address[numRecip];
int pos = 0;
if (to != null) {
System.arraycopy(to, 0, addresses, pos, to.length);
pos += to.length;
}
if (cc != null) {
System.arraycopy(cc, 0, addresses, pos, cc.length);
pos += cc.length;
}
if (bcc != null) {
System.arraycopy(bcc, 0, addresses, pos, bcc.length);
// pos += bcc.length;
}
return addresses;
}
/**
* Set the recipient addresses. All addresses of the specified
* type are replaced by the addresses parameter.
*
* @param type the recipient type
* @param addresses the addresses
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws MessagingException for other failures
*/
public abstract void setRecipients(RecipientType type, Address[] addresses)
throws MessagingException;
/**
* Set the recipient address. All addresses of the specified
* type are replaced by the address parameter. <p>
* <p>
* The default implementation uses the <code>setRecipients</code> method.
*
* @param type the recipient type
* @param address the address
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws MessagingException for other failures
*/
public void setRecipient(RecipientType type, Address address)
throws MessagingException {
if (address == null)
setRecipients(type, null);
else {
Address[] a = new Address[1];
a[0] = address;
setRecipients(type, a);
}
}
/**
* Add these recipient addresses to the existing ones of the given type.
*
* @param type the recipient type
* @param addresses the addresses
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws MessagingException for other failures
*/
public abstract void addRecipients(RecipientType type, Address[] addresses)
throws MessagingException;
/**
* Add this recipient address to the existing ones of the given type. <p>
* <p>
* The default implementation uses the <code>addRecipients</code> method.
*
* @param type the recipient type
* @param address the address
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws MessagingException for other failures
*/
public void addRecipient(RecipientType type, Address address)
throws MessagingException {
Address[] a = new Address[1];
a[0] = address;
addRecipients(type, a);
}
/**
* Get the addresses to which replies should be directed.
* This will usually be the sender of the message, but
* some messages may direct replies to a different address. <p>
* <p>
* The default implementation simply calls the <code>getFrom</code>
* method. <p>
* <p>
* This method returns <code>null</code> if the corresponding
* header is not present. Returns an empty array if the header
* is present, but contains no addresses.
*
* @return addresses to which replies should be directed
* @throws MessagingException for failures
* @see #getFrom
*/
public Address[] getReplyTo() throws MessagingException {
return getFrom();
}
/**
* Set the addresses to which replies should be directed.
* (Normally only a single address will be specified.)
* Not all message types allow this to be specified separately
* from the sender of the message. <p>
* <p>
* The default implementation provided here just throws the
* MethodNotSupportedException.
*
* @param addresses addresses to which replies should be directed
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws MethodNotSupportedException if the underlying
* implementation does not support setting this
* attribute
* @throws MessagingException for other failures
*/
public void setReplyTo(Address[] addresses) throws MessagingException {
throw new MethodNotSupportedException("setReplyTo not supported");
}
/**
* Get the subject of this message.
*
* @return the subject
* @throws MessagingException for failures
*/
public abstract String getSubject() throws MessagingException;
/**
* Set the subject of this message.
*
* @param subject the subject
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws MessagingException for other failures
*/
public abstract void setSubject(String subject)
throws MessagingException;
/**
* Get the date this message was sent.
*
* @return the date this message was sent
* @throws MessagingException for failures
*/
public abstract Date getSentDate() throws MessagingException;
/**
* Set the sent date of this message.
*
* @param date the sent date of this message
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws MessagingException for other failures
*/
public abstract void setSentDate(Date date) throws MessagingException;
/**
* Get the date this message was received.
*
* @return the date this message was received
* @throws MessagingException for failures
*/
public abstract Date getReceivedDate() throws MessagingException;
/**
* Returns a <code>Flags</code> object containing the flags for
* this message. <p>
* <p>
* Modifying any of the flags in this returned Flags object will
* not affect the flags of this message. Use <code>setFlags()</code>
* to do that.
*
* @return Flags object containing the flags for this message
* @throws MessagingException for failures
* @see Flags
* @see #setFlags
*/
public abstract Flags getFlags() throws MessagingException;
/**
* Check whether the flag specified in the <code>flag</code>
* argument is set in this message. <p>
* <p>
* The default implementation uses <code>getFlags</code>.
*
* @param flag the flag
* @return value of the specified flag for this message
* @throws MessagingException for failures
* @see Flags.Flag#ANSWERED
* @see Flags.Flag#DELETED
* @see Flags.Flag#DRAFT
* @see Flags.Flag#FLAGGED
* @see Flags.Flag#RECENT
* @see Flags.Flag#SEEN
* @see Flags.Flag
*/
public boolean isSet(Flags.Flag flag) throws MessagingException {
return getFlags().contains(flag);
}
/**
* Set the specified flags on this message to the specified value.
* Note that any flags in this message that are not specified in
* the given <code>Flags</code> object are unaffected. <p>
* <p>
* This will result in a <code>MessageChangedEvent</code> being
* delivered to any MessageChangedListener registered on this
* Message's containing folder.
*
* @param flag Flags object containing the flags to be set
* @param set the value to be set
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values.
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws MessagingException for other failures
* @see jakarta.mail.event.MessageChangedEvent
*/
public abstract void setFlags(Flags flag, boolean set)
throws MessagingException;
/**
* Set the specified flag on this message to the specified value.
* <p>
* This will result in a <code>MessageChangedEvent</code> being
* delivered to any MessageChangedListener registered on this
* Message's containing folder. <p>
* <p>
* The default implementation uses the <code>setFlags</code> method.
*
* @param flag Flags.Flag object containing the flag to be set
* @param set the value to be set
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values.
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws MessagingException for other failures
* @see jakarta.mail.event.MessageChangedEvent
*/
public void setFlag(Flags.Flag flag, boolean set)
throws MessagingException {
Flags f = new Flags(flag);
setFlags(f, set);
}
/**
* Get the Message number for this Message.
* A Message object's message number is the relative
* position of this Message in its Folder. Note that the message
* number for a particular Message can change during a session
* if other messages in the Folder are deleted and expunged. <p>
* <p>
* Valid message numbers start at 1. Messages that do not belong
* to any folder (like newly composed or derived messages) have 0
* as their message number.
*
* @return the message number
*/
public int getMessageNumber() {
return msgnum;
}
/**
* Set the Message number for this Message. This method is
* invoked only by the implementation classes.
*
* @param msgnum the message number
*/
protected void setMessageNumber(int msgnum) {
this.msgnum = msgnum;
}
/**
* Get the folder from which this message was obtained. If
* this is a new message or nested message, this method returns
* null.
*
* @return the containing folder
*/
public Folder getFolder() {
return folder;
}
/**
* Checks whether this message is expunged. All other methods except
* <code>getMessageNumber()</code> are invalid on an expunged
* Message object. <p>
* <p>
* Messages that are expunged due to an explict <code>expunge()</code>
* request on the containing Folder are removed from the Folder
* immediately. Messages that are externally expunged by another source
* are marked "expunged" and return true for the isExpunged() method,
* but they are not removed from the Folder until an explicit
* <code>expunge()</code> is done on the Folder. <p>
* <p>
* See the description of <code>expunge()</code> for more details on
* expunge handling.
*
* @return true if the message is expunged
* @see Folder#expunge
*/
public boolean isExpunged() {
return expunged;
}
/**
* Sets the expunged flag for this Message. This method is to
* be used only by the implementation classes.
*
* @param expunged the expunged flag
*/
protected void setExpunged(boolean expunged) {
this.expunged = expunged;
}
/**
* Get a new Message suitable for a reply to this message.
* The new Message will have its attributes and headers
* set up appropriately. Note that this new message object
* will be empty, that is, it will <strong>not</strong> have a "content".
* These will have to be suitably filled in by the client. <p>
* <p>
* If <code>replyToAll</code> is set, the new Message will be addressed
* to all recipients of this message. Otherwise, the reply will be
* addressed to only the sender of this message (using the value
* of the <code>getReplyTo</code> method). <p>
* <p>
* The "Subject" field is filled in with the original subject
* prefixed with "Re:" (unless it already starts with "Re:"). <p>
* <p>
* The reply message will use the same session as this message.
*
* @param replyToAll reply should be sent to all recipients
* of this message
* @return the reply Message
* @throws MessagingException for failures
*/
public abstract Message reply(boolean replyToAll) throws MessagingException;
/**
* Save any changes made to this message into the message-store
* when the containing folder is closed, if the message is contained
* in a folder. (Some implementations may save the changes
* immediately.) Update any header fields to be consistent with the
* changed message contents. If any part of a message's headers or
* contents are changed, saveChanges must be called to ensure that
* those changes are permanent. If saveChanges is not called, any
* such modifications may or may not be saved, depending on the
* message store and folder implementation. <p>
* <p>
* Messages obtained from folders opened READ_ONLY should not be
* modified and saveChanges should not be called on such messages.
*
* @throws IllegalStateException if this message is
* obtained from a READ_ONLY folder.
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values.
* @throws MessagingException for other failures
*/
public abstract void saveChanges() throws MessagingException;
/**
* Apply the specified Search criterion to this message.
*
* @param term the Search criterion
* @return true if the Message matches this search
* criterion, false otherwise.
* @throws MessagingException for failures
* @see SearchTerm
*/
public boolean match(SearchTerm term) throws MessagingException {
return term.match(this);
}
/**
* This inner class defines the types of recipients allowed by
* the Message class. The currently defined types are TO,
* CC and BCC.
* <p>
* Note that this class only has a protected constructor, thereby
* restricting new Recipient types to either this class or subclasses.
* This effectively implements an enumeration of the allowed Recipient
* types.
* <p>
* The following code sample shows how to use this class to obtain
* the "TO" recipients from a message.
* <blockquote><pre>
*
* Message msg = folder.getMessages(1);
* Address[] a = m.getRecipients(Message.RecipientType.TO);
*
* </pre></blockquote>
*
* @see Message#getRecipients
* @see Message#setRecipients
* @see Message#addRecipients
*/
public static class RecipientType {
/**
* The "To" (primary) recipients.
*/
public static final RecipientType TO = new RecipientType("To");
/**
* The "Cc" (carbon copy) recipients.
*/
public static final RecipientType CC = new RecipientType("Cc");
/**
* The "Bcc" (blind carbon copy) recipients.
*/
public static final RecipientType BCC = new RecipientType("Bcc");
/**
* The type of recipient, usually the name of a corresponding
* Internet standard header.
*/
protected String type;
/**
* Constructor for use by subclasses.
*
* @param type the recipient type
*/
protected RecipientType(String type) {
this.type = type;
}
@Override
public String toString() {
return type;
}
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* An interface optionally implemented by <code>DataSources</code> to
* supply information to a <code>DataContentHandler</code> about the
* message context in which the data content object is operating.
*
* @see MessageContext
* @see jakarta.activation.DataSource
* @see jakarta.activation.DataContentHandler
* @since JavaMail 1.1
*/
public interface MessageAware {
/**
* Return the message context.
*
* @return the message context
*/
MessageContext getMessageContext();
}

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* The context in which a piece of Message content is contained. A
* <code>MessageContext</code> object is returned by the
* <code>getMessageContext</code> method of the
* <code>MessageAware</code> interface. <code>MessageAware</code> is
* typically implemented by <code>DataSources</code> to allow a
* <code>DataContentHandler</code> to pass on information about the
* context in which a data content object is operating.
*
* @see MessageAware
* @see jakarta.activation.DataSource
* @see jakarta.activation.DataContentHandler
* @since JavaMail 1.1
*/
public class MessageContext {
private Part part;
/**
* Create a MessageContext object describing the context of the given Part.
*
* @param part the Part
*/
public MessageContext(Part part) {
this.part = part;
}
/**
* Return the Message containing an arbitrary Part.
* Follows the parent chain up through containing Multipart
* objects until it comes to a Message object, or null.
*
* @return the containing Message, or null if none
* @see BodyPart#getParent
* @see Multipart#getParent
*/
private static Message getMessage(Part p) throws MessagingException {
while (p != null) {
if (p instanceof Message)
return (Message) p;
BodyPart bp = (BodyPart) p;
Multipart mp = bp.getParent();
if (mp == null) // MimeBodyPart might not be in a MimeMultipart
return null;
p = mp.getParent();
}
return null;
}
/**
* Return the Part that contains the content.
*
* @return the containing Part, or null if not known
*/
public Part getPart() {
return part;
}
/**
* Return the Message that contains the content.
* Follows the parent chain up through containing Multipart
* objects until it comes to a Message object, or null.
*
* @return the containing Message, or null if not known
*/
public Message getMessage() {
try {
return getMessage(part);
} catch (MessagingException ex) {
return null;
}
}
/**
* Return the Session we're operating in.
*
* @return the Session, or null if not known
*/
public Session getSession() {
Message msg = getMessage();
return msg != null ? msg.getSession() : null;
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* The exception thrown when an invalid method is invoked on an expunged
* Message. The only valid methods on an expunged Message are
* <code>isExpunged()</code> and <code>getMessageNumber()</code>.
*
* @author John Mani
* @see Message#isExpunged()
* @see Message#getMessageNumber()
*/
@SuppressWarnings("serial")
public class MessageRemovedException extends MessagingException {
/**
* Constructs a MessageRemovedException with no detail message.
*/
public MessageRemovedException() {
super();
}
/**
* Constructs a MessageRemovedException with the specified
* detail message.
*
* @param s The detailed error message
*/
public MessageRemovedException(String s) {
super(s);
}
/**
* Constructs a MessageRemovedException with the specified
* detail message and embedded exception. The exception is chained
* to this exception.
*
* @param s The detailed error message
* @param e The embedded exception
* @since JavaMail 1.5
*/
public MessageRemovedException(String s, Exception e) {
super(s, e);
}
}

View file

@ -0,0 +1,145 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* The base class for all exceptions thrown by the Messaging classes
*
* @author John Mani
* @author Bill Shannon
*/
@SuppressWarnings("serial")
public class MessagingException extends Exception {
/**
* The next exception in the chain.
*
* @serial
*/
private Exception next;
/**
* Constructs a MessagingException with no detail message.
*/
public MessagingException() {
super();
}
/**
* Constructs a MessagingException with the specified detail message.
*
* @param s the detail message
*/
public MessagingException(String s) {
super(s);
}
/**
* Constructs a MessagingException with the specified
* Exception and detail message. The specified exception is chained
* to this exception.
*
* @param s the detail message
* @param e the embedded exception
* @see #getNextException
* @see #setNextException
* @see #getCause
*/
public MessagingException(String s, Exception e) {
super(s);
next = e;
}
/**
* Get the next exception chained to this one. If the
* next exception is a MessagingException, the chain
* may extend further.
*
* @return next Exception, null if none.
*/
public synchronized Exception getNextException() {
return next;
}
/**
* Overrides the <code>getCause</code> method of <code>Throwable</code>
* to return the next exception in the chain of nested exceptions.
*
* @return next Exception, null if none.
*/
@Override
public synchronized Throwable getCause() {
return next;
}
/**
* Add an exception to the end of the chain. If the end
* is <strong>not</strong> a MessagingException, this
* exception cannot be added to the end.
*
* @param ex the new end of the Exception chain
* @return <code>true</code> if this Exception
* was added, <code>false</code> otherwise.
*/
public synchronized boolean setNextException(Exception ex) {
Exception theEnd = this;
while (theEnd instanceof MessagingException &&
((MessagingException) theEnd).next != null) {
theEnd = ((MessagingException) theEnd).next;
}
// If the end is a MessagingException, we can add this
// exception to the chain.
if (theEnd instanceof MessagingException) {
((MessagingException) theEnd).next = ex;
return true;
} else
return false;
}
/**
* Override toString method to provide information on
* nested exceptions.
*/
@Override
public synchronized String toString() {
String s = super.toString();
Exception n = next;
if (n == null)
return s;
StringBuilder sb = new StringBuilder(s == null ? "" : s);
while (n != null) {
sb.append(";\n nested exception is:\n\t");
if (n instanceof MessagingException) {
MessagingException mex = (MessagingException) n;
sb.append(mex.superToString());
n = mex.next;
} else {
sb.append(n);
n = null;
}
}
return sb.toString();
}
/**
* Return the "toString" information for this exception,
* without any information on nested exceptions.
*/
private final String superToString() {
return super.toString();
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* The exception thrown when a method is not supported by the
* implementation
*
* @author John Mani
*/
@SuppressWarnings("serial")
public class MethodNotSupportedException extends MessagingException {
/**
* Constructs a MethodNotSupportedException with no detail message.
*/
public MethodNotSupportedException() {
super();
}
/**
* Constructs a MethodNotSupportedException with the specified
* detail message.
*
* @param s The detailed error message
*/
public MethodNotSupportedException(String s) {
super(s);
}
/**
* Constructs a MethodNotSupportedException with the specified
* detail message and embedded exception. The exception is chained
* to this exception.
*
* @param s The detailed error message
* @param e The embedded exception
* @since JavaMail 1.5
*/
public MethodNotSupportedException(String s, Exception e) {
super(s, e);
}
}

View file

@ -0,0 +1,265 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import jakarta.mail.util.StreamProvider;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Vector;
/**
* Multipart is a container that holds multiple body parts. Multipart
* provides methods to retrieve and set its subparts. <p>
* <p>
* Multipart also acts as the base class for the content object returned
* by most Multipart DataContentHandlers. For example, invoking getContent()
* on a DataHandler whose source is a "multipart/signed" data source may
* return an appropriate subclass of Multipart. <p>
* <p>
* Some messaging systems provide different subtypes of Multiparts. For
* example, MIME specifies a set of subtypes that include "alternative",
* "mixed", "related", "parallel", "signed", etc. <p>
* <p>
* Multipart is an abstract class. Subclasses provide actual implementations.
*
* @author John Mani
*/
public abstract class Multipart {
/**
* Instance of stream provider.
*
* @since JavaMail 2.1
*/
protected final StreamProvider streamProvider = StreamProvider.provider();
/**
* Vector of BodyPart objects.
*/
protected Vector<BodyPart> parts = new Vector<>(); // Holds BodyParts
/**
* This field specifies the content-type of this multipart
* object. It defaults to "multipart/mixed".
*/
protected String contentType = "multipart/mixed"; // Content-Type
/**
* The <code>Part</code> containing this <code>Multipart</code>,
* if known.
*
* @since JavaMail 1.1
*/
protected Part parent;
/**
* Default constructor. An empty Multipart object is created.
*/
protected Multipart() {
}
/**
* Setup this Multipart object from the given MultipartDataSource. <p>
* <p>
* The method adds the MultipartDataSource's BodyPart
* objects into this Multipart. This Multipart's contentType is
* set to that of the MultipartDataSource. <p>
* <p>
* This method is typically used in those cases where one
* has a multipart data source that has already been pre-parsed into
* the individual body parts (for example, an IMAP datasource), but
* needs to create an appropriate Multipart subclass that represents
* a specific multipart subtype.
*
* @param mp Multipart datasource
* @throws MessagingException for failures
*/
protected synchronized void setMultipartDataSource(MultipartDataSource mp)
throws MessagingException {
contentType = mp.getContentType();
int count = mp.getCount();
for (int i = 0; i < count; i++)
addBodyPart(mp.getBodyPart(i));
}
/**
* Return the content-type of this Multipart. <p>
* <p>
* This implementation just returns the value of the
* <code>contentType</code> field.
*
* @return content-type
* @see #contentType
*/
public synchronized String getContentType() {
return contentType;
}
/**
* Return the number of enclosed BodyPart objects.
*
* @return number of parts
* @throws MessagingException for failures
* @see #parts
*/
public synchronized int getCount() throws MessagingException {
if (parts == null)
return 0;
return parts.size();
}
/**
* Get the specified Part. Parts are numbered starting at 0.
*
* @param index the index of the desired Part
* @return the Part
* @throws IndexOutOfBoundsException if the given index
* is out of range.
* @throws MessagingException for other failures
*/
public synchronized BodyPart getBodyPart(int index)
throws MessagingException {
if (parts == null)
throw new IndexOutOfBoundsException("No such BodyPart");
return parts.elementAt(index);
}
/**
* Remove the specified part from the multipart message.
* Shifts all the parts after the removed part down one.
*
* @param part The part to remove
* @return true if part removed, false otherwise
* @throws MessagingException if no such Part exists
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
*/
public synchronized boolean removeBodyPart(BodyPart part)
throws MessagingException {
if (parts == null)
throw new MessagingException("No such body part");
boolean ret = parts.removeElement(part);
part.setParent(null);
return ret;
}
/**
* Remove the part at specified location (starting from 0).
* Shifts all the parts after the removed part down one.
*
* @param index Index of the part to remove
* @throws IndexOutOfBoundsException if the given index
* is out of range.
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws MessagingException for other failures
*/
public synchronized void removeBodyPart(int index)
throws MessagingException {
if (parts == null)
throw new IndexOutOfBoundsException("No such BodyPart");
BodyPart part = parts.elementAt(index);
parts.removeElementAt(index);
part.setParent(null);
}
/**
* Adds a Part to the multipart. The BodyPart is appended to
* the list of existing Parts.
*
* @param part The Part to be appended
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws MessagingException for other failures
*/
public synchronized void addBodyPart(BodyPart part)
throws MessagingException {
if (parts == null)
parts = new Vector<>();
parts.addElement(part);
part.setParent(this);
}
/**
* Adds a BodyPart at position <code>index</code>.
* If <code>index</code> is not the last one in the list,
* the subsequent parts are shifted up. If <code>index</code>
* is larger than the number of parts present, the
* BodyPart is appended to the end.
*
* @param part The BodyPart to be inserted
* @param index Location where to insert the part
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws MessagingException for other failures
*/
public synchronized void addBodyPart(BodyPart part, int index)
throws MessagingException {
if (parts == null)
parts = new Vector<>();
parts.insertElementAt(part, index);
part.setParent(this);
}
/**
* Output an appropriately encoded bytestream to the given
* OutputStream. The implementation subclass decides the
* appropriate encoding algorithm to be used. The bytestream
* is typically used for sending.
*
* @param os the stream to write to
* @throws IOException if an IO related exception occurs
* @throws MessagingException for other failures
*/
public abstract void writeTo(OutputStream os)
throws IOException, MessagingException;
/**
* Return the <code>Part</code> that contains this <code>Multipart</code>
* object, or <code>null</code> if not known.
*
* @return the parent Part
* @since JavaMail 1.1
*/
public synchronized Part getParent() {
return parent;
}
/**
* Set the parent of this <code>Multipart</code> to be the specified
* <code>Part</code>. Normally called by the <code>Message</code>
* or <code>BodyPart</code> <code>setContent(Multipart)</code> method.
* <code>parent</code> may be <code>null</code> if the
* <code>Multipart</code> is being removed from its containing
* <code>Part</code>.
*
* @param parent the parent Part
* @since JavaMail 1.1
*/
public synchronized void setParent(Part parent) {
this.parent = parent;
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import jakarta.activation.DataSource;
/**
* MultipartDataSource is a <code>DataSource</code> that contains body
* parts. This allows "mail aware" <code>DataContentHandlers</code> to
* be implemented more efficiently by being aware of such
* <code>DataSources</code> and using the appropriate methods to access
* <code>BodyParts</code>. <p>
* <p>
* Note that the data of a MultipartDataSource is also available as
* an input stream. <p>
* <p>
* This interface will typically be implemented by providers that
* preparse multipart bodies, for example an IMAP provider.
*
* @author John Mani
* @see jakarta.activation.DataSource
*/
public interface MultipartDataSource extends DataSource {
/**
* Return the number of enclosed BodyPart objects.
*
* @return number of parts
*/
int getCount();
/**
* Get the specified Part. Parts are numbered starting at 0.
*
* @param index the index of the desired Part
* @return the Part
* @throws IndexOutOfBoundsException if the given index
* is out of range.
* @throws MessagingException for other failures
*/
BodyPart getBodyPart(int index) throws MessagingException;
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* This exception is thrown when Session attempts to instantiate a
* Provider that doesn't exist.
*
* @author Max Spivak
*/
@SuppressWarnings("serial")
public class NoSuchProviderException extends MessagingException {
/**
* Constructs a NoSuchProviderException with no detail message.
*/
public NoSuchProviderException() {
super();
}
/**
* Constructs a NoSuchProviderException with the specified
* detail message.
*
* @param message The detailed error message
*/
public NoSuchProviderException(String message) {
super(message);
}
/**
* Constructs a NoSuchProviderException with the specified
* detail message and embedded exception. The exception is chained
* to this exception.
*
* @param message The detailed error message
* @param e The embedded exception
* @since JavaMail 1.5
*/
public NoSuchProviderException(String message, Exception e) {
super(message, e);
}
}

View file

@ -0,0 +1,454 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import jakarta.activation.DataHandler;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
/**
* The <code>Part</code> interface is the common base interface for
* Messages and BodyParts. <p>
* <p>
* Part consists of a set of attributes and a "Content".<p>
*
* <strong> Attributes: </strong> <p>
* <p>
* The Jakarta Mail API defines a set of standard Part attributes that are
* considered to be common to most existing Mail systems. These
* attributes have their own settor and gettor methods. Mail systems
* may support other Part attributes as well, these are represented as
* name-value pairs where both the name and value are Strings.<p>
*
* <strong> Content: </strong> <p>
* <p>
* The <strong>data type</strong> of the "content" is returned by
* the <code>getContentType()</code> method. The MIME typing system
* is used to name data types. <p>
* <p>
* The "content" of a Part is available in various formats:
* <ul>
* <li> As a DataHandler - using the <code>getDataHandler()</code> method.
* The "content" of a Part is also available through a
* <code>jakarta.activation.DataHandler</code> object. The DataHandler
* object allows clients to discover the operations available on the
* content, and to instantiate the appropriate component to perform
* those operations.
*
* <li> As an input stream - using the <code>getInputStream()</code> method.
* Any mail-specific encodings are decoded before this stream is returned.
*
* <li> As a Java object - using the <code>getContent()</code> method.
* This method returns the "content" as a Java object.
* The returned object is of course dependent on the content
* itself. In particular, a "multipart" Part's content is always a
* Multipart or subclass thereof. That is, <code>getContent()</code> on a
* "multipart" type Part will always return a Multipart (or subclass) object.
* </ul>
* <p>
* Part provides the <code>writeTo()</code> method that streams
* out its bytestream in mail-safe form suitable for transmission.
* This bytestream is typically an aggregation of the Part attributes
* and its content's bytestream. <p>
* <p>
* Message and BodyPart implement the Part interface. Note that in
* MIME parlance, Part models an Entity (RFC 2045, Section 2.4).
*
* @author John Mani
*/
public interface Part {
/**
* This part should be presented as an attachment.
*
* @see #getDisposition
* @see #setDisposition
*/
String ATTACHMENT = "attachment";
/**
* This part should be presented inline.
*
* @see #getDisposition
* @see #setDisposition
*/
String INLINE = "inline";
/**
* Return the size of the content of this part in bytes.
* Return -1 if the size cannot be determined. <p>
* <p>
* Note that the size may not be an exact measure of the content
* size and may or may not account for any transfer encoding
* of the content. The size is appropriate for display in a
* user interface to give the user a rough idea of the size
* of this part.
*
* @return size of content in bytes
* @throws MessagingException for failures
*/
int getSize() throws MessagingException;
/**
* Return the number of lines in the content of this part.
* Return -1 if the number cannot be determined.
* <p>
* Note that this number may not be an exact measure of the
* content length and may or may not account for any transfer
* encoding of the content.
*
* @return number of lines in the content.
* @throws MessagingException for failures
*/
int getLineCount() throws MessagingException;
/**
* Returns the Content-Type of the content of this part.
* Returns null if the Content-Type could not be determined. <p>
* <p>
* The MIME typing system is used to name Content-types.
*
* @return The ContentType of this part
* @throws MessagingException for failures
* @see jakarta.activation.DataHandler
*/
String getContentType() throws MessagingException;
/**
* Is this Part of the specified MIME type? This method
* compares <strong>only the <code>primaryType</code> and
* <code>subType</code></strong>.
* The parameters of the content types are ignored. <p>
* <p>
* For example, this method will return <code>true</code> when
* comparing a Part of content type <strong>"text/plain"</strong>
* with <strong>"text/plain; charset=foobar"</strong>. <p>
* <p>
* If the <code>subType</code> of <code>mimeType</code> is the
* special character '*', then the subtype is ignored during the
* comparison.
*
* @param mimeType the MIME type to test
* @return true if this part is of the specified type
* @throws MessagingException for failures
*/
boolean isMimeType(String mimeType) throws MessagingException;
/**
* Return the disposition of this part. The disposition
* describes how the part should be presented to the user.
* (See RFC 2183.) The return value should be considered
* without regard to case. For example:
* <blockquote><pre>
* String disp = part.getDisposition();
* if (disp == null || disp.equalsIgnoreCase(Part.ATTACHMENT))
* // treat as attachment if not first part
* </pre></blockquote>
*
* @return disposition of this part, or null if unknown
* @throws MessagingException for failures
* @see #ATTACHMENT
* @see #INLINE
* @see #getFileName
*/
String getDisposition() throws MessagingException;
/**
* Set the disposition of this part.
*
* @param disposition disposition of this part
* @throws IllegalWriteException if the underlying implementation
* does not support modification of this header
* @throws IllegalStateException if this Part is obtained
* from a READ_ONLY folder
* @throws MessagingException for other failures
* @see #ATTACHMENT
* @see #INLINE
* @see #setFileName
*/
void setDisposition(String disposition) throws MessagingException;
/**
* Return a description String for this part. This typically
* associates some descriptive information with this part.
* Returns null if none is available.
*
* @return description of this part
* @throws MessagingException for failures
*/
String getDescription() throws MessagingException;
/**
* Set a description String for this part. This typically
* associates some descriptive information with this part.
*
* @param description description of this part
* @throws IllegalWriteException if the underlying implementation
* does not support modification of this header
* @throws IllegalStateException if this Part is obtained
* from a READ_ONLY folder
* @throws MessagingException for other failures
*/
void setDescription(String description) throws MessagingException;
/**
* Get the filename associated with this part, if possible.
* Useful if this part represents an "attachment" that was
* loaded from a file. The filename will usually be a simple
* name, not including directory components.
*
* @return Filename to associate with this part
* @throws MessagingException for failures
*/
String getFileName() throws MessagingException;
/**
* Set the filename associated with this part, if possible.
* Useful if this part represents an "attachment" that was
* loaded from a file. The filename will usually be a simple
* name, not including directory components.
*
* @param filename Filename to associate with this part
* @throws IllegalWriteException if the underlying implementation
* does not support modification of this header
* @throws IllegalStateException if this Part is obtained
* from a READ_ONLY folder
* @throws MessagingException for other failures
*/
void setFileName(String filename) throws MessagingException;
/**
* Return an input stream for this part's "content". Any
* mail-specific transfer encodings will be decoded before the
* input stream is provided. <p>
* <p>
* This is typically a convenience method that just invokes
* the DataHandler's <code>getInputStream()</code> method.
*
* @return an InputStream
* @throws IOException this is typically thrown by the
* DataHandler. Refer to the documentation for
* jakarta.activation.DataHandler for more details.
* @throws MessagingException for other failures
* @see #getDataHandler
* @see jakarta.activation.DataHandler#getInputStream
*/
InputStream getInputStream()
throws IOException, MessagingException;
/**
* Return a DataHandler for the content within this part. The
* DataHandler allows clients to operate on as well as retrieve
* the content.
*
* @return DataHandler for the content
* @throws MessagingException for failures
*/
DataHandler getDataHandler() throws MessagingException;
/**
* This method provides the mechanism to set this part's content.
* The DataHandler wraps around the actual content.
*
* @param dh The DataHandler for the content.
* @throws IllegalWriteException if the underlying implementation
* does not support modification of existing values
* @throws IllegalStateException if this Part is obtained
* from a READ_ONLY folder
* @throws MessagingException for other failures
*/
void setDataHandler(DataHandler dh) throws MessagingException;
/**
* Return the content as a Java object. The type of the returned
* object is of course dependent on the content itself. For example,
* the object returned for "text/plain" content is usually a String
* object. The object returned for a "multipart" content is always a
* Multipart subclass. For content-types that are unknown to the
* DataHandler system, an input stream is returned as the content <p>
* <p>
* This is a convenience method that just invokes the DataHandler's
* getContent() method
*
* @return Object
* @throws IOException this is typically thrown by the
* DataHandler. Refer to the documentation for
* jakarta.activation.DataHandler for more details.
* @throws MessagingException for other failures
* @see jakarta.activation.DataHandler#getContent
*/
Object getContent() throws IOException, MessagingException;
/**
* This method sets the given Multipart object as this message's
* content.
*
* @param mp The multipart object that is the Message's content
* @throws IllegalWriteException if the underlying
* implementation does not support modification of
* existing values
* @throws IllegalStateException if this Part is obtained
* from a READ_ONLY folder
* @throws MessagingException for other failures
*/
void setContent(Multipart mp) throws MessagingException;
/**
* A convenience method for setting this part's content. The part
* internally wraps the content in a DataHandler. <p>
* <p>
* Note that a DataContentHandler class for the specified type should
* be available to the Jakarta Mail implementation for this to work right.
* i.e., to do <code>setContent(foobar, "application/x-foobar")</code>,
* a DataContentHandler for "application/x-foobar" should be installed.
* Refer to the Java Activation Framework for more information.
*
* @param obj A java object.
* @param type MIME type of this object.
* @throws IllegalWriteException if the underlying implementation
* does not support modification of existing values
* @throws IllegalStateException if this Part is obtained
* from a READ_ONLY folder
* @throws MessagingException for other failures
*/
void setContent(Object obj, String type)
throws MessagingException;
/**
* A convenience method that sets the given String as this
* part's content with a MIME type of "text/plain".
*
* @param text The text that is the Message's content.
* @throws IllegalWriteException if the underlying
* implementation does not support modification of
* existing values
* @throws IllegalStateException if this Part is obtained
* from a READ_ONLY folder
* @throws MessagingException for other failures
*/
void setText(String text) throws MessagingException;
/**
* Output a bytestream for this Part. This bytestream is
* typically an aggregration of the Part attributes and
* an appropriately encoded bytestream from its 'content'. <p>
* <p>
* Classes that implement the Part interface decide on
* the appropriate encoding algorithm to be used. <p>
* <p>
* The bytestream is typically used for sending.
*
* @param os the stream to write to
* @throws IOException if an error occurs writing to the
* stream or if an error is generated
* by the jakarta.activation layer.
* @throws MessagingException if an error occurs fetching the
* data to be written
* @see jakarta.activation.DataHandler#writeTo
*/
void writeTo(OutputStream os) throws IOException, MessagingException;
/**
* Get all the headers for this header name. Returns <code>null</code>
* if no headers for this header name are available.
*
* @param header_name the name of this header
* @return the value fields for all headers with
* this name
* @throws MessagingException for failures
*/
String[] getHeader(String header_name)
throws MessagingException;
/**
* Set the value for this header_name. Replaces all existing
* header values with this new value.
*
* @param header_name the name of this header
* @param header_value the value for this header
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this Part is
* obtained from a READ_ONLY folder
* @throws MessagingException for other failures
*/
void setHeader(String header_name, String header_value)
throws MessagingException;
/**
* Add this value to the existing values for this header_name.
*
* @param header_name the name of this header
* @param header_value the value for this header
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this Part is
* obtained from a READ_ONLY folder
* @throws MessagingException for other failures
*/
void addHeader(String header_name, String header_value)
throws MessagingException;
/**
* Remove all headers with this name.
*
* @param header_name the name of this header
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* of existing values
* @throws IllegalStateException if this Part is
* obtained from a READ_ONLY folder
* @throws MessagingException for other failures
*/
void removeHeader(String header_name)
throws MessagingException;
/**
* Return all the headers from this part as an Enumeration of
* Header objects.
*
* @return enumeration of Header objects
* @throws MessagingException for failures
*/
Enumeration<Header> getAllHeaders() throws MessagingException;
/**
* Return matching headers from this part as an Enumeration of
* Header objects.
*
* @param header_names the headers to match
* @return enumeration of Header objects
* @throws MessagingException for failures
*/
Enumeration<Header> getMatchingHeaders(String[] header_names)
throws MessagingException;
/**
* Return non-matching headers from this envelope as an Enumeration
* of Header objects.
*
* @param header_names the headers to not match
* @return enumeration of Header objects
* @throws MessagingException for failures
*/
Enumeration<Header> getNonMatchingHeaders(String[] header_names)
throws MessagingException;
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* The class PasswordAuthentication is a data holder that is used by
* Authenticator. It is simply a repository for a user name and a password.
*
* @author Bill Foote
* @see java.net.PasswordAuthentication
* @see Authenticator
* @see Authenticator#getPasswordAuthentication()
*/
public final class PasswordAuthentication {
private final String userName;
private final String password;
/**
* Initialize a new PasswordAuthentication
*
* @param userName the user name
* @param password The user's password
*/
public PasswordAuthentication(String userName, String password) {
this.userName = userName;
this.password = password;
}
/**
* @return the user name
*/
public String getUserName() {
return userName;
}
/**
* @return the password
*/
public String getPassword() {
return password;
}
}

View file

@ -0,0 +1,145 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* The Provider is a class that describes a protocol
* implementation. The values typically come from the
* javamail.providers and javamail.default.providers
* resource files. An application may also create and
* register a Provider object to dynamically add support
* for a new provider.
*
* @author Max Spivak
* @author Bill Shannon
*/
public class Provider {
private Type type;
private String protocol, className, vendor, version;
/**
* Create a new provider of the specified type for the specified
* protocol. The specified class implements the provider.
*
* @param type Type.STORE or Type.TRANSPORT
* @param protocol valid protocol for the type
* @param classname class name that implements this protocol
* @param vendor optional string identifying the vendor (may be null)
* @param version optional implementation version string (may be null)
* @since JavaMail 1.4
*/
public Provider(Type type, String protocol, String classname,
String vendor, String version) {
this.type = type;
this.protocol = protocol;
this.className = classname;
this.vendor = vendor;
this.version = version;
}
/**
* Returns the type of this Provider.
*
* @return the provider type
*/
public Type getType() {
return type;
}
/**
* Returns the protocol supported by this Provider.
*
* @return the protocol
*/
public String getProtocol() {
return protocol;
}
/**
* Returns the name of the class that implements the protocol.
*
* @return the class name
*/
public String getClassName() {
return className;
}
/**
* Returns the name of the vendor associated with this implementation
* or null.
*
* @return the vendor
*/
public String getVendor() {
return vendor;
}
/**
* Returns the version of this implementation or null if no version.
*
* @return the version
*/
public String getVersion() {
return version;
}
/**
* Overrides Object.toString()
*/
@Override
public String toString() {
String s = "jakarta.mail.Provider[" + type + "," +
protocol + "," + className;
if (vendor != null)
s += "," + vendor;
if (version != null)
s += "," + version;
s += "]";
return s;
}
/**
* This inner class defines the Provider type.
* Currently, STORE and TRANSPORT are the only two provider types
* supported.
*/
public static class Type {
/**
* The Provider of type {@code STORE}.
*/
public static final Type STORE = new Type("STORE");
/**
* The Provider of type {@code TRANSPORT}.
*/
public static final Type TRANSPORT = new Type("TRANSPORT");
private String type;
private Type(String type) {
this.type = type;
}
@Override
public String toString() {
return type;
}
}
}

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* This class represents a set of quotas for a given quota root.
* Each quota root has a set of resources, represented by the
* <code>Quota.Resource</code> class. Each resource has a name
* (for example, "STORAGE"), a current usage, and a usage limit.
* See RFC 2087.
*
* @author Bill Shannon
* @since JavaMail 1.4
*/
public class Quota {
/**
* The name of the quota root.
*/
public String quotaRoot;
/**
* The set of resources associated with this quota root.
*/
public Resource[] resources;
/**
* Create a Quota object for the named quotaroot with no associated
* resources.
*
* @param quotaRoot the name of the quota root
*/
public Quota(String quotaRoot) {
this.quotaRoot = quotaRoot;
}
/**
* Set a resource limit for this quota root.
*
* @param name the name of the resource
* @param limit the resource limit
*/
public void setResourceLimit(String name, long limit) {
if (resources == null) {
resources = new Resource[1];
resources[0] = new Resource(name, 0, limit);
return;
}
for (int i = 0; i < resources.length; i++) {
if (resources[i].name.equalsIgnoreCase(name)) {
resources[i].limit = limit;
return;
}
}
Resource[] ra = new Resource[resources.length + 1];
System.arraycopy(resources, 0, ra, 0, resources.length);
ra[ra.length - 1] = new Resource(name, 0, limit);
resources = ra;
}
/**
* An individual resource in a quota root.
*
* @since JavaMail 1.4
*/
public static class Resource {
/**
* The name of the resource.
*/
public String name;
/**
* The current usage of the resource.
*/
public long usage;
/**
* The usage limit for the resource.
*/
public long limit;
/**
* Construct a Resource object with the given name,
* usage, and limit.
*
* @param name the resource name
* @param usage the current usage of the resource
* @param limit the usage limit for the resource
*/
public Resource(String name, long usage, long limit) {
this.name = name;
this.usage = usage;
this.limit = limit;
}
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* An interface implemented by Stores that support quotas.
* The {@link #getQuota getQuota} and {@link #setQuota setQuota} methods
* support the quota model defined by the IMAP QUOTA extension.
* Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
* for more information.
*
* @since JavaMail 1.4
*/
public interface QuotaAwareStore {
/**
* Get the quotas for the named folder.
* Quotas are controlled on the basis of a quota root, not
* (necessarily) a folder. The relationship between folders
* and quota roots depends on the server. Some servers
* might implement a single quota root for all folders owned by
* a user. Other servers might implement a separate quota root
* for each folder. A single folder can even have multiple
* quota roots, perhaps controlling quotas for different
* resources.
*
* @param folder the name of the folder
* @return array of Quota objects
* @throws MessagingException if the server doesn't support the
* QUOTA extension
*/
Quota[] getQuota(String folder) throws MessagingException;
/**
* Set the quotas for the quota root specified in the quota argument.
* Typically this will be one of the quota roots obtained from the
* <code>getQuota</code> method, but it need not be.
*
* @param quota the quota to set
* @throws MessagingException if the server doesn't support the
* QUOTA extension
*/
void setQuota(Quota quota) throws MessagingException;
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* This exception is thrown when an attempt is made to open a folder
* read-write access when the folder is marked read-only. <p>
* <p>
* The getMessage() method returns more detailed information about the
* error that caused this exception.
*
* @author Jim Glennon
*/
@SuppressWarnings("serial")
public class ReadOnlyFolderException extends MessagingException {
transient private Folder folder;
/**
* Constructs a ReadOnlyFolderException with the specified
* folder and no detail message.
*
* @param folder the Folder
* @since JavaMail 1.2
*/
public ReadOnlyFolderException(Folder folder) {
this(folder, null);
}
/**
* Constructs a ReadOnlyFolderException with the specified
* detail message.
*
* @param folder The Folder
* @param message The detailed error message
* @since JavaMail 1.2
*/
public ReadOnlyFolderException(Folder folder, String message) {
super(message);
this.folder = folder;
}
/**
* Constructs a ReadOnlyFolderException with the specified
* detail message and embedded exception. The exception is chained
* to this exception.
*
* @param folder The Folder
* @param message The detailed error message
* @param e The embedded exception
* @since JavaMail 1.5
*/
public ReadOnlyFolderException(Folder folder, String message, Exception e) {
super(message, e);
this.folder = folder;
}
/**
* Returns the Folder object.
*
* @return the Folder
* @since JavaMail 1.2
*/
public Folder getFolder() {
return folder;
}
}

View file

@ -0,0 +1,127 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* This exception is thrown when the message cannot be sent.<p>
* <p>
* The exception includes those addresses to which the message could not be
* sent as well as the valid addresses to which the message was sent and
* valid addresses to which the message was not sent.
*
* @author John Mani
* @author Max Spivak
* @see Transport#send
* @see Transport#sendMessage
* @see jakarta.mail.event.TransportEvent
*/
@SuppressWarnings("serial")
public class SendFailedException extends MessagingException {
/**
* The invalid addresses.
*/
transient protected Address[] invalid;
/**
* Valid addresses to which message was sent.
*/
transient protected Address[] validSent;
/**
* Valid addresses to which message was not sent.
*/
transient protected Address[] validUnsent;
/**
* Constructs a SendFailedException with no detail message.
*/
public SendFailedException() {
super();
}
/**
* Constructs a SendFailedException with the specified detail message.
*
* @param s the detail message
*/
public SendFailedException(String s) {
super(s);
}
/**
* Constructs a SendFailedException with the specified
* Exception and detail message. The specified exception is chained
* to this exception.
*
* @param s the detail message
* @param e the embedded exception
* @see #getNextException
* @see #setNextException
*/
public SendFailedException(String s, Exception e) {
super(s, e);
}
/**
* Constructs a SendFailedException with the specified string
* and the specified address objects.
*
* @param msg the detail message
* @param ex the embedded exception
* @param validSent valid addresses to which message was sent
* @param validUnsent valid addresses to which message was not sent
* @param invalid the invalid addresses
* @see #getNextException
* @see #setNextException
*/
public SendFailedException(String msg, Exception ex, Address[] validSent,
Address[] validUnsent, Address[] invalid) {
super(msg, ex);
this.validSent = validSent;
this.validUnsent = validUnsent;
this.invalid = invalid;
}
/**
* Return the addresses to which this message was sent succesfully.
*
* @return Addresses to which the message was sent successfully or null
*/
public Address[] getValidSentAddresses() {
return validSent;
}
/**
* Return the addresses that are valid but to which this message
* was not sent.
*
* @return Addresses that are valid but to which the message was
* not sent successfully or null
*/
public Address[] getValidUnsentAddresses() {
return validUnsent;
}
/**
* Return the addresses to which this message could not be sent.
*
* @return Addresses to which the message sending failed or null;
*/
public Address[] getInvalidAddresses() {
return invalid;
}
}

View file

@ -0,0 +1,632 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import jakarta.mail.event.ConnectionEvent;
import jakarta.mail.event.ConnectionListener;
import jakarta.mail.event.MailEvent;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.EventListener;
import java.util.Vector;
import java.util.concurrent.Executor;
/**
* An abstract class that contains the functionality
* common to messaging services, such as stores and transports. <p>
* A messaging service is created from a <code>Session</code> and is
* named using a <code>URLName</code>. A service must be connected
* before it can be used. Connection events are sent to reflect
* its connection status.
*
* @author Christopher Cotton
* @author Bill Shannon
* @author Kanwar Oberoi
*/
public abstract class Service implements AutoCloseable {
/*
* connectionListeners is a Vector, initialized here,
* because we depend on it always existing and depend
* on the synchronization that Vector provides.
* (Sychronizing on the Service object itself can cause
* deadlocks when notifying listeners.)
*/
private final Vector<ConnectionListener> connectionListeners
= new Vector<>();
/**
* The queue of events to be delivered.
*/
private final EventQueue q;
/**
* The session from which this service was created.
*/
protected Session session;
/**
* The <code>URLName</code> of this service.
*/
protected volatile URLName url = null;
/**
* Debug flag for this service. Set from the session's debug
* flag when this service is created.
*/
protected boolean debug;
private boolean connected = false;
/**
* Constructor.
*
* @param session Session object for this service
* @param urlname URLName object to be used for this service
*/
protected Service(Session session, URLName urlname) {
this.session = session;
debug = session.getDebug();
url = urlname;
/*
* Initialize the URLName with default values.
* The URLName will be updated when connect is called.
*/
String protocol = null;
String host = null;
int port = -1;
String user = null;
String password = null;
String file = null;
// get whatever information we can from the URL
// XXX - url should always be non-null here, Session
// passes it into the constructor
if (url != null) {
protocol = url.getProtocol();
host = url.getHost();
port = url.getPort();
user = url.getUsername();
password = url.getPassword();
file = url.getFile();
}
// try to get protocol-specific default properties
if (protocol != null) {
if (host == null)
host = session.getProperty("mail." + protocol + ".host");
if (user == null)
user = session.getProperty("mail." + protocol + ".user");
}
// try to get mail-wide default properties
if (host == null)
host = session.getProperty("mail.host");
if (user == null)
user = session.getProperty("mail.user");
// try using the system username
if (user == null) {
user = System.getProperty("user.name");
}
url = new URLName(protocol, host, port, file, user, password);
// create or choose the appropriate event queue
String scope =
session.getProperties().getProperty("mail.event.scope", "folder");
Executor executor =
(Executor) session.getProperties().get("mail.event.executor");
if (scope.equalsIgnoreCase("application"))
q = EventQueue.getApplicationEventQueue(executor);
else if (scope.equalsIgnoreCase("session"))
q = session.getEventQueue();
else // if (scope.equalsIgnoreCase("store") ||
// scope.equalsIgnoreCase("folder"))
q = new EventQueue(executor);
}
/**
* A generic connect method that takes no parameters. Subclasses
* can implement the appropriate authentication schemes. Subclasses
* that need additional information might want to use some properties
* or might get it interactively using a popup window. <p>
* <p>
* If the connection is successful, an "open" <code>ConnectionEvent</code>
* is delivered to any <code>ConnectionListeners</code> on this service. <p>
* <p>
* Most clients should just call this method to connect to the service.<p>
* <p>
* It is an error to connect to an already connected service. <p>
* <p>
* The implementation provided here simply calls the following
* <code>connect(String, String, String)</code> method with nulls.
*
* @throws AuthenticationFailedException for authentication failures
* @throws IllegalStateException if the service is already connected
* @throws MessagingException for other failures
* @see ConnectionEvent
*/
public void connect() throws MessagingException {
connect(null, null, null);
}
/**
* Connect to the specified address. This method provides a simple
* authentication scheme that requires a username and password. <p>
* <p>
* If the connection is successful, an "open" <code>ConnectionEvent</code>
* is delivered to any <code>ConnectionListeners</code> on this service. <p>
* <p>
* It is an error to connect to an already connected service. <p>
* <p>
* The implementation in the Service class will collect defaults
* for the host, user, and password from the session, from the
* <code>URLName</code> for this service, and from the supplied
* parameters and then call the <code>protocolConnect</code> method.
* If the <code>protocolConnect</code> method returns <code>false</code>,
* the user will be prompted for any missing information and the
* <code>protocolConnect</code> method will be called again. The
* subclass should override the <code>protocolConnect</code> method.
* The subclass should also implement the <code>getURLName</code>
* method, or use the implementation in this class. <p>
* <p>
* On a successful connection, the <code>setURLName</code> method is
* called with a URLName that includes the information used to make
* the connection, including the password. <p>
* <p>
* If the username passed in is null, a default value will be chosen
* as described above.
* <p>
* If the password passed in is null and this is the first successful
* connection to this service, the user name and the password
* collected from the user will be saved as defaults for subsequent
* connection attempts to this same service when using other Service object
* instances (the connection information is typically always saved within
* a particular Service object instance). The password is saved using the
* Session method <code>setPasswordAuthentication</code>. If the
* password passed in is not null, it is not saved, on the assumption
* that the application is managing passwords explicitly.
*
* @param host the host to connect to
* @param user the user name
* @param password this user's password
* @throws AuthenticationFailedException for authentication failures
* @throws IllegalStateException if the service is already connected
* @throws MessagingException for other failures
* @see ConnectionEvent
* @see Session#setPasswordAuthentication
*/
public void connect(String host, String user, String password)
throws MessagingException {
connect(host, -1, user, password);
}
/**
* Connect to the current host using the specified username
* and password. This method is equivalent to calling the
* <code>connect(host, user, password)</code> method with null
* for the host name.
*
* @param user the user name
* @param password this user's password
* @throws AuthenticationFailedException for authentication failures
* @throws IllegalStateException if the service is already connected
* @throws MessagingException for other failures
* @see ConnectionEvent
* @see Session#setPasswordAuthentication
* @see #connect(String, String, String)
* @since JavaMail 1.4
*/
public void connect(String user, String password)
throws MessagingException {
connect(null, user, password);
}
/**
* Similar to connect(host, user, password) except a specific port
* can be specified.
*
* @param host the host to connect to
* @param port the port to connect to (-1 means the default port)
* @param user the user name
* @param password this user's password
* @throws AuthenticationFailedException for authentication failures
* @throws IllegalStateException if the service is already connected
* @throws MessagingException for other failures
* @see #connect(String, String, String)
* @see ConnectionEvent
*/
public synchronized void connect(String host, int port,
String user, String password) throws MessagingException {
// see if the service is already connected
if (isConnected())
throw new IllegalStateException("already connected");
PasswordAuthentication pw;
boolean connected = false;
boolean save = false;
String protocol = null;
String file = null;
// get whatever information we can from the URL
// XXX - url should always be non-null here, Session
// passes it into the constructor
if (url != null) {
protocol = url.getProtocol();
if (host == null)
host = url.getHost();
if (port == -1)
port = url.getPort();
if (user == null) {
user = url.getUsername();
if (password == null) // get password too if we need it
password = url.getPassword();
} else {
if (password == null && user.equals(url.getUsername()))
// only get the password if it matches the username
password = url.getPassword();
}
file = url.getFile();
}
// try to get protocol-specific default properties
if (protocol != null) {
if (host == null)
host = session.getProperty("mail." + protocol + ".host");
if (user == null)
user = session.getProperty("mail." + protocol + ".user");
}
// try to get mail-wide default properties
if (host == null)
host = session.getProperty("mail.host");
if (user == null)
user = session.getProperty("mail.user");
// try using the system username
if (user == null) {
user = System.getProperty("user.name");
}
// if we don't have a password, look for saved authentication info
if (password == null && url != null) {
// canonicalize the URLName
setURLName(new URLName(protocol, host, port, file, user, null));
pw = session.getPasswordAuthentication(getURLName());
if (pw != null) {
if (user == null) {
user = pw.getUserName();
password = pw.getPassword();
} else if (user.equals(pw.getUserName())) {
password = pw.getPassword();
}
} else
save = true;
}
// try connecting, if the protocol needs some missing
// information (user, password) it will not connect.
// if it tries to connect and fails, remember why for later.
AuthenticationFailedException authEx = null;
try {
connected = protocolConnect(host, port, user, password);
} catch (AuthenticationFailedException ex) {
authEx = ex;
}
// if not connected, ask the user and try again
if (!connected) {
InetAddress addr;
try {
addr = InetAddress.getByName(host);
} catch (UnknownHostException e) {
addr = null;
}
pw = session.requestPasswordAuthentication(
addr, port,
protocol,
null, user);
if (pw != null) {
user = pw.getUserName();
password = pw.getPassword();
// have the service connect again
connected = protocolConnect(host, port, user, password);
}
}
// if we're not connected by now, we give up
if (!connected) {
if (authEx != null)
throw authEx;
else if (user == null)
throw new AuthenticationFailedException(
"failed to connect, no user name specified?");
else if (password == null)
throw new AuthenticationFailedException(
"failed to connect, no password specified?");
else
throw new AuthenticationFailedException("failed to connect");
}
setURLName(new URLName(protocol, host, port, file, user, password));
if (save)
session.setPasswordAuthentication(getURLName(),
new PasswordAuthentication(user, password));
// set our connected state
setConnected(true);
// finally, deliver the connection event
notifyConnectionListeners(ConnectionEvent.OPENED);
}
/**
* The service implementation should override this method to
* perform the actual protocol-specific connection attempt.
* The default implementation of the <code>connect</code> method
* calls this method as needed. <p>
* <p>
* The <code>protocolConnect</code> method should return
* <code>false</code> if a user name or password is required
* for authentication but the corresponding parameter is null;
* the <code>connect</code> method will prompt the user when
* needed to supply missing information. This method may
* also return <code>false</code> if authentication fails for
* the supplied user name or password. Alternatively, this method
* may throw an AuthenticationFailedException when authentication
* fails. This exception may include a String message with more
* detail about the failure. <p>
* <p>
* The <code>protocolConnect</code> method should throw an
* exception to report failures not related to authentication,
* such as an invalid host name or port number, loss of a
* connection during the authentication process, unavailability
* of the server, etc.
*
* @param host the name of the host to connect to
* @param port the port to use (-1 means use default port)
* @param user the name of the user to login as
* @param password the user's password
* @return true if connection successful, false if authentication failed
* @throws AuthenticationFailedException for authentication failures
* @throws MessagingException for non-authentication failures
*/
protected boolean protocolConnect(String host, int port, String user,
String password) throws MessagingException {
return false;
}
/**
* Is this service currently connected? <p>
* <p>
* This implementation uses a private boolean field to
* store the connection state. This method returns the value
* of that field. <p>
* <p>
* Subclasses may want to override this method to verify that any
* connection to the message store is still alive.
*
* @return true if the service is connected, false if it is not connected
*/
public synchronized boolean isConnected() {
return connected;
}
/**
* Set the connection state of this service. The connection state
* will automatically be set by the service implementation during the
* <code>connect</code> and <code>close</code> methods.
* Subclasses will need to call this method to set the state
* if the service was automatically disconnected. <p>
* <p>
* The implementation in this class merely sets the private field
* returned by the <code>isConnected</code> method.
*
* @param connected true if the service is connected,
* false if it is not connected
*/
protected synchronized void setConnected(boolean connected) {
this.connected = connected;
}
/**
* Close this service and terminate its connection. A close
* ConnectionEvent is delivered to any ConnectionListeners. Any
* Messaging components (Folders, Messages, etc.) belonging to this
* service are invalid after this service is closed. Note that the service
* is closed even if this method terminates abnormally by throwing
* a MessagingException. <p>
* <p>
* This implementation uses <code>setConnected(false)</code> to set
* this service's connected state to <code>false</code>. It will then
* send a close ConnectionEvent to any registered ConnectionListeners.
* Subclasses overriding this method to do implementation specific
* cleanup should call this method as a last step to insure event
* notification, probably by including a call to <code>super.close()</code>
* in a <code>finally</code> clause.
*
* @throws MessagingException for errors while closing
* @see ConnectionEvent
*/
public synchronized void close() throws MessagingException {
setConnected(false);
notifyConnectionListeners(ConnectionEvent.CLOSED);
}
/**
* Return a URLName representing this service. The returned URLName
* does <em>not</em> include the password field. <p>
* <p>
* Subclasses should only override this method if their
* URLName does not follow the standard format. <p>
* <p>
* The implementation in the Service class returns (usually a copy of)
* the <code>url</code> field with the password and file information
* stripped out.
*
* @return the URLName representing this service
* @see URLName
*/
public URLName getURLName() {
URLName url = this.url; // snapshot
if (url != null && (url.getPassword() != null || url.getFile() != null))
return new URLName(url.getProtocol(), url.getHost(),
url.getPort(), null /* no file */,
url.getUsername(), null /* no password */);
else
return url;
}
/**
* Set the URLName representing this service.
* Normally used to update the <code>url</code> field
* after a service has successfully connected. <p>
* <p>
* Subclasses should only override this method if their
* URL does not follow the standard format. In particular,
* subclasses should override this method if their URL
* does not require all the possible fields supported by
* <code>URLName</code>; a new <code>URLName</code> should
* be constructed with any unneeded fields removed. <p>
* <p>
* The implementation in the Service class simply sets the
* <code>url</code> field.
*
* @param url the URLName
* @see URLName
*/
protected void setURLName(URLName url) {
this.url = url;
}
/**
* Add a listener for Connection events on this service. <p>
* <p>
* The default implementation provided here adds this listener
* to an internal list of ConnectionListeners.
*
* @param l the Listener for Connection events
* @see ConnectionEvent
*/
public void addConnectionListener(ConnectionListener l) {
connectionListeners.addElement(l);
}
/**
* Remove a Connection event listener. <p>
* <p>
* The default implementation provided here removes this listener
* from the internal list of ConnectionListeners.
*
* @param l the listener
* @see #addConnectionListener
*/
public void removeConnectionListener(ConnectionListener l) {
connectionListeners.removeElement(l);
}
/**
* Notify all ConnectionListeners. Service implementations are
* expected to use this method to broadcast connection events. <p>
* <p>
* The provided default implementation queues the event into
* an internal event queue. An event dispatcher thread dequeues
* events from the queue and dispatches them to the registered
* ConnectionListeners. Note that the event dispatching occurs
* in a separate thread, thus avoiding potential deadlock problems.
*
* @param type the ConnectionEvent type
*/
protected void notifyConnectionListeners(int type) {
/*
* Don't bother queuing an event if there's no listeners.
* Yes, listeners could be removed after checking, which
* just makes this an expensive no-op.
*/
if (connectionListeners.size() > 0) {
ConnectionEvent e = new ConnectionEvent(this, type);
queueEvent(e, connectionListeners);
}
/* Fix for broken JDK1.1.x Garbage collector :
* The 'conservative' GC in JDK1.1.x occasionally fails to
* garbage-collect Threads which are in the wait state.
* This would result in thread (and consequently memory) leaks.
*
* We attempt to fix this by sending a 'terminator' event
* to the queue, after we've sent the CLOSED event. The
* terminator event causes the event-dispatching thread to
* self destruct.
*/
if (type == ConnectionEvent.CLOSED)
q.terminateQueue();
}
/**
* Return <code>getURLName.toString()</code> if this service has a URLName,
* otherwise it will return the default <code>toString</code>.
*/
@Override
public String toString() {
URLName url = getURLName();
if (url != null)
return url.toString();
else
return super.toString();
}
/**
* Add the event and vector of listeners to the queue to be delivered.
*
* @param event the event
* @param vector the vector of listeners
*/
protected void queueEvent(MailEvent event,
Vector<? extends EventListener> vector) {
/*
* Copy the vector in order to freeze the state of the set
* of EventListeners the event should be delivered to prior
* to delivery. This ensures that any changes made to the
* Vector from a target listener's method during the delivery
* of this event will not take effect until after the event is
* delivered.
*/
@SuppressWarnings("unchecked")
Vector<? extends EventListener> v = (Vector<? extends EventListener>) vector.clone();
q.enqueue(event, v);
}
/**
* Package private method to allow Folder to get the Session for a Store.
*/
Session getSession() {
return session;
}
/**
* Package private method to allow Folder to get the EventQueue for a Store.
*/
EventQueue getEventQueue() {
return q;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,300 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import jakarta.mail.event.FolderEvent;
import jakarta.mail.event.FolderListener;
import jakarta.mail.event.StoreEvent;
import jakarta.mail.event.StoreListener;
import java.util.Vector;
/**
* An abstract class that models a message store and its
* access protocol, for storing and retrieving messages.
* Subclasses provide actual implementations. <p>
* <p>
* Note that <code>Store</code> extends the <code>Service</code>
* class, which provides many common methods for naming stores,
* connecting to stores, and listening to connection events.
*
* @author John Mani
* @author Bill Shannon
* @see Service
* @see jakarta.mail.event.ConnectionEvent
* @see StoreEvent
*/
public abstract class Store extends Service {
// Vector of Store listeners
private volatile Vector<StoreListener> storeListeners = null;
// Vector of folder listeners
private volatile Vector<FolderListener> folderListeners = null;
/**
* Constructor.
*
* @param session Session object for this Store.
* @param urlname URLName object to be used for this Store
*/
protected Store(Session session, URLName urlname) {
super(session, urlname);
}
/**
* Returns a Folder object that represents the 'root' of
* the default namespace presented to the user by the Store.
*
* @return the root Folder
* @throws IllegalStateException if this Store is not connected.
* @throws MessagingException for other failures
*/
public abstract Folder getDefaultFolder() throws MessagingException;
/**
* Return the Folder object corresponding to the given name. Note
* that a Folder object is returned even if the named folder does
* not physically exist on the Store. The <code>exists()</code>
* method on the folder object indicates whether this folder really
* exists. <p>
* <p>
* Folder objects are not cached by the Store, so invoking this
* method on the same name multiple times will return that many
* distinct Folder objects.
*
* @param name The name of the Folder. In some Stores, name can
* be an absolute path if it starts with the
* hierarchy delimiter. Else it is interpreted
* relative to the 'root' of this namespace.
* @return Folder object
* @throws IllegalStateException if this Store is not connected.
* @throws MessagingException for other failures
* @see Folder#create
* @see Folder#exists
*/
public abstract Folder getFolder(String name)
throws MessagingException;
/**
* Return a closed Folder object, corresponding to the given
* URLName. The store specified in the given URLName should
* refer to this Store object. <p>
* <p>
* Implementations of this method may obtain the name of the
* actual folder using the <code>getFile()</code> method on
* URLName, and use that name to create the folder.
*
* @param url URLName that denotes a folder
* @return Folder object
* @throws IllegalStateException if this Store is not connected.
* @throws MessagingException for other failures
* @see URLName
*/
public abstract Folder getFolder(URLName url)
throws MessagingException;
/**
* Return a set of folders representing the <i>personal</i> namespaces
* for the current user. A personal namespace is a set of names that
* is considered within the personal scope of the authenticated user.
* Typically, only the authenticated user has access to mail folders
* in their personal namespace. If an INBOX exists for a user, it
* must appear within the user's personal namespace. In the
* typical case, there should be only one personal namespace for each
* user in each Store. <p>
* <p>
* This implementation returns an array with a single entry containing
* the return value of the <code>getDefaultFolder</code> method.
* Subclasses should override this method to return appropriate information.
*
* @return array of Folder objects
* @throws IllegalStateException if this Store is not connected.
* @throws MessagingException for other failures
* @since JavaMail 1.2
*/
public Folder[] getPersonalNamespaces() throws MessagingException {
return new Folder[]{getDefaultFolder()};
}
/**
* Return a set of folders representing the namespaces for
* <code>user</code>. The namespaces returned represent the
* personal namespaces for the user. To access mail folders in the
* other user's namespace, the currently authenticated user must be
* explicitly granted access rights. For example, it is common for
* a manager to grant to their secretary access rights to their
* mail folders. <p>
* <p>
* This implementation returns an empty array. Subclasses should
* override this method to return appropriate information.
*
* @param user the user name
* @return array of Folder objects
* @throws IllegalStateException if this Store is not connected.
* @throws MessagingException for other failures
* @since JavaMail 1.2
*/
public Folder[] getUserNamespaces(String user)
throws MessagingException {
return new Folder[0];
}
/**
* Return a set of folders representing the <i>shared</i> namespaces.
* A shared namespace is a namespace that consists of mail folders
* that are intended to be shared amongst users and do not exist
* within a user's personal namespace. <p>
* <p>
* This implementation returns an empty array. Subclasses should
* override this method to return appropriate information.
*
* @return array of Folder objects
* @throws IllegalStateException if this Store is not connected.
* @throws MessagingException for other failures
* @since JavaMail 1.2
*/
public Folder[] getSharedNamespaces() throws MessagingException {
return new Folder[0];
}
/**
* Add a listener for StoreEvents on this Store. <p>
* <p>
* The default implementation provided here adds this listener
* to an internal list of StoreListeners.
*
* @param l the Listener for Store events
* @see StoreEvent
*/
public synchronized void addStoreListener(StoreListener l) {
if (storeListeners == null)
storeListeners = new Vector<>();
storeListeners.addElement(l);
}
/**
* Remove a listener for Store events. <p>
* <p>
* The default implementation provided here removes this listener
* from the internal list of StoreListeners.
*
* @param l the listener
* @see #addStoreListener
*/
public synchronized void removeStoreListener(StoreListener l) {
if (storeListeners != null)
storeListeners.removeElement(l);
}
/**
* Notify all StoreListeners. Store implementations are
* expected to use this method to broadcast StoreEvents. <p>
* <p>
* The provided default implementation queues the event into
* an internal event queue. An event dispatcher thread dequeues
* events from the queue and dispatches them to the registered
* StoreListeners. Note that the event dispatching occurs
* in a separate thread, thus avoiding potential deadlock problems.
*
* @param type the StoreEvent type
* @param message a message for the StoreEvent
*/
protected void notifyStoreListeners(int type, String message) {
if (storeListeners == null)
return;
StoreEvent e = new StoreEvent(this, type, message);
queueEvent(e, storeListeners);
}
/**
* Add a listener for Folder events on any Folder object
* obtained from this Store. FolderEvents are delivered to
* FolderListeners on the affected Folder as well as to
* FolderListeners on the containing Store. <p>
* <p>
* The default implementation provided here adds this listener
* to an internal list of FolderListeners.
*
* @param l the Listener for Folder events
* @see FolderEvent
*/
public synchronized void addFolderListener(FolderListener l) {
if (folderListeners == null)
folderListeners = new Vector<>();
folderListeners.addElement(l);
}
/**
* Remove a listener for Folder events. <p>
* <p>
* The default implementation provided here removes this listener
* from the internal list of FolderListeners.
*
* @param l the listener
* @see #addFolderListener
*/
public synchronized void removeFolderListener(FolderListener l) {
if (folderListeners != null)
folderListeners.removeElement(l);
}
/**
* Notify all FolderListeners. Store implementations are
* expected to use this method to broadcast Folder events. <p>
* <p>
* The provided default implementation queues the event into
* an internal event queue. An event dispatcher thread dequeues
* events from the queue and dispatches them to the registered
* FolderListeners. Note that the event dispatching occurs
* in a separate thread, thus avoiding potential deadlock problems.
*
* @param type type of FolderEvent
* @param folder affected Folder
* @see #notifyFolderRenamedListeners
*/
protected void notifyFolderListeners(int type, Folder folder) {
if (folderListeners == null)
return;
FolderEvent e = new FolderEvent(this, folder, type);
queueEvent(e, folderListeners);
}
/**
* Notify all FolderListeners about the renaming of a folder.
* Store implementations are expected to use this method to broadcast
* Folder events indicating the renaming of folders. <p>
* <p>
* The provided default implementation queues the event into
* an internal event queue. An event dispatcher thread dequeues
* events from the queue and dispatches them to the registered
* FolderListeners. Note that the event dispatching occurs
* in a separate thread, thus avoiding potential deadlock problems.
*
* @param oldF the folder being renamed
* @param newF the folder representing the new name.
* @since JavaMail 1.1
*/
protected void notifyFolderRenamedListeners(Folder oldF, Folder newF) {
if (folderListeners == null)
return;
FolderEvent e = new FolderEvent(this, oldF, newF, FolderEvent.RENAMED);
queueEvent(e, folderListeners);
}
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
/**
* This exception is thrown when a method is invoked on a Messaging object
* and the Store that owns that object has died due to some reason.
* This exception should be treated as a fatal error; in particular any
* messaging object belonging to that Store must be considered invalid. <p>
* <p>
* The connect method may be invoked on the dead Store object to
* revive it. <p>
* <p>
* The getMessage() method returns more detailed information about the
* error that caused this exception.
*
* @author John Mani
*/
@SuppressWarnings("serial")
public class StoreClosedException extends MessagingException {
transient private Store store;
/**
* Constructs a StoreClosedException with no detail message.
*
* @param store The dead Store object
*/
public StoreClosedException(Store store) {
this(store, null);
}
/**
* Constructs a StoreClosedException with the specified
* detail message.
*
* @param store The dead Store object
* @param message The detailed error message
*/
public StoreClosedException(Store store, String message) {
super(message);
this.store = store;
}
/**
* Constructs a StoreClosedException with the specified
* detail message and embedded exception. The exception is chained
* to this exception.
*
* @param store The dead Store object
* @param message The detailed error message
* @param e The embedded exception
* @since JavaMail 1.5
*/
public StoreClosedException(Store store, String message, Exception e) {
super(message, e);
this.store = store;
}
/**
* Returns the dead Store object.
*
* @return the dead Store object
*/
public Store getStore() {
return store;
}
}

View file

@ -0,0 +1,397 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import jakarta.mail.event.TransportEvent;
import jakarta.mail.event.TransportListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
/**
* An abstract class that models a message transport.
* Subclasses provide actual implementations. <p>
* <p>
* Note that <code>Transport</code> extends the <code>Service</code>
* class, which provides many common methods for naming transports,
* connecting to transports, and listening to connection events.
*
* @author John Mani
* @author Max Spivak
* @author Bill Shannon
* @see Service
* @see jakarta.mail.event.ConnectionEvent
* @see TransportEvent
*/
public abstract class Transport extends Service {
// Vector of Transport listeners
private volatile Vector<TransportListener> transportListeners = null;
/**
* Constructor.
*
* @param session Session object for this Transport.
* @param urlname URLName object to be used for this Transport
*/
public Transport(Session session, URLName urlname) {
super(session, urlname);
}
/**
* Send a message. The message will be sent to all recipient
* addresses specified in the message (as returned from the
* <code>Message</code> method <code>getAllRecipients</code>),
* using message transports appropriate to each address. The
* <code>send</code> method calls the <code>saveChanges</code>
* method on the message before sending it. <p>
* <p>
* If any of the recipient addresses is detected to be invalid by
* the Transport during message submission, a SendFailedException
* is thrown. Clients can get more detail about the failure by examining
* the exception. Whether or not the message is still sent successfully
* to any valid addresses depends on the Transport implementation. See
* SendFailedException for more details. Note also that success does
* not imply that the message was delivered to the ultimate recipient,
* as failures may occur in later stages of delivery. Once a Transport
* accepts a message for delivery to a recipient, failures that occur later
* should be reported to the user via another mechanism, such as
* returning the undeliverable message. <p>
* <p>
* In typical usage, a SendFailedException reflects an error detected
* by the server. The details of the SendFailedException will usually
* contain the error message from the server (such as an SMTP error
* message). An address may be detected as invalid for a variety of
* reasons - the address may not exist, the address may have invalid
* syntax, the address may have exceeded its quota, etc. <p>
* <p>
* Note that <code>send</code> is a static method that creates and
* manages its own connection. Any connection associated with any
* Transport instance used to invoke this method is ignored and not
* used. This method should only be invoked using the form
* <code>Transport.send(msg);</code>, and should never be invoked
* using an instance variable.
*
* @param msg the message to send
* @throws SendFailedException if the message could not
* be sent to some or any of the recipients.
* @throws MessagingException for other failures
* @see Message#saveChanges
* @see Message#getAllRecipients
* @see #send(Message, Address[])
* @see SendFailedException
*/
public static void send(Message msg) throws MessagingException {
msg.saveChanges(); // do this first
send0(msg, msg.getAllRecipients(), null, null);
}
/**
* Send the message to the specified addresses, ignoring any
* recipients specified in the message itself. The
* <code>send</code> method calls the <code>saveChanges</code>
* method on the message before sending it.
*
* @param msg the message to send
* @param addresses the addresses to which to send the message
* @throws SendFailedException if the message could not
* be sent to some or any of the recipients.
* @throws MessagingException for other failures
* @see Message#saveChanges
* @see SendFailedException
* @see #send(Message)
*/
public static void send(Message msg, Address[] addresses)
throws MessagingException {
msg.saveChanges();
send0(msg, addresses, null, null);
}
/**
* Send a message. The message will be sent to all recipient
* addresses specified in the message (as returned from the
* <code>Message</code> method <code>getAllRecipients</code>).
* The <code>send</code> method calls the <code>saveChanges</code>
* method on the message before sending it. <p>
* <p>
* Use the specified user name and password to authenticate to
* the mail server.
*
* @param msg the message to send
* @param user the user name
* @param password this user's password
* @throws SendFailedException if the message could not
* be sent to some or any of the recipients.
* @throws MessagingException for other failures
* @see Message#saveChanges
* @see SendFailedException
* @see #send(Message)
* @since JavaMail 1.5
*/
public static void send(Message msg,
String user, String password) throws MessagingException {
msg.saveChanges();
send0(msg, msg.getAllRecipients(), user, password);
}
/**
* Send the message to the specified addresses, ignoring any
* recipients specified in the message itself. The
* <code>send</code> method calls the <code>saveChanges</code>
* method on the message before sending it. <p>
* <p>
* Use the specified user name and password to authenticate to
* the mail server.
*
* @param msg the message to send
* @param addresses the addresses to which to send the message
* @param user the user name
* @param password this user's password
* @throws SendFailedException if the message could not
* be sent to some or any of the recipients.
* @throws MessagingException for other failures
* @see Message#saveChanges
* @see SendFailedException
* @see #send(Message)
* @since JavaMail 1.5
*/
public static void send(Message msg, Address[] addresses,
String user, String password) throws MessagingException {
msg.saveChanges();
send0(msg, addresses, user, password);
}
// send, but without the saveChanges
private static void send0(Message msg, Address[] addresses,
String user, String password) throws MessagingException {
if (addresses == null || addresses.length == 0)
throw new SendFailedException("No recipient addresses");
/*
* protocols is a map containing the addresses
* indexed by address type
*/
Map<String, List<Address>> protocols
= new HashMap<>();
// Lists of addresses
List<Address> invalid = new ArrayList<>();
List<Address> validSent = new ArrayList<>();
List<Address> validUnsent = new ArrayList<>();
for (int i = 0; i < addresses.length; i++) {
// is this address type already in the map?
if (protocols.containsKey(addresses[i].getType())) {
List<Address> v = protocols.get(addresses[i].getType());
v.add(addresses[i]);
} else {
// need to add a new protocol
List<Address> w = new ArrayList<>();
w.add(addresses[i]);
protocols.put(addresses[i].getType(), w);
}
}
int dsize = protocols.size();
if (dsize == 0)
throw new SendFailedException("No recipient addresses");
Session s = (msg.session != null) ? msg.session :
Session.getDefaultInstance(System.getProperties(), null);
Transport transport;
/*
* Optimize the case of a single protocol.
*/
if (dsize == 1) {
transport = s.getTransport(addresses[0]);
try {
if (user != null)
transport.connect(user, password);
else
transport.connect();
transport.sendMessage(msg, addresses);
} finally {
transport.close();
}
return;
}
/*
* More than one protocol. Have to do them one at a time
* and collect addresses and chain exceptions.
*/
MessagingException chainedEx = null;
boolean sendFailed = false;
for (List<Address> v : protocols.values()) {
Address[] protaddresses = new Address[v.size()];
v.toArray(protaddresses);
// Get a Transport that can handle this address type.
if ((transport = s.getTransport(protaddresses[0])) == null) {
// Could not find an appropriate Transport ..
// Mark these addresses invalid.
Collections.addAll(invalid, protaddresses);
continue;
}
try {
transport.connect();
transport.sendMessage(msg, protaddresses);
} catch (SendFailedException sex) {
sendFailed = true;
// chain the exception we're catching to any previous ones
if (chainedEx == null)
chainedEx = sex;
else
chainedEx.setNextException(sex);
// retrieve invalid addresses
Address[] a = sex.getInvalidAddresses();
if (a != null)
Collections.addAll(invalid, a);
// retrieve validSent addresses
a = sex.getValidSentAddresses();
if (a != null)
Collections.addAll(validSent, a);
// retrieve validUnsent addresses
Address[] c = sex.getValidUnsentAddresses();
if (c != null)
Collections.addAll(validUnsent, c);
} catch (MessagingException mex) {
sendFailed = true;
// chain the exception we're catching to any previous ones
if (chainedEx == null)
chainedEx = mex;
else
chainedEx.setNextException(mex);
} finally {
transport.close();
}
}
// done with all protocols. throw exception if something failed
if (sendFailed || invalid.size() != 0 || validUnsent.size() != 0) {
Address[] a = null, b = null, c = null;
// copy address lists into arrays
if (validSent.size() > 0) {
a = new Address[validSent.size()];
validSent.toArray(a);
}
if (validUnsent.size() > 0) {
b = new Address[validUnsent.size()];
validUnsent.toArray(b);
}
if (invalid.size() > 0) {
c = new Address[invalid.size()];
invalid.toArray(c);
}
throw new SendFailedException("Sending failed", chainedEx,
a, b, c);
}
}
/**
* Send the Message to the specified list of addresses. An appropriate
* TransportEvent indicating the delivery status is delivered to any
* TransportListener registered on this Transport. Also, if any of
* the addresses is invalid, a SendFailedException is thrown.
* Whether or not the message is still sent succesfully to
* any valid addresses depends on the Transport implementation. <p>
* <p>
* Unlike the static <code>send</code> method, the <code>sendMessage</code>
* method does <em>not</em> call the <code>saveChanges</code> method on
* the message; the caller should do so.
*
* @param msg The Message to be sent
* @param addresses array of addresses to send this message to
* @throws SendFailedException if the send failed because of
* invalid addresses.
* @throws MessagingException if the connection is dead or not in the
* connected state
* @see TransportEvent
*/
public abstract void sendMessage(Message msg, Address[] addresses)
throws MessagingException;
/**
* Add a listener for Transport events. <p>
* <p>
* The default implementation provided here adds this listener
* to an internal list of TransportListeners.
*
* @param l the Listener for Transport events
* @see TransportEvent
*/
public synchronized void addTransportListener(TransportListener l) {
if (transportListeners == null)
transportListeners = new Vector<>();
transportListeners.addElement(l);
}
/**
* Remove a listener for Transport events. <p>
* <p>
* The default implementation provided here removes this listener
* from the internal list of TransportListeners.
*
* @param l the listener
* @see #addTransportListener
*/
public synchronized void removeTransportListener(TransportListener l) {
if (transportListeners != null)
transportListeners.removeElement(l);
}
/**
* Notify all TransportListeners. Transport implementations are
* expected to use this method to broadcast TransportEvents.<p>
* <p>
* The provided default implementation queues the event into
* an internal event queue. An event dispatcher thread dequeues
* events from the queue and dispatches them to the registered
* TransportListeners. Note that the event dispatching occurs
* in a separate thread, thus avoiding potential deadlock problems.
*
* @param type the TransportEvent type
* @param validSent valid addresses to which message was sent
* @param validUnsent valid addresses to which message was not sent
* @param invalid the invalid addresses
* @param msg the message
*/
protected void notifyTransportListeners(int type, Address[] validSent,
Address[] validUnsent,
Address[] invalid, Message msg) {
if (transportListeners == null)
return;
TransportEvent e = new TransportEvent(this, type, validSent,
validUnsent, invalid, msg);
queueEvent(e, transportListeners);
}
}

View file

@ -0,0 +1,210 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import java.util.NoSuchElementException;
/**
* The <code>UIDFolder</code> interface is implemented by Folders
* that can support the "disconnected" mode of operation, by providing
* unique-ids for messages in the folder. This interface is based on
* the IMAP model for supporting disconnected operation. <p>
* <p>
* A Unique identifier (UID) is a positive long value, assigned to
* each message in a specific folder. Unique identifiers are assigned
* in a strictly <strong>ascending</strong> fashion in the mailbox.
* That is, as each message is added to the mailbox it is assigned a
* higher UID than the message(s) which were added previously. Unique
* identifiers persist across sessions. This permits a client to
* resynchronize its state from a previous session with the server. <p>
* <p>
* Associated with every mailbox is a unique identifier validity value.
* If unique identifiers from an earlier session fail to persist to
* this session, the unique identifier validity value
* <strong>must</strong> be greater than the one used in the earlier
* session. <p>
* <p>
* Refer to <A HREF="http://www.ietf.org/rfc/rfc2060.txt">RFC 2060</A>
* for more information.
* <p>
* All the Folder objects returned by the default IMAP provider implement
* the UIDFolder interface. Use it as follows:
* <blockquote><pre>
*
* Folder f = store.getFolder("whatever");
* UIDFolder uf = (UIDFolder)f;
* long uid = uf.getUID(msg);
*
* </pre></blockquote>
*
* @author Bill Shannon
* @author John Mani
*/
public interface UIDFolder {
/**
* This is a special value that can be used as the <code>end</code>
* parameter in <code>getMessagesByUID(start, end)</code>, to denote the
* UID of the last message in the folder.
*
* @see #getMessagesByUID
*/
long LASTUID = -1;
/**
* The largest value possible for a UID, a 32-bit unsigned integer.
* This can be used to fetch all new messages by keeping track of the
* last UID that was seen and using:
* <blockquote><pre>
*
* Folder f = store.getFolder("whatever");
* UIDFolder uf = (UIDFolder)f;
* Message[] newMsgs =
* uf.getMessagesByUID(lastSeenUID + 1, UIDFolder.MAXUID);
*
* </pre></blockquote>
*
* @since JavaMail 1.6
*/
long MAXUID = 0xffffffffL; // max 32-bit unsigned int
/**
* Returns the UIDValidity value associated with this folder. <p>
* <p>
* Clients typically compare this value against a UIDValidity
* value saved from a previous session to insure that any cached
* UIDs are not stale.
*
* @return UIDValidity
* @throws MessagingException for failures
*/
long getUIDValidity() throws MessagingException;
/**
* Get the Message corresponding to the given UID. If no such
* message exists, <code>null</code> is returned.
*
* @param uid UID for the desired message
* @return the Message object. <code>null</code> is returned
* if no message corresponding to this UID is obtained.
* @throws MessagingException for failures
*/
Message getMessageByUID(long uid) throws MessagingException;
/**
* Get the Messages specified by the given range. The special
* value LASTUID can be used for the <code>end</code> parameter
* to indicate the UID of the last message in the folder. <p>
* <p>
* Note that <code>end</code> need not be greater than <code>start</code>;
* the order of the range doesn't matter.
* Note also that, unless the folder is empty, use of LASTUID ensures
* that at least one message will be returned - the last message in the
* folder.
*
* @param start start UID
* @param end end UID
* @return array of Message objects
* @throws MessagingException for failures
* @see #LASTUID
*/
Message[] getMessagesByUID(long start, long end)
throws MessagingException;
/**
* Get the Messages specified by the given array of UIDs. If any UID is
* invalid, <code>null</code> is returned for that entry. <p>
* <p>
* Note that the returned array will be of the same size as the specified
* array of UIDs, and <code>null</code> entries may be present in the
* array to indicate invalid UIDs.
*
* @param uids array of UIDs
* @return array of Message objects
* @throws MessagingException for failures
*/
Message[] getMessagesByUID(long[] uids)
throws MessagingException;
/**
* Get the UID for the specified message. Note that the message
* <strong>must</strong> belong to this folder. Otherwise
* java.util.NoSuchElementException is thrown.
*
* @param message Message from this folder
* @return UID for this message
* @throws NoSuchElementException if the given Message
* is not in this Folder.
* @throws MessagingException for other failures
*/
long getUID(Message message) throws MessagingException;
/**
* Returns the predicted UID that will be assigned to the
* next message that is appended to this folder.
* Messages might be appended to the folder after this value
* is retrieved, causing this value to be out of date.
* This value might only be updated when a folder is first opened.
* Note that messages may have been appended to the folder
* while it was open and thus this value may be out of
* date. <p>
* <p>
* If the value is unknown, -1 is returned.
*
* @return the UIDNEXT value, or -1 if unknown
* @throws MessagingException for failures
* @since JavaMail 1.6
*/
long getUIDNext() throws MessagingException;
/**
* A fetch profile item for fetching UIDs.
* This inner class extends the <code>FetchProfile.Item</code>
* class to add new FetchProfile item types, specific to UIDFolders.
* The only item currently defined here is the <code>UID</code> item.
*
* @see FetchProfile
*/
class FetchProfileItem extends FetchProfile.Item {
/**
* UID is a fetch profile item that can be included in a
* <code>FetchProfile</code> during a fetch request to a Folder.
* This item indicates that the UIDs for messages in the specified
* range are desired to be prefetched. <p>
* <p>
* An example of how a client uses this is below:
* <blockquote><pre>
*
* FetchProfile fp = new FetchProfile();
* fp.add(UIDFolder.FetchProfileItem.UID);
* folder.fetch(msgs, fp);
*
* </pre></blockquote>
*/
public static final FetchProfileItem UID =
new FetchProfileItem("UID");
/**
* Constructor for an item.
*
* @param name the item name
*/
protected FetchProfileItem(String name) {
super(name);
}
}
}

View file

@ -0,0 +1,774 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.BitSet;
import java.util.Locale;
import java.util.Objects;
/**
* The name of a URL. This class represents a URL name and also
* provides the basic parsing functionality to parse most internet
* standard URL schemes. <p>
* <p>
* Note that this class differs from <code>java.net.URL</code>
* in that this class just represents the name of a URL, it does
* not model the connection to a URL.
*
* @author Christopher Cotton
* @author Bill Shannon
*/
public class URLName {
static final int caseDiff = ('a' - 'A');
/**
* The class contains a utility method for converting a
* <code>String</code> into a MIME format called
* "<code>x-www-form-urlencoded</code>" format.
* <p>
* To convert a <code>String</code>, each character is examined in turn:
* <ul>
* <li>The ASCII characters '<code>a</code>' through '<code>z</code>',
* '<code>A</code>' through '<code>Z</code>', '<code>0</code>'
* through '<code>9</code>', and &quot;.&quot;, &quot;-&quot;,
* &quot;*&quot;, &quot;_&quot; remain the same.
* <li>The space character '<code>&nbsp;</code>' is converted into a
* plus sign '<code>+</code>'.
* <li>All other characters are converted into the 3-character string
* "<code>%<i>xy</i></code>", where <i>xy</i> is the two-digit
* hexadecimal representation of the lower 8-bits of the character.
* </ul>
*
* @author Herb Jellinek
* @since JDK1.0
*/
static BitSet dontNeedEncoding;
/**
* A way to turn off encoding, just in case...
*/
private static boolean doEncode = true;
static {
try {
doEncode = !Boolean.getBoolean("mail.URLName.dontencode");
} catch (Exception ex) {
// ignore any errors
}
}
static {
dontNeedEncoding = new BitSet(256);
int i;
for (i = 'a'; i <= 'z'; i++) {
dontNeedEncoding.set(i);
}
for (i = 'A'; i <= 'Z'; i++) {
dontNeedEncoding.set(i);
}
for (i = '0'; i <= '9'; i++) {
dontNeedEncoding.set(i);
}
/* encoding a space to a + is done in the encode() method */
dontNeedEncoding.set(' ');
dontNeedEncoding.set('-');
dontNeedEncoding.set('_');
dontNeedEncoding.set('.');
dontNeedEncoding.set('*');
}
/**
* The full version of the URL
*/
protected String fullURL;
/**
* The protocol to use (ftp, http, nntp, imap, pop3 ... etc.) .
*/
private String protocol;
/**
* The username to use when connecting
*/
private String username;
/**
* The password to use when connecting.
*/
private String password;
/**
* The host name to which to connect.
*/
private String host;
/**
* The host's IP address, used in equals and hashCode.
* Computed on demand.
*/
private InetAddress hostAddress;
private boolean hostAddressKnown = false;
/**
* The protocol port to connect to.
*/
private int port = -1;
/**
* The specified file name on that host.
*/
private String file;
/**
* # reference.
*/
private String ref;
/**
* Our hash code.
*/
private int hashCode = 0;
/**
* Creates a URLName object from the specified protocol,
* host, port number, file, username, and password. Specifying a port
* number of -1 indicates that the URL should use the default port for
* the protocol.
*
* @param protocol the protocol
* @param host the host name
* @param port the port number
* @param file the file
* @param username the user name
* @param password the password
*/
public URLName(
String protocol,
String host,
int port,
String file,
String username,
String password
) {
this.protocol = protocol;
this.host = host;
this.port = port;
int refStart;
if (file != null && (refStart = file.indexOf('#')) != -1) {
this.file = file.substring(0, refStart);
this.ref = file.substring(refStart + 1);
} else {
this.file = file;
this.ref = null;
}
this.username = doEncode ? encode(username) : username;
this.password = doEncode ? encode(password) : password;
}
/**
* Construct a URLName from a java.net.URL object.
*
* @param url the URL
*/
public URLName(URL url) {
this(url.toString());
}
/**
* Construct a URLName from the string. Parses out all the possible
* information (protocol, host, port, file, username, password).
*
* @param url the URL string
*/
public URLName(String url) {
parseString(url);
}
/**
* Translates a string into <code>x-www-form-urlencoded</code> format.
*
* @param s <code>String</code> to be translated.
* @return the translated <code>String</code>.
*/
static String encode(String s) {
if (s == null)
return null;
// the common case is no encoding is needed
for (int i = 0; i < s.length(); i++) {
int c = s.charAt(i);
if (c == ' ' || !dontNeedEncoding.get(c))
return _encode(s);
}
return s;
}
private static String _encode(String s) {
int maxBytesPerChar = 10;
StringBuilder out = new StringBuilder(s.length());
ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
OutputStreamWriter writer = new OutputStreamWriter(buf);
for (int i = 0; i < s.length(); i++) {
int c = s.charAt(i);
if (dontNeedEncoding.get(c)) {
if (c == ' ') {
c = '+';
}
out.append((char) c);
} else {
// convert to external encoding before hex conversion
try {
writer.write(c);
writer.flush();
} catch (IOException e) {
buf.reset();
continue;
}
byte[] ba = buf.toByteArray();
for (int j = 0; j < ba.length; j++) {
out.append('%');
char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
// converting to use uppercase letter as part of
// the hex value if ch is a letter.
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
out.append(ch);
ch = Character.forDigit(ba[j] & 0xF, 16);
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
out.append(ch);
}
buf.reset();
}
}
return out.toString();
}
/**
* Decodes a &quot;x-www-form-urlencoded&quot;
* to a <code>String</code>.
*
* @param s the <code>String</code> to decode
* @return the newly decoded <code>String</code>
*/
static String decode(String s) {
if (s == null)
return null;
if (indexOfAny(s, "+%") == -1)
return s; // the common case
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '+':
sb.append(' ');
break;
case '%':
try {
sb.append((char) Integer.parseInt(
s.substring(i + 1, i + 3), 16));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"Illegal URL encoded value: " +
s.substring(i, i + 3));
}
i += 2;
break;
default:
sb.append(c);
break;
}
}
// Undo conversion to external encoding
String result = sb.toString();
byte[] inputBytes = result.getBytes(StandardCharsets.ISO_8859_1);
result = new String(inputBytes);
return result;
}
/**
* Return the first index of any of the characters in "any" in "s",
* or -1 if none are found.
* <p>
* This should be a method on String.
*/
private static int indexOfAny(String s, String any) {
return indexOfAny(s, any, 0);
}
private static int indexOfAny(String s, String any, int start) {
try {
int len = s.length();
for (int i = start; i < len; i++) {
if (any.indexOf(s.charAt(i)) >= 0)
return i;
}
return -1;
} catch (StringIndexOutOfBoundsException e) {
return -1;
}
}
/**
* Constructs a string representation of this URLName.
*/
@Override
public String toString() {
if (fullURL == null) {
// add the "protocol:"
StringBuilder tempURL = new StringBuilder();
if (protocol != null) {
tempURL.append(protocol);
tempURL.append(":");
}
if (username != null || host != null) {
// add the "//"
tempURL.append("//");
// add the user:password@
// XXX - can you just have a password? without a username?
if (username != null) {
tempURL.append(username);
if (password != null) {
tempURL.append(":");
tempURL.append(password);
}
tempURL.append("@");
}
// add host
if (host != null) {
tempURL.append(host);
}
// add port (if needed)
if (port != -1) {
tempURL.append(":");
tempURL.append(port);
}
if (file != null)
tempURL.append("/");
}
// add the file
if (file != null) {
tempURL.append(file);
}
// add the ref
if (ref != null) {
tempURL.append("#");
tempURL.append(ref);
}
// create the fullURL now
fullURL = tempURL.toString();
}
return fullURL;
}
/**
* Method which does all of the work of parsing the string.
*
* @param url the URL string to parse
*/
private void parseString(String url) {
// initialize everything in case called from subclass
// (URLName really should be a final class)
protocol = file = ref = host = username = password = null;
port = -1;
int len = url.length();
// find the protocol
// XXX - should check for only legal characters before the colon
// (legal: a-z, A-Z, 0-9, "+", ".", "-")
int protocolEnd = url.indexOf(':');
if (protocolEnd != -1)
protocol = url.substring(0, protocolEnd);
// is this an Internet standard URL that contains a host name?
if (url.regionMatches(protocolEnd + 1, "//", 0, 2)) {
// find where the file starts
String fullhost = null;
int fileStart = url.indexOf('/', protocolEnd + 3);
if (fileStart != -1) {
fullhost = url.substring(protocolEnd + 3, fileStart);
if (fileStart + 1 < len)
file = url.substring(fileStart + 1);
else
file = "";
} else
fullhost = url.substring(protocolEnd + 3);
// examine the fullhost, for username password etc.
int i = fullhost.indexOf('@');
if (i != -1) {
String fulluserpass = fullhost.substring(0, i);
fullhost = fullhost.substring(i + 1);
// get user and password
int passindex = fulluserpass.indexOf(':');
if (passindex != -1) {
username = fulluserpass.substring(0, passindex);
password = fulluserpass.substring(passindex + 1);
} else {
username = fulluserpass;
}
}
// get the port (if there)
int portindex;
if (fullhost.length() > 0 && fullhost.charAt(0) == '[') {
// an IPv6 address?
portindex = fullhost.indexOf(':', fullhost.indexOf(']'));
} else {
portindex = fullhost.indexOf(':');
}
if (portindex != -1) {
String portstring = fullhost.substring(portindex + 1);
if (portstring.length() > 0) {
try {
port = Integer.parseInt(portstring);
} catch (NumberFormatException nfex) {
port = -1;
}
}
host = fullhost.substring(0, portindex);
} else {
host = fullhost;
}
} else {
if (protocolEnd + 1 < len)
file = url.substring(protocolEnd + 1);
}
// extract the reference from the file name, if any
int refStart;
if (file != null && (refStart = file.indexOf('#')) != -1) {
ref = file.substring(refStart + 1);
file = file.substring(0, refStart);
}
}
/**
* Returns the port number of this URLName.
* Returns -1 if the port is not set.
*
* @return the port number
*/
public int getPort() {
return port;
}
/**
* Returns the protocol of this URLName.
* Returns null if this URLName has no protocol.
*
* @return the protocol
*/
public String getProtocol() {
return protocol;
}
/**
* Returns the file name of this URLName.
* Returns null if this URLName has no file name.
*
* @return the file name of this URLName
*/
public String getFile() {
return file;
}
/**
* Returns the reference of this URLName.
* Returns null if this URLName has no reference.
*
* @return the reference part of the URLName
*/
public String getRef() {
return ref;
}
/**
* Returns the host of this URLName.
* Returns null if this URLName has no host.
*
* @return the host name
*/
public String getHost() {
return host;
}
/* The list of characters that are not encoded have been determined by
referencing O'Reilly's "HTML: The Definitive Guide" (page 164). */
/**
* Returns the user name of this URLName.
* Returns null if this URLName has no user name.
*
* @return the user name
*/
public String getUsername() {
return doEncode ? decode(username) : username;
}
/**
* Returns the password of this URLName.
* Returns null if this URLName has no password.
*
* @return the password
*/
public String getPassword() {
return doEncode ? decode(password) : password;
}
/**
* Constructs a URL from the URLName.
*
* @return the URL
* @throws MalformedURLException if the URL is malformed
*/
public URL getURL() throws MalformedURLException {
// URL expects the file to include the separating "/"
String f = getFile();
if (f == null)
f = "";
else
f = "/" + f;
return URI.create(getProtocol() + "://" + getHost() + ":" + getPort() + "/" + f).toURL();
}
/*
* The class contains a utility method for converting from
* a MIME format called "<code>x-www-form-urlencoded</code>"
* to a <code>String</code>
* <p>
* To convert to a <code>String</code>, each character is examined in turn:
* <ul>
* <li>The ASCII characters '<code>a</code>' through '<code>z</code>',
* '<code>A</code>' through '<code>Z</code>', and '<code>0</code>'
* through '<code>9</code>' remain the same.
* <li>The plus sign '<code>+</code>'is converted into a
* space character '<code>&nbsp;</code>'.
* <li>The remaining characters are represented by 3-character
* strings which begin with the percent sign,
* "<code>%<i>xy</i></code>", where <i>xy</i> is the two-digit
* hexadecimal representation of the lower 8-bits of the character.
* </ul>
*
* @author Mark Chamness
* @author Michael McCloskey
* @since 1.2
*/
/**
* Compares two URLNames. The result is true if and only if the
* argument is not null and is a URLName object that represents the
* same URLName as this object. Two URLName objects are equal if
* they have the same protocol and the same host,
* the same port number on the host, the same username,
* and the same file on the host. The fields (host, username,
* file) are also considered the same if they are both
* null. <p>
* <p>
* Hosts are considered equal if the names are equal (case independent)
* or if host name lookups for them both succeed and they both reference
* the same IP address. <p>
* <p>
* Note that URLName has no knowledge of default port numbers for
* particular protocols, so "imap://host" and "imap://host:143"
* would not compare as equal. <p>
* <p>
* Note also that the password field is not included in the comparison,
* nor is any reference field appended to the filename.
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof URLName))
return false;
URLName u2 = (URLName) obj;
// compare protocols
if (!(Objects.equals(protocol, u2.protocol)))
return false;
// compare hosts
InetAddress a1 = getHostAddress(), a2 = u2.getHostAddress();
// if we have internet address for both, and they're not the same, fail
if (a1 != null && a2 != null) {
if (!a1.equals(a2))
return false;
// else, if we have host names for both, and they're not the same, fail
} else if (host != null && u2.host != null) {
if (!host.equalsIgnoreCase(u2.host))
return false;
// else, if not both null
} else if (host != u2.host) {
return false;
}
// at this point, hosts match
// compare usernames
if (!(Objects.equals(username, u2.username)))
return false;
// Forget about password since it doesn't
// really denote a different store.
// compare files
String f1 = file == null ? "" : file;
String f2 = u2.file == null ? "" : u2.file;
if (!f1.equals(f2))
return false;
// compare ports
if (port != u2.port)
return false;
// all comparisons succeeded, they're equal
return true;
}
/**
* Compute the hash code for this URLName.
*/
@Override
public int hashCode() {
if (hashCode != 0)
return hashCode;
if (protocol != null)
hashCode += protocol.hashCode();
InetAddress addr = getHostAddress();
if (addr != null)
hashCode += addr.hashCode();
else if (host != null)
hashCode += host.toLowerCase(Locale.ENGLISH).hashCode();
if (username != null)
hashCode += username.hashCode();
if (file != null)
hashCode += file.hashCode();
hashCode += port;
return hashCode;
}
/**
* Get the IP address of our host. Look up the
* name the first time and remember that we've done
* so, whether the lookup fails or not.
*/
private synchronized InetAddress getHostAddress() {
if (hostAddressKnown)
return hostAddress;
if (host == null)
return null;
try {
hostAddress = InetAddress.getByName(host);
} catch (UnknownHostException ex) {
hostAddress = null;
}
hostAddressKnown = true;
return hostAddress;
}
/*
// Do not remove, this is needed when testing new URL cases
public static void main(String[] argv) {
String [] testURLNames = {
"protocol://userid:password@host:119/file",
"http://funny/folder/file.html",
"http://funny/folder/file.html#ref",
"http://funny/folder/file.html#",
"http://funny/#ref",
"imap://jmr:secret@labyrinth//var/mail/jmr",
"nntp://fred@labyrinth:143/save/it/now.mbox",
"imap://jmr@labyrinth/INBOX",
"imap://labryrinth",
"imap://labryrinth/",
"file:",
"file:INBOX",
"file:/home/shannon/mail/foo",
"/tmp/foo",
"//host/tmp/foo",
":/tmp/foo",
"/really/weird:/tmp/foo#bar",
""
};
URLName url =
new URLName("protocol", "host", 119, "file", "userid", "password");
System.out.println("Test URL: " + url.toString());
if (argv.length == 0) {
for (int i = 0; i < testURLNames.length; i++) {
print(testURLNames[i]);
System.out.println();
}
} else {
for (int i = 0; i < argv.length; i++) {
print(argv[i]);
System.out.println();
}
if (argv.length == 2) {
URLName u1 = new URLName(argv[0]);
URLName u2 = new URLName(argv[1]);
System.out.println("URL1 hash code: " + u1.hashCode());
System.out.println("URL2 hash code: " + u2.hashCode());
if (u1.equals(u2))
System.out.println("success, equal");
else
System.out.println("fail, not equal");
if (u2.equals(u1))
System.out.println("success, equal");
else
System.out.println("fail, not equal");
if (u1.hashCode() == u2.hashCode())
System.out.println("success, hashCodes equal");
else
System.out.println("fail, hashCodes not equal");
}
}
}
private static void print(String name) {
URLName url = new URLName(name);
System.out.println("Original URL: " + name);
System.out.println("The fullUrl : " + url.toString());
if (!name.equals(url.toString()))
System.out.println(" : NOT EQUAL!");
System.out.println("The protocol is: " + url.getProtocol());
System.out.println("The host is: " + url.getHost());
System.out.println("The port is: " + url.getPort());
System.out.println("The user is: " + url.getUsername());
System.out.println("The password is: " + url.getPassword());
System.out.println("The file is: " + url.getFile());
System.out.println("The ref is: " + url.getRef());
}
*/
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* The adapter which receives connection events.
* The methods in this class are empty; this class is provided as a
* convenience for easily creating listeners by extending this class
* and overriding only the methods of interest.
*
* @author John Mani
*/
public abstract class ConnectionAdapter implements ConnectionListener {
/**
* Creates a default {@code ConnectionAdapter}.
*/
public ConnectionAdapter() {
}
@Override
public void opened(ConnectionEvent e) {
}
@Override
public void disconnected(ConnectionEvent e) {
}
@Override
public void closed(ConnectionEvent e) {
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* This class models Connection events.
*
* @author John Mani
*/
@SuppressWarnings("serial")
public class ConnectionEvent extends MailEvent {
/**
* A connection was opened.
*/
public static final int OPENED = 1;
/**
* A connection was disconnected (not currently used).
*/
public static final int DISCONNECTED = 2;
/**
* A connection was closed.
*/
public static final int CLOSED = 3;
/**
* The event type.
*
* @serial
*/
protected int type;
/**
* Construct a ConnectionEvent.
*
* @param source The source object
* @param type the event type
*/
public ConnectionEvent(Object source, int type) {
super(source);
this.type = type;
}
/**
* Return the type of this event
*
* @return type
*/
public int getType() {
return type;
}
/**
* Invokes the appropriate ConnectionListener method
*/
@Override
public void dispatch(Object listener) {
if (type == OPENED)
((ConnectionListener) listener).opened(this);
else if (type == DISCONNECTED)
((ConnectionListener) listener).disconnected(this);
else if (type == CLOSED)
((ConnectionListener) listener).closed(this);
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* This is the Listener interface for Connection events.
*
* @author John Mani
*/
public interface ConnectionListener extends java.util.EventListener {
/**
* Invoked when a Store/Folder/Transport is opened.
*
* @param e the ConnectionEvent
*/
void opened(ConnectionEvent e);
/**
* Invoked when a Store is disconnected. Note that a folder
* cannot be disconnected, so a folder will not fire this event
*
* @param e the ConnectionEvent
*/
void disconnected(ConnectionEvent e);
/**
* Invoked when a Store/Folder/Transport is closed.
*
* @param e the ConnectionEvent
*/
void closed(ConnectionEvent e);
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* The adapter which receives Folder events.
* The methods in this class are empty; this class is provided as a
* convenience for easily creating listeners by extending this class
* and overriding only the methods of interest.
*
* @author John Mani
*/
public abstract class FolderAdapter implements FolderListener {
/**
* Creates a default {@code FolderAdapter}.
*/
public FolderAdapter() {
}
@Override
public void folderCreated(FolderEvent e) {
}
@Override
public void folderRenamed(FolderEvent e) {
}
@Override
public void folderDeleted(FolderEvent e) {
}
}

View file

@ -0,0 +1,147 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
import jakarta.mail.Folder;
/**
* This class models Folder <em>existence</em> events. FolderEvents are
* delivered to FolderListeners registered on the affected Folder as
* well as the containing Store. <p>
* <p>
* Service providers vary widely in their ability to notify clients of
* these events. At a minimum, service providers must notify listeners
* registered on the same Store or Folder object on which the operation
* occurs. Service providers may also notify listeners when changes
* are made through operations on other objects in the same virtual
* machine, or by other clients in the same or other hosts. Such
* notifications are not required and are typically not supported
* by mail protocols (including IMAP).
*
* @author John Mani
* @author Bill Shannon
*/
@SuppressWarnings("serial")
public class FolderEvent extends MailEvent {
/**
* The folder was created.
*/
public static final int CREATED = 1;
/**
* The folder was deleted.
*/
public static final int DELETED = 2;
/**
* The folder was renamed.
*/
public static final int RENAMED = 3;
/**
* The event type.
*
* @serial
*/
protected int type;
/**
* The folder the event occurred on.
*/
transient protected Folder folder;
/**
* The folder that represents the new name, in case of a RENAMED event.
*
* @since JavaMail 1.1
*/
transient protected Folder newFolder;
/**
* Constructor.
*
* @param source The source of the event
* @param folder The affected folder
* @param type The event type
*/
public FolderEvent(Object source, Folder folder, int type) {
this(source, folder, folder, type);
}
/**
* Constructor. Use for RENAMED events.
*
* @param source The source of the event
* @param oldFolder The folder that is renamed
* @param newFolder The folder that represents the new name
* @param type The event type
* @since JavaMail 1.1
*/
public FolderEvent(Object source, Folder oldFolder,
Folder newFolder, int type) {
super(source);
this.folder = oldFolder;
this.newFolder = newFolder;
this.type = type;
}
/**
* Return the type of this event.
*
* @return type
*/
public int getType() {
return type;
}
/**
* Return the affected folder.
*
* @return the affected folder
* @see #getNewFolder
*/
public Folder getFolder() {
return folder;
}
/**
* If this event indicates that a folder is renamed, (i.e, the event type
* is RENAMED), then this method returns the Folder object representing the
* new name. <p>
* <p>
* The <code>getFolder()</code> method returns the folder that is renamed.
*
* @return Folder representing the new name.
* @see #getFolder
* @since JavaMail 1.1
*/
public Folder getNewFolder() {
return newFolder;
}
/**
* Invokes the appropriate FolderListener method
*/
@Override
public void dispatch(Object listener) {
if (type == CREATED)
((FolderListener) listener).folderCreated(this);
else if (type == DELETED)
((FolderListener) listener).folderDeleted(this);
else if (type == RENAMED)
((FolderListener) listener).folderRenamed(this);
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* This is the Listener interface for Folder events.
*
* @author John Mani
*/
public interface FolderListener extends java.util.EventListener {
/**
* Invoked when a Folder is created.
*
* @param e the FolderEvent
*/
void folderCreated(FolderEvent e);
/**
* Invoked when a folder is deleted.
*
* @param e the FolderEvent
*/
void folderDeleted(FolderEvent e);
/**
* Invoked when a folder is renamed.
*
* @param e the FolderEvent
*/
void folderRenamed(FolderEvent e);
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
import java.util.EventObject;
/**
* Common base class for mail events, defining the dispatch method.
*
* @author Bill Shannon
*/
@SuppressWarnings("serial")
public abstract class MailEvent extends EventObject {
/**
* Construct a MailEvent referring to the given source.
*
* @param source the source of the event
*/
public MailEvent(Object source) {
super(source);
}
/**
* This method invokes the appropriate method on a listener for
* this event. Subclasses provide the implementation.
*
* @param listener the listener to invoke on
*/
public abstract void dispatch(Object listener);
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
import jakarta.mail.Message;
/**
* This class models Message change events.
*
* @author John Mani
*/
@SuppressWarnings("serial")
public class MessageChangedEvent extends MailEvent {
/**
* The message's flags changed.
*/
public static final int FLAGS_CHANGED = 1;
/**
* The message's envelope (headers, but not body) changed.
*/
public static final int ENVELOPE_CHANGED = 2;
/**
* The event type.
*
* @serial
*/
protected int type;
/**
* The message that changed.
*/
transient protected Message msg;
/**
* Constructor.
*
* @param source The folder that owns the message
* @param type The change type
* @param msg The changed message
*/
public MessageChangedEvent(Object source, int type, Message msg) {
super(source);
this.msg = msg;
this.type = type;
}
/**
* Return the type of this event.
*
* @return type
*/
public int getMessageChangeType() {
return type;
}
/**
* Return the changed Message.
*
* @return the message
*/
public Message getMessage() {
return msg;
}
/**
* Invokes the appropriate MessageChangedListener method.
*/
@Override
public void dispatch(Object listener) {
((MessageChangedListener) listener).messageChanged(this);
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* This is the Listener interface for MessageChanged events
*
* @author John Mani
*/
public interface MessageChangedListener extends java.util.EventListener {
/**
* Invoked when a message is changed. The change-type specifies
* what changed.
*
* @param e the MessageChangedEvent
* @see MessageChangedEvent#FLAGS_CHANGED
* @see MessageChangedEvent#ENVELOPE_CHANGED
*/
void messageChanged(MessageChangedEvent e);
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* The adapter which receives MessageCount events.
* The methods in this class are empty; this class is provided as a
* convenience for easily creating listeners by extending this class
* and overriding only the methods of interest.
*
* @author John Mani
*/
public abstract class MessageCountAdapter implements MessageCountListener {
/**
* Creates a default {@code MessageCountAdapter}.
*/
public MessageCountAdapter() {
}
@Override
public void messagesAdded(MessageCountEvent e) {
}
@Override
public void messagesRemoved(MessageCountEvent e) {
}
}

View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
import jakarta.mail.Folder;
import jakarta.mail.Message;
/**
* This class notifies changes in the number of messages in a folder. <p>
* <p>
* Note that some folder types may only deliver MessageCountEvents at
* certain times or after certain operations. IMAP in particular will
* only notify the client of MessageCountEvents when a client issues a
* new command. Refer to
* <A HREF="http://www.ietf.org/rfc/rfc3501.txt" TARGET="_top">RFC 3501</A>
* for details.
* A client may want to "poll" the folder by occasionally calling the
* {@link Folder#getMessageCount getMessageCount} or
* {@link Folder#isOpen isOpen} methods
* to solicit any such notifications.
*
* @author John Mani
*/
@SuppressWarnings("serial")
public class MessageCountEvent extends MailEvent {
/**
* The messages were added to their folder
*/
public static final int ADDED = 1;
/**
* The messages were removed from their folder
*/
public static final int REMOVED = 2;
/**
* The event type.
*
* @serial
*/
protected int type;
/**
* If true, this event is the result of an explicit
* expunge by this client, and the messages in this
* folder have been renumbered to account for this.
* If false, this event is the result of an expunge
* by external sources.
*
* @serial
*/
protected boolean removed;
/**
* The messages.
*/
transient protected Message[] msgs;
/**
* Constructor.
*
* @param folder The containing folder
* @param type The event type
* @param removed If true, this event is the result of an explicit
* expunge by this client, and the messages in this
* folder have been renumbered to account for this.
* If false, this event is the result of an expunge
* by external sources.
* @param msgs The messages added/removed
*/
public MessageCountEvent(Folder folder, int type,
boolean removed, Message[] msgs) {
super(folder);
this.type = type;
this.removed = removed;
this.msgs = msgs;
}
/**
* Return the type of this event.
*
* @return type
*/
public int getType() {
return type;
}
/**
* Indicates whether this event is the result of an explicit
* expunge by this client, or due to an expunge from external
* sources. If <code>true</code>, this event is due to an
* explicit expunge and hence all remaining messages in this
* folder have been renumbered. If <code>false</code>, this event
* is due to an external expunge. <p>
* <p>
* Note that this method is valid only if the type of this event
* is <code>REMOVED</code>
*
* @return true if the message has been removed
*/
public boolean isRemoved() {
return removed;
}
/**
* Return the array of messages added or removed.
*
* @return array of messages
*/
public Message[] getMessages() {
return msgs;
}
/**
* Invokes the appropriate MessageCountListener method.
*/
@Override
public void dispatch(Object listener) {
if (type == ADDED)
((MessageCountListener) listener).messagesAdded(this);
else // REMOVED
((MessageCountListener) listener).messagesRemoved(this);
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* This is the Listener interface for MessageCount events.
*
* @author John Mani
*/
public interface MessageCountListener extends java.util.EventListener {
/**
* Invoked when messages are added into a folder.
*
* @param e the MessageCountEvent
*/
void messagesAdded(MessageCountEvent e);
/**
* Invoked when messages are removed (expunged) from a folder.
*
* @param e the MessageCountEvent
*/
void messagesRemoved(MessageCountEvent e);
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
import jakarta.mail.Store;
/**
* This class models notifications from the Store connection. These
* notifications can be ALERTS or NOTICES. ALERTS must be presented
* to the user in a fashion that calls the user's attention to the
* message.
*
* @author John Mani
*/
@SuppressWarnings("serial")
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 event type.
*
* @serial
*/
protected int type;
/**
* The message text to be presented to the user.
*
* @serial
*/
protected String message;
/**
* Construct a StoreEvent.
*
* @param store the source Store
* @param type the event type
* @param message a message assoicated with the event
*/
public StoreEvent(Store store, int type, String message) {
super(store);
this.type = type;
this.message = message;
}
/**
* Return the type of this event.
*
* @return type
* @see #ALERT
* @see #NOTICE
*/
public int getMessageType() {
return type;
}
/**
* Get the message from the Store.
*
* @return message from the Store
*/
public String getMessage() {
return message;
}
/**
* Invokes the appropriate StoreListener method.
*/
@Override
public void dispatch(Object listener) {
((StoreListener) listener).notification(this);
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* This is the Listener interface for Store Notifications.
*
* @author John Mani
*/
public interface StoreListener extends java.util.EventListener {
/**
* Invoked when the Store generates a notification event.
*
* @param e the StoreEvent
* @see StoreEvent#ALERT
* @see StoreEvent#NOTICE
*/
void notification(StoreEvent e);
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* The adapter which receives Transport events.
* The methods in this class are empty; this class is provided as a
* convenience for easily creating listeners by extending this class
* and overriding only the methods of interest.
*
* @author John Mani
*/
public abstract class TransportAdapter implements TransportListener {
/**
* Creates a default {@code TransportAdapter}.
*/
public TransportAdapter() {
}
@Override
public void messageDelivered(TransportEvent e) {
}
@Override
public void messageNotDelivered(TransportEvent e) {
}
@Override
public void messagePartiallyDelivered(TransportEvent e) {
}
}

View file

@ -0,0 +1,164 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
import jakarta.mail.Address;
import jakarta.mail.Message;
import jakarta.mail.Transport;
/**
* This class models Transport events.
*
* @author John Mani
* @author Max Spivak
* @see Transport
* @see TransportListener
*/
@SuppressWarnings("serial")
public class TransportEvent extends MailEvent {
/**
* Message has been successfully delivered to all recipients by the
* transport firing this event. validSent[] contains all the addresses
* this transport sent to successfully. validUnsent[] and invalid[]
* should be null,
*/
public static final int MESSAGE_DELIVERED = 1;
/**
* Message was not sent for some reason. validSent[] should be null.
* validUnsent[] may have addresses that are valid (but the message
* wasn't sent to them). invalid[] should likely contain invalid addresses.
*/
public static final int MESSAGE_NOT_DELIVERED = 2;
/**
* Message was successfully sent to some recipients but not to all.
* validSent[] holds addresses of recipients to whom the message was sent.
* validUnsent[] holds valid addresses to which the message was not sent.
* invalid[] holds invalid addresses, if any.
*/
public static final int MESSAGE_PARTIALLY_DELIVERED = 3;
/**
* The event type.
*
* @serial
*/
protected int type;
/**
* The valid address to which the message was sent.
*/
transient protected Address[] validSent;
/**
* The valid address to which the message was not sent.
*/
transient protected Address[] validUnsent;
/**
* The invalid addresses.
*/
transient protected Address[] invalid;
/**
* The Message to which this event applies.
*/
transient protected Message msg;
/**
* Constructor.
*
* @param transport The Transport object
* @param type the event type (MESSAGE_DELIVERED, etc.)
* @param validSent the valid addresses to which the message was sent
* @param validUnsent the valid addresses to which the message was
* not sent
* @param invalid the invalid addresses
* @param msg the message being sent
*/
public TransportEvent(Transport transport, int type, Address[] validSent,
Address[] validUnsent, Address[] invalid,
Message msg) {
super(transport);
this.type = type;
this.validSent = validSent;
this.validUnsent = validUnsent;
this.invalid = invalid;
this.msg = msg;
}
/**
* Return the type of this event.
*
* @return type
*/
public int getType() {
return type;
}
/**
* Return the addresses to which this message was sent succesfully.
*
* @return Addresses to which the message was sent successfully or null
*/
public Address[] getValidSentAddresses() {
return validSent;
}
/**
* Return the addresses that are valid but to which this message
* was not sent.
*
* @return Addresses that are valid but to which the message was
* not sent successfully or null
*/
public Address[] getValidUnsentAddresses() {
return validUnsent;
}
/**
* Return the addresses to which this message could not be sent.
*
* @return Addresses to which the message sending failed or null
*/
public Address[] getInvalidAddresses() {
return invalid;
}
/**
* Get the Message object associated with this Transport Event.
*
* @return the Message object
* @since JavaMail 1.2
*/
public Message getMessage() {
return msg;
}
/**
* Invokes the appropriate TransportListener method.
*/
@Override
public void dispatch(Object listener) {
if (type == MESSAGE_DELIVERED)
((TransportListener) listener).messageDelivered(this);
else if (type == MESSAGE_NOT_DELIVERED)
((TransportListener) listener).messageNotDelivered(this);
else // MESSAGE_PARTIALLY_DELIVERED
((TransportListener) listener).messagePartiallyDelivered(this);
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.event;
/**
* This is the Listener interface for Transport events
*
* @author John Mani
* @author Max Spivak
* @see jakarta.mail.Transport
* @see TransportEvent
*/
public interface TransportListener extends java.util.EventListener {
/**
* Invoked when a Message is succesfully delivered.
*
* @param e TransportEvent
*/
void messageDelivered(TransportEvent e);
/**
* Invoked when a Message is not delivered.
*
* @param e TransportEvent
* @see TransportEvent
*/
void messageNotDelivered(TransportEvent e);
/**
* Invoked when a Message is partially delivered.
*
* @param e TransportEvent
* @see TransportEvent
*/
void messagePartiallyDelivered(TransportEvent e);
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
/**
* Listeners and events for the Jakarta Mail API.
* This package defines listener classes and event classes used by the classes
* defined in the <code>jakarta.mail</code> package.
*/
package jakarta.mail.event;

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
/**
* The exception thrown when a wrongly formatted address is encountered.
*
* @author Bill Shannon
* @author Max Spivak
*/
@SuppressWarnings("serial")
public class AddressException extends ParseException {
/**
* The string being parsed.
*
* @serial
*/
protected String ref = null;
/**
* The index in the string where the error occurred, or -1 if not known.
*
* @serial
*/
protected int pos = -1;
/**
* Constructs an AddressException with no detail message.
*/
public AddressException() {
super();
}
/**
* Constructs an AddressException with the specified detail message.
*
* @param s the detail message
*/
public AddressException(String s) {
super(s);
}
/**
* Constructs an AddressException with the specified detail message
* and reference info.
*
* @param s the detail message
* @param ref the string being parsed
*/
public AddressException(String s, String ref) {
super(s);
this.ref = ref;
}
/**
* Constructs an AddressException with the specified detail message
* and reference info.
*
* @param s the detail message
* @param ref the string being parsed
* @param pos the position of the error
*/
public AddressException(String s, String ref, int pos) {
super(s);
this.ref = ref;
this.pos = pos;
}
/**
* Get the string that was being parsed when the error was detected
* (null if not relevant).
*
* @return the string that was being parsed
*/
public String getRef() {
return ref;
}
/**
* Get the position with the reference string where the error was
* detected (-1 if not relevant).
*
* @return the position within the string of the error
*/
public int getPos() {
return pos;
}
@Override
public String toString() {
String s = super.toString();
if (ref == null)
return s;
s += " in string ``" + ref + "''";
if (pos < 0)
return s;
return s + " at position " + pos;
}
}

View file

@ -0,0 +1,185 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
/**
* This class represents a MIME ContentDisposition value. It provides
* methods to parse a ContentDisposition string into individual components
* and to generate a MIME style ContentDisposition string.
*
* @author John Mani
*/
public class ContentDisposition {
private static final boolean contentDispositionStrict =
MimeUtility.getBooleanSystemProperty("mail.mime.contentdisposition.strict", true);
private String disposition; // disposition
private ParameterList list; // parameter list
/**
* No-arg Constructor.
*/
public ContentDisposition() {
}
/**
* Constructor.
*
* @param disposition disposition
* @param list ParameterList
* @since JavaMail 1.2
*/
public ContentDisposition(String disposition, ParameterList list) {
this.disposition = disposition;
this.list = list;
}
/**
* Constructor that takes a ContentDisposition string. The String
* is parsed into its constituents: dispostion and parameters.
* A ParseException is thrown if the parse fails.
*
* @param s the ContentDisposition string.
* @throws ParseException if the parse fails.
* @since JavaMail 1.2
*/
public ContentDisposition(String s) throws ParseException {
HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
HeaderTokenizer.Token tk;
// First "disposition" ..
tk = h.next();
if (tk.getType() != HeaderTokenizer.Token.ATOM) {
if (contentDispositionStrict) {
throw new ParseException("Expected disposition, got " +
tk.getValue());
}
} else {
disposition = tk.getValue();
}
// Then parameters ..
String rem = h.getRemainder();
if (rem != null) {
try {
list = new ParameterList(rem);
} catch (ParseException px) {
if (contentDispositionStrict) {
throw px;
}
}
}
}
/**
* Return the disposition value.
*
* @return the disposition
* @since JavaMail 1.2
*/
public String getDisposition() {
return disposition;
}
/**
* Set the disposition. Replaces the existing disposition.
*
* @param disposition the disposition
* @since JavaMail 1.2
*/
public void setDisposition(String disposition) {
this.disposition = disposition;
}
/**
* Return the specified parameter value. Returns <code>null</code>
* if this parameter is absent.
*
* @param name the parameter name
* @return parameter value
* @since JavaMail 1.2
*/
public String getParameter(String name) {
if (list == null)
return null;
return list.get(name);
}
/**
* Return a ParameterList object that holds all the available
* parameters. Returns null if no parameters are available.
*
* @return ParameterList
* @since JavaMail 1.2
*/
public ParameterList getParameterList() {
return list;
}
/**
* Set a new ParameterList.
*
* @param list ParameterList
* @since JavaMail 1.2
*/
public void setParameterList(ParameterList list) {
this.list = list;
}
/**
* Set the specified parameter. If this parameter already exists,
* it is replaced by this new value.
*
* @param name parameter name
* @param value parameter value
* @since JavaMail 1.2
*/
public void setParameter(String name, String value) {
if (list == null)
list = new ParameterList();
list.set(name, value);
}
/**
* Retrieve a RFC2045 style string representation of
* this ContentDisposition. Returns an empty string if
* the conversion failed.
*
* @return RFC2045 style string
* @since JavaMail 1.2
*/
@Override
public String toString() {
if (disposition == null)
return "";
if (list == null)
return disposition;
StringBuilder sb = new StringBuilder(disposition);
// append the parameter list
// use the length of the string buffer + the length of
// the header name formatted as follows "Content-Disposition: "
sb.append(list.toString(sb.length() + 21));
return sb.toString();
}
}

View file

@ -0,0 +1,276 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
/**
* This class represents a MIME Content-Type value. It provides
* methods to parse a Content-Type string into individual components
* and to generate a MIME style Content-Type string.
*
* @author John Mani
*/
public class ContentType {
private String primaryType; // primary type
private String subType; // subtype
private ParameterList list; // parameter list
/**
* No-arg Constructor.
*/
public ContentType() {
}
/**
* Constructor.
*
* @param primaryType primary type
* @param subType subType
* @param list ParameterList
*/
public ContentType(String primaryType, String subType,
ParameterList list) {
this.primaryType = primaryType;
this.subType = subType;
this.list = list;
}
/**
* Constructor that takes a Content-Type string. The String
* is parsed into its constituents: primaryType, subType
* and parameters. A ParseException is thrown if the parse fails.
*
* @param s the Content-Type string.
* @throws ParseException if the parse fails.
*/
public ContentType(String s) throws ParseException {
HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
HeaderTokenizer.Token tk;
// First "type" ..
tk = h.next();
if (tk.getType() != HeaderTokenizer.Token.ATOM)
throw new ParseException("In Content-Type string <" + s + ">" +
", expected MIME type, got " +
tk.getValue());
primaryType = tk.getValue();
// The '/' separator ..
tk = h.next();
if ((char) tk.getType() != '/')
throw new ParseException("In Content-Type string <" + s + ">" +
", expected '/', got " + tk.getValue());
// Then "subType" ..
tk = h.next();
if (tk.getType() != HeaderTokenizer.Token.ATOM)
throw new ParseException("In Content-Type string <" + s + ">" +
", expected MIME subtype, got " +
tk.getValue());
subType = tk.getValue();
// Finally parameters ..
String rem = h.getRemainder();
if (rem != null)
list = new ParameterList(rem);
}
/**
* Return the primary type.
*
* @return the primary type
*/
public String getPrimaryType() {
return primaryType;
}
/**
* Set the primary type. Overrides existing primary type.
*
* @param primaryType primary type
*/
public void setPrimaryType(String primaryType) {
this.primaryType = primaryType;
}
/**
* Return the subType.
*
* @return the subType
*/
public String getSubType() {
return subType;
}
/**
* Set the subType. Replaces the existing subType.
*
* @param subType the subType
*/
public void setSubType(String subType) {
this.subType = subType;
}
/**
* Return the MIME type string, without the parameters.
* The returned value is basically the concatenation of
* the primaryType, the '/' character and the secondaryType.
*
* @return the type
*/
public String getBaseType() {
if (primaryType == null || subType == null)
return "";
return primaryType + '/' + subType;
}
/**
* Return the specified parameter value. Returns <code>null</code>
* if this parameter is absent.
*
* @param name the parameter name
* @return parameter value
*/
public String getParameter(String name) {
if (list == null)
return null;
return list.get(name);
}
/**
* Return a ParameterList object that holds all the available
* parameters. Returns null if no parameters are available.
*
* @return ParameterList
*/
public ParameterList getParameterList() {
return list;
}
/**
* Set a new ParameterList.
*
* @param list ParameterList
*/
public void setParameterList(ParameterList list) {
this.list = list;
}
/**
* Set the specified parameter. If this parameter already exists,
* it is replaced by this new value.
*
* @param name parameter name
* @param value parameter value
*/
public void setParameter(String name, String value) {
if (list == null)
list = new ParameterList();
list.set(name, value);
}
/**
* Retrieve a RFC2045 style string representation of
* this Content-Type. Returns an empty string if
* the conversion failed.
*
* @return RFC2045 style string
*/
@Override
public String toString() {
if (primaryType == null || subType == null) // need both
return "";
StringBuilder sb = new StringBuilder();
sb.append(primaryType).append('/').append(subType);
if (list != null)
// append the parameter list
// use the length of the string buffer + the length of
// the header name formatted as follows "Content-Type: "
sb.append(list.toString(sb.length() + 14));
return sb.toString();
}
/**
* Match with the specified ContentType object. This method
* compares <strong>only the <code>primaryType</code> and
* <code>subType</code> </strong>. The parameters of both operands
* are ignored. <p>
* <p>
* For example, this method will return <code>true</code> when
* comparing the ContentTypes for <strong>"text/plain"</strong>
* and <strong>"text/plain; charset=foobar"</strong>.
* <p>
* If the <code>subType</code> of either operand is the special
* character '*', then the subtype is ignored during the match.
* For example, this method will return <code>true</code> when
* comparing the ContentTypes for <strong>"text/plain"</strong>
* and <strong>"text/*" </strong>
*
* @param cType ContentType to compare this against
* @return true if it matches
*/
public boolean match(ContentType cType) {
// Match primaryType
if (!((primaryType == null && cType.getPrimaryType() == null) ||
(primaryType != null &&
primaryType.equalsIgnoreCase(cType.getPrimaryType()))))
return false;
String sType = cType.getSubType();
// If either one of the subTypes is wildcarded, return true
if ((subType != null && subType.startsWith("*")) ||
(sType != null && sType.startsWith("*")))
return true;
// Match subType
return (subType == null && sType == null) ||
(subType != null && subType.equalsIgnoreCase(sType));
}
/**
* Match with the specified content-type string. This method
* compares <strong>only the <code>primaryType</code> and
* <code>subType</code> </strong>.
* The parameters of both operands are ignored. <p>
* <p>
* For example, this method will return <code>true</code> when
* comparing the ContentType for <strong>"text/plain"</strong>
* with <strong>"text/plain; charset=foobar"</strong>.
* <p>
* If the <code>subType</code> of either operand is the special
* character '*', then the subtype is ignored during the match.
* For example, this method will return <code>true</code> when
* comparing the ContentType for <strong>"text/plain"</strong>
* with <strong>"text/*" </strong>
*
* @param s the content-type string to match
* @return true if it matches
*/
public boolean match(String s) {
try {
return match(new ContentType(s));
} catch (ParseException pex) {
return false;
}
}
}

View file

@ -0,0 +1,463 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
/**
* This class tokenizes RFC822 and MIME headers into the basic
* symbols specified by RFC822 and MIME. <p>
* <p>
* This class handles folded headers (ie headers with embedded
* CRLF SPACE sequences). The folds are removed in the returned
* tokens.
*
* @author John Mani
* @author Bill Shannon
*/
public class HeaderTokenizer {
/**
* RFC822 specials
*/
public final static String RFC822 = "()<>@,;:\\\"\t .[]";
/**
* MIME specials
*/
public final static String MIME = "()<>@,;:\\\"\t []/?=";
// The EOF Token
private final static Token EOFToken = new Token(Token.EOF, null);
private String string; // the string to be tokenized
private boolean skipComments; // should comments be skipped ?
private String delimiters; // delimiter string
private int currentPos; // current parse position
private int maxPos; // string length
private int nextPos; // track start of next Token for next()
private int peekPos; // track start of next Token for peek()
/**
* Constructor that takes a rfc822 style header.
*
* @param skipComments If true, comments are skipped and
* not returned as tokens
* @param header The rfc822 header to be tokenized
* @param delimiters Set of delimiter characters
* to be used to delimit ATOMS. These
* are usually <code>RFC822</code> or
* <code>MIME</code>
*/
public HeaderTokenizer(String header, String delimiters,
boolean skipComments) {
string = (header == null) ? "" : header; // paranoia ?!
this.skipComments = skipComments;
this.delimiters = delimiters;
currentPos = nextPos = peekPos = 0;
maxPos = string.length();
}
/**
* Constructor. Comments are ignored and not returned as tokens
*
* @param header The header that is tokenized
* @param delimiters The delimiters to be used
*/
public HeaderTokenizer(String header, String delimiters) {
this(header, delimiters, true);
}
/**
* Constructor. The RFC822 defined delimiters - RFC822 - are
* used to delimit ATOMS. Also comments are skipped and not
* returned as tokens
*
* @param header the header string
*/
public HeaderTokenizer(String header) {
this(header, RFC822);
}
// Trim SPACE, HT, CR and NL from end of string
private static String trimWhiteSpace(String s) {
char c;
int i;
for (i = s.length() - 1; i >= 0; i--) {
if (((c = s.charAt(i)) != ' ') &&
(c != '\t') && (c != '\r') && (c != '\n'))
break;
}
if (i <= 0)
return "";
else
return s.substring(0, i + 1);
}
/* Process escape sequences and embedded LWSPs from a comment or
* quoted string.
*/
private static String filterToken(String s, int start, int end,
boolean keepEscapes) {
StringBuilder sb = new StringBuilder();
char c;
boolean gotEscape = false;
boolean gotCR = false;
for (int i = start; i < end; i++) {
c = s.charAt(i);
if (c == '\n' && gotCR) {
// This LF is part of an unescaped
// CRLF sequence (i.e, LWSP). Skip it.
gotCR = false;
continue;
}
gotCR = false;
if (!gotEscape) {
// Previous character was NOT '\'
if (c == '\\') // skip this character
gotEscape = true;
else if (c == '\r') // skip this character
gotCR = true;
else // append this character
sb.append(c);
} else {
// Previous character was '\'. So no need to
// bother with any special processing, just
// append this character. If keepEscapes is
// set, keep the backslash. IE6 fails to escape
// backslashes in quoted strings in HTTP headers,
// e.g., in the filename parameter.
if (keepEscapes)
sb.append('\\');
sb.append(c);
gotEscape = false;
}
}
return sb.toString();
}
/**
* Parses the next token from this String. <p>
* <p>
* Clients sit in a loop calling next() to parse successive
* tokens until an EOF Token is returned.
*
* @return the next Token
* @throws ParseException if the parse fails
*/
public Token next() throws ParseException {
return next('\0', false);
}
/**
* Parses the next token from this String.
* If endOfAtom is not NUL, the token extends until the
* endOfAtom character is seen, or to the end of the header.
* This method is useful when parsing headers that don't
* obey the MIME specification, e.g., by failing to quote
* parameter values that contain spaces.
*
* @param endOfAtom if not NUL, character marking end of token
* @return the next Token
* @throws ParseException if the parse fails
* @since JavaMail 1.5
*/
public Token next(char endOfAtom) throws ParseException {
return next(endOfAtom, false);
}
/**
* Parses the next token from this String.
* endOfAtom is handled as above. If keepEscapes is true,
* any backslash escapes are preserved in the returned string.
* This method is useful when parsing headers that don't
* obey the MIME specification, e.g., by failing to escape
* backslashes in the filename parameter.
*
* @param endOfAtom if not NUL, character marking end of token
* @param keepEscapes keep all backslashes in returned string?
* @return the next Token
* @throws ParseException if the parse fails
* @since JavaMail 1.5
*/
public Token next(char endOfAtom, boolean keepEscapes)
throws ParseException {
Token tk;
currentPos = nextPos; // setup currentPos
tk = getNext(endOfAtom, keepEscapes);
nextPos = peekPos = currentPos; // update currentPos and peekPos
return tk;
}
/**
* Peek at the next token, without actually removing the token
* from the parse stream. Invoking this method multiple times
* will return successive tokens, until <code>next()</code> is
* called.
*
* @return the next Token
* @throws ParseException if the parse fails
*/
public Token peek() throws ParseException {
Token tk;
currentPos = peekPos; // setup currentPos
tk = getNext('\0', false);
peekPos = currentPos; // update peekPos
return tk;
}
/**
* Return the rest of the Header.
*
* @return String rest of header. null is returned if we are
* already at end of header
*/
public String getRemainder() {
if (nextPos >= string.length())
return null;
return string.substring(nextPos);
}
/*
* Return the next token starting from 'currentPos'. After the
* parse, 'currentPos' is updated to point to the start of the
* next token.
*/
private Token getNext(char endOfAtom, boolean keepEscapes)
throws ParseException {
// If we're already at end of string, return EOF
if (currentPos >= maxPos)
return EOFToken;
// Skip white-space, position currentPos beyond the space
if (skipWhiteSpace() == Token.EOF)
return EOFToken;
char c;
int start;
boolean filter = false;
c = string.charAt(currentPos);
// Check or Skip comments and position currentPos
// beyond the comment
while (c == '(') {
// Parsing comment ..
int nesting;
for (start = ++currentPos, nesting = 1;
nesting > 0 && currentPos < maxPos;
currentPos++) {
c = string.charAt(currentPos);
if (c == '\\') { // Escape sequence
currentPos++; // skip the escaped character
filter = true;
} else if (c == '\r')
filter = true;
else if (c == '(')
nesting++;
else if (c == ')')
nesting--;
}
if (nesting != 0)
throw new ParseException("Unbalanced comments");
if (!skipComments) {
// Return the comment, if we are asked to.
// Note that the comment start & end markers are ignored.
String s;
if (filter) // need to go thru the token again.
s = filterToken(string, start, currentPos - 1, keepEscapes);
else
s = string.substring(start, currentPos - 1);
return new Token(Token.COMMENT, s);
}
// Skip any whitespace after the comment.
if (skipWhiteSpace() == Token.EOF)
return EOFToken;
c = string.charAt(currentPos);
}
// Check for quoted-string and position currentPos
// beyond the terminating quote
if (c == '"') {
currentPos++; // skip initial quote
return collectString('"', keepEscapes);
}
// Check for SPECIAL or CTL
if (c < 040 || c >= 0177 || delimiters.indexOf(c) >= 0) {
if (endOfAtom > 0 && c != endOfAtom) {
// not expecting a special character here,
// pretend it's a quoted string
return collectString(endOfAtom, keepEscapes);
}
currentPos++; // re-position currentPos
char[] ch = new char[1];
ch[0] = c;
return new Token(c, new String(ch));
}
// Check for ATOM
for (start = currentPos; currentPos < maxPos; currentPos++) {
c = string.charAt(currentPos);
// ATOM is delimited by either SPACE, CTL, "(", <">
// or the specified SPECIALS
if (c < 040 || c >= 0177 || c == '(' || c == ' ' ||
c == '"' || delimiters.indexOf(c) >= 0) {
if (endOfAtom > 0 && c != endOfAtom) {
// not the expected atom after all;
// back up and pretend it's a quoted string
currentPos = start;
return collectString(endOfAtom, keepEscapes);
}
break;
}
}
return new Token(Token.ATOM, string.substring(start, currentPos));
}
private Token collectString(char eos, boolean keepEscapes)
throws ParseException {
int start;
boolean filter = false;
for (start = currentPos; currentPos < maxPos; currentPos++) {
char c = string.charAt(currentPos);
if (c == '\\') { // Escape sequence
currentPos++;
filter = true;
} else if (c == '\r')
filter = true;
else if (c == eos) {
currentPos++;
String s;
if (filter)
s = filterToken(string, start, currentPos - 1, keepEscapes);
else
s = string.substring(start, currentPos - 1);
if (c != '"') { // not a real quoted string
s = trimWhiteSpace(s);
currentPos--; // back up before the eos char
}
return new Token(Token.QUOTEDSTRING, s);
}
}
// ran off the end of the string
// if we're looking for a matching quote, that's an error
if (eos == '"')
throw new ParseException("Unbalanced quoted string");
// otherwise, just return whatever's left
String s;
if (filter)
s = filterToken(string, start, currentPos, keepEscapes);
else
s = string.substring(start, currentPos);
s = trimWhiteSpace(s);
return new Token(Token.QUOTEDSTRING, s);
}
// Skip SPACE, HT, CR and NL
private int skipWhiteSpace() {
char c;
for (; currentPos < maxPos; currentPos++)
if (((c = string.charAt(currentPos)) != ' ') &&
(c != '\t') && (c != '\r') && (c != '\n'))
return currentPos;
return Token.EOF;
}
/**
* The Token class represents tokens returned by the
* HeaderTokenizer.
*/
public static class Token {
/**
* Token type indicating an ATOM.
*/
public static final int ATOM = -1;
/**
* Token type indicating a quoted string. The value
* field contains the string without the quotes.
*/
public static final int QUOTEDSTRING = -2;
/**
* Token type indicating a comment. The value field
* contains the comment string without the comment
* start and end symbols.
*/
public static final int COMMENT = -3;
/**
* Token type indicating end of input.
*/
public static final int EOF = -4;
private int type;
private String value;
/**
* Constructor.
*
* @param type Token type
* @param value Token value
*/
public Token(int type, String value) {
this.type = type;
this.value = value;
}
/**
* Return the type of the token. If the token represents a
* delimiter or a control character, the type is that character
* itself, converted to an integer. Otherwise, it's value is
* one of the following:
* <ul>
* <li><code>ATOM</code> A sequence of ASCII characters
* delimited by either SPACE, CTL, "(", &lt;"&gt; or the
* specified SPECIALS
* <li><code>QUOTEDSTRING</code> A sequence of ASCII characters
* within quotes
* <li><code>COMMENT</code> A sequence of ASCII characters
* within "(" and ")".
* <li><code>EOF</code> End of header
* </ul>
*
* @return the token type
*/
public int getType() {
return type;
}
/**
* Returns the value of the token just read. When the current
* token is a quoted string, this field contains the body of the
* string, without the quotes. When the current token is a comment,
* this field contains the body of the comment.
*
* @return token value
*/
public String getValue() {
return value;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,690 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
import jakarta.mail.Header;
import jakarta.mail.MessagingException;
import jakarta.mail.util.LineInputStream;
import jakarta.mail.util.StreamProvider;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/**
* InternetHeaders is a utility class that manages RFC822 style
* headers. Given an RFC822 format message stream, it reads lines
* until the blank line that indicates end of header. The input stream
* is positioned at the start of the body. The lines are stored
* within the object and can be extracted as either Strings or
* {@link Header} objects. <p>
* <p>
* This class is mostly intended for service providers. MimeMessage
* and MimeBody use this class for holding their headers.
*
* <hr> <strong>A note on RFC822 and MIME headers</strong><p>
* <p>
* RFC822 and MIME header fields <strong>must</strong> contain only
* US-ASCII characters. If a header contains non US-ASCII characters,
* it must be encoded as per the rules in RFC 2047. The MimeUtility
* class provided in this package can be used to to achieve this.
* Callers of the <code>setHeader</code>, <code>addHeader</code>, and
* <code>addHeaderLine</code> methods are responsible for enforcing
* the MIME requirements for the specified headers. In addition, these
* header fields must be folded (wrapped) before being sent if they
* exceed the line length limitation for the transport (1000 bytes for
* SMTP). Received headers may have been folded. The application is
* responsible for folding and unfolding headers as appropriate. <p>
* <p>
* The current implementation supports the System property
* <code>mail.mime.ignorewhitespacelines</code>, which if set to true
* will cause a line containing only whitespace to be considered
* a blank line terminating the header.
*
* @author John Mani
* @author Bill Shannon
* @see MimeUtility
*/
public class InternetHeaders {
private static final boolean ignoreWhitespaceLines =
MimeUtility.getBooleanSystemProperty("mail.mime.ignorewhitespacelines",
false);
/**
* The actual list of Headers, including placeholder entries.
* Placeholder entries are Headers with a null value and
* are never seen by clients of the InternetHeaders class.
* Placeholder entries are used to keep track of the preferred
* order of headers. Headers are never actually removed from
* the list, they're converted into placeholder entries.
* New headers are added after existing headers of the same name
* (or before in the case of <code>Received</code> and
* <code>Return-Path</code> headers). If no existing header
* or placeholder for the header is found, new headers are
* added after the special placeholder with the name ":".
*
* @since JavaMail 1.4
*/
protected List<InternetHeader> headers;
/**
* Create an empty InternetHeaders object. Placeholder entries
* are inserted to indicate the preferred order of headers.
*/
public InternetHeaders() {
headers = new ArrayList<>(40);
headers.add(new InternetHeader("Return-Path", null));
headers.add(new InternetHeader("Received", null));
headers.add(new InternetHeader("Resent-Date", null));
headers.add(new InternetHeader("Resent-From", null));
headers.add(new InternetHeader("Resent-Sender", null));
headers.add(new InternetHeader("Resent-To", null));
headers.add(new InternetHeader("Resent-Cc", null));
headers.add(new InternetHeader("Resent-Bcc", null));
headers.add(new InternetHeader("Resent-Message-Id", null));
headers.add(new InternetHeader("Date", null));
headers.add(new InternetHeader("From", null));
headers.add(new InternetHeader("Sender", null));
headers.add(new InternetHeader("Reply-To", null));
headers.add(new InternetHeader("To", null));
headers.add(new InternetHeader("Cc", null));
headers.add(new InternetHeader("Bcc", null));
headers.add(new InternetHeader("Message-Id", null));
headers.add(new InternetHeader("In-Reply-To", null));
headers.add(new InternetHeader("References", null));
headers.add(new InternetHeader("Subject", null));
headers.add(new InternetHeader("Comments", null));
headers.add(new InternetHeader("Keywords", null));
headers.add(new InternetHeader("Errors-To", null));
headers.add(new InternetHeader("MIME-Version", null));
headers.add(new InternetHeader("Content-Type", null));
headers.add(new InternetHeader("Content-Transfer-Encoding", null));
headers.add(new InternetHeader("Content-MD5", null));
headers.add(new InternetHeader(":", null));
headers.add(new InternetHeader("Content-Length", null));
headers.add(new InternetHeader("Status", null));
}
/**
* Read and parse the given RFC822 message stream till the
* blank line separating the header from the body. The input
* stream is left positioned at the start of the body. The
* header lines are stored internally. <p>
* <p>
* For efficiency, wrap a BufferedInputStream around the actual
* input stream and pass it as the parameter. <p>
* <p>
* No placeholder entries are inserted; the original order of
* the headers is preserved.
*
* @param is RFC822 input stream
* @throws MessagingException for any I/O error reading the stream
*/
public InternetHeaders(InputStream is) throws MessagingException {
this(is, false);
}
/**
* Read and parse the given RFC822 message stream till the
* blank line separating the header from the body. The input
* stream is left positioned at the start of the body. The
* header lines are stored internally. <p>
* <p>
* For efficiency, wrap a BufferedInputStream around the actual
* input stream and pass it as the parameter. <p>
* <p>
* No placeholder entries are inserted; the original order of
* the headers is preserved.
*
* @param is RFC822 input stream
* @param allowutf8 if UTF-8 encoded headers are allowed
* @throws MessagingException for any I/O error reading the stream
* @since JavaMail 1.6
*/
@SuppressWarnings("this-escape")
public InternetHeaders(InputStream is, boolean allowutf8)
throws MessagingException {
headers = new ArrayList<>(40);
load(is, allowutf8);
}
/**
* Is this line an empty (blank) line?
*/
private static boolean isEmpty(String line) {
return line.isEmpty() || (ignoreWhitespaceLines && line.trim().isEmpty());
}
/**
* Read and parse the given RFC822 message stream till the
* blank line separating the header from the body. Store the
* header lines inside this InternetHeaders object. The order
* of header lines is preserved. <p>
* <p>
* Note that the header lines are added into this InternetHeaders
* object, so any existing headers in this object will not be
* affected. Headers are added to the end of the existing list
* of headers, in order.
*
* @param is RFC822 input stream
* @throws MessagingException for any I/O error reading the stream
*/
public void load(InputStream is) throws MessagingException {
load(is, false);
}
/**
* Read and parse the given RFC822 message stream till the
* blank line separating the header from the body. Store the
* header lines inside this InternetHeaders object. The order
* of header lines is preserved. <p>
* <p>
* Note that the header lines are added into this InternetHeaders
* object, so any existing headers in this object will not be
* affected. Headers are added to the end of the existing list
* of headers, in order.
*
* @param is RFC822 input stream
* @param allowutf8 if UTF-8 encoded headers are allowed
* @throws MessagingException for any I/O error reading the stream
* @since JavaMail 1.6
*/
public void load(InputStream is, boolean allowutf8)
throws MessagingException {
// Read header lines until a blank line. It is valid
// to have BodyParts with no header lines.
String line;
LineInputStream lis = StreamProvider.provider().inputLineStream(is, allowutf8);
String prevline = null; // the previous header line, as a string
// a buffer to accumulate the header in, when we know it's needed
StringBuilder lineBuffer = new StringBuilder();
try {
// if the first line being read is a continuation line,
// we ignore it if it's otherwise empty or we treat it as
// a non-continuation line if it has non-whitespace content
boolean first = true;
do {
line = lis.readLine();
if (line != null &&
(line.startsWith(" ") || line.startsWith("\t"))) {
// continuation of header
if (prevline != null) {
lineBuffer.append(prevline);
prevline = null;
}
if (first) {
String lt = line.trim();
if (lt.length() > 0)
lineBuffer.append(lt);
} else {
if (lineBuffer.length() > 0)
lineBuffer.append("\r\n");
lineBuffer.append(line);
}
} else {
// new header
if (prevline != null)
addHeaderLine(prevline);
else if (lineBuffer.length() > 0) {
// store previous header first
addHeaderLine(lineBuffer.toString());
lineBuffer.setLength(0);
}
prevline = line;
}
first = false;
} while (line != null && !isEmpty(line));
} catch (IOException ioex) {
throw new MessagingException("Error in input stream", ioex);
}
}
/**
* Return all the values for the specified header. The
* values are String objects. Returns <code>null</code>
* if no headers with the specified name exist.
*
* @param name header name
* @return array of header values, or null if none
*/
public String[] getHeader(String name) {
Iterator<InternetHeader> e = headers.iterator();
// XXX - should we just step through in index order?
List<String> v = new ArrayList<>(); // accumulate return values
while (e.hasNext()) {
InternetHeader h = e.next();
if (name.equalsIgnoreCase(h.getName()) && h.line != null) {
v.add(h.getValue());
}
}
if (v.size() == 0)
return (null);
// convert List to an array for return
String[] r = new String[v.size()];
r = v.toArray(r);
return (r);
}
/**
* Get all the headers for this header name, returned as a single
* String, with headers separated by the delimiter. If the
* delimiter is <code>null</code>, only the first header is
* returned. Returns <code>null</code>
* if no headers with the specified name exist.
*
* @param delimiter delimiter
* @param name header name
* @return the value fields for all headers with
* this name, or null if none
*/
public String getHeader(String name, String delimiter) {
String[] s = getHeader(name);
if (s == null)
return null;
if ((s.length == 1) || delimiter == null)
return s[0];
StringBuilder r = new StringBuilder(s[0]);
for (int i = 1; i < s.length; i++) {
r.append(delimiter);
r.append(s[i]);
}
return r.toString();
}
/**
* Change the first header line that matches name
* to have value, adding a new header if no existing header
* matches. Remove all matching headers but the first. <p>
* <p>
* Note that RFC822 headers can only contain US-ASCII characters
*
* @param name header name
* @param value header value
*/
public void setHeader(String name, String value) {
boolean found = false;
for (int i = 0; i < headers.size(); i++) {
InternetHeader h = headers.get(i);
if (name.equalsIgnoreCase(h.getName())) {
if (!found) {
int j;
if (h.line != null && (j = h.line.indexOf(':')) >= 0) {
h.line = h.line.substring(0, j + 1) + " " + value;
// preserves capitalization, spacing
} else {
h.line = name + ": " + value;
}
found = true;
} else {
headers.remove(i);
i--; // have to look at i again
}
}
}
if (!found) {
addHeader(name, value);
}
}
/**
* Add a header with the specified name and value to the header list. <p>
* <p>
* The current implementation knows about the preferred order of most
* well-known headers and will insert headers in that order. In
* addition, it knows that <code>Received</code> headers should be
* inserted in reverse order (newest before oldest), and that they
* should appear at the beginning of the headers, preceeded only by
* a possible <code>Return-Path</code> header. <p>
* <p>
* Note that RFC822 headers can only contain US-ASCII characters.
*
* @param name header name
* @param value header value
*/
public void addHeader(String name, String value) {
int pos = headers.size();
boolean addReverse =
name.equalsIgnoreCase("Received") ||
name.equalsIgnoreCase("Return-Path");
if (addReverse)
pos = 0;
for (int i = headers.size() - 1; i >= 0; i--) {
InternetHeader h = headers.get(i);
if (name.equalsIgnoreCase(h.getName())) {
if (addReverse) {
pos = i;
} else {
headers.add(i + 1, new InternetHeader(name, value));
return;
}
}
// marker for default place to add new headers
if (!addReverse && h.getName().equals(":"))
pos = i;
}
headers.add(pos, new InternetHeader(name, value));
}
/**
* Remove all header entries that match the given name
*
* @param name header name
*/
public void removeHeader(String name) {
for (int i = 0; i < headers.size(); i++) {
InternetHeader h = headers.get(i);
if (name.equalsIgnoreCase(h.getName())) {
h.line = null;
//headers.remove(i);
//i--; // have to look at i again
}
}
}
/**
* Return all the headers as an Enumeration of
* {@link Header} objects.
*
* @return Enumeration of Header objects
*/
public Enumeration<Header> getAllHeaders() {
return (new MatchHeaderEnum(headers, null, false));
}
/**
* Return all matching {@link Header} objects.
*
* @param names the headers to return
* @return Enumeration of matching Header objects
*/
public Enumeration<Header> getMatchingHeaders(String[] names) {
return (new MatchHeaderEnum(headers, names, true));
}
/**
* Return all non-matching {@link Header} objects.
*
* @param names the headers to not return
* @return Enumeration of non-matching Header objects
*/
public Enumeration<Header> getNonMatchingHeaders(String[] names) {
return (new MatchHeaderEnum(headers, names, false));
}
/**
* 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. <p>
* <p>
* Note that RFC822 headers can only contain US-ASCII characters
*
* @param line raw RFC822 header line
*/
public void addHeaderLine(String line) {
try {
char c = line.charAt(0);
if (c == ' ' || c == '\t') {
InternetHeader h = headers.get(headers.size() - 1);
h.line += "\r\n" + line;
} else
headers.add(new InternetHeader(line));
} catch (StringIndexOutOfBoundsException e) {
// line is empty, ignore it
return;
} catch (NoSuchElementException e) {
// XXX - list is empty?
}
}
/**
* Return all the header lines as an Enumeration of Strings.
*
* @return Enumeration of Strings of all header lines
*/
public Enumeration<String> getAllHeaderLines() {
return (getNonMatchingHeaderLines(null));
}
/**
* Return all matching header lines as an Enumeration of Strings.
*
* @param names the headers to return
* @return Enumeration of Strings of all matching header lines
*/
public Enumeration<String> getMatchingHeaderLines(String[] names) {
return (new MatchStringEnum(headers, names, true));
}
/**
* Return all non-matching header lines
*
* @param names the headers to not return
* @return Enumeration of Strings of all non-matching header lines
*/
public Enumeration<String> getNonMatchingHeaderLines(String[] names) {
return (new MatchStringEnum(headers, names, false));
}
/**
* An individual internet header. This class is only used by
* subclasses of InternetHeaders. <p>
* <p>
* An InternetHeader object with a null value is used as a placeholder
* for headers of that name, to preserve the order of headers.
* A placeholder InternetHeader object with a name of ":" marks
* the location in the list of headers where new headers are
* added by default.
*
* @since JavaMail 1.4
*/
protected static final class InternetHeader extends Header {
/*
* Note that the value field from the superclass
* isn't used in this class. We extract the value
* from the line field as needed. We store the line
* rather than just the value to ensure that we can
* get back the exact original line, with the original
* whitespace, etc.
*/
String line; // the entire RFC822 header "line",
// or null if placeholder
/**
* Constructor that takes a line and splits out
* the header name.
*
* @param l the header line
*/
public InternetHeader(String l) {
super("", ""); // XXX - we'll change it later
int i = l.indexOf(':');
if (i < 0) {
// should never happen
name = l.trim();
} else {
name = l.substring(0, i).trim();
}
line = l;
}
/**
* Constructor that takes a header name and value.
*
* @param n the name of the header
* @param v the value of the header
*/
public InternetHeader(String n, String v) {
super(n, "");
if (v != null)
line = n + ": " + v;
else
line = null;
}
/**
* Return the "value" part of the header line.
*/
@Override
public String getValue() {
int i = line.indexOf(':');
if (i < 0)
return line;
// skip whitespace after ':'
int j;
for (j = i + 1; j < line.length(); j++) {
char c = line.charAt(j);
if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n'))
break;
}
return line.substring(j);
}
}
/*
* The enumeration object used to enumerate an
* InternetHeaders object. Can return
* either a String or a Header object.
*/
static class MatchEnum {
private Iterator<InternetHeader> e; // enum object of headers List
// XXX - is this overkill? should we step through in index
// order instead?
private String[] names; // names to match, or not
private boolean match; // return matching headers?
private boolean want_line; // return header lines?
private InternetHeader next_header; // the next header to be returned
/*
* Constructor. Initialize the enumeration for the entire
* List of headers, the set of headers, whether to return
* matching or non-matching headers, and whether to return
* header lines or Header objects.
*/
MatchEnum(List<InternetHeader> v, String[] n, boolean m, boolean l) {
e = v.iterator();
names = n;
match = m;
want_line = l;
next_header = null;
}
/*
* Any more elements in this enumeration?
*/
public boolean hasMoreElements() {
// if necessary, prefetch the next matching header,
// and remember it.
if (next_header == null)
next_header = nextMatch();
return next_header != null;
}
/*
* Return the next element.
*/
public Object nextElement() {
if (next_header == null)
next_header = nextMatch();
if (next_header == null)
throw new NoSuchElementException("No more headers");
InternetHeader h = next_header;
next_header = null;
if (want_line)
return h.line;
else
return new Header(h.getName(), h.getValue());
}
/*
* Return the next Header object according to the match
* criteria, or null if none left.
*/
private InternetHeader nextMatch() {
next:
while (e.hasNext()) {
InternetHeader h = e.next();
// skip "place holder" headers
if (h.line == null)
continue;
// if no names to match against, return appropriately
if (names == null)
return match ? null : h;
// check whether this header matches any of the names
for (int i = 0; i < names.length; i++) {
if (names[i].equalsIgnoreCase(h.getName())) {
if (match)
return h;
else
// found a match, but we're
// looking for non-matches.
// try next header.
continue next;
}
}
// found no matches. if that's what we wanted, return it.
if (!match)
return h;
}
return null;
}
}
static class MatchStringEnum extends MatchEnum
implements Enumeration<String> {
MatchStringEnum(List<InternetHeader> v, String[] n, boolean m) {
super(v, n, m, true);
}
@Override
public String nextElement() {
return (String) super.nextElement();
}
}
static class MatchHeaderEnum extends MatchEnum
implements Enumeration<Header> {
MatchHeaderEnum(List<InternetHeader> v, String[] n, boolean m) {
super(v, n, m, false);
}
@Override
public Header nextElement() {
return (Header) super.nextElement();
}
}
}

View file

@ -0,0 +1,990 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
import java.text.DateFormatSymbols;
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.Locale;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Formats and parses date specification based on
* <a href="http://www.ietf.org/rfc/rfc2822.txt" target="_top">RFC 2822</a>. <p>
* <p>
* This class does not support methods that influence the format. It always
* formats the date based on the specification below.<p>
* <p>
* 3.3. Date and Time Specification
* <p>
* Date and time occur in several header fields. This section specifies
* the syntax for a full date and time specification. Though folding
* white space is permitted throughout the date-time specification, it is
* RECOMMENDED that a single space be used in each place that FWS appears
* (whether it is required or optional); some older implementations may
* not interpret other occurrences of folding white space correctly.
* <pre>
* date-time = [ day-of-week "," ] date FWS time [CFWS]
*
* day-of-week = ([FWS] day-name) / obs-day-of-week
*
* day-name = "Mon" / "Tue" / "Wed" / "Thu" /
* "Fri" / "Sat" / "Sun"
*
* date = day month year
*
* year = 4*DIGIT / obs-year
*
* month = (FWS month-name FWS) / obs-month
*
* month-name = "Jan" / "Feb" / "Mar" / "Apr" /
* "May" / "Jun" / "Jul" / "Aug" /
* "Sep" / "Oct" / "Nov" / "Dec"
*
* day = ([FWS] 1*2DIGIT) / obs-day
*
* time = time-of-day FWS zone
*
* time-of-day = hour ":" minute [ ":" second ]
*
* hour = 2DIGIT / obs-hour
*
* minute = 2DIGIT / obs-minute
*
* second = 2DIGIT / obs-second
*
* zone = (( "+" / "-" ) 4DIGIT) / obs-zone
* </pre>
* The day is the numeric day of the month. The year is any numeric year
* 1900 or later.
* <p>
* The time-of-day specifies the number of hours, minutes, and optionally
* seconds since midnight of the date indicated.
* <p>
* The date and time-of-day SHOULD express local time.
* <p>
* The zone specifies the offset from Coordinated Universal Time (UTC,
* formerly referred to as "Greenwich Mean Time") that the date and
* time-of-day represent. The "+" or "-" indicates whether the
* time-of-day is ahead of (i.e., east of) or behind (i.e., west of)
* Universal Time. The first two digits indicate the number of hours
* difference from Universal Time, and the last two digits indicate the
* number of minutes difference from Universal Time. (Hence, +hhmm means
* +(hh * 60 + mm) minutes, and -hhmm means -(hh * 60 + mm) minutes). The
* form "+0000" SHOULD be used to indicate a time zone at Universal Time.
* Though "-0000" also indicates Universal Time, it is used to indicate
* that the time was generated on a system that may be in a local time
* zone other than Universal Time and therefore indicates that the
* date-time contains no information about the local time zone.
* <p>
* A date-time specification MUST be semantically valid. That is, the
* day-of-the-week (if included) MUST be the day implied by the date, the
* numeric day-of-month MUST be between 1 and the number of days allowed
* for the specified month (in the specified year), the time-of-day MUST
* be in the range 00:00:00 through 23:59:60 (the number of seconds
* allowing for a leap second; see [STD12]), and the zone MUST be within
* the range -9959 through +9959.
*
* <h2><a id="synchronization">Synchronization</a></h2>
*
* <p>
* Date formats are not synchronized.
* It is recommended to create separate format instances for each thread.
* If multiple threads access a format concurrently, it must be synchronized
* externally.
*
* @author Anthony Vanelverdinghe
* @author Max Spivak
* @since JavaMail 1.2
*/
@SuppressWarnings("serial")
public class MailDateFormat extends SimpleDateFormat {
private static final String PATTERN = "EEE, d MMM yyyy HH:mm:ss Z (z)";
private static final Logger logger = Logger.getLogger(MailDateFormat.class.getName());
private static final int UNKNOWN_DAY_NAME = -1;
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
private static final int LEAP_SECOND = 60;
/**
* Create a new date format for the RFC2822 specification with lenient
* parsing.
*/
public MailDateFormat() {
super(PATTERN, Locale.US);
}
/**
* Formats the given date in the format specified by
* RFC 2822 in the current TimeZone.
*
* @param date the Date object
* @param dateStrBuf the formatted string
* @param fieldPosition the current field position
* @return StringBuffer the formatted String
* @since JavaMail 1.2
*/
@Override
public StringBuffer format(Date date, StringBuffer dateStrBuf,
FieldPosition fieldPosition) {
return super.format(date, dateStrBuf, fieldPosition);
}
/**
* Parses the given date in the format specified by
* RFC 2822.
* <ul>
* <li>With strict parsing, obs-* tokens are unsupported. Lenient parsing
* supports obs-year and obs-zone, with the exception of the 1-character
* military time zones.
* <li>The optional CFWS token at the end is not parsed.
* <li>RFC 2822 specifies that a zone of "-0000" indicates that the
* date-time contains no information about the local time zone. This class
* uses the UTC time zone in this case.
* </ul>
*
* @param text the formatted date to be parsed
* @param pos the current parse position
* @return Date the parsed date. In case of error, returns null.
* @since JavaMail 1.2
*/
@Override
public Date parse(String text, ParsePosition pos) {
if (text == null || pos == null) {
throw new NullPointerException();
} else if (0 > pos.getIndex() || pos.getIndex() >= text.length()) {
return null;
}
return isLenient()
? new Rfc2822LenientParser(text, pos).parse()
: new Rfc2822StrictParser(text, pos).parse();
}
/**
* This method always throws an UnsupportedOperationException and should not
* be used because RFC 2822 mandates a specific calendar.
*
* @throws UnsupportedOperationException if this method is invoked
*/
@Override
public void setCalendar(Calendar newCalendar) {
throw new UnsupportedOperationException("Method "
+ "setCalendar() shouldn't be called");
}
/**
* This method always throws an UnsupportedOperationException and should not
* be used because RFC 2822 mandates a specific number format.
*
* @throws UnsupportedOperationException if this method is invoked
*/
@Override
public void setNumberFormat(NumberFormat newNumberFormat) {
throw new UnsupportedOperationException("Method "
+ "setNumberFormat() shouldn't be called");
}
/**
* This method always throws an UnsupportedOperationException and should not
* be used because RFC 2822 mandates a specific pattern.
*
* @throws UnsupportedOperationException if this method is invoked
* @since JavaMail 1.6
*/
@Override
public void applyLocalizedPattern(String pattern) {
throw new UnsupportedOperationException("Method "
+ "applyLocalizedPattern() shouldn't be called");
}
/**
* This method always throws an UnsupportedOperationException and should not
* be used because RFC 2822 mandates a specific pattern.
*
* @throws UnsupportedOperationException if this method is invoked
* @since JavaMail 1.6
*/
@Override
public void applyPattern(String pattern) {
throw new UnsupportedOperationException("Method "
+ "applyPattern() shouldn't be called");
}
/**
* This method allows serialization to change the pattern.
*/
private void superApplyPattern(String pattern) {
super.applyPattern(pattern);
}
/**
* This method always throws an UnsupportedOperationException and should not
* be used because RFC 2822 mandates another strategy for interpreting
* 2-digits years.
*
* @return the start of the 100-year period into which two digit years are
* parsed
* @throws UnsupportedOperationException if this method is invoked
* @since JavaMail 1.6
*/
@Override
public Date get2DigitYearStart() {
throw new UnsupportedOperationException("Method "
+ "get2DigitYearStart() shouldn't be called");
}
/**
* This method always throws an UnsupportedOperationException and should not
* be used because RFC 2822 mandates another strategy for interpreting
* 2-digits years.
*
* @throws UnsupportedOperationException if this method is invoked
* @since JavaMail 1.6
*/
@Override
public void set2DigitYearStart(Date startDate) {
throw new UnsupportedOperationException("Method "
+ "set2DigitYearStart() shouldn't be called");
}
/**
* This method always throws an UnsupportedOperationException and should not
* be used because RFC 2822 mandates specific date format symbols.
*
* @throws UnsupportedOperationException if this method is invoked
* @since JavaMail 1.6
*/
@Override
public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
throw new UnsupportedOperationException("Method "
+ "setDateFormatSymbols() shouldn't be called");
}
/**
* Returns the date, as specified by the parameters.
*
* @return the date, as specified by the parameters
* @throws IllegalArgumentException if this instance's Calendar is
* non-lenient and any of the parameters have invalid values, or if dayName
* is not consistent with day-month-year
*/
private Date toDate(int dayName, int day, int month, int year,
int hour, int minute, int second, int zone) {
if (second == LEAP_SECOND) {
second = 59;
}
TimeZone tz = calendar.getTimeZone();
try {
calendar.setTimeZone(UTC);
calendar.clear();
calendar.set(year, month, day, hour, minute, second);
if (dayName == UNKNOWN_DAY_NAME
|| dayName == calendar.get(Calendar.DAY_OF_WEEK)) {
calendar.add(Calendar.MINUTE, zone);
return calendar.getTime();
} else {
throw new IllegalArgumentException("Inconsistent day-name");
}
} finally {
calendar.setTimeZone(tz);
}
}
/**
* This class provides the building blocks for date parsing.
* <p>
* It has the following invariants:
* <ul>
* <li>no exceptions are thrown, except for java.text.ParseException from
* parse* methods
* <li>when parse* throws ParseException OR get* returns INVALID_CHAR OR
* skip* returns false OR peek* is invoked, then pos.getIndex() on method
* exit is the same as it was on method entry
* </ul>
*/
private static abstract class AbstractDateParser {
static final int INVALID_CHAR = -1;
static final int MAX_YEAR_DIGITS = 8; // guarantees that:
// year < new GregorianCalendar().getMaximum(Calendar.YEAR)
final String text;
final ParsePosition pos;
AbstractDateParser(String text, ParsePosition pos) {
this.text = text;
this.pos = pos;
}
final Date parse() {
int startPosition = pos.getIndex();
try {
return tryParse();
} catch (Exception e) { // == ParseException | RuntimeException e
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Bad date: '" + text + "'", e);
}
pos.setErrorIndex(pos.getIndex());
pos.setIndex(startPosition);
return null;
}
}
abstract Date tryParse() throws ParseException;
/**
* @return the java.util.Calendar constant for the parsed day name
*/
final int parseDayName() throws ParseException {
switch (getChar()) {
case 'S':
if (skipPair('u', 'n')) {
return Calendar.SUNDAY;
} else if (skipPair('a', 't')) {
return Calendar.SATURDAY;
}
break;
case 'T':
if (skipPair('u', 'e')) {
return Calendar.TUESDAY;
} else if (skipPair('h', 'u')) {
return Calendar.THURSDAY;
}
break;
case 'M':
if (skipPair('o', 'n')) {
return Calendar.MONDAY;
}
break;
case 'W':
if (skipPair('e', 'd')) {
return Calendar.WEDNESDAY;
}
break;
case 'F':
if (skipPair('r', 'i')) {
return Calendar.FRIDAY;
}
break;
case INVALID_CHAR:
throw new ParseException("Invalid day-name",
pos.getIndex());
}
pos.setIndex(pos.getIndex() - 1);
throw new ParseException("Invalid day-name", pos.getIndex());
}
/**
* @return the java.util.Calendar constant for the parsed month name
*/
@SuppressWarnings("fallthrough")
final int parseMonthName(boolean caseSensitive) throws ParseException {
switch (getChar()) {
case 'j':
if (caseSensitive) {
break;
}
case 'J':
if (skipChar('u') || (!caseSensitive && skipChar('U'))) {
if (skipChar('l') || (!caseSensitive
&& skipChar('L'))) {
return Calendar.JULY;
} else if (skipChar('n') || (!caseSensitive
&& skipChar('N'))) {
return Calendar.JUNE;
} else {
pos.setIndex(pos.getIndex() - 1);
}
} else if (skipPair('a', 'n') || (!caseSensitive
&& skipAlternativePair('a', 'A', 'n', 'N'))) {
return Calendar.JANUARY;
}
break;
case 'm':
if (caseSensitive) {
break;
}
case 'M':
if (skipChar('a') || (!caseSensitive && skipChar('A'))) {
if (skipChar('r') || (!caseSensitive
&& skipChar('R'))) {
return Calendar.MARCH;
} else if (skipChar('y') || (!caseSensitive
&& skipChar('Y'))) {
return Calendar.MAY;
} else {
pos.setIndex(pos.getIndex() - 1);
}
}
break;
case 'a':
if (caseSensitive) {
break;
}
case 'A':
if (skipPair('u', 'g') || (!caseSensitive
&& skipAlternativePair('u', 'U', 'g', 'G'))) {
return Calendar.AUGUST;
} else if (skipPair('p', 'r') || (!caseSensitive
&& skipAlternativePair('p', 'P', 'r', 'R'))) {
return Calendar.APRIL;
}
break;
case 'd':
if (caseSensitive) {
break;
}
case 'D':
if (skipPair('e', 'c') || (!caseSensitive
&& skipAlternativePair('e', 'E', 'c', 'C'))) {
return Calendar.DECEMBER;
}
break;
case 'o':
if (caseSensitive) {
break;
}
case 'O':
if (skipPair('c', 't') || (!caseSensitive
&& skipAlternativePair('c', 'C', 't', 'T'))) {
return Calendar.OCTOBER;
}
break;
case 's':
if (caseSensitive) {
break;
}
case 'S':
if (skipPair('e', 'p') || (!caseSensitive
&& skipAlternativePair('e', 'E', 'p', 'P'))) {
return Calendar.SEPTEMBER;
}
break;
case 'n':
if (caseSensitive) {
break;
}
case 'N':
if (skipPair('o', 'v') || (!caseSensitive
&& skipAlternativePair('o', 'O', 'v', 'V'))) {
return Calendar.NOVEMBER;
}
break;
case 'f':
if (caseSensitive) {
break;
}
case 'F':
if (skipPair('e', 'b') || (!caseSensitive
&& skipAlternativePair('e', 'E', 'b', 'B'))) {
return Calendar.FEBRUARY;
}
break;
case INVALID_CHAR:
throw new ParseException("Invalid month", pos.getIndex());
}
pos.setIndex(pos.getIndex() - 1);
throw new ParseException("Invalid month", pos.getIndex());
}
/**
* @return the number of minutes to be added to the time in the local
* time zone, in order to obtain the equivalent time in the UTC time
* zone. Returns 0 if the date-time contains no information about the
* local time zone.
*/
final int parseZoneOffset() throws ParseException {
int sign = getChar();
if (sign == '+' || sign == '-') {
int offset = parseAsciiDigits(4, 4, true);
if (!isValidZoneOffset(offset)) {
pos.setIndex(pos.getIndex() - 5);
throw new ParseException("Invalid zone", pos.getIndex());
}
return ((sign == '+') ? -1 : 1)
* (offset / 100 * 60 + offset % 100);
} else if (sign != INVALID_CHAR) {
pos.setIndex(pos.getIndex() - 1);
}
throw new ParseException("Invalid zone", pos.getIndex());
}
boolean isValidZoneOffset(int offset) {
return (offset % 100) < 60;
}
final int parseAsciiDigits(int count) throws ParseException {
return parseAsciiDigits(count, count);
}
final int parseAsciiDigits(int min, int max) throws ParseException {
return parseAsciiDigits(min, max, false);
}
final int parseAsciiDigits(int min, int max, boolean isEOF)
throws ParseException {
int result = 0;
int nbDigitsParsed = 0;
while (nbDigitsParsed < max && peekAsciiDigit()) {
result = result * 10 + getAsciiDigit();
nbDigitsParsed++;
}
if ((nbDigitsParsed < min)
|| (nbDigitsParsed == max && !isEOF && peekAsciiDigit())) {
pos.setIndex(pos.getIndex() - nbDigitsParsed);
} else {
return result;
}
String range = (min == max)
? Integer.toString(min)
: "between " + min + " and " + max;
throw new ParseException("Invalid input: expected "
+ range + " ASCII digits", pos.getIndex());
}
final void parseFoldingWhiteSpace() throws ParseException {
if (!skipFoldingWhiteSpace()) {
throw new ParseException("Invalid input: expected FWS",
pos.getIndex());
}
}
final void parseChar(char ch) throws ParseException {
if (!skipChar(ch)) {
throw new ParseException("Invalid input: expected '" + ch + "'",
pos.getIndex());
}
}
final int getAsciiDigit() {
int ch = getChar();
if ('0' <= ch && ch <= '9') {
return Character.digit((char) ch, 10);
} else {
if (ch != INVALID_CHAR) {
pos.setIndex(pos.getIndex() - 1);
}
return INVALID_CHAR;
}
}
final int getChar() {
if (pos.getIndex() < text.length()) {
char ch = text.charAt(pos.getIndex());
pos.setIndex(pos.getIndex() + 1);
return ch;
} else {
return INVALID_CHAR;
}
}
boolean skipFoldingWhiteSpace() {
// fast paths: a single ASCII space or no FWS
if (skipChar(' ')) {
if (!peekFoldingWhiteSpace()) {
return true;
} else {
pos.setIndex(pos.getIndex() - 1);
}
} else if (!peekFoldingWhiteSpace()) {
return false;
}
// normal path
int startIndex = pos.getIndex();
if (skipWhiteSpace()) {
while (skipNewline()) {
if (!skipWhiteSpace()) {
pos.setIndex(startIndex);
return false;
}
}
return true;
} else if (skipNewline() && skipWhiteSpace()) {
return true;
} else {
pos.setIndex(startIndex);
return false;
}
}
final boolean skipWhiteSpace() {
int startIndex = pos.getIndex();
while (skipAlternative(' ', '\t')) { /* empty */ }
return pos.getIndex() > startIndex;
}
final boolean skipNewline() {
return skipPair('\r', '\n');
}
final boolean skipAlternativeTriple(
char firstStandard, char firstAlternative,
char secondStandard, char secondAlternative,
char thirdStandard, char thirdAlternative
) {
if (skipAlternativePair(firstStandard, firstAlternative,
secondStandard, secondAlternative)) {
if (skipAlternative(thirdStandard, thirdAlternative)) {
return true;
} else {
pos.setIndex(pos.getIndex() - 2);
}
}
return false;
}
final boolean skipAlternativePair(
char firstStandard, char firstAlternative,
char secondStandard, char secondAlternative
) {
if (skipAlternative(firstStandard, firstAlternative)) {
if (skipAlternative(secondStandard, secondAlternative)) {
return true;
} else {
pos.setIndex(pos.getIndex() - 1);
}
}
return false;
}
final boolean skipAlternative(char standard, char alternative) {
return skipChar(standard) || skipChar(alternative);
}
final boolean skipPair(char first, char second) {
if (skipChar(first)) {
if (skipChar(second)) {
return true;
} else {
pos.setIndex(pos.getIndex() - 1);
}
}
return false;
}
final boolean skipChar(char ch) {
if (pos.getIndex() < text.length()
&& text.charAt(pos.getIndex()) == ch) {
pos.setIndex(pos.getIndex() + 1);
return true;
} else {
return false;
}
}
final boolean peekAsciiDigit() {
return (pos.getIndex() < text.length()
&& '0' <= text.charAt(pos.getIndex())
&& text.charAt(pos.getIndex()) <= '9');
}
boolean peekFoldingWhiteSpace() {
return (pos.getIndex() < text.length()
&& (text.charAt(pos.getIndex()) == ' '
|| text.charAt(pos.getIndex()) == '\t'
|| text.charAt(pos.getIndex()) == '\r'));
}
final boolean peekChar(char ch) {
return (pos.getIndex() < text.length()
&& text.charAt(pos.getIndex()) == ch);
}
}
private class Rfc2822StrictParser extends AbstractDateParser {
Rfc2822StrictParser(String text, ParsePosition pos) {
super(text, pos);
}
@Override
Date tryParse() throws ParseException {
int dayName = parseOptionalBegin();
int day = parseDay();
int month = parseMonth();
int year = parseYear();
parseFoldingWhiteSpace();
int hour = parseHour();
parseChar(':');
int minute = parseMinute();
int second = (skipChar(':')) ? parseSecond() : 0;
parseFwsBetweenTimeOfDayAndZone();
int zone = parseZone();
try {
return MailDateFormat.this.toDate(dayName, day, month, year,
hour, minute, second, zone);
} catch (IllegalArgumentException e) {
throw new ParseException("Invalid input: some of the calendar "
+ "fields have invalid values, or day-name is "
+ "inconsistent with date", pos.getIndex());
}
}
/**
* @return the java.util.Calendar constant for the parsed day name, or
* UNKNOWN_DAY_NAME iff the begin is missing
*/
int parseOptionalBegin() throws ParseException {
int dayName;
if (!peekAsciiDigit()) {
skipFoldingWhiteSpace();
dayName = parseDayName();
parseChar(',');
} else {
dayName = UNKNOWN_DAY_NAME;
}
return dayName;
}
int parseDay() throws ParseException {
skipFoldingWhiteSpace();
return parseAsciiDigits(1, 2);
}
/**
* @return the java.util.Calendar constant for the parsed month name
*/
int parseMonth() throws ParseException {
parseFwsInMonth();
int month = parseMonthName(isMonthNameCaseSensitive());
parseFwsInMonth();
return month;
}
void parseFwsInMonth() throws ParseException {
parseFoldingWhiteSpace();
}
boolean isMonthNameCaseSensitive() {
return true;
}
int parseYear() throws ParseException {
int year = parseAsciiDigits(4, MAX_YEAR_DIGITS);
if (year >= 1900) {
return year;
} else {
pos.setIndex(pos.getIndex() - 4);
while (text.charAt(pos.getIndex() - 1) == '0') {
pos.setIndex(pos.getIndex() - 1);
}
throw new ParseException("Invalid year", pos.getIndex());
}
}
int parseHour() throws ParseException {
return parseAsciiDigits(2);
}
int parseMinute() throws ParseException {
return parseAsciiDigits(2);
}
int parseSecond() throws ParseException {
return parseAsciiDigits(2);
}
void parseFwsBetweenTimeOfDayAndZone() throws ParseException {
parseFoldingWhiteSpace();
}
int parseZone() throws ParseException {
return parseZoneOffset();
}
}
private class Rfc2822LenientParser extends Rfc2822StrictParser {
private Boolean hasDefaultFws;
Rfc2822LenientParser(String text, ParsePosition pos) {
super(text, pos);
}
@Override
int parseOptionalBegin() {
while (pos.getIndex() < text.length() && !peekAsciiDigit()) {
pos.setIndex(pos.getIndex() + 1);
}
return UNKNOWN_DAY_NAME;
}
@Override
int parseDay() throws ParseException {
skipFoldingWhiteSpace();
return parseAsciiDigits(1, 3);
}
@Override
void parseFwsInMonth() throws ParseException {
// '-' is allowed to accomodate for the date format as specified in
// <a href="http://www.ietf.org/rfc/rfc3501.txt">RFC 3501</a>
if (hasDefaultFws == null) {
hasDefaultFws = !skipChar('-');
skipFoldingWhiteSpace();
} else if (hasDefaultFws) {
skipFoldingWhiteSpace();
} else {
parseChar('-');
}
}
@Override
boolean isMonthNameCaseSensitive() {
return false;
}
@Override
int parseYear() throws ParseException {
int year = parseAsciiDigits(1, MAX_YEAR_DIGITS);
if (year >= 1000) {
return year;
} else if (year >= 50) {
return year + 1900;
} else {
return year + 2000;
}
}
@Override
int parseHour() throws ParseException {
return parseAsciiDigits(1, 2);
}
@Override
int parseMinute() throws ParseException {
return parseAsciiDigits(1, 2);
}
@Override
int parseSecond() throws ParseException {
return parseAsciiDigits(1, 2);
}
@Override
void parseFwsBetweenTimeOfDayAndZone() throws ParseException {
skipFoldingWhiteSpace();
}
@Override
int parseZone() throws ParseException {
try {
if (pos.getIndex() >= text.length()) {
throw new ParseException("Missing zone", pos.getIndex());
}
if (peekChar('+') || peekChar('-')) {
return parseZoneOffset();
} else if (skipAlternativePair('U', 'u', 'T', 't')) {
return 0;
} else if (skipAlternativeTriple('G', 'g', 'M', 'm',
'T', 't')) {
return 0;
} else {
int hoursOffset;
if (skipAlternative('E', 'e')) {
hoursOffset = 4;
} else if (skipAlternative('C', 'c')) {
hoursOffset = 5;
} else if (skipAlternative('M', 'm')) {
hoursOffset = 6;
} else if (skipAlternative('P', 'p')) {
hoursOffset = 7;
} else {
throw new ParseException("Invalid zone",
pos.getIndex());
}
if (skipAlternativePair('S', 's', 'T', 't')) {
hoursOffset += 1;
} else if (skipAlternativePair('D', 'd', 'T', 't')) {
} else {
pos.setIndex(pos.getIndex() - 1);
throw new ParseException("Invalid zone",
pos.getIndex());
}
return hoursOffset * 60;
}
} catch (ParseException e) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "No timezone? : '" + text + "'", e);
}
return 0;
}
}
@Override
boolean isValidZoneOffset(int offset) {
return true;
}
@Override
boolean skipFoldingWhiteSpace() {
boolean result = peekFoldingWhiteSpace();
skipLoop:
while (pos.getIndex() < text.length()) {
switch (text.charAt(pos.getIndex())) {
case ' ':
case '\t':
case '\r':
case '\n':
pos.setIndex(pos.getIndex() + 1);
break;
default:
break skipLoop;
}
}
return result;
}
@Override
boolean peekFoldingWhiteSpace() {
return super.peekFoldingWhiteSpace()
|| (pos.getIndex() < text.length()
&& text.charAt(pos.getIndex()) == '\n');
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,225 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
import jakarta.mail.IllegalWriteException;
import jakarta.mail.MessagingException;
import jakarta.mail.Part;
import java.util.Enumeration;
/**
* The MimePart interface models an <strong>Entity</strong> as defined
* by MIME (RFC2045, Section 2.4). <p>
* <p>
* MimePart extends the Part interface to add additional RFC822 and MIME
* specific semantics and attributes. It provides the base interface for
* the MimeMessage and MimeBodyPart classes
*
* <hr> <strong>A note on RFC822 and MIME headers</strong><p>
* <p>
* RFC822 and MIME header fields <strong>must</strong> contain only
* US-ASCII characters. If a header contains non US-ASCII characters,
* it must be encoded as per the rules in RFC 2047. The MimeUtility
* class provided in this package can be used to to achieve this.
* Callers of the <code>setHeader</code>, <code>addHeader</code>, and
* <code>addHeaderLine</code> methods are responsible for enforcing
* the MIME requirements for the specified headers. In addition, these
* header fields must be folded (wrapped) before being sent if they
* exceed the line length limitation for the transport (1000 bytes for
* SMTP). Received headers may have been folded. The application is
* responsible for folding and unfolding headers as appropriate.
*
* @author John Mani
* @see MimeUtility
* @see Part
*/
public interface MimePart extends Part {
/**
* Get the values of all header fields available for this header,
* returned as a single String, with the values separated by the
* delimiter. If the delimiter is <code>null</code>, only the
* first value is returned.
*
* @param name the name of this header
* @param delimiter delimiter between fields in returned string
* @return the value fields for all headers with
* this name
* @throws MessagingException for failures
*/
String getHeader(String name, String delimiter)
throws MessagingException;
/**
* Add a raw RFC822 header-line.
*
* @param line the line to add
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* @throws IllegalStateException if this Part is
* obtained from a READ_ONLY folder
* @throws MessagingException for other failures
*/
void addHeaderLine(String line) throws MessagingException;
/**
* Get all header lines as an Enumeration of Strings. A Header
* line is a raw RFC822 header-line, containing both the "name"
* and "value" field.
*
* @return an Enumeration of Strings
* @throws MessagingException for failures
*/
Enumeration<String> getAllHeaderLines() throws MessagingException;
/**
* Get matching header lines as an Enumeration of Strings.
* A Header line is a raw RFC822 header-line, containing both
* the "name" and "value" field.
*
* @param names the headers to return
* @return an Enumeration of Strings
* @throws MessagingException for failures
*/
Enumeration<String> getMatchingHeaderLines(String[] names)
throws MessagingException;
/**
* Get non-matching header lines as an Enumeration of Strings.
* A Header line is a raw RFC822 header-line, containing both
* the "name" and "value" field.
*
* @param names the headers to not return
* @return an Enumeration of Strings
* @throws MessagingException for failures
*/
Enumeration<String> getNonMatchingHeaderLines(String[] names)
throws MessagingException;
/**
* Get the transfer encoding of this part.
*
* @return content-transfer-encoding
* @throws MessagingException for failures
*/
String getEncoding() throws MessagingException;
/**
* Get the Content-ID of this part. Returns null if none present.
*
* @return content-ID
* @throws MessagingException for failures
*/
String getContentID() throws MessagingException;
/**
* Get the Content-MD5 digest of this part. Returns null if
* none present.
*
* @return content-MD5
* @throws MessagingException for failures
*/
String getContentMD5() throws MessagingException;
/**
* Set the Content-MD5 of this part.
*
* @param md5 the MD5 value
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* @throws IllegalStateException if this Part is
* obtained from a READ_ONLY folder
*/
void setContentMD5(String md5) throws MessagingException;
/**
* Get the language tags specified in the Content-Language header
* of this MimePart. The Content-Language header is defined by
* RFC 1766. Returns <code>null</code> if this header is not
* available.
*
* @return array of content language strings
* @throws MessagingException for failures
*/
String[] getContentLanguage() throws MessagingException;
/**
* Set the Content-Language header of this MimePart. The
* Content-Language header is defined by RFC1766.
*
* @param languages array of language tags
* @throws IllegalWriteException if the underlying
* implementation does not support modification
* @throws IllegalStateException if this Part is
* obtained from a READ_ONLY folder
*/
void setContentLanguage(String[] languages)
throws MessagingException;
/**
* Convenience method that sets the given String as this
* part's content, with a MIME type of "text/plain". If the
* string contains non US-ASCII characters. it will be encoded
* using the platform's default charset. The charset is also
* used to set the "charset" parameter. <p>
* <p>
* Note that there may be a performance penalty if
* <code>text</code> is large, since this method may have
* to scan all the characters to determine what charset to
* use. <p>
* <p>
* If the charset is already known, use the
* <code>setText</code> method that takes the charset parameter.
*
* @param text the text content to set
* @throws MessagingException if an error occurs
* @see #setText(String text, String charset)
*/
@Override
void setText(String text) throws MessagingException;
/**
* Convenience method that sets the given String as this part's
* content, with a MIME type of "text/plain" and the specified
* charset. The given Unicode string will be charset-encoded
* using the specified charset. The charset is also used to set
* "charset" parameter.
*
* @param text the text content to set
* @param charset the charset to use for the text
* @throws MessagingException if an error occurs
*/
void setText(String text, String charset)
throws MessagingException;
/**
* Convenience method that sets the given String as this part's
* content, with a primary MIME type of "text" and the specified
* MIME subtype. The given Unicode string will be charset-encoded
* using the specified charset. The charset is also used to set
* the "charset" parameter.
*
* @param text the text content to set
* @param charset the charset to use for the text
* @param subtype the MIME subtype to use (e.g., "html")
* @throws MessagingException if an error occurs
* @since JavaMail 1.4
*/
void setText(String text, String charset, String subtype)
throws MessagingException;
}

View file

@ -0,0 +1,156 @@
/*
* Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
import jakarta.activation.DataSource;
import jakarta.mail.FolderClosedException;
import jakarta.mail.MessageAware;
import jakarta.mail.MessageContext;
import jakarta.mail.MessagingException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownServiceException;
/**
* A utility class that implements a DataSource out of
* a MimePart. This class is primarily meant for service providers.
*
* @author John Mani
* @see MimePart
* @see jakarta.activation.DataSource
*/
public class MimePartDataSource implements DataSource, MessageAware {
/**
* The MimePart that provides the data for this DataSource.
*
* @since JavaMail 1.4
*/
protected MimePart part;
private MessageContext context;
/**
* Constructor, that constructs a DataSource from a MimePart.
*
* @param part the MimePart
*/
public MimePartDataSource(MimePart part) {
this.part = part;
}
/**
* Returns an input stream from this MimePart. <p>
* <p>
* This method applies the appropriate transfer-decoding, based
* on the Content-Transfer-Encoding attribute of this MimePart.
* Thus the returned input stream is a decoded stream of bytes.<p>
* <p>
* This implementation obtains the raw content from the Part
* using the <code>getContentStream()</code> method and decodes
* it using the <code>MimeUtility.decode()</code> method.
*
* @return decoded input stream
* @see MimeMessage#getContentStream
* @see MimeBodyPart#getContentStream
* @see MimeUtility#decode
*/
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
try {
if (part instanceof MimeBodyPart)
is = ((MimeBodyPart) part).getContentStream();
else if (part instanceof MimeMessage)
is = ((MimeMessage) part).getContentStream();
else
throw new MessagingException("Unknown part");
String encoding =
MimeBodyPart.restrictEncoding(part, part.getEncoding());
if (encoding != null)
return MimeUtility.decode(is, encoding);
else
return is;
} catch (FolderClosedException fex) {
throw new IOException(new FolderClosedException(fex.getFolder(), fex.getMessage(), fex));
} catch (MessagingException mex) {
// Cause is removed because in upper stack the cause is checked. See test POP3FolderClosedExceptionTest
throw new IOException(mex.getMessage());
}
}
/**
* DataSource method to return an output stream. <p>
* <p>
* This implementation throws the UnknownServiceException.
*/
@Override
public OutputStream getOutputStream() throws IOException {
throw new UnknownServiceException("Writing not supported");
}
/**
* Returns the content-type of this DataSource. <p>
* <p>
* This implementation just invokes the <code>getContentType</code>
* method on the MimePart.
*/
@Override
public String getContentType() {
try {
return part.getContentType();
} catch (MessagingException mex) {
// would like to be able to reflect the exception to the
// application, but since we can't do that we return a
// generic "unknown" value here and hope for another
// exception later.
return "application/octet-stream";
}
}
/**
* DataSource method to return a name. <p>
* <p>
* This implementation just returns an empty string.
*/
@Override
public String getName() {
try {
if (part instanceof MimeBodyPart)
return part.getFileName();
} catch (MessagingException mex) {
// ignore it
}
return "";
}
/**
* Return the <code>MessageContext</code> for the current part.
*
* @since JavaMail 1.1
*/
@Override
public synchronized MessageContext getMessageContext() {
if (context == null)
context = new MessageContext(part);
return context;
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.mail.internet;
import java.lang.reflect.Method;
/**
* General MIME-related utility methods.
*
* @author Bill Shannon
* @since JavaMail 1.4.4
*/
class MimeUtil {
private static final Method cleanContentType;
static {
Method meth = null;
try {
String cth = System.getProperty("mail.mime.contenttypehandler");
if (cth != null) {
ClassLoader cl = getContextClassLoader();
Class<?> clsHandler = null;
if (cl != null) {
try {
clsHandler = Class.forName(cth, false, cl);
} catch (ClassNotFoundException cex) {
}
}
if (clsHandler == null)
clsHandler = Class.forName(cth);
meth = clsHandler.getMethod("cleanContentType",
MimePart.class, String.class);
}
} catch (ClassNotFoundException | RuntimeException | NoSuchMethodException ex) {
// ignore it
} finally {
cleanContentType = meth;
}
}
// No one should instantiate this class.
private MimeUtil() {
}
/**
* If a Content-Type handler has been specified,
* call it to clean up the Content-Type value.
*
* @param mp the MimePart
* @param contentType the Content-Type value
* @return the cleaned Content-Type value
*/
public static String cleanContentType(MimePart mp, String contentType) {
if (cleanContentType != null) {
try {
return (String) cleanContentType.invoke(null,
new Object[]{mp, contentType});
} catch (Exception ex) {
return contentType;
}
} else
return contentType;
}
/**
* Convenience method to get our context class loader.
* Assert any privileges we might have and then call the
* Thread.getContextClassLoader method.
*/
private static ClassLoader getContextClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
}

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more