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