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