| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package java.net; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FilePermission; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.security.AccessControlContext; |
| import java.security.AccessController; |
| import java.security.CodeSource; |
| import java.security.PermissionCollection; |
| import java.security.PrivilegedAction; |
| import java.security.SecureClassLoader; |
| import java.security.cert.Certificate; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| import java.util.Vector; |
| import java.util.jar.Attributes; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| import java.util.zip.ZipEntry; |
| |
| import org.apache.harmony.luni.util.InvalidJarIndexException; |
| import org.apache.harmony.luni.util.Msg; |
| |
| /** |
| * This class loader is responsible for loading classes and resources from a |
| * list of URLs which can refer to either directories or JAR files. Classes |
| * loaded by this {@code URLClassLoader} are granted permission to access the |
| * URLs contained in the URL search list. |
| * |
| * @since Android 1.0 |
| */ |
| public class URLClassLoader extends SecureClassLoader { |
| |
| private static URL[] NO_PATH = new URL[0]; |
| |
| @SuppressWarnings("unchecked") |
| private static <K, V> Hashtable<K, V>[] newHashtableArray(int size) { |
| return new Hashtable[size]; |
| } |
| |
| URL[] urls, orgUrls; |
| |
| Set<URL> invalidUrls = Collections.synchronizedSet(new HashSet<URL>()); |
| |
| private Map<URL, JarFile> resCache = |
| Collections.synchronizedMap(new IdentityHashMap<URL, JarFile>(32)); |
| |
| private Object lock = new Object(); |
| |
| private URLStreamHandlerFactory factory; |
| |
| HashMap<URL, URL[]> extensions; |
| |
| Hashtable<String, URL[]>[] indexes; |
| |
| private AccessControlContext currentContext; |
| |
| static class SubURLClassLoader extends URLClassLoader { |
| // The subclass that overwrites the loadClass() method |
| private boolean checkingPackageAccess = false; |
| |
| SubURLClassLoader(URL[] urls) { |
| super(urls, ClassLoader.getSystemClassLoader()); |
| } |
| |
| SubURLClassLoader(URL[] urls, ClassLoader parent) { |
| super(urls, parent); |
| } |
| |
| /** |
| * Overrides the {@code loadClass()} of {@code ClassLoader}. It calls |
| * the security manager's {@code checkPackageAccess()} before |
| * attempting to load the class. |
| * |
| * @return the Class object. |
| * @param className |
| * String the name of the class to search for. |
| * @param resolveClass |
| * boolean indicates if class should be resolved after |
| * loading. |
| * @throws ClassNotFoundException |
| * If the class could not be found. |
| */ |
| @Override |
| protected synchronized Class<?> loadClass(String className, |
| boolean resolveClass) throws ClassNotFoundException { |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm != null && !checkingPackageAccess) { |
| int index = className.lastIndexOf('.'); |
| if (index > 0) { // skip if class is from a default package |
| try { |
| checkingPackageAccess = true; |
| sm.checkPackageAccess(className.substring(0, index)); |
| } finally { |
| checkingPackageAccess = false; |
| } |
| } |
| } |
| return super.loadClass(className, resolveClass); |
| } |
| } |
| |
| /** |
| * Constructs a new {@code URLClassLoader} instance. The newly created |
| * instance will have the system ClassLoader as its parent. URLs that end |
| * with "/" are assumed to be directories, otherwise they are assumed to be |
| * JAR files. |
| * |
| * @param urls |
| * the list of URLs where a specific class or file could be |
| * found. |
| * @throws SecurityException |
| * if a security manager exists and its {@code |
| * checkCreateClassLoader()} method doesn't allow creation of |
| * new ClassLoaders. |
| * @since Android 1.0 |
| */ |
| public URLClassLoader(URL[] urls) { |
| this(urls, ClassLoader.getSystemClassLoader(), null); |
| } |
| |
| /** |
| * Constructs a new URLClassLoader instance. The newly created instance will |
| * have the system ClassLoader as its parent. URLs that end with "/" are |
| * assumed to be directories, otherwise they are assumed to be JAR files. |
| * |
| * @param urls |
| * the list of URLs where a specific class or file could be |
| * found. |
| * @param parent |
| * the class loader to assign as this loader's parent. |
| * @throws SecurityException |
| * if a security manager exists and its {@code |
| * checkCreateClassLoader()} method doesn't allow creation of |
| * new class loaders. |
| * @since Android 1.0 |
| */ |
| public URLClassLoader(URL[] urls, ClassLoader parent) { |
| this(urls, parent, null); |
| } |
| |
| /** |
| * Adds the specified URL to the search list. |
| * |
| * @param url |
| * the URL which is to add. |
| * @since Android 1.0 |
| */ |
| protected void addURL(URL url) { |
| try { |
| URL search = createSearchURL(url); |
| urls = addURL(urls, search); |
| orgUrls = addURL(orgUrls, url); |
| synchronized (extensions) { |
| extensions.put(search, null); |
| } |
| } catch (MalformedURLException e) { |
| } |
| } |
| |
| /** |
| * Returns an array with the given URL added to the given array. |
| * |
| * @param urlArray |
| * {@code java.net.URL[]} the source array |
| * @param url |
| * {@code java.net.URL} the URL to be added |
| * @return java.net.URL[] an array made of the given array and the new URL |
| */ |
| URL[] addURL(URL[] urlArray, URL url) { |
| URL[] newPath = new URL[urlArray.length + 1]; |
| System.arraycopy(urlArray, 0, newPath, 0, urlArray.length); |
| newPath[urlArray.length] = url; |
| Hashtable<String, URL[]>[] newIndexes = newHashtableArray(indexes.length + 1); |
| System.arraycopy(indexes, 0, newIndexes, 0, indexes.length); |
| indexes = newIndexes; |
| return newPath; |
| } |
| |
| /** |
| * Returns all known URLs which point to the specified resource. |
| * |
| * @param name |
| * the name of the requested resource. |
| * @return the enumeration of URLs which point to the specified resource. |
| * @throws IOException |
| * if an I/O error occurs while attempting to connect. |
| * @since Android 1.0 |
| */ |
| @Override |
| public Enumeration<URL> findResources(final String name) throws IOException { |
| if (name == null) { |
| return null; |
| } |
| Vector<URL> result = AccessController.doPrivileged( |
| new PrivilegedAction<Vector<URL>>() { |
| public Vector<URL> run() { |
| return findResources(urls, name, new Vector<URL>()); |
| } |
| }, currentContext); |
| SecurityManager sm; |
| int length = result.size(); |
| if (length > 0 && (sm = System.getSecurityManager()) != null) { |
| Vector<URL> reduced = new Vector<URL>(length); |
| for (int i = 0; i < length; i++) { |
| URL url = result.elementAt(i); |
| try { |
| sm.checkPermission(url.openConnection().getPermission()); |
| reduced.addElement(url); |
| } catch (IOException e) { |
| } catch (SecurityException e) { |
| } |
| } |
| result = reduced; |
| } |
| return result.elements(); |
| } |
| |
| /** |
| * Returns a Vector of URLs among the given ones that contain the specified |
| * resource. |
| * |
| * @return Vector the enumeration of URLs that contain the specified |
| * resource. |
| * @param searchURLs |
| * {@code java.net.URL[]} the array to be searched. |
| * @param name |
| * {@code java.lang.String} the name of the requested resource. |
| */ |
| Vector<URL> findResources(URL[] searchURLs, String name, Vector<URL> result) { |
| boolean findInExtensions = searchURLs == urls; |
| for (int i = 0; i < searchURLs.length; i++) { |
| if (!invalidUrls.contains(searchURLs[i])) { |
| URL[] search = new URL[] { searchURLs[i] }; |
| URL res = findResourceImpl(search, name); |
| if (!invalidUrls.contains(search[0])) { |
| if (res != null && !result.contains(res)) { |
| result.addElement(res); |
| } |
| if (findInExtensions) { |
| findInExtensions(explore(searchURLs[i], i), name, i, |
| result, false); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Returns an {@code Object[]} containing a class, a URL, and a vector of |
| * URLs, two of which are {@code null}, according to the caller, which is |
| * identified by the {@code int} type. |
| * |
| * @return Object[] a 3-element array : {Class, URL, Vector}. The non-null |
| * element contains the resource(s) found, which are searched in in |
| * {@code indexes[i]}. |
| * @param i |
| * the index of 'indexes' array to use. |
| * @param name |
| * the resource to look for : either a resource or a class. |
| * @param resources |
| * {@code boolean} indicates that a vector of URL should be |
| * returned as the non {@code null} element in {@code Object[]}. |
| * @param url |
| * if {@code true} a URL should be returned as the non-null |
| * element, if {@code false} a class should be returned. |
| */ |
| Object findInIndex(int i, String name, Vector<URL> resources, boolean url) { |
| Hashtable<String, URL[]> index = indexes[i]; |
| if (index != null) { |
| int pos = name.lastIndexOf("/"); //$NON-NLS-1$ |
| // only keep the directory part of the resource |
| // as index.list only keeps track of directories and root files |
| String indexedName = (pos > 0) ? name.substring(0, pos) : name; |
| URL[] jarURLs; |
| if (resources != null) { |
| jarURLs = index.get(indexedName); |
| if (jarURLs != null) { |
| findResources(jarURLs, name, resources); |
| } |
| } else if (url) { |
| jarURLs = index.get(indexedName); |
| if (jarURLs != null) { |
| return findResourceImpl(jarURLs, name); |
| } |
| } else { |
| String clsName = name; |
| String partialName = clsName.replace('.', '/'); |
| int position; |
| if ((position = partialName.lastIndexOf('/')) != -1) { |
| String packageName = partialName.substring(0, position); |
| jarURLs = index.get(packageName); |
| } else { |
| String className = partialName.substring(0, partialName |
| .length()) |
| + ".class"; //$NON-NLS-1$ |
| jarURLs = index.get(className); |
| } |
| if (jarURLs != null) { |
| Class<?> c = findClassImpl(jarURLs, clsName); |
| // InvalidJarException is thrown when a mapping for a class |
| // is not valid, i.e. we can't find the class by following |
| // the mapping. |
| if (c == null) { |
| throw new InvalidJarIndexException(); |
| } |
| return c; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns an {@code Object[]} containing a Class, a URL, and a Vector of |
| * URLs, two of which are {@code null}, according to the caller, which is |
| * identified by the {@code int} type. |
| * |
| * @return Object[] a 3-element array : {Class, URL, Vector}. The non-null |
| * element contains the resource(s) found, which are searched in |
| * newExtensions. |
| * @param newExtensions |
| * URL[] the URLs to look in for. |
| * @param name |
| * the resource to look for : either a resource or a class. |
| * @param i |
| * the index of 'indexes' array to use. |
| * @param resources |
| * indicates that a Vector of URL should be returned as the |
| * non-null element in {@code Object[]}. |
| * @param url |
| * if {@code true} a URL should be returned as the non-null |
| * element, if {@code false} a class should be returned. |
| */ |
| Object findInExtensions(URL[] newExtensions, String name, int i, |
| Vector<URL> resources, boolean url) { |
| if (newExtensions != null) { |
| for (int k = 0; k < newExtensions.length; k++) { |
| if (newExtensions[k] != null) { |
| URL[] search = new URL[] { newExtensions[k] }; |
| if (resources != null) { |
| URL res = findResourceImpl(search, name); |
| if (!invalidUrls.contains(search[0])) { // the URL does |
| // not exist |
| if (res != null && !resources.contains(res)) { |
| resources.addElement(res); |
| } |
| findInExtensions(explore(newExtensions[k], i), |
| name, i, resources, url); |
| } |
| } else { |
| Object result; |
| if (url) { |
| result = findResourceImpl(search, name); |
| } else { |
| result = findClassImpl(search, name); |
| } |
| if (result != null) { |
| return result; |
| } |
| if (!invalidUrls.contains(search[0])) { // the URL |
| // exists |
| result = findInExtensions(explore(newExtensions[k], |
| i), name, i, null, url); |
| if (result != null) { |
| return result; |
| } |
| } |
| } |
| } |
| } |
| } else { |
| try { |
| return findInIndex(i, name, resources, url); |
| } catch (InvalidJarIndexException ex) { |
| // Ignore misleading/wrong jar index |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Converts an input stream into a byte array. |
| * |
| * @return byte[] the byte array |
| * @param is |
| * the input stream |
| */ |
| private static byte[] getBytes(InputStream is, boolean readAvailable) |
| throws IOException { |
| if (readAvailable) { |
| byte[] buf = new byte[is.available()]; |
| is.read(buf, 0, buf.length); |
| is.close(); |
| return buf; |
| } |
| byte[] buf = new byte[4096]; |
| int size = is.available(); |
| if (size < 1024) { |
| size = 1024; |
| } |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(size); |
| int count; |
| while ((count = is.read(buf)) > 0) { |
| bos.write(buf, 0, count); |
| } |
| return bos.toByteArray(); |
| } |
| |
| /** |
| * Gets all permissions for the specified {@code codesource}. First, this |
| * method retrieves the permissions from the system policy. If the protocol |
| * is "file:/" then a new permission, {@code FilePermission}, granting the |
| * read permission to the file is added to the permission collection. |
| * Otherwise, connecting to and accepting connections from the URL is |
| * granted. |
| * |
| * @param codesource |
| * the code source object whose permissions have to be known. |
| * @return the list of permissions according to the code source object. |
| * @since Android 1.0 |
| */ |
| @Override |
| protected PermissionCollection getPermissions(final CodeSource codesource) { |
| PermissionCollection pc = super.getPermissions(codesource); |
| URL u = codesource.getLocation(); |
| if (u.getProtocol().equals("jar")) { //$NON-NLS-1$ |
| try { |
| // Create a URL for the resource the jar refers to |
| u = ((JarURLConnection) u.openConnection()).getJarFileURL(); |
| } catch (IOException e) { |
| // This should never occur. If it does continue using the jar |
| // URL |
| } |
| } |
| if (u.getProtocol().equals("file")) { //$NON-NLS-1$ |
| String path = u.getFile(); |
| String host = u.getHost(); |
| if (host != null && host.length() > 0) { |
| path = "//" + host + path; //$NON-NLS-1$ |
| } |
| |
| if (File.separatorChar != '/') { |
| path = path.replace('/', File.separatorChar); |
| } |
| if (isDirectory(u)) { |
| pc.add(new FilePermission(path + "-", "read")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } else { |
| pc.add(new FilePermission(path, "read")); //$NON-NLS-1$ |
| } |
| } else { |
| String host = u.getHost(); |
| if (host.length() == 0) { |
| host = "localhost"; //$NON-NLS-1$ |
| } |
| pc.add(new SocketPermission(host, "connect, accept")); //$NON-NLS-1$ |
| } |
| return pc; |
| } |
| |
| /** |
| * Returns the search list of this {@code URLClassLoader}. |
| * |
| * @return the list of all known URLs of this instance. |
| * @since Android 1.0 |
| */ |
| public URL[] getURLs() { |
| return orgUrls.clone(); |
| } |
| |
| /** |
| * Determines if the URL is pointing to a directory. |
| */ |
| private static boolean isDirectory(URL url) { |
| String file = url.getFile(); |
| return (file.length() > 0 && file.charAt(file.length() - 1) == '/'); |
| } |
| |
| /** |
| * Returns a new {@code URLClassLoader} instance for the given URLs and the |
| * system {@code ClassLoader} as its parent. The method {@code loadClass()} |
| * of the new instance will call {@code |
| * SecurityManager.checkPackageAccess()} before loading a class. |
| * |
| * @param urls |
| * the list of URLs that is passed to the new {@code |
| * URLClassloader}. |
| * @return the created {@code URLClassLoader} instance. |
| * @since Android 1.0 |
| */ |
| public static URLClassLoader newInstance(final URL[] urls) { |
| URLClassLoader sub = AccessController |
| .doPrivileged(new PrivilegedAction<URLClassLoader>() { |
| public URLClassLoader run() { |
| return new SubURLClassLoader(urls); |
| } |
| }); |
| sub.currentContext = AccessController.getContext(); |
| return sub; |
| } |
| |
| /** |
| * Returns a new {@code URLClassLoader} instance for the given URLs and the |
| * specified {@code ClassLoader} as its parent. The method {@code |
| * loadClass()} of the new instance will call the SecurityManager's {@code |
| * checkPackageAccess()} before loading a class. |
| * |
| * @param urls |
| * the list of URLs that is passed to the new URLClassloader. |
| * @param parentCl |
| * the parent class loader that is passed to the new |
| * URLClassloader. |
| * @return the created {@code URLClassLoader} instance. |
| * @since Android 1.0 |
| */ |
| public static URLClassLoader newInstance(final URL[] urls, |
| final ClassLoader parentCl) { |
| URLClassLoader sub = AccessController |
| .doPrivileged(new PrivilegedAction<URLClassLoader>() { |
| public URLClassLoader run() { |
| return new SubURLClassLoader(urls, parentCl); |
| } |
| }); |
| sub.currentContext = AccessController.getContext(); |
| return sub; |
| } |
| |
| /** |
| * Constructs a new {@code URLClassLoader} instance. The newly created |
| * instance will have the specified {@code ClassLoader} as its parent and |
| * use the specified factory to create stream handlers. URLs that end with |
| * "/" are assumed to be directories, otherwise they are assumed to be JAR |
| * files. |
| * |
| * @param searchUrls |
| * the list of URLs where a specific class or file could be |
| * found. |
| * @param parent |
| * the {@code ClassLoader} to assign as this loader's parent. |
| * @param factory |
| * the factory that will be used to create protocol-specific |
| * stream handlers. |
| * @throws SecurityException |
| * if a security manager exists and its {@code |
| * checkCreateClassLoader()} method doesn't allow creation of |
| * new {@code ClassLoader}s. |
| * @since Android 1.0 |
| */ |
| public URLClassLoader(URL[] searchUrls, ClassLoader parent, |
| URLStreamHandlerFactory factory) { |
| super(parent); |
| // Required for pre-v1.2 security managers to work |
| SecurityManager security = System.getSecurityManager(); |
| if (security != null) { |
| security.checkCreateClassLoader(); |
| } |
| this.factory = factory; |
| // capture the context of the thread that creates this URLClassLoader |
| currentContext = AccessController.getContext(); |
| int nbUrls = searchUrls.length; |
| urls = new URL[nbUrls]; |
| orgUrls = new URL[nbUrls]; |
| // Search each jar for CLASS-PATH attribute in manifest |
| extensions = new HashMap<URL, URL[]>(nbUrls * 2); |
| for (int i = 0; i < nbUrls; i++) { |
| try { |
| urls[i] = createSearchURL(searchUrls[i]); |
| extensions.put(urls[i], null); |
| } catch (MalformedURLException e) { |
| } |
| orgUrls[i] = searchUrls[i]; |
| } |
| // Search each jar for META-INF/INDEX.LIST |
| indexes = newHashtableArray(nbUrls); |
| } |
| |
| /** |
| * Tries to locate and load the specified class using the known URLs. If the |
| * class could be found, a class object representing the loaded class will |
| * be returned. |
| * |
| * @return the class that has been loaded. |
| * @param clsName |
| * the name of the class which has to be found. |
| * @throws ClassNotFoundException |
| * if the specified class cannot be loaded. |
| * @since Android 1.0 |
| */ |
| @Override |
| protected Class<?> findClass(final String clsName) |
| throws ClassNotFoundException { |
| Class<?> cls = AccessController.doPrivileged( |
| new PrivilegedAction<Class<?>>() { |
| public Class<?> run() { |
| return findClassImpl(urls, clsName); |
| } |
| }, currentContext); |
| if (cls != null) { |
| return cls; |
| } |
| throw new ClassNotFoundException(clsName); |
| } |
| |
| /** |
| * Returns an URL that will be checked if it contains the class or resource. |
| * If the file component of the URL is not a directory, a Jar URL will be |
| * created. |
| * |
| * @return java.net.URL a test URL |
| */ |
| private URL createSearchURL(URL url) throws MalformedURLException { |
| if (url == null) { |
| return url; |
| } |
| |
| String protocol = url.getProtocol(); |
| |
| if (isDirectory(url) || protocol.equals("jar")) { //$NON-NLS-1$ |
| return url; |
| } |
| if (factory == null) { |
| return new URL("jar", "", //$NON-NLS-1$ //$NON-NLS-2$ |
| -1, url.toString() + "!/"); //$NON-NLS-1$ |
| } |
| return new URL("jar", "", //$NON-NLS-1$ //$NON-NLS-2$ |
| -1, url.toString() + "!/", //$NON-NLS-1$ |
| factory.createURLStreamHandler(protocol)); |
| } |
| |
| /** |
| * Returns an URL referencing the specified resource or {@code null} if the |
| * resource could not be found. |
| * |
| * @param name |
| * the name of the requested resource. |
| * @return the URL which points to the given resource. |
| * @since Android 1.0 |
| */ |
| @Override |
| public URL findResource(final String name) { |
| if (name == null) { |
| return null; |
| } |
| URL result = AccessController.doPrivileged(new PrivilegedAction<URL>() { |
| public URL run() { |
| return findResourceImpl(urls, name); |
| } |
| }, currentContext); |
| SecurityManager sm; |
| if (result != null && (sm = System.getSecurityManager()) != null) { |
| try { |
| sm.checkPermission(result.openConnection().getPermission()); |
| } catch (IOException e) { |
| return null; |
| } catch (SecurityException e) { |
| return null; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Returns a URL among the given ones referencing the specified resource or |
| * null if no resource could be found. |
| * |
| * @return URL URL for the resource. |
| * @param searchList |
| * java.net.URL[] the array to be searched |
| * @param resName |
| * java.lang.String the name of the requested resource |
| */ |
| URL findResourceImpl(URL[] searchList, String resName) { |
| boolean findInExtensions = searchList == urls; |
| int i = 0; |
| while (i < searchList.length) { |
| if (searchList[i] == null) { |
| // KA024=One of urls is null |
| throw new NullPointerException(Msg.getString("KA024")); //$NON-NLS-1$ |
| } else if (!invalidUrls.contains(searchList[i])) { |
| JarFile jf = null; |
| try { |
| URL currentUrl = searchList[i]; |
| String protocol = currentUrl.getProtocol(); |
| |
| if (protocol.equals("jar")) { //$NON-NLS-1$ |
| jf = resCache.get(currentUrl); |
| if (jf == null) { |
| if (invalidUrls.contains(currentUrl)) { |
| continue; |
| } |
| // each jf should be found only once |
| // so we do this job in the synchronized block |
| synchronized (lock) { |
| // Check the cache again in case another thread |
| // updated it while we're waiting on lock |
| jf = resCache.get(currentUrl); |
| if (jf == null) { |
| if (invalidUrls.contains(currentUrl)) { |
| continue; |
| } |
| /* |
| * If the connection for currentUrl or resURL is |
| * used, getJarFile() will throw an exception if the |
| * entry doesn't exist. |
| */ |
| URL jarURL = ((JarURLConnection) currentUrl |
| .openConnection()).getJarFileURL(); |
| try { |
| JarURLConnection juc = (JarURLConnection) new URL( |
| "jar", "", //$NON-NLS-1$ //$NON-NLS-2$ |
| jarURL.toExternalForm() + "!/").openConnection(); //$NON-NLS-1$ |
| jf = juc.getJarFile(); |
| resCache.put(currentUrl, jf); |
| } catch (IOException e) { |
| // Don't look for this jar file again |
| invalidUrls.add(searchList[i]); |
| throw e; |
| } |
| } |
| } |
| } |
| String entryName; |
| if (currentUrl.getFile().endsWith("!/")) { //$NON-NLS-1$ |
| entryName = resName; |
| } else { |
| String file = currentUrl.getFile(); |
| int sepIdx = file.lastIndexOf("!/"); //$NON-NLS-1$ |
| if (sepIdx == -1) { |
| // Invalid URL, don't look here again |
| invalidUrls.add(searchList[i]); |
| continue; |
| } |
| sepIdx += 2; |
| entryName = new StringBuffer(file.length() - sepIdx |
| + resName.length()).append( |
| file.substring(sepIdx)).append(resName) |
| .toString(); |
| } |
| if (jf.getEntry(entryName) != null) { |
| return targetURL(currentUrl, resName); |
| } |
| } else if (protocol.equals("file")) { //$NON-NLS-1$ |
| String baseFile = currentUrl.getFile(); |
| String host = currentUrl.getHost(); |
| int hostLength = 0; |
| if (host != null) { |
| hostLength = host.length(); |
| } |
| StringBuffer buf = new StringBuffer(2 + hostLength |
| + baseFile.length() + resName.length()); |
| if (hostLength > 0) { |
| buf.append("//").append(host); //$NON-NLS-1$ |
| } |
| // baseFile always ends with '/' |
| buf.append(baseFile); |
| String fixedResName = resName; |
| // Do not create a UNC path, i.e. \\host |
| while (fixedResName.startsWith("/") //$NON-NLS-1$ |
| || fixedResName.startsWith("\\")) { //$NON-NLS-1$ |
| fixedResName = fixedResName.substring(1); |
| } |
| buf.append(fixedResName); |
| |
| String filename = buf.toString(); |
| |
| try { |
| filename = URLDecoder.decode(filename, "UTF-8"); //$NON-NLS-1$ |
| } catch (IllegalArgumentException e) { |
| return null; |
| } |
| |
| if (new File(filename).exists()) { |
| return targetURL(currentUrl, fixedResName); |
| } |
| } else { |
| URL resURL = targetURL(currentUrl, resName); |
| URLConnection uc = resURL.openConnection(); |
| try { |
| uc.getInputStream().close(); |
| } catch (SecurityException e) { |
| return null; |
| } |
| // HTTP can return a stream on a non-existent file |
| // So check for the return code; |
| if (!resURL.getProtocol().equals("http")) { //$NON-NLS-1$ |
| return resURL; |
| } |
| int code; |
| if ((code = ((HttpURLConnection) uc).getResponseCode()) >= 200 |
| && code < 300) { |
| return resURL; |
| } |
| } |
| } catch (MalformedURLException e) { |
| // Keep iterating through the URL list |
| } catch (IOException e) { |
| } catch (SecurityException e) { |
| } |
| if ((jf != null) && findInExtensions) { |
| if (indexes[i] != null) { |
| try { |
| URL result = (URL) findInIndex(i, resName, null, |
| true); |
| if (result != null) { |
| return result; |
| } |
| } catch (InvalidJarIndexException ex) { |
| // Ignore invalid/misleading JAR index file |
| } |
| } else { |
| URL result = (URL) findInExtensions(explore( |
| searchList[i], i), resName, i, null, true); |
| if (result != null) { |
| return result; |
| } |
| } |
| } |
| } |
| ++i; |
| } |
| return null; |
| } |
| |
| /** |
| * Defines a new package using the information extracted from the specified |
| * manifest. |
| * |
| * @param packageName |
| * the name of the new package. |
| * @param manifest |
| * the manifest containing additional information for the new |
| * package. |
| * @param url |
| * the URL to the code source for the new package. |
| * @return the created package. |
| * @throws IllegalArgumentException |
| * if a package with the given name already exists. |
| * @since Android 1.0 |
| */ |
| protected Package definePackage(String packageName, Manifest manifest, |
| URL url) throws IllegalArgumentException { |
| Attributes mainAttributes = manifest.getMainAttributes(); |
| String dirName = packageName.replace('.', '/') + "/"; //$NON-NLS-1$ |
| Attributes packageAttributes = manifest.getAttributes(dirName); |
| boolean noEntry = false; |
| if (packageAttributes == null) { |
| noEntry = true; |
| packageAttributes = mainAttributes; |
| } |
| String specificationTitle = packageAttributes |
| .getValue(Attributes.Name.SPECIFICATION_TITLE); |
| if (specificationTitle == null && !noEntry) { |
| specificationTitle = mainAttributes |
| .getValue(Attributes.Name.SPECIFICATION_TITLE); |
| } |
| String specificationVersion = packageAttributes |
| .getValue(Attributes.Name.SPECIFICATION_VERSION); |
| if (specificationVersion == null && !noEntry) { |
| specificationVersion = mainAttributes |
| .getValue(Attributes.Name.SPECIFICATION_VERSION); |
| } |
| String specificationVendor = packageAttributes |
| .getValue(Attributes.Name.SPECIFICATION_VENDOR); |
| if (specificationVendor == null && !noEntry) { |
| specificationVendor = mainAttributes |
| .getValue(Attributes.Name.SPECIFICATION_VENDOR); |
| } |
| String implementationTitle = packageAttributes |
| .getValue(Attributes.Name.IMPLEMENTATION_TITLE); |
| if (implementationTitle == null && !noEntry) { |
| implementationTitle = mainAttributes |
| .getValue(Attributes.Name.IMPLEMENTATION_TITLE); |
| } |
| String implementationVersion = packageAttributes |
| .getValue(Attributes.Name.IMPLEMENTATION_VERSION); |
| if (implementationVersion == null && !noEntry) { |
| implementationVersion = mainAttributes |
| .getValue(Attributes.Name.IMPLEMENTATION_VERSION); |
| } |
| String implementationVendor = packageAttributes |
| .getValue(Attributes.Name.IMPLEMENTATION_VENDOR); |
| if (implementationVendor == null && !noEntry) { |
| implementationVendor = mainAttributes |
| .getValue(Attributes.Name.IMPLEMENTATION_VENDOR); |
| } |
| |
| return definePackage(packageName, specificationTitle, |
| specificationVersion, specificationVendor, implementationTitle, |
| implementationVersion, implementationVendor, isSealed(manifest, |
| dirName) ? url : null); |
| } |
| |
| private boolean isSealed(Manifest manifest, String dirName) { |
| Attributes mainAttributes = manifest.getMainAttributes(); |
| String value = mainAttributes.getValue(Attributes.Name.SEALED); |
| boolean sealed = value != null && value.toLowerCase().equals("true"); //$NON-NLS-1$ |
| Attributes attributes = manifest.getAttributes(dirName); |
| if (attributes != null) { |
| value = attributes.getValue(Attributes.Name.SEALED); |
| if (value != null) { |
| sealed = value.toLowerCase().equals("true"); //$NON-NLS-1$ |
| } |
| } |
| return sealed; |
| } |
| |
| /** |
| * returns URLs referenced in the string classpath. |
| * |
| * @param root |
| * the jar URL that classpath is related to |
| * @param classpath |
| * the relative URLs separated by spaces |
| * |
| * @return URL[] the URLs contained in the string classpath. |
| */ |
| private URL[] getInternalURLs(URL root, String classpath) { |
| // Class-path attribute is composed of space-separated values. |
| StringTokenizer tokenizer = new java.util.StringTokenizer(classpath); |
| Vector<URL> addedURLs = new Vector<URL>(); |
| String file = root.getFile(); |
| int jarIndex = file.lastIndexOf("!/") - 1; //$NON-NLS-1$ |
| int index = file.lastIndexOf("/", jarIndex) + 1; //$NON-NLS-1$ |
| if (index == 0) { |
| index = file.lastIndexOf( |
| System.getProperty("file.separator"), jarIndex) + 1; //$NON-NLS-1$ |
| } |
| file = file.substring(0, index); |
| String protocol = root.getProtocol(); |
| String host = root.getHost(); |
| int port = root.getPort(); |
| while (tokenizer.hasMoreElements()) { |
| String element = tokenizer.nextToken(); |
| if (!element.equals("")) { //$NON-NLS-1$ |
| try { |
| URL newURL = new URL(protocol, host, port, file + element |
| + "!/"); //$NON-NLS-1$ |
| synchronized (extensions) { |
| if (!extensions.containsKey(newURL)) { |
| extensions.put(newURL, null); |
| addedURLs.add(newURL); |
| } |
| } |
| } catch (MalformedURLException e) { |
| // Nothing is added |
| } |
| } |
| } |
| URL[] newURLs = addedURLs.toArray(new URL[] {}); |
| return newURLs; |
| } |
| |
| /** |
| * @param in |
| * InputStream the stream to read lines from |
| * @return List a list of String lines |
| */ |
| private List<String> readLines(InputStream in) throws IOException { |
| byte[] buff = new byte[144]; |
| List<String> lines = new ArrayList<String>(); |
| int pos = 0; |
| int next; |
| while ((next = in.read()) != -1) { |
| if (next == '\n') { |
| lines.add(new String(buff, 0, pos, "UTF8")); //$NON-NLS-1$ |
| pos = 0; |
| continue; |
| } |
| if (next == '\r') { |
| lines.add(new String(buff, 0, pos, "UTF8")); //$NON-NLS-1$ |
| pos = 0; |
| if ((next = in.read()) == '\n') { |
| continue; |
| } |
| } |
| if (pos == buff.length) { |
| byte[] newBuf = new byte[buff.length * 2]; |
| System.arraycopy(buff, 0, newBuf, 0, buff.length); |
| buff = newBuf; |
| } |
| buff[pos++] = (byte) next; |
| } |
| if (pos > 0) { |
| lines.add(new String(buff, 0, pos, "UTF8")); //$NON-NLS-1$ |
| } |
| return lines; |
| } |
| |
| private URL targetURL(URL base, String name) throws MalformedURLException { |
| try { |
| String file = base.getFile() + URIEncoderDecoder.quoteIllegal(name, |
| "/@" + URI.someLegal); |
| |
| return new URL(base.getProtocol(), base.getHost(), base.getPort(), |
| file, null); |
| } catch (UnsupportedEncodingException e) { |
| MalformedURLException e2 = new MalformedURLException(e.toString()); |
| |
| e2.initCause(e); |
| throw e2; |
| } |
| |
| } |
| |
| /** |
| * @param searchURLs |
| * java.net.URL[] the URLs to search in |
| * @param clsName |
| * java.lang.String the class name to be found |
| * @return Class the class found or null if not found |
| */ |
| Class<?> findClassImpl(URL[] searchURLs, String clsName) { |
| boolean readAvailable = false; |
| boolean findInExtensions = searchURLs == urls; |
| final String name = new StringBuffer(clsName.replace('.', '/')).append( |
| ".class").toString(); //$NON-NLS-1$ |
| for (int i = 0; i < searchURLs.length; i++) { |
| if (searchURLs[i] == null) { |
| // KA024=One of urls is null |
| throw new NullPointerException(Msg.getString("KA024")); //$NON-NLS-1$ |
| } else if (!invalidUrls.contains(searchURLs[i])) { |
| Manifest manifest = null; |
| InputStream is = null; |
| JarEntry entry = null; |
| JarFile jf = null; |
| byte[] clBuf = null; |
| try { |
| URL thisURL = searchURLs[i]; |
| String protocol = thisURL.getProtocol(); |
| if (protocol.equals("jar")) { //$NON-NLS-1$ |
| jf = resCache.get(thisURL); |
| if ((jf == null) && (!invalidUrls.contains(thisURL))) { |
| synchronized (lock) { |
| // Check the cache again in case another thread updated it |
| // updated it while we're waiting on lock |
| jf = resCache.get(thisURL); |
| if (jf == null) { |
| if (invalidUrls.contains(thisURL)) { |
| continue; |
| } |
| // If the connection for testURL or thisURL is used, |
| // getJarFile() will throw an exception if the entry |
| // doesn't exist. |
| URL jarURL = ((JarURLConnection) thisURL |
| .openConnection()).getJarFileURL(); |
| try { |
| JarURLConnection juc = (JarURLConnection) new URL( |
| "jar", "", jarURL.toExternalForm() + "!/") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| .openConnection(); |
| jf = juc.getJarFile(); |
| resCache.put(thisURL, jf); |
| } catch (IOException e) { |
| // Don't look for this jar file again |
| invalidUrls.add(searchURLs[i]); |
| throw e; |
| } |
| } |
| } |
| } |
| if (thisURL.getFile().endsWith("!/")) { //$NON-NLS-1$ |
| entry = jf.getJarEntry(name); |
| } else { |
| String file = thisURL.getFile(); |
| int sepIdx = file.lastIndexOf("!/"); //$NON-NLS-1$ |
| if (sepIdx == -1) { |
| // Invalid URL, don't look here again |
| invalidUrls.add(searchURLs[i]); |
| continue; |
| } |
| sepIdx += 2; |
| String entryName = new StringBuffer(file.length() |
| - sepIdx + name.length()).append( |
| file.substring(sepIdx)).append(name) |
| .toString(); |
| entry = jf.getJarEntry(entryName); |
| } |
| if (entry != null) { |
| readAvailable = true; |
| is = jf.getInputStream(entry); |
| /** |
| * Avoid recursive load class, especially the class |
| * is an implementation class of security provider |
| * and the jar is signed. |
| */ |
| Class loadedClass = findLoadedClass(clsName); |
| if (null != loadedClass) { |
| is.close(); |
| return loadedClass; |
| } |
| manifest = jf.getManifest(); |
| } |
| } else if (protocol.equals("file")) { //$NON-NLS-1$ |
| String filename = thisURL.getFile(); |
| String host = thisURL.getHost(); |
| if (host != null && host.length() > 0) { |
| filename = new StringBuffer(host.length() |
| + filename.length() + name.length() + 2) |
| .append("//").append(host).append(filename) //$NON-NLS-1$ |
| .append(name).toString(); |
| } else { |
| filename = new StringBuffer(filename.length() |
| + name.length()).append(filename).append( |
| name).toString(); |
| } |
| |
| // Just return null for caller to throw |
| // ClassNotFoundException. |
| try { |
| filename = URLDecoder.decode(filename, "UTF-8"); //$NON-NLS-1$ |
| } catch (IllegalArgumentException e) { |
| return null; |
| } |
| |
| File file = new File(filename); |
| // Don't throw exceptions for speed |
| if (file.exists()) { |
| is = new FileInputStream(file); |
| readAvailable = true; |
| } else { |
| continue; |
| } |
| } else { |
| is = targetURL(thisURL, name).openStream(); |
| } |
| } catch (MalformedURLException e) { |
| // Keep iterating through the URL list |
| } catch (IOException e) { |
| } |
| if (is != null) { |
| URL codeSourceURL = null; |
| Certificate[] certificates = null; |
| CodeSource codeS = null; |
| try { |
| codeSourceURL = findInExtensions ? orgUrls[i] |
| : ((JarURLConnection) searchURLs[i] |
| .openConnection()).getJarFileURL(); |
| } catch (IOException e) { |
| codeSourceURL = searchURLs[i]; |
| } |
| if (is != null) { |
| try { |
| clBuf = getBytes(is, readAvailable); |
| is.close(); |
| } catch (IOException e) { |
| return null; |
| } |
| } |
| if (entry != null) { |
| certificates = entry.getCertificates(); |
| } |
| // Use the original URL, not the possible jar URL |
| codeS = new CodeSource(codeSourceURL, certificates); |
| int dotIndex = clsName.lastIndexOf("."); //$NON-NLS-1$ |
| if (dotIndex != -1) { |
| String packageName = clsName.substring(0, dotIndex); |
| synchronized (this) { |
| Package packageObj = getPackage(packageName); |
| if (packageObj == null) { |
| if (manifest != null) { |
| definePackage(packageName, manifest, |
| codeSourceURL); |
| } else { |
| definePackage(packageName, null, null, |
| null, null, null, null, null); |
| } |
| } else { |
| boolean exception = false; |
| if (manifest != null) { |
| String dirName = packageName.replace('.', |
| '/') |
| + "/"; //$NON-NLS-1$ |
| if (isSealed(manifest, dirName)) { |
| exception = !packageObj |
| .isSealed(codeSourceURL); |
| } |
| } else { |
| exception = packageObj.isSealed(); |
| } |
| if (exception) { |
| throw new SecurityException(Msg |
| .getString("K004c")); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| return defineClass(clsName, clBuf, 0, clBuf.length, codeS); |
| } |
| if ((jf != null) && findInExtensions) { |
| if (indexes[i] != null) { |
| try { |
| Class<?> c = (Class<?>) findInIndex(i, clsName, |
| null, false); |
| if (c != null) { |
| return c; |
| } |
| } catch (InvalidJarIndexException ex) { |
| // Ignore misleading/wrong jar index |
| } |
| } else { |
| Class<?> c = (Class<?>) findInExtensions(explore( |
| searchURLs[i], i), clsName, i, null, false); |
| if (c != null) { |
| return c; |
| } |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @param url |
| * URL the URL to explore |
| * @param indexNumber |
| * int the index in extensions to consider |
| * |
| * @return URL[] the URLs of bundled extensions that have been found (i.e. |
| * the URL of jar files in the class-path attribute), or null if |
| * none. if an INDEX.LIST has been found, an empty array is returned |
| */ |
| URL[] explore(URL url, int indexNumber) { |
| URL[] internal; |
| synchronized (extensions) { |
| internal = extensions.get(url); |
| } |
| if (internal != null) { |
| return internal; |
| } |
| if (indexes[indexNumber] != null) { |
| return null; |
| } |
| |
| if (!url.getProtocol().equals("jar")) { //$NON-NLS-1$ |
| return null; |
| } |
| |
| JarFile jf = resCache.get(url); |
| // Add mappings from INDEX.LIST |
| ZipEntry ze = jf.getEntry("META-INF/INDEX.LIST"); //$NON-NLS-1$ |
| if (ze != null) { |
| if (url.equals(urls[indexNumber])) { |
| try { |
| Hashtable<String, URL[]> index = new Hashtable<String, URL[]>( |
| 15); |
| InputStream indexIS = jf.getInputStream(ze); |
| List<String> lines = readLines(indexIS); |
| indexIS.close(); |
| ListIterator<String> iterator = lines.listIterator(); |
| // Ignore the 2 first lines (index version) |
| iterator.next(); |
| iterator.next(); |
| // Add mappings from resource to jar file |
| URL fileURL = ((JarURLConnection) url.openConnection()) |
| .getJarFileURL(); |
| String file = fileURL.getFile(); |
| String parentFile = new File(file).getParent(); |
| parentFile = parentFile.replace(File.separatorChar, '/'); |
| if (parentFile.charAt(0) != '/') { |
| parentFile = "/" + parentFile; //$NON-NLS-1$ |
| } |
| URL parentURL = new URL(fileURL.getProtocol(), fileURL |
| .getHost(), fileURL.getPort(), parentFile); |
| while (iterator.hasNext()) { |
| URL jar = new URL("jar:" //$NON-NLS-1$ |
| + parentURL.toExternalForm() + "/" //$NON-NLS-1$ |
| + iterator.next() + "!/"); //$NON-NLS-1$ |
| String resource = null; |
| while (iterator.hasNext() |
| && !(resource = iterator.next()).equals("")) { //$NON-NLS-1$ |
| if (index.containsKey(resource)) { |
| URL[] jars = index.get(resource); |
| URL[] newJars = new URL[jars.length + 1]; |
| System.arraycopy(jars, 0, newJars, 0, |
| jars.length); |
| newJars[jars.length] = jar; |
| index.put(resource, newJars); |
| } else { |
| URL[] jars = { jar }; |
| index.put(resource, jars); |
| } |
| } |
| } |
| indexes[indexNumber] = index; |
| } catch (MalformedURLException e) { |
| // Ignore this jar's index |
| } catch (IOException e) { |
| // Ignore this jar's index |
| } |
| } |
| return null; |
| } |
| |
| // Returns URLs referenced by the class-path attribute. |
| Manifest manifest = null; |
| try { |
| manifest = jf.getManifest(); |
| } catch (IOException e) { |
| } |
| String classpath = null; |
| if (manifest != null) { |
| classpath = manifest.getMainAttributes().getValue( |
| Attributes.Name.CLASS_PATH); |
| } |
| synchronized (extensions) { |
| internal = extensions.get(url); |
| if (internal == null) { |
| internal = classpath != null ? getInternalURLs(url, classpath) |
| : NO_PATH; |
| extensions.put(url, internal); |
| } |
| } |
| return internal; |
| } |
| } |