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