26 package jdk.internal.loader;
27
28 import java.io.Closeable;
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.FileNotFoundException;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.net.HttpURLConnection;
35 import java.net.JarURLConnection;
36 import java.net.MalformedURLException;
37 import java.net.URL;
38 import java.net.URLConnection;
39 import java.net.URLStreamHandler;
40 import java.net.URLStreamHandlerFactory;
41 import java.security.AccessControlContext;
42 import java.security.AccessControlException;
43 import java.security.AccessController;
44 import java.security.CodeSigner;
45 import java.security.Permission;
46 import java.security.PrivilegedExceptionAction;
47 import java.security.cert.Certificate;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Collections;
51 import java.util.Enumeration;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.LinkedList;
55 import java.util.List;
56 import java.util.NoSuchElementException;
57 import java.util.Properties;
58 import java.util.Set;
59 import java.util.Stack;
60 import java.util.StringTokenizer;
61 import java.util.jar.JarFile;
62 import java.util.zip.ZipEntry;
63 import java.util.jar.JarEntry;
64 import java.util.jar.Manifest;
65 import java.util.jar.Attributes;
82 * @author David Connelly
83 */
84 public class URLClassPath {
85 private static final String USER_AGENT_JAVA_VERSION = "UA-Java-Version";
86 private static final String JAVA_VERSION;
87 private static final boolean DEBUG;
88 private static final boolean DISABLE_JAR_CHECKING;
89 private static final boolean DISABLE_ACC_CHECKING;
90
91 static {
92 Properties props = GetPropertyAction.privilegedGetProperties();
93 JAVA_VERSION = props.getProperty("java.version");
94 DEBUG = (props.getProperty("sun.misc.URLClassPath.debug") != null);
95 String p = props.getProperty("sun.misc.URLClassPath.disableJarChecking");
96 DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.equals("") : false;
97
98 p = props.getProperty("jdk.net.URLClassPath.disableRestrictedPermissions");
99 DISABLE_ACC_CHECKING = p != null ? p.equals("true") || p.equals("") : false;
100 }
101
102 /* The original search path of URLs. */
103 private final List<URL> path;
104
105 /* The stack of unopened URLs */
106 private final Stack<URL> urls = new Stack<>();
107
108 /* The resulting search path of Loaders */
109 private final ArrayList<Loader> loaders = new ArrayList<>();
110
111 /* Map of each URL opened to its corresponding Loader */
112 private final HashMap<String, Loader> lmap = new HashMap<>();
113
114 /* The jar protocol handler to use when creating new URLs */
115 private final URLStreamHandler jarHandler;
116
117 /* Whether this URLClassLoader has been closed yet */
118 private boolean closed = false;
119
120 /* The context to be used when loading classes and resources. If non-null
121 * this is the context that was captured during the creation of the
122 * URLClassLoader. null implies no additional security restrictions. */
123 private final AccessControlContext acc;
124
125 /**
126 * Creates a new URLClassPath for the given URLs. The URLs will be
127 * searched in the order specified for classes and resources. A URL
128 * ending with a '/' is assumed to refer to a directory. Otherwise,
129 * the URL is assumed to refer to a JAR file.
130 *
131 * @param urls the directory and JAR file URLs to search for classes
132 * and resources
133 * @param factory the URLStreamHandlerFactory to use when creating new URLs
134 * @param acc the context to be used when loading classes and resources, may
135 * be null
136 */
137 public URLClassPath(URL[] urls,
138 URLStreamHandlerFactory factory,
139 AccessControlContext acc) {
140 List<URL> path = new ArrayList<>(urls.length);
141 for (URL url : urls) {
142 path.add(url);
192 // push the URLs
193 for (int i = path.size() - 1; i >= 0; --i) {
194 urls.push(path.get(i));
195 }
196 }
197
198 this.path = path;
199 this.jarHandler = null;
200 this.acc = null;
201 }
202
203 public synchronized List<IOException> closeLoaders() {
204 if (closed) {
205 return Collections.emptyList();
206 }
207 List<IOException> result = new LinkedList<>();
208 for (Loader loader : loaders) {
209 try {
210 loader.close();
211 } catch (IOException e) {
212 result.add (e);
213 }
214 }
215 closed = true;
216 return result;
217 }
218
219 /**
220 * Appends the specified URL to the search path of directory and JAR
221 * file URLs from which to load classes and resources.
222 * <p>
223 * If the URL specified is null or is already in the list of
224 * URLs, then invoking this method has no effect.
225 */
226 public synchronized void addURL(URL url) {
227 if (closed)
228 return;
229 synchronized (urls) {
230 if (url == null || path.contains(url))
231 return;
232
255 } catch (IOException e) {
256 return null;
257 }
258 }
259
260 /**
261 * Returns the original search path of URLs.
262 */
263 public URL[] getURLs() {
264 synchronized (urls) {
265 return path.toArray(new URL[path.size()]);
266 }
267 }
268
269 /**
270 * Finds the resource with the specified name on the URL search path
271 * or null if not found or security check fails.
272 *
273 * @param name the name of the resource
274 * @param check whether to perform a security check
275 * @return a <code>URL</code> for the resource, or <code>null</code>
276 * if the resource could not be found.
277 */
278 public URL findResource(String name, boolean check) {
279 Loader loader;
280 for (int i = 0; (loader = getLoader(i)) != null; i++) {
281 URL url = loader.findResource(name, check);
282 if (url != null) {
283 return url;
284 }
285 }
286 return null;
287 }
288
289 /**
290 * Finds the first Resource on the URL search path which has the specified
291 * name. Returns null if no Resource could be found.
292 *
293 * @param name the name of the Resource
294 * @param check whether to perform a security check
295 * @return the Resource, or null if not found
386
387 public boolean hasMoreElements() {
388 return next();
389 }
390
391 public Resource nextElement() {
392 if (!next()) {
393 throw new NoSuchElementException();
394 }
395 Resource r = res;
396 res = null;
397 return r;
398 }
399 };
400 }
401
402 public Enumeration<Resource> getResources(final String name) {
403 return getResources(name, true);
404 }
405
406 /*
407 * Returns the Loader at the specified position in the URL search
408 * path. The URLs are opened and expanded as needed. Returns null
409 * if the specified index is out of range.
410 */
411 private synchronized Loader getLoader(int index) {
412 if (closed) {
413 return null;
414 }
415 // Expand URL search path until the request can be satisfied
416 // or the URL stack is empty.
417 while (loaders.size() < index + 1) {
418 // Pop the next URL from the URL stack
419 URL url;
420 synchronized (urls) {
421 if (urls.empty()) {
422 return null;
423 } else {
424 url = urls.pop();
425 }
426 }
443 }
444 } catch (IOException e) {
445 // Silently ignore for now...
446 continue;
447 } catch (SecurityException se) {
448 // Always silently ignore. The context, if there is one, that
449 // this URLClassPath was given during construction will never
450 // have permission to access the URL.
451 if (DEBUG) {
452 System.err.println("Failed to access " + url + ", " + se );
453 }
454 continue;
455 }
456 // Finally, add the Loader to the search path.
457 loaders.add(loader);
458 lmap.put(urlNoFragString, loader);
459 }
460 return loaders.get(index);
461 }
462
463 /*
464 * Returns the Loader for the specified base URL.
465 */
466 private Loader getLoader(final URL url) throws IOException {
467 try {
468 return java.security.AccessController.doPrivileged(
469 new java.security.PrivilegedExceptionAction<>() {
470 public Loader run() throws IOException {
471 String protocol = url.getProtocol(); // lower cased in URL
472 String file = url.getFile();
473 if (file != null && file.endsWith("/")) {
474 if ("file".equals(protocol)) {
475 return new FileLoader(url);
476 } else if ("jar".equals(protocol) &&
477 isDefaultJarHandler(url) &&
478 file.endsWith("!/")) {
479 // extract the nested URL
480 URL nestedUrl = new URL(file.substring(0, file.length() - 2));
481 return new JarLoader(nestedUrl, jarHandler, lmap, acc);
482 } else {
483 return new Loader(url);
484 }
485 } else {
486 return new JarLoader(url, jarHandler, lmap, acc);
487 }
488 }
489 }, acc);
490 } catch (java.security.PrivilegedActionException pae) {
491 throw (IOException)pae.getException();
492 }
493 }
494
495 private static final JavaNetURLAccess JNUA
496 = SharedSecrets.getJavaNetURLAccess();
497
498 private static boolean isDefaultJarHandler(URL u) {
499 URLStreamHandler h = JNUA.getHandler(u);
500 return h instanceof sun.net.www.protocol.jar.Handler;
501 }
502
503 /*
504 * Pushes the specified URLs onto the list of unopened URLs.
505 */
506 private void push(URL[] us) {
507 synchronized (urls) {
508 for (int i = us.length - 1; i >= 0; --i) {
509 urls.push(us[i]);
510 }
511 }
512 }
513
514 /*
515 * Check whether the resource URL should be returned.
516 * Return null on security check failure.
517 * Called by java.net.URLClassLoader.
518 */
519 public static URL checkURL(URL url) {
520 if (url != null) {
521 try {
522 check(url);
523 } catch (Exception e) {
524 return null;
525 }
526 }
527 return url;
528 }
529
530 /*
531 * Check whether the resource URL should be returned.
532 * Throw exception on failure.
533 * Called internally within this file.
534 */
535 public static void check(URL url) throws IOException {
536 SecurityManager security = System.getSecurityManager();
537 if (security != null) {
538 URLConnection urlConnection = url.openConnection();
539 Permission perm = urlConnection.getPermission();
540 if (perm != null) {
541 try {
542 security.checkPermission(perm);
543 } catch (SecurityException se) {
544 // fallback to checkRead/checkConnect for pre 1.2
545 // security managers
546 if ((perm instanceof java.io.FilePermission) &&
547 perm.getActions().indexOf("read") != -1) {
548 security.checkRead(perm.getName());
549 } else if ((perm instanceof
550 java.net.SocketPermission) &&
551 perm.getActions().indexOf("connect") != -1) {
552 URL locUrl = url;
554 locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
555 }
556 security.checkConnect(locUrl.getHost(),
557 locUrl.getPort());
558 } else {
559 throw se;
560 }
561 }
562 }
563 }
564 }
565
566 /**
567 * Nested class used to represent a loader of resources and classes
568 * from a base URL.
569 */
570 private static class Loader implements Closeable {
571 private final URL base;
572 private JarFile jarfile; // if this points to a jar file
573
574 /*
575 * Creates a new Loader for the specified URL.
576 */
577 Loader(URL url) {
578 base = url;
579 }
580
581 /*
582 * Returns the base URL for this Loader.
583 */
584 URL getBaseURL() {
585 return base;
586 }
587
588 URL findResource(final String name, boolean check) {
589 URL url;
590 try {
591 url = new URL(base, ParseUtil.encodePath(name, false));
592 } catch (MalformedURLException e) {
593 throw new IllegalArgumentException("name");
594 }
595
596 try {
597 if (check) {
598 URLClassPath.check(url);
599 }
600
601 /*
641 */
642 JarURLConnection juc = (JarURLConnection)uc;
643 jarfile = JarLoader.checkJar(juc.getJarFile());
644 }
645 } catch (Exception e) {
646 return null;
647 }
648 return new Resource() {
649 public String getName() { return name; }
650 public URL getURL() { return url; }
651 public URL getCodeSourceURL() { return base; }
652 public InputStream getInputStream() throws IOException {
653 return uc.getInputStream();
654 }
655 public int getContentLength() throws IOException {
656 return uc.getContentLength();
657 }
658 };
659 }
660
661 /*
662 * Returns the Resource for the specified name, or null if not
663 * found or the caller does not have the permission to get the
664 * resource.
665 */
666 Resource getResource(final String name) {
667 return getResource(name, true);
668 }
669
670 /*
671 * close this loader and release all resources
672 * method overridden in sub-classes
673 */
674 @Override
675 public void close() throws IOException {
676 if (jarfile != null) {
677 jarfile.close();
678 }
679 }
680
681 /*
682 * Returns the local class path for this loader, or null if none.
683 */
684 URL[] getClassPath() throws IOException {
685 return null;
686 }
687 }
688
689 /*
690 * Nested class used to represent a Loader of resources from a JAR URL.
691 */
692 static class JarLoader extends Loader {
693 private JarFile jar;
694 private final URL csu;
695 private JarIndex index;
696 private URLStreamHandler handler;
697 private final HashMap<String, Loader> lmap;
698 private final AccessControlContext acc;
699 private boolean closed = false;
700 private static final JavaUtilZipFileAccess zipAccess =
701 SharedSecrets.getJavaUtilZipFileAccess();
702
703 /*
704 * Creates a new JarLoader for the specified URL referring to
705 * a JAR file.
706 */
707 JarLoader(URL url, URLStreamHandler jarHandler,
708 HashMap<String, Loader> loaderMap,
709 AccessControlContext acc)
710 throws IOException
711 {
712 super(new URL("jar", "", -1, url + "!/", jarHandler));
713 csu = url;
714 handler = jarHandler;
715 lmap = loaderMap;
716 this.acc = acc;
717
718 ensureOpen();
719 }
720
721 @Override
722 public void close () throws IOException {
723 // closing is synchronized at higher level
724 if (!closed) {
725 closed = true;
726 // in case not already open.
727 ensureOpen();
728 jar.close();
729 }
730 }
731
732 JarFile getJarFile () {
733 return jar;
734 }
735
736 private boolean isOptimizable(URL url) {
737 return "file".equals(url.getProtocol());
738 }
739
740 private void ensureOpen() throws IOException {
741 if (jar == null) {
742 try {
743 java.security.AccessController.doPrivileged(
744 new java.security.PrivilegedExceptionAction<>() {
745 public Void run() throws IOException {
746 if (DEBUG) {
747 System.err.println("Opening " + csu);
748 Thread.dumpStack();
749 }
750
751 jar = getJarFile(csu);
752 index = JarIndex.getJarIndex(jar);
753 if (index != null) {
754 String[] jarfiles = index.getJarFiles();
755 // Add all the dependent URLs to the lmap so that loaders
756 // will not be created for them by URLClassPath.getLoader(int)
757 // if the same URL occurs later on the main class path. We set
758 // Loader to null here to avoid creating a Loader for each
759 // URL until we actually need to try to load something from them.
760 for(int i = 0; i < jarfiles.length; i++) {
761 try {
762 URL jarURL = new URL(csu, jarfiles[i]);
763 // If a non-null loader already exists, leave it alone.
764 String urlNoFragString = URLUtil.urlNoFragString(jarURL);
765 if (!lmap.containsKey(urlNoFragString)) {
766 lmap.put(urlNoFragString, null);
767 }
768 } catch (MalformedURLException e) {
769 continue;
770 }
771 }
772 }
773 return null;
774 }
775 }, acc);
776 } catch (java.security.PrivilegedActionException pae) {
777 throw (IOException)pae.getException();
778 }
779 }
780 }
781
782 /* Throws if the given jar file is does not start with the correct LOC */
783 static JarFile checkJar(JarFile jar) throws IOException {
784 if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING
785 && !zipAccess.startsWithLocHeader(jar)) {
786 IOException x = new IOException("Invalid Jar file");
787 try {
788 jar.close();
789 } catch (IOException ex) {
790 x.addSuppressed(ex);
791 }
792 throw x;
793 }
794
795 return jar;
796 }
797
798 private JarFile getJarFile(URL url) throws IOException {
799 // Optimize case where url refers to a local jar file
800 if (isOptimizable(url)) {
801 FileURLMapper p = new FileURLMapper (url);
802 if (!p.exists()) {
803 throw new FileNotFoundException(p.getPath());
804 }
805 return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ,
806 JarFile.runtimeVersion()));
807 }
808 URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection();
809 uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
810 JarFile jarFile = ((JarURLConnection)uc).getJarFile();
811 return checkJar(jarFile);
812 }
813
814 /*
815 * Returns the index of this JarLoader if it exists.
816 */
817 JarIndex getIndex() {
818 try {
819 ensureOpen();
820 } catch (IOException e) {
821 throw new InternalError(e);
822 }
823 return index;
824 }
825
826 /*
827 * Creates the resource and if the check flag is set to true, checks if
828 * is its okay to return the resource.
829 */
830 Resource checkResource(final String name, boolean check,
831 final JarEntry entry) {
832
833 final URL url;
834 try {
835 String nm;
836 if (jar.isMultiRelease()) {
837 nm = entry.getRealName();
838 } else {
839 nm = name;
840 }
841 url = new URL(getBaseURL(), ParseUtil.encodePath(nm, false));
842 if (check) {
843 URLClassPath.check(url);
844 }
845 } catch (MalformedURLException e) {
846 return null;
852 }
853
854 return new Resource() {
855 public String getName() { return name; }
856 public URL getURL() { return url; }
857 public URL getCodeSourceURL() { return csu; }
858 public InputStream getInputStream() throws IOException
859 { return jar.getInputStream(entry); }
860 public int getContentLength()
861 { return (int)entry.getSize(); }
862 public Manifest getManifest() throws IOException
863 { return jar.getManifest(); };
864 public Certificate[] getCertificates()
865 { return entry.getCertificates(); };
866 public CodeSigner[] getCodeSigners()
867 { return entry.getCodeSigners(); };
868 };
869 }
870
871
872 /*
873 * Returns true iff at least one resource in the jar file has the same
874 * package name as that of the specified resource name.
875 */
876 boolean validIndex(final String name) {
877 String packageName = name;
878 int pos;
879 if((pos = name.lastIndexOf('/')) != -1) {
880 packageName = name.substring(0, pos);
881 }
882
883 String entryName;
884 ZipEntry entry;
885 Enumeration<JarEntry> enum_ = jar.entries();
886 while (enum_.hasMoreElements()) {
887 entry = enum_.nextElement();
888 entryName = entry.getName();
889 if((pos = entryName.lastIndexOf('/')) != -1)
890 entryName = entryName.substring(0, pos);
891 if (entryName.equals(packageName)) {
892 return true;
893 }
894 }
895 return false;
896 }
897
898 /*
899 * Returns the URL for a resource with the specified name
900 */
901 @Override
902 URL findResource(final String name, boolean check) {
903 Resource rsc = getResource(name, check);
904 if (rsc != null) {
905 return rsc.getURL();
906 }
907 return null;
908 }
909
910 /*
911 * Returns the JAR Resource for the specified name.
912 */
913 @Override
914 Resource getResource(final String name, boolean check) {
915 try {
916 ensureOpen();
917 } catch (IOException e) {
918 throw new InternalError(e);
919 }
920 final JarEntry entry = jar.getJarEntry(name);
921 if (entry != null)
922 return checkResource(name, check, entry);
923
924 if (index == null)
925 return null;
926
927 HashSet<String> visited = new HashSet<>();
928 return getResource(name, check, visited);
929 }
930
931 /*
932 * Version of getResource() that tracks the jar files that have been
933 * visited by linking through the index files. This helper method uses
934 * a HashSet to store the URLs of jar files that have been searched and
935 * uses it to avoid going into an infinite loop, looking for a
936 * non-existent resource
937 */
938 Resource getResource(final String name, boolean check,
939 Set<String> visited) {
940 Resource res;
941 String[] jarFiles;
942 int count = 0;
943 LinkedList<String> jarFilesList = null;
944
945 /* If there no jar files in the index that can potential contain
946 * this resource then return immediately.
947 */
948 if((jarFilesList = index.get(name)) == null)
949 return null;
950
951 do {
952 int size = jarFilesList.size();
953 jarFiles = jarFilesList.toArray(new String[size]);
954 /* loop through the mapped jar file list */
955 while(count < size) {
956 String jarName = jarFiles[count++];
957 JarLoader newLoader;
958 final URL url;
959
960 try{
961 url = new URL(csu, jarName);
962 String urlNoFragString = URLUtil.urlNoFragString(url);
963 if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) {
964 /* no loader has been set up for this jar file
965 * before
966 */
967 newLoader = AccessController.doPrivileged(
968 new PrivilegedExceptionAction<>() {
969 public JarLoader run() throws IOException {
970 return new JarLoader(url, handler,
971 lmap, acc);
972 }
973 }, acc);
974
975 /* this newly opened jar file has its own index,
976 * merge it into the parent's index, taking into
977 * account the relative path.
978 */
979 JarIndex newIndex = newLoader.getIndex();
980 if(newIndex != null) {
981 int pos = jarName.lastIndexOf('/');
982 newIndex.merge(this.index, (pos == -1 ?
983 null : jarName.substring(0, pos + 1)));
984 }
985
986 /* put it in the global hashtable */
987 lmap.put(urlNoFragString, newLoader);
988 }
989 } catch (java.security.PrivilegedActionException pae) {
990 continue;
991 } catch (MalformedURLException e) {
992 continue;
993 }
994
995 /* Note that the addition of the url to the list of visited
996 * jars incorporates a check for presence in the hashmap
997 */
998 boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url));
999 if (!visitedURL) {
1000 try {
1001 newLoader.ensureOpen();
1002 } catch (IOException e) {
1003 throw new InternalError(e);
1004 }
1005 final JarEntry entry = newLoader.jar.getJarEntry(name);
1006 if (entry != null) {
1007 return newLoader.checkResource(name, check, entry);
1008 }
1009
1012 * present in the new jar
1013 */
1014 if (!newLoader.validIndex(name)) {
1015 /* the mapping is wrong */
1016 throw new InvalidJarIndexError("Invalid index");
1017 }
1018 }
1019
1020 /* If newLoader is the current loader or if it is a
1021 * loader that has already been searched or if the new
1022 * loader does not have an index then skip it
1023 * and move on to the next loader.
1024 */
1025 if (visitedURL || newLoader == this ||
1026 newLoader.getIndex() == null) {
1027 continue;
1028 }
1029
1030 /* Process the index of the new loader
1031 */
1032 if((res = newLoader.getResource(name, check, visited))
1033 != null) {
1034 return res;
1035 }
1036 }
1037 // Get the list of jar files again as the list could have grown
1038 // due to merging of index files.
1039 jarFilesList = index.get(name);
1040
1041 // If the count is unchanged, we are done.
1042 } while(count < jarFilesList.size());
1043 return null;
1044 }
1045
1046
1047 /*
1048 * Returns the JAR file local class path, or null if none.
1049 */
1050 @Override
1051 URL[] getClassPath() throws IOException {
1052 if (index != null) {
1053 return null;
1054 }
1055
1056 ensureOpen();
1057
1058 // Only get manifest when necessary
1059 if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) {
1060 Manifest man = jar.getManifest();
1061 if (man != null) {
1062 Attributes attr = man.getMainAttributes();
1063 if (attr != null) {
1064 String value = attr.getValue(Name.CLASS_PATH);
1065 if (value != null) {
1066 return parseClassPath(csu, value);
1067 }
1068 }
1069 }
1070 }
1071 return null;
1072 }
1073
1074 /*
1075 * Parses value of the Class-Path manifest attribute and returns
1076 * an array of URLs relative to the specified base URL.
1077 */
1078 private static URL[] parseClassPath(URL base, String value)
1079 throws MalformedURLException
1080 {
1081 StringTokenizer st = new StringTokenizer(value);
1082 URL[] urls = new URL[st.countTokens()];
1083 int i = 0;
1084 while (st.hasMoreTokens()) {
1085 String path = st.nextToken();
1086 urls[i] = new URL(base, path);
1087 i++;
1088 }
1089 return urls;
1090 }
1091 }
1092
1093 /*
1094 * Nested class used to represent a loader of classes and resources
1095 * from a file URL that refers to a directory.
1096 */
1097 private static class FileLoader extends Loader {
1098 /* Canonicalized File */
1099 private File dir;
1100
1101 FileLoader(URL url) throws IOException {
1102 super(url);
1103 if (!"file".equals(url.getProtocol())) {
1104 throw new IllegalArgumentException("url");
1105 }
1106 String path = url.getFile().replace('/', File.separatorChar);
1107 path = ParseUtil.decode(path);
1108 dir = (new File(path)).getCanonicalFile();
1109 }
1110
1111 /*
1112 * Returns the URL for a resource with the specified name
1113 */
1114 @Override
1115 URL findResource(final String name, boolean check) {
1116 Resource rsc = getResource(name, check);
1117 if (rsc != null) {
1118 return rsc.getURL();
1119 }
1120 return null;
1121 }
1122
1123 @Override
1124 Resource getResource(final String name, boolean check) {
1125 final URL url;
1126 try {
1127 URL normalizedBase = new URL(getBaseURL(), ".");
1128 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
1129
1130 if (url.getFile().startsWith(normalizedBase.getFile()) == false) {
1131 // requested resource had ../..'s in path
|
26 package jdk.internal.loader;
27
28 import java.io.Closeable;
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.FileNotFoundException;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.net.HttpURLConnection;
35 import java.net.JarURLConnection;
36 import java.net.MalformedURLException;
37 import java.net.URL;
38 import java.net.URLConnection;
39 import java.net.URLStreamHandler;
40 import java.net.URLStreamHandlerFactory;
41 import java.security.AccessControlContext;
42 import java.security.AccessControlException;
43 import java.security.AccessController;
44 import java.security.CodeSigner;
45 import java.security.Permission;
46 import java.security.PrivilegedActionException;
47 import java.security.PrivilegedExceptionAction;
48 import java.security.cert.Certificate;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collections;
52 import java.util.Enumeration;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.LinkedList;
56 import java.util.List;
57 import java.util.NoSuchElementException;
58 import java.util.Properties;
59 import java.util.Set;
60 import java.util.Stack;
61 import java.util.StringTokenizer;
62 import java.util.jar.JarFile;
63 import java.util.zip.ZipEntry;
64 import java.util.jar.JarEntry;
65 import java.util.jar.Manifest;
66 import java.util.jar.Attributes;
83 * @author David Connelly
84 */
85 public class URLClassPath {
86 private static final String USER_AGENT_JAVA_VERSION = "UA-Java-Version";
87 private static final String JAVA_VERSION;
88 private static final boolean DEBUG;
89 private static final boolean DISABLE_JAR_CHECKING;
90 private static final boolean DISABLE_ACC_CHECKING;
91
92 static {
93 Properties props = GetPropertyAction.privilegedGetProperties();
94 JAVA_VERSION = props.getProperty("java.version");
95 DEBUG = (props.getProperty("sun.misc.URLClassPath.debug") != null);
96 String p = props.getProperty("sun.misc.URLClassPath.disableJarChecking");
97 DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.equals("") : false;
98
99 p = props.getProperty("jdk.net.URLClassPath.disableRestrictedPermissions");
100 DISABLE_ACC_CHECKING = p != null ? p.equals("true") || p.equals("") : false;
101 }
102
103 /** The original search path of URLs. */
104 private final List<URL> path;
105
106 /** The stack of unopened URLs. */
107 private final Stack<URL> urls = new Stack<>();
108
109 /** The resulting search path of Loaders. */
110 private final ArrayList<Loader> loaders = new ArrayList<>();
111
112 /** Map of each URL opened to its corresponding Loader. */
113 private final HashMap<String, Loader> lmap = new HashMap<>();
114
115 /** The jar protocol handler to use when creating new URLs. */
116 private final URLStreamHandler jarHandler;
117
118 /** Whether this URLClassLoader has been closed yet. */
119 private boolean closed = false;
120
121 /**
122 * The context to be used when loading classes and resources. If
123 * non-null this is the context that was captured during the creation
124 * of the URLClassLoader. null implies no additional security
125 * restrictions.
126 */
127 private final AccessControlContext acc;
128
129 /**
130 * Creates a new URLClassPath for the given URLs. The URLs will be
131 * searched in the order specified for classes and resources. A URL
132 * ending with a '/' is assumed to refer to a directory. Otherwise,
133 * the URL is assumed to refer to a JAR file.
134 *
135 * @param urls the directory and JAR file URLs to search for classes
136 * and resources
137 * @param factory the URLStreamHandlerFactory to use when creating new URLs
138 * @param acc the context to be used when loading classes and resources, may
139 * be null
140 */
141 public URLClassPath(URL[] urls,
142 URLStreamHandlerFactory factory,
143 AccessControlContext acc) {
144 List<URL> path = new ArrayList<>(urls.length);
145 for (URL url : urls) {
146 path.add(url);
196 // push the URLs
197 for (int i = path.size() - 1; i >= 0; --i) {
198 urls.push(path.get(i));
199 }
200 }
201
202 this.path = path;
203 this.jarHandler = null;
204 this.acc = null;
205 }
206
207 public synchronized List<IOException> closeLoaders() {
208 if (closed) {
209 return Collections.emptyList();
210 }
211 List<IOException> result = new LinkedList<>();
212 for (Loader loader : loaders) {
213 try {
214 loader.close();
215 } catch (IOException e) {
216 result.add(e);
217 }
218 }
219 closed = true;
220 return result;
221 }
222
223 /**
224 * Appends the specified URL to the search path of directory and JAR
225 * file URLs from which to load classes and resources.
226 * <p>
227 * If the URL specified is null or is already in the list of
228 * URLs, then invoking this method has no effect.
229 */
230 public synchronized void addURL(URL url) {
231 if (closed)
232 return;
233 synchronized (urls) {
234 if (url == null || path.contains(url))
235 return;
236
259 } catch (IOException e) {
260 return null;
261 }
262 }
263
264 /**
265 * Returns the original search path of URLs.
266 */
267 public URL[] getURLs() {
268 synchronized (urls) {
269 return path.toArray(new URL[path.size()]);
270 }
271 }
272
273 /**
274 * Finds the resource with the specified name on the URL search path
275 * or null if not found or security check fails.
276 *
277 * @param name the name of the resource
278 * @param check whether to perform a security check
279 * @return a {@code URL} for the resource, or {@code null}
280 * if the resource could not be found.
281 */
282 public URL findResource(String name, boolean check) {
283 Loader loader;
284 for (int i = 0; (loader = getLoader(i)) != null; i++) {
285 URL url = loader.findResource(name, check);
286 if (url != null) {
287 return url;
288 }
289 }
290 return null;
291 }
292
293 /**
294 * Finds the first Resource on the URL search path which has the specified
295 * name. Returns null if no Resource could be found.
296 *
297 * @param name the name of the Resource
298 * @param check whether to perform a security check
299 * @return the Resource, or null if not found
390
391 public boolean hasMoreElements() {
392 return next();
393 }
394
395 public Resource nextElement() {
396 if (!next()) {
397 throw new NoSuchElementException();
398 }
399 Resource r = res;
400 res = null;
401 return r;
402 }
403 };
404 }
405
406 public Enumeration<Resource> getResources(final String name) {
407 return getResources(name, true);
408 }
409
410 /**
411 * Returns the Loader at the specified position in the URL search
412 * path. The URLs are opened and expanded as needed. Returns null
413 * if the specified index is out of range.
414 */
415 private synchronized Loader getLoader(int index) {
416 if (closed) {
417 return null;
418 }
419 // Expand URL search path until the request can be satisfied
420 // or the URL stack is empty.
421 while (loaders.size() < index + 1) {
422 // Pop the next URL from the URL stack
423 URL url;
424 synchronized (urls) {
425 if (urls.empty()) {
426 return null;
427 } else {
428 url = urls.pop();
429 }
430 }
447 }
448 } catch (IOException e) {
449 // Silently ignore for now...
450 continue;
451 } catch (SecurityException se) {
452 // Always silently ignore. The context, if there is one, that
453 // this URLClassPath was given during construction will never
454 // have permission to access the URL.
455 if (DEBUG) {
456 System.err.println("Failed to access " + url + ", " + se );
457 }
458 continue;
459 }
460 // Finally, add the Loader to the search path.
461 loaders.add(loader);
462 lmap.put(urlNoFragString, loader);
463 }
464 return loaders.get(index);
465 }
466
467 /**
468 * Returns the Loader for the specified base URL.
469 */
470 private Loader getLoader(final URL url) throws IOException {
471 try {
472 return AccessController.doPrivileged(
473 new PrivilegedExceptionAction<>() {
474 public Loader run() throws IOException {
475 String protocol = url.getProtocol(); // lower cased in URL
476 String file = url.getFile();
477 if (file != null && file.endsWith("/")) {
478 if ("file".equals(protocol)) {
479 return new FileLoader(url);
480 } else if ("jar".equals(protocol) &&
481 isDefaultJarHandler(url) &&
482 file.endsWith("!/")) {
483 // extract the nested URL
484 URL nestedUrl = new URL(file.substring(0, file.length() - 2));
485 return new JarLoader(nestedUrl, jarHandler, lmap, acc);
486 } else {
487 return new Loader(url);
488 }
489 } else {
490 return new JarLoader(url, jarHandler, lmap, acc);
491 }
492 }
493 }, acc);
494 } catch (PrivilegedActionException pae) {
495 throw (IOException)pae.getException();
496 }
497 }
498
499 private static final JavaNetURLAccess JNUA
500 = SharedSecrets.getJavaNetURLAccess();
501
502 private static boolean isDefaultJarHandler(URL u) {
503 URLStreamHandler h = JNUA.getHandler(u);
504 return h instanceof sun.net.www.protocol.jar.Handler;
505 }
506
507 /**
508 * Pushes the specified URLs onto the list of unopened URLs.
509 */
510 private void push(URL[] us) {
511 synchronized (urls) {
512 for (int i = us.length - 1; i >= 0; --i) {
513 urls.push(us[i]);
514 }
515 }
516 }
517
518 /**
519 * Checks whether the resource URL should be returned.
520 * Returns null on security check failure.
521 * Called by java.net.URLClassLoader.
522 */
523 public static URL checkURL(URL url) {
524 if (url != null) {
525 try {
526 check(url);
527 } catch (Exception e) {
528 return null;
529 }
530 }
531 return url;
532 }
533
534 /**
535 * Checks whether the resource URL should be returned.
536 * Throws exception on failure.
537 * Called internally within this file.
538 */
539 public static void check(URL url) throws IOException {
540 SecurityManager security = System.getSecurityManager();
541 if (security != null) {
542 URLConnection urlConnection = url.openConnection();
543 Permission perm = urlConnection.getPermission();
544 if (perm != null) {
545 try {
546 security.checkPermission(perm);
547 } catch (SecurityException se) {
548 // fallback to checkRead/checkConnect for pre 1.2
549 // security managers
550 if ((perm instanceof java.io.FilePermission) &&
551 perm.getActions().indexOf("read") != -1) {
552 security.checkRead(perm.getName());
553 } else if ((perm instanceof
554 java.net.SocketPermission) &&
555 perm.getActions().indexOf("connect") != -1) {
556 URL locUrl = url;
558 locUrl = ((JarURLConnection)urlConnection).getJarFileURL();
559 }
560 security.checkConnect(locUrl.getHost(),
561 locUrl.getPort());
562 } else {
563 throw se;
564 }
565 }
566 }
567 }
568 }
569
570 /**
571 * Nested class used to represent a loader of resources and classes
572 * from a base URL.
573 */
574 private static class Loader implements Closeable {
575 private final URL base;
576 private JarFile jarfile; // if this points to a jar file
577
578 /**
579 * Creates a new Loader for the specified URL.
580 */
581 Loader(URL url) {
582 base = url;
583 }
584
585 /**
586 * Returns the base URL for this Loader.
587 */
588 URL getBaseURL() {
589 return base;
590 }
591
592 URL findResource(final String name, boolean check) {
593 URL url;
594 try {
595 url = new URL(base, ParseUtil.encodePath(name, false));
596 } catch (MalformedURLException e) {
597 throw new IllegalArgumentException("name");
598 }
599
600 try {
601 if (check) {
602 URLClassPath.check(url);
603 }
604
605 /*
645 */
646 JarURLConnection juc = (JarURLConnection)uc;
647 jarfile = JarLoader.checkJar(juc.getJarFile());
648 }
649 } catch (Exception e) {
650 return null;
651 }
652 return new Resource() {
653 public String getName() { return name; }
654 public URL getURL() { return url; }
655 public URL getCodeSourceURL() { return base; }
656 public InputStream getInputStream() throws IOException {
657 return uc.getInputStream();
658 }
659 public int getContentLength() throws IOException {
660 return uc.getContentLength();
661 }
662 };
663 }
664
665 /**
666 * Returns the Resource for the specified name, or null if not
667 * found or the caller does not have the permission to get the
668 * resource.
669 */
670 Resource getResource(final String name) {
671 return getResource(name, true);
672 }
673
674 /**
675 * Closes this loader and release all resources.
676 * Method overridden in sub-classes.
677 */
678 @Override
679 public void close() throws IOException {
680 if (jarfile != null) {
681 jarfile.close();
682 }
683 }
684
685 /**
686 * Returns the local class path for this loader, or null if none.
687 */
688 URL[] getClassPath() throws IOException {
689 return null;
690 }
691 }
692
693 /**
694 * Nested class used to represent a Loader of resources from a JAR URL.
695 */
696 static class JarLoader extends Loader {
697 private JarFile jar;
698 private final URL csu;
699 private JarIndex index;
700 private URLStreamHandler handler;
701 private final HashMap<String, Loader> lmap;
702 private final AccessControlContext acc;
703 private boolean closed = false;
704 private static final JavaUtilZipFileAccess zipAccess =
705 SharedSecrets.getJavaUtilZipFileAccess();
706
707 /**
708 * Creates a new JarLoader for the specified URL referring to
709 * a JAR file.
710 */
711 JarLoader(URL url, URLStreamHandler jarHandler,
712 HashMap<String, Loader> loaderMap,
713 AccessControlContext acc)
714 throws IOException
715 {
716 super(new URL("jar", "", -1, url + "!/", jarHandler));
717 csu = url;
718 handler = jarHandler;
719 lmap = loaderMap;
720 this.acc = acc;
721
722 ensureOpen();
723 }
724
725 @Override
726 public void close () throws IOException {
727 // closing is synchronized at higher level
728 if (!closed) {
729 closed = true;
730 // in case not already open.
731 ensureOpen();
732 jar.close();
733 }
734 }
735
736 JarFile getJarFile () {
737 return jar;
738 }
739
740 private boolean isOptimizable(URL url) {
741 return "file".equals(url.getProtocol());
742 }
743
744 private void ensureOpen() throws IOException {
745 if (jar == null) {
746 try {
747 AccessController.doPrivileged(
748 new PrivilegedExceptionAction<>() {
749 public Void run() throws IOException {
750 if (DEBUG) {
751 System.err.println("Opening " + csu);
752 Thread.dumpStack();
753 }
754
755 jar = getJarFile(csu);
756 index = JarIndex.getJarIndex(jar);
757 if (index != null) {
758 String[] jarfiles = index.getJarFiles();
759 // Add all the dependent URLs to the lmap so that loaders
760 // will not be created for them by URLClassPath.getLoader(int)
761 // if the same URL occurs later on the main class path. We set
762 // Loader to null here to avoid creating a Loader for each
763 // URL until we actually need to try to load something from them.
764 for (int i = 0; i < jarfiles.length; i++) {
765 try {
766 URL jarURL = new URL(csu, jarfiles[i]);
767 // If a non-null loader already exists, leave it alone.
768 String urlNoFragString = URLUtil.urlNoFragString(jarURL);
769 if (!lmap.containsKey(urlNoFragString)) {
770 lmap.put(urlNoFragString, null);
771 }
772 } catch (MalformedURLException e) {
773 continue;
774 }
775 }
776 }
777 return null;
778 }
779 }, acc);
780 } catch (PrivilegedActionException pae) {
781 throw (IOException)pae.getException();
782 }
783 }
784 }
785
786 /** Throws if the given jar file is does not start with the correct LOC */
787 static JarFile checkJar(JarFile jar) throws IOException {
788 if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING
789 && !zipAccess.startsWithLocHeader(jar)) {
790 IOException x = new IOException("Invalid Jar file");
791 try {
792 jar.close();
793 } catch (IOException ex) {
794 x.addSuppressed(ex);
795 }
796 throw x;
797 }
798
799 return jar;
800 }
801
802 private JarFile getJarFile(URL url) throws IOException {
803 // Optimize case where url refers to a local jar file
804 if (isOptimizable(url)) {
805 FileURLMapper p = new FileURLMapper (url);
806 if (!p.exists()) {
807 throw new FileNotFoundException(p.getPath());
808 }
809 return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ,
810 JarFile.runtimeVersion()));
811 }
812 URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection();
813 uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
814 JarFile jarFile = ((JarURLConnection)uc).getJarFile();
815 return checkJar(jarFile);
816 }
817
818 /**
819 * Returns the index of this JarLoader if it exists.
820 */
821 JarIndex getIndex() {
822 try {
823 ensureOpen();
824 } catch (IOException e) {
825 throw new InternalError(e);
826 }
827 return index;
828 }
829
830 /**
831 * Creates the resource and if the check flag is set to true, checks if
832 * is its okay to return the resource.
833 */
834 Resource checkResource(final String name, boolean check,
835 final JarEntry entry) {
836
837 final URL url;
838 try {
839 String nm;
840 if (jar.isMultiRelease()) {
841 nm = entry.getRealName();
842 } else {
843 nm = name;
844 }
845 url = new URL(getBaseURL(), ParseUtil.encodePath(nm, false));
846 if (check) {
847 URLClassPath.check(url);
848 }
849 } catch (MalformedURLException e) {
850 return null;
856 }
857
858 return new Resource() {
859 public String getName() { return name; }
860 public URL getURL() { return url; }
861 public URL getCodeSourceURL() { return csu; }
862 public InputStream getInputStream() throws IOException
863 { return jar.getInputStream(entry); }
864 public int getContentLength()
865 { return (int)entry.getSize(); }
866 public Manifest getManifest() throws IOException
867 { return jar.getManifest(); };
868 public Certificate[] getCertificates()
869 { return entry.getCertificates(); };
870 public CodeSigner[] getCodeSigners()
871 { return entry.getCodeSigners(); };
872 };
873 }
874
875
876 /**
877 * Returns true iff at least one resource in the jar file has the same
878 * package name as that of the specified resource name.
879 */
880 boolean validIndex(final String name) {
881 String packageName = name;
882 int pos;
883 if ((pos = name.lastIndexOf('/')) != -1) {
884 packageName = name.substring(0, pos);
885 }
886
887 String entryName;
888 ZipEntry entry;
889 Enumeration<JarEntry> enum_ = jar.entries();
890 while (enum_.hasMoreElements()) {
891 entry = enum_.nextElement();
892 entryName = entry.getName();
893 if ((pos = entryName.lastIndexOf('/')) != -1)
894 entryName = entryName.substring(0, pos);
895 if (entryName.equals(packageName)) {
896 return true;
897 }
898 }
899 return false;
900 }
901
902 /**
903 * Returns the URL for a resource with the specified name
904 */
905 @Override
906 URL findResource(final String name, boolean check) {
907 Resource rsc = getResource(name, check);
908 if (rsc != null) {
909 return rsc.getURL();
910 }
911 return null;
912 }
913
914 /**
915 * Returns the JAR Resource for the specified name.
916 */
917 @Override
918 Resource getResource(final String name, boolean check) {
919 try {
920 ensureOpen();
921 } catch (IOException e) {
922 throw new InternalError(e);
923 }
924 final JarEntry entry = jar.getJarEntry(name);
925 if (entry != null)
926 return checkResource(name, check, entry);
927
928 if (index == null)
929 return null;
930
931 HashSet<String> visited = new HashSet<>();
932 return getResource(name, check, visited);
933 }
934
935 /**
936 * Version of getResource() that tracks the jar files that have been
937 * visited by linking through the index files. This helper method uses
938 * a HashSet to store the URLs of jar files that have been searched and
939 * uses it to avoid going into an infinite loop, looking for a
940 * non-existent resource.
941 */
942 Resource getResource(final String name, boolean check,
943 Set<String> visited) {
944 Resource res;
945 String[] jarFiles;
946 int count = 0;
947 LinkedList<String> jarFilesList = null;
948
949 /* If there no jar files in the index that can potential contain
950 * this resource then return immediately.
951 */
952 if ((jarFilesList = index.get(name)) == null)
953 return null;
954
955 do {
956 int size = jarFilesList.size();
957 jarFiles = jarFilesList.toArray(new String[size]);
958 /* loop through the mapped jar file list */
959 while (count < size) {
960 String jarName = jarFiles[count++];
961 JarLoader newLoader;
962 final URL url;
963
964 try{
965 url = new URL(csu, jarName);
966 String urlNoFragString = URLUtil.urlNoFragString(url);
967 if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) {
968 /* no loader has been set up for this jar file
969 * before
970 */
971 newLoader = AccessController.doPrivileged(
972 new PrivilegedExceptionAction<>() {
973 public JarLoader run() throws IOException {
974 return new JarLoader(url, handler,
975 lmap, acc);
976 }
977 }, acc);
978
979 /* this newly opened jar file has its own index,
980 * merge it into the parent's index, taking into
981 * account the relative path.
982 */
983 JarIndex newIndex = newLoader.getIndex();
984 if (newIndex != null) {
985 int pos = jarName.lastIndexOf('/');
986 newIndex.merge(this.index, (pos == -1 ?
987 null : jarName.substring(0, pos + 1)));
988 }
989
990 /* put it in the global hashtable */
991 lmap.put(urlNoFragString, newLoader);
992 }
993 } catch (PrivilegedActionException pae) {
994 continue;
995 } catch (MalformedURLException e) {
996 continue;
997 }
998
999 /* Note that the addition of the url to the list of visited
1000 * jars incorporates a check for presence in the hashmap
1001 */
1002 boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url));
1003 if (!visitedURL) {
1004 try {
1005 newLoader.ensureOpen();
1006 } catch (IOException e) {
1007 throw new InternalError(e);
1008 }
1009 final JarEntry entry = newLoader.jar.getJarEntry(name);
1010 if (entry != null) {
1011 return newLoader.checkResource(name, check, entry);
1012 }
1013
1016 * present in the new jar
1017 */
1018 if (!newLoader.validIndex(name)) {
1019 /* the mapping is wrong */
1020 throw new InvalidJarIndexError("Invalid index");
1021 }
1022 }
1023
1024 /* If newLoader is the current loader or if it is a
1025 * loader that has already been searched or if the new
1026 * loader does not have an index then skip it
1027 * and move on to the next loader.
1028 */
1029 if (visitedURL || newLoader == this ||
1030 newLoader.getIndex() == null) {
1031 continue;
1032 }
1033
1034 /* Process the index of the new loader
1035 */
1036 if ((res = newLoader.getResource(name, check, visited))
1037 != null) {
1038 return res;
1039 }
1040 }
1041 // Get the list of jar files again as the list could have grown
1042 // due to merging of index files.
1043 jarFilesList = index.get(name);
1044
1045 // If the count is unchanged, we are done.
1046 } while (count < jarFilesList.size());
1047 return null;
1048 }
1049
1050
1051 /**
1052 * Returns the JAR file local class path, or null if none.
1053 */
1054 @Override
1055 URL[] getClassPath() throws IOException {
1056 if (index != null) {
1057 return null;
1058 }
1059
1060 ensureOpen();
1061
1062 // Only get manifest when necessary
1063 if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) {
1064 Manifest man = jar.getManifest();
1065 if (man != null) {
1066 Attributes attr = man.getMainAttributes();
1067 if (attr != null) {
1068 String value = attr.getValue(Name.CLASS_PATH);
1069 if (value != null) {
1070 return parseClassPath(csu, value);
1071 }
1072 }
1073 }
1074 }
1075 return null;
1076 }
1077
1078 /**
1079 * Parses value of the Class-Path manifest attribute and returns
1080 * an array of URLs relative to the specified base URL.
1081 */
1082 private static URL[] parseClassPath(URL base, String value)
1083 throws MalformedURLException
1084 {
1085 StringTokenizer st = new StringTokenizer(value);
1086 URL[] urls = new URL[st.countTokens()];
1087 int i = 0;
1088 while (st.hasMoreTokens()) {
1089 String path = st.nextToken();
1090 urls[i] = new URL(base, path);
1091 i++;
1092 }
1093 return urls;
1094 }
1095 }
1096
1097 /**
1098 * Nested class used to represent a loader of classes and resources
1099 * from a file URL that refers to a directory.
1100 */
1101 private static class FileLoader extends Loader {
1102 /** Canonicalized File */
1103 private File dir;
1104
1105 FileLoader(URL url) throws IOException {
1106 super(url);
1107 if (!"file".equals(url.getProtocol())) {
1108 throw new IllegalArgumentException("url");
1109 }
1110 String path = url.getFile().replace('/', File.separatorChar);
1111 path = ParseUtil.decode(path);
1112 dir = (new File(path)).getCanonicalFile();
1113 }
1114
1115 /**
1116 * Returns the URL for a resource with the specified name
1117 */
1118 @Override
1119 URL findResource(final String name, boolean check) {
1120 Resource rsc = getResource(name, check);
1121 if (rsc != null) {
1122 return rsc.getURL();
1123 }
1124 return null;
1125 }
1126
1127 @Override
1128 Resource getResource(final String name, boolean check) {
1129 final URL url;
1130 try {
1131 URL normalizedBase = new URL(getBaseURL(), ".");
1132 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
1133
1134 if (url.getFile().startsWith(normalizedBase.getFile()) == false) {
1135 // requested resource had ../..'s in path
|