Hi, In an earlier message I said that since java.net.URLClassLoader was based on SecureClassLoader which depends on some 1.2 features not yet implemented in Classpath it would not be I good idea to use it for loading Locales. But that did not stop me from trying to implement it. (But I wise that I had followed my own advise since it depends on a lot of tricky issues with ClassLoaders, Security and Jar Manifests :) Attached is a hopefully fairly complete and not to buggy implementation. But it is not tested since there is no 1.2 support yet. (There are also some notes in the comments about issues I was not completely sure of.) I did implement java.lang.Package so I could at least almost compile it with Classpath. And there is also a diff for JarURLConnection attached to change the getIdentities() method to a getCertificates() method which URLClassLoader also needs. I will now start on implementing java.util.jar so I can at least compile everything with Classpath. And if that is finished and Loren has not yet surfaced then I want to implement java.util.zip. Cheers, Mark
/************************************************************************* /* URLClassLoader.java -- Class loader that loads classes from one or more URLs /* /* Copyright (c) 1999 Free Software Foundation, Inc. /* Written by Mark Wielaard ([EMAIL PROTECTED]) /* /* This library is free software; you can redistribute it and/or modify /* it under the terms of the GNU Library General Public License as published /* by the Free Software Foundation, either version 2 of the License, or /* (at your option) any later verion. /* /* This library 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 Library General Public License for more details. /* /* You should have received a copy of the GNU Library General Public License /* along with this library; if not, write to the Free Software Foundation /* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA /*************************************************************************/ package java.net; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.FilePermission; import java.security.CodeSource; import java.security.SecureClassLoader; import java.security.PermissionCollection; import java.security.cert.Certificate; import java.util.Enumeration; import java.util.Vector; import java.util.jar.Attributes; import java.util.jar.Manifest; /** * 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. * <p> * New instances can be created by two static newInstance() methods or by * three public contructors. Both give the option to supply an initial array * of URLs and (optionally) a parent classloader (that is different from the * standard system class loader). * Creating a <code>URLClassLoader</code> can always throw a * <code>SecurityException</code> if the checkes performed by the constructor of * <code>SecureClassLoader</code>, which are called when creating a new * instance, fail. * Note that only subclasses 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 * <code>getURLs()</code> method. * <p> * XXX - TODO - FIXME This implementation is not finished or tested!<br> * Open issues: * <ul> * <li>Should the URLClassLoader actually add the locations found in the * manifest or is this the responsibility of some other loader/(sub)class? * (see <a href="http://java.sun.com/products/jdk/1.3/docs/guide/extensions/spec.html#bundled">Extension Machanism Architecture - Bundles Extensions</a>) * <li> How does <code>definePackage()</code> and sealing work precisely? * (Note that the method is currently not even implemented in * <code>java.lang.ClassLoader</code>.) * <li> Should the static <code>newInstance()</code> methods always succeed? Or * are they also restricted by the normal security checks? * <li> We might want to do some caching of URLs, Connections or JarFiles. * For now we just assume that the (Jar)URLConnection takes care of this. * <li> Is the way we create the "jar" URLs correct? What if it was already a * "jar" URL? * <li> XXX * </ul> * * @author Mark Wielaard ([EMAIL PROTECTED]) */ public class URLClassLoader extends SecureClassLoader { // Variables /** Locations to load classes from */ private Vector urls = new Vector(); /** Factory used to get the protocol handlers of the URLs */ private URLStreamHandlerFactory factory = null; // Constructors /** * Creates a URLClassLoader that gets classes from the supplied URLs. * To determine if this classloader may be created the constructor of * the super class (<CODE>SecureClassLoader</CODE>) 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(); addURLs(urls); } /** * Creates a URLClassLoader that gets classes from the supplied URLs. * To determine if this classloader may be created the constructor of * the super class (<CODE>SecureClassLoader</CODE>) 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); addURLs(urls); } /** * Creates a URLClassLoader that gets classes from the supplied URLs. * To determine if this classloader may be created the constructor of * the super class (<CODE>SecureClassLoader</CODE>) 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 <CODE>URLStreamHandlerFactory</CODE> 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); addURLs(urls); this.factory = factory; } // Methods /** * Adds a new location to the end of the internal URL store. * @param newUrl the location to add */ protected void addURL(URL newUrl) { urls.add(newUrl); } /** * 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 * vendor information of the specification and implementation and wheter the * package is sealed. If the Manifest indicates that the package is sealed * then the Package will be sealed with respect to the supplied URL. * * @exception IllegalArgumentException If this package name already exists * in this class loader * @param name The name of the package * @param manifest The manifest describing the specification, * implementation and sealing details of the package * @param url the code source url to seal the package * @return the defined Package */ protected Package definePackage(String name, Manifest manifest, URL url) throws IllegalArgumentException { Attributes attr = manifest.getMainAttributes(); String specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE); String specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION); String specVendor = attr.getValue(Attributes.Name.SPECIFICATION_VENDOR); String implTitle = attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE); String implVersion = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION); String implVendor = attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR); // 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 // 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; } // XXX - Since ClassLoader.definePackage is not implemented return null return null; // XXX - Replace it with this return when ClassLoader.definePackage() // is actually implemented. // return definePackage(name, specTitle, specVersion, specVendor, // implTitle, implVersion, implVendor, url); } /** * 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(String className) throws ClassNotFoundException { // Just try to find the resource by the (almost) same name String resourceName = className.replace('.', '/') + ".class"; URL url = findResource(resourceName); if (url == null) { throw new ClassNotFoundException(className); } // Try to read the class data, create the CodeSource and construct the // class (and watch out for those nasty IOExceptions) try { byte [] classData; URLConnection conn = url.openConnection(); InputStream in = conn.getInputStream(); int length = conn.getContentLength(); if (length != -1) { // We know the length of the data, just read it all in at once classData = new byte[length]; in.read(classData); } else { // We don't know the data length so we have to read it in chunks ByteArrayOutputStream out = new ByteArrayOutputStream(1024); byte b[] = new byte[1024]; int l = 0; while (l != -1) { l = in.read(b); if (l != -1) { out.write(b, 0, l); } } classData = out.toByteArray(); } // Now construct the CodeSource (if loaded from a jar file) CodeSource source = null; if (url.getProtocol().equals("jar")) { Certificate[] certificates = ((JarURLConnection)conn).getCertificates(); source = new CodeSource(url, certificates); } // And finally construct the class! 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 URL if found, null otherwise */ public URL findResource(String resourceName) { Enumeration e = urls.elements(); while (e.hasMoreElements()) { URL url = findResource(resourceName, (URL)e.nextElement()); if (url != null) { // Found the resource return url; } } // Resource not found return null; } /** * Find a resource relative to a base URL. If the base URL ends with a / * character then the base URL is interpreted as a directory and the * resource name is appended to it, otherwise the base URL is interpreted * as a jar file and a "jar" URL is constructed from the base URL and the * supplied resource name. This method tries to open a connection to the * resulting URL to make sure the resource is actually there. * <p> * XXX - We might want to do some caching of URLs, Connections or JarFiles. * For now we just assume that the (Jar)URLConnection takes care of this. * XXX - Should we also disconnect/close the URLConnection again? And how * would we do that? For now just assume the connection will actually be * used. * @param resourceName the resource name to look for * @param url the base URL * @return the URL if found, null otherwise * @see JarURLConnection */ private URL findResource(String resourceName, URL url) { URL resourceURL; // Get the file portion of the base URL String file = url.getFile(); // Construct the resourceURL if (file.endsWith("/")) { // Interpret it as a directory and just append the resource name try { resourceURL = new URL(url, resourceName, createURLStreamHandler(url.getProtocol())); } catch (MalformedURLException e) { return null; } } else { // Interpret it as a jar file and construct a "jar" URL String external = url.toExternalForm(); try { // XXX - Is this correct? Why is there no URL constructor that // takes just a string and a URLStreamHandler? resourceURL = new URL(new URL("jar:" + external + "!/"), resourceName, createURLStreamHandler("jar")); } catch (MalformedURLException e) { return null; } } // Check if the resource is actually at that location try { resourceURL.openConnection(); } catch (IOException ioe) { return null; } return resourceURL; } /** * If the URLStreamHandlerFactory has been set this return the appropriate * URLStreamHandler for the given protocol. * @param protocol the protocol for which we need a URLStreamHandler * @return the appropriate URLStreamHandler or null */ private URLStreamHandler createURLStreamHandler(String protocol) { if (factory != null) { return factory.createURLStreamHandler(protocol); } else { return null; } } /** * 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(); Enumeration e = urls.elements(); while (e.hasMoreElements()) { URL url = findResource(resourceName, (URL)e.nextElement()); if (url != null) { // Found another resource resources.add(url); } } return resources.elements(); } /** * Returns the permissions needed to access a particular code source. * These permissions includes those returned by * <CODE>SecureClassLoader.getPermissions</CODE> 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. * @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() */ protected PermissionCollection getPermissions(CodeSource source) { // XXX - This implementation does exactly as the Javadoc describes. // But maybe we should/could use URLConnection.getPermissions()? // First get the permissions that would normally be granted PermissionCollection permissions = super.getPermissions(source); // Now add the any extra permissions depending on the URL location URL url = source.getLocation(); String protocol = url.getProtocol(); if (protocol.equals("file")) { String file = url.getFile(); // If the file end in / it must be an directory if (file.endsWith("/")) { // Grant permission to read everything in that directory and // all subdirectories permissions.add(new FilePermission(file + "-", "read")); } else { // It is a 'normal' file // Grant permission to access that file permissions.add(new FilePermission(file, "read")); } } else { // Grant permission to connect to and accept connections from host String host = url.getHost(); 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(); } /** * 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 */ public static URLClassLoader newInstance(URL urls[]) throws SecurityException { return new URLClassLoader(urls); } /** * 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 */ public static URLClassLoader newInstance(URL urls[], ClassLoader parent) throws SecurityException { return new URLClassLoader(urls, parent); } }
/* * java.lang.Package: part of the Java Class Libraries project. * Copyright (C) 1999 Free Software Foundation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package java.lang; import java.net.URL; import java.util.NoSuchElementException; import java.util.StringTokenizer; /** * Everything you ever wanted to know about a package. This class makes it * possible to attach specification and implementation information to a * package as explained in the * <a href="http://java.sun.com/products/jdk/1.3/docs/guide/versioning/spec/VersioningSpecification.html#PackageVersionSpecification">Package Versioning Specification</a> * section of the * <a href="http://java.sun.com/products/jdk/1.3/docs/guide/versioning/spec/VersioningSpecification.html">Product Versioning Specification</a>. * It also allows packages to be sealed with respect to the originating URL. * <p> * The most usefull method is the <code>isCompatibleWith()</code> method that * compares a desired version of a specification with the version of the * specification as implemented by a package. A package is considered compatible * with another version if the version of the specification is equal or higer * then the requested version. Version numbers are represented as strings of * positive numbers seperated by dots (e.g. "1.2.0"). The first number is called * the major number, the second the minor, the third the micro, etc. A version * is considered higher then another version if it has a bigger major number * then the another version or when the major numbers of the versions are equal * if it has a bigger minor number then the other version, etc. (If a version * has no minor, micro, etc numbers then they are considered the be 0.) * * @since JDK1.2 * @author Mark Wielaard * @version 1.0, 19 Dec 1999 */ public class Package { // Variables /** The name of the Package */ final private String name; /** The name if the implementation */ final private String implTitle; /** The vendor that wrote this implementation */ final private String implVendor; /** The version of this implementation */ final private String implVersion; /** The name of the specification */ final private String specTitle; /** The name of the specification designer */ final private String specVendor; /** The version of this specification */ final private String specVersion; /** If sealed the origin of the package classes */ final private URL sealed; // Constructors /** * A package local constructor for the Package class. * XXX - There are no public constructors defined for Package so I just * invented a package local constructor that can be used by classes in * java.lang. * * @param name The name of the Package * @param implTitle The name of the implementation * @param implVendor The vendor that wrote this implementation * @param implVersion The version of this implementation * @param specTitle The name of the specification * @param specVendor The name of the specification designer * @param specVersion The version of this specification * @param sealed If sealed the origin of the package classes */ Package(String name, String implTitle, String implVendor, String implVersion, String specTitle, String specVendor, String specVersion, URL sealed) { this.name = name; this.implTitle = implTitle; this.implVendor = implVendor; this.implVersion = implVersion; this.specTitle = specTitle; this.specVendor = specVendor; this.specVersion = specVersion; this.sealed = sealed; } // Methods /** * Returns the Package name. */ public String getName() { return name; } /** * Returns the name of the implementation or null if unknown. */ public String getImplementationTitle() { return implTitle; } /** * Returns the vendor that wrote this implementation or null if unknown. */ public String getImplementationVendor() { return implVendor; } /** * Returns the version of this implementation or null if unknown. */ public String getImplementationVersion() { return implVersion; } /** * Returns the name of the specification or null if unknown. */ public String getSpecificationTitle() { return specTitle; } /** * Returns the name of the specification designer. */ public String getSpecificationVendor() { return specVendor; } /** * Returns the version of the specification or null if unknown. */ public String getSpecificationVersion() { return specVersion; } /** * Returns true if this Package is sealed. */ public boolean isSealed() { return (sealed != null); } /** * Returns true if this Package is sealed and the origin of the classes is * the given URL. * * @param url */ public boolean isSealed(URL url) { return url.equals(sealed); } /** * Checks if the version of the specification is higher or at least as high * as the desired version. * XXX - It may throw unexpected (NullPointer) exceptions when the supplied * version or the specification version are null. * @param version the (minimal) desired version of the specification * @exception NumberFormatException when either version or the specification * version is not a correctly formatted version number */ public boolean isCompatibleWith(String version) throws NumberFormatException { StringTokenizer versionTokens = new StringTokenizer(version, "."); StringTokenizer specTokens = new StringTokenizer(specVersion, "."); try { while (versionTokens.hasMoreElements()) { int vers = Integer.parseInt(versionTokens.nextToken()); int spec = Integer.parseInt(specTokens.nextToken()); if (spec < vers) { return false; } else if (spec > vers) { return true; } // They must be equal, next Token please! } } catch (NoSuchElementException e) { // this must have been thrown by spec.netToken() so return false return false; } // They must have been exactly the same version. // Or the specVersion has more subversions. That is also good. return true; } /** * Returns the named package if it is known by the callers class loader. * It may return null if the package is unknown or when there is no * information on that particular package available. * XXX - Since ClassLoader.getPackage() is not yet implemented it just * returns null. * @param name the name of the desired package */ public static Package getPackage(String name) { return null; } /** * Returns all the packages that are known to the callers class loader. * XXX - Since ClassLoader.getPackages() is not yet implemented it just * returns null. */ public static Package[] getPackages() { return null; } /** * Returns the hashCode of the name of this package. */ public int hashCode() { return name.hashCode(); } /** * Returns a string representation of this package name, specification, * implementation and class origin if sealed. */ public String toString() { return "package: " + name + " spec: " + specTitle + " version: " + specVersion + " vendor: " + specVendor + " implementation: " + implTitle + " version: " + implVersion + " vendor: " + implVendor + " sealed: " + sealed; } }
--- JarURLConnection.java 1998/07/24 01:59:40 1.2 +++ JarURLConnection.java 1999/12/19 22:19:51 @@ -22,7 +22,7 @@ package java.net; import java.io.IOException; -import java.security.Identity; +import java.security.cert.Certificate; import java.util.jar.*; /** @@ -222,17 +222,17 @@ /*************************************************************************/ /** - * Returns an array of Identity objects for the jar file entry specified + * Returns an array of Certificate objects for the jar file entry specified * by this URL or null if there are none * - * @return An Identity array + * @return A Certificate array * * @exception IOException If an error occurs */ -public Identity[] -getIdentities() throws IOException +public Certificate[] +getCertificates() throws IOException { - return(getJarEntry().getIdentities()); + return(getJarEntry().getCertificates()); } } // class JarURLConnection