/*
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
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.
SharedSecrets;
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.
* <p>
* The AccessControlContext of the thread that created the instance of
* URLClassLoader will be used when subsequently loading classes and
* resources.
* <p>
* 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.
*
* <p>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();
}
this.
acc =
AccessController.
getContext();
ucp = new
URLClassPath(
urls,
acc);
}
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();
}
this.
acc =
acc;
ucp = new
URLClassPath(
urls,
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.
*
* <p>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();
}
this.
acc =
AccessController.
getContext();
ucp = new
URLClassPath(
urls,
acc);
}
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();
}
this.
acc =
acc;
ucp = new
URLClassPath(
urls,
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.
*
* <p>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.
* @exception NullPointerException if {@code urls} is {@code null}.
* @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<
Closeable,
Void>
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.
*
* <p> The search order is described in the documentation for {@link
* #getResource(String)}. </p>
*
* @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.
* <p>
* 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.
* <p>
* 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.
* <p>
* @exception 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.
*
* @exception 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<
IOException>
errors =
ucp.
closeLoaders();
// now close any remaining streams.
synchronized (
closeables) {
Set<
Closeable>
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.
* <p>
* 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<
Class<?>>() {
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);
sun.misc.
PerfCounter.
getReadClassBytesTime().
addElapsedTimeFrom(
t0);
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);
sun.misc.
PerfCounter.
getReadClassBytesTime().
addElapsedTimeFrom(
t0);
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 specTitle = null,
specVersion = null,
specVendor = null;
String implTitle = null,
implVersion = null,
implVendor = null;
String sealed = null;
URL sealBase = null;
Attributes attr =
SharedSecrets.
javaUtilJarAccess()
.
getTrustedAttributes(
man,
name.
replace('.', '/').
concat("/"));
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.
*
* @throws SecurityException if the package name is untrusted in the manifest
*/
private boolean
isSealed(
String name,
Manifest man) {
Attributes attr =
SharedSecrets.
javaUtilJarAccess()
.
getTrustedAttributes(
man,
name.
replace('.', '/').
concat("/"));
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<
URL>() {
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<
URL>
findResources(final
String name)
throws
IOException
{
final
Enumeration<
URL>
e =
ucp.
findResources(
name, true);
return new
Enumeration<
URL>() {
private
URL url = null;
private boolean
next() {
if (
url != null) {
return true;
}
do {
URL u =
AccessController.
doPrivileged(
new
PrivilegedAction<
URL>() {
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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<
Void>() {
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<
URLClassLoader>() {
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<
URLClassLoader>() {
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);
}
}