Path: blob/master/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java
67766 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.CRC32;64import java.util.zip.ZipEntry;65import java.util.jar.JarEntry;66import java.util.jar.Manifest;67import java.util.jar.Attributes;68import java.util.jar.Attributes.Name;69import java.util.zip.ZipFile;7071import jdk.internal.access.JavaNetURLAccess;72import jdk.internal.access.JavaUtilZipFileAccess;73import jdk.internal.access.SharedSecrets;74import jdk.internal.util.jar.InvalidJarIndexError;75import jdk.internal.util.jar.JarIndex;76import sun.net.util.URLUtil;77import sun.net.www.ParseUtil;78import sun.security.action.GetPropertyAction;7980/**81* This class is used to maintain a search path of URLs for loading classes82* and resources from both JAR files and directories.83*84* @author David Connelly85*/86public class URLClassPath {87private static final String USER_AGENT_JAVA_VERSION = "UA-Java-Version";88private static final String JAVA_VERSION;89private static final boolean DEBUG;90private static final boolean DISABLE_JAR_CHECKING;91private static final boolean DISABLE_ACC_CHECKING;92private static final boolean DISABLE_CP_URL_CHECK;93private static final boolean DEBUG_CP_URL_CHECK;9495static {96Properties props = GetPropertyAction.privilegedGetProperties();97JAVA_VERSION = props.getProperty("java.version");98DEBUG = (props.getProperty("sun.misc.URLClassPath.debug") != null);99String p = props.getProperty("sun.misc.URLClassPath.disableJarChecking");100DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.isEmpty() : false;101102p = props.getProperty("jdk.net.URLClassPath.disableRestrictedPermissions");103DISABLE_ACC_CHECKING = p != null ? p.equals("true") || p.isEmpty() : false;104105// This property will be removed in a later release106p = props.getProperty("jdk.net.URLClassPath.disableClassPathURLCheck");107DISABLE_CP_URL_CHECK = p != null ? p.equals("true") || p.isEmpty() : false;108109// Print a message for each Class-Path entry that is ignored (assuming110// the check is not disabled).111p = props.getProperty("jdk.net.URLClassPath.showIgnoredClassPathEntries");112DEBUG_CP_URL_CHECK = p != null ? p.equals("true") || p.isEmpty() : false;113}114115/* The original search path of URLs. */116private final ArrayList<URL> path;117118/* The deque of unopened URLs */119private final ArrayDeque<URL> unopenedUrls;120121/* The resulting search path of Loaders */122private final ArrayList<Loader> loaders = new ArrayList<>();123124/* Map of each URL opened to its corresponding Loader */125private final HashMap<String, Loader> lmap = new HashMap<>();126127/* The jar protocol handler to use when creating new URLs */128private final URLStreamHandler jarHandler;129130/* Whether this URLClassLoader has been closed yet */131private boolean closed = false;132133/* The context to be used when loading classes and resources. If non-null134* this is the context that was captured during the creation of the135* URLClassLoader. null implies no additional security restrictions. */136@SuppressWarnings("removal")137private final AccessControlContext acc;138139/**140* Creates a new URLClassPath for the given URLs. The URLs will be141* searched in the order specified for classes and resources. A URL142* ending with a '/' is assumed to refer to a directory. Otherwise,143* the URL is assumed to refer to a JAR file.144*145* @param urls the directory and JAR file URLs to search for classes146* and resources147* @param factory the URLStreamHandlerFactory to use when creating new URLs148* @param acc the context to be used when loading classes and resources, may149* be null150*/151public URLClassPath(URL[] urls,152URLStreamHandlerFactory factory,153@SuppressWarnings("removal") AccessControlContext acc) {154ArrayList<URL> path = new ArrayList<>(urls.length);155ArrayDeque<URL> unopenedUrls = new ArrayDeque<>(urls.length);156for (URL url : urls) {157path.add(url);158unopenedUrls.add(url);159}160this.path = path;161this.unopenedUrls = unopenedUrls;162163if (factory != null) {164jarHandler = factory.createURLStreamHandler("jar");165} else {166jarHandler = null;167}168if (DISABLE_ACC_CHECKING)169this.acc = null;170else171this.acc = acc;172}173174public URLClassPath(URL[] urls, @SuppressWarnings("removal") AccessControlContext acc) {175this(urls, null, acc);176}177178/**179* Constructs a URLClassPath from a class path string.180*181* @param cp the class path string182* @param skipEmptyElements indicates if empty elements are ignored or183* treated as the current working directory184*185* @apiNote Used to create the application class path.186*/187URLClassPath(String cp, boolean skipEmptyElements) {188ArrayList<URL> path = new ArrayList<>();189if (cp != null) {190// map each element of class path to a file URL191int off = 0, next;192do {193next = cp.indexOf(File.pathSeparator, off);194String element = (next == -1)195? cp.substring(off)196: cp.substring(off, next);197if (!element.isEmpty() || !skipEmptyElements) {198URL url = toFileURL(element);199if (url != null) path.add(url);200}201off = next + 1;202} while (next != -1);203}204205// can't use ArrayDeque#addAll or new ArrayDeque(Collection);206// it's too early in the bootstrap to trigger use of lambdas207int size = path.size();208ArrayDeque<URL> unopenedUrls = new ArrayDeque<>(size);209for (int i = 0; i < size; i++)210unopenedUrls.add(path.get(i));211212this.unopenedUrls = unopenedUrls;213this.path = path;214this.jarHandler = null;215this.acc = null;216}217218public synchronized List<IOException> closeLoaders() {219if (closed) {220return Collections.emptyList();221}222List<IOException> result = new LinkedList<>();223for (Loader loader : loaders) {224try {225loader.close();226} catch (IOException e) {227result.add(e);228}229}230closed = true;231return result;232}233234/**235* Appends the specified URL to the search path of directory and JAR236* file URLs from which to load classes and resources.237* <p>238* If the URL specified is null or is already in the list of239* URLs, then invoking this method has no effect.240*/241public synchronized void addURL(URL url) {242if (closed || url == null)243return;244synchronized (unopenedUrls) {245if (! path.contains(url)) {246unopenedUrls.addLast(url);247path.add(url);248}249}250}251252/**253* Appends the specified file path as a file URL to the search path.254*/255public void addFile(String s) {256URL url = toFileURL(s);257if (url != null) {258addURL(url);259}260}261262/**263* Returns a file URL for the given file path.264*/265private static URL toFileURL(String s) {266try {267File f = new File(s).getCanonicalFile();268return ParseUtil.fileToEncodedURL(f);269} catch (IOException e) {270return null;271}272}273274/**275* Returns the original search path of URLs.276*/277public URL[] getURLs() {278synchronized (unopenedUrls) {279return path.toArray(new URL[0]);280}281}282283/**284* Finds the resource with the specified name on the URL search path285* or null if not found or security check fails.286*287* @param name the name of the resource288* @param check whether to perform a security check289* @return a {@code URL} for the resource, or {@code null}290* if the resource could not be found.291*/292public URL findResource(String name, boolean check) {293Loader loader;294for (int i = 0; (loader = getLoader(i)) != null; i++) {295URL url = loader.findResource(name, check);296if (url != null) {297return url;298}299}300return null;301}302303/**304* Finds the first Resource on the URL search path which has the specified305* name. Returns null if no Resource could be found.306*307* @param name the name of the Resource308* @param check whether to perform a security check309* @return the Resource, or null if not found310*/311public Resource getResource(String name, boolean check) {312if (DEBUG) {313System.err.println("URLClassPath.getResource(\"" + name + "\")");314}315316Loader loader;317for (int i = 0; (loader = getLoader(i)) != null; i++) {318Resource res = loader.getResource(name, check);319if (res != null) {320return res;321}322}323return null;324}325326/**327* Finds all resources on the URL search path with the given name.328* Returns an enumeration of the URL objects.329*330* @param name the resource name331* @return an Enumeration of all the urls having the specified name332*/333public Enumeration<URL> findResources(final String name,334final boolean check) {335return new Enumeration<>() {336private int index = 0;337private URL url = null;338339private boolean next() {340if (url != null) {341return true;342} else {343Loader loader;344while ((loader = getLoader(index++)) != null) {345url = loader.findResource(name, check);346if (url != null) {347return true;348}349}350return false;351}352}353354public boolean hasMoreElements() {355return next();356}357358public URL nextElement() {359if (!next()) {360throw new NoSuchElementException();361}362URL u = url;363url = null;364return u;365}366};367}368369public Resource getResource(String name) {370return getResource(name, true);371}372373/**374* Finds all resources on the URL search path with the given name.375* Returns an enumeration of the Resource objects.376*377* @param name the resource name378* @return an Enumeration of all the resources having the specified name379*/380public Enumeration<Resource> getResources(final String name,381final boolean check) {382return new Enumeration<>() {383private int index = 0;384private Resource res = null;385386private boolean next() {387if (res != null) {388return true;389} else {390Loader loader;391while ((loader = getLoader(index++)) != null) {392res = loader.getResource(name, check);393if (res != null) {394return true;395}396}397return false;398}399}400401public boolean hasMoreElements() {402return next();403}404405public Resource nextElement() {406if (!next()) {407throw new NoSuchElementException();408}409Resource r = res;410res = null;411return r;412}413};414}415416public Enumeration<Resource> getResources(final String name) {417return getResources(name, true);418}419420/*421* Returns the Loader at the specified position in the URL search422* path. The URLs are opened and expanded as needed. Returns null423* if the specified index is out of range.424*/425private synchronized Loader getLoader(int index) {426if (closed) {427return null;428}429// Expand URL search path until the request can be satisfied430// or unopenedUrls is exhausted.431while (loaders.size() < index + 1) {432final URL url;433synchronized (unopenedUrls) {434url = unopenedUrls.pollFirst();435if (url == null)436return null;437}438// Skip this URL if it already has a Loader. (Loader439// may be null in the case where URL has not been opened440// but is referenced by a JAR index.)441String urlNoFragString = URLUtil.urlNoFragString(url);442if (lmap.containsKey(urlNoFragString)) {443continue;444}445// Otherwise, create a new Loader for the URL.446Loader loader;447try {448loader = getLoader(url);449// If the loader defines a local class path then add the450// URLs as the next URLs to be opened.451URL[] urls = loader.getClassPath();452if (urls != null) {453push(urls);454}455} catch (IOException e) {456// Silently ignore for now...457continue;458} catch (SecurityException se) {459// Always silently ignore. The context, if there is one, that460// this URLClassPath was given during construction will never461// have permission to access the URL.462if (DEBUG) {463System.err.println("Failed to access " + url + ", " + se );464}465continue;466}467// Finally, add the Loader to the search path.468loaders.add(loader);469lmap.put(urlNoFragString, loader);470}471return loaders.get(index);472}473474/*475* Returns the Loader for the specified base URL.476*/477@SuppressWarnings("removal")478private Loader getLoader(final URL url) throws IOException {479try {480return AccessController.doPrivileged(481new PrivilegedExceptionAction<>() {482public Loader run() throws IOException {483String protocol = url.getProtocol(); // lower cased in URL484String file = url.getFile();485if (file != null && file.endsWith("/")) {486if ("file".equals(protocol)) {487return new FileLoader(url);488} else if ("jar".equals(protocol) &&489isDefaultJarHandler(url) &&490file.endsWith("!/")) {491// extract the nested URL492URL nestedUrl = new URL(file.substring(0, file.length() - 2));493return new JarLoader(nestedUrl, jarHandler, lmap, acc);494} else {495return new Loader(url);496}497} else {498return new JarLoader(url, jarHandler, lmap, acc);499}500}501}, acc);502} catch (PrivilegedActionException pae) {503throw (IOException)pae.getException();504}505}506507private static final JavaNetURLAccess JNUA508= SharedSecrets.getJavaNetURLAccess();509510private static boolean isDefaultJarHandler(URL u) {511URLStreamHandler h = JNUA.getHandler(u);512return h instanceof sun.net.www.protocol.jar.Handler;513}514515/*516* Pushes the specified URLs onto the head of unopened URLs.517*/518private void push(URL[] urls) {519synchronized (unopenedUrls) {520for (int i = urls.length - 1; i >= 0; --i) {521unopenedUrls.addFirst(urls[i]);522}523}524}525526/*527* Checks whether the resource URL should be returned.528* Returns null on security check failure.529* Called by java.net.URLClassLoader.530*/531public static URL checkURL(URL url) {532if (url != null) {533try {534check(url);535} catch (Exception e) {536return null;537}538}539return url;540}541542/*543* Checks whether the resource URL should be returned.544* Throws exception on failure.545* Called internally within this file.546*/547public static void check(URL url) throws IOException {548@SuppressWarnings("removal")549SecurityManager security = System.getSecurityManager();550if (security != null) {551URLConnection urlConnection = url.openConnection();552Permission perm = urlConnection.getPermission();553if (perm != null) {554try {555security.checkPermission(perm);556} catch (SecurityException se) {557// fallback to checkRead/checkConnect for pre 1.2558// security managers559if ((perm instanceof java.io.FilePermission) &&560perm.getActions().indexOf("read") != -1) {561security.checkRead(perm.getName());562} else if ((perm instanceof563java.net.SocketPermission) &&564perm.getActions().indexOf("connect") != -1) {565URL locUrl = url;566if (urlConnection instanceof JarURLConnection) {567locUrl = ((JarURLConnection)urlConnection).getJarFileURL();568}569security.checkConnect(locUrl.getHost(),570locUrl.getPort());571} else {572throw se;573}574}575}576}577}578579/**580* Nested class used to represent a loader of resources and classes581* from a base URL.582*/583private static class Loader implements Closeable {584private final URL base;585private JarFile jarfile; // if this points to a jar file586587/*588* Creates a new Loader for the specified URL.589*/590Loader(URL url) {591base = url;592}593594/*595* Returns the base URL for this Loader.596*/597URL getBaseURL() {598return base;599}600601URL findResource(final String name, boolean check) {602URL url;603try {604url = new URL(base, ParseUtil.encodePath(name, false));605} catch (MalformedURLException e) {606return null;607}608609try {610if (check) {611URLClassPath.check(url);612}613614/*615* For a HTTP connection we use the HEAD method to616* check if the resource exists.617*/618URLConnection uc = url.openConnection();619if (uc instanceof HttpURLConnection) {620HttpURLConnection hconn = (HttpURLConnection)uc;621hconn.setRequestMethod("HEAD");622if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) {623return null;624}625} else {626// our best guess for the other cases627uc.setUseCaches(false);628InputStream is = uc.getInputStream();629is.close();630}631return url;632} catch (Exception e) {633return null;634}635}636637Resource getResource(final String name, boolean check) {638final URL url;639try {640url = new URL(base, ParseUtil.encodePath(name, false));641} catch (MalformedURLException e) {642return null;643}644final URLConnection uc;645try {646if (check) {647URLClassPath.check(url);648}649uc = url.openConnection();650651if (uc instanceof JarURLConnection) {652/* Need to remember the jar file so it can be closed653* in a hurry.654*/655JarURLConnection juc = (JarURLConnection)uc;656jarfile = JarLoader.checkJar(juc.getJarFile());657}658659InputStream in = uc.getInputStream();660} catch (Exception e) {661return null;662}663return new Resource() {664public String getName() { return name; }665public URL getURL() { return url; }666public URL getCodeSourceURL() { return base; }667public InputStream getInputStream() throws IOException {668return uc.getInputStream();669}670public int getContentLength() throws IOException {671return uc.getContentLength();672}673};674}675676/*677* Returns the Resource for the specified name, or null if not678* found or the caller does not have the permission to get the679* resource.680*/681Resource getResource(final String name) {682return getResource(name, true);683}684685/*686* Closes this loader and release all resources.687* Method overridden in sub-classes.688*/689@Override690public void close() throws IOException {691if (jarfile != null) {692jarfile.close();693}694}695696/*697* Returns the local class path for this loader, or null if none.698*/699URL[] getClassPath() throws IOException {700return null;701}702}703704/*705* Nested class used to represent a Loader of resources from a JAR URL.706*/707private static class JarLoader extends Loader {708private JarFile jar;709private final URL csu;710private JarIndex index;711private URLStreamHandler handler;712private final HashMap<String, Loader> lmap;713@SuppressWarnings("removal")714private final AccessControlContext acc;715private boolean closed = false;716private static final JavaUtilZipFileAccess zipAccess =717SharedSecrets.getJavaUtilZipFileAccess();718719/*720* Creates a new JarLoader for the specified URL referring to721* a JAR file.722*/723private JarLoader(URL url, URLStreamHandler jarHandler,724HashMap<String, Loader> loaderMap,725@SuppressWarnings("removal") AccessControlContext acc)726throws IOException727{728super(new URL("jar", "", -1, url + "!/", jarHandler));729csu = url;730handler = jarHandler;731lmap = loaderMap;732this.acc = acc;733734ensureOpen();735}736737@Override738public void close () throws IOException {739// closing is synchronized at higher level740if (!closed) {741closed = true;742// in case not already open.743ensureOpen();744jar.close();745}746}747748JarFile getJarFile () {749return jar;750}751752private boolean isOptimizable(URL url) {753return "file".equals(url.getProtocol());754}755756@SuppressWarnings("removal")757private void ensureOpen() throws IOException {758if (jar == null) {759try {760AccessController.doPrivileged(761new PrivilegedExceptionAction<>() {762public Void run() throws IOException {763if (DEBUG) {764System.err.println("Opening " + csu);765Thread.dumpStack();766}767768jar = getJarFile(csu);769index = JarIndex.getJarIndex(jar);770if (index != null) {771String[] jarfiles = index.getJarFiles();772// Add all the dependent URLs to the lmap so that loaders773// will not be created for them by URLClassPath.getLoader(int)774// if the same URL occurs later on the main class path. We set775// Loader to null here to avoid creating a Loader for each776// URL until we actually need to try to load something from them.777for (int i = 0; i < jarfiles.length; i++) {778try {779URL jarURL = new URL(csu, jarfiles[i]);780// If a non-null loader already exists, leave it alone.781String urlNoFragString = URLUtil.urlNoFragString(jarURL);782if (!lmap.containsKey(urlNoFragString)) {783lmap.put(urlNoFragString, null);784}785} catch (MalformedURLException e) {786continue;787}788}789}790return null;791}792}, acc);793} catch (PrivilegedActionException pae) {794throw (IOException)pae.getException();795}796}797}798799/* Throws if the given jar file is does not start with the correct LOC */800@SuppressWarnings("removal")801static JarFile checkJar(JarFile jar) throws IOException {802if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING803&& !zipAccess.startsWithLocHeader(jar)) {804IOException x = new IOException("Invalid Jar file");805try {806jar.close();807} catch (IOException ex) {808x.addSuppressed(ex);809}810throw x;811}812813return jar;814}815816private JarFile getJarFile(URL url) throws IOException {817// Optimize case where url refers to a local jar file818if (isOptimizable(url)) {819FileURLMapper p = new FileURLMapper(url);820if (!p.exists()) {821throw new FileNotFoundException(p.getPath());822}823return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ,824JarFile.runtimeVersion()));825}826URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection();827uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);828JarFile jarFile = ((JarURLConnection)uc).getJarFile();829return checkJar(jarFile);830}831832/*833* Returns the index of this JarLoader if it exists.834*/835JarIndex getIndex() {836try {837ensureOpen();838} catch (IOException e) {839throw new InternalError(e);840}841return index;842}843844/*845* Creates the resource and if the check flag is set to true, checks if846* is its okay to return the resource.847*/848Resource checkResource(final String name, boolean check,849final JarEntry entry) {850851final URL url;852try {853String nm;854if (jar.isMultiRelease()) {855nm = entry.getRealName();856} else {857nm = name;858}859url = new URL(getBaseURL(), ParseUtil.encodePath(nm, false));860if (check) {861URLClassPath.check(url);862}863} catch (MalformedURLException e) {864return null;865// throw new IllegalArgumentException("name");866} catch (IOException e) {867return null;868} catch (@SuppressWarnings("removal") AccessControlException e) {869return null;870}871872return new Resource() {873private Exception dataError = null;874public String getName() { return name; }875public URL getURL() { return url; }876public URL getCodeSourceURL() { return csu; }877public InputStream getInputStream() throws IOException878{ return jar.getInputStream(entry); }879public int getContentLength()880{ return (int)entry.getSize(); }881public Manifest getManifest() throws IOException {882SharedSecrets.javaUtilJarAccess().ensureInitialization(jar);883return jar.getManifest();884}885public Certificate[] getCertificates()886{ return entry.getCertificates(); };887public CodeSigner[] getCodeSigners()888{ return entry.getCodeSigners(); };889public Exception getDataError()890{ return dataError; }891public byte[] getBytes() throws IOException {892byte[] bytes = super.getBytes();893CRC32 crc32 = new CRC32();894crc32.update(bytes);895if (crc32.getValue() != entry.getCrc()) {896dataError = new IOException(897"CRC error while extracting entry from JAR file");898}899return bytes;900}901};902}903904905/*906* Returns true iff at least one resource in the jar file has the same907* package name as that of the specified resource name.908*/909boolean validIndex(final String name) {910String packageName = name;911int pos;912if ((pos = name.lastIndexOf('/')) != -1) {913packageName = name.substring(0, pos);914}915916String entryName;917ZipEntry entry;918Enumeration<JarEntry> enum_ = jar.entries();919while (enum_.hasMoreElements()) {920entry = enum_.nextElement();921entryName = entry.getName();922if ((pos = entryName.lastIndexOf('/')) != -1)923entryName = entryName.substring(0, pos);924if (entryName.equals(packageName)) {925return true;926}927}928return false;929}930931/*932* Returns the URL for a resource with the specified name933*/934@Override935URL findResource(final String name, boolean check) {936Resource rsc = getResource(name, check);937if (rsc != null) {938return rsc.getURL();939}940return null;941}942943/*944* Returns the JAR Resource for the specified name.945*/946@Override947Resource getResource(final String name, boolean check) {948try {949ensureOpen();950} catch (IOException e) {951throw new InternalError(e);952}953final JarEntry entry = jar.getJarEntry(name);954if (entry != null)955return checkResource(name, check, entry);956957if (index == null)958return null;959960HashSet<String> visited = new HashSet<>();961return getResource(name, check, visited);962}963964/*965* Version of getResource() that tracks the jar files that have been966* visited by linking through the index files. This helper method uses967* a HashSet to store the URLs of jar files that have been searched and968* uses it to avoid going into an infinite loop, looking for a969* non-existent resource.970*/971@SuppressWarnings("removal")972Resource getResource(final String name, boolean check,973Set<String> visited) {974Resource res;975String[] jarFiles;976int count = 0;977LinkedList<String> jarFilesList = null;978979/* If there no jar files in the index that can potential contain980* this resource then return immediately.981*/982if ((jarFilesList = index.get(name)) == null)983return null;984985do {986int size = jarFilesList.size();987jarFiles = jarFilesList.toArray(new String[size]);988/* loop through the mapped jar file list */989while (count < size) {990String jarName = jarFiles[count++];991JarLoader newLoader;992final URL url;993994try{995url = new URL(csu, jarName);996String urlNoFragString = URLUtil.urlNoFragString(url);997if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) {998/* no loader has been set up for this jar file999* before1000*/1001newLoader = AccessController.doPrivileged(1002new PrivilegedExceptionAction<>() {1003public JarLoader run() throws IOException {1004return new JarLoader(url, handler,1005lmap, acc);1006}1007}, acc);10081009/* this newly opened jar file has its own index,1010* merge it into the parent's index, taking into1011* account the relative path.1012*/1013JarIndex newIndex = newLoader.getIndex();1014if (newIndex != null) {1015int pos = jarName.lastIndexOf('/');1016newIndex.merge(this.index, (pos == -1 ?1017null : jarName.substring(0, pos + 1)));1018}10191020/* put it in the global hashtable */1021lmap.put(urlNoFragString, newLoader);1022}1023} catch (PrivilegedActionException pae) {1024continue;1025} catch (MalformedURLException e) {1026continue;1027}10281029/* Note that the addition of the url to the list of visited1030* jars incorporates a check for presence in the hashmap1031*/1032boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url));1033if (!visitedURL) {1034try {1035newLoader.ensureOpen();1036} catch (IOException e) {1037throw new InternalError(e);1038}1039final JarEntry entry = newLoader.jar.getJarEntry(name);1040if (entry != null) {1041return newLoader.checkResource(name, check, entry);1042}10431044/* Verify that at least one other resource with the1045* same package name as the lookedup resource is1046* present in the new jar1047*/1048if (!newLoader.validIndex(name)) {1049/* the mapping is wrong */1050throw new InvalidJarIndexError("Invalid index");1051}1052}10531054/* If newLoader is the current loader or if it is a1055* loader that has already been searched or if the new1056* loader does not have an index then skip it1057* and move on to the next loader.1058*/1059if (visitedURL || newLoader == this ||1060newLoader.getIndex() == null) {1061continue;1062}10631064/* Process the index of the new loader1065*/1066if ((res = newLoader.getResource(name, check, visited))1067!= null) {1068return res;1069}1070}1071// Get the list of jar files again as the list could have grown1072// due to merging of index files.1073jarFilesList = index.get(name);10741075// If the count is unchanged, we are done.1076} while (count < jarFilesList.size());1077return null;1078}107910801081/*1082* Returns the JAR file local class path, or null if none.1083*/1084@Override1085URL[] getClassPath() throws IOException {1086if (index != null) {1087return null;1088}10891090ensureOpen();10911092// Only get manifest when necessary1093if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) {1094Manifest man = jar.getManifest();1095if (man != null) {1096Attributes attr = man.getMainAttributes();1097if (attr != null) {1098String value = attr.getValue(Name.CLASS_PATH);1099if (value != null) {1100return parseClassPath(csu, value);1101}1102}1103}1104}1105return null;1106}11071108/*1109* Parses value of the Class-Path manifest attribute and returns1110* an array of URLs relative to the specified base URL.1111*/1112private static URL[] parseClassPath(URL base, String value)1113throws MalformedURLException1114{1115StringTokenizer st = new StringTokenizer(value);1116URL[] urls = new URL[st.countTokens()];1117int i = 0;1118while (st.hasMoreTokens()) {1119String path = st.nextToken();1120URL url = DISABLE_CP_URL_CHECK ? new URL(base, path) : tryResolve(base, path);1121if (url != null) {1122urls[i] = url;1123i++;1124} else {1125if (DEBUG_CP_URL_CHECK) {1126System.err.println("Class-Path entry: \"" + path1127+ "\" ignored in JAR file " + base);1128}1129}1130}1131if (i == 0) {1132urls = null;1133} else if (i != urls.length) {1134// Truncate nulls from end of array1135urls = Arrays.copyOf(urls, i);1136}1137return urls;1138}11391140static URL tryResolve(URL base, String input) throws MalformedURLException {1141if ("file".equalsIgnoreCase(base.getProtocol())) {1142return tryResolveFile(base, input);1143} else {1144return tryResolveNonFile(base, input);1145}1146}11471148/**1149* Attempt to return a file URL by resolving input against a base file1150* URL.1151* @return the resolved URL or null if the input is an absolute URL with1152* a scheme other than file (ignoring case)1153* @throws MalformedURLException1154*/1155static URL tryResolveFile(URL base, String input) throws MalformedURLException {1156URL retVal = new URL(base, input);1157if (input.indexOf(':') >= 0 &&1158!"file".equalsIgnoreCase(retVal.getProtocol())) {1159// 'input' contains a ':', which might be a scheme, or might be1160// a Windows drive letter. If the protocol for the resolved URL1161// isn't "file:", it should be ignored.1162return null;1163}1164return retVal;1165}11661167/**1168* Attempt to return a URL by resolving input against a base URL. Returns1169* null if the resolved URL is not contained by the base URL.1170*1171* @return the resolved URL or null1172* @throws MalformedURLException1173*/1174static URL tryResolveNonFile(URL base, String input) throws MalformedURLException {1175String child = input.replace(File.separatorChar, '/');1176if (isRelative(child)) {1177URL url = new URL(base, child);1178String bp = base.getPath();1179String urlp = url.getPath();1180int pos = bp.lastIndexOf('/');1181if (pos == -1) {1182pos = bp.length() - 1;1183}1184if (urlp.regionMatches(0, bp, 0, pos + 1)1185&& urlp.indexOf("..", pos) == -1) {1186return url;1187}1188}1189return null;1190}11911192/**1193* Returns true if the given input is a relative URI.1194*/1195static boolean isRelative(String child) {1196try {1197return !URI.create(child).isAbsolute();1198} catch (IllegalArgumentException e) {1199return false;1200}1201}1202}12031204/*1205* Nested class used to represent a loader of classes and resources1206* from a file URL that refers to a directory.1207*/1208private static class FileLoader extends Loader {1209/* Canonicalized File */1210private File dir;12111212/*1213* Creates a new FileLoader for the specified URL with a file protocol.1214*/1215private FileLoader(URL url) throws IOException {1216super(url);1217String path = url.getFile().replace('/', File.separatorChar);1218path = ParseUtil.decode(path);1219dir = (new File(path)).getCanonicalFile();1220}12211222/*1223* Returns the URL for a resource with the specified name1224*/1225@Override1226URL findResource(final String name, boolean check) {1227Resource rsc = getResource(name, check);1228if (rsc != null) {1229return rsc.getURL();1230}1231return null;1232}12331234@Override1235Resource getResource(final String name, boolean check) {1236final URL url;1237try {1238URL normalizedBase = new URL(getBaseURL(), ".");1239url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));12401241if (url.getFile().startsWith(normalizedBase.getFile()) == false) {1242// requested resource had ../..'s in path1243return null;1244}12451246if (check)1247URLClassPath.check(url);12481249final File file;1250if (name.indexOf("..") != -1) {1251file = (new File(dir, name.replace('/', File.separatorChar)))1252.getCanonicalFile();1253if ( !((file.getPath()).startsWith(dir.getPath())) ) {1254/* outside of base dir */1255return null;1256}1257} else {1258file = new File(dir, name.replace('/', File.separatorChar));1259}12601261if (file.exists()) {1262return new Resource() {1263public String getName() { return name; };1264public URL getURL() { return url; };1265public URL getCodeSourceURL() { return getBaseURL(); };1266public InputStream getInputStream() throws IOException1267{ return new FileInputStream(file); };1268public int getContentLength() throws IOException1269{ return (int)file.length(); };1270};1271}1272} catch (Exception e) {1273return null;1274}1275return null;1276}1277}1278}127912801281