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