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