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