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