/* * Copyright (c) 1997, 2015, Oracle and/or its affiliates. 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.net; import java.io.Closeable; import java.io.File; import java.io.FilePermission; import java.io.IOException; import java.io.InputStream; import java.security.AccessControlContext; import java.security.AccessController; import java.security.CodeSigner; import java.security.CodeSource; import java.security.Permission; import java.security.PermissionCollection; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.security.SecureClassLoader; import java.util.Enumeration; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.WeakHashMap; import java.util.jar.Attributes; import java.util.jar.Attributes.Name; import java.util.jar.JarFile; import java.util.jar.Manifest; 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 implements Closeable { /* The search path for classes and resources */ private final URLClassPath ucp; /* The context to be used when loading classes and resources */ private final 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 {@code 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 * {@code checkCreateClassLoader} method doesn't allow * creation of a class loader. * @exception NullPointerException if {@code urls} is {@code null}. * @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); this.acc = AccessController.getContext(); } URLClassLoader(URL[] urls, ClassLoader parent, AccessControlContext acc) { 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); this.acc = acc; } /** * Constructs a new URLClassLoader for the specified URLs using the * default delegation parent {@code 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 {@code 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 * {@code checkCreateClassLoader} method doesn't allow * creation of a class loader. * @exception NullPointerException if {@code urls} is {@code null}. * @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); this.acc = AccessController.getContext(); } URLClassLoader(URL[] urls, AccessControlContext acc) { 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); this.acc = acc; } /** * 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 {@code 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 * {@code 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(); } acc = AccessController.getContext(); ucp = new URLClassPath(urls, factory, acc); } /* A map (used as a set) to keep track of closeable local resources * (either JarFiles or FileInputStreams). We don't care about * Http resources since they don't need to be closed. * * If the resource is coming from a jar file * we keep a (weak) reference to the JarFile object which can * be closed if URLClassLoader.close() called. Due to jar file * caching there will typically be only one JarFile object * per underlying jar file. * * For file resources, which is probably a less common situation * we have to keep a weak reference to each stream. */ private WeakHashMap closeables = new WeakHashMap<>(); /** * Returns an input stream for reading the specified resource. * If this loader is closed, then any resources opened by this method * will be closed. * *

The search order is described in the documentation for {@link * #getResource(String)}.

* * @param name * The resource name * * @return An input stream for reading the resource, or {@code null} * if the resource could not be found * * @since 1.7 */ public InputStream getResourceAsStream(String name) { URL url = getResource(name); try { if (url == null) { return null; } URLConnection urlc = url.openConnection(); InputStream is = urlc.getInputStream(); if (urlc instanceof JarURLConnection) { JarURLConnection juc = (JarURLConnection)urlc; JarFile jar = juc.getJarFile(); synchronized (closeables) { if (!closeables.containsKey(jar)) { closeables.put(jar, null); } } } else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) { synchronized (closeables) { closeables.put(is, null); } } return is; } catch (IOException e) { return null; } } /** * Closes this URLClassLoader, so that it can no longer be used to load * new classes or resources that are defined by this loader. * Classes and resources defined by any of this loader's parents in the * delegation hierarchy are still accessible. Also, any classes or resources * that are already loaded, are still accessible. *

* In the case of jar: and file: URLs, it also closes any files * that were opened by it. If another thread is loading a * class when the {@code close} method is invoked, then the result of * that load is undefined. *

* The method makes a best effort attempt to close all opened files, * by catching {@link IOException}s internally. Unchecked exceptions * and errors are not caught. Calling close on an already closed * loader has no effect. *

* @throws IOException if closing any file opened by this class loader * resulted in an IOException. Any such exceptions are caught internally. * If only one is caught, then it is re-thrown. If more than one exception * is caught, then the second and following exceptions are added * as suppressed exceptions of the first one caught, which is then re-thrown. * * @throws SecurityException if a security manager is set, and it denies * {@link RuntimePermission}{@code ("closeClassLoader")} * * @since 1.7 */ public void close() throws IOException { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPermission(new RuntimePermission("closeClassLoader")); } List errors = ucp.closeLoaders(); // now close any remaining streams. synchronized (closeables) { Set keys = closeables.keySet(); for (Closeable c : keys) { try { c.close(); } catch (IOException ioex) { errors.add(ioex); } } closeables.clear(); } if (errors.isEmpty()) { return; } IOException firstex = errors.remove(0); // Suppress any remaining exceptions for (IOException error: errors) { firstex.addSuppressed(error); } throw firstex; } /** * Appends the specified URL to the list of URLs to search for * classes and resources. *

* If the URL specified is {@code null} or is already in the * list of URLs, or if this loader is closed, 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, * or if the loader is closed. * @exception NullPointerException if {@code name} is {@code null}. */ protected Class findClass(final String name) throws ClassNotFoundException { final Class result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction>() { public Class 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 { return null; } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; } /* * Retrieve the package using the specified package name. * If non-null, verify the package using the specified code * source and manifest. */ private Package getAndVerifyPackage(String pkgname, Manifest man, URL url) { Package pkg = getPackage(pkgname); 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"); } } } return pkg; } // Also called by VM to define Package for classes loaded from the CDS // archive private void definePackageInternal(String pkgname, Manifest man, URL url) { if (getAndVerifyPackage(pkgname, man, url) == null) { try { if (man != null) { definePackage(pkgname, man, url); } else { definePackage(pkgname, null, null, null, null, null, null, null); } } catch (IllegalArgumentException iae) { // parallel-capable class loaders: re-verify in case of a // race condition if (getAndVerifyPackage(pkgname, man, url) == null) { // Should never happen throw new AssertionError("Cannot find package " + pkgname); } } } } /* * 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 { long t0 = System.nanoTime(); int i = name.lastIndexOf('.'); URL url = res.getCodeSourceURL(); if (i != -1) { String pkgname = name.substring(0, i); // Check if package already loaded. Manifest man = res.getManifest(); definePackageInternal(pkgname, man, url); } // 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 {@code URL} for the resource, or {@code null} * if the resource could not be found, or if the loader is closed. */ public URL findResource(final String name) { /* * The same restriction to finding classes applies to resources */ URL url = AccessController.doPrivileged( new PrivilegedAction() { public URL 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 {@code Enumeration} of {@code URL}s * If the loader is closed, the Enumeration will be empty. */ 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 = AccessController.doPrivileged( new PrivilegedAction() { public URL 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 * @exception NullPointerException if {@code codesource} is {@code null}. * @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 Void 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 {@code loadClass} method of the URLClassLoader * returned by this method will invoke the * {@code 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 * @exception NullPointerException if {@code urls} is {@code null}. * @return the resulting class loader */ public static URLClassLoader newInstance(final URL[] urls, final ClassLoader parent) { // Save the caller's context final AccessControlContext acc = AccessController.getContext(); // Need a privileged block to create the class loader URLClassLoader ucl = AccessController.doPrivileged( new PrivilegedAction() { public URLClassLoader run() { return new FactoryURLClassLoader(urls, parent, 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 {@code loadClass} method of the URLClassLoader * returned by this method will invoke the * {@code SecurityManager.checkPackageAccess} before * loading the class. * * @param urls the URLs to search for classes and resources * @exception NullPointerException if {@code urls} is {@code null}. * @return the resulting class loader */ public static URLClassLoader newInstance(final URL[] urls) { // Save the caller's context final AccessControlContext acc = AccessController.getContext(); // Need a privileged block to create the class loader URLClassLoader ucl = AccessController.doPrivileged( new PrivilegedAction() { public URLClassLoader run() { return new FactoryURLClassLoader(urls, acc); } }); return ucl; } static { /*sun.misc.SharedSecrets.setJavaNetAccess ( new sun.misc.JavaNetAccess() { public URLClassPath getURLClassPath (URLClassLoader u) { return u.ucp; } public String getOriginalHostName(InetAddress ia) { return ia.holder.getOriginalHostName(); } } );*/ ClassLoader.registerAsParallelCapable(); } } final class FactoryURLClassLoader extends URLClassLoader { static { ClassLoader.registerAsParallelCapable(); } FactoryURLClassLoader(URL[] urls, ClassLoader parent, AccessControlContext acc) { super(urls, parent, acc); } FactoryURLClassLoader(URL[] urls, AccessControlContext acc) { super(urls, acc); } public final 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); } }