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