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