This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View File

@@ -0,0 +1,296 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.scan;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;
import org.apache.tomcat.Jar;
import org.apache.tomcat.util.compat.JreCompat;
/**
* Base implementation of Jar for implementations that use a JarInputStream to
* access the JAR file.
*/
public abstract class AbstractInputStreamJar implements Jar {
private final URL jarFileURL;
private NonClosingJarInputStream jarInputStream = null;
private JarEntry entry = null;
private Boolean multiRelease = null;
private Map<String,String> mrMap = null;
public AbstractInputStreamJar(URL jarFileUrl) {
this.jarFileURL = jarFileUrl;
}
@Override
public URL getJarFileURL() {
return jarFileURL;
}
@Override
public void nextEntry() {
if (jarInputStream == null) {
try {
reset();
} catch (IOException e) {
entry = null;
return;
}
}
try {
entry = jarInputStream.getNextJarEntry();
if (multiRelease.booleanValue()) {
// Skip base entries where there is a multi-release entry
// Skip multi-release entries that are not being used
while (entry != null &&
(mrMap.keySet().contains(entry.getName()) ||
entry.getName().startsWith("META-INF/versions/") &&
!mrMap.values().contains(entry.getName()))) {
entry = jarInputStream.getNextJarEntry();
}
} else {
// Skip multi-release entries
while (entry != null && entry.getName().startsWith("META-INF/versions/")) {
entry = jarInputStream.getNextJarEntry();
}
}
} catch (IOException ioe) {
entry = null;
}
}
@Override
public String getEntryName() {
// Given how the entry name is used, there is no requirement to convert
// the name for a multi-release entry to the corresponding base name.
if (entry == null) {
return null;
} else {
return entry.getName();
}
}
@Override
public InputStream getEntryInputStream() throws IOException {
return jarInputStream;
}
@Override
@Deprecated
public boolean entryExists(String name) throws IOException {
return false;
}
@Override
public InputStream getInputStream(String name) throws IOException {
gotoEntry(name);
if (entry == null) {
return null;
} else {
// Clear the entry so that multiple calls to this method for the
// same entry will result in a new InputStream for each call
// (BZ 60798)
entry = null;
return jarInputStream;
}
}
@Override
public long getLastModified(String name) throws IOException {
gotoEntry(name);
if (entry == null) {
return -1;
} else {
return entry.getTime();
}
}
@Override
public boolean exists(String name) throws IOException {
gotoEntry(name);
return entry != null;
}
@Override
public String getURL(String entry) {
StringBuilder result = new StringBuilder("jar:");
result.append(getJarFileURL().toExternalForm());
result.append("!/");
result.append(entry);
return result.toString();
}
@Override
public Manifest getManifest() throws IOException {
reset();
return jarInputStream.getManifest();
}
@Override
public void reset() throws IOException {
closeStream();
entry = null;
jarInputStream = createJarInputStream();
// Only perform multi-release processing on first access
if (multiRelease == null) {
if (JreCompat.isJre9Available()) {
Manifest manifest = jarInputStream.getManifest();
if (manifest == null) {
multiRelease = Boolean.FALSE;
} else {
String mrValue = manifest.getMainAttributes().getValue("Multi-Release");
if (mrValue == null) {
multiRelease = Boolean.FALSE;
} else {
multiRelease = Boolean.valueOf(mrValue);
}
}
} else {
multiRelease = Boolean.FALSE;
}
if (multiRelease.booleanValue()) {
if (mrMap == null) {
populateMrMap();
}
}
}
}
protected void closeStream() {
if (jarInputStream != null) {
try {
jarInputStream.reallyClose();
} catch (IOException ioe) {
// Ignore
}
}
}
protected abstract NonClosingJarInputStream createJarInputStream() throws IOException;
private void gotoEntry(String name) throws IOException {
boolean needsReset = true;
if (multiRelease == null) {
reset();
needsReset = false;
}
// Need to convert requested name to multi-release name (if one exists)
if (multiRelease.booleanValue()) {
String mrName = mrMap.get(name);
if (mrName != null) {
name = mrName;
}
} else if (name.startsWith("META-INF/versions/")) {
entry = null;
return;
}
if (entry != null && name.equals(entry.getName())) {
return;
}
if (needsReset) {
reset();
}
JarEntry jarEntry = jarInputStream.getNextJarEntry();
while (jarEntry != null) {
if (name.equals(jarEntry.getName())) {
entry = jarEntry;
break;
}
jarEntry = jarInputStream.getNextJarEntry();
}
}
private void populateMrMap() throws IOException {
int targetVersion = JreCompat.getInstance().jarFileRuntimeMajorVersion();
Map<String,Integer> mrVersions = new HashMap<>();
JarEntry jarEntry = jarInputStream.getNextJarEntry();
// Tracking the base name and the latest valid version found is
// sufficient to be able to create the renaming map required
while (jarEntry != null) {
String name = jarEntry.getName();
if (name.startsWith("META-INF/versions/") && name.endsWith(".class")) {
// Get the base name and version for this versioned entry
int i = name.indexOf('/', 18);
if (i > 0) {
String baseName = name.substring(i + 1);
int version = Integer.parseInt(name.substring(18, i));
// Ignore any entries targeting for a later version than
// the target for this runtime
if (version <= targetVersion) {
Integer mappedVersion = mrVersions.get(baseName);
if (mappedVersion == null) {
// No version found for this name. Create one.
mrVersions.put(baseName, Integer.valueOf(version));
} else {
// Ignore any entry for which we have already found
// a later version
if (version > mappedVersion.intValue()) {
// Replace the earlier version
mrVersions.put(baseName, Integer.valueOf(version));
}
}
}
}
}
jarEntry = jarInputStream.getNextJarEntry();
}
mrMap = new HashMap<>();
for (Entry<String,Integer> mrVersion : mrVersions.entrySet()) {
mrMap.put(mrVersion.getKey() , "META-INF/versions/" + mrVersion.getValue().toString() +
"/" + mrVersion.getKey());
}
// Reset stream back to the beginning of the JAR
closeStream();
jarInputStream = createJarInputStream();
}
}

View File

@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.scan;
/**
* String constants for the scan package.
*/
public final class Constants {
public static final String Package = "org.apache.tomcat.util.scan";
/* System properties */
public static final String SKIP_JARS_PROPERTY =
"tomcat.util.scan.StandardJarScanFilter.jarsToSkip";
public static final String SCAN_JARS_PROPERTY =
"tomcat.util.scan.StandardJarScanFilter.jarsToScan";
/* Commons strings */
public static final String JAR_EXT = ".jar";
public static final String WEB_INF_LIB = "/WEB-INF/lib/";
public static final String WEB_INF_CLASSES = "/WEB-INF/classes";
}

View File

@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.scan;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import org.apache.tomcat.Jar;
import org.apache.tomcat.util.buf.UriUtil;
/**
* Provide a mechanism to obtain objects that implement {@link Jar}.
*/
public class JarFactory {
private JarFactory() {
// Factory class. Hide public constructor.
}
public static Jar newInstance(URL url) throws IOException {
String urlString = url.toString();
if (urlString.startsWith("jar:file:")) {
if (urlString.endsWith("!/")) {
return new JarFileUrlJar(url, true);
} else {
return new JarFileUrlNestedJar(url);
}
} else if (urlString.startsWith("war:file:")) {
URL jarUrl = UriUtil.warToJar(url);
return new JarFileUrlNestedJar(jarUrl);
} else if (urlString.startsWith("file:")) {
return new JarFileUrlJar(url, false);
} else {
return new UrlJar(url);
}
}
public static URL getJarEntryURL(URL baseUrl, String entryName)
throws MalformedURLException {
String baseExternal = baseUrl.toExternalForm();
if (baseExternal.startsWith("jar")) {
// Assume this is pointing to a JAR file within a WAR. Java doesn't
// support jar:jar:file:... so switch to Tomcat's war:file:...
baseExternal = baseExternal.replaceFirst("^jar:", "war:");
baseExternal = baseExternal.replaceFirst("!/",
Matcher.quoteReplacement(UriUtil.getWarSeparator()));
}
return new URL("jar:" + baseExternal + "!/" + entryName);
}
}

View File

@@ -0,0 +1,221 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.scan;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import org.apache.tomcat.Jar;
import org.apache.tomcat.util.compat.JreCompat;
/**
* Implementation of {@link Jar} that is optimised for file based JAR URLs that
* refer directly to a JAR file (e.g URLs of the form jar:file: ... .jar!/ or
* file:... .jar).
*/
public class JarFileUrlJar implements Jar {
private final JarFile jarFile;
private final URL jarFileURL;
private final boolean multiRelease;
private Enumeration<JarEntry> entries;
private Set<String> entryNamesSeen;
private JarEntry entry = null;
public JarFileUrlJar(URL url, boolean startsWithJar) throws IOException {
if (startsWithJar) {
// jar:file:...
JarURLConnection jarConn = (JarURLConnection) url.openConnection();
jarConn.setUseCaches(false);
jarFile = jarConn.getJarFile();
jarFileURL = jarConn.getJarFileURL();
} else {
// file:...
File f;
try {
f = new File(url.toURI());
} catch (URISyntaxException e) {
throw new IOException(e);
}
jarFile = JreCompat.getInstance().jarFileNewInstance(f);
jarFileURL = url;
}
multiRelease = JreCompat.getInstance().jarFileIsMultiRelease(jarFile);
}
@Override
public URL getJarFileURL() {
return jarFileURL;
}
@Override
@Deprecated
public boolean entryExists(String name) {
return false;
}
@Override
public InputStream getInputStream(String name) throws IOException {
// JarFile#getEntry() is multi-release aware
ZipEntry entry = jarFile.getEntry(name);
if (entry == null) {
return null;
} else {
return jarFile.getInputStream(entry);
}
}
@Override
public long getLastModified(String name) throws IOException {
// JarFile#getEntry() is multi-release aware
ZipEntry entry = jarFile.getEntry(name);
if (entry == null) {
return -1;
} else {
return entry.getTime();
}
}
@Override
public boolean exists(String name) throws IOException {
// JarFile#getEntry() is multi-release aware
ZipEntry entry = jarFile.getEntry(name);
return entry != null;
}
@Override
public String getURL(String entry) {
StringBuilder result = new StringBuilder("jar:");
result.append(getJarFileURL().toExternalForm());
result.append("!/");
result.append(entry);
return result.toString();
}
@Override
public void close() {
if (jarFile != null) {
try {
jarFile.close();
} catch (IOException e) {
// Ignore
}
}
}
@Override
public void nextEntry() {
// JarFile#entries() is NOT multi-release aware
if (entries == null) {
entries = jarFile.entries();
if (multiRelease) {
entryNamesSeen = new HashSet<>();
}
}
if (multiRelease) {
// Need to ensure that:
// - the one, correct entry is returned where multiple versions
// are available
// - that the order of entries in the JAR doesn't prevent the
// correct entries being returned
// - the case where an entry appears in the versions location
// but not in the the base location is handled correctly
// Enumerate the entries until one is reached that represents an
// entry that has not been seen before.
String name = null;
while (true) {
if (entries.hasMoreElements()) {
entry = entries.nextElement();
name = entry.getName();
// Get 'base' name
if (name.startsWith("META-INF/versions/")) {
int i = name.indexOf('/', 18);
if (i == -1) {
continue;
}
name = name.substring(i + 1);
}
if (name.length() == 0 || entryNamesSeen.contains(name)) {
continue;
}
entryNamesSeen.add(name);
// JarFile.getJarEntry is version aware so use it
entry = jarFile.getJarEntry(entry.getName());
break;
} else {
entry = null;
break;
}
}
} else {
if (entries.hasMoreElements()) {
entry = entries.nextElement();
} else {
entry = null;
}
}
}
@Override
public String getEntryName() {
if (entry == null) {
return null;
} else {
return entry.getName();
}
}
@Override
public InputStream getEntryInputStream() throws IOException {
if (entry == null) {
return null;
} else {
return jarFile.getInputStream(entry);
}
}
@Override
public Manifest getManifest() throws IOException {
return jarFile.getManifest();
}
@Override
public void reset() throws IOException {
entries = null;
entryNamesSeen = null;
entry = null;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.scan;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Implementation of {@link org.apache.tomcat.Jar} that is optimised for file
* based JAR URLs that refer to a JAR file nested inside a WAR
* (e.g URLs of the form jar:file: ... .war!/ ... .jar).
*/
public class JarFileUrlNestedJar extends AbstractInputStreamJar {
private final JarFile warFile;
private final JarEntry jarEntry;
public JarFileUrlNestedJar(URL url) throws IOException {
super(url);
JarURLConnection jarConn = (JarURLConnection) url.openConnection();
jarConn.setUseCaches(false);
warFile = jarConn.getJarFile();
String urlAsString = url.toString();
int pathStart = urlAsString.indexOf("!/") + 2;
String jarPath = urlAsString.substring(pathStart);
jarEntry = warFile.getJarEntry(jarPath);
}
@Override
public void close() {
closeStream();
if (warFile != null) {
try {
warFile.close();
} catch (IOException e) {
// Ignore
}
}
}
@Override
protected NonClosingJarInputStream createJarInputStream() throws IOException {
return new NonClosingJarInputStream(warFile.getInputStream(jarEntry));
}
}

View File

@@ -0,0 +1,25 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
jarScan.classloaderFail=Failed to scan [{0}] from classloader hierarchy
jarScan.classloaderJarNoScan=Not performing JAR scanning on file [{0}] from classpath
jarScan.classloaderJarScan=Scanning JAR [{0}] from classpath
jarScan.classloaderStart=Scanning for JARs in classloader hierarchy
jarScan.jarUrlStart=Scanning JAR at URL [{0}]
jarScan.webinfclassesFail=Failed to scan /WEB-INF/classes
jarScan.webinflibFail=Failed to scan JAR [{0}] from /WEB-INF/lib
jarScan.webinflibJarNoScan=Not performing JAR scanning on file [{0}] from /WEB-INF/lib
jarScan.webinflibJarScan=Scanning JAR [{0}] from /WEB-INF/lib
jarScan.webinflibStart=Scanning /WEB-INF/lib for JARs

View File

@@ -0,0 +1,16 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
jarScan.classloaderJarNoScan=Werde auf Datei [{0}] aus dem Classpath kein JAR Scanning durchführen

View File

@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
jarScan.classloaderFail=Fallo al escanear [{0}] desde la herarquia classloader hierarchy
jarScan.classloaderJarNoScan=No se ejecutó el escaneo de JAR en el archivo [{0}] de la ruta de clase
jarScan.webinflibJarNoScan=No se ejecutó escaneo JAR en el archivo [{0}] de /WEB-INF/lib\n
jarScan.webinflibStart=Buscando JARs en /WEB-INF/lib

View File

@@ -0,0 +1,25 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
jarScan.classloaderFail=Echec de recherche dans [{0}] de la hiérarchie de chargeurs de classes
jarScan.classloaderJarNoScan=Le JAR [{0}] dans le chemin de classes ne sera pas analysé
jarScan.classloaderJarScan=Analyse du JAR [{0}] du chemin de classes
jarScan.classloaderStart=Recherche dans les JARs de la hiérarchie de chargeurs de classe
jarScan.jarUrlStart=Recherche dans le JAR à l''URL [{0}]
jarScan.webinfclassesFail=Impossible de parcourir /WEB-INF/classes
jarScan.webinflibFail=Échec de scan du JAR [{0}] de /WEB-INF/lib
jarScan.webinflibJarNoScan=Le JAR [{0}] dans /WEB-INF/lib ne sera pas analysé
jarScan.webinflibJarScan=Analyse du JAR [{0}] dans /WEB-INF/lib
jarScan.webinflibStart=Recherche de JARs dans /WEB-INF/lib

View File

@@ -0,0 +1,25 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
jarScan.classloaderFail=クラスローダー階層から[{0}]をスキャンできませんでした
jarScan.classloaderJarNoScan=クラスパス中のファイル [{0}] は JAR スキャンをしませんでした。
jarScan.classloaderJarScan=クラスパスの JAR ファイル [{0}] をスキャンします。
jarScan.classloaderStart=クラスローダー階層中の JAR ファイルをスキャンします。
jarScan.jarUrlStart=URL [{0}] の JAR ファイルをスキャンします。
jarScan.webinfclassesFail=/WEB-INF/classesのスキャンに失敗しました
jarScan.webinflibFail=/WEB-INF/libからJAR [{0}]をスキャンできませんでした。
jarScan.webinflibJarNoScan=/WEB-INF/lib のファイル [{0}] は JAR スキャンをしませんでした。
jarScan.webinflibJarScan=/WEB-INF/libからJAR [{0}]のスキャンを行います。
jarScan.webinflibStart=/WEB-INF/lib の JAR ファイルを検査しています。

View File

@@ -0,0 +1,25 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
jarScan.classloaderFail=클래스로더 계층구조로부터 [{0}]을(를) 스캔하지 못했습니다.
jarScan.classloaderJarNoScan=클래스패스로부터 파일 [{0}]에 대한 JAR 스캔을 수행하지 않습니다.
jarScan.classloaderJarScan=클래스패스로부터 JAR [{0}]을(를) 스캔합니다.
jarScan.classloaderStart=클래스로더 계층 구조에서 JAR들을 스캔합니다.
jarScan.jarUrlStart=URL [{0}]에 위치한 JAR를 스캔합니다.
jarScan.webinfclassesFail=/WEB-INF/classes를 스캔하지 못했습니다.
jarScan.webinflibFail=/WEB-INF/lib으로부터 JAR [{0}]을(를) 스캔하지 못했습니다.
jarScan.webinflibJarNoScan=/WEB-INF/lib 내의 파일 [{0}]에 대한 JAR 스캔을 수행하지 않습니다.
jarScan.webinflibJarScan=/WEB-INF/lib으로부터 JAR [{0}]을(를) 스캔합니다.
jarScan.webinflibStart=JAR들을 찾기 위해 /WEB-INF/lib을 스캔합니다.

View File

@@ -0,0 +1,23 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
jarScan.classloaderFail=在多级类加载器中扫描[{0}]失败
jarScan.classloaderJarNoScan=跳过classpath路径[{0}]下的jar包扫描。
jarScan.classloaderJarScan=从classpath扫描JAR[{0}]
jarScan.classloaderStart=在类加载器层次结构中扫描JAR
jarScan.jarUrlStart=正在扫描URL [{0}] 上的JAR文件
jarScan.webinfclassesFail=无法扫描/WEB-INF/classes
jarScan.webinflibJarNoScan=没有扫描到/WEB-INF/lib目录下的JAR [{0}]
jarScan.webinflibStart=扫描./WEB-INF/lib 中的JARs

View File

@@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.scan;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.JarInputStream;
/**
* When using a {@link JarInputStream} with an XML parser, the stream will be
* closed by the parser. This causes problems if multiple entries from the JAR
* need to be parsed. This implementation makes {{@link #close()} a NO-OP and
* adds {@link #reallyClose()} that will close the stream.
*/
public class NonClosingJarInputStream extends JarInputStream {
public NonClosingJarInputStream(InputStream in, boolean verify)
throws IOException {
super(in, verify);
}
public NonClosingJarInputStream(InputStream in) throws IOException {
super(in);
}
@Override
public void close() throws IOException {
// Make this a NO-OP so that further entries can be read from the stream
}
public void reallyClose() throws IOException {
super.close();
}
}

View File

@@ -0,0 +1,155 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.scan;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.jar.Manifest;
import org.apache.tomcat.Jar;
/**
* This class provides a wrapper around {@link Jar} that uses reference counting
* to close and re-create the wrapped {@link Jar} instance as required.
*/
public class ReferenceCountedJar implements Jar {
private final URL url;
private Jar wrappedJar;
private int referenceCount = 0;
public ReferenceCountedJar(URL url) throws IOException {
this.url = url;
open();
}
/*
* Note: Returns this instance so it can be used with try-with-resources
*/
private synchronized ReferenceCountedJar open() throws IOException {
if (wrappedJar == null) {
wrappedJar = JarFactory.newInstance(url);
}
referenceCount++;
return this;
}
@Override
public synchronized void close() {
referenceCount--;
if (referenceCount == 0) {
wrappedJar.close();
wrappedJar = null;
}
}
@Override
public URL getJarFileURL() {
return url;
}
@Override
public InputStream getInputStream(String name) throws IOException {
try (ReferenceCountedJar jar = open()) {
return jar.wrappedJar.getInputStream(name);
}
}
@Override
public long getLastModified(String name) throws IOException {
try (ReferenceCountedJar jar = open()) {
return jar.wrappedJar.getLastModified(name);
}
}
@Override
public boolean exists(String name) throws IOException {
try (ReferenceCountedJar jar = open()) {
return jar.wrappedJar.exists(name);
}
}
@Override
public void nextEntry() {
try (ReferenceCountedJar jar = open()) {
jar.wrappedJar.nextEntry();
} catch (IOException ioe) {
throw new IllegalStateException(ioe);
}
}
@Override
public String getEntryName() {
try (ReferenceCountedJar jar = open()) {
return jar.wrappedJar.getEntryName();
} catch (IOException ioe) {
throw new IllegalStateException(ioe);
}
}
@Override
public InputStream getEntryInputStream() throws IOException {
try (ReferenceCountedJar jar = open()) {
return jar.wrappedJar.getEntryInputStream();
}
}
@Override
public String getURL(String entry) {
try (ReferenceCountedJar jar = open()) {
return jar.wrappedJar.getURL(entry);
} catch (IOException ioe) {
throw new IllegalStateException(ioe);
}
}
@Override
public Manifest getManifest() throws IOException {
try (ReferenceCountedJar jar = open()) {
return jar.wrappedJar.getManifest();
}
}
@Override
public void reset() throws IOException {
try (ReferenceCountedJar jar = open()) {
jar.wrappedJar.reset();
}
}
@Override
@Deprecated
public boolean entryExists(String name) throws IOException {
try (ReferenceCountedJar jar = open()) {
return jar.wrappedJar.entryExists(name);
}
}
}

View File

@@ -0,0 +1,260 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.scan;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.tomcat.JarScanFilter;
import org.apache.tomcat.JarScanType;
import org.apache.tomcat.util.file.Matcher;
public class StandardJarScanFilter implements JarScanFilter {
private final ReadWriteLock configurationLock =
new ReentrantReadWriteLock();
private static final String defaultSkip;
private static final String defaultScan;
private static final Set<String> defaultSkipSet = new HashSet<>();
private static final Set<String> defaultScanSet = new HashSet<>();
private static final boolean defaultSkipAll;
static {
// Initialize defaults. There are no setter methods for them.
defaultSkip = System.getProperty(Constants.SKIP_JARS_PROPERTY);
populateSetFromAttribute(defaultSkip, defaultSkipSet);
defaultSkipAll = defaultSkipSet.contains("*") || defaultSkipSet.contains("*.jar");
defaultScan = System.getProperty(Constants.SCAN_JARS_PROPERTY);
populateSetFromAttribute(defaultScan, defaultScanSet);
}
private String tldSkip;
private String tldScan;
private final Set<String> tldSkipSet;
private final Set<String> tldScanSet;
private boolean defaultTldScan = true;
private String pluggabilitySkip;
private String pluggabilityScan;
private final Set<String> pluggabilitySkipSet;
private final Set<String> pluggabilityScanSet;
private boolean defaultPluggabilityScan = true;
/**
* This is the standard implementation of {@link JarScanFilter}. By default,
* the following filtering rules are used:
* <ul>
* <li>JARs that match neither the skip nor the scan list will be included
* in scan results.</li>
* <li>JARs that match the skip list but not the scan list will be excluded
* from scan results.</li>
* <li>JARs that match the scan list will be included from scan results.
* </li>
* </ul>
* The default skip list and default scan list are obtained from the system
* properties {@link Constants#SKIP_JARS_PROPERTY} and
* {@link Constants#SCAN_JARS_PROPERTY} respectively. These default values
* may be over-ridden for the {@link JarScanType#TLD} and
* {@link JarScanType#PLUGGABILITY} scans. The filtering rules may also be
* modified for these scan types using {@link #setDefaultTldScan(boolean)}
* and {@link #setDefaultPluggabilityScan(boolean)}. If set to
* <code>false</code>, the following filtering rules are used for associated
* type:
* <ul>
* <li>JARs that match neither the skip nor the scan list will be excluded
* from scan results.</li>
* <li>JARs that match the scan list but not the skip list will be included
* in scan results.</li>
* <li>JARs that match the skip list will be excluded from scan results.
* </li>
* </ul>
*/
public StandardJarScanFilter() {
tldSkip = defaultSkip;
tldSkipSet = new HashSet<>(defaultSkipSet);
tldScan = defaultScan;
tldScanSet = new HashSet<>(defaultScanSet);
pluggabilitySkip = defaultSkip;
pluggabilitySkipSet = new HashSet<>(defaultSkipSet);
pluggabilityScan = defaultScan;
pluggabilityScanSet = new HashSet<>(defaultScanSet);
}
public String getTldSkip() {
return tldSkip;
}
public void setTldSkip(String tldSkip) {
this.tldSkip = tldSkip;
Lock writeLock = configurationLock.writeLock();
writeLock.lock();
try {
populateSetFromAttribute(tldSkip, tldSkipSet);
} finally {
writeLock.unlock();
}
}
public String getTldScan() {
return tldScan;
}
public void setTldScan(String tldScan) {
this.tldScan = tldScan;
Lock writeLock = configurationLock.writeLock();
writeLock.lock();
try {
populateSetFromAttribute(tldScan, tldScanSet);
} finally {
writeLock.unlock();
}
}
public boolean isSkipAll() {
return defaultSkipAll;
}
public boolean isDefaultTldScan() {
return defaultTldScan;
}
public void setDefaultTldScan(boolean defaultTldScan) {
this.defaultTldScan = defaultTldScan;
}
public String getPluggabilitySkip() {
return pluggabilitySkip;
}
public void setPluggabilitySkip(String pluggabilitySkip) {
this.pluggabilitySkip = pluggabilitySkip;
Lock writeLock = configurationLock.writeLock();
writeLock.lock();
try {
populateSetFromAttribute(pluggabilitySkip, pluggabilitySkipSet);
} finally {
writeLock.unlock();
}
}
public String getPluggabilityScan() {
return pluggabilityScan;
}
public void setPluggabilityScan(String pluggabilityScan) {
this.pluggabilityScan = pluggabilityScan;
Lock writeLock = configurationLock.writeLock();
writeLock.lock();
try {
populateSetFromAttribute(pluggabilityScan, pluggabilityScanSet);
} finally {
writeLock.unlock();
}
}
public boolean isDefaultPluggabilityScan() {
return defaultPluggabilityScan;
}
public void setDefaultPluggabilityScan(boolean defaultPluggabilityScan) {
this.defaultPluggabilityScan = defaultPluggabilityScan;
}
@Override
public boolean check(JarScanType jarScanType, String jarName) {
Lock readLock = configurationLock.readLock();
readLock.lock();
try {
final boolean defaultScan;
final Set<String> toSkip;
final Set<String> toScan;
switch (jarScanType) {
case TLD: {
defaultScan = defaultTldScan;
toSkip = tldSkipSet;
toScan = tldScanSet;
break;
}
case PLUGGABILITY: {
defaultScan = defaultPluggabilityScan;
toSkip = pluggabilitySkipSet;
toScan = pluggabilityScanSet;
break;
}
case OTHER:
default: {
defaultScan = true;
toSkip = defaultSkipSet;
toScan = defaultScanSet;
}
}
if (defaultScan) {
if (Matcher.matchName(toSkip, jarName)) {
if (Matcher.matchName(toScan, jarName)) {
return true;
} else {
return false;
}
}
return true;
} else {
if (Matcher.matchName(toScan, jarName)) {
if (Matcher.matchName(toSkip, jarName)) {
return false;
} else {
return true;
}
}
return false;
}
} finally {
readLock.unlock();
}
}
private static void populateSetFromAttribute(String attribute, Set<String> set) {
set.clear();
if (attribute != null) {
StringTokenizer tokenizer = new StringTokenizer(attribute, ",");
while (tokenizer.hasMoreElements()) {
String token = tokenizer.nextToken().trim();
if (token.length() > 0) {
set.add(token);
}
}
}
}
}

View File

@@ -0,0 +1,506 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.scan;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import javax.servlet.ServletContext;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.Jar;
import org.apache.tomcat.JarScanFilter;
import org.apache.tomcat.JarScanType;
import org.apache.tomcat.JarScanner;
import org.apache.tomcat.JarScannerCallback;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.compat.JreCompat;
import org.apache.tomcat.util.res.StringManager;
/**
* The default {@link JarScanner} implementation scans the WEB-INF/lib directory
* followed by the provided classloader and then works up the classloader
* hierarchy. This implementation is sufficient to meet the requirements of the
* Servlet 3.0 specification as well as to provide a number of Tomcat specific
* extensions. The extensions are:
* <ul>
* <li>Scanning the classloader hierarchy (enabled by default)</li>
* <li>Testing all files to see if they are JARs (disabled by default)</li>
* <li>Testing all directories to see if they are exploded JARs
* (disabled by default)</li>
* </ul>
* All of the extensions may be controlled via configuration.
*/
public class StandardJarScanner implements JarScanner {
private final Log log = LogFactory.getLog(StandardJarScanner.class); // must not be static
/**
* The string resources for this package.
*/
private static final StringManager sm = StringManager.getManager(Constants.Package);
private static final Set<ClassLoader> CLASSLOADER_HIERARCHY;
static {
Set<ClassLoader> cls = new HashSet<>();
ClassLoader cl = StandardJarScanner.class.getClassLoader();
while (cl != null) {
cls.add(cl);
cl = cl.getParent();
}
CLASSLOADER_HIERARCHY = Collections.unmodifiableSet(cls);
}
/**
* Controls the classpath scanning extension.
*/
private boolean scanClassPath = true;
public boolean isScanClassPath() {
return scanClassPath;
}
public void setScanClassPath(boolean scanClassPath) {
this.scanClassPath = scanClassPath;
}
/**
* Controls the JAR file Manifest scanning extension.
*/
private boolean scanManifest = true;
public boolean isScanManifest() {
return scanManifest;
}
public void setScanManifest(boolean scanManifest) {
this.scanManifest = scanManifest;
}
/**
* Controls the testing all files to see of they are JAR files extension.
*/
private boolean scanAllFiles = false;
public boolean isScanAllFiles() {
return scanAllFiles;
}
public void setScanAllFiles(boolean scanAllFiles) {
this.scanAllFiles = scanAllFiles;
}
/**
* Controls the testing all directories to see of they are exploded JAR
* files extension.
*/
private boolean scanAllDirectories = true;
public boolean isScanAllDirectories() {
return scanAllDirectories;
}
public void setScanAllDirectories(boolean scanAllDirectories) {
this.scanAllDirectories = scanAllDirectories;
}
/**
* Controls the testing of the bootstrap classpath which consists of the
* runtime classes provided by the JVM and any installed system extensions.
*/
private boolean scanBootstrapClassPath = false;
public boolean isScanBootstrapClassPath() {
return scanBootstrapClassPath;
}
public void setScanBootstrapClassPath(boolean scanBootstrapClassPath) {
this.scanBootstrapClassPath = scanBootstrapClassPath;
}
/**
* Controls the filtering of the results from the scan for JARs
*/
private JarScanFilter jarScanFilter = new StandardJarScanFilter();
@Override
public JarScanFilter getJarScanFilter() {
return jarScanFilter;
}
@Override
public void setJarScanFilter(JarScanFilter jarScanFilter) {
this.jarScanFilter = jarScanFilter;
}
/**
* Scan the provided ServletContext and class loader for JAR files. Each JAR
* file found will be passed to the callback handler to be processed.
*
* @param scanType The type of JAR scan to perform. This is passed to
* the filter which uses it to determine how to
* filter the results
* @param context The ServletContext - used to locate and access
* WEB-INF/lib
* @param callback The handler to process any JARs found
*/
@Override
public void scan(JarScanType scanType, ServletContext context,
JarScannerCallback callback) {
if (log.isTraceEnabled()) {
log.trace(sm.getString("jarScan.webinflibStart"));
}
if (jarScanFilter instanceof StandardJarScanFilter) {
if (((StandardJarScanFilter) jarScanFilter).isSkipAll())
return;
}
Set<URL> processedURLs = new HashSet<>();
// Scan WEB-INF/lib
Set<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB);
if (dirList != null) {
for (String path : dirList) {
if (path.endsWith(Constants.JAR_EXT) &&
getJarScanFilter().check(scanType,
path.substring(path.lastIndexOf('/')+1))) {
// Need to scan this JAR
if (log.isDebugEnabled()) {
log.debug(sm.getString("jarScan.webinflibJarScan", path));
}
URL url = null;
try {
url = context.getResource(path);
processedURLs.add(url);
process(scanType, callback, url, path, true, null);
} catch (IOException e) {
log.warn(sm.getString("jarScan.webinflibFail", url), e);
}
} else {
if (log.isTraceEnabled()) {
log.trace(sm.getString("jarScan.webinflibJarNoScan", path));
}
}
}
}
// Scan WEB-INF/classes
try {
URL webInfURL = context.getResource(Constants.WEB_INF_CLASSES);
if (webInfURL != null) {
// WEB-INF/classes will also be included in the URLs returned
// by the web application class loader so ensure the class path
// scanning below does not re-scan this location.
processedURLs.add(webInfURL);
if (isScanAllDirectories()) {
URL url = context.getResource(Constants.WEB_INF_CLASSES + "/META-INF");
if (url != null) {
try {
callback.scanWebInfClasses();
} catch (IOException e) {
log.warn(sm.getString("jarScan.webinfclassesFail"), e);
}
}
}
}
} catch (MalformedURLException e) {
// Ignore. Won't happen. URLs are of the correct form.
}
// Scan the classpath
if (isScanClassPath()) {
doScanClassPath(scanType, context, callback, processedURLs);
}
}
protected void doScanClassPath(JarScanType scanType, ServletContext context,
JarScannerCallback callback, Set<URL> processedURLs) {
if (log.isTraceEnabled()) {
log.trace(sm.getString("jarScan.classloaderStart"));
}
ClassLoader stopLoader = null;
if (!isScanBootstrapClassPath()) {
// Stop when we reach the bootstrap class loader
stopLoader = ClassLoader.getSystemClassLoader().getParent();
}
ClassLoader classLoader = context.getClassLoader();
// JARs are treated as application provided until the common class
// loader is reached.
boolean isWebapp = true;
// Use a Deque so URLs can be removed as they are processed
// and new URLs can be added as they are discovered during
// processing.
Deque<URL> classPathUrlsToProcess = new LinkedList<>();
while (classLoader != null && classLoader != stopLoader) {
if (classLoader instanceof URLClassLoader) {
if (isWebapp) {
isWebapp = isWebappClassLoader(classLoader);
}
classPathUrlsToProcess.addAll(
Arrays.asList(((URLClassLoader) classLoader).getURLs()));
processURLs(scanType, callback, processedURLs, isWebapp, classPathUrlsToProcess);
}
classLoader = classLoader.getParent();
}
if (JreCompat.isJre9Available()) {
// The application and platform class loaders are not
// instances of URLClassLoader. Use the class path in this
// case.
addClassPath(classPathUrlsToProcess);
// Also add any modules
JreCompat.getInstance().addBootModulePath(classPathUrlsToProcess);
processURLs(scanType, callback, processedURLs, false, classPathUrlsToProcess);
}
}
protected void processURLs(JarScanType scanType, JarScannerCallback callback,
Set<URL> processedURLs, boolean isWebapp, Deque<URL> classPathUrlsToProcess) {
if (jarScanFilter instanceof StandardJarScanFilter) {
if (((StandardJarScanFilter) jarScanFilter).isSkipAll())
return;
}
while (!classPathUrlsToProcess.isEmpty()) {
URL url = classPathUrlsToProcess.pop();
if (processedURLs.contains(url)) {
// Skip this URL it has already been processed
continue;
}
ClassPathEntry cpe = new ClassPathEntry(url);
// JARs are scanned unless the filter says not to.
// Directories are scanned for pluggability scans or
// if scanAllDirectories is enabled unless the
// filter says not to.
if ((cpe.isJar() ||
scanType == JarScanType.PLUGGABILITY ||
isScanAllDirectories()) &&
getJarScanFilter().check(scanType,
cpe.getName())) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("jarScan.classloaderJarScan", url));
}
try {
processedURLs.add(url);
process(scanType, callback, url, null, isWebapp, classPathUrlsToProcess);
} catch (IOException ioe) {
log.warn(sm.getString("jarScan.classloaderFail", url), ioe);
}
} else {
// JAR / directory has been skipped
if (log.isTraceEnabled()) {
log.trace(sm.getString("jarScan.classloaderJarNoScan", url));
}
}
}
}
protected void addClassPath(Deque<URL> classPathUrlsToProcess) {
String classPath = System.getProperty("java.class.path");
if (classPath == null || classPath.length() == 0) {
return;
}
String[] classPathEntries = classPath.split(File.pathSeparator);
for (String classPathEntry : classPathEntries) {
File f = new File(classPathEntry);
try {
classPathUrlsToProcess.add(f.toURI().toURL());
} catch (MalformedURLException e) {
log.warn(sm.getString("jarScan.classPath.badEntry", classPathEntry), e);
}
}
}
/*
* Since class loader hierarchies can get complicated, this method attempts
* to apply the following rule: A class loader is a web application class
* loader unless it loaded this class (StandardJarScanner) or is a parent
* of the class loader that loaded this class.
*
* This should mean:
* the webapp class loader is an application class loader
* the shared class loader is an application class loader
* the server class loader is not an application class loader
* the common class loader is not an application class loader
* the system class loader is not an application class loader
* the bootstrap class loader is not an application class loader
*/
private static boolean isWebappClassLoader(ClassLoader classLoader) {
return !CLASSLOADER_HIERARCHY.contains(classLoader);
}
/*
* Scan a URL for JARs with the optional extensions to look at all files
* and all directories.
*/
protected void process(JarScanType scanType, JarScannerCallback callback,
URL url, String webappPath, boolean isWebapp, Deque<URL> classPathUrlsToProcess)
throws IOException {
if (log.isTraceEnabled()) {
log.trace(sm.getString("jarScan.jarUrlStart", url));
}
if ("jar".equals(url.getProtocol()) || url.getPath().endsWith(Constants.JAR_EXT)) {
try (Jar jar = JarFactory.newInstance(url)) {
if (isScanManifest()) {
processManifest(jar, isWebapp, classPathUrlsToProcess);
}
callback.scan(jar, webappPath, isWebapp);
}
} else if ("file".equals(url.getProtocol())) {
File f;
try {
f = new File(url.toURI());
if (f.isFile() && isScanAllFiles()) {
// Treat this file as a JAR
URL jarURL = UriUtil.buildJarUrl(f);
try (Jar jar = JarFactory.newInstance(jarURL)) {
if (isScanManifest()) {
processManifest(jar, isWebapp, classPathUrlsToProcess);
}
callback.scan(jar, webappPath, isWebapp);
}
} else if (f.isDirectory()) {
if (scanType == JarScanType.PLUGGABILITY) {
callback.scan(f, webappPath, isWebapp);
} else {
File metainf = new File(f.getAbsoluteFile() + File.separator + "META-INF");
if (metainf.isDirectory()) {
callback.scan(f, webappPath, isWebapp);
}
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// Wrap the exception and re-throw
IOException ioe = new IOException();
ioe.initCause(t);
throw ioe;
}
}
}
private void processManifest(Jar jar, boolean isWebapp,
Deque<URL> classPathUrlsToProcess) throws IOException {
// Not processed for web application JARs nor if the caller did not
// provide a Deque of URLs to append to.
if (isWebapp || classPathUrlsToProcess == null) {
return;
}
Manifest manifest = jar.getManifest();
if (manifest != null) {
Attributes attributes = manifest.getMainAttributes();
String classPathAttribute = attributes.getValue("Class-Path");
if (classPathAttribute == null) {
return;
}
String[] classPathEntries = classPathAttribute.split(" ");
for (String classPathEntry : classPathEntries) {
classPathEntry = classPathEntry.trim();
if (classPathEntry.length() == 0) {
continue;
}
URL jarURL = jar.getJarFileURL();
URL classPathEntryURL;
try {
URI jarURI = jarURL.toURI();
/*
* Note: Resolving the relative URLs from the manifest has the
* potential to introduce security concerns. However, since
* only JARs provided by the container and NOT those provided
* by web applications are processed, there should be no
* issues.
* If this feature is ever extended to include JARs provided
* by web applications, checks should be added to ensure that
* any relative URL does not step outside the web application.
*/
URI classPathEntryURI = jarURI.resolve(classPathEntry);
classPathEntryURL = classPathEntryURI.toURL();
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("jarScan.invalidUri", jarURL), e);
}
continue;
}
classPathUrlsToProcess.add(classPathEntryURL);
}
}
}
private static class ClassPathEntry {
private final boolean jar;
private final String name;
public ClassPathEntry(URL url) {
String path = url.getPath();
int end = path.lastIndexOf(Constants.JAR_EXT);
if (end != -1) {
jar = true;
int start = path.lastIndexOf('/', end);
name = path.substring(start + 1, end + 4);
} else {
jar = false;
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
int start = path.lastIndexOf('/');
name = path.substring(start + 1);
}
}
public boolean isJar() {
return jar;
}
public String getName() {
return name;
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tomcat.util.scan;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
/**
* Implementation of {@link org.apache.tomcat.Jar} that is optimised for
* non-file based JAR URLs.
*/
public class UrlJar extends AbstractInputStreamJar {
public UrlJar(URL jarFileURL) {
super(jarFileURL);
}
@Override
public void close() {
closeStream();
}
@Override
protected NonClosingJarInputStream createJarInputStream() throws IOException {
JarURLConnection jarConn = (JarURLConnection) getJarFileURL().openConnection();
URL resourceURL = jarConn.getJarFileURL();
URLConnection resourceConn = resourceURL.openConnection();
resourceConn.setUseCaches(false);
return new NonClosingJarInputStream(resourceConn.getInputStream());
}
}

View File

@@ -0,0 +1,27 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
</head>
<body bgcolor="white">
<p>
This package contains the common classes used to perform configuration scanning
for Catalina and Jasper.
</p>
</body>
</html>