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