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.JarIndex;
32 import sun.misc.InvalidJarIndexException;
33 import sun.net.www.ParseUtil;
34 import java.util.zip.ZipEntry;
35 import java.util.jar.JarEntry;
36 import java.util.jar.Manifest;
37 import java.util.jar.Attributes;
38 import java.util.jar.Attributes.Name;
39 import java.net.JarURLConnection;
40 import java.net.MalformedURLException;
41 import java.net.URL;
42 import java.net.URLConnection;
43 import java.net.HttpURLConnection;
44 import java.net.URLStreamHandler;
45 import java.net.URLStreamHandlerFactory;
46 import java.io.*;
47 import java.security.AccessControlContext;
48 import java.security.AccessController;
49 import java.security.AccessControlException;
50 import java.security.CodeSigner;
51 import java.security.Permission;
52 import java.security.PrivilegedAction;
53 import java.security.PrivilegedExceptionAction;
54 import java.security.cert.Certificate;
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 MetaIndex metaIndex;
815         private URLStreamHandler handler;
816         private final HashMap<String, Loader> lmap;
817         private final AccessControlContext acc;
818         private boolean closed = false;
819         // Android-changed: Not needed, called directly.
820         // private static final sun.misc.JavaUtilZipFileAccess zipAccess =
821         //      sun.misc.SharedSecrets.getJavaUtilZipFileAccess();
822 
823         /*
824          * Creates a new JarLoader for the specified URL referring to
825          * a JAR file.
826          */
827         JarLoader(URL url, URLStreamHandler jarHandler,
828                   HashMap<String, Loader> loaderMap,
829                   AccessControlContext acc)
830             throws IOException
831         {
832             super(new URL("jar", "", -1, url + "!/", jarHandler));
833             csu = url;
834             handler = jarHandler;
835             lmap = loaderMap;
836             this.acc = acc;
837 
838             if (!isOptimizable(url)) {
839                 ensureOpen();
840             } else {
841                  String fileName = url.getFile();
842                 if (fileName != null) {
843                     fileName = ParseUtil.decode(fileName);
844                     File f = new File(fileName);
845                     metaIndex = MetaIndex.forJar(f);
846                     // If the meta index is found but the file is not
847                     // installed, set metaIndex to null. A typical
848                     // senario is charsets.jar which won't be installed
849                     // when the user is running in certain locale environment.
850                     // The side effect of null metaIndex will cause
851                     // ensureOpen get called so that IOException is thrown.
852                     if (metaIndex != null && !f.exists()) {
853                         metaIndex = null;
854                     }
855                 }
856 
857                 // metaIndex is null when either there is no such jar file
858                 // entry recorded in meta-index file or such jar file is
859                 // missing in JRE. See bug 6340399.
860                 if (metaIndex == null) {
861                     ensureOpen();
862                 }
863             }
864         }
865 
866         @Override
867         public void close () throws IOException {
868             // closing is synchronized at higher level
869             if (!closed) {
870                 closed = true;
871                 // in case not already open.
872                 ensureOpen();
873                 jar.close();
874             }
875         }
876 
877         JarFile getJarFile () {
878             return jar;
879         }
880 
881         private boolean isOptimizable(URL url) {
882             return "file".equals(url.getProtocol());
883         }
884 
885         private void ensureOpen() throws IOException {
886             if (jar == null) {
887                 try {
888                     java.security.AccessController.doPrivileged(
889                         new java.security.PrivilegedExceptionAction<Void>() {
890                             public Void run() throws IOException {
891                                 if (DEBUG) {
892                                     System.err.println("Opening " + csu);
893                                     Thread.dumpStack();
894                                 }
895 
896                                 jar = getJarFile(csu);
897                                 index = JarIndex.getJarIndex(jar, metaIndex);
898                                 if (index != null) {
899                                     String[] jarfiles = index.getJarFiles();
900                                 // Add all the dependent URLs to the lmap so that loaders
901                                 // will not be created for them by URLClassPath.getLoader(int)
902                                 // if the same URL occurs later on the main class path.  We set
903                                 // Loader to null here to avoid creating a Loader for each
904                                 // URL until we actually need to try to load something from them.
905                                     for(int i = 0; i < jarfiles.length; i++) {
906                                         try {
907                                             URL jarURL = new URL(csu, jarfiles[i]);
908                                             // If a non-null loader already exists, leave it alone.
909                                             String urlNoFragString = URLUtil.urlNoFragString(jarURL);
910                                             if (!lmap.containsKey(urlNoFragString)) {
911                                                 lmap.put(urlNoFragString, null);
912                                             }
913                                         } catch (MalformedURLException e) {
914                                             continue;
915                                         }
916                                     }
917                                 }
918                                 return null;
919                             }
920                         }, acc);
921                 } catch (java.security.PrivilegedActionException pae) {
922                     throw (IOException)pae.getException();
923                 }
924             }
925         }
926 
927         /* Throws if the given jar file is does not start with the correct LOC */
928         static JarFile checkJar(JarFile jar) throws IOException {
929             if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING
930                 && !jar.startsWithLocHeader()) {
931                 IOException x = new IOException("Invalid Jar file");
932                 try {
933                     jar.close();
934                 } catch (IOException ex) {
935                     x.addSuppressed(ex);
936                 }
937                 throw x;
938             }
939 
940             return jar;
941         }
942 
943         private JarFile getJarFile(URL url) throws IOException {
944             // Optimize case where url refers to a local jar file
945             if (isOptimizable(url)) {
946                 FileURLMapper p = new FileURLMapper (url);
947                 if (!p.exists()) {
948                     throw new FileNotFoundException(p.getPath());
949                 }
950                 return checkJar(new JarFile(p.getPath()));
951             }
952             URLConnection uc = getBaseURL().openConnection();
953             uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
954             JarFile jarFile = ((JarURLConnection)uc).getJarFile();
955             return checkJar(jarFile);
956         }
957 
958         /*
959          * Returns the index of this JarLoader if it exists.
960          */
961         JarIndex getIndex() {
962             try {
963                 ensureOpen();
964             } catch (IOException e) {
965                 throw new InternalError(e);
966             }
967             return index;
968         }
969 
970         /*
971          * Creates the resource and if the check flag is set to true, checks if
972          * is its okay to return the resource.
973          */
974         Resource checkResource(final String name, boolean check,
975             final JarEntry entry) {
976 
977             final URL url;
978             try {
979                 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
980                 if (check) {
981                     URLClassPath.check(url);
982                 }
983             } catch (MalformedURLException e) {
984                 return null;
985                 // throw new IllegalArgumentException("name");
986             } catch (IOException e) {
987                 return null;
988             } catch (AccessControlException e) {
989                 return null;
990             }
991 
992             return new Resource() {
993                 public String getName() { return name; }
994                 public URL getURL() { return url; }
995                 public URL getCodeSourceURL() { return csu; }
996                 public InputStream getInputStream() throws IOException
997                     { return jar.getInputStream(entry); }
998                 public int getContentLength()
999                     { return (int)entry.getSize(); }
1000                 public Manifest getManifest() throws IOException
1001                     { return jar.getManifest(); };
1002                 public Certificate[] getCertificates()
1003                     { return entry.getCertificates(); };
1004                 public CodeSigner[] getCodeSigners()
1005                     { return entry.getCodeSigners(); };
1006             };
1007         }
1008 
1009 
1010         /*
1011          * Returns true iff atleast one resource in the jar file has the same
1012          * package name as that of the specified resource name.
1013          */
1014         boolean validIndex(final String name) {
1015             String packageName = name;
1016             int pos;
1017             if((pos = name.lastIndexOf("/")) != -1) {
1018                 packageName = name.substring(0, pos);
1019             }
1020 
1021             String entryName;
1022             ZipEntry entry;
1023             Enumeration<JarEntry> enum_ = jar.entries();
1024             while (enum_.hasMoreElements()) {
1025                 entry = enum_.nextElement();
1026                 entryName = entry.getName();
1027                 if((pos = entryName.lastIndexOf("/")) != -1)
1028                     entryName = entryName.substring(0, pos);
1029                 if (entryName.equals(packageName)) {
1030                     return true;
1031                 }
1032             }
1033             return false;
1034         }
1035 
1036         /*
1037          * Returns the URL for a resource with the specified name
1038          */
1039         URL findResource(final String name, boolean check) {
1040             Resource rsc = getResource(name, check);
1041             if (rsc != null) {
1042                 return rsc.getURL();
1043             }
1044             return null;
1045         }
1046 
1047         /*
1048          * Returns the JAR Resource for the specified name.
1049          */
1050         Resource getResource(final String name, boolean check) {
1051             if (metaIndex != null) {
1052                 if (!metaIndex.mayContain(name)) {
1053                     return null;
1054                 }
1055             }
1056 
1057             try {
1058                 ensureOpen();
1059             } catch (IOException e) {
1060                 throw new InternalError(e);
1061             }
1062             final JarEntry entry = jar.getJarEntry(name);
1063             if (entry != null)
1064                 return checkResource(name, check, entry);
1065 
1066             if (index == null)
1067                 return null;
1068 
1069             HashSet<String> visited = new HashSet<String>();
1070             return getResource(name, check, visited);
1071         }
1072 
1073         /*
1074          * Version of getResource() that tracks the jar files that have been
1075          * visited by linking through the index files. This helper method uses
1076          * a HashSet to store the URLs of jar files that have been searched and
1077          * uses it to avoid going into an infinite loop, looking for a
1078          * non-existent resource
1079          */
1080         Resource getResource(final String name, boolean check,
1081                              Set<String> visited) {
1082 
1083             Resource res;
1084             String[] jarFiles;
1085             int count = 0;
1086             LinkedList<String> jarFilesList = null;
1087 
1088             /* If there no jar files in the index that can potential contain
1089              * this resource then return immediately.
1090              */
1091             if((jarFilesList = index.get(name)) == null)
1092                 return null;
1093 
1094             do {
1095                 int size = jarFilesList.size();
1096                 jarFiles = jarFilesList.toArray(new String[size]);
1097                 /* loop through the mapped jar file list */
1098                 while(count < size) {
1099                     String jarName = jarFiles[count++];
1100                     JarLoader newLoader;
1101                     final URL url;
1102 
1103                     try{
1104                         url = new URL(csu, jarName);
1105                         String urlNoFragString = URLUtil.urlNoFragString(url);
1106                         if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) {
1107                             /* no loader has been set up for this jar file
1108                              * before
1109                              */
1110                             newLoader = AccessController.doPrivileged(
1111                                 new PrivilegedExceptionAction<JarLoader>() {
1112                                     public JarLoader run() throws IOException {
1113                                         return new JarLoader(url, handler,
1114                                             lmap, acc);
1115                                     }
1116                                 }, acc);
1117 
1118                             /* this newly opened jar file has its own index,
1119                              * merge it into the parent's index, taking into
1120                              * account the relative path.
1121                              */
1122                             JarIndex newIndex = newLoader.getIndex();
1123                             if(newIndex != null) {
1124                                 int pos = jarName.lastIndexOf("/");
1125                                 newIndex.merge(this.index, (pos == -1 ?
1126                                     null : jarName.substring(0, pos + 1)));
1127                             }
1128 
1129                             /* put it in the global hashtable */
1130                             lmap.put(urlNoFragString, newLoader);
1131                         }
1132                     } catch (java.security.PrivilegedActionException pae) {
1133                         continue;
1134                     } catch (MalformedURLException e) {
1135                         continue;
1136                     }
1137 
1138 
1139                     /* Note that the addition of the url to the list of visited
1140                      * jars incorporates a check for presence in the hashmap
1141                      */
1142                     boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url));
1143                     if (!visitedURL) {
1144                         try {
1145                             newLoader.ensureOpen();
1146                         } catch (IOException e) {
1147                             throw new InternalError(e);
1148                         }
1149                         final JarEntry entry = newLoader.jar.getJarEntry(name);
1150                         if (entry != null) {
1151                             return newLoader.checkResource(name, check, entry);
1152                         }
1153 
1154                         /* Verify that at least one other resource with the
1155                          * same package name as the lookedup resource is
1156                          * present in the new jar
1157                          */
1158                         if (!newLoader.validIndex(name)) {
1159                             /* the mapping is wrong */
1160                             throw new InvalidJarIndexException("Invalid index");
1161                         }
1162                     }
1163 
1164                     /* If newLoader is the current loader or if it is a
1165                      * loader that has already been searched or if the new
1166                      * loader does not have an index then skip it
1167                      * and move on to the next loader.
1168                      */
1169                     if (visitedURL || newLoader == this ||
1170                             newLoader.getIndex() == null) {
1171                         continue;
1172                     }
1173 
1174                     /* Process the index of the new loader
1175                      */
1176                     if((res = newLoader.getResource(name, check, visited))
1177                             != null) {
1178                         return res;
1179                     }
1180                 }
1181                 // Get the list of jar files again as the list could have grown
1182                 // due to merging of index files.
1183                 jarFilesList = index.get(name);
1184 
1185             // If the count is unchanged, we are done.
1186             } while(count < jarFilesList.size());
1187             return null;
1188         }
1189 
1190 
1191         /*
1192          * Returns the JAR file local class path, or null if none.
1193          */
1194         URL[] getClassPath() throws IOException {
1195             if (index != null) {
1196                 return null;
1197             }
1198 
1199             if (metaIndex != null) {
1200                 return null;
1201             }
1202 
1203             ensureOpen();
1204             parseExtensionsDependencies();
1205             if (jar.hasClassPathAttribute()) { // Only get manifest when necessary
1206                 Manifest man = jar.getManifest();
1207                 if (man != null) {
1208                     Attributes attr = man.getMainAttributes();
1209                     if (attr != null) {
1210                         String value = attr.getValue(Name.CLASS_PATH);
1211                         if (value != null) {
1212                             return parseClassPath(csu, value);
1213                         }
1214                     }
1215                 }
1216             }
1217             return null;
1218         }
1219 
1220         /*
1221          * parse the standard extension dependencies
1222          */
1223         private void  parseExtensionsDependencies() throws IOException {
1224             // Android-changed: checkExtensionsDependencies(jar) is not supported on Android.
1225             //ExtensionDependency.checkExtensionsDependencies(jar);
1226         }
1227 
1228         /*
1229          * Parses value of the Class-Path manifest attribute and returns
1230          * an array of URLs relative to the specified base URL.
1231          */
1232         private URL[] parseClassPath(URL base, String value)
1233             throws MalformedURLException
1234         {
1235             StringTokenizer st = new StringTokenizer(value);
1236             URL[] urls = new URL[st.countTokens()];
1237             int i = 0;
1238             while (st.hasMoreTokens()) {
1239                 String path = st.nextToken();
1240                 urls[i] = new URL(base, path);
1241                 i++;
1242             }
1243             return urls;
1244         }
1245     }
1246 
1247     /*
1248      * Inner class used to represent a loader of classes and resources
1249      * from a file URL that refers to a directory.
1250      */
1251     private static class FileLoader extends Loader {
1252         /* Canonicalized File */
1253         private File dir;
1254 
1255         FileLoader(URL url) throws IOException {
1256             super(url);
1257             if (!"file".equals(url.getProtocol())) {
1258                 throw new IllegalArgumentException("url");
1259             }
1260             String path = url.getFile().replace('/', File.separatorChar);
1261             path = ParseUtil.decode(path);
1262             dir = (new File(path)).getCanonicalFile();
1263         }
1264 
1265         /*
1266          * Returns the URL for a resource with the specified name
1267          */
1268         URL findResource(final String name, boolean check) {
1269             Resource rsc = getResource(name, check);
1270             if (rsc != null) {
1271                 return rsc.getURL();
1272             }
1273             return null;
1274         }
1275 
1276         Resource getResource(final String name, boolean check) {
1277             final URL url;
1278             try {
1279                 URL normalizedBase = new URL(getBaseURL(), ".");
1280                 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
1281 
1282                 if (url.getFile().startsWith(normalizedBase.getFile()) == false) {
1283                     // requested resource had ../..'s in path
1284                     return null;
1285                 }
1286 
1287                 if (check)
1288                     URLClassPath.check(url);
1289 
1290                 final File file;
1291                 if (name.indexOf("..") != -1) {
1292                     file = (new File(dir, name.replace('/', File.separatorChar)))
1293                           .getCanonicalFile();
1294                     if ( !((file.getPath()).startsWith(dir.getPath())) ) {
1295                         /* outside of base dir */
1296                         return null;
1297                     }
1298                 } else {
1299                     file = new File(dir, name.replace('/', File.separatorChar));
1300                 }
1301 
1302                 if (file.exists()) {
1303                     return new Resource() {
1304                         public String getName() { return name; };
1305                         public URL getURL() { return url; };
1306                         public URL getCodeSourceURL() { return getBaseURL(); };
1307                         public InputStream getInputStream() throws IOException
1308                             { return new FileInputStream(file); };
1309                         public int getContentLength() throws IOException
1310                             { return (int)file.length(); };
1311                     };
1312                 }
1313             } catch (Exception e) {
1314                 return null;
1315             }
1316             return null;
1317         }
1318     }
1319 }
1320