/* * @(#)Session.java 1.43 99/12/16 * * Copyright 1997-1999 Sun Microsystems, Inc. All Rights Reserved. * * This software is the proprietary information of Sun Microsystems, Inc. * Use is subject to license terms. * */ package javax.mail; import java.lang.*; import java.lang.reflect.*; import java.io.*; import java.net.*; import java.util.Enumeration; import java.util.Hashtable; import java.util.Properties; import java.util.StringTokenizer; import java.util.Vector; import javax.activation.*; import com.sun.mail.util.LineInputStream; /** * 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. * * @version 1.43, 99/12/16 * @author John Mani * @author Bill Shannon * @author Max Spivak */ public final class Session { private Properties props; private Authenticator authenticator; private Hashtable authTable = new Hashtable(); private boolean debug = false; private Vector providers = new Vector(); private Hashtable providersByProtocol = new Hashtable(); private Hashtable providersByClassName = new Hashtable(); private Properties addressMap = new Properties(); // maps type to protocol private static Method getResources = null; private static Method getSystemResources = null; static { try { Class c = java.lang.ClassLoader.class; // assume both succeed or both fail getResources = c.getMethod("getResources", new Class[] { String.class }); getSystemResources = c.getMethod("getSystemResources", new Class[] { String.class }); } catch (Throwable t) { } // ignore any errors } // The default session. private static Session defaultSession = null; // Constructor is not public private Session(Properties props, Authenticator authenticator) { this.props = props; this.authenticator = authenticator; if (Boolean.valueOf(props.getProperty("mail.debug")).booleanValue()) debug = true; // get the Class associated with the Authenticator Class cl; if (authenticator != null) cl = authenticator.getClass(); else cl = this.getClass(); // load the resources loadProviders(cl); loadAddressMap(cl); } /** * 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 * JavaMail 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 javax.mail.Authenticator */ public static Session getInstance(Properties props, Authenticator authenticator) { return new Session(props, authenticator); } /** * 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.

* * In JDK 1.2, additional security Permission objects may be used to * control access to the default session. * * @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 * JavaMail 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 Session getDefaultInstance(Properties props, Authenticator authenticator) { if (defaultSession == null) 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; } /** * Set the debug setting for this Session. *

* Since debug can be turned on only after the Session * has been created, to turn on debugging in the Session * constructor, set the property mail.debug * to true. Subsequent calls to get/setDebug() have no * affect on and override the mail.debug * property. * @param debug Debug setting */ public void setDebug(boolean debug) { this.debug = debug; } /** * Get the debug setting for this Session. *

* Since debug can be turned on only after the Session * has been created, to turn on debugging in the Session * constructor, set the property mail.debug * to true. Subsequent calls to get/setDebug() have no * affect on and override the mail.debug * property. * @return current debug setting */ public boolean getDebug() { return debug; } /** * 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 Provider[] getProviders() { Provider[] _providers = new Provider[providers.size()]; providers.copyInto(_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 * @exception NoSuchProviderException If a provider for the given * protocol is not found. */ public 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 (debug) { System.out.println("DEBUG: mail."+protocol+ ".class property exists and points to " + _className); } _provider = (Provider)providersByClassName.get(_className); } if (_provider != null) { return _provider; } else { // returning currently default protocol in providersByProtocol _provider = (Provider)providersByProtocol.get(protocol); } if (_provider == null) { throw new NoSuchProviderException("No provider for " + protocol); } else { if (debug) { System.out.println("\nDEBUG: getProvider() returning " + _provider.toString()); } 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 * @exception NoSuchProviderException If the provider passed in * is invalid. */ public void setProvider(Provider provider) throws NoSuchProviderException { if (provider == null) { throw new NoSuchProviderException("Can't set null provider"); } providersByProtocol.put(provider.getProtocol(), provider); props.put("mail." + provider.getProtocol() + ".class", provider.getClassName()); } /** * Get a Store object that implements this user's desired Store * protcol. The mail.store.protocol property specifies the desired * protocol. If an appropriate Store object is not obtained, * NoSuchProviderException is thrown * * @return a Store object * @exception 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 * @return a Store object * @exception 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 * @see #getFolder(URLName) * @see javax.mail.URLName * @exception NoSuchProviderException If a provider for the given * URLName is not found. */ 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 * @exception 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 * @exception 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"); } try { return (Store) getService(provider, url); } catch (ClassCastException cce) { throw new NoSuchProviderException("incorrect 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 (ie * 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). So * providers of IP-based mail Stores should implement that * syntax for referring to Folders.

* * @param url URLName that represents the desired folder * @return Folder * @see #getStore(URLName) * @see javax.mail.URLName * @exception NoSuchProviderException If a provider for the given * URLName is not found. * @exception MessagingException if the Folder could not be * located or created. */ 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 * @exception NoSuchProviderException If the provider is not found. */ public Transport getTransport() throws NoSuchProviderException { return getTransport(getProperty("mail.transport.protocol")); } /** * Get a Transport object that implements the specified protocol. * If an appropriate Transport object cannot be obtained, null is * returned. * * @return a Transport object * @exception 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 * @see javax.mail.URLName * @exception NoSuchProviderException If a provider for the given * URLName is not found. */ 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 * @exception 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 to the * specified address type. * * @param address * @return A Transport object * @see javax.mail.Address * @exception NoSuchProviderException If provider for the * Address type is not found */ public Transport getTransport(Address address) throws NoSuchProviderException { String transportProtocol = (String)addressMap.get(address.getType()); if (transportProtocol == null) { throw new NoSuchProviderException("No provider for Address type: "+ address.getType()); } else { return getTransport(transportProtocol); } } /** * 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 * @exception 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"); } try { return (Transport) getService(provider, url); } catch (ClassCastException cce) { throw new NoSuchProviderException("incorrect 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) * @exception 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 Object getService(Provider provider, URLName url) 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); } Object service = null; // get the ClassLoader associated with the Authenticator ClassLoader cl; if (authenticator != null) cl = authenticator.getClass().getClassLoader(); else cl = this.getClass().getClassLoader(); // now load the class Class serviceClass = null; try { // First try the "application's" class loader. // This should eventually be replaced by // Thread.currentThread().getContextClassLoader(). serviceClass = cl.loadClass(provider.getClassName()); } catch (Exception ex1) { // 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()); } catch (Exception ex) { // Nothing worked, give up. if (debug) ex.printStackTrace(); throw new NoSuchProviderException(provider.getProtocol()); } } // construct an instance of the class try { Class[] c = {javax.mail.Session.class, javax.mail.URLName.class}; Constructor cons = serviceClass.getConstructor(c); Object[] o = {this, url}; service = cons.newInstance(o); } catch (Exception ex) { if (debug) ex.printStackTrace(); throw new NoSuchProviderException(provider.getProtocol()); } return service; } /** * 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. */ 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. * * @return the PasswordAuthentication corresponding to the URLName */ public PasswordAuthentication getPasswordAuthentication(URLName url) { return (PasswordAuthentication)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 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. * @return String that is the property value */ public String getProperty(String name) { return props.getProperty(name); } private void loadProviders(Class cl) { // load system-wide javamail.providers from the /lib dir // since we have an absolute path to the file, use FileInputStream InputStream javahomeProviderStream = null; try { String res = System.getProperty("java.home") + File.separator + "lib" + File.separator + "javamail.providers"; javahomeProviderStream = new BufferedInputStream(new FileInputStream(res)); if (javahomeProviderStream != null) { loadProvidersFromStream(javahomeProviderStream); javahomeProviderStream.close(); if (debug) pr("DEBUG: loaded providers in /lib"); } else { if (debug) pr("DEBUG: not loading system providers in /lib"); } } catch (FileNotFoundException fex) { /*ignore: don't load resource*/ if (debug) pr("DEBUG: not loading system providers in /lib"); } catch (IOException ioex) { /*ignore: don't load resource*/ if (debug) pr("DEBUG: " + ioex.getMessage()); } catch (SecurityException sex) { /*ignore: don't load resource*/ if (debug) pr("DEBUG: not loading system providers in /lib"); } // load the META-INF/javamail.providers file supplied by an application // first try loading the resource file using the app classloader // if it fails, try loading it as system res. InputStream appProviderStream = null; final String clappRes = "META-INF/javamail.providers"; final String appRes = "/" + clappRes; boolean anyLoaded = false; if (getResources != null) { try { Enumeration e; ClassLoader cld = cl.getClassLoader(); if (cld != null) e = (Enumeration)getResources.invoke( cld, new String[] { clappRes }); else e = (Enumeration)getSystemResources.invoke( cld, new String[] { clappRes }); while (e.hasMoreElements()) { URL url = (URL)e.nextElement(); appProviderStream = url.openStream(); if (appProviderStream != null) { try { loadProvidersFromStream(appProviderStream); anyLoaded = true; appProviderStream.close(); if (debug) pr("DEBUG: successfully loaded " + "optional custom providers from URL: " + url); } catch (IOException ioex) { if (debug) pr("DEBUG: " + ioex.getMessage()); } } else { if (debug) pr("DEBUG: not loading optional custom providers " + "from URL: " + url); } } } catch (Exception ex) { if (debug) pr("DEBUG: " + ex); } } // if failed to load anything, fall back to old technique, just in case if (!anyLoaded) { appProviderStream = cl.getResourceAsStream(appRes); if (appProviderStream != null) { try { loadProvidersFromStream(appProviderStream); appProviderStream.close(); if (debug) pr("DEBUG: successfully loaded " + "optional custom providers: " + appRes); } catch (IOException ioex) { if (debug) pr("DEBUG: " + ioex); } } else { if (debug) pr("DEBUG: not loading optional custom providers file: " + appRes); } } // load default META-INF/javamail.default.providers from mail.jar file InputStream defProviderStream = null; String defRes = "/META-INF/javamail.default.providers"; defProviderStream = cl.getResourceAsStream(defRes); if (defProviderStream != null) { try { loadProvidersFromStream(defProviderStream); defProviderStream.close(); if (debug) pr("DEBUG: successfully loaded default providers"); } catch (IOException ioex) { if (debug) pr("DEBUG: " + ioex.getMessage()); } } else { if (debug) pr("DEBUG: can't load default providers file" + defRes); } if (debug) { System.out.println("\nDEBUG: Tables of loaded providers"); // dump the output of the tables for debugging //pr("DEBUG: --- Providers Listed By Class Name --------"); //System.out.println("size " + providersByClassName.size()); pr("DEBUG: Providers Listed By Class Name: " + providersByClassName.toString()); //pr("\nDEBUG: --- Providers Listed By Protocol ---------"); //System.out.println("size " + providersByProtocol.size()); pr("DEBUG: Providers Listed By Protocol: " + providersByProtocol.toString()); } } private void loadProvidersFromStream(InputStream is) throws IOException { if (is != null) { LineInputStream lis = new LineInputStream(is); String currLine; // load and process one line at a time using LineInputStream while ((currLine = lis.readLine()) != null) { if (currLine.startsWith("#")) continue; 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) { if (debug) System.out.println("DEBUG: Bad provider entry: " + currLine); continue; } Provider provider = new Provider(type, protocol, className, vendor, version); // add the newly-created Provider to the lookup tables providers.addElement(provider); providersByClassName.put(className, provider); if (!providersByProtocol.containsKey(protocol)) { providersByProtocol.put(protocol, 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) { // load default META-INF/javamail.default.address.map from mail.jar // first try loading the resource file using the app classloader // if it fails, try loading it as system res. InputStream defAddressStream = null; String defRes = "/META-INF/javamail.default.address.map"; defAddressStream = cl.getResourceAsStream(defRes); if (defAddressStream != null) { try { addressMap.load(defAddressStream); defAddressStream.close(); } catch (IOException ioex) { /* ignore it */ } catch (SecurityException sex) { /* ignore it */ } } // load the META-INF/javamail.address.map file supplied by an app // first try loading the resource file using the app classloader // if it fails, try loading it as system res. InputStream appAddressStream = null; final String clappRes = "META-INF/javamail.address.map"; final String appRes = "/" + clappRes; boolean anyLoaded = false; if (getResources != null) { try { Enumeration e; ClassLoader cld = cl.getClassLoader(); if (cld != null) e = (Enumeration)getResources.invoke( cld, new String[] { clappRes }); else e = (Enumeration)getSystemResources.invoke( cld, new String[] { clappRes }); while (e.hasMoreElements()) { URL url = (URL)e.nextElement(); appAddressStream = url.openStream(); if (appAddressStream != null) { try { addressMap.load(appAddressStream); anyLoaded = true; appAddressStream.close(); if (debug) pr("DEBUG: successfully loaded " + "optional address map from URL: " + url); } catch (IOException ioex) { if (debug) pr("DEBUG: " + ioex.getMessage()); } } else { if (debug) pr("DEBUG: not loading optional address map " + "from URL: " + url); } } } catch (Exception ex) { if (debug) pr("DEBUG: " + ex); } } // if failed to load anything, fall back to old technique, just in case if (!anyLoaded) { appAddressStream = cl.getResourceAsStream(appRes); if (appAddressStream != null) { try { addressMap.load(appAddressStream); appAddressStream.close(); if (debug) pr("DEBUG: successfully loaded " + "optional address map: " + appRes); } catch (IOException ioex) { if (debug) pr("DEBUG: " + ioex); } } else { if (debug) pr("DEBUG: not loading optional address map file: " + appRes); } } // load system-wide javamail.address.map from the /lib dir // since we have an absolute path to the file, use FileInputStream InputStream javahomeAddressStream = null; try { String res = System.getProperty("java.home") + File.separator + "lib" + File.separator + "javamail.address.map"; javahomeAddressStream = new BufferedInputStream(new FileInputStream(res)); } catch (FileNotFoundException fex) { /*ignore: don't load resource*/ } catch (SecurityException sex) { /*ignore: don't load resource*/ } if (javahomeAddressStream != null) { try { addressMap.load(javahomeAddressStream); javahomeAddressStream.close(); } catch (IOException ioex) { /* ignore it */ } } } private static void pr(String str) { System.out.println(str); } }