Home | History | Annotate | Download | only in misc
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
      4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      5  *
      6  * This code is free software; you can redistribute it and/or modify it
      7  * under the terms of the GNU General Public License version 2 only, as
      8  * published by the Free Software Foundation.  Oracle designates this
      9  * particular file as subject to the "Classpath" exception as provided
     10  * by Oracle in the LICENSE file that accompanied this code.
     11  *
     12  * This code is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     15  * version 2 for more details (a copy is included in the LICENSE file that
     16  * accompanied this code).
     17  *
     18  * You should have received a copy of the GNU General Public License version
     19  * 2 along with this work; if not, write to the Free Software Foundation,
     20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     21  *
     22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     23  * or visit www.oracle.com if you need additional information or have any
     24  * questions.
     25  */
     26 
     27 package sun.misc;
     28 
     29 import java.util.*;
     30 import java.util.jar.JarFile;
     31 import sun.misc.JarIndex;
     32 import sun.misc.InvalidJarIndexException;
     33 import sun.net.www.ParseUtil;
     34 import java.util.zip.ZipEntry;
     35 import java.util.jar.JarEntry;
     36 import java.util.jar.Manifest;
     37 import java.util.jar.Attributes;
     38 import java.util.jar.Attributes.Name;
     39 import java.net.JarURLConnection;
     40 import java.net.MalformedURLException;
     41 import java.net.URL;
     42 import java.net.URLConnection;
     43 import java.net.HttpURLConnection;
     44 import java.net.URLStreamHandler;
     45 import java.net.URLStreamHandlerFactory;
     46 import java.io.*;
     47 import java.security.AccessControlContext;
     48 import java.security.AccessController;
     49 import java.security.AccessControlException;
     50 import java.security.CodeSigner;
     51 import java.security.Permission;
     52 import java.security.PrivilegedAction;
     53 import java.security.PrivilegedExceptionAction;
     54 import java.security.cert.Certificate;
     55 import sun.misc.FileURLMapper;
     56 import sun.net.util.URLUtil;
     57 import sun.security.action.GetPropertyAction;
     58 
     59 /**
     60  * This class is used to maintain a search path of URLs for loading classes
     61  * and resources from both JAR files and directories.
     62  *
     63  * @author  David Connelly
     64  */
     65 public class URLClassPath {
     66     final static String USER_AGENT_JAVA_VERSION = "UA-Java-Version";
     67     final static String JAVA_VERSION;
     68     private static final boolean DEBUG;
     69     private static final boolean DEBUG_LOOKUP_CACHE;
     70     private static final boolean DISABLE_JAR_CHECKING;
     71     private static final boolean DISABLE_ACC_CHECKING;
     72 
     73     static {
     74         JAVA_VERSION = java.security.AccessController.doPrivileged(
     75             new GetPropertyAction("java.version"));
     76         DEBUG        = (java.security.AccessController.doPrivileged(
     77             new GetPropertyAction("sun.misc.URLClassPath.debug")) != null);
     78         DEBUG_LOOKUP_CACHE = (java.security.AccessController.doPrivileged(
     79             new GetPropertyAction("sun.misc.URLClassPath.debugLookupCache")) != null);
     80         String p = java.security.AccessController.doPrivileged(
     81             new GetPropertyAction("sun.misc.URLClassPath.disableJarChecking"));
     82         DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.equals("") : false;
     83 
     84         p = AccessController.doPrivileged(
     85             new GetPropertyAction("jdk.net.URLClassPath.disableRestrictedPermissions"));
     86         DISABLE_ACC_CHECKING = p != null ? p.equals("true") || p.equals("") : false;
     87     }
     88 
     89     /* The original search path of URLs. */
     90     private ArrayList<URL> path = new ArrayList<URL>();
     91 
     92     /* The stack of unopened URLs */
     93     Stack<URL> urls = new Stack<URL>();
     94 
     95     /* The resulting search path of Loaders */
     96     ArrayList<Loader> loaders = new ArrayList<Loader>();
     97 
     98     /* Map of each URL opened to its corresponding Loader */
     99     HashMap<String, Loader> lmap = new HashMap<String, Loader>();
    100 
    101     /* The jar protocol handler to use when creating new URLs */
    102     private URLStreamHandler jarHandler;
    103 
    104     /* Whether this URLClassLoader has been closed yet */
    105     private boolean closed = false;
    106 
    107     /* The context to be used when loading classes and resources.  If non-null
    108      * this is the context that was captured during the creation of the
    109      * URLClassLoader. null implies no additional security restrictions. */
    110     private final AccessControlContext acc;
    111 
    112     /**
    113      * Creates a new URLClassPath for the given URLs. The URLs will be
    114      * searched in the order specified for classes and resources. A URL
    115      * ending with a '/' is assumed to refer to a directory. Otherwise,
    116      * the URL is assumed to refer to a JAR file.
    117      *
    118      * @param urls the directory and JAR file URLs to search for classes
    119      *        and resources
    120      * @param factory the URLStreamHandlerFactory to use when creating new URLs
    121      * @param acc the context to be used when loading classes and resources, may
    122      *            be null
    123      */
    124     public URLClassPath(URL[] urls,
    125                         URLStreamHandlerFactory factory,
    126                         AccessControlContext acc) {
    127         for (int i = 0; i < urls.length; i++) {
    128             path.add(urls[i]);
    129         }
    130         push(urls);
    131         if (factory != null) {
    132             jarHandler = factory.createURLStreamHandler("jar");
    133         }
    134         if (DISABLE_ACC_CHECKING)
    135             this.acc = null;
    136         else
    137             this.acc = acc;
    138     }
    139 
    140     /**
    141      * Constructs a URLClassPath with no additional security restrictions.
    142      * Used by code that implements the class path.
    143      */
    144     public URLClassPath(URL[] urls) {
    145         this(urls, null, null);
    146     }
    147 
    148     public URLClassPath(URL[] urls, AccessControlContext acc) {
    149         this(urls, null, acc);
    150     }
    151 
    152     public synchronized List<IOException> closeLoaders() {
    153         if (closed) {
    154             return Collections.emptyList();
    155         }
    156         List<IOException> result = new LinkedList<IOException>();
    157         for (Loader loader : loaders) {
    158             try {
    159                 loader.close();
    160             } catch (IOException e) {
    161                 result.add (e);
    162             }
    163         }
    164         closed = true;
    165         return result;
    166     }
    167 
    168     /**
    169      * Appends the specified URL to the search path of directory and JAR
    170      * file URLs from which to load classes and resources.
    171      * <p>
    172      * If the URL specified is null or is already in the list of
    173      * URLs, then invoking this method has no effect.
    174      */
    175     public synchronized void addURL(URL url) {
    176         if (closed)
    177             return;
    178         synchronized (urls) {
    179             if (url == null || path.contains(url))
    180                 return;
    181 
    182             urls.add(0, url);
    183             path.add(url);
    184 
    185             if (lookupCacheURLs != null) {
    186                 // The lookup cache is no longer valid, since getLookupCache()
    187                 // does not consider the newly added url.
    188                 disableAllLookupCaches();
    189             }
    190         }
    191     }
    192 
    193     /**
    194      * Returns the original search path of URLs.
    195      */
    196     public URL[] getURLs() {
    197         synchronized (urls) {
    198             return path.toArray(new URL[path.size()]);
    199         }
    200     }
    201 
    202     /**
    203      * Finds the resource with the specified name on the URL search path
    204      * or null if not found or security check fails.
    205      *
    206      * @param name      the name of the resource
    207      * @param check     whether to perform a security check
    208      * @return a <code>URL</code> for the resource, or <code>null</code>
    209      * if the resource could not be found.
    210      */
    211     public URL findResource(String name, boolean check) {
    212         Loader loader;
    213         int[] cache = getLookupCache(name);
    214         for (int i = 0; (loader = getNextLoader(cache, i)) != null; i++) {
    215             URL url = loader.findResource(name, check);
    216             if (url != null) {
    217                 return url;
    218             }
    219         }
    220         return null;
    221     }
    222 
    223     /**
    224      * Finds the first Resource on the URL search path which has the specified
    225      * name. Returns null if no Resource could be found.
    226      *
    227      * @param name the name of the Resource
    228      * @param check     whether to perform a security check
    229      * @return the Resource, or null if not found
    230      */
    231     public Resource getResource(String name, boolean check) {
    232         if (DEBUG) {
    233             System.err.println("URLClassPath.getResource(\"" + name + "\")");
    234         }
    235 
    236         Loader loader;
    237         int[] cache = getLookupCache(name);
    238         for (int i = 0; (loader = getNextLoader(cache, i)) != null; i++) {
    239             Resource res = loader.getResource(name, check);
    240             if (res != null) {
    241                 return res;
    242             }
    243         }
    244         return null;
    245     }
    246 
    247     /**
    248      * Finds all resources on the URL search path with the given name.
    249      * Returns an enumeration of the URL objects.
    250      *
    251      * @param name the resource name
    252      * @return an Enumeration of all the urls having the specified name
    253      */
    254     public Enumeration<URL> findResources(final String name,
    255                                      final boolean check) {
    256         return new Enumeration<URL>() {
    257             private int index = 0;
    258             private int[] cache = getLookupCache(name);
    259             private URL url = null;
    260 
    261             private boolean next() {
    262                 if (url != null) {
    263                     return true;
    264                 } else {
    265                     Loader loader;
    266                     while ((loader = getNextLoader(cache, index++)) != null) {
    267                         url = loader.findResource(name, check);
    268                         if (url != null) {
    269                             return true;
    270                         }
    271                     }
    272                     return false;
    273                 }
    274             }
    275 
    276             public boolean hasMoreElements() {
    277                 return next();
    278             }
    279 
    280             public URL nextElement() {
    281                 if (!next()) {
    282                     throw new NoSuchElementException();
    283                 }
    284                 URL u = url;
    285                 url = null;
    286                 return u;
    287             }
    288         };
    289     }
    290 
    291     public Resource getResource(String name) {
    292         return getResource(name, true);
    293     }
    294 
    295     /**
    296      * Finds all resources on the URL search path with the given name.
    297      * Returns an enumeration of the Resource objects.
    298      *
    299      * @param name the resource name
    300      * @return an Enumeration of all the resources having the specified name
    301      */
    302     public Enumeration<Resource> getResources(final String name,
    303                                     final boolean check) {
    304         return new Enumeration<Resource>() {
    305             private int index = 0;
    306             private int[] cache = getLookupCache(name);
    307             private Resource res = null;
    308 
    309             private boolean next() {
    310                 if (res != null) {
    311                     return true;
    312                 } else {
    313                     Loader loader;
    314                     while ((loader = getNextLoader(cache, index++)) != null) {
    315                         res = loader.getResource(name, check);
    316                         if (res != null) {
    317                             return true;
    318                         }
    319                     }
    320                     return false;
    321                 }
    322             }
    323 
    324             public boolean hasMoreElements() {
    325                 return next();
    326             }
    327 
    328             public Resource nextElement() {
    329                 if (!next()) {
    330                     throw new NoSuchElementException();
    331                 }
    332                 Resource r = res;
    333                 res = null;
    334                 return r;
    335             }
    336         };
    337     }
    338 
    339     public Enumeration<Resource> getResources(final String name) {
    340         return getResources(name, true);
    341     }
    342 
    343     private static volatile boolean lookupCacheEnabled
    344     // Android-changed: No lookup cache support.
    345     //    = "true".equals(VM.getSavedProperty("sun.cds.enableSharedLookupCache"));
    346           = false;
    347     private URL[] lookupCacheURLs;
    348     private ClassLoader lookupCacheLoader;
    349 
    350     synchronized void initLookupCache(ClassLoader loader) {
    351         if ((lookupCacheURLs = getLookupCacheURLs(loader)) != null) {
    352             lookupCacheLoader = loader;
    353         } else {
    354             // This JVM instance does not support lookup cache.
    355             disableAllLookupCaches();
    356         }
    357     }
    358 
    359     static void disableAllLookupCaches() {
    360         lookupCacheEnabled = false;
    361     }
    362 
    363     // BEGIN Android-changed: No lookup chache support
    364     /*
    365     private static native URL[] getLookupCacheURLs(ClassLoader loader);
    366     private static native int[] getLookupCacheForClassLoader(ClassLoader loader,
    367                                                              String name);
    368     private static native boolean knownToNotExist0(ClassLoader loader,
    369                                                    String className);
    370     */
    371 
    372     private URL[] getLookupCacheURLs(ClassLoader loader) {
    373         return null;
    374     }
    375     private static int[] getLookupCacheForClassLoader(ClassLoader loader,
    376                                                       String name) {
    377         return null;
    378     }
    379     private static boolean knownToNotExist0(ClassLoader loader,
    380                                             String className) {
    381         return false;
    382     }
    383     // END Android-changed: No lookup chache support
    384 
    385 
    386     synchronized boolean knownToNotExist(String className) {
    387         if (lookupCacheURLs != null && lookupCacheEnabled) {
    388             return knownToNotExist0(lookupCacheLoader, className);
    389         }
    390 
    391         // Don't know if this class exists or not -- need to do a full search.
    392         return false;
    393     }
    394 
    395     /**
    396      * Returns an array of the index to lookupCacheURLs that may
    397      * contain the specified resource. The values in the returned
    398      * array are in strictly ascending order and must be a valid index
    399      * to lookupCacheURLs array.
    400      *
    401      * This method returns an empty array if the specified resource
    402      * cannot be found in this URLClassPath. If there is no lookup
    403      * cache or it's disabled, this method returns null and the lookup
    404      * should search the entire classpath.
    405      *
    406      * Example: if lookupCacheURLs contains {a.jar, b.jar, c.jar, d.jar}
    407      * and package "foo" only exists in a.jar and c.jar,
    408      * getLookupCache("foo/Bar.class") will return {0, 2}
    409      *
    410      * @param name the resource name
    411      * @return an array of the index to lookupCacheURLs that may contain the
    412      *         specified resource; or null if no lookup cache is used.
    413      */
    414     private synchronized int[] getLookupCache(String name) {
    415         if (lookupCacheURLs == null || !lookupCacheEnabled) {
    416             return null;
    417         }
    418 
    419         int[] cache = getLookupCacheForClassLoader(lookupCacheLoader, name);
    420         if (cache != null && cache.length > 0) {
    421             int maxindex = cache[cache.length - 1]; // cache[] is strictly ascending.
    422             if (!ensureLoaderOpened(maxindex)) {
    423                 if (DEBUG_LOOKUP_CACHE) {
    424                     System.out.println("Expanded loaders FAILED " +
    425                                        loaders.size() + " for maxindex=" + maxindex);
    426                 }
    427                 return null;
    428             }
    429         }
    430 
    431         return cache;
    432     }
    433 
    434     private boolean ensureLoaderOpened(int index) {
    435         if (loaders.size() <= index) {
    436             // Open all Loaders up to, and including, index
    437             if (getLoader(index) == null) {
    438                 return false;
    439             }
    440             if (!lookupCacheEnabled) {
    441                 // cache was invalidated as the result of the above call.
    442                 return false;
    443             }
    444             if (DEBUG_LOOKUP_CACHE) {
    445                 System.out.println("Expanded loaders " + loaders.size() +
    446                                    " to index=" + index);
    447             }
    448         }
    449         return true;
    450     }
    451 
    452     /*
    453      * The CLASS-PATH attribute was expanded by the VM when building
    454      * the resource lookup cache in the same order as the getLoader
    455      * method does. This method validates if the URL from the lookup
    456      * cache matches the URL of the Loader at the given index;
    457      * otherwise, this method disables the lookup cache.
    458      */
    459     private synchronized void validateLookupCache(int index,
    460                                                   String urlNoFragString) {
    461         if (lookupCacheURLs != null && lookupCacheEnabled) {
    462             if (index < lookupCacheURLs.length &&
    463                 urlNoFragString.equals(
    464                     URLUtil.urlNoFragString(lookupCacheURLs[index]))) {
    465                 return;
    466             }
    467             if (DEBUG || DEBUG_LOOKUP_CACHE) {
    468                 System.out.println("WARNING: resource lookup cache invalidated "
    469                                    + "for lookupCacheLoader at " + index);
    470             }
    471             disableAllLookupCaches();
    472         }
    473     }
    474 
    475     /**
    476      * Returns the next Loader that may contain the resource to
    477      * lookup. If the given cache is null, return loaders.get(index)
    478      * that may be lazily created; otherwise, cache[index] is the next
    479      * Loader that may contain the resource to lookup and so returns
    480      * loaders.get(cache[index]).
    481      *
    482      * If cache is non-null, loaders.get(cache[index]) must be present.
    483      *
    484      * @param cache lookup cache. If null, search the entire class path
    485      * @param index index to the given cache array; or to the loaders list.
    486      */
    487     private synchronized Loader getNextLoader(int[] cache, int index) {
    488         if (closed) {
    489             return null;
    490         }
    491         if (cache != null) {
    492             if (index < cache.length) {
    493                 Loader loader = loaders.get(cache[index]);
    494                 if (DEBUG_LOOKUP_CACHE) {
    495                     System.out.println("HASCACHE: Loading from : " + cache[index]
    496                                        + " = " + loader.getBaseURL());
    497                 }
    498                 return loader;
    499             } else {
    500                 return null; // finished iterating over cache[]
    501             }
    502         } else {
    503             return getLoader(index);
    504         }
    505     }
    506 
    507     /*
    508      * Returns the Loader at the specified position in the URL search
    509      * path. The URLs are opened and expanded as needed. Returns null
    510      * if the specified index is out of range.
    511      */
    512      private synchronized Loader getLoader(int index) {
    513         if (closed) {
    514             return null;
    515         }
    516          // Expand URL search path until the request can be satisfied
    517          // or the URL stack is empty.
    518         while (loaders.size() < index + 1) {
    519             // Pop the next URL from the URL stack
    520             URL url;
    521             synchronized (urls) {
    522                 if (urls.empty()) {
    523                     return null;
    524                 } else {
    525                     url = urls.pop();
    526                 }
    527             }
    528             // Skip this URL if it already has a Loader. (Loader
    529             // may be null in the case where URL has not been opened
    530             // but is referenced by a JAR index.)
    531             String urlNoFragString = URLUtil.urlNoFragString(url);
    532             if (lmap.containsKey(urlNoFragString)) {
    533                 continue;
    534             }
    535             // Otherwise, create a new Loader for the URL.
    536             Loader loader;
    537             try {
    538                 loader = getLoader(url);
    539                 // If the loader defines a local class path then add the
    540                 // URLs to the list of URLs to be opened.
    541                 URL[] urls = loader.getClassPath();
    542                 if (urls != null) {
    543                     push(urls);
    544                 }
    545             } catch (IOException e) {
    546                 // Silently ignore for now...
    547                 continue;
    548             } catch (SecurityException se) {
    549                 // Always silently ignore. The context, if there is one, that
    550                 // this URLClassPath was given during construction will never
    551                 // have permission to access the URL.
    552                 if (DEBUG) {
    553                     System.err.println("Failed to access " + url + ", " + se );
    554                 }
    555                 continue;
    556             }
    557             // Finally, add the Loader to the search path.
    558             validateLookupCache(loaders.size(), urlNoFragString);
    559             loaders.add(loader);
    560             lmap.put(urlNoFragString, loader);
    561         }
    562         if (DEBUG_LOOKUP_CACHE) {
    563             System.out.println("NOCACHE: Loading from : " + index );
    564         }
    565         return loaders.get(index);
    566     }
    567 
    568     /*
    569      * Returns the Loader for the specified base URL.
    570      */
    571     private Loader getLoader(final URL url) throws IOException {
    572         try {
    573             return java.security.AccessController.doPrivileged(
    574                 new java.security.PrivilegedExceptionAction<Loader>() {
    575                 public Loader run() throws IOException {
    576                     String file = url.getFile();
    577                     if (file != null && file.endsWith("/")) {
    578                         if ("file".equals(url.getProtocol())) {
    579                             return new FileLoader(url);
    580                         } else {
    581                             return new Loader(url);
    582                         }
    583                     } else {
    584                         return new JarLoader(url, jarHandler, lmap, acc);
    585                     }
    586                 }
    587             }, acc);
    588         } catch (java.security.PrivilegedActionException pae) {
    589             throw (IOException)pae.getException();
    590         }
    591     }
    592 
    593     /*
    594      * Pushes the specified URLs onto the list of unopened URLs.
    595      */
    596     private void push(URL[] us) {
    597         synchronized (urls) {
    598             for (int i = us.length - 1; i >= 0; --i) {
    599                 urls.push(us[i]);
    600             }
    601         }
    602     }
    603 
    604     /**
    605      * Convert class path specification into an array of file URLs.
    606      *
    607      * The path of the file is encoded before conversion into URL
    608      * form so that reserved characters can safely appear in the path.
    609      */
    610     public static URL[] pathToURLs(String path) {
    611         StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
    612         URL[] urls = new URL[st.countTokens()];
    613         int count = 0;
    614         while (st.hasMoreTokens()) {
    615             File f = new File(st.nextToken());
    616             try {
    617                 f = new File(f.getCanonicalPath());
    618             } catch (IOException x) {
    619                 // use the non-canonicalized filename
    620             }
    621             try {
    622                 urls[count++] = ParseUtil.fileToEncodedURL(f);
    623             } catch (IOException x) { }
    624         }
    625 
    626         if (urls.length != count) {
    627             URL[] tmp = new URL[count];
    628             System.arraycopy(urls, 0, tmp, 0, count);
    629             urls = tmp;
    630         }
    631         return urls;
    632     }
    633 
    634     /*
    635      * Check whether the resource URL should be returned.
    636      * Return null on security check failure.
    637      * Called by java.net.URLClassLoader.
    638      */
    639     public URL checkURL(URL url) {
    640         try {
    641             check(url);
    642         } catch (Exception e) {
    643             return null;
    644         }
    645 
    646         return url;
    647     }
    648 
    649     /*
    650      * Check whether the resource URL should be returned.
    651      * Throw exception on failure.
    652      * Called internally within this file.
    653      */
    654     static void check(URL url) throws IOException {
    655         SecurityManager security = System.getSecurityManager();
    656         if (security != null) {
    657             URLConnection urlConnection = url.openConnection();
    658             Permission perm = urlConnection.getPermission();
    659             if (perm != null) {
    660                 try {
    661                     security.checkPermission(perm);
    662                 } catch (SecurityException se) {
    663                     // fallback to checkRead/checkConnect for pre 1.2
    664                     // security managers
    665                     if ((perm instanceof java.io.FilePermission) &&
    666                         perm.getActions().indexOf("read") != -1) {
    667                         security.checkRead(perm.getName());
    668                     } else if ((perm instanceof
    669                         java.net.SocketPermission) &&
    670                         perm.getActions().indexOf("connect") != -1) {
    671                         URL locUrl = url;
    672                         if (urlConnection instanceof JarURLConnection) {
    673                             locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
    674                         }
    675                         security.checkConnect(locUrl.getHost(),
    676                                               locUrl.getPort());
    677                     } else {
    678                         throw se;
    679                     }
    680                 }
    681             }
    682         }
    683     }
    684 
    685     /**
    686      * Inner class used to represent a loader of resources and classes
    687      * from a base URL.
    688      */
    689     private static class Loader implements Closeable {
    690         private final URL base;
    691         private JarFile jarfile; // if this points to a jar file
    692 
    693         /*
    694          * Creates a new Loader for the specified URL.
    695          */
    696         Loader(URL url) {
    697             base = url;
    698         }
    699 
    700         /*
    701          * Returns the base URL for this Loader.
    702          */
    703         URL getBaseURL() {
    704             return base;
    705         }
    706 
    707         URL findResource(final String name, boolean check) {
    708             URL url;
    709             try {
    710                 url = new URL(base, ParseUtil.encodePath(name, false));
    711             } catch (MalformedURLException e) {
    712                 throw new IllegalArgumentException("name");
    713             }
    714 
    715             try {
    716                 if (check) {
    717                     URLClassPath.check(url);
    718                 }
    719 
    720                 /*
    721                  * For a HTTP connection we use the HEAD method to
    722                  * check if the resource exists.
    723                  */
    724                 URLConnection uc = url.openConnection();
    725                 if (uc instanceof HttpURLConnection) {
    726                     HttpURLConnection hconn = (HttpURLConnection)uc;
    727                     hconn.setRequestMethod("HEAD");
    728                     if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
    729                         return null;
    730                     }
    731                 } else {
    732                     // our best guess for the other cases
    733                     uc.setUseCaches(false);
    734                     InputStream is = uc.getInputStream();
    735                     is.close();
    736                 }
    737                 return url;
    738             } catch (Exception e) {
    739                 return null;
    740             }
    741         }
    742 
    743         Resource getResource(final String name, boolean check) {
    744             final URL url;
    745             try {
    746                 url = new URL(base, ParseUtil.encodePath(name, false));
    747             } catch (MalformedURLException e) {
    748                 throw new IllegalArgumentException("name");
    749             }
    750             final URLConnection uc;
    751             try {
    752                 if (check) {
    753                     URLClassPath.check(url);
    754                 }
    755                 uc = url.openConnection();
    756                 InputStream in = uc.getInputStream();
    757                 if (uc instanceof JarURLConnection) {
    758                     /* Need to remember the jar file so it can be closed
    759                      * in a hurry.
    760                      */
    761                     JarURLConnection juc = (JarURLConnection)uc;
    762                     jarfile = JarLoader.checkJar(juc.getJarFile());
    763                 }
    764             } catch (Exception e) {
    765                 return null;
    766             }
    767             return new Resource() {
    768                 public String getName() { return name; }
    769                 public URL getURL() { return url; }
    770                 public URL getCodeSourceURL() { return base; }
    771                 public InputStream getInputStream() throws IOException {
    772                     return uc.getInputStream();
    773                 }
    774                 public int getContentLength() throws IOException {
    775                     return uc.getContentLength();
    776                 }
    777             };
    778         }
    779 
    780         /*
    781          * Returns the Resource for the specified name, or null if not
    782          * found or the caller does not have the permission to get the
    783          * resource.
    784          */
    785         Resource getResource(final String name) {
    786             return getResource(name, true);
    787         }
    788 
    789         /*
    790          * close this loader and release all resources
    791          * method overridden in sub-classes
    792          */
    793         public void close () throws IOException {
    794             if (jarfile != null) {
    795                 jarfile.close();
    796             }
    797         }
    798 
    799         /*
    800          * Returns the local class path for this loader, or null if none.
    801          */
    802         URL[] getClassPath() throws IOException {
    803             return null;
    804         }
    805     }
    806 
    807     /*
    808      * Inner class used to represent a Loader of resources from a JAR URL.
    809      */
    810     static class JarLoader extends Loader {
    811         private JarFile jar;
    812         private final URL csu;
    813         private JarIndex index;
    814         private MetaIndex metaIndex;
    815         private URLStreamHandler handler;
    816         private final HashMap<String, Loader> lmap;
    817         private final AccessControlContext acc;
    818         private boolean closed = false;
    819         // Android-changed: Not needed, called directly
    820         // private static final sun.misc.JavaUtilZipFileAccess zipAccess =
    821         //      sun.misc.SharedSecrets.getJavaUtilZipFileAccess();
    822 
    823         /*
    824          * Creates a new JarLoader for the specified URL referring to
    825          * a JAR file.
    826          */
    827         JarLoader(URL url, URLStreamHandler jarHandler,
    828                   HashMap<String, Loader> loaderMap,
    829                   AccessControlContext acc)
    830             throws IOException
    831         {
    832             super(new URL("jar", "", -1, url + "!/", jarHandler));
    833             csu = url;
    834             handler = jarHandler;
    835             lmap = loaderMap;
    836             this.acc = acc;
    837 
    838             if (!isOptimizable(url)) {
    839                 ensureOpen();
    840             } else {
    841                  String fileName = url.getFile();
    842                 if (fileName != null) {
    843                     fileName = ParseUtil.decode(fileName);
    844                     File f = new File(fileName);
    845                     metaIndex = MetaIndex.forJar(f);
    846                     // If the meta index is found but the file is not
    847                     // installed, set metaIndex to null. A typical
    848                     // senario is charsets.jar which won't be installed
    849                     // when the user is running in certain locale environment.
    850                     // The side effect of null metaIndex will cause
    851                     // ensureOpen get called so that IOException is thrown.
    852                     if (metaIndex != null && !f.exists()) {
    853                         metaIndex = null;
    854                     }
    855                 }
    856 
    857                 // metaIndex is null when either there is no such jar file
    858                 // entry recorded in meta-index file or such jar file is
    859                 // missing in JRE. See bug 6340399.
    860                 if (metaIndex == null) {
    861                     ensureOpen();
    862                 }
    863             }
    864         }
    865 
    866         @Override
    867         public void close () throws IOException {
    868             // closing is synchronized at higher level
    869             if (!closed) {
    870                 closed = true;
    871                 // in case not already open.
    872                 ensureOpen();
    873                 jar.close();
    874             }
    875         }
    876 
    877         JarFile getJarFile () {
    878             return jar;
    879         }
    880 
    881         private boolean isOptimizable(URL url) {
    882             return "file".equals(url.getProtocol());
    883         }
    884 
    885         private void ensureOpen() throws IOException {
    886             if (jar == null) {
    887                 try {
    888                     java.security.AccessController.doPrivileged(
    889                         new java.security.PrivilegedExceptionAction<Void>() {
    890                             public Void run() throws IOException {
    891                                 if (DEBUG) {
    892                                     System.err.println("Opening " + csu);
    893                                     Thread.dumpStack();
    894                                 }
    895 
    896                                 jar = getJarFile(csu);
    897                                 index = JarIndex.getJarIndex(jar, metaIndex);
    898                                 if (index != null) {
    899                                     String[] jarfiles = index.getJarFiles();
    900                                 // Add all the dependent URLs to the lmap so that loaders
    901                                 // will not be created for them by URLClassPath.getLoader(int)
    902                                 // if the same URL occurs later on the main class path.  We set
    903                                 // Loader to null here to avoid creating a Loader for each
    904                                 // URL until we actually need to try to load something from them.
    905                                     for(int i = 0; i < jarfiles.length; i++) {
    906                                         try {
    907                                             URL jarURL = new URL(csu, jarfiles[i]);
    908                                             // If a non-null loader already exists, leave it alone.
    909                                             String urlNoFragString = URLUtil.urlNoFragString(jarURL);
    910                                             if (!lmap.containsKey(urlNoFragString)) {
    911                                                 lmap.put(urlNoFragString, null);
    912                                             }
    913                                         } catch (MalformedURLException e) {
    914                                             continue;
    915                                         }
    916                                     }
    917                                 }
    918                                 return null;
    919                             }
    920                         }, acc);
    921                 } catch (java.security.PrivilegedActionException pae) {
    922                     throw (IOException)pae.getException();
    923                 }
    924             }
    925         }
    926 
    927         /* Throws if the given jar file is does not start with the correct LOC */
    928         static JarFile checkJar(JarFile jar) throws IOException {
    929             if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING
    930                 && !jar.startsWithLocHeader()) {
    931                 IOException x = new IOException("Invalid Jar file");
    932                 try {
    933                     jar.close();
    934                 } catch (IOException ex) {
    935                     x.addSuppressed(ex);
    936                 }
    937                 throw x;
    938             }
    939 
    940             return jar;
    941         }
    942 
    943         private JarFile getJarFile(URL url) throws IOException {
    944             // Optimize case where url refers to a local jar file
    945             if (isOptimizable(url)) {
    946                 FileURLMapper p = new FileURLMapper (url);
    947                 if (!p.exists()) {
    948                     throw new FileNotFoundException(p.getPath());
    949                 }
    950                 return checkJar(new JarFile(p.getPath()));
    951             }
    952             URLConnection uc = getBaseURL().openConnection();
    953             uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
    954             JarFile jarFile = ((JarURLConnection)uc).getJarFile();
    955             return checkJar(jarFile);
    956         }
    957 
    958         /*
    959          * Returns the index of this JarLoader if it exists.
    960          */
    961         JarIndex getIndex() {
    962             try {
    963                 ensureOpen();
    964             } catch (IOException e) {
    965                 throw new InternalError(e);
    966             }
    967             return index;
    968         }
    969 
    970         /*
    971          * Creates the resource and if the check flag is set to true, checks if
    972          * is its okay to return the resource.
    973          */
    974         Resource checkResource(final String name, boolean check,
    975             final JarEntry entry) {
    976 
    977             final URL url;
    978             try {
    979                 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
    980                 if (check) {
    981                     URLClassPath.check(url);
    982                 }
    983             } catch (MalformedURLException e) {
    984                 return null;
    985                 // throw new IllegalArgumentException("name");
    986             } catch (IOException e) {
    987                 return null;
    988             } catch (AccessControlException e) {
    989                 return null;
    990             }
    991 
    992             return new Resource() {
    993                 public String getName() { return name; }
    994                 public URL getURL() { return url; }
    995                 public URL getCodeSourceURL() { return csu; }
    996                 public InputStream getInputStream() throws IOException
    997                     { return jar.getInputStream(entry); }
    998                 public int getContentLength()
    999                     { return (int)entry.getSize(); }
   1000                 public Manifest getManifest() throws IOException
   1001                     { return jar.getManifest(); };
   1002                 public Certificate[] getCertificates()
   1003                     { return entry.getCertificates(); };
   1004                 public CodeSigner[] getCodeSigners()
   1005                     { return entry.getCodeSigners(); };
   1006             };
   1007         }
   1008 
   1009 
   1010         /*
   1011          * Returns true iff atleast one resource in the jar file has the same
   1012          * package name as that of the specified resource name.
   1013          */
   1014         boolean validIndex(final String name) {
   1015             String packageName = name;
   1016             int pos;
   1017             if((pos = name.lastIndexOf("/")) != -1) {
   1018                 packageName = name.substring(0, pos);
   1019             }
   1020 
   1021             String entryName;
   1022             ZipEntry entry;
   1023             Enumeration<JarEntry> enum_ = jar.entries();
   1024             while (enum_.hasMoreElements()) {
   1025                 entry = enum_.nextElement();
   1026                 entryName = entry.getName();
   1027                 if((pos = entryName.lastIndexOf("/")) != -1)
   1028                     entryName = entryName.substring(0, pos);
   1029                 if (entryName.equals(packageName)) {
   1030                     return true;
   1031                 }
   1032             }
   1033             return false;
   1034         }
   1035 
   1036         /*
   1037          * Returns the URL for a resource with the specified name
   1038          */
   1039         URL findResource(final String name, boolean check) {
   1040             Resource rsc = getResource(name, check);
   1041             if (rsc != null) {
   1042                 return rsc.getURL();
   1043             }
   1044             return null;
   1045         }
   1046 
   1047         /*
   1048          * Returns the JAR Resource for the specified name.
   1049          */
   1050         Resource getResource(final String name, boolean check) {
   1051             if (metaIndex != null) {
   1052                 if (!metaIndex.mayContain(name)) {
   1053                     return null;
   1054                 }
   1055             }
   1056 
   1057             try {
   1058                 ensureOpen();
   1059             } catch (IOException e) {
   1060                 throw new InternalError(e);
   1061             }
   1062             final JarEntry entry = jar.getJarEntry(name);
   1063             if (entry != null)
   1064                 return checkResource(name, check, entry);
   1065 
   1066             if (index == null)
   1067                 return null;
   1068 
   1069             HashSet<String> visited = new HashSet<String>();
   1070             return getResource(name, check, visited);
   1071         }
   1072 
   1073         /*
   1074          * Version of getResource() that tracks the jar files that have been
   1075          * visited by linking through the index files. This helper method uses
   1076          * a HashSet to store the URLs of jar files that have been searched and
   1077          * uses it to avoid going into an infinite loop, looking for a
   1078          * non-existent resource
   1079          */
   1080         Resource getResource(final String name, boolean check,
   1081                              Set<String> visited) {
   1082 
   1083             Resource res;
   1084             String[] jarFiles;
   1085             int count = 0;
   1086             LinkedList<String> jarFilesList = null;
   1087 
   1088             /* If there no jar files in the index that can potential contain
   1089              * this resource then return immediately.
   1090              */
   1091             if((jarFilesList = index.get(name)) == null)
   1092                 return null;
   1093 
   1094             do {
   1095                 int size = jarFilesList.size();
   1096                 jarFiles = jarFilesList.toArray(new String[size]);
   1097                 /* loop through the mapped jar file list */
   1098                 while(count < size) {
   1099                     String jarName = jarFiles[count++];
   1100                     JarLoader newLoader;
   1101                     final URL url;
   1102 
   1103                     try{
   1104                         url = new URL(csu, jarName);
   1105                         String urlNoFragString = URLUtil.urlNoFragString(url);
   1106                         if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) {
   1107                             /* no loader has been set up for this jar file
   1108                              * before
   1109                              */
   1110                             newLoader = AccessController.doPrivileged(
   1111                                 new PrivilegedExceptionAction<JarLoader>() {
   1112                                     public JarLoader run() throws IOException {
   1113                                         return new JarLoader(url, handler,
   1114                                             lmap, acc);
   1115                                     }
   1116                                 }, acc);
   1117 
   1118                             /* this newly opened jar file has its own index,
   1119                              * merge it into the parent's index, taking into
   1120                              * account the relative path.
   1121                              */
   1122                             JarIndex newIndex = newLoader.getIndex();
   1123                             if(newIndex != null) {
   1124                                 int pos = jarName.lastIndexOf("/");
   1125                                 newIndex.merge(this.index, (pos == -1 ?
   1126                                     null : jarName.substring(0, pos + 1)));
   1127                             }
   1128 
   1129                             /* put it in the global hashtable */
   1130                             lmap.put(urlNoFragString, newLoader);
   1131                         }
   1132                     } catch (java.security.PrivilegedActionException pae) {
   1133                         continue;
   1134                     } catch (MalformedURLException e) {
   1135                         continue;
   1136                     }
   1137 
   1138 
   1139                     /* Note that the addition of the url to the list of visited
   1140                      * jars incorporates a check for presence in the hashmap
   1141                      */
   1142                     boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url));
   1143                     if (!visitedURL) {
   1144                         try {
   1145                             newLoader.ensureOpen();
   1146                         } catch (IOException e) {
   1147                             throw new InternalError(e);
   1148                         }
   1149                         final JarEntry entry = newLoader.jar.getJarEntry(name);
   1150                         if (entry != null) {
   1151                             return newLoader.checkResource(name, check, entry);
   1152                         }
   1153 
   1154                         /* Verify that at least one other resource with the
   1155                          * same package name as the lookedup resource is
   1156                          * present in the new jar
   1157                          */
   1158                         if (!newLoader.validIndex(name)) {
   1159                             /* the mapping is wrong */
   1160                             throw new InvalidJarIndexException("Invalid index");
   1161                         }
   1162                     }
   1163 
   1164                     /* If newLoader is the current loader or if it is a
   1165                      * loader that has already been searched or if the new
   1166                      * loader does not have an index then skip it
   1167                      * and move on to the next loader.
   1168                      */
   1169                     if (visitedURL || newLoader == this ||
   1170                             newLoader.getIndex() == null) {
   1171                         continue;
   1172                     }
   1173 
   1174                     /* Process the index of the new loader
   1175                      */
   1176                     if((res = newLoader.getResource(name, check, visited))
   1177                             != null) {
   1178                         return res;
   1179                     }
   1180                 }
   1181                 // Get the list of jar files again as the list could have grown
   1182                 // due to merging of index files.
   1183                 jarFilesList = index.get(name);
   1184 
   1185             // If the count is unchanged, we are done.
   1186             } while(count < jarFilesList.size());
   1187             return null;
   1188         }
   1189 
   1190 
   1191         /*
   1192          * Returns the JAR file local class path, or null if none.
   1193          */
   1194         URL[] getClassPath() throws IOException {
   1195             if (index != null) {
   1196                 return null;
   1197             }
   1198 
   1199             if (metaIndex != null) {
   1200                 return null;
   1201             }
   1202 
   1203             ensureOpen();
   1204             parseExtensionsDependencies();
   1205             if (jar.hasClassPathAttribute()) { // Only get manifest when necessary
   1206                 Manifest man = jar.getManifest();
   1207                 if (man != null) {
   1208                     Attributes attr = man.getMainAttributes();
   1209                     if (attr != null) {
   1210                         String value = attr.getValue(Name.CLASS_PATH);
   1211                         if (value != null) {
   1212                             return parseClassPath(csu, value);
   1213                         }
   1214                     }
   1215                 }
   1216             }
   1217             return null;
   1218         }
   1219 
   1220         /*
   1221          * parse the standard extension dependencies
   1222          */
   1223         private void  parseExtensionsDependencies() throws IOException {
   1224             // Android-changed: checkExtensionsDependencies(jar) is not supported on Android.
   1225             //ExtensionDependency.checkExtensionsDependencies(jar);
   1226         }
   1227 
   1228         /*
   1229          * Parses value of the Class-Path manifest attribute and returns
   1230          * an array of URLs relative to the specified base URL.
   1231          */
   1232         private URL[] parseClassPath(URL base, String value)
   1233             throws MalformedURLException
   1234         {
   1235             StringTokenizer st = new StringTokenizer(value);
   1236             URL[] urls = new URL[st.countTokens()];
   1237             int i = 0;
   1238             while (st.hasMoreTokens()) {
   1239                 String path = st.nextToken();
   1240                 urls[i] = new URL(base, path);
   1241                 i++;
   1242             }
   1243             return urls;
   1244         }
   1245     }
   1246 
   1247     /*
   1248      * Inner class used to represent a loader of classes and resources
   1249      * from a file URL that refers to a directory.
   1250      */
   1251     private static class FileLoader extends Loader {
   1252         /* Canonicalized File */
   1253         private File dir;
   1254 
   1255         FileLoader(URL url) throws IOException {
   1256             super(url);
   1257             if (!"file".equals(url.getProtocol())) {
   1258                 throw new IllegalArgumentException("url");
   1259             }
   1260             String path = url.getFile().replace('/', File.separatorChar);
   1261             path = ParseUtil.decode(path);
   1262             dir = (new File(path)).getCanonicalFile();
   1263         }
   1264 
   1265         /*
   1266          * Returns the URL for a resource with the specified name
   1267          */
   1268         URL findResource(final String name, boolean check) {
   1269             Resource rsc = getResource(name, check);
   1270             if (rsc != null) {
   1271                 return rsc.getURL();
   1272             }
   1273             return null;
   1274         }
   1275 
   1276         Resource getResource(final String name, boolean check) {
   1277             final URL url;
   1278             try {
   1279                 URL normalizedBase = new URL(getBaseURL(), ".");
   1280                 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
   1281 
   1282                 if (url.getFile().startsWith(normalizedBase.getFile()) == false) {
   1283                     // requested resource had ../..'s in path
   1284                     return null;
   1285                 }
   1286 
   1287                 if (check)
   1288                     URLClassPath.check(url);
   1289 
   1290                 final File file;
   1291                 if (name.indexOf("..") != -1) {
   1292                     file = (new File(dir, name.replace('/', File.separatorChar)))
   1293                           .getCanonicalFile();
   1294                     if ( !((file.getPath()).startsWith(dir.getPath())) ) {
   1295                         /* outside of base dir */
   1296                         return null;
   1297                     }
   1298                 } else {
   1299                     file = new File(dir, name.replace('/', File.separatorChar));
   1300                 }
   1301 
   1302                 if (file.exists()) {
   1303                     return new Resource() {
   1304                         public String getName() { return name; };
   1305                         public URL getURL() { return url; };
   1306                         public URL getCodeSourceURL() { return getBaseURL(); };
   1307                         public InputStream getInputStream() throws IOException
   1308                             { return new FileInputStream(file); };
   1309                         public int getContentLength() throws IOException
   1310                             { return (int)file.length(); };
   1311                     };
   1312                 }
   1313             } catch (Exception e) {
   1314                 return null;
   1315             }
   1316             return null;
   1317         }
   1318     }
   1319 }
   1320