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