001: /*
002: * @(#)URLClassLoader.java 1.81 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package java.net;
029:
030: import java.lang.reflect.Method;
031: import java.lang.reflect.Modifier;
032: import java.io.File;
033: import java.io.FilePermission;
034: import java.io.InputStream;
035: import java.io.IOException;
036: import java.net.URL;
037: import java.net.URLConnection;
038: import java.net.URLStreamHandlerFactory;
039: import java.util.Enumeration;
040: import java.util.NoSuchElementException;
041: import java.util.StringTokenizer;
042: import java.util.jar.Manifest;
043: import java.util.jar.Attributes;
044: import java.util.jar.Attributes.Name;
045: import java.security.PrivilegedAction;
046: import java.security.PrivilegedExceptionAction;
047: import java.security.AccessController;
048: import java.security.AccessControlContext;
049: import java.security.SecureClassLoader;
050: import java.security.CodeSource;
051: import java.security.Permission;
052: import java.security.PermissionCollection;
053: import sun.misc.Resource;
054: import sun.misc.URLClassPath;
055: import sun.net.www.ParseUtil;
056: import sun.security.util.SecurityConstants;
057:
058: /**
059: * This class loader is used to load classes and resources from a search
060: * path of URLs referring to both JAR files and directories. Any URL that
061: * ends with a '/' is assumed to refer to a directory. Otherwise, the URL
062: * is assumed to refer to a JAR file which will be opened as needed.
063: * <p>
064: * The AccessControlContext of the thread that created the instance of
065: * URLClassLoader will be used when subsequently loading classes and
066: * resources.
067: * <p>
068: * The classes that are loaded are by default granted permission only to
069: * access the URLs specified when the URLClassLoader was created.
070: *
071: * @author David Connelly
072: * @version 1.72 10/17/00
073: * @since 1.2
074: */
075: public class URLClassLoader extends SecureClassLoader {
076: /* The search path for classes and resources */
077: private URLClassPath ucp;
078:
079: /* The context to be used when loading classes and resources */
080: private AccessControlContext acc;
081:
082: /**
083: * Constructs a new URLClassLoader for the given URLs. The URLs will be
084: * searched in the order specified for classes and resources after first
085: * searching in the specified parent class loader. Any URL that ends with
086: * a '/' is assumed to refer to a directory. Otherwise, the URL is assumed
087: * to refer to a JAR file which will be downloaded and opened as needed.
088: *
089: * <p>If there is a security manager, this method first
090: * calls the security manager's <code>checkCreateClassLoader</code> method
091: * to ensure creation of a class loader is allowed.
092: *
093: * @param urls the URLs from which to load classes and resources
094: * @param parent the parent class loader for delegation
095: * @exception SecurityException if a security manager exists and its
096: * <code>checkCreateClassLoader</code> method doesn't allow
097: * creation of a class loader.
098: * @see SecurityManager#checkCreateClassLoader
099: */
100: public URLClassLoader(URL[] urls, ClassLoader parent) {
101: super (parent);
102: // this is to make the stack depth consistent with 1.1
103: SecurityManager security = System.getSecurityManager();
104: if (security != null) {
105: security.checkCreateClassLoader();
106: }
107: ucp = new URLClassPath(urls);
108: acc = AccessController.getContext();
109: }
110:
111: /**
112: * Constructs a new URLClassLoader for the specified URLs using the
113: * default delegation parent <code>ClassLoader</code>. The URLs will
114: * be searched in the order specified for classes and resources after
115: * first searching in the parent class loader. Any URL that ends with
116: * a '/' is assumed to refer to a directory. Otherwise, the URL is
117: * assumed to refer to a JAR file which will be downloaded and opened
118: * as needed.
119: *
120: * <p>If there is a security manager, this method first
121: * calls the security manager's <code>checkCreateClassLoader</code> method
122: * to ensure creation of a class loader is allowed.
123: *
124: * @param urls the URLs from which to load classes and resources
125: *
126: * @exception SecurityException if a security manager exists and its
127: * <code>checkCreateClassLoader</code> method doesn't allow
128: * creation of a class loader.
129: * @see SecurityManager#checkCreateClassLoader
130: */
131: public URLClassLoader(URL[] urls) {
132: super ();
133: // this is to make the stack depth consistent with 1.1
134: SecurityManager security = System.getSecurityManager();
135: if (security != null) {
136: security.checkCreateClassLoader();
137: }
138: ucp = new URLClassPath(urls);
139: acc = AccessController.getContext();
140: }
141:
142: /**
143: * Constructs a new URLClassLoader for the specified URLs, parent
144: * class loader, and URLStreamHandlerFactory. The parent argument
145: * will be used as the parent class loader for delegation. The
146: * factory argument will be used as the stream handler factory to
147: * obtain protocol handlers when creating new URLs.
148: *
149: * <p>If there is a security manager, this method first
150: * calls the security manager's <code>checkCreateClassLoader</code> method
151: * to ensure creation of a class loader is allowed.
152: *
153: * @param urls the URLs from which to load classes and resources
154: * @param parent the parent class loader for delegation
155: * @param factory the URLStreamHandlerFactory to use when creating URLs
156: *
157: * @exception SecurityException if a security manager exists and its
158: * <code>checkCreateClassLoader</code> method doesn't allow
159: * creation of a class loader.
160: * @see SecurityManager#checkCreateClassLoader
161: */
162: public URLClassLoader(URL[] urls, ClassLoader parent,
163: URLStreamHandlerFactory factory) {
164: super (parent);
165: // this is to make the stack depth consistent with 1.1
166: SecurityManager security = System.getSecurityManager();
167: if (security != null) {
168: security.checkCreateClassLoader();
169: }
170: ucp = new URLClassPath(urls, factory);
171: acc = AccessController.getContext();
172: }
173:
174: /**
175: * Appends the specified URL to the list of URLs to search for
176: * classes and resources.
177: *
178: * @param url the URL to be added to the search path of URLs
179: */
180: protected void addURL(URL url) {
181: ucp.addURL(url);
182: }
183:
184: /**
185: * Returns the search path of URLs for loading classes and resources.
186: * This includes the original list of URLs specified to the constructor,
187: * along with any URLs subsequently appended by the addURL() method.
188: * @return the search path of URLs for loading classes and resources.
189: */
190: public URL[] getURLs() {
191: return ucp.getURLs();
192: }
193:
194: /**
195: * Finds and loads the class with the specified name from the URL search
196: * path. Any URLs referring to JAR files are loaded and opened as needed
197: * until the class is found.
198: *
199: * @param name the name of the class
200: * @return the resulting class
201: * @exception ClassNotFoundException if the class could not be found
202: */
203: protected Class findClass(final String name)
204: throws ClassNotFoundException {
205: Class retValue = null;
206: try {
207: retValue = (Class) AccessController.doPrivileged(
208: new PrivilegedExceptionAction() {
209: /*
210: * Formerly, this method would throw an exception if
211: * resource res == null. This would be wrapped in
212: * a PrivilegedActionException then unwrapped in
213: * the body of findClass (as it still can be).
214: * Fewer exceptions need to be allocated if we return null.
215: */
216: public Object run()
217: throws ClassNotFoundException {
218: String path = name.replace('.', '/')
219: .concat(".class");
220: Resource res = ucp.getResource(path, false);
221: if (res != null) {
222: try {
223: return defineClass(name, res);
224: } catch (IOException e) {
225: throw new ClassNotFoundException(
226: name, e);
227: }
228: }
229: return null;
230: }
231: }, acc);
232: } catch (java.security.PrivilegedActionException pae) {
233: throw (ClassNotFoundException) pae.getException();
234: }
235: if (retValue == null) {
236: throw new ClassNotFoundException(name);
237: }
238: return retValue;
239: }
240:
241: /*
242: * Defines a Class using the class bytes obtained from the specified
243: * Resource. The resulting Class must be resolved before it can be
244: * used.
245: */
246: private Class defineClass(String name, Resource res)
247: throws IOException {
248: int i = name.lastIndexOf('.');
249: URL url = res.getCodeSourceURL();
250: if (i != -1) {
251: String pkgname = name.substring(0, i);
252: // Check if package already loaded.
253: Package pkg = getPackage(pkgname);
254: Manifest man = res.getManifest();
255: if (pkg != null) {
256: // Package found, so check package sealing.
257: if (pkg.isSealed()) {
258: // Verify that code source URL is the same.
259: if (!pkg.isSealed(url)) {
260: throw new SecurityException(
261: "sealing violation: package " + pkgname
262: + " is sealed");
263: }
264:
265: } else {
266: // Make sure we are not attempting to seal the package
267: // at this code source URL.
268: if ((man != null) && isSealed(pkgname, man)) {
269: throw new SecurityException(
270: "sealing violation: can't seal package "
271: + pkgname + ": already loaded");
272: }
273: }
274: } else {
275: if (man != null) {
276: definePackage(pkgname, man, url);
277: } else {
278: definePackage(pkgname, null, null, null, null,
279: null, null, null);
280: }
281: }
282: }
283: // Now read the class bytes and define the class
284: byte[] b = res.getBytes();
285: java.security.cert.Certificate[] certs = res.getCertificates();
286: CodeSource cs = new CodeSource(url, certs);
287: return defineClass(name, b, 0, b.length, cs);
288: }
289:
290: /**
291: * Defines a new package by name in this ClassLoader. The attributes
292: * contained in the specified Manifest will be used to obtain package
293: * version and sealing information. For sealed packages, the additional
294: * URL specifies the code source URL from which the package was loaded.
295: *
296: * @param name the package name
297: * @param man the Manifest containing package version and sealing
298: * information
299: * @param url the code source url for the package, or null if none
300: * @exception IllegalArgumentException if the package name duplicates
301: * an existing package either in this class loader or one
302: * of its ancestors
303: * @return the newly defined Package object
304: */
305: protected Package definePackage(String name, Manifest man, URL url)
306: throws IllegalArgumentException {
307: String path = name.replace('.', '/').concat("/");
308: String specTitle = null, specVersion = null, specVendor = null;
309: String implTitle = null, implVersion = null, implVendor = null;
310: String sealed = null;
311: URL sealBase = null;
312:
313: Attributes attr = man.getAttributes(path);
314: if (attr != null) {
315: specTitle = attr.getValue(Name.SPECIFICATION_TITLE);
316: specVersion = attr.getValue(Name.SPECIFICATION_VERSION);
317: specVendor = attr.getValue(Name.SPECIFICATION_VENDOR);
318: implTitle = attr.getValue(Name.IMPLEMENTATION_TITLE);
319: implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION);
320: implVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR);
321: sealed = attr.getValue(Name.SEALED);
322: }
323: attr = man.getMainAttributes();
324: if (attr != null) {
325: if (specTitle == null) {
326: specTitle = attr.getValue(Name.SPECIFICATION_TITLE);
327: }
328: if (specVersion == null) {
329: specVersion = attr.getValue(Name.SPECIFICATION_VERSION);
330: }
331: if (specVendor == null) {
332: specVendor = attr.getValue(Name.SPECIFICATION_VENDOR);
333: }
334: if (implTitle == null) {
335: implTitle = attr.getValue(Name.IMPLEMENTATION_TITLE);
336: }
337: if (implVersion == null) {
338: implVersion = attr
339: .getValue(Name.IMPLEMENTATION_VERSION);
340: }
341: if (implVendor == null) {
342: implVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR);
343: }
344: if (sealed == null) {
345: sealed = attr.getValue(Name.SEALED);
346: }
347: }
348: if ("true".equalsIgnoreCase(sealed)) {
349: sealBase = url;
350: }
351: return definePackage(name, specTitle, specVersion, specVendor,
352: implTitle, implVersion, implVendor, sealBase);
353: }
354:
355: /*
356: * Returns true if the specified package name is sealed according to the
357: * given manifest.
358: */
359: private boolean isSealed(String name, Manifest man) {
360: String path = name.replace('.', '/').concat("/");
361: Attributes attr = man.getAttributes(path);
362: String sealed = null;
363: if (attr != null) {
364: sealed = attr.getValue(Name.SEALED);
365: }
366: if (sealed == null) {
367: if ((attr = man.getMainAttributes()) != null) {
368: sealed = attr.getValue(Name.SEALED);
369: }
370: }
371: return "true".equalsIgnoreCase(sealed);
372: }
373:
374: /**
375: * Finds the resource with the specified name on the URL search path.
376: *
377: * @param name the name of the resource
378: * @return a <code>URL</code> for the resource, or <code>null</code>
379: * if the resource could not be found.
380: */
381: public URL findResource(final String name) {
382: /*
383: * The same restriction to finding classes applies to resources
384: */
385: URL url = (URL) AccessController.doPrivileged(
386: new PrivilegedAction() {
387: public Object run() {
388: return ucp.findResource(name, true);
389: }
390: }, acc);
391: return url != null ? ucp.checkURL(url) : null;
392: }
393:
394: /**
395: * Returns an Enumeration of URLs representing all of the resources
396: * on the URL search path having the specified name.
397: *
398: * @param name the resource name
399: * @exception IOException if an I/O exception occurs
400: * @return an <code>Enumeration</code> of <code>URL</code>s
401: */
402: public Enumeration findResources(final String name)
403: throws IOException {
404: final Enumeration e = ucp.findResources(name, true);
405: return new Enumeration() {
406: private URL url = null;
407:
408: private boolean next() {
409: if (url != null) {
410: return true;
411: }
412: do {
413: URL u = (URL) AccessController.doPrivileged(
414: new PrivilegedAction() {
415: public Object run() {
416: if (!e.hasMoreElements())
417: return null;
418: return e.nextElement();
419: }
420: }, acc);
421: if (u == null)
422: break;
423: url = ucp.checkURL(u);
424: } while (url == null);
425: return url != null;
426: }
427:
428: public Object nextElement() {
429: if (!next()) {
430: throw new NoSuchElementException();
431: }
432: URL u = url;
433: url = null;
434: return u;
435: }
436:
437: public boolean hasMoreElements() {
438: return next();
439: }
440: };
441: }
442:
443: /**
444: * Returns the permissions for the given codesource object.
445: * The implementation of this method first calls super.getPermissions
446: * and then adds permissions based on the URL of the codesource.
447: * <p>
448: * If the protocol is "file"
449: * and the path specifies a file, then permission to read that
450: * file is granted. If protocol is "file" and the path is
451: * a directory, permission is granted to read all files
452: * and (recursively) all files and subdirectories contained in
453: * that directory.
454: * <p>
455: * If the protocol is not "file", then
456: * to connect to and accept connections from the URL's host is granted.
457: * @param codesource the codesource
458: * @return the permissions granted to the codesource
459: */
460: protected PermissionCollection getPermissions(CodeSource codesource) {
461: PermissionCollection perms = super .getPermissions(codesource);
462:
463: URL url = codesource.getLocation();
464:
465: Permission p;
466: URLConnection urlConnection;
467:
468: try {
469: urlConnection = url.openConnection();
470: p = urlConnection.getPermission();
471: } catch (java.io.IOException ioe) {
472: p = null;
473: urlConnection = null;
474: }
475:
476: if (p instanceof FilePermission) {
477: // if the permission has a separator char on the end,
478: // it means the codebase is a directory, and we need
479: // to add an additional permission to read recursively
480: String path = p.getName();
481: if (path.endsWith(File.separator)) {
482: path += "-";
483: p = new FilePermission(path,
484: SecurityConstants.FILE_READ_ACTION);
485: }
486: } else if ((p == null) && (url.getProtocol().equals("file"))) {
487: String path = url.getFile()
488: .replace('/', File.separatorChar);
489: path = ParseUtil.decode(path);
490: if (path.endsWith(File.separator))
491: path += "-";
492: p = new FilePermission(path,
493: SecurityConstants.FILE_READ_ACTION);
494: } else {
495: URL locUrl = url;
496: if (urlConnection instanceof JarURLConnection) {
497: locUrl = ((JarURLConnection) urlConnection)
498: .getJarFileURL();
499: }
500: String host = locUrl.getHost();
501: if (host != null && (host.length() > 0))
502: p = new SocketPermission(host,
503: SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION);
504: }
505:
506: // make sure the person that created this class loader
507: // would have this permission
508:
509: if (p != null) {
510: final SecurityManager sm = System.getSecurityManager();
511: if (sm != null) {
512: final Permission fp = p;
513: AccessController.doPrivileged(new PrivilegedAction() {
514: public Object run() throws SecurityException {
515: sm.checkPermission(fp);
516: return null;
517: }
518: }, acc);
519: }
520: perms.add(p);
521: }
522: return perms;
523: }
524:
525: /**
526: * Creates a new instance of URLClassLoader for the specified
527: * URLs and parent class loader. If a security manager is
528: * installed, the <code>loadClass</code> method of the URLClassLoader
529: * returned by this method will invoke the
530: * <code>SecurityManager.checkPackageAccess</code> method before
531: * loading the class.
532: *
533: * @param urls the URLs to search for classes and resources
534: * @param parent the parent class loader for delegation
535: * @return the resulting class loader
536: */
537: public static URLClassLoader newInstance(final URL[] urls,
538: final ClassLoader parent) {
539: // Save the caller's context
540: AccessControlContext acc = AccessController.getContext();
541: // Need a privileged block to create the class loader
542: URLClassLoader ucl = (URLClassLoader) AccessController
543: .doPrivileged(new PrivilegedAction() {
544: public Object run() {
545: return new FactoryURLClassLoader(urls, parent);
546: }
547: });
548: // Now set the context on the loader using the one we saved,
549: // not the one inside the privileged block...
550: ucl.acc = acc;
551: return ucl;
552: }
553:
554: /**
555: * Creates a new instance of URLClassLoader for the specified
556: * URLs and default parent class loader. If a security manager is
557: * installed, the <code>loadClass</code> method of the URLClassLoader
558: * returned by this method will invoke the
559: * <code>SecurityManager.checkPackageAccess</code> before
560: * loading the class.
561: *
562: * @param urls the URLs to search for classes and resources
563: * @return the resulting class loader
564: */
565: public static URLClassLoader newInstance(final URL[] urls) {
566: // Save the caller's context
567: AccessControlContext acc = AccessController.getContext();
568: // Need a privileged block to create the class loader
569: URLClassLoader ucl = (URLClassLoader) AccessController
570: .doPrivileged(new PrivilegedAction() {
571: public Object run() {
572: return new FactoryURLClassLoader(urls);
573: }
574: });
575:
576: // Now set the context on the loader using the one we saved,
577: // not the one inside the privileged block...
578: ucl.acc = acc;
579: return ucl;
580: }
581: }
582:
583: final class FactoryURLClassLoader extends URLClassLoader {
584:
585: FactoryURLClassLoader(URL[] urls, ClassLoader parent) {
586: super (urls, parent);
587: }
588:
589: FactoryURLClassLoader(URL[] urls) {
590: super (urls);
591: }
592:
593: public final synchronized Class loadClass(String name,
594: boolean resolve) throws ClassNotFoundException {
595: // First check if we have permission to access the package. This
596: // should go away once we've added support for exported packages.
597: SecurityManager sm = System.getSecurityManager();
598: if (sm != null) {
599: String cname = name.replace('/', '.');
600: if (cname.startsWith("[")) {
601: int b = cname.lastIndexOf('[') + 2;
602: if (b > 1 && b < cname.length()) {
603: cname = cname.substring(b);
604: }
605: }
606: int i = cname.lastIndexOf('.');
607: if (i != -1) {
608: sm.checkPackageAccess(name.substring(0, i));
609: }
610: }
611: return super.loadClass(name, resolve);
612: }
613: }
|