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