From e825ca7ff5b76e07bdcecd4e02fc3e5df792ea72 Mon Sep 17 00:00:00 2001 From: Mark Wielaard Date: Mon, 9 Dec 2002 00:04:00 +0000 Subject: [PATCH] Connection.java (getJarFile): download and cache remote jar files. * gnu/gcj/protocol/jar/Connection.java (getJarFile): download and cache remote jar files. * gnu/gcj/runtime/VMClassLoader.java: Don't construct jar URL, only add File.separator to URL when it is a directory. * java/lang/ClassLoader.java: Add Classpath javadoc. (parent): final. (getParent): Add (disabled) security check. (findLibrary): New default method. * java/net/JarURLConnection.java (getManifest): Implement. (getInputStream): Only create InputStream when entry exists. (getHeaders): Only use jarFileURLConnection or JarEntry to set length when they exist. * java/net/URLClassLoader.java: New/Rewritten version from Classpath. From-SVN: r59949 --- libjava/ChangeLog | 16 + libjava/gnu/gcj/protocol/jar/Connection.java | 31 +- libjava/gnu/gcj/runtime/VMClassLoader.java | 21 +- libjava/java/lang/ClassLoader.java | 124 +- libjava/java/net/JarURLConnection.java | 20 +- libjava/java/net/URLClassLoader.java | 1184 +++++++++++++----- 6 files changed, 1065 insertions(+), 331 deletions(-) diff --git a/libjava/ChangeLog b/libjava/ChangeLog index 9e255b2cd57..15ce27c6165 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,19 @@ +2002-12-08 Mark Wielaard + + * gnu/gcj/protocol/jar/Connection.java (getJarFile): download and + cache remote jar files. + * gnu/gcj/runtime/VMClassLoader.java: Don't construct jar URL, only + add File.separator to URL when it is a directory. + * java/lang/ClassLoader.java: Add Classpath javadoc. + (parent): final. + (getParent): Add (disabled) security check. + (findLibrary): New default method. + * java/net/JarURLConnection.java (getManifest): Implement. + (getInputStream): Only create InputStream when entry exists. + (getHeaders): Only use jarFileURLConnection or JarEntry to set length + when they exist. + * java/net/URLClassLoader.java: New/Rewritten version from Classpath. + 2002-12-08 Mark Wielaard * java/util/ResourceBundle.java (resourceBundleCache): Not final. diff --git a/libjava/gnu/gcj/protocol/jar/Connection.java b/libjava/gnu/gcj/protocol/jar/Connection.java index 60641182f3a..c9db04997dc 100644 --- a/libjava/gnu/gcj/protocol/jar/Connection.java +++ b/libjava/gnu/gcj/protocol/jar/Connection.java @@ -1,4 +1,4 @@ -/* Copyright (C) 1999 Free Software Foundation +/* Copyright (C) 1999, 2002 Free Software Foundation This file is part of libgcj. @@ -9,19 +9,21 @@ details. */ package gnu.gcj.protocol.jar; import java.net.URL; +import java.net.URLConnection; import java.net.JarURLConnection; import java.net.URLStreamHandler; import java.net.MalformedURLException; import java.net.ProtocolException; import java.io.IOException; +import java.io.InputStream; +import java.io.File; +import java.io.FileOutputStream; import java.util.jar.JarFile; +import java.util.zip.ZipFile; import java.util.Hashtable; /** * Written using on-line Java Platform 1.2 API Specification. - * Status: Needs a way to download jar files and store them in the local file - * system. I don't know how to do that in a portable way. For now, it can only handle - * connections to a jar:file: url's. * * @author Kresten Krab Thorup * @date Aug 10, 1999. @@ -70,14 +72,19 @@ public class Connection extends JarURLConnection } else { - /* - FIXME: Here we need to download and cache the jar - file in the local file system! Stupid design. Why - can't we just create a JarFile from a bag of bytes? - */ - - throw new java.io.IOException("cannot create jar file from " + - jarFileURL); + URLConnection urlconn = jarFileURL.openConnection(); + InputStream is = urlconn.getInputStream(); + byte[] buf = new byte[4*1024]; + File f = File.createTempFile("cache", "jar"); + FileOutputStream fos = new FileOutputStream(f); + int len = 0; + while((len = is.read(buf)) != -1) + fos.write(buf, 0, len); + fos.close(); + // Always verify the Manifest, open read only and delete when done. + // XXX ZipFile.OPEN_DELETE not yet implemented. + // jf = new JarFile(f, true, ZipFile.OPEN_READ | ZipFile.OPEN_DELETE); + jarfile = new JarFile(f, true, ZipFile.OPEN_READ); } return jarfile; diff --git a/libjava/gnu/gcj/runtime/VMClassLoader.java b/libjava/gnu/gcj/runtime/VMClassLoader.java index 77d21606621..fd0c32c2c57 100644 --- a/libjava/gnu/gcj/runtime/VMClassLoader.java +++ b/libjava/gnu/gcj/runtime/VMClassLoader.java @@ -1,4 +1,4 @@ -/* Copyright (C) 1999, 2001 Free Software Foundation +/* Copyright (C) 1999, 2001, 2002 Free Software Foundation This file is part of libgcj. @@ -33,23 +33,10 @@ public final class VMClassLoader extends java.net.URLClassLoader String e = st.nextToken (); try { - if (e.endsWith(".jar") || e.endsWith (".zip")) - { - File archive = new File (e); - try { - p.addElement(new URL("jar", "", -1, "file://" - + archive.getCanonicalPath () - + "!/")); - } catch (IOException ex) { - // empty - } - } - else if (e.endsWith ("/")) - p.addElement (new URL("file", "", -1, e)); - else if (new File (e).isDirectory ()) - p.addElement (new URL("file", "", -1, e + "/")); + if (!e.endsWith (File.separator) && new File (e).isDirectory ()) + p.addElement (new URL("file", "", -1, e + File.separator)); else - /* Ignore path element. */; + p.addElement (new URL("file", "", -1, e)); } catch (java.net.MalformedURLException x) { diff --git a/libjava/java/lang/ClassLoader.java b/libjava/java/lang/ClassLoader.java index 1b3b310cab3..ea6546cf6ee 100644 --- a/libjava/java/lang/ClassLoader.java +++ b/libjava/java/lang/ClassLoader.java @@ -13,7 +13,6 @@ package java.lang; import java.io.InputStream; import java.io.IOException; import java.net.URL; -import java.net.URLConnection; import java.security.AllPermission; import java.security.CodeSource; import java.security.Permission; @@ -23,13 +22,68 @@ import java.security.ProtectionDomain; import java.util.*; /** - * The class ClassLoader is intended to be subclassed by - * applications in order to describe new ways of loading classes, - * such as over the network. + * The ClassLoader is a way of customizing the way Java gets its classes + * and loads them into memory. The verifier and other standard Java things + * still run, but the ClassLoader is allowed great flexibility in determining + * where to get the classfiles and when to load and resolve them. For that + * matter, a custom ClassLoader can perform on-the-fly code generation or + * modification! * - * @author Kresten Krab Thorup + *

Every classloader has a parent classloader that is consulted before + * the 'child' classloader when classes or resources should be loaded. + * This is done to make sure that classes can be loaded from an hierarchy of + * multiple classloaders and classloaders do not accidentially redefine + * already loaded classes by classloaders higher in the hierarchy. + * + *

The grandparent of all classloaders is the bootstrap classloader, which + * loads all the standard system classes as implemented by GNU Classpath. The + * other special classloader is the system classloader (also called + * application classloader) that loads all classes from the CLASSPATH + * (java.class.path system property). The system classloader + * is responsible for finding the application classes from the classpath, + * and delegates all requests for the standard library classes to its parent + * the bootstrap classloader. Most programs will load all their classes + * through the system classloaders. + * + *

The bootstrap classloader in GNU Classpath is implemented as a couple of + * static (native) methods on the package private class + * java.lang.VMClassLoader, the system classloader is an + * instance of gnu.java.lang.SystemClassLoader + * (which is a subclass of java.net.URLClassLoader). + * + *

Users of a ClassLoader will normally just use the methods + *

    + *
  • loadClass() to load a class.
  • + *
  • getResource() or getResourceAsStream() + * to access a resource.
  • + *
  • getResources() to get an Enumeration of URLs to all + * the resources provided by the classloader and its parents with the + * same name.
  • + *
+ * + *

Subclasses should implement the methods + *

    + *
  • findClass() which is called by loadClass() + * when the parent classloader cannot provide a named class.
  • + *
  • findResource() which is called by + * getResource() when the parent classloader cannot provide + * a named resource.
  • + *
  • findResources() which is called by + * getResource() to combine all the resources with the + * same name from the classloader and its parents.
  • + *
  • findLibrary() which is called by + * Runtime.loadLibrary() when a class defined by the + * classloader wants to load a native library.
  • + *
+ * + * @author John Keiser + * @author Mark Wielaard + * @author Eric Blake + * @author Kresten Krab Thorup + * @see Class + * @since 1.0 + * @status still missing 1.4 functionality */ - public abstract class ClassLoader { /** @@ -73,12 +127,40 @@ public abstract class ClassLoader // Package visible for use by Class. Map classAssertionStatus; - private ClassLoader parent; + /** + * The classloader that is consulted before this classloader. + * If null then the parent is the bootstrap classloader. + */ + private final ClassLoader parent; + + /** + * All packages defined by this classloader. It is not private in order to + * allow native code (and trusted subclasses) access to this field. + */ private HashMap definedPackages = new HashMap(); + /** + * Returns the parent of this classloader. If the parent of this + * classloader is the bootstrap classloader then this method returns + * null. A security check may be performed on + * RuntimePermission("getClassLoader"). + * + * @throws SecurityException if the security check fails + * @since 1.2 + */ public final ClassLoader getParent () { - /* FIXME: security */ + // Check if we may return the parent classloader + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + { + /* FIXME: security, getClassContext() not implemented. + Class c = VMSecurityManager.getClassContext()[1]; + ClassLoader cl = c.getClassLoader(); + if (cl != null && cl != this) + sm.checkPermission(new RuntimePermission("getClassLoader")); + */ + } return parent; } @@ -449,7 +531,8 @@ public abstract class ClassLoader else { InternalError e - = new InternalError ("unexpected exception during linking"); + = new InternalError ("unexpected exception during linking: " + + clazz.getName()); e.initCause (x); throw e; } @@ -521,6 +604,8 @@ public abstract class ClassLoader * null when the package is not defined by this classloader or one of its * parents. * + * @param name the package name to find + * @return the package, if defined * @since 1.2 */ protected Package getPackage(String name) @@ -546,6 +631,7 @@ public abstract class ClassLoader /** * Returns all Package objects defined by this classloader and its parents. * + * @return an array of all defined packages * @since 1.2 */ protected Package[] getPackages() @@ -577,6 +663,26 @@ public abstract class ClassLoader return allPackages; } + /** + * Called by Runtime.loadLibrary() to get an absolute path + * to a (system specific) library that was requested by a class loaded + * by this classloader. The default implementation returns + * null. It should be implemented by subclasses when they + * have a way to find the absolute path to a library. If this method + * returns null the library is searched for in the default locations + * (the directories listed in the java.library.path system + * property). + * + * @param name the (system specific) name of the requested library + * @return the full pathname to the requested library, or null + * @see Runtime#loadLibrary() + * @since 1.2 + */ + protected String findLibrary(String name) + { + return null; + } + /** * Returns a class found in a system-specific way, typically * via the java.class.path system property. Loads the diff --git a/libjava/java/net/JarURLConnection.java b/libjava/java/net/JarURLConnection.java index a90c7f33037..6f4cd1fe7ea 100644 --- a/libjava/java/net/JarURLConnection.java +++ b/libjava/java/net/JarURLConnection.java @@ -134,7 +134,11 @@ public abstract class JarURLConnection extends URLConnection if (jarfile != null) { // this is the easy way... - return jarfile.getInputStream (jarfile.getEntry (element)); + ZipEntry entry = jarfile.getEntry(element); + if (entry != null) + return jarfile.getInputStream (entry); + else + return null; } else { @@ -320,12 +324,17 @@ public abstract class JarURLConnection extends URLConnection // to add others later and for consistency, we'll implement it this way. // Add the only header we know about right now: Content-length. - long len; + long len = -1; if (element == null) - len = jarFileURLConnection.getContentLength (); + if (jarFileURLConnection != null) + len = jarFileURLConnection.getContentLength (); else - len = getJarEntry ().getSize (); + { + JarEntry entry = getJarEntry(); + if (entry != null) + len = entry.getSize (); + } String line = "Content-length: " + len; hdrVec.addElement(line); @@ -381,7 +390,6 @@ public abstract class JarURLConnection extends URLConnection { JarFile file = getJarFile (); - // FIXME: implement this - return null; + return (file != null) ? file.getManifest() : null; } } diff --git a/libjava/java/net/URLClassLoader.java b/libjava/java/net/URLClassLoader.java index 5e059cf9cb6..e37a81a5021 100644 --- a/libjava/java/net/URLClassLoader.java +++ b/libjava/java/net/URLClassLoader.java @@ -1,332 +1,701 @@ -/* Copyright (C) 1999, 2000, 2002 Free Software Foundation +/* URLClassLoader.java -- ClassLoader that loads classes from one or more URLs + Copyright (C) 1999, 2000, 2001, 2002 Free Software Foundation, Inc. - This file is part of libgcj. +This file is part of GNU Classpath. -This software is copyrighted work licensed under the terms of the -Libgcj License. Please consult the file "LIBGCJ_LICENSE" for -details. */ +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ package java.net; -import java.io.*; -import java.util.jar.*; -import java.util.Enumeration; -import java.util.Vector; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilterInputStream; +import java.io.FilePermission; +import java.io.InputStream; +import java.io.IOException; +import java.security.AccessController; +import java.security.AccessControlContext; import java.security.CodeSource; import java.security.SecureClassLoader; +import java.security.PrivilegedAction; import java.security.PermissionCollection; import java.security.cert.Certificate; +import java.util.Enumeration; +import java.util.Vector; +import java.util.HashMap; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.zip.ZipException; /** + * A secure class loader that can load classes and resources from + * multiple locations. Given an array of URLs this class + * loader will retrieve classes and resources by fetching them from + * possible remote locations. Each URL is searched in + * order in which it was added. If the file portion of the + * URL ends with a '/' character then it is interpreted + * as a base directory, otherwise it is interpreted as a jar file from + * which the classes/resources are resolved. + * + *

New instances can be created by two static + * newInstance() methods or by three public + * contructors. Both ways give the option to supply an initial array + * of URLs and (optionally) a parent classloader (that is + * different from the standard system class loader).

+ * + *

Normally creating a URLClassLoader throws a + * SecurityException if a SecurityManager is + * installed and the checkCreateClassLoader() method does + * not return true. But the newInstance() methods may be + * used by any code as long as it has permission to acces the given + * URLs. URLClassLoaders created by the + * newInstance() methods also explicitly call the + * checkPackageAccess() method of + * SecurityManager if one is installed before trying to + * load a class. Note that only subclasses of + * URLClassLoader can add new URLs after the + * URLClassLoader had been created. But it is always possible to get + * an array of all URLs that the class loader uses to resolve classes + * and resources by way of the getURLs() method.

+ * + *

Open issues: + *

    + * + *
  • Should the URLClassLoader actually add the locations found in + * the manifest or is this the responsibility of some other + * loader/(sub)class? (see + * Extension Mechanism Architecture - Bundles Extensions)
  • + * + *
  • How does definePackage() and sealing work + * precisely?
  • + * + *
  • We save and use the security context (when a created by + * newInstance() but do we have to use it in more + * places?
  • + * + *
  • The use of URLStreamHandlers has not been tested.
  • + * + *
+ *

+ * * @since 1.2 + * + * @author Mark Wielaard (mark@klomp.org) + * @author Wu Gansha (gansha.wu@intel.com) */ + public class URLClassLoader extends SecureClassLoader { - // The URLStreamHandlerFactory - URLStreamHandlerFactory factory = null; + // Class Variables - // `path' contains simply the URL's we're using for the searching. - private Vector path; + /** + * A global cache to store mappings between URLLoader and URL, + * so we can avoid do all the homework each time the same URL + * comes. + * XXX - Keeps these loaders forever which prevents garbage collection. + */ + private static HashMap urlloaders = new HashMap(); + + /** + * A cache to store mappings between handler factory and its + * private protocol handler cache (also a HashMap), so we can avoid + * create handlers each time the same protocol comes. + */ + private static HashMap factoryCache = new HashMap(5); - // If path[n] is a zip/jar, then this holds a JarURLConnection for - // that thing, otherwise, path[n] is null. - private Vector info; + // Instance variables - private URLStreamHandler getHandler0 (String protocol) - { - if (factory != null) - return factory.createURLStreamHandler(protocol); - else - return null; - } + /** Locations to load classes from */ + private final Vector urls = new Vector(); /** - * Createa a new URL class loader object - * - * @exception SecurityException If a security manager exists and its - * checkCreateClassLoader method doesn't allow creation of a class loader + * Store pre-parsed information for each url into this vector + * each element is a URL loader, corresponding to the URL of + * the same index in "urls" */ - public URLClassLoader (URL[] urls) - { - this (urls, null, null); - } - + private final Vector urlinfos = new Vector(); + + /** Factory used to get the protocol handlers of the URLs */ + private final URLStreamHandlerFactory factory; + /** - * Createa a new URL class loader object - * - * @exception SecurityException If a security manager exists and its - * checkCreateClassLoader method doesn't allow creation of a class loader + * The security context when created from newInstance() + * or null when created through a normal constructor or when no + * SecurityManager was installed. */ - public URLClassLoader (URL[] urls, ClassLoader parent) - { - this (urls, parent, null); - } + private final AccessControlContext securityContext; - // A File URL may actually be a Jar URL. Convert if possible. - private URL jarFileize (URL url) + // Helper classes + + /** + * A URLLoader contains all logic to load resources from a + * given base URL. + */ + static abstract class URLLoader { - if (! url.getProtocol ().equals ("jar")) - { - String f = url.getFile (); + /** + * Our classloader to get info from if needed. + */ + final URLClassLoader classloader; - // If it ends with '/' we'll take it for a directory, - // otherwise it's a jar file. This is how JDK 1.2 defines - // it, so we will not try to be smart here. - if (f.charAt (f.length ()-1) != '/') - { - try - { - url = new URL ("jar", "", -1, (url.toExternalForm ())+"!/", - getHandler0 ("jar")); - } - catch (MalformedURLException x) - { - /* ignore */ - } - } - } - return url; - } + /** + * The base URL from which all resources are loaded. + */ + final URL baseURL; - protected void addURL (URL url) - { - JarURLConnection conn = null; + /** + * A CodeSource without any associated certificates. + * It is common for classes to not have certificates associated + * with them. If they come from the same URLLoader + * then it is safe to share the associated CodeSource + * between them since CodeSource is immutable. + */ + final CodeSource noCertCodeSource; + + URLLoader(URLClassLoader classloader, URL baseURL) + { + this.classloader = classloader; + this.baseURL = baseURL; + this.noCertCodeSource = new CodeSource(baseURL, null); + } + + /** + * Returns a Resource loaded by this + * URLLoader, or null when no + * Resource with the given name exists. + */ + abstract Resource getResource(String s); + + /** + * Returns the Manifest associated with the + * Resources loaded by this URLLoader or + * null there is no such Manifest. + */ + Manifest getManifest() + { + return null; + } + } - // Convert a Jar File URL into Jar URL if possible. - url = jarFileize (url); + /** + * A Resource represents a resource in some + * URLLoader. It also contains all information (e.g., + * URL, CodeSource, Manifest and + * InputStream) that is necessary for loading resources + * and creating classes from a URL. + */ + static abstract class Resource + { + final URLLoader loader; + final String name; - path.addElement (url); + Resource(URLLoader loader, String name) + { + this.loader = loader; + this.name = name; + } - if (url.getProtocol ().equals ("jar")) - { - try - { - conn = (JarURLConnection) url.openConnection (); - } - catch (java.io.IOException x) - { - /* ignore */ - } - } + /** + * Returns the non-null CodeSource associated with + * this resource. + */ + CodeSource getCodeSource() + { + Certificate[] certs = getCertificates(); + if (certs != null) + return loader.noCertCodeSource; + else + return new CodeSource(loader.baseURL, certs); + } + + /** + * Returns Certificates associated with this + * resource, or null when there are none. + */ + Certificate[] getCertificates() + { + return null; + } + + /** + * Return a URL that can be used to access this resource. + */ + abstract URL getURL(); - info.addElement (conn); + /** + * Returns the size of this Resource in bytes or + * -1 when unknown. + */ + abstract int getLength(); + + /** + * Returns the non-null InputStream through which + * this resource can be loaded. + */ + abstract InputStream getInputStream() throws IOException; } /** - * Createa a new URL class loader object - * - * @exception SecurityException If a security manager exists and its - * checkCreateClassLoader method doesn't allow creation of a class loader + * A JarURLLoader is a type of URLLoader + * only loading from jar url. */ - public URLClassLoader (URL[] urls, ClassLoader parent, - URLStreamHandlerFactory fac) - { - super (parent); - - SecurityManager s = System.getSecurityManager(); - if (s != null) - s.checkCreateClassLoader(); - - factory = fac; + final static class JarURLLoader extends URLLoader + { + final JarFile jarfile; // The canonical jar file for this url + final URL baseJarURL; // Base jar: url for all resources loaded from jar - if (urls == null || urls.length == 0) - { - path = new Vector (1); - info = new Vector (1); - return; - } + public JarURLLoader(URLClassLoader classloader, URL baseURL) + { + super(classloader, baseURL); + + // cache url prefix for all resources in this jar url + String external = baseURL.toExternalForm(); + StringBuffer sb = new StringBuffer(external.length() + 6); + sb.append("jar:"); + sb.append(external); + sb.append("!/"); + String jarURL = sb.toString(); - path = new Vector (urls.length); - info = new Vector (urls.length); + URL baseJarURL = null; + JarFile jarfile = null; + try + { + baseJarURL + = new URL(null, jarURL, classloader.getURLStreamHandler("jar")); + jarfile + = ((JarURLConnection) baseJarURL.openConnection()).getJarFile(); + } + catch (IOException ioe) { /* ignored */ } - for (int i = 0; i < urls.length; i++) - { - // Convert a Jar File URL into a Jar URL if possible. - URL u = jarFileize(urls[i]); + this.baseJarURL = baseJarURL; + this.jarfile = jarfile; + } + + /** get resource with the name "name" in the jar url */ + Resource getResource(String name) + { + if (jarfile == null) + return null; - path.addElement (u); + JarEntry je = jarfile.getJarEntry(name); + if(je != null) + return new JarURLResource(this, name, je); + else + return null; + } - if (u.getProtocol ().equals ("jar")) - { - JarURLConnection conn = null; - try - { - conn = (JarURLConnection) u.openConnection (); - } - catch (java.io.IOException x) - { - /* ignore */ - } - info.addElement (conn); - } - else - { - info.addElement (null); - } - } + Manifest getManifest() + { + try + { + return (jarfile == null) ? null : jarfile.getManifest(); + } + catch (IOException ioe) + { + return null; + } + } + } - public URL[] getURLs () + final static class JarURLResource extends Resource { - URL[] urls = new URL[path.size()]; - path.copyInto (urls); - return urls; + private final JarEntry entry; + + JarURLResource(JarURLLoader loader, String name, JarEntry entry) + { + super(loader, name); + this.entry = entry; + } + + InputStream getInputStream() throws IOException + { + return ((JarURLLoader)loader).jarfile.getInputStream(entry); + } + + int getLength() + { + return (int)entry.getSize(); + } + + Certificate[] getCertificates() + { + return entry.getCertificates(); + } + + URL getURL() + { + try + { + return new URL(((JarURLLoader)loader).baseJarURL, name, + loader.classloader.getURLStreamHandler("jar")); + } + catch(MalformedURLException e) + { + throw new InternalError(e.toString()); + } + } } - + /** - * Returns an Enumeration of URLs representing all of the resources on the - * URL search path having the specified name - * - * @exception IOException If an error occurs + * Loader for remote directories. */ - public Enumeration findResources (String name) + final static class RemoteURLLoader extends URLLoader { - Vector results = new Vector (); + final private String protocol; - for (int i = 0; i < path.size(); i++) - { - URL u = (URL)path.elementAt (i); - - try { - JarURLConnection conn = (JarURLConnection) info.elementAt (i); - - if (conn != null) + RemoteURLLoader(URLClassLoader classloader, URL url) + { + super(classloader, url); + protocol = url.getProtocol(); + } + + /** + * Get a remote resource. + * Returns null if no such resource exists. + */ + Resource getResource(String name) + { + try + { + URL url = new URL(baseURL, name, + classloader.getURLStreamHandler(protocol)); + URLConnection connection = url.openConnection(); + + // Open the connection and check the stream + // just to be sure it exists. + int length = connection.getContentLength(); + InputStream stream = connection.getInputStream(); + + // We can do some extra checking if it is a http request + if (connection instanceof HttpURLConnection) { - if (conn.getJarFile().getJarEntry (name) != null) - results.addElement (new URL(u, name, - getHandler0 (u.getProtocol()))); + int response + = ((HttpURLConnection)connection).getResponseCode(); + if (response/100 != 2) + return null; } + + if (stream != null) + return new RemoteResource(this, name, url, stream, length); else - { - URL p = new URL (u, name, getHandler0 (u.getProtocol())); - - InputStream is = p.openStream(); - if (is != null) - { - is.close(); - results.addElement (p); - } - } - - // if we get an exception ... try the next path element - } catch (IOException x) { - continue; + return null; } - } - - return results.elements (); + catch (IOException ioe) + { + return null; + } + } } - public URL findResource (String name) + /** + * A resource from some remote location. + */ + final static class RemoteResource extends Resource { - for (int i = 0; i < path.size(); i++) - { - URL u = (URL)path.elementAt (i); - - try { - JarURLConnection conn = (JarURLConnection) info.elementAt (i); - - if (conn != null) - { - if (conn.getJarFile().getJarEntry (name) != null) - return new URL(u, name, getHandler0 (u.getProtocol())); - } - else - { - URL p = new URL (u, name, getHandler0 (u.getProtocol())); + final private URL url; + final private InputStream stream; + final private int length; - InputStream is = p.openStream(); - if (is != null) - { - is.close(); - return p; - } - } - - // if we get an exception ... try the next path element - } catch (IOException x) { - continue; + RemoteResource(RemoteURLLoader loader, String name, URL url, + InputStream stream, int length) + { + super(loader, name); + this.url = url; + this.stream = stream; + this.length = length; + } + + InputStream getInputStream() throws IOException + { + return stream; + } + + public int getLength() + { + return length; + } + + public URL getURL() + { + return url; + } + } + + /** + * A FileURLLoader is a type of URLLoader + * only loading from file url. + */ + final static class FileURLLoader extends URLLoader + { + File dir; //the canonical file for this file url + + FileURLLoader(URLClassLoader classloader, URL url) + { + super(classloader, url); + // Note that this must be a "file" protocol URL. + dir = new File(url.getFile()); + } + + /** get resource with the name "name" in the file url */ + Resource getResource(String name) + { + File file = new File(dir, name); + if (file.exists() && !file.isDirectory()) + return new FileResource(this, name, file); + else + return null; + } + } + + final static class FileResource extends Resource + { + final File file; + + FileResource(FileURLLoader loader, String name, File file) + { + super(loader, name); + this.file = file; + } + + InputStream getInputStream() throws IOException + { + return new FileInputStream(file); + } + + public int getLength() + { + return (int)file.length(); + } + + public URL getURL() + { + try + { + return new URL(loader.baseURL, name, + loader.classloader.getURLStreamHandler("file")); } - } + catch(MalformedURLException e) + { + throw new InternalError(e.toString()); + } + } + } + + // Constructors - return null; + /** + * Creates a URLClassLoader that gets classes from the supplied URLs. + * To determine if this classloader may be created the constructor of + * the super class (SecureClassLoader) is called first, which + * can throw a SecurityException. Then the supplied URLs are added + * in the order given to the URLClassLoader which uses these URLs to + * load classes and resources (after using the default parent ClassLoader). + * + * @exception SecurityException if the SecurityManager disallows the + * creation of a ClassLoader. + * @param urls Locations that should be searched by this ClassLoader when + * resolving Classes or Resources. + * @see SecureClassLoader + */ + public URLClassLoader(URL[] urls) throws SecurityException + { + super(); + this.factory = null; + this.securityContext = null; + addURLs(urls); } /** - * Finds and loads the class with the specified name from the - * URL search path + * Private constructor used by the static + * newInstance(URL[]) method. Creates an + * URLClassLoader without any URLs + * yet. This is used to bypass the normal security check for + * creating classloaders, but remembers the security context which + * will be used when defining classes. The URLs to + * load from must be added by the newInstance() method + * in the security context of the caller. * - * @exception ClassNotFoundException If the class could not be found + * @param securityContext the security context of the unprivileged code. */ - protected Class findClass (String name) - throws ClassNotFoundException + private URLClassLoader(AccessControlContext securityContext) { - if (name == null) - throw new ClassNotFoundException ("null"); + super(); + this.factory = null; + this.securityContext = securityContext; + } - try - { - URL url = getResource (name.replace ('.', '/') + ".class"); + /** + * Creates a URLClassLoader that gets classes from the supplied + * URLs. + * To determine if this classloader may be created the constructor of + * the super class (SecureClassLoader) is called first, which + * can throw a SecurityException. Then the supplied URLs are added + * in the order given to the URLClassLoader which uses these URLs to + * load classes and resources (after using the supplied parent ClassLoader). + * @exception SecurityException if the SecurityManager disallows the + * creation of a ClassLoader. + * @exception SecurityException + * @param urls Locations that should be searched by this ClassLoader when + * resolving Classes or Resources. + * @param parent The parent class loader used before trying this class + * loader. + * @see SecureClassLoader + */ + public URLClassLoader(URL[] urls, ClassLoader parent) + throws SecurityException + { + super(parent); + this.factory = null; + this.securityContext = null; + addURLs(urls); + } - if (url == null) - throw new ClassNotFoundException (name); + /** + * Private constructor used by the static + * newInstance(URL[]) method. Creates an + * URLClassLoader with the given parent but without any + * URLs yet. This is used to bypass the normal security + * check for creating classloaders, but remembers the security + * context which will be used when defining classes. The + * URLs to load from must be added by the + * newInstance() method in the security context of the + * caller. + * + * @param securityContext the security context of the unprivileged code. + */ + private URLClassLoader(ClassLoader parent, + AccessControlContext securityContext) + { + super(parent); + this.factory = null; + this.securityContext = securityContext; + } - URLConnection connection = url.openConnection (); - InputStream is = connection.getInputStream (); + /** + * Creates a URLClassLoader that gets classes from the supplied URLs. + * To determine if this classloader may be created the constructor of + * the super class (SecureClassLoader) is called first, which + * can throw a SecurityException. Then the supplied URLs are added + * in the order given to the URLClassLoader which uses these URLs to + * load classes and resources (after using the supplied parent ClassLoader). + * It will use the supplied URLStreamHandlerFactory to get the + * protocol handlers of the supplied URLs. + * @exception SecurityException if the SecurityManager disallows the + * creation of a ClassLoader. + * @exception SecurityException + * @param urls Locations that should be searched by this ClassLoader when + * resolving Classes or Resources. + * @param parent The parent class loader used before trying this class + * loader. + * @param factory Used to get the protocol handler for the URLs. + * @see SecureClassLoader + */ + public URLClassLoader(URL[] urls, + ClassLoader parent, + URLStreamHandlerFactory factory) + throws SecurityException + { + super(parent); + this.securityContext = null; + this.factory = factory; + addURLs(urls); - int len = connection.getContentLength (); - byte[] data = new byte[len]; + // If this factory is still not in factoryCache, add it, + // since we only support three protocols so far, 5 is enough + // for cache initial size + synchronized(factoryCache) + { + if(factory != null && factoryCache.get(factory) == null) + factoryCache.put(factory, new HashMap(5)); + } + } - int left = len; - int off = 0; - while (left > 0) - { - int c = is.read (data, off, len-off); - if (c == -1 || c == 0) - throw new InternalError ("premature end of file"); - left -= c; - off += c; - } + // Methods - // Now construct the CodeSource (if loaded from a jar file) - CodeSource source = null; - if (url.getProtocol().equals("jar")) - { - Certificate[] certificates = - ((JarURLConnection) connection).getCertificates(); - String u = url.toExternalForm (); - u = u.substring (4); //skip "jar:" - int i = u.indexOf ('!'); - if (i >= 0) - u = u.substring (0, i); - url = new URL(u); - - source = new CodeSource(url, certificates); - } - else if (url.getProtocol().equals("file")) + /** + * Adds a new location to the end of the internal URL store. + * @param newUrl the location to add + */ + protected void addURL(URL newUrl) + { + synchronized(urlloaders) + { + if (newUrl == null) + return; // Silently ignore... + + // check global cache to see if there're already url loader + // for this url + URLLoader loader = (URLLoader)urlloaders.get(newUrl); + if (loader == null) { - try - { - String u = url.toExternalForm(); - // Skip "file:" and then get canonical directory name. - File f = new File(u.substring(5)); - f = f.getCanonicalFile(); - url = new URL("file", "", f.getParent()); - source = new CodeSource (url, null); - } - catch (IOException ignore) - { - } + String file = newUrl.getFile(); + // Check that it is not a directory + if (!(file.endsWith("/") || file.endsWith(File.separator))) + loader = new JarURLLoader(this, newUrl); + else // it's a url that point to a jar file + if ("file".equals(newUrl.getProtocol())) + loader = new FileURLLoader(this, newUrl); + else + loader = new RemoteURLLoader(this, newUrl); + + // cache it + urlloaders.put(newUrl, loader); } - return defineClass (name, data, 0, len, source); - } - catch (java.io.IOException x) - { - throw new ClassNotFoundException(name); + urls.add(newUrl); + urlinfos.add(loader); } } + /** + * Adds an array of new locations to the end of the internal URL store. + * @param newUrls the locations to add + */ + private void addURLs(URL[] newUrls) + { + for (int i = 0; i < newUrls.length; i++) + { + addURL(newUrls[i]); + } + } + /** * Defines a Package based on the given name and the supplied manifest * information. The manifest indicates the tile, version and @@ -362,36 +731,231 @@ public class URLClassLoader extends SecureClassLoader // Look if the Manifest indicates that this package is sealed // XXX - most likely not completely correct! // Shouldn't we also check the sealed attribute of the complete jar? - // http://java.sun.com/products/jdk/1.3/docs/guide/extensions/spec.html#bundled + // http://java.sun.com/products/jdk/1.4/docs/guide/extensions/spec.html#bundled // But how do we get that jar manifest here? String sealed = attr.getValue(Attributes.Name.SEALED); if ("false".equals(sealed)) - { - // Make sure that the URL is null so the package is not - // sealed. - url = null; - } + { + // make sure that the URL is null so the package is not sealed + url = null; + } return definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, url); } /** - * Returns the permissions needed to access a particular code source. - * These permissions includes those returned by - * SecureClassLoader.getPermissions and the actual permissions - * to access the objects referenced by the URL of the code source. - * The extra permissions added depend on the protocol and file portion of - * the URL in the code source. If the URL has the "file" protocol ends with - * a / character then it must be a directory and a file Permission to read - * everthing in that directory and all subdirectories is added. If the URL - * had the "file" protocol and doesn't end with a / character then it must - * be a normal file and a file permission to read that file is added. If the - * URL has any other protocol then a socket permission to connect and accept - * connections from the host portion of the URL is added. + * Finds (the first) class by name from one of the locations. The locations + * are searched in the order they were added to the URLClassLoader. + * + * @param className the classname to find + * @exception ClassNotFoundException when the class could not be found or + * loaded + * @return a Class object representing the found class + */ + protected Class findClass(final String className) + throws ClassNotFoundException + { + // Just try to find the resource by the (almost) same name + String resourceName = className.replace('.', '/') + ".class"; + Resource resource = findURLResource(resourceName); + if (resource == null) + throw new ClassNotFoundException(className + " not found in " + urls); + + // Try to read the class data, create the CodeSource, Package and + // construct the class (and watch out for those nasty IOExceptions) + try + { + byte [] data; + InputStream in = resource.getInputStream(); + int length = resource.getLength(); + if (length != -1) + { + // We know the length of the data. + // Just try to read it in all at once + data = new byte[length]; + int pos = 0; + while(length - pos > 0) + { + int len = in.read(data, pos, length - pos); + if (len == -1) + throw new EOFException("Not enough data reading from: " + + in); + pos += len; + } + } + else + { + // We don't know the data length. + // Have to read it in chunks. + ByteArrayOutputStream out = new ByteArrayOutputStream(4096); + byte b[] = new byte[4096]; + int l = 0; + while (l != -1) + { + l = in.read(b); + if (l != -1) + out.write(b, 0, l); + } + data = out.toByteArray(); + } + final byte[] classData = data; + + // Now get the CodeSource + final CodeSource source = resource.getCodeSource(); + + // Find out package name + String packageName = null; + int lastDot = className.lastIndexOf('.'); + if (lastDot != -1) + packageName = className.substring(0, lastDot); + + if (packageName != null && getPackage(packageName) == null) + { + // define the package + Manifest manifest = resource.loader.getManifest(); + if (manifest == null) + definePackage(packageName, + null, null, null, null, null, null, null); + else + definePackage(packageName, manifest, resource.loader.baseURL); + } + + // And finally construct the class! + SecurityManager sm = System.getSecurityManager(); + if (sm != null && securityContext != null) + { + return (Class)AccessController.doPrivileged + (new PrivilegedAction() + { + public Object run() + { + return defineClass(className, classData, + 0, classData.length, + source); + } + }, securityContext); + } + else + return defineClass(className, classData, + 0, classData.length, + source); + } + catch (IOException ioe) + { + throw new ClassNotFoundException(className, ioe); + } + } + + /** + * Finds the first occurrence of a resource that can be found. The locations + * are searched in the order they were added to the URLClassLoader. + * + * @param resourceName the resource name to look for + * @return the URLResource for the resource if found, null otherwise + */ + private Resource findURLResource(String resourceName) + { + int max = urls.size(); + for (int i = 0; i < max; i++) + { + URLLoader loader = (URLLoader)urlinfos.elementAt(i); + if (loader == null) + continue; + + Resource resource = loader.getResource(resourceName); + if (resource != null) + return resource; + } + return null; + } + + /** + * Finds the first occurrence of a resource that can be found. + * + * @param resourceName the resource name to look for + * @return the URL if found, null otherwise + */ + public URL findResource(String resourceName) + { + Resource resource = findURLResource(resourceName); + if (resource != null) + return resource.getURL(); + + // Resource not found + return null; + } + + /** + * If the URLStreamHandlerFactory has been set this return the appropriate + * URLStreamHandler for the given protocol, if not set returns null. + * + * @param protocol the protocol for which we need a URLStreamHandler + * @return the appropriate URLStreamHandler or null + */ + URLStreamHandler getURLStreamHandler(String protocol) + { + if (factory == null) + return null; + + URLStreamHandler handler; + synchronized (factoryCache) + { + // check if there're handler for the same protocol in cache + HashMap cache = (HashMap)factoryCache.get(factory); + handler = (URLStreamHandler)cache.get(protocol); + if(handler == null) + { + // add it to cache + handler = factory.createURLStreamHandler(protocol); + cache.put(protocol, handler); + } + } + return handler; + } + + /** + * Finds all the resources with a particular name from all the locations. + * + * @exception IOException when an error occurs accessing one of the + * locations + * @param resourceName the name of the resource to lookup + * @return a (possible empty) enumeration of URLs where the resource can be + * found + */ + public Enumeration findResources(String resourceName) throws IOException + { + Vector resources = new Vector(); + int max = urls.size(); + for (int i = 0; i < max; i++) + { + URLLoader loader = (URLLoader)urlinfos.elementAt(i); + Resource resource = loader.getResource(resourceName); + if (resource != null) + resources.add(resource.getURL()); + } + return resources.elements(); + } + + /** + * Returns the permissions needed to access a particular code + * source. These permissions includes those returned by + * SecureClassLoader.getPermissions() and the actual + * permissions to access the objects referenced by the URL of the + * code source. The extra permissions added depend on the protocol + * and file portion of the URL in the code source. If the URL has + * the "file" protocol ends with a '/' character then it must be a + * directory and a file Permission to read everything in that + * directory and all subdirectories is added. If the URL had the + * "file" protocol and doesn't end with a '/' character then it must + * be a normal file and a file permission to read that file is + * added. If the URL has any other protocol then a + * socket permission to connect and accept connections from the host + * portion of the URL is added. + * * @param source The codesource that needs the permissions to be accessed * @return the collection of permissions needed to access the code resource - * @see SecureClassLoader.getPermissions() + * @see java.security.SecureClassLoader#getPermissions() */ protected PermissionCollection getPermissions(CodeSource source) { @@ -408,7 +972,7 @@ public class URLClassLoader extends SecureClassLoader { String file = url.getFile(); // If the file end in / it must be an directory - if (file.endsWith("/")) + if (file.endsWith("/") || file.endsWith(File.separator)) { // Grant permission to read everything in that directory and // all subdirectories @@ -424,35 +988,81 @@ public class URLClassLoader extends SecureClassLoader else { // Grant permission to connect to and accept connections from host - String host = url.getHost(); + String host = url.getHost(); + if (host != null) permissions.add(new SocketPermission(host, "connect,accept")); } return permissions; } + + /** + * Returns all the locations that this class loader currently uses the + * resolve classes and resource. This includes both the initially supplied + * URLs as any URLs added later by the loader. + * @return All the currently used URLs + */ + public URL[] getURLs() + { + return (URL[]) urls.toArray(new URL[urls.size()]); + } /** - * Creates a new instance of a URLClassLoader that gets classes from the - * supplied URLs. This class loader will have as parent the standard - * system class loader. - * @param urls the initial URLs used to resolve classes and resources + * Creates a new instance of a URLClassLoader that gets + * classes from the supplied URLs. This class loader + * will have as parent the standard system class loader. + * + * @param urls the initial URLs used to resolve classes and + * resources + * + * @exception SecurityException when the calling code does not have + * permission to access the given URLs */ - public static URLClassLoader newInstance(URL[] urls) throws - SecurityException + public static URLClassLoader newInstance(URL urls[]) + throws SecurityException { - return new URLClassLoader(urls); + return newInstance(urls, null); } /** - * Creates a new instance of a URLClassLoader that gets classes from the - * supplied URLs and with the supplied loader as parent class loader. - * @param urls the initial URLs used to resolve classes and resources + * Creates a new instance of a URLClassLoader that gets + * classes from the supplied URLs and with the supplied + * loader as parent class loader. + * + * @param urls the initial URLs used to resolve classes and + * resources * @param parent the parent class loader + * + * @exception SecurityException when the calling code does not have + * permission to access the given URLs */ - public static URLClassLoader newInstance(URL[] urls, - ClassLoader parent) + public static URLClassLoader newInstance(URL urls[], + final ClassLoader parent) throws SecurityException { - return new URLClassLoader(urls, parent); + SecurityManager sm = System.getSecurityManager(); + if (sm == null) + return new URLClassLoader(urls, parent); + else + { + final Object securityContext = sm.getSecurityContext(); + // XXX - What to do with anything else then an AccessControlContext? + if (!(securityContext instanceof AccessControlContext)) + throw new SecurityException + ("securityContext must be AccessControlContext: " + + securityContext); + + URLClassLoader loader = + (URLClassLoader)AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + return new URLClassLoader + (parent, (AccessControlContext)securityContext); + } + }); + loader.addURLs(urls); + return loader; + } } } -- 2.30.2