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