diff -r fd16c54261b3 -r 90ce3da70b43 jdk/src/share/classes/java/net/URLClassLoader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/java/net/URLClassLoader.java Sat Dec 01 00:00:00 2007 +0000 @@ -0,0 +1,622 @@ +/* + * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package java.net; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.io.File; +import java.io.FilePermission; +import java.io.InputStream; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandlerFactory; +import java.util.Enumeration; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; +import java.util.jar.Manifest; +import java.util.jar.Attributes; +import java.util.jar.Attributes.Name; +import java.security.CodeSigner; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.security.AccessController; +import java.security.AccessControlContext; +import java.security.SecureClassLoader; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import sun.misc.Resource; +import sun.misc.URLClassPath; +import sun.net.www.ParseUtil; +import sun.security.util.SecurityConstants; + +/** + * This class loader is used to load classes and resources from a search + * path of URLs referring to both JAR files and directories. Any URL that + * ends with a '/' is assumed to refer to a directory. Otherwise, the URL + * is assumed to refer to a JAR file which will be opened as needed. + *

+ * The AccessControlContext of the thread that created the instance of + * URLClassLoader will be used when subsequently loading classes and + * resources. + *

+ * The classes that are loaded are by default granted permission only to + * access the URLs specified when the URLClassLoader was created. + * + * @author David Connelly + * @since 1.2 + */ +public class URLClassLoader extends SecureClassLoader { + /* The search path for classes and resources */ + URLClassPath ucp; + + /* The context to be used when loading classes and resources */ + private AccessControlContext acc; + + /** + * Constructs a new URLClassLoader for the given URLs. The URLs will be + * searched in the order specified for classes and resources after first + * searching in the specified parent class loader. Any URL that ends with + * a '/' is assumed to refer to a directory. Otherwise, the URL is assumed + * to refer to a JAR file which will be downloaded and opened as needed. + * + *

If there is a security manager, this method first + * calls the security manager's checkCreateClassLoader method + * to ensure creation of a class loader is allowed. + * + * @param urls the URLs from which to load classes and resources + * @param parent the parent class loader for delegation + * @exception SecurityException if a security manager exists and its + * checkCreateClassLoader method doesn't allow + * creation of a class loader. + * @see SecurityManager#checkCreateClassLoader + */ + public URLClassLoader(URL[] urls, ClassLoader parent) { + super(parent); + // this is to make the stack depth consistent with 1.1 + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkCreateClassLoader(); + } + ucp = new URLClassPath(urls); + acc = AccessController.getContext(); + } + + /** + * Constructs a new URLClassLoader for the specified URLs using the + * default delegation parent ClassLoader. The URLs will + * be searched in the order specified for classes and resources after + * first searching in the parent class loader. Any URL that ends with + * a '/' is assumed to refer to a directory. Otherwise, the URL is + * assumed to refer to a JAR file which will be downloaded and opened + * as needed. + * + *

If there is a security manager, this method first + * calls the security manager's checkCreateClassLoader method + * to ensure creation of a class loader is allowed. + * + * @param urls the URLs from which to load classes and resources + * + * @exception SecurityException if a security manager exists and its + * checkCreateClassLoader method doesn't allow + * creation of a class loader. + * @see SecurityManager#checkCreateClassLoader + */ + public URLClassLoader(URL[] urls) { + super(); + // this is to make the stack depth consistent with 1.1 + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkCreateClassLoader(); + } + ucp = new URLClassPath(urls); + acc = AccessController.getContext(); + } + + /** + * Constructs a new URLClassLoader for the specified URLs, parent + * class loader, and URLStreamHandlerFactory. The parent argument + * will be used as the parent class loader for delegation. The + * factory argument will be used as the stream handler factory to + * obtain protocol handlers when creating new jar URLs. + * + *

If there is a security manager, this method first + * calls the security manager's checkCreateClassLoader method + * to ensure creation of a class loader is allowed. + * + * @param urls the URLs from which to load classes and resources + * @param parent the parent class loader for delegation + * @param factory the URLStreamHandlerFactory to use when creating URLs + * + * @exception SecurityException if a security manager exists and its + * checkCreateClassLoader method doesn't allow + * creation of a class loader. + * @see SecurityManager#checkCreateClassLoader + */ + public URLClassLoader(URL[] urls, ClassLoader parent, + URLStreamHandlerFactory factory) { + super(parent); + // this is to make the stack depth consistent with 1.1 + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkCreateClassLoader(); + } + ucp = new URLClassPath(urls, factory); + acc = AccessController.getContext(); + } + + /** + * Appends the specified URL to the list of URLs to search for + * classes and resources. + *

+ * If the URL specified is null or is already in the + * list of URLs, then invoking this method has no effect. + * + * @param url the URL to be added to the search path of URLs + */ + protected void addURL(URL url) { + ucp.addURL(url); + } + + /** + * Returns the search path of URLs for loading classes and resources. + * This includes the original list of URLs specified to the constructor, + * along with any URLs subsequently appended by the addURL() method. + * @return the search path of URLs for loading classes and resources. + */ + public URL[] getURLs() { + return ucp.getURLs(); + } + + /** + * Finds and loads the class with the specified name from the URL search + * path. Any URLs referring to JAR files are loaded and opened as needed + * until the class is found. + * + * @param name the name of the class + * @return the resulting class + * @exception ClassNotFoundException if the class could not be found + */ + protected Class findClass(final String name) + throws ClassNotFoundException + { + try { + return (Class) + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws ClassNotFoundException { + String path = name.replace('.', '/').concat(".class"); + Resource res = ucp.getResource(path, false); + if (res != null) { + try { + return defineClass(name, res); + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } else { + throw new ClassNotFoundException(name); + } + } + }, acc); + } catch (java.security.PrivilegedActionException pae) { + throw (ClassNotFoundException) pae.getException(); + } + } + + /* + * Defines a Class using the class bytes obtained from the specified + * Resource. The resulting Class must be resolved before it can be + * used. + */ + private Class defineClass(String name, Resource res) throws IOException { + int i = name.lastIndexOf('.'); + URL url = res.getCodeSourceURL(); + if (i != -1) { + String pkgname = name.substring(0, i); + // Check if package already loaded. + Package pkg = getPackage(pkgname); + Manifest man = res.getManifest(); + if (pkg != null) { + // Package found, so check package sealing. + if (pkg.isSealed()) { + // Verify that code source URL is the same. + if (!pkg.isSealed(url)) { + throw new SecurityException( + "sealing violation: package " + pkgname + " is sealed"); + } + + } else { + // Make sure we are not attempting to seal the package + // at this code source URL. + if ((man != null) && isSealed(pkgname, man)) { + throw new SecurityException( + "sealing violation: can't seal package " + pkgname + + ": already loaded"); + } + } + } else { + if (man != null) { + definePackage(pkgname, man, url); + } else { + definePackage(pkgname, null, null, null, null, null, null, null); + } + } + } + // Now read the class bytes and define the class + java.nio.ByteBuffer bb = res.getByteBuffer(); + if (bb != null) { + // Use (direct) ByteBuffer: + CodeSigner[] signers = res.getCodeSigners(); + CodeSource cs = new CodeSource(url, signers); + return defineClass(name, bb, cs); + } else { + byte[] b = res.getBytes(); + // must read certificates AFTER reading bytes. + CodeSigner[] signers = res.getCodeSigners(); + CodeSource cs = new CodeSource(url, signers); + return defineClass(name, b, 0, b.length, cs); + } + } + + /** + * Defines a new package by name in this ClassLoader. The attributes + * contained in the specified Manifest will be used to obtain package + * version and sealing information. For sealed packages, the additional + * URL specifies the code source URL from which the package was loaded. + * + * @param name the package name + * @param man the Manifest containing package version and sealing + * information + * @param url the code source url for the package, or null if none + * @exception IllegalArgumentException if the package name duplicates + * an existing package either in this class loader or one + * of its ancestors + * @return the newly defined Package object + */ + protected Package definePackage(String name, Manifest man, URL url) + throws IllegalArgumentException + { + String path = name.replace('.', '/').concat("/"); + String specTitle = null, specVersion = null, specVendor = null; + String implTitle = null, implVersion = null, implVendor = null; + String sealed = null; + URL sealBase = null; + + Attributes attr = man.getAttributes(path); + if (attr != null) { + specTitle = attr.getValue(Name.SPECIFICATION_TITLE); + specVersion = attr.getValue(Name.SPECIFICATION_VERSION); + specVendor = attr.getValue(Name.SPECIFICATION_VENDOR); + implTitle = attr.getValue(Name.IMPLEMENTATION_TITLE); + implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION); + implVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR); + sealed = attr.getValue(Name.SEALED); + } + attr = man.getMainAttributes(); + if (attr != null) { + if (specTitle == null) { + specTitle = attr.getValue(Name.SPECIFICATION_TITLE); + } + if (specVersion == null) { + specVersion = attr.getValue(Name.SPECIFICATION_VERSION); + } + if (specVendor == null) { + specVendor = attr.getValue(Name.SPECIFICATION_VENDOR); + } + if (implTitle == null) { + implTitle = attr.getValue(Name.IMPLEMENTATION_TITLE); + } + if (implVersion == null) { + implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION); + } + if (implVendor == null) { + implVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR); + } + if (sealed == null) { + sealed = attr.getValue(Name.SEALED); + } + } + if ("true".equalsIgnoreCase(sealed)) { + sealBase = url; + } + return definePackage(name, specTitle, specVersion, specVendor, + implTitle, implVersion, implVendor, sealBase); + } + + /* + * Returns true if the specified package name is sealed according to the + * given manifest. + */ + private boolean isSealed(String name, Manifest man) { + String path = name.replace('.', '/').concat("/"); + Attributes attr = man.getAttributes(path); + String sealed = null; + if (attr != null) { + sealed = attr.getValue(Name.SEALED); + } + if (sealed == null) { + if ((attr = man.getMainAttributes()) != null) { + sealed = attr.getValue(Name.SEALED); + } + } + return "true".equalsIgnoreCase(sealed); + } + + /** + * Finds the resource with the specified name on the URL search path. + * + * @param name the name of the resource + * @return a URL for the resource, or null + * if the resource could not be found. + */ + public URL findResource(final String name) { + /* + * The same restriction to finding classes applies to resources + */ + URL url = + (URL) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return ucp.findResource(name, true); + } + }, acc); + + return url != null ? ucp.checkURL(url) : null; + } + + /** + * Returns an Enumeration of URLs representing all of the resources + * on the URL search path having the specified name. + * + * @param name the resource name + * @exception IOException if an I/O exception occurs + * @return an Enumeration of URLs + */ + public Enumeration findResources(final String name) + throws IOException + { + final Enumeration e = ucp.findResources(name, true); + + return new Enumeration() { + private URL url = null; + + private boolean next() { + if (url != null) { + return true; + } + do { + URL u = (URL) + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + if (!e.hasMoreElements()) + return null; + return e.nextElement(); + } + }, acc); + if (u == null) + break; + url = ucp.checkURL(u); + } while (url == null); + return url != null; + } + + public URL nextElement() { + if (!next()) { + throw new NoSuchElementException(); + } + URL u = url; + url = null; + return u; + } + + public boolean hasMoreElements() { + return next(); + } + }; + } + + /** + * Returns the permissions for the given codesource object. + * The implementation of this method first calls super.getPermissions + * and then adds permissions based on the URL of the codesource. + *

+ * If the protocol of this URL is "jar", then the permission granted + * is based on the permission that is required by the URL of the Jar + * file. + *

+ * If the protocol is "file" and there is an authority component, then + * permission to connect to and accept connections from that authority + * may be granted. If the protocol is "file" + * and the path specifies a file, then permission to read that + * file is granted. If protocol is "file" and the path is + * a directory, permission is granted to read all files + * and (recursively) all files and subdirectories contained in + * that directory. + *

+ * If the protocol is not "file", then permission + * to connect to and accept connections from the URL's host is granted. + * @param codesource the codesource + * @return the permissions granted to the codesource + */ + protected PermissionCollection getPermissions(CodeSource codesource) + { + PermissionCollection perms = super.getPermissions(codesource); + + URL url = codesource.getLocation(); + + Permission p; + URLConnection urlConnection; + + try { + urlConnection = url.openConnection(); + p = urlConnection.getPermission(); + } catch (java.io.IOException ioe) { + p = null; + urlConnection = null; + } + + if (p instanceof FilePermission) { + // if the permission has a separator char on the end, + // it means the codebase is a directory, and we need + // to add an additional permission to read recursively + String path = p.getName(); + if (path.endsWith(File.separator)) { + path += "-"; + p = new FilePermission(path, SecurityConstants.FILE_READ_ACTION); + } + } else if ((p == null) && (url.getProtocol().equals("file"))) { + String path = url.getFile().replace('/', File.separatorChar); + path = ParseUtil.decode(path); + if (path.endsWith(File.separator)) + path += "-"; + p = new FilePermission(path, SecurityConstants.FILE_READ_ACTION); + } else { + /** + * Not loading from a 'file:' URL so we want to give the class + * permission to connect to and accept from the remote host + * after we've made sure the host is the correct one and is valid. + */ + URL locUrl = url; + if (urlConnection instanceof JarURLConnection) { + locUrl = ((JarURLConnection)urlConnection).getJarFileURL(); + } + String host = locUrl.getHost(); + if (host != null && (host.length() > 0)) + p = new SocketPermission(host, + SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION); + } + + // make sure the person that created this class loader + // would have this permission + + if (p != null) { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + final Permission fp = p; + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() throws SecurityException { + sm.checkPermission(fp); + return null; + } + }, acc); + } + perms.add(p); + } + return perms; + } + + /** + * Creates a new instance of URLClassLoader for the specified + * URLs and parent class loader. If a security manager is + * installed, the loadClass method of the URLClassLoader + * returned by this method will invoke the + * SecurityManager.checkPackageAccess method before + * loading the class. + * + * @param urls the URLs to search for classes and resources + * @param parent the parent class loader for delegation + * @return the resulting class loader + */ + public static URLClassLoader newInstance(final URL[] urls, + final ClassLoader parent) { + // Save the caller's context + AccessControlContext acc = AccessController.getContext(); + // Need a privileged block to create the class loader + URLClassLoader ucl = + (URLClassLoader) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return new FactoryURLClassLoader(urls, parent); + } + }); + // Now set the context on the loader using the one we saved, + // not the one inside the privileged block... + ucl.acc = acc; + return ucl; + } + + /** + * Creates a new instance of URLClassLoader for the specified + * URLs and default parent class loader. If a security manager is + * installed, the loadClass method of the URLClassLoader + * returned by this method will invoke the + * SecurityManager.checkPackageAccess before + * loading the class. + * + * @param urls the URLs to search for classes and resources + * @return the resulting class loader + */ + public static URLClassLoader newInstance(final URL[] urls) { + // Save the caller's context + AccessControlContext acc = AccessController.getContext(); + // Need a privileged block to create the class loader + URLClassLoader ucl = (URLClassLoader) + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return new FactoryURLClassLoader(urls); + } + }); + + // Now set the context on the loader using the one we saved, + // not the one inside the privileged block... + ucl.acc = acc; + return ucl; + } + + static { + sun.misc.SharedSecrets.setJavaNetAccess ( + new sun.misc.JavaNetAccess() { + public URLClassPath getURLClassPath (URLClassLoader u) { + return u.ucp; + } + } + ); + } +} + +final class FactoryURLClassLoader extends URLClassLoader { + + FactoryURLClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + FactoryURLClassLoader(URL[] urls) { + super(urls); + } + + public final synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + // First check if we have permission to access the package. This + // should go away once we've added support for exported packages. + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + int i = name.lastIndexOf('.'); + if (i != -1) { + sm.checkPackageAccess(name.substring(0, i)); + } + } + return super.loadClass(name, resolve); + } +}