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