1/*
2 * Copyright (c) 1997, 2017, 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
26package jdk.internal.loader;
27
28import java.io.Closeable;
29import java.io.File;
30import java.io.FileInputStream;
31import java.io.FileNotFoundException;
32import java.io.IOException;
33import java.io.InputStream;
34import java.net.HttpURLConnection;
35import java.net.JarURLConnection;
36import java.net.MalformedURLException;
37import java.net.URL;
38import java.net.URLConnection;
39import java.net.URLStreamHandler;
40import java.net.URLStreamHandlerFactory;
41import java.security.AccessControlContext;
42import java.security.AccessControlException;
43import java.security.AccessController;
44import java.security.CodeSigner;
45import java.security.Permission;
46import java.security.PrivilegedExceptionAction;
47import java.security.cert.Certificate;
48import java.util.ArrayList;
49import java.util.Arrays;
50import java.util.Collections;
51import java.util.Enumeration;
52import java.util.HashMap;
53import java.util.HashSet;
54import java.util.LinkedList;
55import java.util.List;
56import java.util.NoSuchElementException;
57import java.util.Properties;
58import java.util.Set;
59import java.util.Stack;
60import java.util.StringTokenizer;
61import java.util.jar.JarFile;
62import java.util.zip.ZipEntry;
63import java.util.jar.JarEntry;
64import java.util.jar.Manifest;
65import java.util.jar.Attributes;
66import java.util.jar.Attributes.Name;
67import java.util.zip.ZipFile;
68
69import jdk.internal.misc.JavaNetURLAccess;
70import jdk.internal.misc.JavaUtilZipFileAccess;
71import jdk.internal.misc.SharedSecrets;
72import jdk.internal.util.jar.InvalidJarIndexError;
73import jdk.internal.util.jar.JarIndex;
74import sun.net.util.URLUtil;
75import sun.net.www.ParseUtil;
76import sun.security.action.GetPropertyAction;
77
78/**
79 * This class is used to maintain a search path of URLs for loading classes
80 * and resources from both JAR files and directories.
81 *
82 * @author  David Connelly
83 */
84public 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);
143        }
144        this.path = path;
145        push(urls);
146        if (factory != null) {
147            jarHandler = factory.createURLStreamHandler("jar");
148        } else {
149            jarHandler = null;
150        }
151        if (DISABLE_ACC_CHECKING)
152            this.acc = null;
153        else
154            this.acc = acc;
155    }
156
157    public URLClassPath(URL[] urls, AccessControlContext acc) {
158        this(urls, null, acc);
159    }
160
161    /**
162     * Constructs a URLClassPath from a class path string.
163     *
164     * @param cp the class path string
165     * @param skipEmptyElements indicates if empty elements are ignored or
166     *        treated as the current working directory
167     *
168     * @apiNote Used to create the application class path.
169     */
170    URLClassPath(String cp, boolean skipEmptyElements) {
171        List<URL> path = new ArrayList<>();
172        if (cp != null) {
173            // map each element of class path to a file URL
174            int off = 0;
175            int next;
176            while ((next = cp.indexOf(File.pathSeparator, off)) != -1) {
177                String element = cp.substring(off, next);
178                if (element.length() > 0 || !skipEmptyElements) {
179                    URL url = toFileURL(element);
180                    if (url != null) path.add(url);
181                }
182                off = next + 1;
183            }
184
185            // remaining element
186            String element = cp.substring(off);
187            if (element.length() > 0 || !skipEmptyElements) {
188                URL url = toFileURL(element);
189                if (url != null) path.add(url);
190            }
191
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
233            urls.add(0, url);
234            path.add(url);
235        }
236    }
237
238    /**
239     * Appends the specified file path as a file URL to the search path.
240     */
241    public void addFile(String s) {
242        URL url = toFileURL(s);
243        if (url != null) {
244            addURL(url);
245        }
246    }
247
248    /**
249     * Returns a file URL for the given file path.
250     */
251    private static URL toFileURL(String s) {
252        try {
253            File f = new File(s).getCanonicalFile();
254            return ParseUtil.fileToEncodedURL(f);
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
296     */
297    public Resource getResource(String name, boolean check) {
298        if (DEBUG) {
299            System.err.println("URLClassPath.getResource(\"" + name + "\")");
300        }
301
302        Loader loader;
303        for (int i = 0; (loader = getLoader(i)) != null; i++) {
304            Resource res = loader.getResource(name, check);
305            if (res != null) {
306                return res;
307            }
308        }
309        return null;
310    }
311
312    /**
313     * Finds all resources on the URL search path with the given name.
314     * Returns an enumeration of the URL objects.
315     *
316     * @param name the resource name
317     * @return an Enumeration of all the urls having the specified name
318     */
319    public Enumeration<URL> findResources(final String name,
320                                     final boolean check) {
321        return new Enumeration<>() {
322            private int index = 0;
323            private URL url = null;
324
325            private boolean next() {
326                if (url != null) {
327                    return true;
328                } else {
329                    Loader loader;
330                    while ((loader = getLoader(index++)) != null) {
331                        url = loader.findResource(name, check);
332                        if (url != null) {
333                            return true;
334                        }
335                    }
336                    return false;
337                }
338            }
339
340            public boolean hasMoreElements() {
341                return next();
342            }
343
344            public URL nextElement() {
345                if (!next()) {
346                    throw new NoSuchElementException();
347                }
348                URL u = url;
349                url = null;
350                return u;
351            }
352        };
353    }
354
355    public Resource getResource(String name) {
356        return getResource(name, true);
357    }
358
359    /**
360     * Finds all resources on the URL search path with the given name.
361     * Returns an enumeration of the Resource objects.
362     *
363     * @param name the resource name
364     * @return an Enumeration of all the resources having the specified name
365     */
366    public Enumeration<Resource> getResources(final String name,
367                                    final boolean check) {
368        return new Enumeration<>() {
369            private int index = 0;
370            private Resource res = null;
371
372            private boolean next() {
373                if (res != null) {
374                    return true;
375                } else {
376                    Loader loader;
377                    while ((loader = getLoader(index++)) != null) {
378                        res = loader.getResource(name, check);
379                        if (res != null) {
380                            return true;
381                        }
382                    }
383                    return false;
384                }
385            }
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            }
427            // Skip this URL if it already has a Loader. (Loader
428            // may be null in the case where URL has not been opened
429            // but is referenced by a JAR index.)
430            String urlNoFragString = URLUtil.urlNoFragString(url);
431            if (lmap.containsKey(urlNoFragString)) {
432                continue;
433            }
434            // Otherwise, create a new Loader for the URL.
435            Loader loader;
436            try {
437                loader = getLoader(url);
438                // If the loader defines a local class path then add the
439                // URLs to the list of URLs to be opened.
440                URL[] urls = loader.getClassPath();
441                if (urls != null) {
442                    push(urls);
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;
553                        if (urlConnection instanceof JarURLConnection) {
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                /*
602                 * For a HTTP connection we use the HEAD method to
603                 * check if the resource exists.
604                 */
605                URLConnection uc = url.openConnection();
606                if (uc instanceof HttpURLConnection) {
607                    HttpURLConnection hconn = (HttpURLConnection)uc;
608                    hconn.setRequestMethod("HEAD");
609                    if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {
610                        return null;
611                    }
612                } else {
613                    // our best guess for the other cases
614                    uc.setUseCaches(false);
615                    InputStream is = uc.getInputStream();
616                    is.close();
617                }
618                return url;
619            } catch (Exception e) {
620                return null;
621            }
622        }
623
624        Resource getResource(final String name, boolean check) {
625            final URL url;
626            try {
627                url = new URL(base, ParseUtil.encodePath(name, false));
628            } catch (MalformedURLException e) {
629                throw new IllegalArgumentException("name");
630            }
631            final URLConnection uc;
632            try {
633                if (check) {
634                    URLClassPath.check(url);
635                }
636                uc = url.openConnection();
637                InputStream in = uc.getInputStream();
638                if (uc instanceof JarURLConnection) {
639                    /* Need to remember the jar file so it can be closed
640                     * in a hurry.
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 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 = SharedSecrets.javaUtilJarAccess().getRealName(jar, entry);
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;
847                // throw new IllegalArgumentException("name");
848            } catch (IOException e) {
849                return null;
850            } catch (AccessControlException e) {
851                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
1010                        /* Verify that at least one other resource with the
1011                         * same package name as the lookedup resource is
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
1132                    return null;
1133                }
1134
1135                if (check)
1136                    URLClassPath.check(url);
1137
1138                final File file;
1139                if (name.indexOf("..") != -1) {
1140                    file = (new File(dir, name.replace('/', File.separatorChar)))
1141                          .getCanonicalFile();
1142                    if ( !((file.getPath()).startsWith(dir.getPath())) ) {
1143                        /* outside of base dir */
1144                        return null;
1145                    }
1146                } else {
1147                    file = new File(dir, name.replace('/', File.separatorChar));
1148                }
1149
1150                if (file.exists()) {
1151                    return new Resource() {
1152                        public String getName() { return name; };
1153                        public URL getURL() { return url; };
1154                        public URL getCodeSourceURL() { return getBaseURL(); };
1155                        public InputStream getInputStream() throws IOException
1156                            { return new FileInputStream(file); };
1157                        public int getContentLength() throws IOException
1158                            { return (int)file.length(); };
1159                    };
1160                }
1161            } catch (Exception e) {
1162                return null;
1163            }
1164            return null;
1165        }
1166    }
1167}
1168