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