1 /*
2 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package java.net;
27
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
30 import java.io.File;
31 import java.io.FilePermission;
32 import java.io.InputStream;
33 import java.io.IOException;
34 import java.net.URL;
35 import java.net.URLConnection;
36 import java.net.URLStreamHandlerFactory;
37 import java.util.Enumeration;
38 import java.util.NoSuchElementException;
39 import java.util.StringTokenizer;
40 import java.util.jar.Manifest;
41 import java.util.jar.Attributes;
42 import java.util.jar.Attributes.Name;
43 import java.security.CodeSigner;
44 import java.security.PrivilegedAction;
45 import java.security.PrivilegedExceptionAction;
46 import java.security.AccessController;
47 import java.security.AccessControlContext;
48 import java.security.SecureClassLoader;
49 import java.security.CodeSource;
50 import java.security.Permission;
51 import java.security.PermissionCollection;
52 import sun.misc.Resource;
53 import sun.misc.URLClassPath;
54 import sun.net.www.ParseUtil;
55 import sun.security.util.SecurityConstants;
56
57 /**
58 * This class loader is used to load classes and resources from a search
59 * path of URLs referring to both JAR files and directories. Any URL that
60 * ends with a '/' is assumed to refer to a directory. Otherwise, the URL
61 * is assumed to refer to a JAR file which will be opened as needed.
62 * <p>
63 * The AccessControlContext of the thread that created the instance of
64 * URLClassLoader will be used when subsequently loading classes and
65 * resources.
66 * <p>
67 * The classes that are loaded are by default granted permission only to
68 * access the URLs specified when the URLClassLoader was created.
69 *
70 * @author David Connelly
71 * @since 1.2
72 */
73 public class URLClassLoader extends SecureClassLoader {
74 /* The search path for classes and resources */
75 URLClassPath ucp;
76
77 /* The context to be used when loading classes and resources */
78 private AccessControlContext acc;
79
80 /**
81 * Constructs a new URLClassLoader for the given URLs. The URLs will be
82 * searched in the order specified for classes and resources after first
83 * searching in the specified parent class loader. Any URL that ends with
84 * a '/' is assumed to refer to a directory. Otherwise, the URL is assumed
85 * to refer to a JAR file which will be downloaded and opened as needed.
86 *
87 * <p>If there is a security manager, this method first
88 * calls the security manager's <code>checkCreateClassLoader</code> method
89 * to ensure creation of a class loader is allowed.
90 *
91 * @param urls the URLs from which to load classes and resources
92 * @param parent the parent class loader for delegation
93 * @exception SecurityException if a security manager exists and its
94 * <code>checkCreateClassLoader</code> method doesn't allow
95 * creation of a class loader.
96 * @see SecurityManager#checkCreateClassLoader
97 */
98 public URLClassLoader(URL[] urls, ClassLoader parent) {
99 super(parent);
100 // this is to make the stack depth consistent with 1.1
101 SecurityManager security = System.getSecurityManager();
102 if (security != null) {
103 security.checkCreateClassLoader();
104 }
105 ucp = new URLClassPath(urls);
106 acc = AccessController.getContext();
107 }
108
109 /**
110 * Constructs a new URLClassLoader for the specified URLs using the
111 * default delegation parent <code>ClassLoader</code>. The URLs will
112 * be searched in the order specified for classes and resources after
113 * first searching in the parent class loader. Any URL that ends with
114 * a '/' is assumed to refer to a directory. Otherwise, the URL is
115 * assumed to refer to a JAR file which will be downloaded and opened
116 * as needed.
117 *
118 * <p>If there is a security manager, this method first
119 * calls the security manager's <code>checkCreateClassLoader</code> method
120 * to ensure creation of a class loader is allowed.
121 *
122 * @param urls the URLs from which to load classes and resources
123 *
124 * @exception SecurityException if a security manager exists and its
125 * <code>checkCreateClassLoader</code> method doesn't allow
126 * creation of a class loader.
127 * @see SecurityManager#checkCreateClassLoader
128 */
129 public URLClassLoader(URL[] urls) {
130 super();
131 // this is to make the stack depth consistent with 1.1
132 SecurityManager security = System.getSecurityManager();
133 if (security != null) {
134 security.checkCreateClassLoader();
135 }
136 ucp = new URLClassPath(urls);
137 acc = AccessController.getContext();
138 }
139
140 /**
141 * Constructs a new URLClassLoader for the specified URLs, parent
142 * class loader, and URLStreamHandlerFactory. The parent argument
143 * will be used as the parent class loader for delegation. The
144 * factory argument will be used as the stream handler factory to
145 * obtain protocol handlers when creating new jar URLs.
146 *
147 * <p>If there is a security manager, this method first
148 * calls the security manager's <code>checkCreateClassLoader</code> method
149 * to ensure creation of a class loader is allowed.
150 *
151 * @param urls the URLs from which to load classes and resources
152 * @param parent the parent class loader for delegation
153 * @param factory the URLStreamHandlerFactory to use when creating URLs
154 *
155 * @exception SecurityException if a security manager exists and its
156 * <code>checkCreateClassLoader</code> method doesn't allow
157 * creation of a class loader.
158 * @see SecurityManager#checkCreateClassLoader
159 */
160 public URLClassLoader(URL[] urls, ClassLoader parent,
161 URLStreamHandlerFactory factory) {
162 super(parent);
163 // this is to make the stack depth consistent with 1.1
164 SecurityManager security = System.getSecurityManager();
165 if (security != null) {
166 security.checkCreateClassLoader();
167 }
168 ucp = new URLClassPath(urls, factory);
169 acc = AccessController.getContext();
170 }
171
172 /**
173 * Appends the specified URL to the list of URLs to search for
174 * classes and resources.
175 * <p>
176 * If the URL specified is <code>null</code> or is already in the
177 * list of URLs, then invoking this method has no effect.
178 *
179 * @param url the URL to be added to the search path of URLs
180 */
181 protected void addURL(URL url) {
182 ucp.addURL(url);
183 }
184
185 /**
186 * Returns the search path of URLs for loading classes and resources.
187 * This includes the original list of URLs specified to the constructor,
188 * along with any URLs subsequently appended by the addURL() method.
189 * @return the search path of URLs for loading classes and resources.
190 */
191 public URL[] getURLs() {
192 return ucp.getURLs();
193 }
194
195 /**
196 * Finds and loads the class with the specified name from the URL search
197 * path. Any URLs referring to JAR files are loaded and opened as needed
198 * until the class is found.
199 *
200 * @param name the name of the class
201 * @return the resulting class
202 * @exception ClassNotFoundException if the class could not be found
203 */
204 protected Class<?> findClass(final String name)
205 throws ClassNotFoundException
206 {
207 try {
208 return AccessController.doPrivileged(
209 new PrivilegedExceptionAction<Class>() {
210 public Class run() throws ClassNotFoundException {
211 String path = name.replace('.', '/').concat(".class");
212 Resource res = ucp.getResource(path, false);
213 if (res != null) {
214 try {
215 return defineClass(name, res);
216 } catch (IOException e) {
217 throw new ClassNotFoundException(name, e);
218 }
219 } else {
220 throw new ClassNotFoundException(name);
221 }
222 }
223 }, acc);
224 } catch (java.security.PrivilegedActionException pae) {
225 throw (ClassNotFoundException) pae.getException();
226 }
227 }
228
229 /*
230 * Defines a Class using the class bytes obtained from the specified
231 * Resource. The resulting Class must be resolved before it can be
232 * used.
233 */
234 private Class defineClass(String name, Resource res) throws IOException {
235 int i = name.lastIndexOf('.');
236 URL url = res.getCodeSourceURL();
237 if (i != -1) {
238 String pkgname = name.substring(0, i);
239 // Check if package already loaded.
240 Package pkg = getPackage(pkgname);
241 Manifest man = res.getManifest();
242 if (pkg != null) {
243 // Package found, so check package sealing.
244 if (pkg.isSealed()) {
245 // Verify that code source URL is the same.
246 if (!pkg.isSealed(url)) {
247 throw new SecurityException(
248 "sealing violation: package " + pkgname + " is sealed");
249 }
250
251 } else {
252 // Make sure we are not attempting to seal the package
253 // at this code source URL.
254 if ((man != null) && isSealed(pkgname, man)) {
255 throw new SecurityException(
256 "sealing violation: can't seal package " + pkgname +
257 ": already loaded");
258 }
259 }
260 } else {
261 if (man != null) {
262 definePackage(pkgname, man, url);
263 } else {
264 definePackage(pkgname, null, null, null, null, null, null, null);
265 }
266 }
267 }
268 // Now read the class bytes and define the class
269 java.nio.ByteBuffer bb = res.getByteBuffer();
270 if (bb != null) {
271 // Use (direct) ByteBuffer:
272 CodeSigner[] signers = res.getCodeSigners();
273 CodeSource cs = new CodeSource(url, signers);
274 return defineClass(name, bb, cs);
275 } else {
276 byte[] b = res.getBytes();
277 // must read certificates AFTER reading bytes.
278 CodeSigner[] signers = res.getCodeSigners();
279 CodeSource cs = new CodeSource(url, signers);
280 return defineClass(name, b, 0, b.length, cs);
281 }
282 }
283
284 /**
285 * Defines a new package by name in this ClassLoader. The attributes
286 * contained in the specified Manifest will be used to obtain package
287 * version and sealing information. For sealed packages, the additional
288 * URL specifies the code source URL from which the package was loaded.
289 *
290 * @param name the package name
291 * @param man the Manifest containing package version and sealing
292 * information
293 * @param url the code source url for the package, or null if none
294 * @exception IllegalArgumentException if the package name duplicates
295 * an existing package either in this class loader or one
296 * of its ancestors
297 * @return the newly defined Package object
298 */
299 protected Package definePackage(String name, Manifest man, URL url)
300 throws IllegalArgumentException
301 {
302 String path = name.replace('.', '/').concat("/");
303 String specTitle = null, specVersion = null, specVendor = null;
304 String implTitle = null, implVersion = null, implVendor = null;
305 String sealed = null;
306 URL sealBase = null;
307
308 Attributes attr = man.getAttributes(path);
309 if (attr != null) {
310 specTitle = attr.getValue(Name.SPECIFICATION_TITLE);
311 specVersion = attr.getValue(Name.SPECIFICATION_VERSION);
312 specVendor = attr.getValue(Name.SPECIFICATION_VENDOR);
313 implTitle = attr.getValue(Name.IMPLEMENTATION_TITLE);
314 implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION);
315 implVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR);
316 sealed = attr.getValue(Name.SEALED);
317 }
318 attr = man.getMainAttributes();
319 if (attr != null) {
320 if (specTitle == null) {
321 specTitle = attr.getValue(Name.SPECIFICATION_TITLE);
322 }
323 if (specVersion == null) {
324 specVersion = attr.getValue(Name.SPECIFICATION_VERSION);
325 }
326 if (specVendor == null) {
327 specVendor = attr.getValue(Name.SPECIFICATION_VENDOR);
328 }
329 if (implTitle == null) {
330 implTitle = attr.getValue(Name.IMPLEMENTATION_TITLE);
331 }
332 if (implVersion == null) {
333 implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION);
334 }
335 if (implVendor == null) {
336 implVendor = attr.getValue(Name.IMPLEMENTATION_VENDOR);
337 }
338 if (sealed == null) {
339 sealed = attr.getValue(Name.SEALED);
340 }
341 }
342 if ("true".equalsIgnoreCase(sealed)) {
343 sealBase = url;
344 }
345 return definePackage(name, specTitle, specVersion, specVendor,
346 implTitle, implVersion, implVendor, sealBase);
347 }
348
349 /*
350 * Returns true if the specified package name is sealed according to the
351 * given manifest.
352 */
353 private boolean isSealed(String name, Manifest man) {
354 String path = name.replace('.', '/').concat("/");
355 Attributes attr = man.getAttributes(path);
356 String sealed = null;
357 if (attr != null) {
358 sealed = attr.getValue(Name.SEALED);
359 }
360 if (sealed == null) {
361 if ((attr = man.getMainAttributes()) != null) {
362 sealed = attr.getValue(Name.SEALED);
363 }
364 }
365 return "true".equalsIgnoreCase(sealed);
366 }
367
368 /**
369 * Finds the resource with the specified name on the URL search path.
370 *
371 * @param name the name of the resource
372 * @return a <code>URL</code> for the resource, or <code>null</code>
373 * if the resource could not be found.
374 */
375 public URL findResource(final String name) {
376 /*
377 * The same restriction to finding classes applies to resources
378 */
379 URL url = AccessController.doPrivileged(
380 new PrivilegedAction<URL>() {
381 public URL run() {
382 return ucp.findResource(name, true);
383 }
384 }, acc);
385
386 return url != null ? ucp.checkURL(url) : null;
387 }
388
389 /**
390 * Returns an Enumeration of URLs representing all of the resources
391 * on the URL search path having the specified name.
392 *
393 * @param name the resource name
394 * @exception IOException if an I/O exception occurs
395 * @return an <code>Enumeration</code> of <code>URL</code>s
396 */
397 public Enumeration<URL> findResources(final String name)
398 throws IOException
399 {
400 final Enumeration<URL> e = ucp.findResources(name, true);
401
402 return new Enumeration<URL>() {
403 private URL url = null;
404
405 private boolean next() {
406 if (url != null) {
407 return true;
408 }
409 do {
410 URL u = AccessController.doPrivileged(
411 new PrivilegedAction<URL>() {
412 public URL run() {
413 if (!e.hasMoreElements())
414 return null;
415 return e.nextElement();
416 }
417 }, acc);
418 if (u == null)
419 break;
420 url = ucp.checkURL(u);
421 } while (url == null);
422 return url != null;
423 }
424
425 public URL nextElement() {
426 if (!next()) {
427 throw new NoSuchElementException();
428 }
429 URL u = url;
430 url = null;
431 return u;
432 }
433
434 public boolean hasMoreElements() {
435 return next();
436 }
437 };
438 }
439
440 /**
441 * Returns the permissions for the given codesource object.
442 * The implementation of this method first calls super.getPermissions
443 * and then adds permissions based on the URL of the codesource.
444 * <p>
445 * If the protocol of this URL is "jar", then the permission granted
446 * is based on the permission that is required by the URL of the Jar
447 * file.
448 * <p>
449 * If the protocol is "file" and there is an authority component, then
450 * permission to connect to and accept connections from that authority
451 * may be granted. If the protocol is "file"
452 * and the path specifies a file, then permission to read that
453 * file is granted. If protocol is "file" and the path is
454 * a directory, permission is granted to read all files
455 * and (recursively) all files and subdirectories contained in
456 * that directory.
457 * <p>
458 * If the protocol is not "file", then permission
459 * to connect to and accept connections from the URL's host is granted.
460 * @param codesource the codesource
461 * @return the permissions granted to the codesource
462 */
463 protected PermissionCollection getPermissions(CodeSource codesource)
464 {
465 PermissionCollection perms = super.getPermissions(codesource);
466
467 URL url = codesource.getLocation();
468
469 Permission p;
470 URLConnection urlConnection;
471
472 try {
473 urlConnection = url.openConnection();
474 p = urlConnection.getPermission();
475 } catch (java.io.IOException ioe) {
476 p = null;
477 urlConnection = null;
478 }
479
480 if (p instanceof FilePermission) {
481 // if the permission has a separator char on the end,
482 // it means the codebase is a directory, and we need
483 // to add an additional permission to read recursively
484 String path = p.getName();
485 if (path.endsWith(File.separator)) {
486 path += "-";
487 p = new FilePermission(path, SecurityConstants.FILE_READ_ACTION);
488 }
489 } else if ((p == null) && (url.getProtocol().equals("file"))) {
490 String path = url.getFile().replace('/', File.separatorChar);
491 path = ParseUtil.decode(path);
492 if (path.endsWith(File.separator))
493 path += "-";
494 p = new FilePermission(path, SecurityConstants.FILE_READ_ACTION);
495 } else {
496 /**
497 * Not loading from a 'file:' URL so we want to give the class
498 * permission to connect to and accept from the remote host
499 * after we've made sure the host is the correct one and is valid.
500 */
501 URL locUrl = url;
502 if (urlConnection instanceof JarURLConnection) {
503 locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
504 }
505 String host = locUrl.getHost();
506 if (host != null && (host.length() > 0))
507 p = new SocketPermission(host,
508 SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION);
509 }
510
511 // make sure the person that created this class loader
512 // would have this permission
513
514 if (p != null) {
515 final SecurityManager sm = System.getSecurityManager();
516 if (sm != null) {
517 final Permission fp = p;
518 AccessController.doPrivileged(new PrivilegedAction<Void>() {
519 public Void run() throws SecurityException {
520 sm.checkPermission(fp);
521 return null;
522 }
523 }, acc);
524 }
525 perms.add(p);
526 }
527 return perms;
528 }
529
530 /**
531 * Creates a new instance of URLClassLoader for the specified
532 * URLs and parent class loader. If a security manager is
533 * installed, the <code>loadClass</code> method of the URLClassLoader
534 * returned by this method will invoke the
535 * <code>SecurityManager.checkPackageAccess</code> method before
536 * loading the class.
537 *
538 * @param urls the URLs to search for classes and resources
539 * @param parent the parent class loader for delegation
540 * @return the resulting class loader
541 */
542 public static URLClassLoader newInstance(final URL[] urls,
543 final ClassLoader parent) {
544 // Save the caller's context
545 AccessControlContext acc = AccessController.getContext();
546 // Need a privileged block to create the class loader
547 URLClassLoader ucl = AccessController.doPrivileged(
548 new PrivilegedAction<URLClassLoader>() {
549 public URLClassLoader run() {
550 return new FactoryURLClassLoader(urls, parent);
551 }
552 });
553 // Now set the context on the loader using the one we saved,
554 // not the one inside the privileged block...
555 ucl.acc = acc;
556 return ucl;
557 }
558
559 /**
560 * Creates a new instance of URLClassLoader for the specified
561 * URLs and default parent class loader. If a security manager is
562 * installed, the <code>loadClass</code> method of the URLClassLoader
563 * returned by this method will invoke the
564 * <code>SecurityManager.checkPackageAccess</code> before
565 * loading the class.
566 *
567 * @param urls the URLs to search for classes and resources
568 * @return the resulting class loader
569 */
570 public static URLClassLoader newInstance(final URL[] urls) {
571 // Save the caller's context
572 AccessControlContext acc = AccessController.getContext();
573 // Need a privileged block to create the class loader
574 URLClassLoader ucl = AccessController.doPrivileged(
575 new PrivilegedAction<URLClassLoader>() {
576 public URLClassLoader run() {
577 return new FactoryURLClassLoader(urls);
578 }
579 });
580
581 // Now set the context on the loader using the one we saved,
582 // not the one inside the privileged block...
583 ucl.acc = acc;
584 return ucl;
585 }
586
587 static {
588 sun.misc.SharedSecrets.setJavaNetAccess (
589 new sun.misc.JavaNetAccess() {
590 public URLClassPath getURLClassPath (URLClassLoader u) {
591 return u.ucp;
592 }
593 }
594 );
595 }
596 }
597
598 final class FactoryURLClassLoader extends URLClassLoader {
599
600 FactoryURLClassLoader(URL[] urls, ClassLoader parent) {
601 super(urls, parent);
602 }
603
604 FactoryURLClassLoader(URL[] urls) {
605 super(urls);
606 }
607
608 public final synchronized Class loadClass(String name, boolean resolve)
609 throws ClassNotFoundException
610 {
611 // First check if we have permission to access the package. This
612 // should go away once we've added support for exported packages.
613 SecurityManager sm = System.getSecurityManager();
614 if (sm != null) {
615 int i = name.lastIndexOf('.');
616 if (i != -1) {
617 sm.checkPackageAccess(name.substring(0, i));
618 }
619 }
620 return super.loadClass(name, resolve);
621 }
622 }