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