jakarta.mail.Session Maven / Gradle / Ivy
/*
* 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.LineInputStream;
import jakarta.mail.util.StreamProvider;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.StringTokenizer;
import java.util.concurrent.Executor;
import java.util.logging.Level;
/**
* The Session class represents a mail session and is not subclassed.
* It collects together properties and defaults used by the mail API's.
* A single default session can be shared by multiple applications on the
* desktop. Unshared sessions can also be created.
*
* The Session class provides access to the protocol providers that
* implement the Store
, Transport
, and related
* classes. The protocol providers are configured using the following files:
*
* -
javamail.providers
and
* javamail.default.providers
* -
javamail.address.map
and
* javamail.default.address.map
*
*
* Each javamail.
X resource file is searched for using
* three methods in the following order:
*
* -
java.home/conf/javamail.
X
* -
META-INF/javamail.
X
* -
META-INF/javamail.default.
X
*
*
* (Where java.home is the value of the "java.home" System property
* and conf is the directory named "conf" if it exists,
* otherwise the directory named "lib"; the "conf" directory was
* introduced in JDK 1.9.)
*
* The first method allows the user to include their own version of the
* resource file by placing it in the conf directory where the
* java.home
property points. The second method allows an
* application that uses the Jakarta Mail APIs to include their own resource
* files in their application's or jar file's META-INF
* directory. The javamail.default.
X default files
* are part of the Jakarta Mail mail.jar
file and should not be
* supplied by users.
*
* File location depends upon how the ClassLoader
method
* getResource
is implemented. Usually, the
* getResource
method searches through CLASSPATH until it
* finds the requested file and then stops.
*
* The ordering of entries in the resource files matters. If multiple
* entries exist, the first entries take precedence over the later
* entries. For example, the first IMAP provider found will be set as the
* default IMAP implementation until explicitly changed by the
* application. The user- or system-supplied resource files augment, they
* do not override, the default files included with the Jakarta Mail APIs.
* This means that all entries in all files loaded will be available.
*
* javamail.providers
and
* javamail.default.providers
*
* These resource files specify the stores and transports that are
* available on the system, allowing an application to "discover" what
* store and transport implementations are available. The protocol
* implementations are listed one per line. The file format defines four
* attributes that describe a protocol implementation. Each attribute is
* an "="-separated name-value pair with the name in lowercase. Each
* name-value pair is semi-colon (";") separated. The following names
* are defined.
*
*
*
* Attribute Names in Providers Files
*
*
* Name Description
*
*
* protocol
* Name assigned to protocol.
* For example, smtp
for Transport.
*
*
* type
* Valid entries are store
and transport
.
*
*
* class
* Class name that implements this protocol.
*
*
* vendor
* Optional string identifying the vendor.
*
*
* version
* Optional string identifying the version.
*
*
*
* Here's an example of META-INF/javamail.default.providers
* file contents:
*
* protocol=imap; type=store; class=org.eclipse.angus.mail.imap.IMAPStore; vendor=Oracle;
* protocol=smtp; type=transport; class=org.eclipse.angus.mail.smtp.SMTPTransport; vendor=Oracle;
*
*
* The current implementation also supports configuring providers using
* the Java SE {@link java.util.ServiceLoader ServiceLoader} mechanism.
* When creating your own provider, create a {@link Provider} subclass,
* for example:
*
* package com.example;
*
* import jakarta.mail.Provider;
*
* public class MyProvider extends Provider {
* public MyProvider() {
* super(Provider.Type.STORE, "myprot", MyStore.class.getName(),
* "Example", null);
* }
* }
*
* Then include a file named META-INF/services/jakarta.mail.Provider
* in your jar file that lists the name of your Provider class:
*
* com.example.MyProvider
*
*
*
* javamail.address.map
and
* javamail.default.address.map
*
* These resource files map transport address types to the transport
* protocol. The getType
method of
* jakarta.mail.Address
returns the address type. The
* javamail.address.map
file maps the transport type to the
* protocol. The file format is a series of name-value pairs. Each key
* name should correspond to an address type that is currently installed
* on the system; there should also be an entry for each
* jakarta.mail.Address
implementation that is present if it is
* to be used. For example, the
* jakarta.mail.internet.InternetAddress
method
* getType
returns "rfc822". Each referenced protocol should
* be installed on the system. For the case of news
, below,
* the client should install a Transport provider supporting the nntp
* protocol.
*
* Here are the typical contents of a javamail.address.map
file:
*
* rfc822=smtp
* news=nntp
*
*
* @author John Mani
* @author Bill Shannon
* @author Max Spivak
*/
public final class Session {
// Support legacy @DefaultProvider
private static final String DEFAULT_PROVIDER = "org.eclipse.angus.mail.util.DefaultProvider";
private final StreamProvider streamProvider;
private final Properties props;
private final Authenticator authenticator;
private final Hashtable authTable
= new Hashtable<>();
private boolean debug = false;
private PrintStream out; // debug output stream
private MailLogger logger;
private final List providers = new ArrayList<>();
private final Map providersByProtocol = new HashMap<>();
private final Map providersByClassName = new HashMap<>();
private final Properties addressMap = new Properties();
// maps type to protocol
// the queue of events to be delivered, if mail.event.scope===session
private final EventQueue q;
// The default session.
private static Session defaultSession = null;
private static final String confDir;
static {
String dir = null;
try {
dir = AccessController.doPrivileged(
new PrivilegedAction() {
@Override
public String run() {
String home = System.getProperty("java.home");
String newdir = home + File.separator + "conf";
File conf = new File(newdir);
if (conf.exists())
return newdir + File.separator;
else
return home + File.separator +
"lib" + File.separator;
}
});
} catch (Exception ex) {
// ignore any exceptions
}
confDir = dir;
}
// Constructor is not public
private Session(Properties props, Authenticator authenticator) {
this.props = props;
this.authenticator = authenticator;
this.streamProvider = StreamProvider.provider();
if (Boolean.parseBoolean(props.getProperty("mail.debug")))
debug = true;
initLogger();
logger.log(Level.CONFIG, "Jakarta Mail version {0}", Version.version);
// get the Class associated with the Authenticator
Class> cl;
if (authenticator != null) {
cl = authenticator.getClass();
} else {
// Use implementation class, because that class loader has access to jakarta.mail module and implementation resources
cl = streamProvider.getClass();
}
// load the resources
loadProviders(cl);
loadAddressMap(cl);
q = new EventQueue((Executor) props.get("mail.event.executor"));
}
/**
* Get the stream provider instance of the session.
*
* @return the stream provider
* @since JavaMail 2.1
*/
public StreamProvider getStreamProvider() {
return streamProvider;
}
private final synchronized void initLogger() {
logger = new MailLogger(this.getClass(), "DEBUG", debug, getDebugOut());
}
/**
* Get a new Session object.
*
* @param props Properties object that hold relevant properties.
* It is expected that the client supplies values
* for the properties listed in Appendix A of the
* Jakarta Mail spec (particularly mail.store.protocol,
* mail.transport.protocol, mail.host, mail.user,
* and mail.from) as the defaults are unlikely to
* work in all cases.
* @param authenticator Authenticator object used to call back to
* the application when a user name and password is
* needed.
* @return a new Session object
* @see jakarta.mail.Authenticator
*/
public static Session getInstance(Properties props, Authenticator authenticator) {
return new Session(props, authenticator);
}
/**
* Get a new Session object.
*
* @param props Properties object that hold relevant properties.
* It is expected that the client supplies values
* for the properties listed in Appendix A of the
* Jakarta Mail spec (particularly mail.store.protocol,
* mail.transport.protocol, mail.host, mail.user,
* and mail.from) as the defaults are unlikely to
* work in all cases.
* @return a new Session object
* @since JavaMail 1.2
*/
public static Session getInstance(Properties props) {
return new Session(props, null);
}
/**
* Get the default Session object. If a default has not yet been
* setup, a new Session object is created and installed as the
* default.
*
* Since the default session is potentially available to all
* code executing in the same Java virtual machine, and the session
* can contain security sensitive information such as user names
* and passwords, access to the default session is restricted.
* The Authenticator object, which must be created by the caller,
* is used indirectly to check access permission. The Authenticator
* object passed in when the session is created is compared with
* the Authenticator object passed in to subsequent requests to
* get the default session. If both objects are the same, or are
* from the same ClassLoader, the request is allowed. Otherwise,
* it is denied.
*
* Note that if the Authenticator object used to create the session
* is null, anyone can get the default session by passing in null.
*
* Note also that the Properties object is used only the first time
* this method is called, when a new Session object is created.
* Subsequent calls return the Session object that was created by the
* first call, and ignore the passed Properties object. Use the
* getInstance
method to get a new Session object every
* time the method is called.
*
* Additional security Permission objects may be used to
* control access to the default session.
*
* In the current implementation, if a SecurityManager is set, the
* caller must have the RuntimePermission("setFactory")
* permission.
*
* @param props Properties object. Used only if a new Session
* object is created.
* It is expected that the client supplies values
* for the properties listed in Appendix A of the
* Jakarta Mail spec (particularly mail.store.protocol,
* mail.transport.protocol, mail.host, mail.user,
* and mail.from) as the defaults are unlikely to
* work in all cases.
* @param authenticator Authenticator object. Used only if a
* new Session object is created. Otherwise,
* it must match the Authenticator used to create
* the Session.
* @return the default Session object
*/
public static synchronized Session getDefaultInstance(Properties props, Authenticator authenticator) {
if (defaultSession == null) {
SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkSetFactory();
defaultSession = new Session(props, authenticator);
} else {
// have to check whether caller is allowed to see default session
if (defaultSession.authenticator == authenticator)
; // either same object or both null, either way OK
else if (defaultSession.authenticator != null &&
authenticator != null &&
defaultSession.authenticator.getClass().getClassLoader() ==
authenticator.getClass().getClassLoader())
; // both objects came from the same class loader, OK
else
// anything else is not allowed
throw new SecurityException("Access to default session denied");
}
return defaultSession;
}
/**
* Get the default Session object. If a default has not yet been
* setup, a new Session object is created and installed as the
* default.
*
* Note that a default session created with no Authenticator is
* available to all code executing in the same Java virtual
* machine, and the session can contain security sensitive
* information such as user names and passwords.
*
* @param props Properties object. Used only if a new Session
* object is created.
* It is expected that the client supplies values
* for the properties listed in Appendix A of the
* Jakarta Mail spec (particularly mail.store.protocol,
* mail.transport.protocol, mail.host, mail.user,
* and mail.from) as the defaults are unlikely to
* work in all cases.
* @return the default Session object
* @since JavaMail 1.2
*/
public static Session getDefaultInstance(Properties props) {
return getDefaultInstance(props, null);
}
/**
* Set the debug setting for this Session.
*
* Since the debug setting can be turned on only after the Session
* has been created, to turn on debugging in the Session
* constructor, set the property mail.debug
in the
* Properties object passed in to the constructor to true. The
* value of the mail.debug
property is used to
* initialize the per-Session debugging flag. Subsequent calls to
* the setDebug
method manipulate the per-Session
* debugging flag and have no effect on the mail.debug
* property.
*
* @param debug Debug setting
*/
public synchronized void setDebug(boolean debug) {
this.debug = debug;
initLogger();
logger.log(Level.CONFIG, "setDebug: Jakarta Mail version {0}", Version.version);
}
/**
* Get the debug setting for this Session.
*
* @return current debug setting
*/
public synchronized boolean getDebug() {
return debug;
}
/**
* Set the stream to be used for debugging output for this session.
* If out
is null, System.out
will be used.
* Note that debugging output that occurs before any session is created,
* as a result of setting the mail.debug
system property,
* will always be sent to System.out
.
*
* @param out the PrintStream to use for debugging output
* @since JavaMail 1.3
*/
public synchronized void setDebugOut(PrintStream out) {
this.out = out;
initLogger();
}
/**
* Returns the stream to be used for debugging output. If no stream
* has been set, System.out
is returned.
*
* @return the PrintStream to use for debugging output
* @since JavaMail 1.3
*/
public synchronized PrintStream getDebugOut() {
if (out == null)
return System.out;
else
return out;
}
/**
* This method returns an array of all the implementations installed
* via the javamail.[default.]providers files that can
* be loaded using the ClassLoader available to this application.
*
* @return Array of configured providers
*/
public synchronized Provider[] getProviders() {
Provider[] _providers = new Provider[providers.size()];
providers.toArray(_providers);
return _providers;
}
/**
* Returns the default Provider for the protocol
* specified. Checks mail.<protocol>.class property
* first and if it exists, returns the Provider
* associated with this implementation. If it doesn't exist,
* returns the Provider that appeared first in the
* configuration files. If an implementation for the protocol
* isn't found, throws NoSuchProviderException
*
* @param protocol Configured protocol (i.e. smtp, imap, etc)
* @return Currently configured Provider for the specified protocol
* @throws NoSuchProviderException If a provider for the given
* protocol is not found.
*/
public synchronized Provider getProvider(String protocol) throws NoSuchProviderException {
if (protocol == null || protocol.length() == 0) {
throw new NoSuchProviderException("Invalid protocol: null");
}
Provider _provider = null;
// check if the mail..class property exists
String _className = props.getProperty("mail." + protocol + ".class");
if (_className != null) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("mail." + protocol +
".class property exists and points to " +
_className);
}
_provider = providersByClassName.get(_className);
}
if (_provider != null) {
return _provider;
} else {
// returning currently default protocol in providersByProtocol
_provider = providersByProtocol.get(protocol);
}
if (_provider == null) {
throw new NoSuchProviderException("No provider for " + protocol);
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine("getProvider() returning " + _provider);
}
return _provider;
}
}
/**
* Set the passed Provider to be the default implementation
* for the protocol in Provider.protocol overriding any previous values.
*
* @param provider Currently configured Provider which will be
* set as the default for the protocol
* @throws NoSuchProviderException If the provider passed in
* is invalid.
*/
public synchronized void setProvider(Provider provider)
throws NoSuchProviderException {
if (provider == null) {
throw new NoSuchProviderException("Can't set null provider");
}
providersByProtocol.put(provider.getProtocol(), provider);
providersByClassName.put(provider.getClassName(), provider);
props.put("mail." + provider.getProtocol() + ".class", provider.getClassName());
}
/**
* Get a Store object that implements this user's desired Store
* protocol. The mail.store.protocol
property specifies the
* desired protocol. If an appropriate Store object is not obtained,
* NoSuchProviderException is thrown
*
* @return a Store object
* @throws NoSuchProviderException If a provider for the given
* protocol is not found.
*/
public Store getStore() throws NoSuchProviderException {
return getStore(getProperty("mail.store.protocol"));
}
/**
* Get a Store object that implements the specified protocol. If an
* appropriate Store object cannot be obtained,
* NoSuchProviderException is thrown.
*
* @param protocol the Store protocol
* @return a Store object
* @throws NoSuchProviderException If a provider for the given
* protocol is not found.
*/
public Store getStore(String protocol) throws NoSuchProviderException {
return getStore(new URLName(protocol, null, -1, null, null, null));
}
/**
* Get a Store object for the given URLName. If the requested Store
* object cannot be obtained, NoSuchProviderException is thrown.
*
* The "scheme" part of the URL string (Refer RFC 1738) is used
* to locate the Store protocol.
*
* @param url URLName that represents the desired Store
* @return a closed Store object
* @throws NoSuchProviderException If a provider for the given
* URLName is not found.
* @see #getFolder(URLName)
* @see jakarta.mail.URLName
*/
public Store getStore(URLName url) throws NoSuchProviderException {
String protocol = url.getProtocol();
Provider p = getProvider(protocol);
return getStore(p, url);
}
/**
* Get an instance of the store specified by Provider. Instantiates
* the store and returns it.
*
* @param provider Store Provider that will be instantiated
* @return Instantiated Store
* @throws NoSuchProviderException If a provider for the given
* Provider is not found.
*/
public Store getStore(Provider provider) throws NoSuchProviderException {
return getStore(provider, null);
}
/**
* Get an instance of the store specified by Provider. If the URLName
* is not null, uses it, otherwise creates a new one. Instantiates
* the store and returns it. This is a private method used by
* getStore(Provider) and getStore(URLName)
*
* @param provider Store Provider that will be instantiated
* @param url URLName used to instantiate the Store
* @return Instantiated Store
* @throws NoSuchProviderException If a provider for the given
* Provider/URLName is not found.
*/
private Store getStore(Provider provider, URLName url) throws NoSuchProviderException {
// make sure we have the correct type of provider
if (provider == null || provider.getType() != Provider.Type.STORE) {
throw new NoSuchProviderException("invalid provider");
}
return getService(provider, url, Store.class);
}
/**
* Get a closed Folder object for the given URLName. If the requested
* Folder object cannot be obtained, null is returned.
*
* The "scheme" part of the URL string (Refer RFC 1738) is used
* to locate the Store protocol. The rest of the URL string (that is,
* the "schemepart", as per RFC 1738) is used by that Store
* in a protocol dependent manner to locate and instantiate the
* appropriate Folder object.
*
* Note that RFC 1738 also specifies the syntax for the
* "schemepart" for IP-based protocols (IMAP4, POP3, etc.).
* Providers of IP-based mail Stores should implement that
* syntax for referring to Folders.
*
* @param url URLName that represents the desired folder
* @return Folder
* @throws NoSuchProviderException If a provider for the given
* URLName is not found.
* @throws MessagingException if the Folder could not be
* located or created.
* @see #getStore(URLName)
* @see jakarta.mail.URLName
*/
public Folder getFolder(URLName url) throws MessagingException {
// First get the Store
Store store = getStore(url);
store.connect();
return store.getFolder(url);
}
/**
* Get a Transport object that implements this user's desired
* Transport protcol. The mail.transport.protocol
property
* specifies the desired protocol. If an appropriate Transport
* object cannot be obtained, MessagingException is thrown.
*
* @return a Transport object
* @throws NoSuchProviderException If the provider is not found.
*/
public Transport getTransport() throws NoSuchProviderException {
String prot = getProperty("mail.transport.protocol");
if (prot != null)
return getTransport(prot);
// if the property isn't set, use the protocol for "rfc822"
prot = (String) addressMap.get("rfc822");
if (prot != null)
return getTransport(prot);
return getTransport("smtp"); // if all else fails
}
/**
* Get a Transport object that implements the specified protocol.
* If an appropriate Transport object cannot be obtained, null is
* returned.
*
* @param protocol the Transport protocol
* @return a Transport object
* @throws NoSuchProviderException If provider for the given
* protocol is not found.
*/
public Transport getTransport(String protocol)
throws NoSuchProviderException {
return getTransport(new URLName(protocol, null, -1, null, null, null));
}
/**
* Get a Transport object for the given URLName. If the requested
* Transport object cannot be obtained, NoSuchProviderException is thrown.
*
* The "scheme" part of the URL string (Refer RFC 1738) is used
* to locate the Transport protocol.
*
* @param url URLName that represents the desired Transport
* @return a closed Transport object
* @throws NoSuchProviderException If a provider for the given
* URLName is not found.
* @see jakarta.mail.URLName
*/
public Transport getTransport(URLName url) throws NoSuchProviderException {
String protocol = url.getProtocol();
Provider p = getProvider(protocol);
return getTransport(p, url);
}
/**
* Get an instance of the transport specified in the Provider. Instantiates
* the transport and returns it.
*
* @param provider Transport Provider that will be instantiated
* @return Instantiated Transport
* @throws NoSuchProviderException If provider for the given
* provider is not found.
*/
public Transport getTransport(Provider provider) throws NoSuchProviderException {
return getTransport(provider, null);
}
/**
* Get a Transport object that can transport a Message of the
* specified address type.
*
* @param address an address for which a Transport is needed
* @return A Transport object
* @throws NoSuchProviderException If provider for the
* Address type is not found
* @see jakarta.mail.Address
*/
public Transport getTransport(Address address) throws NoSuchProviderException {
String transportProtocol;
transportProtocol =
getProperty("mail.transport.protocol." + address.getType());
if (transportProtocol != null)
return getTransport(transportProtocol);
transportProtocol = (String) addressMap.get(address.getType());
if (transportProtocol != null)
return getTransport(transportProtocol);
throw new NoSuchProviderException("No provider for Address type: " + address.getType());
}
/**
* Get a Transport object using the given provider and urlname.
*
* @param provider the provider to use
* @param url urlname to use (can be null)
* @return A Transport object
* @throws NoSuchProviderException If no provider or the provider
* was the wrong class.
*/
private Transport getTransport(Provider provider, URLName url) throws NoSuchProviderException {
// make sure we have the correct type of provider
if (provider == null || provider.getType() != Provider.Type.TRANSPORT) {
throw new NoSuchProviderException("invalid provider");
}
return getService(provider, url, Transport.class);
}
/**
* Get a Service object. Needs a provider object, but will
* create a URLName if needed. It attempts to instantiate
* the correct class.
*
* @param provider which provider to use
* @param url which URLName to use (can be null)
* @param type the service type (class)
* @throws NoSuchProviderException thrown when the class cannot be
* found or when it does not have the correct constructor
* (Session, URLName), or if it is not derived from
* Service.
*/
private T getService(Provider provider, URLName url, Class type) throws NoSuchProviderException {
// need a provider and url
if (provider == null) {
throw new NoSuchProviderException("null");
}
// create a url if needed
if (url == null) {
url = new URLName(provider.getProtocol(), null, -1,
null, null, null);
}
// get the ClassLoader associated with the Authenticator
Class> acl;
if (authenticator != null)
acl = authenticator.getClass();
else
acl = streamProvider.getClass();
Class> serviceClass = null;
for (ClassLoader l : getClassLoaders(Thread.class,
provider.getClass(),
acl,
streamProvider.getClass(),
getClass(),
System.class)) {
try {
//load and verify provider is compatible in this classloader
serviceClass = Class.forName(provider.getClassName(),
false, l).asSubclass(type);
break;
} catch (ClassNotFoundException | ClassCastException ex) {
// ignore it
}
}
if (serviceClass == null) {
// That didn't work, now try the "system" class loader.
// (Need both of these because JDK 1.1 class loaders
// may not delegate to their parent class loader.)
try {
serviceClass = Class.forName(provider.getClassName())
.asSubclass(type);
} catch (Exception ex) {
// Nothing worked, give up.
logger.log(Level.FINE, "Exception loading provider", ex);
throw new NoSuchProviderException(provider.getProtocol());
}
}
// construct an instance of the class
try {
Class>[] c = {jakarta.mail.Session.class, jakarta.mail.URLName.class};
Constructor> cons = serviceClass.getConstructor(c);
Object[] o = {this, url};
return type.cast(cons.newInstance(o));
} catch (Exception ex) {
logger.log(Level.FINE, "Exception loading provider", ex);
throw new NoSuchProviderException(provider.getProtocol());
}
}
/**
* Save a PasswordAuthentication for this (store or transport) URLName.
* If pw is null the entry corresponding to the URLName is removed.
*
* This is normally used only by the store or transport implementations
* to allow authentication information to be shared among multiple
* uses of a session.
*
* @param url the URLName
* @param pw the PasswordAuthentication to save
*/
public void setPasswordAuthentication(URLName url, PasswordAuthentication pw) {
if (pw == null)
authTable.remove(url);
else
authTable.put(url, pw);
}
/**
* Return any saved PasswordAuthentication for this (store or transport)
* URLName. Normally used only by store or transport implementations.
*
* @param url the URLName
* @return the PasswordAuthentication corresponding to the URLName
*/
public PasswordAuthentication getPasswordAuthentication(URLName url) {
return authTable.get(url);
}
/**
* Call back to the application to get the needed user name and password.
* The application should put up a dialog something like:
*
* Connecting to <protocol> mail service on host <addr>, port <port>.
* <prompt>
*
* User Name: <defaultUserName>
* Password:
*
*
* @param addr InetAddress of the host. may be null.
* @param port the port on the host
* @param protocol protocol scheme (e.g. imap, pop3, etc.)
* @param prompt any additional String to show as part of
* the prompt; may be null.
* @param defaultUserName the default username. may be null.
* @return the authentication which was collected by the authenticator;
* may be null.
*/
public PasswordAuthentication requestPasswordAuthentication(InetAddress addr, int port, String protocol, String prompt, String defaultUserName) {
if (authenticator != null) {
return authenticator.requestPasswordAuthentication(
addr, port, protocol, prompt, defaultUserName);
} else {
return null;
}
}
/**
* Returns the Properties object associated with this Session
*
* @return Properties object
*/
public Properties getProperties() {
return props;
}
/**
* Returns the value of the specified property. Returns null
* if this property does not exist.
*
* @param name the property name
* @return String that is the property value
*/
public String getProperty(String name) {
return props.getProperty(name);
}
static boolean containsDefaultProvider(Provider provider) {
Annotation[] annotations = provider.getClass().getDeclaredAnnotations();
for (Annotation annotation : annotations) {
if (DEFAULT_PROVIDER.equals(annotation.annotationType().getName())) {
return true;
}
}
return false;
}
/**
* Load the protocol providers config files.
*/
private void loadProviders(Class> cl) {
StreamLoader loader = new StreamLoader() {
@Override
public void load(InputStream is) throws IOException {
loadProvidersFromStream(is);
}
};
// load system-wide javamail.providers from the
// /{conf,lib} directory
try {
if (confDir != null)
loadFile(confDir + "javamail.providers", loader);
} catch (SecurityException ex) {
}
//Fetch classloader of given class, falling back to others if needed.
ClassLoader gcl;
ClassLoader[] loaders = getClassLoaders(cl, Thread.class, System.class);
if (loaders.length != 0) {
gcl = loaders[0];
} else {
gcl = getContextClassLoader(); //Fail safe
}
// next, add all the non-default services
ServiceLoader sl = ServiceLoader.load(Provider.class, gcl);
for (Provider p : sl) {
if (!containsDefaultProvider(p))
addProvider(p);
}
// + handle Glassfish/OSGi (platform specific default)
Iterator iter = lookupUsingHk2ServiceLoader(Provider.class, gcl);
while (iter.hasNext()) {
Provider p = iter.next();
if (!containsDefaultProvider(p))
addProvider(p);
}
// load the META-INF/javamail.providers file supplied by an application
loadAllResources("META-INF/javamail.providers", cl, loader);
// load default META-INF/javamail.default.providers from mail.jar file
loadResource("/META-INF/javamail.default.providers", cl, loader, false);
// finally, add all the default services
sl = ServiceLoader.load(Provider.class, gcl);
for (Provider p : sl) {
if (containsDefaultProvider(p))
addProvider(p);
}
// + handle Glassfish/OSGi (platform specific default)
iter = lookupUsingHk2ServiceLoader(Provider.class, gcl);
while (iter.hasNext()) {
Provider p = iter.next();
if (containsDefaultProvider(p)) {
addProvider(p);
}
}
/*
* If we haven't loaded any providers, fake it.
*/
if (providers.isEmpty()) {
logger.config("failed to load any providers, using defaults");
// failed to load any providers, initialize with our defaults
addProvider(new Provider(Provider.Type.STORE,
"imap", "org.eclipse.angus.mail.imap.IMAPStore",
"Oracle", Version.version));
addProvider(new Provider(Provider.Type.STORE,
"imaps", "org.eclipse.angus.mail.imap.IMAPSSLStore",
"Oracle", Version.version));
addProvider(new Provider(Provider.Type.STORE,
"pop3", "org.eclipse.angus.mail.pop3.POP3Store",
"Oracle", Version.version));
addProvider(new Provider(Provider.Type.STORE,
"pop3s", "org.eclipse.angus.mail.pop3.POP3SSLStore",
"Oracle", Version.version));
addProvider(new Provider(Provider.Type.TRANSPORT,
"smtp", "org.eclipse.angus.mail.smtp.SMTPTransport",
"Oracle", Version.version));
addProvider(new Provider(Provider.Type.TRANSPORT,
"smtps", "org.eclipse.angus.mail.smtp.SMTPSSLTransport",
"Oracle", Version.version));
}
if (logger.isLoggable(Level.CONFIG)) {
// dump the output of the tables for debugging
logger.config("Tables of loaded providers");
logger.config("Providers Listed By Class Name: " +
providersByClassName);
logger.config("Providers Listed By Protocol: " +
providersByProtocol);
}
}
private void loadProvidersFromStream(InputStream is) throws IOException {
if (is != null) {
LineInputStream lis = streamProvider.inputLineStream(is, false);
String currLine;
// load and process one line at a time using LineInputStream
while ((currLine = lis.readLine()) != null) {
if (currLine.startsWith("#"))
continue;
if (currLine.trim().length() == 0)
continue; // skip blank line
Provider.Type type = null;
String protocol = null, className = null;
String vendor = null, version = null;
// separate line into key-value tuples
StringTokenizer tuples = new StringTokenizer(currLine, ";");
while (tuples.hasMoreTokens()) {
String currTuple = tuples.nextToken().trim();
// set the value of each attribute based on its key
int sep = currTuple.indexOf("=");
if (currTuple.startsWith("protocol=")) {
protocol = currTuple.substring(sep + 1);
} else if (currTuple.startsWith("type=")) {
String strType = currTuple.substring(sep + 1);
if (strType.equalsIgnoreCase("store")) {
type = Provider.Type.STORE;
} else if (strType.equalsIgnoreCase("transport")) {
type = Provider.Type.TRANSPORT;
}
} else if (currTuple.startsWith("class=")) {
className = currTuple.substring(sep + 1);
} else if (currTuple.startsWith("vendor=")) {
vendor = currTuple.substring(sep + 1);
} else if (currTuple.startsWith("version=")) {
version = currTuple.substring(sep + 1);
}
}
// check if a valid Provider; else, continue
if (type == null || protocol == null || className == null
|| protocol.length() == 0 || className.length() == 0) {
logger.log(Level.CONFIG, "Bad provider entry: {0}",
currLine);
continue;
}
Provider provider = new Provider(type, protocol, className,
vendor, version);
// add the newly-created Provider to the lookup tables
addProvider(provider);
}
}
}
/**
* Add a provider to the session.
*
* @param provider the provider to add
* @since JavaMail 1.4
*/
public synchronized void addProvider(Provider provider) {
providers.add(provider);
providersByClassName.put(provider.getClassName(), provider);
if (!providersByProtocol.containsKey(provider.getProtocol()))
providersByProtocol.put(provider.getProtocol(), provider);
}
// load maps in reverse order of preference so that the preferred
// map is loaded last since its entries will override the previous ones
private void loadAddressMap(Class> cl) {
StreamLoader loader = new StreamLoader() {
@Override
public void load(InputStream is) throws IOException {
addressMap.load(is);
}
};
// load default META-INF/javamail.default.address.map from mail.jar
loadResource("/META-INF/javamail.default.address.map", cl, loader, true);
// load the META-INF/javamail.address.map file supplied by an app
loadAllResources("META-INF/javamail.address.map", cl, loader);
// load system-wide javamail.address.map from the
// /{conf,lib} directory
try {
if (confDir != null)
loadFile(confDir + "javamail.address.map", loader);
} catch (SecurityException ex) {
}
if (addressMap.isEmpty()) {
logger.config("failed to load address map, using defaults");
addressMap.put("rfc822", "smtp");
}
}
/**
* Set the default transport protocol to use for addresses of
* the specified type. Normally the default is set by the
* javamail.default.address.map
or
* javamail.address.map
files or resources.
*
* @param addresstype type of address
* @param protocol name of protocol
* @see #getTransport(Address)
* @since JavaMail 1.4
*/
public synchronized void setProtocolForAddress(String addresstype, String protocol) {
if (protocol == null)
addressMap.remove(addresstype);
else
addressMap.put(addresstype, protocol);
}
/**
* Load from the named file.
*/
private void loadFile(String name, StreamLoader loader) {
InputStream clis = null;
try {
clis = new BufferedInputStream(new FileInputStream(name));
loader.load(clis);
logger.log(Level.CONFIG, "successfully loaded file: {0}", name);
} catch (FileNotFoundException fex) {
// ignore it
} catch (IOException | SecurityException e) {
if (logger.isLoggable(Level.CONFIG))
logger.log(Level.CONFIG, "not loading file: " + name, e);
} finally {
try {
if (clis != null)
clis.close();
} catch (IOException ex) {
} // ignore it
}
}
/**
* Load from the named resource.
*/
private void loadResource(String name, Class> cl, StreamLoader loader, boolean expected) {
InputStream clis = null;
try {
clis = getResourceAsStream(cl, name);
if (clis != null) {
loader.load(clis);
logger.log(Level.CONFIG, "successfully loaded resource: {0}",
name);
} else {
if (expected)
logger.log(Level.WARNING,
"expected resource not found: {0}", name);
}
} catch (IOException | SecurityException e) {
logger.log(Level.CONFIG, "Exception loading resource", e);
} finally {
try {
if (clis != null)
clis.close();
} catch (IOException ex) {
} // ignore it
}
}
/**
* Load all of the named resource.
*/
private void loadAllResources(String name, Class> cl, StreamLoader loader) {
boolean anyLoaded = false;
try {
URL[] urls;
ClassLoader cld = null;
// First try the "application's" class loader.
cld = getContextClassLoader();
if (cld == null)
cld = cl.getClassLoader();
if (cld != null)
urls = getResources(cld, name);
else
urls = getSystemResources(name);
if (urls != null) {
for (int i = 0; i < urls.length; i++) {
URL url = urls[i];
InputStream clis = null;
logger.log(Level.CONFIG, "URL {0}", url);
try {
clis = openStream(url);
if (clis != null) {
loader.load(clis);
anyLoaded = true;
logger.log(Level.CONFIG,
"successfully loaded resource: {0}", url);
} else {
logger.log(Level.CONFIG,
"not loading resource: {0}", url);
}
} catch (FileNotFoundException fex) {
// ignore it
} catch (IOException | SecurityException ioex) {
logger.log(Level.CONFIG, "Exception loading resource",
ioex);
} finally {
try {
if (clis != null)
clis.close();
} catch (IOException cex) {
}
}
}
}
} catch (Exception ex) {
logger.log(Level.CONFIG, "Exception loading resource", ex);
}
// if failed to load anything, fall back to old technique, just in case
if (!anyLoaded) {
/*
logger.config("!anyLoaded");
*/
loadResource("/" + name, cl, loader, false);
}
}
/*
* Following are security related methods that work on JDK 1.2 or newer.
*/
static ClassLoader getContextClassLoader() {
return AccessController.doPrivileged(
new PrivilegedAction() {
@Override
public ClassLoader run() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
} catch (SecurityException ex) {
}
return cl;
}
}
);
}
private static ClassLoader[] getClassLoaders(final Class>... classes) {
return AccessController.doPrivileged(
new PrivilegedAction() {
@Override
public ClassLoader[] run() {
ClassLoader[] loaders = new ClassLoader[classes.length];
int w = 0;
for (Class> k : classes) {
ClassLoader cl = null;
if (k == Thread.class) {
try {
cl = Thread.currentThread().getContextClassLoader();
} catch (SecurityException ex) {
}
} else if (k == System.class) {
try {
cl = ClassLoader.getSystemClassLoader();
} catch (SecurityException ex) {
}
} else {
try {
cl = k.getClassLoader();
} catch (SecurityException ex) {
}
}
if (cl != null) {
loaders[w++] = cl;
}
}
if (loaders.length != w) {
loaders = Arrays.copyOf(loaders, w);
}
return loaders;
}
}
);
}
private static InputStream getResourceAsStream(final Class> c, final String name) throws IOException {
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction() {
@Override
public InputStream run() throws IOException {
try {
return c.getResourceAsStream(name);
} catch (RuntimeException e) {
// gracefully handle ClassLoader bugs (Tomcat)
IOException ioex = new IOException(
"ClassLoader.getResourceAsStream failed");
ioex.initCause(e);
throw ioex;
}
}
}
);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
private static URL[] getResources(final ClassLoader cl, final String name) {
return AccessController.doPrivileged(new PrivilegedAction() {
@Override
public URL[] run() {
URL[] ret = null;
try {
List v = Collections.list(cl.getResources(name));
if (!v.isEmpty()) {
ret = new URL[v.size()];
v.toArray(ret);
}
} catch (IOException | SecurityException ioex) {
}
return ret;
}
});
}
private static URL[] getSystemResources(final String name) {
return AccessController.doPrivileged(new PrivilegedAction() {
@Override
public URL[] run() {
URL[] ret = null;
try {
List v = Collections.list(
ClassLoader.getSystemResources(name));
if (!v.isEmpty()) {
ret = new URL[v.size()];
v.toArray(ret);
}
} catch (IOException | SecurityException ioex) {
}
return ret;
}
});
}
private static InputStream openStream(final URL url) throws IOException {
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction() {
@Override
public InputStream run() throws IOException {
return url.openStream();
}
}
);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
EventQueue getEventQueue() {
return q;
}
private static Class>[] getHk2ServiceLoaderTargets(Class> factoryClass) {
ClassLoader[] loaders = getClassLoaders(Thread.class, factoryClass, System.class);
Class>[] classes = new Class>[loaders.length];
int w = 0;
for (ClassLoader loader : loaders) {
if (loader != null) {
try {
classes[w++] = Class.forName("org.glassfish.hk2.osgiresourcelocator.ServiceLoader", false, loader);
} catch (Exception | LinkageError ignored) {
} //GlassFish class loaders can throw undocumented exceptions
}
}
if (classes.length != w) {
classes = Arrays.copyOf(classes, w);
}
return classes;
}
@SuppressWarnings({"unchecked"})
private Iterator lookupUsingHk2ServiceLoader(Class factoryId, ClassLoader loader) {
for (Class> target : getHk2ServiceLoaderTargets(factoryId)) {
try {
// Use reflection to avoid having any dependency on HK2 ServiceLoader class
Class> serviceClass = Class.forName(factoryId.getName(), false, loader);
Class>[] args = new Class>[]{serviceClass};
Method m = target.getMethod("lookupProviderInstances", Class.class);
Iterable result = ((Iterable) m.invoke(null, (Object[]) args));
if (result != null) {
return result.iterator();
}
} catch (Exception ignored) {
// log and continue
}
}
return Collections.emptyIterator();
}
}
/**
* Support interface to generalize
* code that loads resources from stream.
*/
interface StreamLoader {
void load(InputStream is) throws IOException;
}