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.equals("") : false;
 101 
 102         p = props.getProperty("jdk.net.URLClassPath.disableRestrictedPermissions");
 103         DISABLE_ACC_CHECKING = p != null ? p.equals("true") || p.equals("") : 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(final String name,
 330                                      final boolean check) {
 331         return new Enumeration<>() {
 332             private int index = 0;
 333             private URL url = null;
 334 
 335             private boolean next() {
 336                 if (url != null) {
 337                     return true;
 338                 } else {
 339                     Loader loader;
 340                     while ((loader = getLoader(index++)) != null) {
 341                         url = loader.findResource(name, check);
 342                         if (url != null) {
 343                             return true;
 344                         }
 345                     }
 346                     return false;
 347                 }
 348             }
 349 
 350             public boolean hasMoreElements() {
 351                 return next();
 352             }
 353 
 354             public URL nextElement() {
 355                 if (!next()) {
 356                     throw new NoSuchElementException();
 357                 }
 358                 URL u = url;
 359                 url = null;
 360                 return u;
 361             }
 362         };
 363     }
 364 
 365     public Resource getResource(String name) {
 366         return getResource(name, true);
 367     }
 368 
 369     /**
 370      * Finds all resources on the URL search path with the given name.
 371      * Returns an enumeration of the Resource objects.
 372      *
 373      * @param name the resource name
 374      * @return an Enumeration of all the resources having the specified name
 375      */
 376     public Enumeration<Resource> getResources(final String name,
 377                                     final boolean check) {
 378         return new Enumeration<>() {
 379             private int index = 0;
 380             private Resource res = null;
 381 
 382             private boolean next() {
 383                 if (res != null) {
 384                     return true;
 385                 } else {
 386                     Loader loader;
 387                     while ((loader = getLoader(index++)) != null) {
 388                         res = loader.getResource(name, check);
 389                         if (res != null) {
 390                             return true;
 391                         }
 392                     }
 393                     return false;
 394                 }
 395             }
 396 
 397             public boolean hasMoreElements() {
 398                 return next();
 399             }
 400 
 401             public Resource nextElement() {
 402                 if (!next()) {
 403                     throw new NoSuchElementException();
 404                 }
 405                 Resource r = res;
 406                 res = null;
 407                 return r;
 408             }
 409         };
 410     }
 411 
 412     public Enumeration<Resource> getResources(final String name) {
 413         return getResources(name, true);
 414     }
 415 
 416     /*
 417      * Returns the Loader at the specified position in the URL search
 418      * path. The URLs are opened and expanded as needed. Returns null
 419      * if the specified index is out of range.
 420      */
 421     private synchronized Loader getLoader(int index) {
 422         if (closed) {
 423             return null;
 424         }
 425         // Expand URL search path until the request can be satisfied
 426         // or unopenedUrls is exhausted.
 427         while (loaders.size() < index + 1) {
 428             final URL url;
 429             synchronized (unopenedUrls) {
 430                 url = unopenedUrls.pollFirst();
 431                 if (url == null)
 432                     return null;
 433             }
 434             // Skip this URL if it already has a Loader. (Loader
 435             // may be null in the case where URL has not been opened
 436             // but is referenced by a JAR index.)
 437             String urlNoFragString = URLUtil.urlNoFragString(url);
 438             if (lmap.containsKey(urlNoFragString)) {
 439                 continue;
 440             }
 441             // Otherwise, create a new Loader for the URL.
 442             Loader loader;
 443             try {
 444                 loader = getLoader(url);
 445                 // If the loader defines a local class path then add the
 446                 // URLs as the next URLs to be opened.
 447                 URL[] urls = loader.getClassPath();
 448                 if (urls != null) {
 449                     push(urls);
 450                 }
 451             } catch (IOException e) {
 452                 // Silently ignore for now...
 453                 continue;
 454             } catch (SecurityException se) {
 455                 // Always silently ignore. The context, if there is one, that
 456                 // this URLClassPath was given during construction will never
 457                 // have permission to access the URL.
 458                 if (DEBUG) {
 459                     System.err.println("Failed to access " + url + ", " + se );
 460                 }
 461                 continue;
 462             }
 463             // Finally, add the Loader to the search path.
 464             loaders.add(loader);
 465             lmap.put(urlNoFragString, loader);
 466         }
 467         return loaders.get(index);
 468     }
 469 
 470     /*
 471      * Returns the Loader for the specified base URL.
 472      */
 473     private Loader getLoader(final URL url) throws IOException {
 474         try {
 475             return AccessController.doPrivileged(
 476                     new PrivilegedExceptionAction<>() {
 477                         public Loader run() throws IOException {
 478                             String protocol = url.getProtocol();  // lower cased in URL
 479                             String file = url.getFile();
 480                             if (file != null && file.endsWith("/")) {
 481                                 if ("file".equals(protocol)) {
 482                                     return new FileLoader(url);
 483                                 } else if ("jar".equals(protocol) &&
 484                                         isDefaultJarHandler(url) &&
 485                                         file.endsWith("!/")) {
 486                                     // extract the nested URL
 487                                     URL nestedUrl = new URL(file.substring(0, file.length() - 2));
 488                                     return new JarLoader(nestedUrl, jarHandler, lmap, acc);
 489                                 } else {
 490                                     return new Loader(url);
 491                                 }
 492                             } else {
 493                                 return new JarLoader(url, jarHandler, lmap, acc);
 494                             }
 495                         }
 496                     }, acc);
 497         } catch (PrivilegedActionException pae) {
 498             throw (IOException)pae.getException();
 499         }
 500     }
 501 
 502     private static final JavaNetURLAccess JNUA
 503             = SharedSecrets.getJavaNetURLAccess();
 504 
 505     private static boolean isDefaultJarHandler(URL u) {
 506         URLStreamHandler h = JNUA.getHandler(u);
 507         return h instanceof sun.net.www.protocol.jar.Handler;
 508     }
 509 
 510     /*
 511      * Pushes the specified URLs onto the head of unopened URLs.
 512      */
 513     private void push(URL[] urls) {
 514         synchronized (unopenedUrls) {
 515             for (int i = urls.length - 1; i >= 0; --i) {
 516                 unopenedUrls.addFirst(urls[i]);
 517             }
 518         }
 519     }
 520 
 521     /*
 522      * Checks whether the resource URL should be returned.
 523      * Returns null on security check failure.
 524      * Called by java.net.URLClassLoader.
 525      */
 526     public static URL checkURL(URL url) {
 527         if (url != null) {
 528             try {
 529                 check(url);
 530             } catch (Exception e) {
 531                 return null;
 532             }
 533         }
 534         return url;
 535     }
 536 
 537     /*
 538      * Checks whether the resource URL should be returned.
 539      * Throws exception on failure.
 540      * Called internally within this file.
 541      */
 542     public static void check(URL url) throws IOException {
 543         SecurityManager security = System.getSecurityManager();
 544         if (security != null) {
 545             URLConnection urlConnection = url.openConnection();
 546             Permission perm = urlConnection.getPermission();
 547             if (perm != null) {
 548                 try {
 549                     security.checkPermission(perm);
 550                 } catch (SecurityException se) {
 551                     // fallback to checkRead/checkConnect for pre 1.2
 552                     // security managers
 553                     if ((perm instanceof java.io.FilePermission) &&
 554                         perm.getActions().indexOf("read") != -1) {
 555                         security.checkRead(perm.getName());
 556                     } else if ((perm instanceof
 557                         java.net.SocketPermission) &&
 558                         perm.getActions().indexOf("connect") != -1) {
 559                         URL locUrl = url;
 560                         if (urlConnection instanceof JarURLConnection) {
 561                             locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
 562                         }
 563                         security.checkConnect(locUrl.getHost(),
 564                                               locUrl.getPort());
 565                     } else {
 566                         throw se;
 567                     }
 568                 }
 569             }
 570         }
 571     }
 572 
 573     /**
 574      * Nested class used to represent a loader of resources and classes
 575      * from a base URL.
 576      */
 577     private static class Loader implements Closeable {
 578         private final URL base;
 579         private JarFile jarfile; // if this points to a jar file
 580 
 581         /*
 582          * Creates a new Loader for the specified URL.
 583          */
 584         Loader(URL url) {
 585             base = url;
 586         }
 587 
 588         /*
 589          * Returns the base URL for this Loader.
 590          */
 591         URL getBaseURL() {
 592             return base;
 593         }
 594 
 595         URL findResource(final String name, boolean check) {
 596             URL url;
 597             try {
 598                 url = new URL(base, ParseUtil.encodePath(name, false));
 599             } catch (MalformedURLException e) {
 600                 throw new IllegalArgumentException("name");
 601             }
 602 
 603             try {
 604                 if (check) {
 605                     URLClassPath.check(url);
 606                 }
 607 
 608                 /*
 609                  * For a HTTP connection we use the HEAD method to
 610                  * check if the resource exists.
 611                  */
 612                 URLConnection uc = url.openConnection();
 613                 if (uc instanceof HttpURLConnection) {
 614                     HttpURLConnection hconn = (HttpURLConnection)uc;
 615                     hconn.setRequestMethod("HEAD");
 616                     if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
 617                         return null;
 618                     }
 619                 } else {
 620                     // our best guess for the other cases
 621                     uc.setUseCaches(false);
 622                     InputStream is = uc.getInputStream();
 623                     is.close();
 624                 }
 625                 return url;
 626             } catch (Exception e) {
 627                 return null;
 628             }
 629         }
 630 
 631         Resource getResource(final String name, boolean check) {
 632             final URL url;
 633             try {
 634                 url = new URL(base, ParseUtil.encodePath(name, false));
 635             } catch (MalformedURLException e) {
 636                 throw new IllegalArgumentException("name");
 637             }
 638             final URLConnection uc;
 639             try {
 640                 if (check) {
 641                     URLClassPath.check(url);
 642                 }
 643                 uc = url.openConnection();
 644                 InputStream in = uc.getInputStream();
 645                 if (uc instanceof JarURLConnection) {
 646                     /* Need to remember the jar file so it can be closed
 647                      * in a hurry.
 648                      */
 649                     JarURLConnection juc = (JarURLConnection)uc;
 650                     jarfile = JarLoader.checkJar(juc.getJarFile());
 651                 }
 652             } catch (Exception e) {
 653                 return null;
 654             }
 655             return new Resource() {
 656                 public String getName() { return name; }
 657                 public URL getURL() { return url; }
 658                 public URL getCodeSourceURL() { return base; }
 659                 public InputStream getInputStream() throws IOException {
 660                     return uc.getInputStream();
 661                 }
 662                 public int getContentLength() throws IOException {
 663                     return uc.getContentLength();
 664                 }
 665             };
 666         }
 667 
 668         /*
 669          * Returns the Resource for the specified name, or null if not
 670          * found or the caller does not have the permission to get the
 671          * resource.
 672          */
 673         Resource getResource(final String name) {
 674             return getResource(name, true);
 675         }
 676 
 677         /*
 678          * Closes this loader and release all resources.
 679          * Method overridden in sub-classes.
 680          */
 681         @Override
 682         public void close() throws IOException {
 683             if (jarfile != null) {
 684                 jarfile.close();
 685             }
 686         }
 687 
 688         /*
 689          * Returns the local class path for this loader, or null if none.
 690          */
 691         URL[] getClassPath() throws IOException {
 692             return null;
 693         }
 694     }
 695 
 696     /*
 697      * Nested class used to represent a Loader of resources from a JAR URL.
 698      */
 699     static class JarLoader extends Loader {
 700         private JarFile jar;
 701         private final URL csu;
 702         private JarIndex index;
 703         private URLStreamHandler handler;
 704         private final HashMap<String, Loader> lmap;
 705         private final AccessControlContext acc;
 706         private boolean closed = false;
 707         private static final JavaUtilZipFileAccess zipAccess =
 708                 SharedSecrets.getJavaUtilZipFileAccess();
 709 
 710         /*
 711          * Creates a new JarLoader for the specified URL referring to
 712          * a JAR file.
 713          */
 714         JarLoader(URL url, URLStreamHandler jarHandler,
 715                   HashMap<String, Loader> loaderMap,
 716                   AccessControlContext acc)
 717             throws IOException
 718         {
 719             super(new URL("jar", "", -1, url + "!/", jarHandler));
 720             csu = url;
 721             handler = jarHandler;
 722             lmap = loaderMap;
 723             this.acc = acc;
 724 
 725             ensureOpen();
 726         }
 727 
 728         @Override
 729         public void close () throws IOException {
 730             // closing is synchronized at higher level
 731             if (!closed) {
 732                 closed = true;
 733                 // in case not already open.
 734                 ensureOpen();
 735                 jar.close();
 736             }
 737         }
 738 
 739         JarFile getJarFile () {
 740             return jar;
 741         }
 742 
 743         private boolean isOptimizable(URL url) {
 744             return "file".equals(url.getProtocol());
 745         }
 746 
 747         private void ensureOpen() throws IOException {
 748             if (jar == null) {
 749                 try {
 750                     AccessController.doPrivileged(
 751                         new PrivilegedExceptionAction<>() {
 752                             public Void run() throws IOException {
 753                                 if (DEBUG) {
 754                                     System.err.println("Opening " + csu);
 755                                     Thread.dumpStack();
 756                                 }
 757 
 758                                 jar = getJarFile(csu);
 759                                 index = JarIndex.getJarIndex(jar);
 760                                 if (index != null) {
 761                                     String[] jarfiles = index.getJarFiles();
 762                                 // Add all the dependent URLs to the lmap so that loaders
 763                                 // will not be created for them by URLClassPath.getLoader(int)
 764                                 // if the same URL occurs later on the main class path.  We set
 765                                 // Loader to null here to avoid creating a Loader for each
 766                                 // URL until we actually need to try to load something from them.
 767                                     for (int i = 0; i < jarfiles.length; i++) {
 768                                         try {
 769                                             URL jarURL = new URL(csu, jarfiles[i]);
 770                                             // If a non-null loader already exists, leave it alone.
 771                                             String urlNoFragString = URLUtil.urlNoFragString(jarURL);
 772                                             if (!lmap.containsKey(urlNoFragString)) {
 773                                                 lmap.put(urlNoFragString, null);
 774                                             }
 775                                         } catch (MalformedURLException e) {
 776                                             continue;
 777                                         }
 778                                     }
 779                                 }
 780                                 return null;
 781                             }
 782                         }, acc);
 783                 } catch (PrivilegedActionException pae) {
 784                     throw (IOException)pae.getException();
 785                 }
 786             }
 787         }
 788 
 789         /* Throws if the given jar file is does not start with the correct LOC */
 790         static JarFile checkJar(JarFile jar) throws IOException {
 791             if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING
 792                 && !zipAccess.startsWithLocHeader(jar)) {
 793                 IOException x = new IOException("Invalid Jar file");
 794                 try {
 795                     jar.close();
 796                 } catch (IOException ex) {
 797                     x.addSuppressed(ex);
 798                 }
 799                 throw x;
 800             }
 801 
 802             return jar;
 803         }
 804 
 805         private JarFile getJarFile(URL url) throws IOException {
 806             // Optimize case where url refers to a local jar file
 807             if (isOptimizable(url)) {
 808                 FileURLMapper p = new FileURLMapper (url);
 809                 if (!p.exists()) {
 810                     throw new FileNotFoundException(p.getPath());
 811                 }
 812                 return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ,
 813                         JarFile.runtimeVersion()));
 814             }
 815             URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection();
 816             uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
 817             JarFile jarFile = ((JarURLConnection)uc).getJarFile();
 818             return checkJar(jarFile);
 819         }
 820 
 821         /*
 822          * Returns the index of this JarLoader if it exists.
 823          */
 824         JarIndex getIndex() {
 825             try {
 826                 ensureOpen();
 827             } catch (IOException e) {
 828                 throw new InternalError(e);
 829             }
 830             return index;
 831         }
 832 
 833         /*
 834          * Creates the resource and if the check flag is set to true, checks if
 835          * is its okay to return the resource.
 836          */
 837         Resource checkResource(final String name, boolean check,
 838             final JarEntry entry) {
 839 
 840             final URL url;
 841             try {
 842                 String nm;
 843                 if (jar.isMultiRelease()) {
 844                     nm = entry.getRealName();
 845                 } else {
 846                     nm = name;
 847                 }
 848                 url = new URL(getBaseURL(), ParseUtil.encodePath(nm, false));
 849                 if (check) {
 850                     URLClassPath.check(url);
 851                 }
 852             } catch (MalformedURLException e) {
 853                 return null;
 854                 // throw new IllegalArgumentException("name");
 855             } catch (IOException e) {
 856                 return null;
 857             } catch (AccessControlException e) {
 858                 return null;
 859             }
 860 
 861             return new Resource() {
 862                 public String getName() { return name; }
 863                 public URL getURL() { return url; }
 864                 public URL getCodeSourceURL() { return csu; }
 865                 public InputStream getInputStream() throws IOException
 866                     { return jar.getInputStream(entry); }
 867                 public int getContentLength()
 868                     { return (int)entry.getSize(); }
 869                 public Manifest getManifest() throws IOException {
 870                     SharedSecrets.javaUtilJarAccess().ensureInitialization(jar);
 871                     return jar.getManifest();
 872                 }
 873                 public Certificate[] getCertificates()
 874                     { return entry.getCertificates(); };
 875                 public CodeSigner[] getCodeSigners()
 876                     { return entry.getCodeSigners(); };
 877             };
 878         }
 879 
 880 
 881         /*
 882          * Returns true iff at least one resource in the jar file has the same
 883          * package name as that of the specified resource name.
 884          */
 885         boolean validIndex(final String name) {
 886             String packageName = name;
 887             int pos;
 888             if ((pos = name.lastIndexOf('/')) != -1) {
 889                 packageName = name.substring(0, pos);
 890             }
 891 
 892             String entryName;
 893             ZipEntry entry;
 894             Enumeration<JarEntry> enum_ = jar.entries();
 895             while (enum_.hasMoreElements()) {
 896                 entry = enum_.nextElement();
 897                 entryName = entry.getName();
 898                 if ((pos = entryName.lastIndexOf('/')) != -1)
 899                     entryName = entryName.substring(0, pos);
 900                 if (entryName.equals(packageName)) {
 901                     return true;
 902                 }
 903             }
 904             return false;
 905         }
 906 
 907         /*
 908          * Returns the URL for a resource with the specified name
 909          */
 910         @Override
 911         URL findResource(final String name, boolean check) {
 912             Resource rsc = getResource(name, check);
 913             if (rsc != null) {
 914                 return rsc.getURL();
 915             }
 916             return null;
 917         }
 918 
 919         /*
 920          * Returns the JAR Resource for the specified name.
 921          */
 922         @Override
 923         Resource getResource(final String name, boolean check) {
 924             try {
 925                 ensureOpen();
 926             } catch (IOException e) {
 927                 throw new InternalError(e);
 928             }
 929             final JarEntry entry = jar.getJarEntry(name);
 930             if (entry != null)
 931                 return checkResource(name, check, entry);
 932 
 933             if (index == null)
 934                 return null;
 935 
 936             HashSet<String> visited = new HashSet<>();
 937             return getResource(name, check, visited);
 938         }
 939 
 940         /*
 941          * Version of getResource() that tracks the jar files that have been
 942          * visited by linking through the index files. This helper method uses
 943          * a HashSet to store the URLs of jar files that have been searched and
 944          * uses it to avoid going into an infinite loop, looking for a
 945          * non-existent resource.
 946          */
 947         Resource getResource(final String name, boolean check,
 948                              Set<String> visited) {
 949             Resource res;
 950             String[] jarFiles;
 951             int count = 0;
 952             LinkedList<String> jarFilesList = null;
 953 
 954             /* If there no jar files in the index that can potential contain
 955              * this resource then return immediately.
 956              */
 957             if ((jarFilesList = index.get(name)) == null)
 958                 return null;
 959 
 960             do {
 961                 int size = jarFilesList.size();
 962                 jarFiles = jarFilesList.toArray(new String[size]);
 963                 /* loop through the mapped jar file list */
 964                 while (count < size) {
 965                     String jarName = jarFiles[count++];
 966                     JarLoader newLoader;
 967                     final URL url;
 968 
 969                     try{
 970                         url = new URL(csu, jarName);
 971                         String urlNoFragString = URLUtil.urlNoFragString(url);
 972                         if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) {
 973                             /* no loader has been set up for this jar file
 974                              * before
 975                              */
 976                             newLoader = AccessController.doPrivileged(
 977                                 new PrivilegedExceptionAction<>() {
 978                                     public JarLoader run() throws IOException {
 979                                         return new JarLoader(url, handler,
 980                                             lmap, acc);
 981                                     }
 982                                 }, acc);
 983 
 984                             /* this newly opened jar file has its own index,
 985                              * merge it into the parent's index, taking into
 986                              * account the relative path.
 987                              */
 988                             JarIndex newIndex = newLoader.getIndex();
 989                             if (newIndex != null) {
 990                                 int pos = jarName.lastIndexOf('/');
 991                                 newIndex.merge(this.index, (pos == -1 ?
 992                                     null : jarName.substring(0, pos + 1)));
 993                             }
 994 
 995                             /* put it in the global hashtable */
 996                             lmap.put(urlNoFragString, newLoader);
 997                         }
 998                     } catch (PrivilegedActionException pae) {
 999                         continue;
1000                     } catch (MalformedURLException e) {
1001                         continue;
1002                     }
1003 
1004                     /* Note that the addition of the url to the list of visited
1005                      * jars incorporates a check for presence in the hashmap
1006                      */
1007                     boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url));
1008                     if (!visitedURL) {
1009                         try {
1010                             newLoader.ensureOpen();
1011                         } catch (IOException e) {
1012                             throw new InternalError(e);
1013                         }
1014                         final JarEntry entry = newLoader.jar.getJarEntry(name);
1015                         if (entry != null) {
1016                             return newLoader.checkResource(name, check, entry);
1017                         }
1018 
1019                         /* Verify that at least one other resource with the
1020                          * same package name as the lookedup resource is
1021                          * present in the new jar
1022                          */
1023                         if (!newLoader.validIndex(name)) {
1024                             /* the mapping is wrong */
1025                             throw new InvalidJarIndexError("Invalid index");
1026                         }
1027                     }
1028 
1029                     /* If newLoader is the current loader or if it is a
1030                      * loader that has already been searched or if the new
1031                      * loader does not have an index then skip it
1032                      * and move on to the next loader.
1033                      */
1034                     if (visitedURL || newLoader == this ||
1035                             newLoader.getIndex() == null) {
1036                         continue;
1037                     }
1038 
1039                     /* Process the index of the new loader
1040                      */
1041                     if ((res = newLoader.getResource(name, check, visited))
1042                             != null) {
1043                         return res;
1044                     }
1045                 }
1046                 // Get the list of jar files again as the list could have grown
1047                 // due to merging of index files.
1048                 jarFilesList = index.get(name);
1049 
1050             // If the count is unchanged, we are done.
1051             } while (count < jarFilesList.size());
1052             return null;
1053         }
1054 
1055 
1056         /*
1057          * Returns the JAR file local class path, or null if none.
1058          */
1059         @Override
1060         URL[] getClassPath() throws IOException {
1061             if (index != null) {
1062                 return null;
1063             }
1064 
1065             ensureOpen();
1066 
1067             // Only get manifest when necessary
1068             if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) {
1069                 Manifest man = jar.getManifest();
1070                 if (man != null) {
1071                     Attributes attr = man.getMainAttributes();
1072                     if (attr != null) {
1073                         String value = attr.getValue(Name.CLASS_PATH);
1074                         if (value != null) {
1075                             return parseClassPath(csu, value);
1076                         }
1077                     }
1078                 }
1079             }
1080             return null;
1081         }
1082 
1083         /*
1084          * Parses value of the Class-Path manifest attribute and returns
1085          * an array of URLs relative to the specified base URL.
1086          */
1087         private static URL[] parseClassPath(URL base, String value)
1088             throws MalformedURLException
1089         {
1090             StringTokenizer st = new StringTokenizer(value);
1091             URL[] urls = new URL[st.countTokens()];
1092             int i = 0;
1093             while (st.hasMoreTokens()) {
1094                 String path = st.nextToken();
1095                 URL url = DISABLE_CP_URL_CHECK ? new URL(base, path) : safeResolve(base, path);
1096                 if (url != null) {
1097                     urls[i] = url;
1098                     i++;
1099                 }
1100             }
1101             if (i == 0) {
1102                 urls = null;
1103             } else if (i != urls.length) {
1104                 // Truncate nulls from end of array
1105                 urls = Arrays.copyOf(urls, i);
1106             }
1107             return urls;
1108         }
1109 
1110         /*
1111          * Return a URL for the given path resolved against the base URL, or
1112          * null if the resulting URL is invalid.
1113          */
1114         static URL safeResolve(URL base, String path) {
1115             String child = path.replace(File.separatorChar, '/');
1116             try {
1117                 if (!URI.create(child).isAbsolute()) {
1118                     URL url = new URL(base, child);
1119                     if (base.getProtocol().equalsIgnoreCase("file")) {
1120                         return url;
1121                     } else {
1122                         String bp = base.getPath();
1123                         String urlp = url.getPath();
1124                         int pos = bp.lastIndexOf('/');
1125                         if (pos == -1) {
1126                             pos = bp.length() - 1;
1127                         }
1128                         if (urlp.regionMatches(0, bp, 0, pos + 1)
1129                             && urlp.indexOf("..", pos) == -1) {
1130                             return url;
1131                         }
1132                     }
1133                 }
1134             } catch (MalformedURLException | IllegalArgumentException e) {}
1135             if (DEBUG_CP_URL_CHECK) {
1136                 System.err.println("Class-Path entry: \"" + path + "\" ignored in JAR file " + base);
1137             }
1138             return null;
1139         }
1140     }
1141 
1142     /*
1143      * Nested class used to represent a loader of classes and resources
1144      * from a file URL that refers to a directory.
1145      */
1146     private static class FileLoader extends Loader {
1147         /* Canonicalized File */
1148         private File dir;
1149 
1150         FileLoader(URL url) throws IOException {
1151             super(url);
1152             if (!"file".equals(url.getProtocol())) {
1153                 throw new IllegalArgumentException("url");
1154             }
1155             String path = url.getFile().replace('/', File.separatorChar);
1156             path = ParseUtil.decode(path);
1157             dir = (new File(path)).getCanonicalFile();
1158         }
1159 
1160         /*
1161          * Returns the URL for a resource with the specified name
1162          */
1163         @Override
1164         URL findResource(final String name, boolean check) {
1165             Resource rsc = getResource(name, check);
1166             if (rsc != null) {
1167                 return rsc.getURL();
1168             }
1169             return null;
1170         }
1171 
1172         @Override
1173         Resource getResource(final String name, boolean check) {
1174             final URL url;
1175             try {
1176                 URL normalizedBase = new URL(getBaseURL(), ".");
1177                 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
1178 
1179                 if (url.getFile().startsWith(normalizedBase.getFile()) == false) {
1180                     // requested resource had ../..'s in path
1181                     return null;
1182                 }
1183 
1184                 if (check)
1185                     URLClassPath.check(url);
1186 
1187                 final File file;
1188                 if (name.indexOf("..") != -1) {
1189                     file = (new File(dir, name.replace('/', File.separatorChar)))
1190                           .getCanonicalFile();
1191                     if ( !((file.getPath()).startsWith(dir.getPath())) ) {
1192                         /* outside of base dir */
1193                         return null;
1194                     }
1195                 } else {
1196                     file = new File(dir, name.replace('/', File.separatorChar));
1197                 }
1198 
1199                 if (file.exists()) {
1200                     return new Resource() {
1201                         public String getName() { return name; };
1202                         public URL getURL() { return url; };
1203                         public URL getCodeSourceURL() { return getBaseURL(); };
1204                         public InputStream getInputStream() throws IOException
1205                             { return new FileInputStream(file); };
1206                         public int getContentLength() throws IOException
1207                             { return (int)file.length(); };
1208                     };
1209                 }
1210             } catch (Exception e) {
1211                 return null;
1212             }
1213             return null;
1214         }
1215     }
1216 }