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