Home | History | Annotate | Download | only in misc
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  * Copyright (c) 1997, 2011, 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.AccessController;
     48 import java.security.AccessControlException;
     49 import java.security.CodeSigner;
     50 import java.security.Permission;
     51 import java.security.PrivilegedAction;
     52 import java.security.PrivilegedExceptionAction;
     53 import java.security.cert.Certificate;
     54 import sun.misc.FileURLMapper;
     55 import sun.net.util.URLUtil;
     56 
     57 /**
     58  * This class is used to maintain a search path of URLs for loading classes
     59  * and resources from both JAR files and directories.
     60  *
     61  * @author  David Connelly
     62  */
     63 public class URLClassPath {
     64     final static String USER_AGENT_JAVA_VERSION = "UA-Java-Version";
     65     final static String JAVA_VERSION;
     66     private static final boolean DEBUG;
     67     private static final boolean DISABLE_JAR_CHECKING;
     68 
     69     static {
     70         JAVA_VERSION = java.security.AccessController.doPrivileged(
     71             new sun.security.action.GetPropertyAction("java.version"));
     72         DEBUG        = (java.security.AccessController.doPrivileged(
     73             new sun.security.action.GetPropertyAction("sun.misc.URLClassPath.debug")) != null);
     74         String p = java.security.AccessController.doPrivileged(
     75             new sun.security.action.GetPropertyAction("sun.misc.URLClassPath.disableJarChecking"));
     76         DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.equals("") : false;
     77     }
     78 
     79     /* The original search path of URLs. */
     80     private ArrayList<URL> path = new ArrayList<URL>();
     81 
     82     /* The stack of unopened URLs */
     83     Stack<URL> urls = new Stack<URL>();
     84 
     85     /* The resulting search path of Loaders */
     86     ArrayList<Loader> loaders = new ArrayList<Loader>();
     87 
     88     /* Map of each URL opened to its corresponding Loader */
     89     HashMap<String, Loader> lmap = new HashMap<String, Loader>();
     90 
     91     /* The jar protocol handler to use when creating new URLs */
     92     private URLStreamHandler jarHandler;
     93 
     94     /* Whether this URLClassLoader has been closed yet */
     95     private boolean closed = false;
     96 
     97     /**
     98      * Creates a new URLClassPath for the given URLs. The URLs will be
     99      * searched in the order specified for classes and resources. A URL
    100      * ending with a '/' is assumed to refer to a directory. Otherwise,
    101      * the URL is assumed to refer to a JAR file.
    102      *
    103      * @param urls the directory and JAR file URLs to search for classes
    104      *        and resources
    105      * @param factory the URLStreamHandlerFactory to use when creating new URLs
    106      */
    107     public URLClassPath(URL[] urls, URLStreamHandlerFactory factory) {
    108         for (int i = 0; i < urls.length; i++) {
    109             path.add(urls[i]);
    110         }
    111         push(urls);
    112         if (factory != null) {
    113             jarHandler = factory.createURLStreamHandler("jar");
    114         }
    115     }
    116 
    117     public URLClassPath(URL[] urls) {
    118         this(urls, null);
    119     }
    120 
    121     public synchronized List<IOException> closeLoaders() {
    122         if (closed) {
    123             return Collections.emptyList();
    124         }
    125         List<IOException> result = new LinkedList<IOException>();
    126         for (Loader loader : loaders) {
    127             try {
    128                 loader.close();
    129             } catch (IOException e) {
    130                 result.add (e);
    131             }
    132         }
    133         closed = true;
    134         return result;
    135     }
    136 
    137     /**
    138      * Appends the specified URL to the search path of directory and JAR
    139      * file URLs from which to load classes and resources.
    140      * <p>
    141      * If the URL specified is null or is already in the list of
    142      * URLs, then invoking this method has no effect.
    143      */
    144     public synchronized void addURL(URL url) {
    145         if (closed)
    146             return;
    147         synchronized (urls) {
    148             if (url == null || path.contains(url))
    149                 return;
    150 
    151             urls.add(0, url);
    152             path.add(url);
    153         }
    154     }
    155 
    156     /**
    157      * Returns the original search path of URLs.
    158      */
    159     public URL[] getURLs() {
    160         synchronized (urls) {
    161             return path.toArray(new URL[path.size()]);
    162         }
    163     }
    164 
    165     /**
    166      * Finds the resource with the specified name on the URL search path
    167      * or null if not found or security check fails.
    168      *
    169      * @param name      the name of the resource
    170      * @param check     whether to perform a security check
    171      * @return a <code>URL</code> for the resource, or <code>null</code>
    172      * if the resource could not be found.
    173      */
    174     public URL findResource(String name, boolean check) {
    175         Loader loader;
    176         for (int i = 0; (loader = getLoader(i)) != null; i++) {
    177             URL url = loader.findResource(name, check);
    178             if (url != null) {
    179                 return url;
    180             }
    181         }
    182         return null;
    183     }
    184 
    185     /**
    186      * Finds the first Resource on the URL search path which has the specified
    187      * name. Returns null if no Resource could be found.
    188      *
    189      * @param name the name of the Resource
    190      * @param check     whether to perform a security check
    191      * @return the Resource, or null if not found
    192      */
    193     public Resource getResource(String name, boolean check) {
    194         if (DEBUG) {
    195             System.err.println("URLClassPath.getResource(\"" + name + "\")");
    196         }
    197 
    198         Loader loader;
    199         for (int i = 0; (loader = getLoader(i)) != null; i++) {
    200             Resource res = loader.getResource(name, check);
    201             if (res != null) {
    202                 return res;
    203             }
    204         }
    205         return null;
    206     }
    207 
    208     /**
    209      * Finds all resources on the URL search path with the given name.
    210      * Returns an enumeration of the URL objects.
    211      *
    212      * @param name the resource name
    213      * @return an Enumeration of all the urls having the specified name
    214      */
    215     public Enumeration<URL> findResources(final String name,
    216                                      final boolean check) {
    217         return new Enumeration<URL>() {
    218             private int index = 0;
    219             private URL url = null;
    220 
    221             private boolean next() {
    222                 if (url != null) {
    223                     return true;
    224                 } else {
    225                     Loader loader;
    226                     while ((loader = getLoader(index++)) != null) {
    227                         url = loader.findResource(name, check);
    228                         if (url != null) {
    229                             return true;
    230                         }
    231                     }
    232                     return false;
    233                 }
    234             }
    235 
    236             public boolean hasMoreElements() {
    237                 return next();
    238             }
    239 
    240             public URL nextElement() {
    241                 if (!next()) {
    242                     throw new NoSuchElementException();
    243                 }
    244                 URL u = url;
    245                 url = null;
    246                 return u;
    247             }
    248         };
    249     }
    250 
    251     public Resource getResource(String name) {
    252         return getResource(name, true);
    253     }
    254 
    255     /**
    256      * Finds all resources on the URL search path with the given name.
    257      * Returns an enumeration of the Resource objects.
    258      *
    259      * @param name the resource name
    260      * @return an Enumeration of all the resources having the specified name
    261      */
    262     public Enumeration<Resource> getResources(final String name,
    263                                     final boolean check) {
    264         return new Enumeration<Resource>() {
    265             private int index = 0;
    266             private Resource res = null;
    267 
    268             private boolean next() {
    269                 if (res != null) {
    270                     return true;
    271                 } else {
    272                     Loader loader;
    273                     while ((loader = getLoader(index++)) != null) {
    274                         res = loader.getResource(name, check);
    275                         if (res != null) {
    276                             return true;
    277                         }
    278                     }
    279                     return false;
    280                 }
    281             }
    282 
    283             public boolean hasMoreElements() {
    284                 return next();
    285             }
    286 
    287             public Resource nextElement() {
    288                 if (!next()) {
    289                     throw new NoSuchElementException();
    290                 }
    291                 Resource r = res;
    292                 res = null;
    293                 return r;
    294             }
    295         };
    296     }
    297 
    298     public Enumeration<Resource> getResources(final String name) {
    299         return getResources(name, true);
    300     }
    301 
    302     /*
    303      * Returns the Loader at the specified position in the URL search
    304      * path. The URLs are opened and expanded as needed. Returns null
    305      * if the specified index is out of range.
    306      */
    307      private synchronized Loader getLoader(int index) {
    308         if (closed) {
    309             return null;
    310         }
    311          // Expand URL search path until the request can be satisfied
    312          // or the URL stack is empty.
    313         while (loaders.size() < index + 1) {
    314             // Pop the next URL from the URL stack
    315             URL url;
    316             synchronized (urls) {
    317                 if (urls.empty()) {
    318                     return null;
    319                 } else {
    320                     url = urls.pop();
    321                 }
    322             }
    323             // Skip this URL if it already has a Loader. (Loader
    324             // may be null in the case where URL has not been opened
    325             // but is referenced by a JAR index.)
    326             String urlNoFragString = URLUtil.urlNoFragString(url);
    327             if (lmap.containsKey(urlNoFragString)) {
    328                 continue;
    329             }
    330             // Otherwise, create a new Loader for the URL.
    331             Loader loader;
    332             try {
    333                 loader = getLoader(url);
    334                 // If the loader defines a local class path then add the
    335                 // URLs to the list of URLs to be opened.
    336                 URL[] urls = loader.getClassPath();
    337                 if (urls != null) {
    338                     push(urls);
    339                 }
    340             } catch (IOException e) {
    341                 // Silently ignore for now...
    342                 continue;
    343             }
    344             // Finally, add the Loader to the search path.
    345             loaders.add(loader);
    346             lmap.put(urlNoFragString, loader);
    347         }
    348         return loaders.get(index);
    349     }
    350 
    351     /*
    352      * Returns the Loader for the specified base URL.
    353      */
    354     private Loader getLoader(final URL url) throws IOException {
    355         try {
    356             return java.security.AccessController.doPrivileged(
    357                 new java.security.PrivilegedExceptionAction<Loader>() {
    358                 public Loader run() throws IOException {
    359                     String file = url.getFile();
    360                     if (file != null && file.endsWith("/")) {
    361                         if ("file".equals(url.getProtocol())) {
    362                             return new FileLoader(url);
    363                         } else {
    364                             return new Loader(url);
    365                         }
    366                     } else {
    367                         return new JarLoader(url, jarHandler, lmap);
    368                     }
    369                 }
    370             });
    371         } catch (java.security.PrivilegedActionException pae) {
    372             throw (IOException)pae.getException();
    373         }
    374     }
    375 
    376     /*
    377      * Pushes the specified URLs onto the list of unopened URLs.
    378      */
    379     private void push(URL[] us) {
    380         synchronized (urls) {
    381             for (int i = us.length - 1; i >= 0; --i) {
    382                 urls.push(us[i]);
    383             }
    384         }
    385     }
    386 
    387     /**
    388      * Convert class path specification into an array of file URLs.
    389      *
    390      * The path of the file is encoded before conversion into URL
    391      * form so that reserved characters can safely appear in the path.
    392      */
    393     public static URL[] pathToURLs(String path) {
    394         StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
    395         URL[] urls = new URL[st.countTokens()];
    396         int count = 0;
    397         while (st.hasMoreTokens()) {
    398             File f = new File(st.nextToken());
    399             try {
    400                 f = new File(f.getCanonicalPath());
    401             } catch (IOException x) {
    402                 // use the non-canonicalized filename
    403             }
    404             try {
    405                 urls[count++] = ParseUtil.fileToEncodedURL(f);
    406             } catch (IOException x) { }
    407         }
    408 
    409         if (urls.length != count) {
    410             URL[] tmp = new URL[count];
    411             System.arraycopy(urls, 0, tmp, 0, count);
    412             urls = tmp;
    413         }
    414         return urls;
    415     }
    416 
    417     /*
    418      * Check whether the resource URL should be returned.
    419      * Return null on security check failure.
    420      * Called by java.net.URLClassLoader.
    421      */
    422     public URL checkURL(URL url) {
    423         try {
    424             check(url);
    425         } catch (Exception e) {
    426             return null;
    427         }
    428 
    429         return url;
    430     }
    431 
    432     /*
    433      * Check whether the resource URL should be returned.
    434      * Throw exception on failure.
    435      * Called internally within this file.
    436      */
    437     static void check(URL url) throws IOException {
    438         SecurityManager security = System.getSecurityManager();
    439         if (security != null) {
    440             URLConnection urlConnection = url.openConnection();
    441             Permission perm = urlConnection.getPermission();
    442             if (perm != null) {
    443                 try {
    444                     security.checkPermission(perm);
    445                 } catch (SecurityException se) {
    446                     // fallback to checkRead/checkConnect for pre 1.2
    447                     // security managers
    448                     if ((perm instanceof java.io.FilePermission) &&
    449                         perm.getActions().indexOf("read") != -1) {
    450                         security.checkRead(perm.getName());
    451                     } else if ((perm instanceof
    452                         java.net.SocketPermission) &&
    453                         perm.getActions().indexOf("connect") != -1) {
    454                         URL locUrl = url;
    455                         if (urlConnection instanceof JarURLConnection) {
    456                             locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
    457                         }
    458                         security.checkConnect(locUrl.getHost(),
    459                                               locUrl.getPort());
    460                     } else {
    461                         throw se;
    462                     }
    463                 }
    464             }
    465         }
    466     }
    467 
    468     /**
    469      * Inner class used to represent a loader of resources and classes
    470      * from a base URL.
    471      */
    472     private static class Loader implements Closeable {
    473         private final URL base;
    474         private JarFile jarfile; // if this points to a jar file
    475 
    476         /*
    477          * Creates a new Loader for the specified URL.
    478          */
    479         Loader(URL url) {
    480             base = url;
    481         }
    482 
    483         /*
    484          * Returns the base URL for this Loader.
    485          */
    486         URL getBaseURL() {
    487             return base;
    488         }
    489 
    490         URL findResource(final String name, boolean check) {
    491             URL url;
    492             try {
    493                 url = new URL(base, ParseUtil.encodePath(name, false));
    494             } catch (MalformedURLException e) {
    495                 throw new IllegalArgumentException("name");
    496             }
    497 
    498             try {
    499                 if (check) {
    500                     URLClassPath.check(url);
    501                 }
    502 
    503                 /*
    504                  * For a HTTP connection we use the HEAD method to
    505                  * check if the resource exists.
    506                  */
    507                 URLConnection uc = url.openConnection();
    508                 if (uc instanceof HttpURLConnection) {
    509                     HttpURLConnection hconn = (HttpURLConnection)uc;
    510                     hconn.setRequestMethod("HEAD");
    511                     if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
    512                         return null;
    513                     }
    514                 } else {
    515                     // our best guess for the other cases
    516                     InputStream is = url.openStream();
    517                     is.close();
    518                 }
    519                 return url;
    520             } catch (Exception e) {
    521                 return null;
    522             }
    523         }
    524 
    525         Resource getResource(final String name, boolean check) {
    526             final URL url;
    527             try {
    528                 url = new URL(base, ParseUtil.encodePath(name, false));
    529             } catch (MalformedURLException e) {
    530                 throw new IllegalArgumentException("name");
    531             }
    532             final URLConnection uc;
    533             try {
    534                 if (check) {
    535                     URLClassPath.check(url);
    536                 }
    537                 uc = url.openConnection();
    538                 InputStream in = uc.getInputStream();
    539                 if (uc instanceof JarURLConnection) {
    540                     /* Need to remember the jar file so it can be closed
    541                      * in a hurry.
    542                      */
    543                     JarURLConnection juc = (JarURLConnection)uc;
    544                     jarfile = JarLoader.checkJar(juc.getJarFile());
    545                 }
    546             } catch (Exception e) {
    547                 return null;
    548             }
    549             return new Resource() {
    550                 public String getName() { return name; }
    551                 public URL getURL() { return url; }
    552                 public URL getCodeSourceURL() { return base; }
    553                 public InputStream getInputStream() throws IOException {
    554                     return uc.getInputStream();
    555                 }
    556                 public int getContentLength() throws IOException {
    557                     return uc.getContentLength();
    558                 }
    559             };
    560         }
    561 
    562         /*
    563          * Returns the Resource for the specified name, or null if not
    564          * found or the caller does not have the permission to get the
    565          * resource.
    566          */
    567         Resource getResource(final String name) {
    568             return getResource(name, true);
    569         }
    570 
    571         /*
    572          * close this loader and release all resources
    573          * method overridden in sub-classes
    574          */
    575         public void close () throws IOException {
    576             if (jarfile != null) {
    577                 jarfile.close();
    578             }
    579         }
    580 
    581         /*
    582          * Returns the local class path for this loader, or null if none.
    583          */
    584         URL[] getClassPath() throws IOException {
    585             return null;
    586         }
    587     }
    588 
    589     /*
    590      * Inner class used to represent a Loader of resources from a JAR URL.
    591      */
    592     static class JarLoader extends Loader {
    593         private JarFile jar;
    594         private URL csu;
    595         private JarIndex index;
    596         private MetaIndex metaIndex;
    597         private URLStreamHandler handler;
    598         private HashMap<String, Loader> lmap;
    599         private boolean closed = false;
    600 
    601         /*
    602          * Creates a new JarLoader for the specified URL referring to
    603          * a JAR file.
    604          */
    605         JarLoader(URL url, URLStreamHandler jarHandler,
    606                   HashMap<String, Loader> loaderMap)
    607             throws IOException
    608         {
    609             super(new URL("jar", "", -1, url + "!/", jarHandler));
    610             csu = url;
    611             handler = jarHandler;
    612             lmap = loaderMap;
    613 
    614             if (!isOptimizable(url)) {
    615                 ensureOpen();
    616             } else {
    617                  String fileName = url.getFile();
    618                 if (fileName != null) {
    619                     fileName = ParseUtil.decode(fileName);
    620                     File f = new File(fileName);
    621                     metaIndex = MetaIndex.forJar(f);
    622                     // If the meta index is found but the file is not
    623                     // installed, set metaIndex to null. A typical
    624                     // senario is charsets.jar which won't be installed
    625                     // when the user is running in certain locale environment.
    626                     // The side effect of null metaIndex will cause
    627                     // ensureOpen get called so that IOException is thrown.
    628                     if (metaIndex != null && !f.exists()) {
    629                         metaIndex = null;
    630                     }
    631                 }
    632 
    633                 // metaIndex is null when either there is no such jar file
    634                 // entry recorded in meta-index file or such jar file is
    635                 // missing in JRE. See bug 6340399.
    636                 if (metaIndex == null) {
    637                     ensureOpen();
    638                 }
    639             }
    640         }
    641 
    642         @Override
    643         public void close () throws IOException {
    644             // closing is synchronized at higher level
    645             if (!closed) {
    646                 closed = true;
    647                 // in case not already open.
    648                 ensureOpen();
    649                 jar.close();
    650             }
    651         }
    652 
    653         JarFile getJarFile () {
    654             return jar;
    655         }
    656 
    657         private boolean isOptimizable(URL url) {
    658             return "file".equals(url.getProtocol());
    659         }
    660 
    661         private void ensureOpen() throws IOException {
    662             if (jar == null) {
    663                 try {
    664                     java.security.AccessController.doPrivileged(
    665                         new java.security.PrivilegedExceptionAction<Void>() {
    666                             public Void run() throws IOException {
    667                                 if (DEBUG) {
    668                                     System.err.println("Opening " + csu);
    669                                     Thread.dumpStack();
    670                                 }
    671 
    672                                 jar = getJarFile(csu);
    673                                 index = JarIndex.getJarIndex(jar, metaIndex);
    674                                 if (index != null) {
    675                                     String[] jarfiles = index.getJarFiles();
    676                                 // Add all the dependent URLs to the lmap so that loaders
    677                                 // will not be created for them by URLClassPath.getLoader(int)
    678                                 // if the same URL occurs later on the main class path.  We set
    679                                 // Loader to null here to avoid creating a Loader for each
    680                                 // URL until we actually need to try to load something from them.
    681                                     for(int i = 0; i < jarfiles.length; i++) {
    682                                         try {
    683                                             URL jarURL = new URL(csu, jarfiles[i]);
    684                                             // If a non-null loader already exists, leave it alone.
    685                                             String urlNoFragString = URLUtil.urlNoFragString(jarURL);
    686                                             if (!lmap.containsKey(urlNoFragString)) {
    687                                                 lmap.put(urlNoFragString, null);
    688                                             }
    689                                         } catch (MalformedURLException e) {
    690                                             continue;
    691                                         }
    692                                     }
    693                                 }
    694                                 return null;
    695                             }
    696                         }
    697                     );
    698                 } catch (java.security.PrivilegedActionException pae) {
    699                     throw (IOException)pae.getException();
    700                 }
    701             }
    702         }
    703 
    704         /* Throws if the given jar file is does not start with the correct LOC */
    705         static JarFile checkJar(JarFile jar) throws IOException {
    706             if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING
    707                 && !jar.startsWithLocHeader()) {
    708                 IOException x = new IOException("Invalid Jar file");
    709                 try {
    710                     jar.close();
    711                 } catch (IOException ex) {
    712                     x.addSuppressed(ex);
    713                 }
    714                 throw x;
    715             }
    716 
    717             return jar;
    718         }
    719 
    720         private JarFile getJarFile(URL url) throws IOException {
    721             // Optimize case where url refers to a local jar file
    722             if (isOptimizable(url)) {
    723                 FileURLMapper p = new FileURLMapper (url);
    724                 if (!p.exists()) {
    725                     throw new FileNotFoundException(p.getPath());
    726                 }
    727                 return checkJar(new JarFile(p.getPath()));
    728             }
    729             URLConnection uc = getBaseURL().openConnection();
    730             uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
    731             JarFile jarFile = ((JarURLConnection)uc).getJarFile();
    732             return checkJar(jarFile);
    733         }
    734 
    735         /*
    736          * Returns the index of this JarLoader if it exists.
    737          */
    738         JarIndex getIndex() {
    739             try {
    740                 ensureOpen();
    741             } catch (IOException e) {
    742                 throw (InternalError) new InternalError().initCause(e);
    743             }
    744             return index;
    745         }
    746 
    747         /*
    748          * Creates the resource and if the check flag is set to true, checks if
    749          * is its okay to return the resource.
    750          */
    751         Resource checkResource(final String name, boolean check,
    752             final JarEntry entry) {
    753 
    754             final URL url;
    755             try {
    756                 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
    757                 if (check) {
    758                     URLClassPath.check(url);
    759                 }
    760             } catch (MalformedURLException e) {
    761                 return null;
    762                 // throw new IllegalArgumentException("name");
    763             } catch (IOException e) {
    764                 return null;
    765             } catch (AccessControlException e) {
    766                 return null;
    767             }
    768 
    769             return new Resource() {
    770                 public String getName() { return name; }
    771                 public URL getURL() { return url; }
    772                 public URL getCodeSourceURL() { return csu; }
    773                 public InputStream getInputStream() throws IOException
    774                     { return jar.getInputStream(entry); }
    775                 public int getContentLength()
    776                     { return (int)entry.getSize(); }
    777                 public Manifest getManifest() throws IOException
    778                     { return jar.getManifest(); };
    779                 public Certificate[] getCertificates()
    780                     { return entry.getCertificates(); };
    781                 public CodeSigner[] getCodeSigners()
    782                     { return entry.getCodeSigners(); };
    783             };
    784         }
    785 
    786 
    787         /*
    788          * Returns true iff atleast one resource in the jar file has the same
    789          * package name as that of the specified resource name.
    790          */
    791         boolean validIndex(final String name) {
    792             String packageName = name;
    793             int pos;
    794             if((pos = name.lastIndexOf("/")) != -1) {
    795                 packageName = name.substring(0, pos);
    796             }
    797 
    798             String entryName;
    799             ZipEntry entry;
    800             Enumeration<JarEntry> enum_ = jar.entries();
    801             while (enum_.hasMoreElements()) {
    802                 entry = enum_.nextElement();
    803                 entryName = entry.getName();
    804                 if((pos = entryName.lastIndexOf("/")) != -1)
    805                     entryName = entryName.substring(0, pos);
    806                 if (entryName.equals(packageName)) {
    807                     return true;
    808                 }
    809             }
    810             return false;
    811         }
    812 
    813         /*
    814          * Returns the URL for a resource with the specified name
    815          */
    816         URL findResource(final String name, boolean check) {
    817             Resource rsc = getResource(name, check);
    818             if (rsc != null) {
    819                 return rsc.getURL();
    820             }
    821             return null;
    822         }
    823 
    824         /*
    825          * Returns the JAR Resource for the specified name.
    826          */
    827         Resource getResource(final String name, boolean check) {
    828             if (metaIndex != null) {
    829                 if (!metaIndex.mayContain(name)) {
    830                     return null;
    831                 }
    832             }
    833 
    834             try {
    835                 ensureOpen();
    836             } catch (IOException e) {
    837                 throw (InternalError) new InternalError().initCause(e);
    838             }
    839             final JarEntry entry = jar.getJarEntry(name);
    840             if (entry != null)
    841                 return checkResource(name, check, entry);
    842 
    843             if (index == null)
    844                 return null;
    845 
    846             HashSet<String> visited = new HashSet<String>();
    847             return getResource(name, check, visited);
    848         }
    849 
    850         /*
    851          * Version of getResource() that tracks the jar files that have been
    852          * visited by linking through the index files. This helper method uses
    853          * a HashSet to store the URLs of jar files that have been searched and
    854          * uses it to avoid going into an infinite loop, looking for a
    855          * non-existent resource
    856          */
    857         Resource getResource(final String name, boolean check,
    858                              Set<String> visited) {
    859 
    860             Resource res;
    861             Object[] jarFiles;
    862             boolean done = false;
    863             int count = 0;
    864             LinkedList jarFilesList = null;
    865 
    866             /* If there no jar files in the index that can potential contain
    867              * this resource then return immediately.
    868              */
    869             if((jarFilesList = index.get(name)) == null)
    870                 return null;
    871 
    872             do {
    873                 jarFiles = jarFilesList.toArray();
    874                 int size = jarFilesList.size();
    875                 /* loop through the mapped jar file list */
    876                 while(count < size) {
    877                     String jarName = (String)jarFiles[count++];
    878                     JarLoader newLoader;
    879                     final URL url;
    880 
    881                     try{
    882                         url = new URL(csu, jarName);
    883                         String urlNoFragString = URLUtil.urlNoFragString(url);
    884                         if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) {
    885                             /* no loader has been set up for this jar file
    886                              * before
    887                              */
    888                             newLoader = AccessController.doPrivileged(
    889                                 new PrivilegedExceptionAction<JarLoader>() {
    890                                     public JarLoader run() throws IOException {
    891                                         return new JarLoader(url, handler,
    892                                             lmap);
    893                                     }
    894                                 });
    895 
    896                             /* this newly opened jar file has its own index,
    897                              * merge it into the parent's index, taking into
    898                              * account the relative path.
    899                              */
    900                             JarIndex newIndex = newLoader.getIndex();
    901                             if(newIndex != null) {
    902                                 int pos = jarName.lastIndexOf("/");
    903                                 newIndex.merge(this.index, (pos == -1 ?
    904                                     null : jarName.substring(0, pos + 1)));
    905                             }
    906 
    907                             /* put it in the global hashtable */
    908                             lmap.put(urlNoFragString, newLoader);
    909                         }
    910                     } catch (java.security.PrivilegedActionException pae) {
    911                         continue;
    912                     } catch (MalformedURLException e) {
    913                         continue;
    914                     }
    915 
    916 
    917                     /* Note that the addition of the url to the list of visited
    918                      * jars incorporates a check for presence in the hashmap
    919                      */
    920                     boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url));
    921                     if (!visitedURL) {
    922                         try {
    923                             newLoader.ensureOpen();
    924                         } catch (IOException e) {
    925                             throw (InternalError) new InternalError().initCause(e);
    926                         }
    927                         final JarEntry entry = newLoader.jar.getJarEntry(name);
    928                         if (entry != null) {
    929                             return newLoader.checkResource(name, check, entry);
    930                         }
    931 
    932                         /* Verify that at least one other resource with the
    933                          * same package name as the lookedup resource is
    934                          * present in the new jar
    935                          */
    936                         if (!newLoader.validIndex(name)) {
    937                             /* the mapping is wrong */
    938                             throw new InvalidJarIndexException("Invalid index");
    939                         }
    940                     }
    941 
    942                     /* If newLoader is the current loader or if it is a
    943                      * loader that has already been searched or if the new
    944                      * loader does not have an index then skip it
    945                      * and move on to the next loader.
    946                      */
    947                     if (visitedURL || newLoader == this ||
    948                             newLoader.getIndex() == null) {
    949                         continue;
    950                     }
    951 
    952                     /* Process the index of the new loader
    953                      */
    954                     if((res = newLoader.getResource(name, check, visited))
    955                             != null) {
    956                         return res;
    957                     }
    958                 }
    959                 // Get the list of jar files again as the list could have grown
    960                 // due to merging of index files.
    961                 jarFilesList = index.get(name);
    962 
    963             // If the count is unchanged, we are done.
    964             } while(count < jarFilesList.size());
    965             return null;
    966         }
    967 
    968 
    969         /*
    970          * Returns the JAR file local class path, or null if none.
    971          */
    972         URL[] getClassPath() throws IOException {
    973             if (index != null) {
    974                 return null;
    975             }
    976 
    977             if (metaIndex != null) {
    978                 return null;
    979             }
    980 
    981             ensureOpen();
    982             parseExtensionsDependencies();
    983             if (jar.hasClassPathAttribute()) { // Only get manifest when necessary
    984                 Manifest man = jar.getManifest();
    985                 if (man != null) {
    986                     Attributes attr = man.getMainAttributes();
    987                     if (attr != null) {
    988                         String value = attr.getValue(Name.CLASS_PATH);
    989                         if (value != null) {
    990                             return parseClassPath(csu, value);
    991                         }
    992                     }
    993                 }
    994             }
    995             return null;
    996         }
    997 
    998         /*
    999          * parse the standard extension dependencies
   1000          */
   1001         private void  parseExtensionsDependencies() throws IOException {
   1002         }
   1003 
   1004         /*
   1005          * Parses value of the Class-Path manifest attribute and returns
   1006          * an array of URLs relative to the specified base URL.
   1007          */
   1008         private URL[] parseClassPath(URL base, String value)
   1009             throws MalformedURLException
   1010         {
   1011             StringTokenizer st = new StringTokenizer(value);
   1012             URL[] urls = new URL[st.countTokens()];
   1013             int i = 0;
   1014             while (st.hasMoreTokens()) {
   1015                 String path = st.nextToken();
   1016                 urls[i] = new URL(base, path);
   1017                 i++;
   1018             }
   1019             return urls;
   1020         }
   1021     }
   1022 
   1023     /*
   1024      * Inner class used to represent a loader of classes and resources
   1025      * from a file URL that refers to a directory.
   1026      */
   1027     private static class FileLoader extends Loader {
   1028         /* Canonicalized File */
   1029         private File dir;
   1030 
   1031         FileLoader(URL url) throws IOException {
   1032             super(url);
   1033             if (!"file".equals(url.getProtocol())) {
   1034                 throw new IllegalArgumentException("url");
   1035             }
   1036             String path = url.getFile().replace('/', File.separatorChar);
   1037             path = ParseUtil.decode(path);
   1038             dir = (new File(path)).getCanonicalFile();
   1039         }
   1040 
   1041         /*
   1042          * Returns the URL for a resource with the specified name
   1043          */
   1044         URL findResource(final String name, boolean check) {
   1045             Resource rsc = getResource(name, check);
   1046             if (rsc != null) {
   1047                 return rsc.getURL();
   1048             }
   1049             return null;
   1050         }
   1051 
   1052         Resource getResource(final String name, boolean check) {
   1053             final URL url;
   1054             try {
   1055                 URL normalizedBase = new URL(getBaseURL(), ".");
   1056                 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
   1057 
   1058                 if (url.getFile().startsWith(normalizedBase.getFile()) == false) {
   1059                     // requested resource had ../..'s in path
   1060                     return null;
   1061                 }
   1062 
   1063                 if (check)
   1064                     URLClassPath.check(url);
   1065 
   1066                 final File file;
   1067                 if (name.indexOf("..") != -1) {
   1068                     file = (new File(dir, name.replace('/', File.separatorChar)))
   1069                           .getCanonicalFile();
   1070                     if ( !((file.getPath()).startsWith(dir.getPath())) ) {
   1071                         /* outside of base dir */
   1072                         return null;
   1073                     }
   1074                 } else {
   1075                     file = new File(dir, name.replace('/', File.separatorChar));
   1076                 }
   1077 
   1078                 if (file.exists()) {
   1079                     return new Resource() {
   1080                         public String getName() { return name; };
   1081                         public URL getURL() { return url; };
   1082                         public URL getCodeSourceURL() { return getBaseURL(); };
   1083                         public InputStream getInputStream() throws IOException
   1084                             { return new FileInputStream(file); };
   1085                         public int getContentLength() throws IOException
   1086                             { return (int)file.length(); };
   1087                     };
   1088                 }
   1089             } catch (Exception e) {
   1090                 return null;
   1091             }
   1092             return null;
   1093         }
   1094     }
   1095 }
   1096