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