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