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