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