Path: blob/master/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java
41159 views
/*1* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package jdk.internal.loader;2627import java.io.Closeable;28import java.io.File;29import java.io.FileInputStream;30import java.io.FileNotFoundException;31import java.io.IOException;32import java.io.InputStream;33import java.net.HttpURLConnection;34import java.net.JarURLConnection;35import java.net.MalformedURLException;36import java.net.URI;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.PrivilegedActionException;47import java.security.PrivilegedExceptionAction;48import java.security.cert.Certificate;49import java.util.ArrayDeque;50import java.util.ArrayList;51import java.util.Arrays;52import java.util.Collections;53import java.util.Enumeration;54import java.util.HashMap;55import java.util.HashSet;56import java.util.LinkedList;57import java.util.List;58import java.util.NoSuchElementException;59import java.util.Properties;60import java.util.Set;61import java.util.StringTokenizer;62import java.util.jar.JarFile;63import java.util.zip.ZipEntry;64import java.util.jar.JarEntry;65import java.util.jar.Manifest;66import java.util.jar.Attributes;67import java.util.jar.Attributes.Name;68import java.util.zip.ZipFile;6970import jdk.internal.access.JavaNetURLAccess;71import jdk.internal.access.JavaUtilZipFileAccess;72import jdk.internal.access.SharedSecrets;73import jdk.internal.util.jar.InvalidJarIndexError;74import jdk.internal.util.jar.JarIndex;75import sun.net.util.URLUtil;76import sun.net.www.ParseUtil;77import sun.security.action.GetPropertyAction;7879/**80* This class is used to maintain a search path of URLs for loading classes81* and resources from both JAR files and directories.82*83* @author David Connelly84*/85public class URLClassPath {86private static final String USER_AGENT_JAVA_VERSION = "UA-Java-Version";87private static final String JAVA_VERSION;88private static final boolean DEBUG;89private static final boolean DISABLE_JAR_CHECKING;90private static final boolean DISABLE_ACC_CHECKING;91private static final boolean DISABLE_CP_URL_CHECK;92private static final boolean DEBUG_CP_URL_CHECK;9394static {95Properties props = GetPropertyAction.privilegedGetProperties();96JAVA_VERSION = props.getProperty("java.version");97DEBUG = (props.getProperty("sun.misc.URLClassPath.debug") != null);98String p = props.getProperty("sun.misc.URLClassPath.disableJarChecking");99DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.isEmpty() : false;100101p = props.getProperty("jdk.net.URLClassPath.disableRestrictedPermissions");102DISABLE_ACC_CHECKING = p != null ? p.equals("true") || p.isEmpty() : false;103104// This property will be removed in a later release105p = props.getProperty("jdk.net.URLClassPath.disableClassPathURLCheck");106DISABLE_CP_URL_CHECK = p != null ? p.equals("true") || p.isEmpty() : false;107108// Print a message for each Class-Path entry that is ignored (assuming109// the check is not disabled).110p = props.getProperty("jdk.net.URLClassPath.showIgnoredClassPathEntries");111DEBUG_CP_URL_CHECK = p != null ? p.equals("true") || p.isEmpty() : false;112}113114/* The original search path of URLs. */115private final ArrayList<URL> path;116117/* The deque of unopened URLs */118private final ArrayDeque<URL> unopenedUrls;119120/* The resulting search path of Loaders */121private final ArrayList<Loader> loaders = new ArrayList<>();122123/* Map of each URL opened to its corresponding Loader */124private final HashMap<String, Loader> lmap = new HashMap<>();125126/* The jar protocol handler to use when creating new URLs */127private final URLStreamHandler jarHandler;128129/* Whether this URLClassLoader has been closed yet */130private boolean closed = false;131132/* The context to be used when loading classes and resources. If non-null133* this is the context that was captured during the creation of the134* URLClassLoader. null implies no additional security restrictions. */135@SuppressWarnings("removal")136private final AccessControlContext acc;137138/**139* Creates a new URLClassPath for the given URLs. The URLs will be140* searched in the order specified for classes and resources. A URL141* ending with a '/' is assumed to refer to a directory. Otherwise,142* the URL is assumed to refer to a JAR file.143*144* @param urls the directory and JAR file URLs to search for classes145* and resources146* @param factory the URLStreamHandlerFactory to use when creating new URLs147* @param acc the context to be used when loading classes and resources, may148* be null149*/150public URLClassPath(URL[] urls,151URLStreamHandlerFactory factory,152@SuppressWarnings("removal") AccessControlContext acc) {153ArrayList<URL> path = new ArrayList<>(urls.length);154ArrayDeque<URL> unopenedUrls = new ArrayDeque<>(urls.length);155for (URL url : urls) {156path.add(url);157unopenedUrls.add(url);158}159this.path = path;160this.unopenedUrls = unopenedUrls;161162if (factory != null) {163jarHandler = factory.createURLStreamHandler("jar");164} else {165jarHandler = null;166}167if (DISABLE_ACC_CHECKING)168this.acc = null;169else170this.acc = acc;171}172173public URLClassPath(URL[] urls, @SuppressWarnings("removal") AccessControlContext acc) {174this(urls, null, acc);175}176177/**178* Constructs a URLClassPath from a class path string.179*180* @param cp the class path string181* @param skipEmptyElements indicates if empty elements are ignored or182* treated as the current working directory183*184* @apiNote Used to create the application class path.185*/186URLClassPath(String cp, boolean skipEmptyElements) {187ArrayList<URL> path = new ArrayList<>();188if (cp != null) {189// map each element of class path to a file URL190int off = 0, next;191do {192next = cp.indexOf(File.pathSeparator, off);193String element = (next == -1)194? cp.substring(off)195: cp.substring(off, next);196if (!element.isEmpty() || !skipEmptyElements) {197URL url = toFileURL(element);198if (url != null) path.add(url);199}200off = next + 1;201} while (next != -1);202}203204// can't use ArrayDeque#addAll or new ArrayDeque(Collection);205// it's too early in the bootstrap to trigger use of lambdas206int size = path.size();207ArrayDeque<URL> unopenedUrls = new ArrayDeque<>(size);208for (int i = 0; i < size; i++)209unopenedUrls.add(path.get(i));210211this.unopenedUrls = unopenedUrls;212this.path = path;213this.jarHandler = null;214this.acc = null;215}216217public synchronized List<IOException> closeLoaders() {218if (closed) {219return Collections.emptyList();220}221List<IOException> result = new LinkedList<>();222for (Loader loader : loaders) {223try {224loader.close();225} catch (IOException e) {226result.add(e);227}228}229closed = true;230return result;231}232233/**234* Appends the specified URL to the search path of directory and JAR235* file URLs from which to load classes and resources.236* <p>237* If the URL specified is null or is already in the list of238* URLs, then invoking this method has no effect.239*/240public synchronized void addURL(URL url) {241if (closed || url == null)242return;243synchronized (unopenedUrls) {244if (! path.contains(url)) {245unopenedUrls.addLast(url);246path.add(url);247}248}249}250251/**252* Appends the specified file path as a file URL to the search path.253*/254public void addFile(String s) {255URL url = toFileURL(s);256if (url != null) {257addURL(url);258}259}260261/**262* Returns a file URL for the given file path.263*/264private static URL toFileURL(String s) {265try {266File f = new File(s).getCanonicalFile();267return ParseUtil.fileToEncodedURL(f);268} catch (IOException e) {269return null;270}271}272273/**274* Returns the original search path of URLs.275*/276public URL[] getURLs() {277synchronized (unopenedUrls) {278return path.toArray(new URL[0]);279}280}281282/**283* Finds the resource with the specified name on the URL search path284* or null if not found or security check fails.285*286* @param name the name of the resource287* @param check whether to perform a security check288* @return a {@code URL} for the resource, or {@code null}289* if the resource could not be found.290*/291public URL findResource(String name, boolean check) {292Loader loader;293for (int i = 0; (loader = getLoader(i)) != null; i++) {294URL url = loader.findResource(name, check);295if (url != null) {296return url;297}298}299return null;300}301302/**303* Finds the first Resource on the URL search path which has the specified304* name. Returns null if no Resource could be found.305*306* @param name the name of the Resource307* @param check whether to perform a security check308* @return the Resource, or null if not found309*/310public Resource getResource(String name, boolean check) {311if (DEBUG) {312System.err.println("URLClassPath.getResource(\"" + name + "\")");313}314315Loader loader;316for (int i = 0; (loader = getLoader(i)) != null; i++) {317Resource res = loader.getResource(name, check);318if (res != null) {319return res;320}321}322return null;323}324325/**326* Finds all resources on the URL search path with the given name.327* Returns an enumeration of the URL objects.328*329* @param name the resource name330* @return an Enumeration of all the urls having the specified name331*/332public Enumeration<URL> findResources(final String name,333final boolean check) {334return new Enumeration<>() {335private int index = 0;336private URL url = null;337338private boolean next() {339if (url != null) {340return true;341} else {342Loader loader;343while ((loader = getLoader(index++)) != null) {344url = loader.findResource(name, check);345if (url != null) {346return true;347}348}349return false;350}351}352353public boolean hasMoreElements() {354return next();355}356357public URL nextElement() {358if (!next()) {359throw new NoSuchElementException();360}361URL u = url;362url = null;363return u;364}365};366}367368public Resource getResource(String name) {369return getResource(name, true);370}371372/**373* Finds all resources on the URL search path with the given name.374* Returns an enumeration of the Resource objects.375*376* @param name the resource name377* @return an Enumeration of all the resources having the specified name378*/379public Enumeration<Resource> getResources(final String name,380final boolean check) {381return new Enumeration<>() {382private int index = 0;383private Resource res = null;384385private boolean next() {386if (res != null) {387return true;388} else {389Loader loader;390while ((loader = getLoader(index++)) != null) {391res = loader.getResource(name, check);392if (res != null) {393return true;394}395}396return false;397}398}399400public boolean hasMoreElements() {401return next();402}403404public Resource nextElement() {405if (!next()) {406throw new NoSuchElementException();407}408Resource r = res;409res = null;410return r;411}412};413}414415public Enumeration<Resource> getResources(final String name) {416return getResources(name, true);417}418419/*420* Returns the Loader at the specified position in the URL search421* path. The URLs are opened and expanded as needed. Returns null422* if the specified index is out of range.423*/424private synchronized Loader getLoader(int index) {425if (closed) {426return null;427}428// Expand URL search path until the request can be satisfied429// or unopenedUrls is exhausted.430while (loaders.size() < index + 1) {431final URL url;432synchronized (unopenedUrls) {433url = unopenedUrls.pollFirst();434if (url == null)435return null;436}437// Skip this URL if it already has a Loader. (Loader438// may be null in the case where URL has not been opened439// but is referenced by a JAR index.)440String urlNoFragString = URLUtil.urlNoFragString(url);441if (lmap.containsKey(urlNoFragString)) {442continue;443}444// Otherwise, create a new Loader for the URL.445Loader loader;446try {447loader = getLoader(url);448// If the loader defines a local class path then add the449// URLs as the next URLs to be opened.450URL[] urls = loader.getClassPath();451if (urls != null) {452push(urls);453}454} catch (IOException e) {455// Silently ignore for now...456continue;457} catch (SecurityException se) {458// Always silently ignore. The context, if there is one, that459// this URLClassPath was given during construction will never460// have permission to access the URL.461if (DEBUG) {462System.err.println("Failed to access " + url + ", " + se );463}464continue;465}466// Finally, add the Loader to the search path.467loaders.add(loader);468lmap.put(urlNoFragString, loader);469}470return loaders.get(index);471}472473/*474* Returns the Loader for the specified base URL.475*/476@SuppressWarnings("removal")477private Loader getLoader(final URL url) throws IOException {478try {479return AccessController.doPrivileged(480new PrivilegedExceptionAction<>() {481public Loader run() throws IOException {482String protocol = url.getProtocol(); // lower cased in URL483String file = url.getFile();484if (file != null && file.endsWith("/")) {485if ("file".equals(protocol)) {486return new FileLoader(url);487} else if ("jar".equals(protocol) &&488isDefaultJarHandler(url) &&489file.endsWith("!/")) {490// extract the nested URL491URL nestedUrl = new URL(file.substring(0, file.length() - 2));492return new JarLoader(nestedUrl, jarHandler, lmap, acc);493} else {494return new Loader(url);495}496} else {497return new JarLoader(url, jarHandler, lmap, acc);498}499}500}, acc);501} catch (PrivilegedActionException pae) {502throw (IOException)pae.getException();503}504}505506private static final JavaNetURLAccess JNUA507= SharedSecrets.getJavaNetURLAccess();508509private static boolean isDefaultJarHandler(URL u) {510URLStreamHandler h = JNUA.getHandler(u);511return h instanceof sun.net.www.protocol.jar.Handler;512}513514/*515* Pushes the specified URLs onto the head of unopened URLs.516*/517private void push(URL[] urls) {518synchronized (unopenedUrls) {519for (int i = urls.length - 1; i >= 0; --i) {520unopenedUrls.addFirst(urls[i]);521}522}523}524525/*526* Checks whether the resource URL should be returned.527* Returns null on security check failure.528* Called by java.net.URLClassLoader.529*/530public static URL checkURL(URL url) {531if (url != null) {532try {533check(url);534} catch (Exception e) {535return null;536}537}538return url;539}540541/*542* Checks whether the resource URL should be returned.543* Throws exception on failure.544* Called internally within this file.545*/546public static void check(URL url) throws IOException {547@SuppressWarnings("removal")548SecurityManager security = System.getSecurityManager();549if (security != null) {550URLConnection urlConnection = url.openConnection();551Permission perm = urlConnection.getPermission();552if (perm != null) {553try {554security.checkPermission(perm);555} catch (SecurityException se) {556// fallback to checkRead/checkConnect for pre 1.2557// security managers558if ((perm instanceof java.io.FilePermission) &&559perm.getActions().indexOf("read") != -1) {560security.checkRead(perm.getName());561} else if ((perm instanceof562java.net.SocketPermission) &&563perm.getActions().indexOf("connect") != -1) {564URL locUrl = url;565if (urlConnection instanceof JarURLConnection) {566locUrl = ((JarURLConnection)urlConnection).getJarFileURL();567}568security.checkConnect(locUrl.getHost(),569locUrl.getPort());570} else {571throw se;572}573}574}575}576}577578/**579* Nested class used to represent a loader of resources and classes580* from a base URL.581*/582private static class Loader implements Closeable {583private final URL base;584private JarFile jarfile; // if this points to a jar file585586/*587* Creates a new Loader for the specified URL.588*/589Loader(URL url) {590base = url;591}592593/*594* Returns the base URL for this Loader.595*/596URL getBaseURL() {597return base;598}599600URL findResource(final String name, boolean check) {601URL url;602try {603url = new URL(base, ParseUtil.encodePath(name, false));604} catch (MalformedURLException e) {605return null;606}607608try {609if (check) {610URLClassPath.check(url);611}612613/*614* For a HTTP connection we use the HEAD method to615* check if the resource exists.616*/617URLConnection uc = url.openConnection();618if (uc instanceof HttpURLConnection) {619HttpURLConnection hconn = (HttpURLConnection)uc;620hconn.setRequestMethod("HEAD");621if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {622return null;623}624} else {625// our best guess for the other cases626uc.setUseCaches(false);627InputStream is = uc.getInputStream();628is.close();629}630return url;631} catch (Exception e) {632return null;633}634}635636Resource getResource(final String name, boolean check) {637final URL url;638try {639url = new URL(base, ParseUtil.encodePath(name, false));640} catch (MalformedURLException e) {641return null;642}643final URLConnection uc;644try {645if (check) {646URLClassPath.check(url);647}648uc = url.openConnection();649650if (uc instanceof JarURLConnection) {651/* Need to remember the jar file so it can be closed652* in a hurry.653*/654JarURLConnection juc = (JarURLConnection)uc;655jarfile = JarLoader.checkJar(juc.getJarFile());656}657658InputStream in = uc.getInputStream();659} catch (Exception e) {660return null;661}662return new Resource() {663public String getName() { return name; }664public URL getURL() { return url; }665public URL getCodeSourceURL() { return base; }666public InputStream getInputStream() throws IOException {667return uc.getInputStream();668}669public int getContentLength() throws IOException {670return uc.getContentLength();671}672};673}674675/*676* Returns the Resource for the specified name, or null if not677* found or the caller does not have the permission to get the678* resource.679*/680Resource getResource(final String name) {681return getResource(name, true);682}683684/*685* Closes this loader and release all resources.686* Method overridden in sub-classes.687*/688@Override689public void close() throws IOException {690if (jarfile != null) {691jarfile.close();692}693}694695/*696* Returns the local class path for this loader, or null if none.697*/698URL[] getClassPath() throws IOException {699return null;700}701}702703/*704* Nested class used to represent a Loader of resources from a JAR URL.705*/706private static class JarLoader extends Loader {707private JarFile jar;708private final URL csu;709private JarIndex index;710private URLStreamHandler handler;711private final HashMap<String, Loader> lmap;712@SuppressWarnings("removal")713private final AccessControlContext acc;714private boolean closed = false;715private static final JavaUtilZipFileAccess zipAccess =716SharedSecrets.getJavaUtilZipFileAccess();717718/*719* Creates a new JarLoader for the specified URL referring to720* a JAR file.721*/722private JarLoader(URL url, URLStreamHandler jarHandler,723HashMap<String, Loader> loaderMap,724@SuppressWarnings("removal") AccessControlContext acc)725throws IOException726{727super(new URL("jar", "", -1, url + "!/", jarHandler));728csu = url;729handler = jarHandler;730lmap = loaderMap;731this.acc = acc;732733ensureOpen();734}735736@Override737public void close () throws IOException {738// closing is synchronized at higher level739if (!closed) {740closed = true;741// in case not already open.742ensureOpen();743jar.close();744}745}746747JarFile getJarFile () {748return jar;749}750751private boolean isOptimizable(URL url) {752return "file".equals(url.getProtocol());753}754755@SuppressWarnings("removal")756private void ensureOpen() throws IOException {757if (jar == null) {758try {759AccessController.doPrivileged(760new PrivilegedExceptionAction<>() {761public Void run() throws IOException {762if (DEBUG) {763System.err.println("Opening " + csu);764Thread.dumpStack();765}766767jar = getJarFile(csu);768index = JarIndex.getJarIndex(jar);769if (index != null) {770String[] jarfiles = index.getJarFiles();771// Add all the dependent URLs to the lmap so that loaders772// will not be created for them by URLClassPath.getLoader(int)773// if the same URL occurs later on the main class path. We set774// Loader to null here to avoid creating a Loader for each775// URL until we actually need to try to load something from them.776for (int i = 0; i < jarfiles.length; i++) {777try {778URL jarURL = new URL(csu, jarfiles[i]);779// If a non-null loader already exists, leave it alone.780String urlNoFragString = URLUtil.urlNoFragString(jarURL);781if (!lmap.containsKey(urlNoFragString)) {782lmap.put(urlNoFragString, null);783}784} catch (MalformedURLException e) {785continue;786}787}788}789return null;790}791}, acc);792} catch (PrivilegedActionException pae) {793throw (IOException)pae.getException();794}795}796}797798/* Throws if the given jar file is does not start with the correct LOC */799@SuppressWarnings("removal")800static JarFile checkJar(JarFile jar) throws IOException {801if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING802&& !zipAccess.startsWithLocHeader(jar)) {803IOException x = new IOException("Invalid Jar file");804try {805jar.close();806} catch (IOException ex) {807x.addSuppressed(ex);808}809throw x;810}811812return jar;813}814815private JarFile getJarFile(URL url) throws IOException {816// Optimize case where url refers to a local jar file817if (isOptimizable(url)) {818FileURLMapper p = new FileURLMapper(url);819if (!p.exists()) {820throw new FileNotFoundException(p.getPath());821}822return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ,823JarFile.runtimeVersion()));824}825URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection();826uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);827JarFile jarFile = ((JarURLConnection)uc).getJarFile();828return checkJar(jarFile);829}830831/*832* Returns the index of this JarLoader if it exists.833*/834JarIndex getIndex() {835try {836ensureOpen();837} catch (IOException e) {838throw new InternalError(e);839}840return index;841}842843/*844* Creates the resource and if the check flag is set to true, checks if845* is its okay to return the resource.846*/847Resource checkResource(final String name, boolean check,848final JarEntry entry) {849850final URL url;851try {852String nm;853if (jar.isMultiRelease()) {854nm = entry.getRealName();855} else {856nm = name;857}858url = new URL(getBaseURL(), ParseUtil.encodePath(nm, false));859if (check) {860URLClassPath.check(url);861}862} catch (MalformedURLException e) {863return null;864// throw new IllegalArgumentException("name");865} catch (IOException e) {866return null;867} catch (@SuppressWarnings("removal") AccessControlException e) {868return null;869}870871return new Resource() {872public String getName() { return name; }873public URL getURL() { return url; }874public URL getCodeSourceURL() { return csu; }875public InputStream getInputStream() throws IOException876{ return jar.getInputStream(entry); }877public int getContentLength()878{ return (int)entry.getSize(); }879public Manifest getManifest() throws IOException {880SharedSecrets.javaUtilJarAccess().ensureInitialization(jar);881return jar.getManifest();882}883public Certificate[] getCertificates()884{ return entry.getCertificates(); };885public CodeSigner[] getCodeSigners()886{ return entry.getCodeSigners(); };887};888}889890891/*892* Returns true iff at least one resource in the jar file has the same893* package name as that of the specified resource name.894*/895boolean validIndex(final String name) {896String packageName = name;897int pos;898if ((pos = name.lastIndexOf('/')) != -1) {899packageName = name.substring(0, pos);900}901902String entryName;903ZipEntry entry;904Enumeration<JarEntry> enum_ = jar.entries();905while (enum_.hasMoreElements()) {906entry = enum_.nextElement();907entryName = entry.getName();908if ((pos = entryName.lastIndexOf('/')) != -1)909entryName = entryName.substring(0, pos);910if (entryName.equals(packageName)) {911return true;912}913}914return false;915}916917/*918* Returns the URL for a resource with the specified name919*/920@Override921URL findResource(final String name, boolean check) {922Resource rsc = getResource(name, check);923if (rsc != null) {924return rsc.getURL();925}926return null;927}928929/*930* Returns the JAR Resource for the specified name.931*/932@Override933Resource getResource(final String name, boolean check) {934try {935ensureOpen();936} catch (IOException e) {937throw new InternalError(e);938}939final JarEntry entry = jar.getJarEntry(name);940if (entry != null)941return checkResource(name, check, entry);942943if (index == null)944return null;945946HashSet<String> visited = new HashSet<>();947return getResource(name, check, visited);948}949950/*951* Version of getResource() that tracks the jar files that have been952* visited by linking through the index files. This helper method uses953* a HashSet to store the URLs of jar files that have been searched and954* uses it to avoid going into an infinite loop, looking for a955* non-existent resource.956*/957@SuppressWarnings("removal")958Resource getResource(final String name, boolean check,959Set<String> visited) {960Resource res;961String[] jarFiles;962int count = 0;963LinkedList<String> jarFilesList = null;964965/* If there no jar files in the index that can potential contain966* this resource then return immediately.967*/968if ((jarFilesList = index.get(name)) == null)969return null;970971do {972int size = jarFilesList.size();973jarFiles = jarFilesList.toArray(new String[size]);974/* loop through the mapped jar file list */975while (count < size) {976String jarName = jarFiles[count++];977JarLoader newLoader;978final URL url;979980try{981url = new URL(csu, jarName);982String urlNoFragString = URLUtil.urlNoFragString(url);983if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) {984/* no loader has been set up for this jar file985* before986*/987newLoader = AccessController.doPrivileged(988new PrivilegedExceptionAction<>() {989public JarLoader run() throws IOException {990return new JarLoader(url, handler,991lmap, acc);992}993}, acc);994995/* this newly opened jar file has its own index,996* merge it into the parent's index, taking into997* account the relative path.998*/999JarIndex newIndex = newLoader.getIndex();1000if (newIndex != null) {1001int pos = jarName.lastIndexOf('/');1002newIndex.merge(this.index, (pos == -1 ?1003null : jarName.substring(0, pos + 1)));1004}10051006/* put it in the global hashtable */1007lmap.put(urlNoFragString, newLoader);1008}1009} catch (PrivilegedActionException pae) {1010continue;1011} catch (MalformedURLException e) {1012continue;1013}10141015/* Note that the addition of the url to the list of visited1016* jars incorporates a check for presence in the hashmap1017*/1018boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url));1019if (!visitedURL) {1020try {1021newLoader.ensureOpen();1022} catch (IOException e) {1023throw new InternalError(e);1024}1025final JarEntry entry = newLoader.jar.getJarEntry(name);1026if (entry != null) {1027return newLoader.checkResource(name, check, entry);1028}10291030/* Verify that at least one other resource with the1031* same package name as the lookedup resource is1032* present in the new jar1033*/1034if (!newLoader.validIndex(name)) {1035/* the mapping is wrong */1036throw new InvalidJarIndexError("Invalid index");1037}1038}10391040/* If newLoader is the current loader or if it is a1041* loader that has already been searched or if the new1042* loader does not have an index then skip it1043* and move on to the next loader.1044*/1045if (visitedURL || newLoader == this ||1046newLoader.getIndex() == null) {1047continue;1048}10491050/* Process the index of the new loader1051*/1052if ((res = newLoader.getResource(name, check, visited))1053!= null) {1054return res;1055}1056}1057// Get the list of jar files again as the list could have grown1058// due to merging of index files.1059jarFilesList = index.get(name);10601061// If the count is unchanged, we are done.1062} while (count < jarFilesList.size());1063return null;1064}106510661067/*1068* Returns the JAR file local class path, or null if none.1069*/1070@Override1071URL[] getClassPath() throws IOException {1072if (index != null) {1073return null;1074}10751076ensureOpen();10771078// Only get manifest when necessary1079if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) {1080Manifest man = jar.getManifest();1081if (man != null) {1082Attributes attr = man.getMainAttributes();1083if (attr != null) {1084String value = attr.getValue(Name.CLASS_PATH);1085if (value != null) {1086return parseClassPath(csu, value);1087}1088}1089}1090}1091return null;1092}10931094/*1095* Parses value of the Class-Path manifest attribute and returns1096* an array of URLs relative to the specified base URL.1097*/1098private static URL[] parseClassPath(URL base, String value)1099throws MalformedURLException1100{1101StringTokenizer st = new StringTokenizer(value);1102URL[] urls = new URL[st.countTokens()];1103int i = 0;1104while (st.hasMoreTokens()) {1105String path = st.nextToken();1106URL url = DISABLE_CP_URL_CHECK ? new URL(base, path) : tryResolve(base, path);1107if (url != null) {1108urls[i] = url;1109i++;1110} else {1111if (DEBUG_CP_URL_CHECK) {1112System.err.println("Class-Path entry: \"" + path1113+ "\" ignored in JAR file " + base);1114}1115}1116}1117if (i == 0) {1118urls = null;1119} else if (i != urls.length) {1120// Truncate nulls from end of array1121urls = Arrays.copyOf(urls, i);1122}1123return urls;1124}11251126static URL tryResolve(URL base, String input) throws MalformedURLException {1127if ("file".equalsIgnoreCase(base.getProtocol())) {1128return tryResolveFile(base, input);1129} else {1130return tryResolveNonFile(base, input);1131}1132}11331134/**1135* Attempt to return a file URL by resolving input against a base file1136* URL.1137* @return the resolved URL or null if the input is an absolute URL with1138* a scheme other than file (ignoring case)1139* @throws MalformedURLException1140*/1141static URL tryResolveFile(URL base, String input) throws MalformedURLException {1142URL retVal = new URL(base, input);1143if (input.indexOf(':') >= 0 &&1144!"file".equalsIgnoreCase(retVal.getProtocol())) {1145// 'input' contains a ':', which might be a scheme, or might be1146// a Windows drive letter. If the protocol for the resolved URL1147// isn't "file:", it should be ignored.1148return null;1149}1150return retVal;1151}11521153/**1154* Attempt to return a URL by resolving input against a base URL. Returns1155* null if the resolved URL is not contained by the base URL.1156*1157* @return the resolved URL or null1158* @throws MalformedURLException1159*/1160static URL tryResolveNonFile(URL base, String input) throws MalformedURLException {1161String child = input.replace(File.separatorChar, '/');1162if (isRelative(child)) {1163URL url = new URL(base, child);1164String bp = base.getPath();1165String urlp = url.getPath();1166int pos = bp.lastIndexOf('/');1167if (pos == -1) {1168pos = bp.length() - 1;1169}1170if (urlp.regionMatches(0, bp, 0, pos + 1)1171&& urlp.indexOf("..", pos) == -1) {1172return url;1173}1174}1175return null;1176}11771178/**1179* Returns true if the given input is a relative URI.1180*/1181static boolean isRelative(String child) {1182try {1183return !URI.create(child).isAbsolute();1184} catch (IllegalArgumentException e) {1185return false;1186}1187}1188}11891190/*1191* Nested class used to represent a loader of classes and resources1192* from a file URL that refers to a directory.1193*/1194private static class FileLoader extends Loader {1195/* Canonicalized File */1196private File dir;11971198/*1199* Creates a new FileLoader for the specified URL with a file protocol.1200*/1201private FileLoader(URL url) throws IOException {1202super(url);1203String path = url.getFile().replace('/', File.separatorChar);1204path = ParseUtil.decode(path);1205dir = (new File(path)).getCanonicalFile();1206}12071208/*1209* Returns the URL for a resource with the specified name1210*/1211@Override1212URL findResource(final String name, boolean check) {1213Resource rsc = getResource(name, check);1214if (rsc != null) {1215return rsc.getURL();1216}1217return null;1218}12191220@Override1221Resource getResource(final String name, boolean check) {1222final URL url;1223try {1224URL normalizedBase = new URL(getBaseURL(), ".");1225url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));12261227if (url.getFile().startsWith(normalizedBase.getFile()) == false) {1228// requested resource had ../..'s in path1229return null;1230}12311232if (check)1233URLClassPath.check(url);12341235final File file;1236if (name.indexOf("..") != -1) {1237file = (new File(dir, name.replace('/', File.separatorChar)))1238.getCanonicalFile();1239if ( !((file.getPath()).startsWith(dir.getPath())) ) {1240/* outside of base dir */1241return null;1242}1243} else {1244file = new File(dir, name.replace('/', File.separatorChar));1245}12461247if (file.exists()) {1248return new Resource() {1249public String getName() { return name; };1250public URL getURL() { return url; };1251public URL getCodeSourceURL() { return getBaseURL(); };1252public InputStream getInputStream() throws IOException1253{ return new FileInputStream(file); };1254public int getContentLength() throws IOException1255{ return (int)file.length(); };1256};1257}1258} catch (Exception e) {1259return null;1260}1261return null;1262}1263}1264}126512661267