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