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