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,315 @@
/*
* 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.catalina.webresources;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;
public abstract class AbstractArchiveResource extends AbstractResource {
private final AbstractArchiveResourceSet archiveResourceSet;
private final String baseUrl;
private final JarEntry resource;
private final String codeBaseUrl;
private final String name;
private boolean readCerts = false;
private Certificate[] certificates;
protected AbstractArchiveResource(AbstractArchiveResourceSet archiveResourceSet,
String webAppPath, String baseUrl, JarEntry jarEntry, String codeBaseUrl) {
super(archiveResourceSet.getRoot(), webAppPath);
this.archiveResourceSet = archiveResourceSet;
this.baseUrl = baseUrl;
this.resource = jarEntry;
this.codeBaseUrl = codeBaseUrl;
String resourceName = resource.getName();
if (resourceName.charAt(resourceName.length() - 1) == '/') {
resourceName = resourceName.substring(0, resourceName.length() - 1);
}
String internalPath = archiveResourceSet.getInternalPath();
if (internalPath.length() > 0 && resourceName.equals(
internalPath.subSequence(1, internalPath.length()))) {
name = "";
} else {
int index = resourceName.lastIndexOf('/');
if (index == -1) {
name = resourceName;
} else {
name = resourceName.substring(index + 1);
}
}
}
protected AbstractArchiveResourceSet getArchiveResourceSet() {
return archiveResourceSet;
}
protected final String getBase() {
return archiveResourceSet.getBase();
}
protected final String getBaseUrl() {
return baseUrl;
}
protected final JarEntry getResource() {
return resource;
}
@Override
public long getLastModified() {
return resource.getTime();
}
@Override
public boolean exists() {
return true;
}
@Override
public boolean isVirtual() {
return false;
}
@Override
public boolean isDirectory() {
return resource.isDirectory();
}
@Override
public boolean isFile() {
return !resource.isDirectory();
}
@Override
public boolean delete() {
return false;
}
@Override
public String getName() {
return name;
}
@Override
public long getContentLength() {
if (isDirectory()) {
return -1;
}
return resource.getSize();
}
@Override
public String getCanonicalPath() {
return null;
}
@Override
public boolean canRead() {
return true;
}
@Override
public long getCreation() {
return resource.getTime();
}
@Override
public URL getURL() {
String url = baseUrl + resource.getName();
try {
return new URL(url);
} catch (MalformedURLException e) {
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("fileResource.getUrlFail", url), e);
}
return null;
}
}
@Override
public URL getCodeBase() {
try {
return new URL(codeBaseUrl);
} catch (MalformedURLException e) {
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("fileResource.getUrlFail", codeBaseUrl), e);
}
return null;
}
}
@Override
public final byte[] getContent() {
long len = getContentLength();
if (len > Integer.MAX_VALUE) {
// Can't create an array that big
throw new ArrayIndexOutOfBoundsException(sm.getString(
"abstractResource.getContentTooLarge", getWebappPath(),
Long.valueOf(len)));
}
if (len < 0) {
// Content is not applicable here (e.g. is a directory)
return null;
}
int size = (int) len;
byte[] result = new byte[size];
int pos = 0;
try (JarInputStreamWrapper jisw = getJarInputStreamWrapper()) {
if (jisw == null) {
// An error occurred, don't return corrupted content
return null;
}
while (pos < size) {
int n = jisw.read(result, pos, size - pos);
if (n < 0) {
break;
}
pos += n;
}
// Once the stream has been read, read the certs
certificates = jisw.getCertificates();
readCerts = true;
} catch (IOException ioe) {
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("abstractResource.getContentFail",
getWebappPath()), ioe);
}
// Don't return corrupted content
return null;
}
return result;
}
@Override
public Certificate[] getCertificates() {
if (!readCerts) {
// TODO - get content first
throw new IllegalStateException();
}
return certificates;
}
@Override
public Manifest getManifest() {
return archiveResourceSet.getManifest();
}
@Override
protected final InputStream doGetInputStream() {
if (isDirectory()) {
return null;
}
return getJarInputStreamWrapper();
}
protected abstract JarInputStreamWrapper getJarInputStreamWrapper();
/**
* This wrapper assumes that the InputStream was created from a JarFile
* obtained from a call to getArchiveResourceSet().openJarFile(). If this is
* not the case then the usage counting in AbstractArchiveResourceSet will
* break and the JarFile may be unexpectedly closed.
*/
protected class JarInputStreamWrapper extends InputStream {
private final JarEntry jarEntry;
private final InputStream is;
private final AtomicBoolean closed = new AtomicBoolean(false);
public JarInputStreamWrapper(JarEntry jarEntry, InputStream is) {
this.jarEntry = jarEntry;
this.is = is;
}
@Override
public int read() throws IOException {
return is.read();
}
@Override
public int read(byte[] b) throws IOException {
return is.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return is.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
return is.skip(n);
}
@Override
public int available() throws IOException {
return is.available();
}
@Override
public void close() throws IOException {
if (closed.compareAndSet(false, true)) {
// Must only call this once else the usage counting will break
archiveResourceSet.closeJarFile();
}
is.close();
}
@Override
public synchronized void mark(int readlimit) {
is.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
is.reset();
}
@Override
public boolean markSupported() {
return is.markSupported();
}
public Certificate[] getCertificates() {
return jarEntry.getCertificates();
}
}
}

View File

@@ -0,0 +1,335 @@
/*
* 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.catalina.webresources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.util.ResourceSet;
import org.apache.tomcat.util.compat.JreCompat;
public abstract class AbstractArchiveResourceSet extends AbstractResourceSet {
private URL baseUrl;
private String baseUrlString;
private JarFile archive = null;
protected HashMap<String,JarEntry> archiveEntries = null;
protected final Object archiveLock = new Object();
private long archiveUseCount = 0;
protected final void setBaseUrl(URL baseUrl) {
this.baseUrl = baseUrl;
if (baseUrl == null) {
this.baseUrlString = null;
} else {
this.baseUrlString = baseUrl.toString();
}
}
@Override
public final URL getBaseUrl() {
return baseUrl;
}
protected final String getBaseUrlString() {
return baseUrlString;
}
@Override
public final String[] list(String path) {
checkPath(path);
String webAppMount = getWebAppMount();
ArrayList<String> result = new ArrayList<>();
if (path.startsWith(webAppMount)) {
String pathInJar =
getInternalPath() + path.substring(webAppMount.length());
// Always strip off the leading '/' to get the JAR path
if (pathInJar.length() > 0 && pathInJar.charAt(0) == '/') {
pathInJar = pathInJar.substring(1);
}
for (String name : getArchiveEntries(false).keySet()) {
if (name.length() > pathInJar.length() &&
name.startsWith(pathInJar)) {
if (name.charAt(name.length() - 1) == '/') {
name = name.substring(
pathInJar.length(), name.length() - 1);
} else {
name = name.substring(pathInJar.length());
}
if (name.length() == 0) {
continue;
}
if (name.charAt(0) == '/') {
name = name.substring(1);
}
if (name.length() > 0 && name.lastIndexOf('/') == -1) {
result.add(name);
}
}
}
} else {
if (!path.endsWith("/")) {
path = path + "/";
}
if (webAppMount.startsWith(path)) {
int i = webAppMount.indexOf('/', path.length());
if (i == -1) {
return new String[] {webAppMount.substring(path.length())};
} else {
return new String[] {
webAppMount.substring(path.length(), i)};
}
}
}
return result.toArray(new String[result.size()]);
}
@Override
public final Set<String> listWebAppPaths(String path) {
checkPath(path);
String webAppMount = getWebAppMount();
ResourceSet<String> result = new ResourceSet<>();
if (path.startsWith(webAppMount)) {
String pathInJar =
getInternalPath() + path.substring(webAppMount.length());
// Always strip off the leading '/' to get the JAR path and make
// sure it ends in '/'
if (pathInJar.length() > 0) {
if (pathInJar.charAt(pathInJar.length() - 1) != '/') {
pathInJar = pathInJar.substring(1) + '/';
}
if (pathInJar.charAt(0) == '/') {
pathInJar = pathInJar.substring(1);
}
}
for (String name : getArchiveEntries(false).keySet()) {
if (name.length() > pathInJar.length() && name.startsWith(pathInJar)) {
int nextSlash = name.indexOf('/', pathInJar.length());
if (nextSlash != -1 && nextSlash != name.length() - 1) {
name = name.substring(0, nextSlash + 1);
}
result.add(webAppMount + '/' + name.substring(getInternalPath().length()));
}
}
} else {
if (!path.endsWith("/")) {
path = path + "/";
}
if (webAppMount.startsWith(path)) {
int i = webAppMount.indexOf('/', path.length());
if (i == -1) {
result.add(webAppMount + "/");
} else {
result.add(webAppMount.substring(0, i + 1));
}
}
}
result.setLocked(true);
return result;
}
/**
* Obtain the map of entries in the archive. May return null in which case
* {@link #getArchiveEntry(String)} should be used.
*
* @param single Is this request being make to support a single lookup? If
* false, a map will always be returned. If true,
* implementations may use this as a hint in determining the
* optimum way to respond.
*
* @return The archives entries mapped to their names or null if
* {@link #getArchiveEntry(String)} should be used.
*/
protected abstract HashMap<String,JarEntry> getArchiveEntries(boolean single);
/**
* Obtain a single entry from the archive. For performance reasons,
* {@link #getArchiveEntries(boolean)} should always be called first and the
* archive entry looked up in the map if one is returned. Only if that call
* returns null should this method be used.
*
* @param pathInArchive The path in the archive of the entry required
*
* @return The specified archive entry or null if it does not exist
*/
protected abstract JarEntry getArchiveEntry(String pathInArchive);
@Override
public final boolean mkdir(String path) {
checkPath(path);
return false;
}
@Override
public final boolean write(String path, InputStream is, boolean overwrite) {
checkPath(path);
if (is == null) {
throw new NullPointerException(
sm.getString("dirResourceSet.writeNpe"));
}
return false;
}
@Override
public final WebResource getResource(String path) {
checkPath(path);
String webAppMount = getWebAppMount();
WebResourceRoot root = getRoot();
/*
* Implementation notes
*
* The path parameter passed into this method always starts with '/'.
*
* The path parameter passed into this method may or may not end with a
* '/'. JarFile.getEntry() will return a matching directory entry
* whether or not the name ends in a '/'. However, if the entry is
* requested without the '/' subsequent calls to JarEntry.isDirectory()
* will return false.
*
* Paths in JARs never start with '/'. Leading '/' need to be removed
* before any JarFile.getEntry() call.
*/
// If the JAR has been mounted below the web application root, return
// an empty resource for requests outside of the mount point.
if (path.startsWith(webAppMount)) {
String pathInJar = getInternalPath() + path.substring(
webAppMount.length(), path.length());
// Always strip off the leading '/' to get the JAR path
if (pathInJar.length() > 0 && pathInJar.charAt(0) == '/') {
pathInJar = pathInJar.substring(1);
}
if (pathInJar.equals("")) {
// Special case
// This is a directory resource so the path must end with /
if (!path.endsWith("/")) {
path = path + "/";
}
return new JarResourceRoot(root, new File(getBase()),
baseUrlString, path);
} else {
JarEntry jarEntry = null;
if (isMultiRelease()) {
// Calls JarFile.getJarEntry() which is multi-release aware
jarEntry = getArchiveEntry(pathInJar);
} else {
Map<String,JarEntry> jarEntries = getArchiveEntries(true);
if (!(pathInJar.charAt(pathInJar.length() - 1) == '/')) {
if (jarEntries == null) {
jarEntry = getArchiveEntry(pathInJar + '/');
} else {
jarEntry = jarEntries.get(pathInJar + '/');
}
if (jarEntry != null) {
path = path + '/';
}
}
if (jarEntry == null) {
if (jarEntries == null) {
jarEntry = getArchiveEntry(pathInJar);
} else {
jarEntry = jarEntries.get(pathInJar);
}
}
}
if (jarEntry == null) {
return new EmptyResource(root, path);
} else {
return createArchiveResource(jarEntry, path, getManifest());
}
}
} else {
return new EmptyResource(root, path);
}
}
protected abstract boolean isMultiRelease();
protected abstract WebResource createArchiveResource(JarEntry jarEntry,
String webAppPath, Manifest manifest);
@Override
public final boolean isReadOnly() {
return true;
}
@Override
public void setReadOnly(boolean readOnly) {
if (readOnly) {
// This is the hard-coded default - ignore the call
return;
}
throw new IllegalArgumentException(
sm.getString("abstractArchiveResourceSet.setReadOnlyFalse"));
}
protected JarFile openJarFile() throws IOException {
synchronized (archiveLock) {
if (archive == null) {
archive = JreCompat.getInstance().jarFileNewInstance(getBase());
}
archiveUseCount++;
return archive;
}
}
protected void closeJarFile() {
synchronized (archiveLock) {
archiveUseCount--;
}
}
@Override
public void gc() {
synchronized (archiveLock) {
if (archive != null && archiveUseCount == 0) {
try {
archive.close();
} catch (IOException e) {
// Log at least WARN
}
archive = null;
archiveEntries = null;
}
}
}
}

View File

@@ -0,0 +1,219 @@
/*
* 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.catalina.webresources;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.catalina.LifecycleException;
import org.apache.tomcat.util.compat.JrePlatform;
import org.apache.tomcat.util.http.RequestUtil;
public abstract class AbstractFileResourceSet extends AbstractResourceSet {
protected static final String[] EMPTY_STRING_ARRAY = new String[0];
private File fileBase;
private String absoluteBase;
private String canonicalBase;
private boolean readOnly = false;
protected AbstractFileResourceSet(String internalPath) {
setInternalPath(internalPath);
}
protected final File getFileBase() {
return fileBase;
}
@Override
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
@Override
public boolean isReadOnly() {
return readOnly;
}
protected final File file(String name, boolean mustExist) {
if (name.equals("/")) {
name = "";
}
File file = new File(fileBase, name);
// If the requested names ends in '/', the Java File API will return a
// matching file if one exists. This isn't what we want as it is not
// consistent with the Servlet spec rules for request mapping.
if (name.endsWith("/") && file.isFile()) {
return null;
}
// If the file/dir must exist but the identified file/dir can't be read
// then signal that the resource was not found
if (mustExist && !file.canRead()) {
return null;
}
// If allow linking is enabled, files are not limited to being located
// under the fileBase so all further checks are disabled.
if (getRoot().getAllowLinking()) {
return file;
}
// Additional Windows specific checks to handle known problems with
// File.getCanonicalPath()
if (JrePlatform.IS_WINDOWS && isInvalidWindowsFilename(name)) {
return null;
}
// Check that this file is located under the WebResourceSet's base
String canPath = null;
try {
canPath = file.getCanonicalPath();
} catch (IOException e) {
// Ignore
}
if (canPath == null || !canPath.startsWith(canonicalBase)) {
return null;
}
// Ensure that the file is not outside the fileBase. This should not be
// possible for standard requests (the request is normalized early in
// the request processing) but might be possible for some access via the
// Servlet API (RequestDispatcher, HTTP/2 push etc.) therefore these
// checks are retained as an additional safety measure
// absoluteBase has been normalized so absPath needs to be normalized as
// well.
String absPath = normalize(file.getAbsolutePath());
if (absoluteBase.length() > absPath.length()) {
return null;
}
// Remove the fileBase location from the start of the paths since that
// was not part of the requested path and the remaining check only
// applies to the request path
absPath = absPath.substring(absoluteBase.length());
canPath = canPath.substring(canonicalBase.length());
// Case sensitivity check
// The normalized requested path should be an exact match the equivalent
// canonical path. If it is not, possible reasons include:
// - case differences on case insensitive file systems
// - Windows removing a trailing ' ' or '.' from the file name
//
// In all cases, a mis-match here results in the resource not being
// found
//
// absPath is normalized so canPath needs to be normalized as well
// Can't normalize canPath earlier as canonicalBase is not normalized
if (canPath.length() > 0) {
canPath = normalize(canPath);
}
if (!canPath.equals(absPath)) {
return null;
}
return file;
}
private boolean isInvalidWindowsFilename(String name) {
final int len = name.length();
if (len == 0) {
return false;
}
// This consistently ~10 times faster than the equivalent regular
// expression irrespective of input length.
for (int i = 0; i < len; i++) {
char c = name.charAt(i);
if (c == '\"' || c == '<' || c == '>') {
// These characters are disallowed in Windows file names and
// there are known problems for file names with these characters
// when using File#getCanonicalPath().
// Note: There are additional characters that are disallowed in
// Windows file names but these are not known to cause
// problems when using File#getCanonicalPath().
return true;
}
}
// Windows does not allow file names to end in ' ' unless specific low
// level APIs are used to create the files that bypass various checks.
// File names that end in ' ' are known to cause problems when using
// File#getCanonicalPath().
if (name.charAt(len -1) == ' ') {
return true;
}
return false;
}
/**
* Return a context-relative path, beginning with a "/", that represents
* the canonical version of the specified path after ".." and "." elements
* are resolved out. If the specified path attempts to go outside the
* boundaries of the current context (i.e. too many ".." path elements
* are present), return <code>null</code> instead.
*
* @param path Path to be normalized
*/
private String normalize(String path) {
return RequestUtil.normalize(path, File.separatorChar == '\\');
}
@Override
public URL getBaseUrl() {
try {
return getFileBase().toURI().toURL();
} catch (MalformedURLException e) {
return null;
}
}
/**
* {@inheritDoc}
* <p>
* This is a NO-OP by default for File based resource sets.
*/
@Override
public void gc() {
// NO-OP
}
//-------------------------------------------------------- Lifecycle methods
@Override
protected void initInternal() throws LifecycleException {
fileBase = new File(getBase(), getInternalPath());
checkType(fileBase);
this.absoluteBase = normalize(fileBase.getAbsolutePath());
try {
this.canonicalBase = fileBase.getCanonicalPath();
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
protected abstract void checkType(File file);
}

View File

@@ -0,0 +1,106 @@
/*
* 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.catalina.webresources;
import java.io.InputStream;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.juli.logging.Log;
import org.apache.tomcat.util.http.FastHttpDateFormat;
import org.apache.tomcat.util.res.StringManager;
public abstract class AbstractResource implements WebResource {
protected static final StringManager sm = StringManager.getManager(AbstractResource.class);
private final WebResourceRoot root;
private final String webAppPath;
private String mimeType = null;
private volatile String weakETag;
protected AbstractResource(WebResourceRoot root, String webAppPath) {
this.root = root;
this.webAppPath = webAppPath;
}
@Override
public final WebResourceRoot getWebResourceRoot() {
return root;
}
@Override
public final String getWebappPath() {
return webAppPath;
}
@Override
public final String getLastModifiedHttp() {
return FastHttpDateFormat.formatDate(getLastModified());
}
@Override
public final String getETag() {
if (weakETag == null) {
synchronized (this) {
if (weakETag == null) {
long contentLength = getContentLength();
long lastModified = getLastModified();
if ((contentLength >= 0) || (lastModified >= 0)) {
weakETag = "W/\"" + contentLength + "-" +
lastModified + "\"";
}
}
}
}
return weakETag;
}
@Override
public final void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
@Override
public final String getMimeType() {
return mimeType;
}
@Override
public final InputStream getInputStream() {
InputStream is = doGetInputStream();
if (is == null || !root.getTrackLockedFiles()) {
return is;
}
return new TrackedInputStream(root, getName(), is);
}
protected abstract InputStream doGetInputStream();
protected abstract Log getLog();
}

View File

@@ -0,0 +1,140 @@
/*
* 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.catalina.webresources;
import java.util.jar.Manifest;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.WebResourceSet;
import org.apache.catalina.util.LifecycleBase;
import org.apache.tomcat.util.res.StringManager;
public abstract class AbstractResourceSet extends LifecycleBase
implements WebResourceSet {
private WebResourceRoot root;
private String base;
private String internalPath = "";
private String webAppMount;
private boolean classLoaderOnly;
private boolean staticOnly;
private Manifest manifest;
protected static final StringManager sm = StringManager.getManager(AbstractResourceSet.class);
protected final void checkPath(String path) {
if (path == null || path.length() == 0 || path.charAt(0) != '/') {
throw new IllegalArgumentException(
sm.getString("abstractResourceSet.checkPath", path));
}
}
@Override
public final void setRoot(WebResourceRoot root) {
this.root = root;
}
protected final WebResourceRoot getRoot() {
return root;
}
protected final String getInternalPath() {
return internalPath;
}
public final void setInternalPath(String internalPath) {
checkPath(internalPath);
// Optimise internal processing
if (internalPath.equals("/")) {
this.internalPath = "";
} else {
this.internalPath = internalPath;
}
}
public final void setWebAppMount(String webAppMount) {
checkPath(webAppMount);
// Optimise internal processing
if (webAppMount.equals("/")) {
this.webAppMount = "";
} else {
this.webAppMount = webAppMount;
}
}
protected final String getWebAppMount() {
return webAppMount;
}
public final void setBase(String base) {
this.base = base;
}
protected final String getBase() {
return base;
}
@Override
public boolean getClassLoaderOnly() {
return classLoaderOnly;
}
@Override
public void setClassLoaderOnly(boolean classLoaderOnly) {
this.classLoaderOnly = classLoaderOnly;
}
@Override
public boolean getStaticOnly() {
return staticOnly;
}
@Override
public void setStaticOnly(boolean staticOnly) {
this.staticOnly = staticOnly;
}
protected final void setManifest(Manifest manifest) {
this.manifest = manifest;
}
protected final Manifest getManifest() {
return manifest;
}
//-------------------------------------------------------- Lifecycle methods
@Override
protected final void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
}
@Override
protected final void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
}
@Override
protected final void destroyInternal() throws LifecycleException {
gc();
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.catalina.webresources;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public abstract class AbstractSingleArchiveResource extends AbstractArchiveResource {
protected AbstractSingleArchiveResource(AbstractArchiveResourceSet archiveResourceSet, String webAppPath,
String baseUrl, JarEntry jarEntry, String codeBaseUrl) {
super(archiveResourceSet, webAppPath, baseUrl, jarEntry, codeBaseUrl);
}
@Override
protected JarInputStreamWrapper getJarInputStreamWrapper() {
JarFile jarFile = null;
try {
jarFile = getArchiveResourceSet().openJarFile();
// Need to create a new JarEntry so the certificates can be read
JarEntry jarEntry = jarFile.getJarEntry(getResource().getName());
InputStream is = jarFile.getInputStream(jarEntry);
return new JarInputStreamWrapper(jarEntry, is);
} catch (IOException e) {
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("jarResource.getInputStreamFail",
getResource().getName(), getBaseUrl()), e);
}
if (jarFile != null) {
getArchiveResourceSet().closeJarFile();
}
return null;
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* 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.catalina.webresources;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.WebResourceRoot;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.compat.JreCompat;
/**
* Base class for a {@link org.apache.catalina.WebResourceSet} based on a
* single, rather than nested, archive.
*/
public abstract class AbstractSingleArchiveResourceSet extends AbstractArchiveResourceSet {
private volatile Boolean multiRelease;
/**
* A no argument constructor is required for this to work with the digester.
*/
public AbstractSingleArchiveResourceSet() {
}
public AbstractSingleArchiveResourceSet(WebResourceRoot root, String webAppMount, String base,
String internalPath) throws IllegalArgumentException {
setRoot(root);
setWebAppMount(webAppMount);
setBase(base);
setInternalPath(internalPath);
if (getRoot().getState().isAvailable()) {
try {
start();
} catch (LifecycleException e) {
throw new IllegalStateException(e);
}
}
}
@Override
protected HashMap<String,JarEntry> getArchiveEntries(boolean single) {
synchronized (archiveLock) {
if (archiveEntries == null && !single) {
JarFile jarFile = null;
archiveEntries = new HashMap<>();
try {
jarFile = openJarFile();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
archiveEntries.put(entry.getName(), entry);
}
} catch (IOException ioe) {
// Should never happen
archiveEntries = null;
throw new IllegalStateException(ioe);
} finally {
if (jarFile != null) {
closeJarFile();
}
}
}
return archiveEntries;
}
}
@Override
protected JarEntry getArchiveEntry(String pathInArchive) {
JarFile jarFile = null;
try {
jarFile = openJarFile();
return jarFile.getJarEntry(pathInArchive);
} catch (IOException ioe) {
// Should never happen
throw new IllegalStateException(ioe);
} finally {
if (jarFile != null) {
closeJarFile();
}
}
}
@Override
protected boolean isMultiRelease() {
if (multiRelease == null) {
synchronized (archiveLock) {
if (multiRelease == null) {
JarFile jarFile = null;
try {
jarFile = openJarFile();
multiRelease = Boolean.valueOf(
JreCompat.getInstance().jarFileIsMultiRelease(jarFile));
} catch (IOException ioe) {
// Should never happen
throw new IllegalStateException(ioe);
} finally {
if (jarFile != null) {
closeJarFile();
}
}
}
}
}
return multiRelease.booleanValue();
}
//-------------------------------------------------------- Lifecycle methods
@Override
protected void initInternal() throws LifecycleException {
try (JarFile jarFile = JreCompat.getInstance().jarFileNewInstance(getBase())) {
setManifest(jarFile.getManifest());
} catch (IOException ioe) {
throw new IllegalArgumentException(ioe);
}
try {
setBaseUrl(UriUtil.buildJarSafeUrl(new File(getBase())));
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}
}

View File

@@ -0,0 +1,329 @@
/*
* 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.catalina.webresources;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.catalina.WebResource;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
public class Cache {
private static final Log log = LogFactory.getLog(Cache.class);
protected static final StringManager sm = StringManager.getManager(Cache.class);
private static final long TARGET_FREE_PERCENT_GET = 5;
private static final long TARGET_FREE_PERCENT_BACKGROUND = 10;
// objectMaxSize must be < maxSize/20
private static final int OBJECT_MAX_SIZE_FACTOR = 20;
private final StandardRoot root;
private final AtomicLong size = new AtomicLong(0);
private long ttl = 5000;
private long maxSize = 10 * 1024 * 1024;
private int objectMaxSize = (int) maxSize/OBJECT_MAX_SIZE_FACTOR;
private AtomicLong lookupCount = new AtomicLong(0);
private AtomicLong hitCount = new AtomicLong(0);
private final ConcurrentMap<String,CachedResource> resourceCache =
new ConcurrentHashMap<>();
public Cache(StandardRoot root) {
this.root = root;
}
protected WebResource getResource(String path, boolean useClassLoaderResources) {
if (noCache(path)) {
return root.getResourceInternal(path, useClassLoaderResources);
}
lookupCount.incrementAndGet();
CachedResource cacheEntry = resourceCache.get(path);
if (cacheEntry != null && !cacheEntry.validateResource(useClassLoaderResources)) {
removeCacheEntry(path);
cacheEntry = null;
}
if (cacheEntry == null) {
// Local copy to ensure consistency
int objectMaxSizeBytes = getObjectMaxSizeBytes();
CachedResource newCacheEntry = new CachedResource(this, root, path, getTtl(),
objectMaxSizeBytes, useClassLoaderResources);
// Concurrent callers will end up with the same CachedResource
// instance
cacheEntry = resourceCache.putIfAbsent(path, newCacheEntry);
if (cacheEntry == null) {
// newCacheEntry was inserted into the cache - validate it
cacheEntry = newCacheEntry;
cacheEntry.validateResource(useClassLoaderResources);
// Even if the resource content larger than objectMaxSizeBytes
// there is still benefit in caching the resource metadata
long delta = cacheEntry.getSize();
size.addAndGet(delta);
if (size.get() > maxSize) {
// Process resources unordered for speed. Trades cache
// efficiency (younger entries may be evicted before older
// ones) for speed since this is on the critical path for
// request processing
long targetSize = maxSize * (100 - TARGET_FREE_PERCENT_GET) / 100;
long newSize = evict(targetSize, resourceCache.values().iterator());
if (newSize > maxSize) {
// Unable to create sufficient space for this resource
// Remove it from the cache
removeCacheEntry(path);
log.warn(sm.getString("cache.addFail", path, root.getContext().getName()));
}
}
} else {
// Another thread added the entry to the cache
// Make sure it is validated
cacheEntry.validateResource(useClassLoaderResources);
}
} else {
hitCount.incrementAndGet();
}
return cacheEntry;
}
protected WebResource[] getResources(String path, boolean useClassLoaderResources) {
lookupCount.incrementAndGet();
// Don't call noCache(path) since the class loader only caches
// individual resources. Therefore, always cache collections here
CachedResource cacheEntry = resourceCache.get(path);
if (cacheEntry != null && !cacheEntry.validateResources(useClassLoaderResources)) {
removeCacheEntry(path);
cacheEntry = null;
}
if (cacheEntry == null) {
// Local copy to ensure consistency
int objectMaxSizeBytes = getObjectMaxSizeBytes();
CachedResource newCacheEntry = new CachedResource(this, root, path, getTtl(),
objectMaxSizeBytes, useClassLoaderResources);
// Concurrent callers will end up with the same CachedResource
// instance
cacheEntry = resourceCache.putIfAbsent(path, newCacheEntry);
if (cacheEntry == null) {
// newCacheEntry was inserted into the cache - validate it
cacheEntry = newCacheEntry;
cacheEntry.validateResources(useClassLoaderResources);
// Content will not be cached but we still need metadata size
long delta = cacheEntry.getSize();
size.addAndGet(delta);
if (size.get() > maxSize) {
// Process resources unordered for speed. Trades cache
// efficiency (younger entries may be evicted before older
// ones) for speed since this is on the critical path for
// request processing
long targetSize = maxSize * (100 - TARGET_FREE_PERCENT_GET) / 100;
long newSize = evict(targetSize, resourceCache.values().iterator());
if (newSize > maxSize) {
// Unable to create sufficient space for this resource
// Remove it from the cache
removeCacheEntry(path);
log.warn(sm.getString("cache.addFail", path));
}
}
} else {
// Another thread added the entry to the cache
// Make sure it is validated
cacheEntry.validateResources(useClassLoaderResources);
}
} else {
hitCount.incrementAndGet();
}
return cacheEntry.getWebResources();
}
protected void backgroundProcess() {
// Create an ordered set of all cached resources with the least recently
// used first. This is a background process so we can afford to take the
// time to order the elements first
TreeSet<CachedResource> orderedResources =
new TreeSet<>(new EvictionOrder());
orderedResources.addAll(resourceCache.values());
Iterator<CachedResource> iter = orderedResources.iterator();
long targetSize =
maxSize * (100 - TARGET_FREE_PERCENT_BACKGROUND) / 100;
long newSize = evict(targetSize, iter);
if (newSize > targetSize) {
log.info(sm.getString("cache.backgroundEvictFail",
Long.valueOf(TARGET_FREE_PERCENT_BACKGROUND),
root.getContext().getName(),
Long.valueOf(newSize / 1024)));
}
}
private boolean noCache(String path) {
// Don't cache classes. The class loader handles this.
// Don't cache JARs. The ResourceSet handles this.
if ((path.endsWith(".class") &&
(path.startsWith("/WEB-INF/classes/") || path.startsWith("/WEB-INF/lib/")))
||
(path.startsWith("/WEB-INF/lib/") && path.endsWith(".jar"))) {
return true;
}
return false;
}
private long evict(long targetSize, Iterator<CachedResource> iter) {
long now = System.currentTimeMillis();
long newSize = size.get();
while (newSize > targetSize && iter.hasNext()) {
CachedResource resource = iter.next();
// Don't expire anything that has been checked within the TTL
if (resource.getNextCheck() > now) {
continue;
}
// Remove the entry from the cache
removeCacheEntry(resource.getWebappPath());
newSize = size.get();
}
return newSize;
}
void removeCacheEntry(String path) {
// With concurrent calls for the same path, the entry is only removed
// once and the cache size is only updated (if required) once.
CachedResource cachedResource = resourceCache.remove(path);
if (cachedResource != null) {
long delta = cachedResource.getSize();
size.addAndGet(-delta);
}
}
public long getTtl() {
return ttl;
}
public void setTtl(long ttl) {
this.ttl = ttl;
}
public long getMaxSize() {
// Internally bytes, externally kilobytes
return maxSize / 1024;
}
public void setMaxSize(long maxSize) {
// Internally bytes, externally kilobytes
this.maxSize = maxSize * 1024;
}
public long getLookupCount() {
return lookupCount.get();
}
public long getHitCount() {
return hitCount.get();
}
public void setObjectMaxSize(int objectMaxSize) {
if (objectMaxSize * 1024L > Integer.MAX_VALUE) {
log.warn(sm.getString("cache.objectMaxSizeTooBigBytes", Integer.valueOf(objectMaxSize)));
this.objectMaxSize = Integer.MAX_VALUE;
}
// Internally bytes, externally kilobytes
this.objectMaxSize = objectMaxSize * 1024;
}
public int getObjectMaxSize() {
// Internally bytes, externally kilobytes
return objectMaxSize / 1024;
}
public int getObjectMaxSizeBytes() {
return objectMaxSize;
}
void enforceObjectMaxSizeLimit() {
long limit = maxSize / OBJECT_MAX_SIZE_FACTOR;
if (limit > Integer.MAX_VALUE) {
return;
}
if (objectMaxSize > limit) {
log.warn(sm.getString("cache.objectMaxSizeTooBig",
Integer.valueOf(objectMaxSize / 1024), Integer.valueOf((int)limit / 1024)));
objectMaxSize = (int) limit;
}
}
public void clear() {
resourceCache.clear();
size.set(0);
}
public long getSize() {
return size.get() / 1024;
}
private static class EvictionOrder implements Comparator<CachedResource> {
@Override
public int compare(CachedResource cr1, CachedResource cr2) {
long nc1 = cr1.getNextCheck();
long nc2 = cr2.getNextCheck();
// Oldest resource should be first (so iterator goes from oldest to
// youngest.
if (nc1 == nc2) {
return 0;
} else if (nc1 > nc2) {
return -1;
} else {
return 1;
}
}
}
}

View File

@@ -0,0 +1,601 @@
/*
* 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.catalina.webresources;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.charset.Charset;
import java.security.Permission;
import java.security.cert.Certificate;
import java.text.Collator;
import java.util.Arrays;
import java.util.Locale;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
/**
* This class is designed to wrap a 'raw' WebResource and providing caching for
* expensive operations. Inexpensive operations may be passed through to the
* underlying resource.
*/
public class CachedResource implements WebResource {
private static final Log log = LogFactory.getLog(CachedResource.class);
private static final StringManager sm = StringManager.getManager(CachedResource.class);
// Estimate (on high side to be safe) of average size excluding content
// based on profiler data.
private static final long CACHE_ENTRY_SIZE = 500;
private final Cache cache;
private final StandardRoot root;
private final String webAppPath;
private final long ttl;
private final int objectMaxSizeBytes;
private final boolean usesClassLoaderResources;
private volatile WebResource webResource;
private volatile WebResource[] webResources;
private volatile long nextCheck;
private volatile Long cachedLastModified = null;
private volatile String cachedLastModifiedHttp = null;
private volatile byte[] cachedContent = null;
private volatile Boolean cachedIsFile = null;
private volatile Boolean cachedIsDirectory = null;
private volatile Boolean cachedExists = null;
private volatile Boolean cachedIsVirtual = null;
private volatile Long cachedContentLength = null;
public CachedResource(Cache cache, StandardRoot root, String path, long ttl,
int objectMaxSizeBytes, boolean usesClassLoaderResources) {
this.cache = cache;
this.root = root;
this.webAppPath = path;
this.ttl = ttl;
this.objectMaxSizeBytes = objectMaxSizeBytes;
this.usesClassLoaderResources = usesClassLoaderResources;
}
protected boolean validateResource(boolean useClassLoaderResources) {
// It is possible that some resources will only be visible for a given
// value of useClassLoaderResources. Therefore, if the lookup is made
// with a different value of useClassLoaderResources than was used when
// creating the cache entry, invalidate the entry. This should have
// minimal performance impact as it would be unusual for a resource to
// be looked up both as a static resource and as a class loader
// resource.
if (usesClassLoaderResources != useClassLoaderResources) {
return false;
}
long now = System.currentTimeMillis();
if (webResource == null) {
synchronized (this) {
if (webResource == null) {
webResource = root.getResourceInternal(
webAppPath, useClassLoaderResources);
getLastModified();
getContentLength();
nextCheck = ttl + now;
// exists() is a relatively expensive check for a file so
// use the fact that we know if it exists at this point
if (webResource instanceof EmptyResource) {
cachedExists = Boolean.FALSE;
} else {
cachedExists = Boolean.TRUE;
}
return true;
}
}
}
if (now < nextCheck) {
return true;
}
// Assume resources inside WARs will not change
if (!root.isPackedWarFile()) {
WebResource webResourceInternal = root.getResourceInternal(
webAppPath, useClassLoaderResources);
if (!webResource.exists() && webResourceInternal.exists()) {
return false;
}
// If modified date or length change - resource has changed / been
// removed etc.
if (webResource.getLastModified() != getLastModified() ||
webResource.getContentLength() != getContentLength()) {
return false;
}
// Has a resource been inserted / removed in a different resource set
if (webResource.getLastModified() != webResourceInternal.getLastModified() ||
webResource.getContentLength() != webResourceInternal.getContentLength()) {
return false;
}
}
nextCheck = ttl + now;
return true;
}
protected boolean validateResources(boolean useClassLoaderResources) {
long now = System.currentTimeMillis();
if (webResources == null) {
synchronized (this) {
if (webResources == null) {
webResources = root.getResourcesInternal(
webAppPath, useClassLoaderResources);
nextCheck = ttl + now;
return true;
}
}
}
if (now < nextCheck) {
return true;
}
// Assume resources inside WARs will not change
if (root.isPackedWarFile()) {
nextCheck = ttl + now;
return true;
} else {
// At this point, always expire the entry and re-populating it is
// likely to be as expensive as validating it.
return false;
}
}
protected long getNextCheck() {
return nextCheck;
}
@Override
public long getLastModified() {
Long cachedLastModified = this.cachedLastModified;
if (cachedLastModified == null) {
cachedLastModified =
Long.valueOf(webResource.getLastModified());
this.cachedLastModified = cachedLastModified;
}
return cachedLastModified.longValue();
}
@Override
public String getLastModifiedHttp() {
String cachedLastModifiedHttp = this.cachedLastModifiedHttp;
if (cachedLastModifiedHttp == null) {
cachedLastModifiedHttp = webResource.getLastModifiedHttp();
this.cachedLastModifiedHttp = cachedLastModifiedHttp;
}
return cachedLastModifiedHttp;
}
@Override
public boolean exists() {
Boolean cachedExists = this.cachedExists;
if (cachedExists == null) {
cachedExists = Boolean.valueOf(webResource.exists());
this.cachedExists = cachedExists;
}
return cachedExists.booleanValue();
}
@Override
public boolean isVirtual() {
Boolean cachedIsVirtual = this.cachedIsVirtual;
if (cachedIsVirtual == null) {
cachedIsVirtual = Boolean.valueOf(webResource.isVirtual());
this.cachedIsVirtual = cachedIsVirtual;
}
return cachedIsVirtual.booleanValue();
}
@Override
public boolean isDirectory() {
Boolean cachedIsDirectory = this.cachedIsDirectory;
if (cachedIsDirectory == null) {
cachedIsDirectory = Boolean.valueOf(webResource.isDirectory());
this.cachedIsDirectory = cachedIsDirectory;
}
return cachedIsDirectory.booleanValue();
}
@Override
public boolean isFile() {
Boolean cachedIsFile = this.cachedIsFile;
if (cachedIsFile == null) {
cachedIsFile = Boolean.valueOf(webResource.isFile());
this.cachedIsFile = cachedIsFile;
}
return cachedIsFile.booleanValue();
}
@Override
public boolean delete() {
boolean deleteResult = webResource.delete();
if (deleteResult) {
cache.removeCacheEntry(webAppPath);
}
return deleteResult;
}
@Override
public String getName() {
return webResource.getName();
}
@Override
public long getContentLength() {
Long cachedContentLength = this.cachedContentLength;
if (cachedContentLength == null) {
long result = 0;
if (webResource != null) {
result = webResource.getContentLength();
cachedContentLength = Long.valueOf(result);
this.cachedContentLength = cachedContentLength;
}
return result;
}
return cachedContentLength.longValue();
}
@Override
public String getCanonicalPath() {
return webResource.getCanonicalPath();
}
@Override
public boolean canRead() {
return webResource.canRead();
}
@Override
public String getWebappPath() {
return webAppPath;
}
@Override
public String getETag() {
return webResource.getETag();
}
@Override
public void setMimeType(String mimeType) {
webResource.setMimeType(mimeType);
}
@Override
public String getMimeType() {
return webResource.getMimeType();
}
@Override
public InputStream getInputStream() {
byte[] content = getContent();
if (content == null) {
// Can't cache InputStreams
return webResource.getInputStream();
}
return new ByteArrayInputStream(content);
}
@Override
public byte[] getContent() {
byte[] cachedContent = this.cachedContent;
if (cachedContent == null) {
if (getContentLength() > objectMaxSizeBytes) {
return null;
}
cachedContent = webResource.getContent();
this.cachedContent = cachedContent;
}
return cachedContent;
}
@Override
public long getCreation() {
return webResource.getCreation();
}
@Override
public URL getURL() {
/*
* We don't want applications using this URL to access the resource
* directly as that could lead to inconsistent results when the resource
* is updated on the file system but the cache entry has not yet
* expired. We saw this, for example, in JSP compilation.
* - last modified time was obtained via
* ServletContext.getResource("path").openConnection().getLastModified()
* - JSP content was obtained via
* ServletContext.getResourceAsStream("path")
* The result was that the JSP modification was detected but the JSP
* content was read from the cache so the non-updated JSP page was
* used to generate the .java and .class file
*
* One option to resolve this issue is to use a custom URL scheme for
* resource URLs. This would allow us, via registration of a
* URLStreamHandlerFactory, to control how the resources are accessed
* and ensure that all access go via the cache We took this approach for
* war: URLs so we can use jar:war:file: URLs to reference resources in
* unpacked WAR files. However, because URL.setURLStreamHandlerFactory()
* may only be caused once, this can cause problems when using other
* libraries that also want to use a custom URL scheme.
*
* The approach below allows us to insert a custom URLStreamHandler
* without registering a custom protocol. The only limitation (compared
* to registering a custom protocol) is that if the application
* constructs the same URL from a String, they will access the resource
* directly and not via the cache.
*/
URL resourceURL = webResource.getURL();
if (resourceURL == null) {
return null;
}
try {
CachedResourceURLStreamHandler handler =
new CachedResourceURLStreamHandler(resourceURL, root, webAppPath, usesClassLoaderResources);
URL result = new URL(null, resourceURL.toExternalForm(), handler);
handler.setAssociatedURL(result);
return result;
} catch (MalformedURLException e) {
log.error(sm.getString("cachedResource.invalidURL", resourceURL.toExternalForm()), e);
return null;
}
}
@Override
public URL getCodeBase() {
return webResource.getCodeBase();
}
@Override
public Certificate[] getCertificates() {
return webResource.getCertificates();
}
@Override
public Manifest getManifest() {
return webResource.getManifest();
}
@Override
public WebResourceRoot getWebResourceRoot() {
return webResource.getWebResourceRoot();
}
WebResource getWebResource() {
return webResource;
}
WebResource[] getWebResources() {
return webResources;
}
// Assume that the cache entry will always include the content unless the
// resource content is larger than objectMaxSizeBytes. This isn't always the
// case but it makes tracking the current cache size easier.
long getSize() {
long result = CACHE_ENTRY_SIZE;
if (getContentLength() <= objectMaxSizeBytes) {
result += getContentLength();
}
return result;
}
/*
* Mimics the behaviour of FileURLConnection.getInputStream for a directory.
* Deliberately uses default locale.
*/
private static InputStream buildInputStream(String[] files) {
Arrays.sort(files, Collator.getInstance(Locale.getDefault()));
StringBuilder result = new StringBuilder();
for (String file : files) {
result.append(file);
// Every entry is followed by \n including the last
result.append('\n');
}
return new ByteArrayInputStream(result.toString().getBytes(Charset.defaultCharset()));
}
private static class CachedResourceURLStreamHandler extends URLStreamHandler {
private final URL resourceURL;
private final StandardRoot root;
private final String webAppPath;
private final boolean usesClassLoaderResources;
private URL associatedURL = null;
public CachedResourceURLStreamHandler(URL resourceURL, StandardRoot root, String webAppPath,
boolean usesClassLoaderResources) {
this.resourceURL = resourceURL;
this.root = root;
this.webAppPath = webAppPath;
this.usesClassLoaderResources = usesClassLoaderResources;
}
protected void setAssociatedURL(URL associatedURL) {
this.associatedURL = associatedURL;
}
@Override
protected URLConnection openConnection(URL u) throws IOException {
// This deliberately uses ==. If u isn't the URL object this
// URLStreamHandler was constructed for we do not want to use this
// URLStreamHandler to create a connection.
if (associatedURL != null && u == associatedURL) {
if ("jar".equals(associatedURL.getProtocol())) {
return new CachedResourceJarURLConnection(resourceURL, root, webAppPath, usesClassLoaderResources);
} else {
return new CachedResourceURLConnection(resourceURL, root, webAppPath, usesClassLoaderResources);
}
} else {
// The stream handler has been inherited by a URL that was
// constructed from a cache URL. We need to break that link.
URL constructedURL = new URL(u.toExternalForm());
return constructedURL.openConnection();
}
}
}
/*
* Keep this in sync with CachedResourceJarURLConnection.
*/
private static class CachedResourceURLConnection extends URLConnection {
private final StandardRoot root;
private final String webAppPath;
private final boolean usesClassLoaderResources;
private final URL resourceURL;
protected CachedResourceURLConnection(URL resourceURL, StandardRoot root, String webAppPath,
boolean usesClassLoaderResources) {
super(resourceURL);
this.root = root;
this.webAppPath = webAppPath;
this.usesClassLoaderResources = usesClassLoaderResources;
this.resourceURL = resourceURL;
}
@Override
public void connect() throws IOException {
// NO-OP
}
@Override
public InputStream getInputStream() throws IOException {
WebResource resource = getResource();
if (resource.isDirectory()) {
return buildInputStream(resource.getWebResourceRoot().list(webAppPath));
} else {
return getResource().getInputStream();
}
}
@Override
public Permission getPermission() throws IOException {
// Doesn't trigger a call to connect for file:// URLs
return resourceURL.openConnection().getPermission();
}
@Override
public long getLastModified() {
return getResource().getLastModified();
}
@Override
public long getContentLengthLong() {
return getResource().getContentLength();
}
private WebResource getResource() {
return root.getResource(webAppPath, false, usesClassLoaderResources);
}
}
/*
* Keep this in sync with CachedResourceURLConnection.
*/
private static class CachedResourceJarURLConnection extends JarURLConnection {
private final StandardRoot root;
private final String webAppPath;
private final boolean usesClassLoaderResources;
private final URL resourceURL;
protected CachedResourceJarURLConnection(URL resourceURL, StandardRoot root, String webAppPath,
boolean usesClassLoaderResources) throws IOException {
super(resourceURL);
this.root = root;
this.webAppPath = webAppPath;
this.usesClassLoaderResources = usesClassLoaderResources;
this.resourceURL = resourceURL;
}
@Override
public void connect() throws IOException {
// NO-OP
}
@Override
public InputStream getInputStream() throws IOException {
WebResource resource = getResource();
if (resource.isDirectory()) {
return buildInputStream(resource.getWebResourceRoot().list(webAppPath));
} else {
return getResource().getInputStream();
}
}
@Override
public Permission getPermission() throws IOException {
// Doesn't trigger a call to connect for jar:// URLs
return resourceURL.openConnection().getPermission();
}
@Override
public long getLastModified() {
return getResource().getLastModified();
}
@Override
public long getContentLengthLong() {
return getResource().getContentLength();
}
private WebResource getResource() {
return root.getResource(webAppPath, false, usesClassLoaderResources);
}
@Override
public JarFile getJarFile() throws IOException {
return ((JarURLConnection) resourceURL.openConnection()).getJarFile();
}
@Override
public JarEntry getJarEntry() throws IOException {
if (getEntryName() == null) {
return null;
} else {
return super.getJarEntry();
}
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.catalina.webresources;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import org.apache.tomcat.util.res.StringManager;
public class ClasspathURLStreamHandler extends URLStreamHandler {
private static final StringManager sm =
StringManager.getManager(ClasspathURLStreamHandler.class);
@Override
protected URLConnection openConnection(URL u) throws IOException {
String path = u.getPath();
// Thread context class loader first
URL classpathUrl = Thread.currentThread().getContextClassLoader().getResource(path);
if (classpathUrl == null) {
// This class's class loader if no joy with the tccl
classpathUrl = ClasspathURLStreamHandler.class.getResource(path);
}
if (classpathUrl == null) {
throw new FileNotFoundException(sm.getString("classpathUrlStreamHandler.notFound", u));
}
return classpathUrl.openConnection();
}
}

View File

@@ -0,0 +1,279 @@
/*
* 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.catalina.webresources;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Set;
import java.util.jar.Manifest;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.WebResourceRoot.ResourceSetType;
import org.apache.catalina.util.ResourceSet;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Represents a {@link org.apache.catalina.WebResourceSet} based on a directory.
*/
public class DirResourceSet extends AbstractFileResourceSet {
private static final Log log = LogFactory.getLog(DirResourceSet.class);
/**
* A no argument constructor is required for this to work with the digester.
*/
public DirResourceSet() {
super("/");
}
/**
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a
* directory.
*
* @param root The {@link WebResourceRoot} this new
* {@link org.apache.catalina.WebResourceSet} will
* be added to.
* @param webAppMount The path within the web application at which this
* {@link org.apache.catalina.WebResourceSet} will
* be mounted. For example, to add a directory of
* JARs to a web application, the directory would
* be mounted at "/WEB-INF/lib/"
* @param base The absolute path to the directory on the file
* system from which the resources will be served.
* @param internalPath The path within this new {@link
* org.apache.catalina.WebResourceSet} where
* resources will be served from.
*/
public DirResourceSet(WebResourceRoot root, String webAppMount, String base,
String internalPath) {
super(internalPath);
setRoot(root);
setWebAppMount(webAppMount);
setBase(base);
if (root.getContext().getAddWebinfClassesResources()) {
File f = new File(base, internalPath);
f = new File(f, "/WEB-INF/classes/META-INF/resources");
if (f.isDirectory()) {
root.createWebResourceSet(ResourceSetType.RESOURCE_JAR, "/",
f.getAbsolutePath(), null, "/");
}
}
if (getRoot().getState().isAvailable()) {
try {
start();
} catch (LifecycleException e) {
throw new IllegalStateException(e);
}
}
}
@Override
public WebResource getResource(String path) {
checkPath(path);
String webAppMount = getWebAppMount();
WebResourceRoot root = getRoot();
if (path.startsWith(webAppMount)) {
File f = file(path.substring(webAppMount.length()), false);
if (f == null) {
return new EmptyResource(root, path);
}
if (!f.exists()) {
return new EmptyResource(root, path, f);
}
if (f.isDirectory() && path.charAt(path.length() - 1) != '/') {
path = path + '/';
}
return new FileResource(root, path, f, isReadOnly(), getManifest());
} else {
return new EmptyResource(root, path);
}
}
@Override
public String[] list(String path) {
checkPath(path);
String webAppMount = getWebAppMount();
if (path.startsWith(webAppMount)) {
File f = file(path.substring(webAppMount.length()), true);
if (f == null) {
return EMPTY_STRING_ARRAY;
}
String[] result = f.list();
if (result == null) {
return EMPTY_STRING_ARRAY;
} else {
return result;
}
} else {
if (!path.endsWith("/")) {
path = path + "/";
}
if (webAppMount.startsWith(path)) {
int i = webAppMount.indexOf('/', path.length());
if (i == -1) {
return new String[] {webAppMount.substring(path.length())};
} else {
return new String[] {
webAppMount.substring(path.length(), i)};
}
}
return EMPTY_STRING_ARRAY;
}
}
@Override
public Set<String> listWebAppPaths(String path) {
checkPath(path);
String webAppMount = getWebAppMount();
ResourceSet<String> result = new ResourceSet<>();
if (path.startsWith(webAppMount)) {
File f = file(path.substring(webAppMount.length()), true);
if (f != null) {
File[] list = f.listFiles();
if (list != null) {
for (File entry : list) {
StringBuilder sb = new StringBuilder(path);
if (path.charAt(path.length() - 1) != '/') {
sb.append('/');
}
sb.append(entry.getName());
if (entry.isDirectory()) {
sb.append('/');
}
result.add(sb.toString());
}
}
}
} else {
if (!path.endsWith("/")) {
path = path + "/";
}
if (webAppMount.startsWith(path)) {
int i = webAppMount.indexOf('/', path.length());
if (i == -1) {
result.add(webAppMount + "/");
} else {
result.add(webAppMount.substring(0, i + 1));
}
}
}
result.setLocked(true);
return result;
}
@Override
public boolean mkdir(String path) {
checkPath(path);
if (isReadOnly()) {
return false;
}
String webAppMount = getWebAppMount();
if (path.startsWith(webAppMount)) {
File f = file(path.substring(webAppMount.length()), false);
if (f == null) {
return false;
}
return f.mkdir();
} else {
return false;
}
}
@Override
public boolean write(String path, InputStream is, boolean overwrite) {
checkPath(path);
if (is == null) {
throw new NullPointerException(
sm.getString("dirResourceSet.writeNpe"));
}
if (isReadOnly()) {
return false;
}
// write() is meant to create a file so ensure that the path doesn't
// end in '/'
if (path.endsWith("/")) {
return false;
}
File dest = null;
String webAppMount = getWebAppMount();
if (path.startsWith(webAppMount)) {
dest = file(path.substring(webAppMount.length()), false);
if (dest == null) {
return false;
}
} else {
return false;
}
if (dest.exists() && !overwrite) {
return false;
}
try {
if (overwrite) {
Files.copy(is, dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
} else {
Files.copy(is, dest.toPath());
}
} catch (IOException ioe) {
return false;
}
return true;
}
@Override
protected void checkType(File file) {
if (file.isDirectory() == false) {
throw new IllegalArgumentException(sm.getString("dirResourceSet.notDirectory",
getBase(), File.separator, getInternalPath()));
}
}
//-------------------------------------------------------- Lifecycle methods
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Is this an exploded web application?
if (getWebAppMount().equals("")) {
// Look for a manifest
File mf = file("META-INF/MANIFEST.MF", true);
if (mf != null && mf.isFile()) {
try (FileInputStream fis = new FileInputStream(mf)) {
setManifest(new Manifest(fis));
} catch (IOException e) {
log.warn(sm.getString("dirResourceSet.manifestFail", mf.getAbsolutePath()), e);
}
}
}
}
}

View File

@@ -0,0 +1,172 @@
/*
* 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.catalina.webresources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.jar.Manifest;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
public class EmptyResource implements WebResource {
private final WebResourceRoot root;
private final String webAppPath;
private final File file;
public EmptyResource(WebResourceRoot root, String webAppPath) {
this(root, webAppPath, null);
}
public EmptyResource(WebResourceRoot root, String webAppPath, File file) {
this.root = root;
this.webAppPath = webAppPath;
this.file = file;
}
@Override
public long getLastModified() {
return 0;
}
@Override
public String getLastModifiedHttp() {
return null;
}
@Override
public boolean exists() {
return false;
}
@Override
public boolean isVirtual() {
return false;
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public boolean isFile() {
return false;
}
@Override
public boolean delete() {
return false;
}
@Override
public String getName() {
int index = webAppPath.lastIndexOf('/');
if (index == -1) {
return webAppPath;
} else {
return webAppPath.substring(index + 1);
}
}
@Override
public long getContentLength() {
return -1;
}
@Override
public String getCanonicalPath() {
if (file == null) {
return null;
} else {
try {
return file.getCanonicalPath();
} catch (IOException e) {
return null;
}
}
}
@Override
public boolean canRead() {
return false;
}
@Override
public String getWebappPath() {
return webAppPath;
}
@Override
public String getETag() {
return null;
}
@Override
public void setMimeType(String mimeType) {
// NOOP
}
@Override
public String getMimeType() {
return null;
}
@Override
public InputStream getInputStream() {
return null;
}
@Override
public byte[] getContent() {
return null;
}
@Override
public long getCreation() {
return 0;
}
@Override
public URL getURL() {
return null;
}
@Override
public URL getCodeBase() {
return null;
}
@Override
public Certificate[] getCertificates() {
return null;
}
@Override
public Manifest getManifest() {
return null;
}
@Override
public WebResourceRoot getWebResourceRoot() {
return root;
}
}

View File

@@ -0,0 +1,179 @@
/*
* 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.catalina.webresources;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.Set;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.WebResourceSet;
import org.apache.catalina.util.LifecycleBase;
/**
* A {@link WebResourceSet} implementation that is not backed by a file system
* and behaves as if it has no resources available. This is primarily used in
* embedded mode when the web application is configured entirely
* programmatically and does not use any static resources from the file system.
*/
public class EmptyResourceSet extends LifecycleBase implements WebResourceSet {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private WebResourceRoot root;
private boolean classLoaderOnly;
private boolean staticOnly;
public EmptyResourceSet(WebResourceRoot root) {
this.root = root;
}
/**
* {@inheritDoc}
* <p>
* This implementation always returns an {@link EmptyResource}.
*/
@Override
public WebResource getResource(String path) {
return new EmptyResource(root, path);
}
/**
* {@inheritDoc}
* <p>
* This implementation always returns an empty array.
*/
@Override
public String[] list(String path) {
return EMPTY_STRING_ARRAY;
}
/**
* {@inheritDoc}
* <p>
* This implementation always returns an empty set.
*/
@Override
public Set<String> listWebAppPaths(String path) {
return Collections.emptySet();
}
/**
* {@inheritDoc}
* <p>
* This implementation always returns false.
*/
@Override
public boolean mkdir(String path) {
return false;
}
/**
* {@inheritDoc}
* <p>
* This implementation always returns false.
*/
@Override
public boolean write(String path, InputStream is, boolean overwrite) {
return false;
}
@Override
public void setRoot(WebResourceRoot root) {
this.root = root;
}
@Override
public boolean getClassLoaderOnly() {
return classLoaderOnly;
}
@Override
public void setClassLoaderOnly(boolean classLoaderOnly) {
this.classLoaderOnly = classLoaderOnly;
}
@Override
public boolean getStaticOnly() {
return staticOnly;
}
@Override
public void setStaticOnly(boolean staticOnly) {
this.staticOnly = staticOnly;
}
/**
* {@inheritDoc}
* <p>
* This implementation always returns null.
*/
@Override
public URL getBaseUrl() {
return null;
}
/**
* {@inheritDoc}
* <p>
* Calls to this method will be ignored as this implementation always read
* only.
*/
@Override
public void setReadOnly(boolean readOnly) {
}
/**
* {@inheritDoc}
* <p>
* This implementation always returns true.
*/
@Override
public boolean isReadOnly() {
return true;
}
@Override
public void gc() {
// NO-OP
}
@Override
protected void initInternal() throws LifecycleException {
// NO-OP
}
@Override
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
}
@Override
protected void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
}
@Override
protected void destroyInternal() throws LifecycleException {
// NO-OP
}
}

View File

@@ -0,0 +1,106 @@
/*
* 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.catalina.webresources;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.ServletContext;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.WebResource;
import org.apache.catalina.startup.ExpandWar;
import org.apache.catalina.util.IOTools;
import org.apache.tomcat.util.res.StringManager;
/**
* If the main resources are packaged as a WAR file then any JARs will be
* extracted to the work directory and used from there.
*/
public class ExtractingRoot extends StandardRoot {
private static final StringManager sm = StringManager.getManager(ExtractingRoot.class);
private static final String APPLICATION_JARS_DIR = "application-jars";
@Override
protected void processWebInfLib() throws LifecycleException {
// Don't extract JAR files unless the application is deployed as a
// packed WAR file.
if (!super.isPackedWarFile()) {
super.processWebInfLib();
return;
}
File expansionTarget = getExpansionTarget();
if (!expansionTarget.isDirectory()) {
if (!expansionTarget.mkdirs()) {
throw new LifecycleException(
sm.getString("extractingRoot.targetFailed", expansionTarget));
}
}
WebResource[] possibleJars = listResources("/WEB-INF/lib", false);
for (WebResource possibleJar : possibleJars) {
if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) {
try {
File dest = new File(expansionTarget, possibleJar.getName());
dest = dest.getCanonicalFile();
try (InputStream sourceStream = possibleJar.getInputStream();
OutputStream destStream= new FileOutputStream(dest)) {
IOTools.flow(sourceStream, destStream);
}
createWebResourceSet(ResourceSetType.CLASSES_JAR,
"/WEB-INF/classes", dest.toURI().toURL(), "/");
} catch (IOException ioe) {
throw new LifecycleException(
sm.getString("extractingRoot.jarFailed", possibleJar.getName()), ioe);
}
}
}
}
private File getExpansionTarget() {
File tmpDir = (File) getContext().getServletContext().getAttribute(ServletContext.TEMPDIR);
File expansionTarget = new File(tmpDir, APPLICATION_JARS_DIR);
return expansionTarget;
}
@Override
protected boolean isPackedWarFile() {
return false;
}
@Override
protected void stopInternal() throws LifecycleException {
super.stopInternal();
if (super.isPackedWarFile()) {
// Remove the extracted JARs from the work directory
File expansionTarget = getExpansionTarget();
ExpandWar.delete(expansionTarget);
}
}
}

View File

@@ -0,0 +1,297 @@
/*
* 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.catalina.webresources;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.cert.Certificate;
import java.util.jar.Manifest;
import org.apache.catalina.WebResourceRoot;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Represents a single resource (file or directory) that is located on a file
* system.
*/
public class FileResource extends AbstractResource {
private static final Log log = LogFactory.getLog(FileResource.class);
private static final boolean PROPERTIES_NEED_CONVERT;
static {
boolean isEBCDIC = false;
try {
String encoding = System.getProperty("file.encoding");
if (encoding.contains("EBCDIC")) {
isEBCDIC = true;
}
} catch (SecurityException e) {
// Ignore
}
PROPERTIES_NEED_CONVERT = isEBCDIC;
}
private final File resource;
private final String name;
private final boolean readOnly;
private final Manifest manifest;
private final boolean needConvert;
public FileResource(WebResourceRoot root, String webAppPath,
File resource, boolean readOnly, Manifest manifest) {
super(root,webAppPath);
this.resource = resource;
if (webAppPath.charAt(webAppPath.length() - 1) == '/') {
String realName = resource.getName() + '/';
if (webAppPath.endsWith(realName)) {
name = resource.getName();
} else {
// This is the root directory of a mounted ResourceSet
// Need to return the mounted name, not the real name
int endOfName = webAppPath.length() - 1;
name = webAppPath.substring(
webAppPath.lastIndexOf('/', endOfName - 1) + 1,
endOfName);
}
} else {
// Must be a file
name = resource.getName();
}
this.readOnly = readOnly;
this.manifest = manifest;
this.needConvert = PROPERTIES_NEED_CONVERT && name.endsWith(".properties");
}
@Override
public long getLastModified() {
return resource.lastModified();
}
@Override
public boolean exists() {
return resource.exists();
}
@Override
public boolean isVirtual() {
return false;
}
@Override
public boolean isDirectory() {
return resource.isDirectory();
}
@Override
public boolean isFile() {
return resource.isFile();
}
@Override
public boolean delete() {
if (readOnly) {
return false;
}
return resource.delete();
}
@Override
public String getName() {
return name;
}
@Override
public long getContentLength() {
return getContentLengthInternal(needConvert);
}
private long getContentLengthInternal(boolean convert) {
if (convert) {
byte[] content = getContent();
if (content == null) {
return -1;
} else {
return content.length;
}
}
if (isDirectory()) {
return -1;
}
return resource.length();
}
@Override
public String getCanonicalPath() {
try {
return resource.getCanonicalPath();
} catch (IOException ioe) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("fileResource.getCanonicalPathFail",
resource.getPath()), ioe);
}
return null;
}
}
@Override
public boolean canRead() {
return resource.canRead();
}
@Override
protected InputStream doGetInputStream() {
if (needConvert) {
byte[] content = getContent();
if (content == null) {
return null;
} else {
return new ByteArrayInputStream(content);
}
}
try {
return new FileInputStream(resource);
} catch (FileNotFoundException fnfe) {
// Race condition (file has been deleted) - not an error
return null;
}
}
@Override
public final byte[] getContent() {
// Use internal version to avoid loop when needConvert is true
long len = getContentLengthInternal(false);
if (len > Integer.MAX_VALUE) {
// Can't create an array that big
throw new ArrayIndexOutOfBoundsException(sm.getString(
"abstractResource.getContentTooLarge", getWebappPath(),
Long.valueOf(len)));
}
if (len < 0) {
// Content is not applicable here (e.g. is a directory)
return null;
}
int size = (int) len;
byte[] result = new byte[size];
int pos = 0;
try (InputStream is = new FileInputStream(resource)) {
while (pos < size) {
int n = is.read(result, pos, size - pos);
if (n < 0) {
break;
}
pos += n;
}
} catch (IOException ioe) {
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("abstractResource.getContentFail",
getWebappPath()), ioe);
}
return null;
}
if (needConvert) {
// Workaround for certain files on platforms that use
// EBCDIC encoding, when they are read through FileInputStream.
// See commit message of rev.303915 for original details
// https://svn.apache.org/viewvc?view=revision&revision=303915
String str = new String(result);
try {
result = str.getBytes(StandardCharsets.UTF_8);
} catch (Exception e) {
result = null;
}
}
return result;
}
@Override
public long getCreation() {
try {
BasicFileAttributes attrs = Files.readAttributes(resource.toPath(),
BasicFileAttributes.class);
return attrs.creationTime().toMillis();
} catch (IOException e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("fileResource.getCreationFail",
resource.getPath()), e);
}
return 0;
}
}
@Override
public URL getURL() {
if (resource.exists()) {
try {
return resource.toURI().toURL();
} catch (MalformedURLException e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("fileResource.getUrlFail",
resource.getPath()), e);
}
return null;
}
} else {
return null;
}
}
@Override
public URL getCodeBase() {
if (getWebappPath().startsWith("/WEB-INF/classes/") && name.endsWith(".class")) {
return getWebResourceRoot().getResource("/WEB-INF/classes/").getURL();
} else {
return getURL();
}
}
@Override
public Certificate[] getCertificates() {
return null;
}
@Override
public Manifest getManifest() {
return manifest;
}
@Override
protected Log getLog() {
return log;
}
}

View File

@@ -0,0 +1,176 @@
/*
* 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.catalina.webresources;
import java.io.File;
import java.io.InputStream;
import java.util.Set;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.util.ResourceSet;
/**
* Represents a {@link org.apache.catalina.WebResourceSet} based on a single
* file.
*/
public class FileResourceSet extends AbstractFileResourceSet {
/**
* A no argument constructor is required for this to work with the digester.
*/
public FileResourceSet() {
super("/");
}
/**
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a
* file.
*
* @param root The {@link WebResourceRoot} this new
* {@link org.apache.catalina.WebResourceSet} will
* be added to.
* @param webAppMount The path within the web application at which this
* {@link org.apache.catalina.WebResourceSet} will
* be mounted. For example, to add a directory of
* JARs to a web application, the directory would
* be mounted at "WEB-INF/lib/"
* @param base The absolute path to the file on the file system
* from which the resource will be served.
* @param internalPath The path within this new {@link
* org.apache.catalina.WebResourceSet} where
* resources will be served from.
*/
public FileResourceSet(WebResourceRoot root, String webAppMount,
String base, String internalPath) {
super(internalPath);
setRoot(root);
setWebAppMount(webAppMount);
setBase(base);
if (getRoot().getState().isAvailable()) {
try {
start();
} catch (LifecycleException e) {
throw new IllegalStateException(e);
}
}
}
@Override
public WebResource getResource(String path) {
checkPath(path);
String webAppMount = getWebAppMount();
WebResourceRoot root = getRoot();
if (path.equals(webAppMount)) {
File f = file("", true);
if (f == null) {
return new EmptyResource(root, path);
}
return new FileResource(root, path, f, isReadOnly(), null);
}
if (path.charAt(path.length() - 1) != '/') {
path = path + '/';
}
if (webAppMount.startsWith(path)) {
String name = path.substring(0, path.length() - 1);
name = name.substring(name.lastIndexOf('/') + 1);
if (name.length() > 0) {
return new VirtualResource(root, path, name);
}
}
return new EmptyResource(root, path);
}
@Override
public String[] list(String path) {
checkPath(path);
if (path.charAt(path.length() - 1) != '/') {
path = path + '/';
}
String webAppMount = getWebAppMount();
if (webAppMount.startsWith(path)) {
webAppMount = webAppMount.substring(path.length());
if (webAppMount.equals(getFileBase().getName())) {
return new String[] {getFileBase().getName()};
} else {
// Virtual directory
int i = webAppMount.indexOf('/');
if (i > 0) {
return new String[] {webAppMount.substring(0, i)};
}
}
}
return EMPTY_STRING_ARRAY;
}
@Override
public Set<String> listWebAppPaths(String path) {
checkPath(path);
ResourceSet<String> result = new ResourceSet<>();
if (path.charAt(path.length() - 1) != '/') {
path = path + '/';
}
String webAppMount = getWebAppMount();
if (webAppMount.startsWith(path)) {
webAppMount = webAppMount.substring(path.length());
if (webAppMount.equals(getFileBase().getName())) {
result.add(path + getFileBase().getName());
} else {
// Virtual directory
int i = webAppMount.indexOf('/');
if (i > 0) {
result.add(path + webAppMount.substring(0, i + 1));
}
}
}
result.setLocked(true);
return result;
}
@Override
public boolean mkdir(String path) {
checkPath(path);
return false;
}
@Override
public boolean write(String path, InputStream is, boolean overwrite) {
checkPath(path);
return false;
}
@Override
protected void checkType(File file) {
if (file.isFile() == false) {
throw new IllegalArgumentException(sm.getString("fileResourceSet.notFile",
getBase(), File.separator, getInternalPath()));
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.catalina.webresources;
import java.util.jar.JarEntry;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Represents a single resource (file or directory) that is located within a
* JAR.
*/
public class JarResource extends AbstractSingleArchiveResource {
private static final Log log = LogFactory.getLog(JarResource.class);
public JarResource(AbstractArchiveResourceSet archiveResourceSet, String webAppPath,
String baseUrl, JarEntry jarEntry) {
super(archiveResourceSet, webAppPath, "jar:" + baseUrl + "!/", jarEntry, baseUrl);
}
@Override
protected Log getLog() {
return log;
}
}

View File

@@ -0,0 +1,161 @@
/*
* 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.catalina.webresources;
import java.io.File;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.jar.Manifest;
import org.apache.catalina.WebResourceRoot;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
public class JarResourceRoot extends AbstractResource {
private static final Log log = LogFactory.getLog(JarResourceRoot.class);
private final File base;
private final String baseUrl;
private final String name;
public JarResourceRoot(WebResourceRoot root, File base, String baseUrl,
String webAppPath) {
super(root, webAppPath);
// Validate the webAppPath before going any further
if (!webAppPath.endsWith("/")) {
throw new IllegalArgumentException(sm.getString(
"jarResourceRoot.invalidWebAppPath", webAppPath));
}
this.base = base;
this.baseUrl = "jar:" + baseUrl;
// Extract the name from the webAppPath
// Strip the trailing '/' character
String resourceName = webAppPath.substring(0, webAppPath.length() - 1);
int i = resourceName.lastIndexOf('/');
if (i > -1) {
resourceName = resourceName.substring(i + 1);
}
name = resourceName;
}
@Override
public long getLastModified() {
return base.lastModified();
}
@Override
public boolean exists() {
return true;
}
@Override
public boolean isVirtual() {
return false;
}
@Override
public boolean isDirectory() {
return true;
}
@Override
public boolean isFile() {
return false;
}
@Override
public boolean delete() {
return false;
}
@Override
public String getName() {
return name;
}
@Override
public long getContentLength() {
return -1;
}
@Override
public String getCanonicalPath() {
return null;
}
@Override
public boolean canRead() {
return true;
}
@Override
protected InputStream doGetInputStream() {
return null;
}
@Override
public byte[] getContent() {
return null;
}
@Override
public long getCreation() {
return base.lastModified();
}
@Override
public URL getURL() {
String url = baseUrl + "!/";
try {
return new URL(url);
} catch (MalformedURLException e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("fileResource.getUrlFail", url), e);
}
return null;
}
}
@Override
public URL getCodeBase() {
try {
return new URL(baseUrl);
} catch (MalformedURLException e) {
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("fileResource.getUrlFail", baseUrl), e);
}
return null;
}
}
@Override
protected Log getLog() {
return log;
}
@Override
public Certificate[] getCertificates() {
return null;
}
@Override
public Manifest getManifest() {
return null;
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.catalina.webresources;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
/**
* Represents a {@link org.apache.catalina.WebResourceSet} based on a JAR file.
*/
public class JarResourceSet extends AbstractSingleArchiveResourceSet {
/**
* A no argument constructor is required for this to work with the digester.
*/
public JarResourceSet() {
}
/**
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a JAR
* file.
*
* @param root The {@link WebResourceRoot} this new
* {@link org.apache.catalina.WebResourceSet} will
* be added to.
* @param webAppMount The path within the web application at which this
* {@link org.apache.catalina.WebResourceSet} will
* be mounted.
* @param base The absolute path to the JAR file on the file system
* from which the resources will be served.
* @param internalPath The path within this new {@link
* org.apache.catalina.WebResourceSet} where
* resources will be served from. E.g. for a
* resource JAR, this would be "META-INF/resources"
*
* @throws IllegalArgumentException if the webAppMount or internalPath is
* not valid (valid paths must start with '/')
*/
public JarResourceSet(WebResourceRoot root, String webAppMount, String base,
String internalPath) throws IllegalArgumentException {
super(root, webAppMount, base, internalPath);
}
@Override
protected WebResource createArchiveResource(JarEntry jarEntry,
String webAppPath, Manifest manifest) {
return new JarResource(this, webAppPath, getBaseUrlString(), jarEntry);
}
}

View File

@@ -0,0 +1,98 @@
/*
* 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.catalina.webresources;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.UriUtil;
/**
* Represents a single resource (file or directory) that is located within a
* JAR that in turn is located in a WAR file.
*/
public class JarWarResource extends AbstractArchiveResource {
private static final Log log = LogFactory.getLog(JarWarResource.class);
private final String archivePath;
public JarWarResource(AbstractArchiveResourceSet archiveResourceSet, String webAppPath,
String baseUrl, JarEntry jarEntry, String archivePath) {
super(archiveResourceSet, webAppPath,
"jar:war:" + baseUrl + UriUtil.getWarSeparator() + archivePath + "!/",
jarEntry, "war:" + baseUrl + UriUtil.getWarSeparator() + archivePath);
this.archivePath = archivePath;
}
@Override
protected JarInputStreamWrapper getJarInputStreamWrapper() {
JarFile warFile = null;
JarInputStream jarIs = null;
JarEntry entry = null;
try {
warFile = getArchiveResourceSet().openJarFile();
JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
InputStream isInWar = warFile.getInputStream(jarFileInWar);
jarIs = new JarInputStream(isInWar);
entry = jarIs.getNextJarEntry();
while (entry != null &&
!entry.getName().equals(getResource().getName())) {
entry = jarIs.getNextJarEntry();
}
if (entry == null) {
return null;
}
return new JarInputStreamWrapper(entry, jarIs);
} catch (IOException e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("jarResource.getInputStreamFail",
getResource().getName(), getBaseUrl()), e);
}
// Ensure jarIs is closed if there is an exception
entry = null;
return null;
} finally {
if (entry == null) {
if (jarIs != null) {
try {
jarIs.close();
} catch (IOException ioe) {
// Ignore
}
}
if (warFile != null) {
getArchiveResourceSet().closeJarFile();
}
}
}
}
@Override
protected Log getLog() {
return log;
}
}

View File

@@ -0,0 +1,279 @@
/*
* 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.catalina.webresources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.compat.JreCompat;
/**
* Represents a {@link org.apache.catalina.WebResourceSet} based on a JAR file
* that is nested inside a packed WAR file. This is only intended for internal
* use within Tomcat and therefore cannot be created via configuration.
*/
public class JarWarResourceSet extends AbstractArchiveResourceSet {
private final String archivePath;
/**
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a JAR
* file that is nested inside a WAR.
*
* @param root The {@link WebResourceRoot} this new
* {@link org.apache.catalina.WebResourceSet} will
* be added to.
* @param webAppMount The path within the web application at which this
* {@link org.apache.catalina.WebResourceSet} will
* be mounted.
* @param base The absolute path to the WAR file on the file system
* in which the JAR is located.
* @param archivePath The path within the WAR file where the JAR file is
* located.
* @param internalPath The path within this new {@link
* org.apache.catalina.WebResourceSet} where
* resources will be served from. E.g. for a
* resource JAR, this would be "META-INF/resources"
*
* @throws IllegalArgumentException if the webAppMount or internalPath is
* not valid (valid paths must start with '/')
*/
public JarWarResourceSet(WebResourceRoot root, String webAppMount,
String base, String archivePath, String internalPath)
throws IllegalArgumentException {
setRoot(root);
setWebAppMount(webAppMount);
setBase(base);
this.archivePath = archivePath;
setInternalPath(internalPath);
if (getRoot().getState().isAvailable()) {
try {
start();
} catch (LifecycleException e) {
throw new IllegalStateException(e);
}
}
}
@Override
protected WebResource createArchiveResource(JarEntry jarEntry,
String webAppPath, Manifest manifest) {
return new JarWarResource(this, webAppPath, getBaseUrlString(), jarEntry, archivePath);
}
/**
* {@inheritDoc}
* <p>
* JarWar can't optimise for a single resource so the Map is always
* returned.
*/
@Override
protected HashMap<String,JarEntry> getArchiveEntries(boolean single) {
synchronized (archiveLock) {
if (archiveEntries == null) {
JarFile warFile = null;
InputStream jarFileIs = null;
archiveEntries = new HashMap<>();
boolean multiRelease = false;
try {
warFile = openJarFile();
JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
jarFileIs = warFile.getInputStream(jarFileInWar);
try (TomcatJarInputStream jarIs = new TomcatJarInputStream(jarFileIs)) {
JarEntry entry = jarIs.getNextJarEntry();
while (entry != null) {
archiveEntries.put(entry.getName(), entry);
entry = jarIs.getNextJarEntry();
}
Manifest m = jarIs.getManifest();
setManifest(m);
if (m != null && JreCompat.isJre9Available()) {
String value = m.getMainAttributes().getValue("Multi-Release");
if (value != null) {
multiRelease = Boolean.parseBoolean(value);
}
}
// Hack to work-around JarInputStream swallowing these
// entries. TomcatJarInputStream is used above which
// extends JarInputStream and the method that creates
// the entries over-ridden so we can a) tell if the
// entries are present and b) cache them so we can
// access them here.
entry = jarIs.getMetaInfEntry();
if (entry != null) {
archiveEntries.put(entry.getName(), entry);
}
entry = jarIs.getManifestEntry();
if (entry != null) {
archiveEntries.put(entry.getName(), entry);
}
}
if (multiRelease) {
processArchivesEntriesForMultiRelease();
}
} catch (IOException ioe) {
// Should never happen
archiveEntries = null;
throw new IllegalStateException(ioe);
} finally {
if (warFile != null) {
closeJarFile();
}
if (jarFileIs != null) {
try {
jarFileIs.close();
} catch (IOException e) {
// Ignore
}
}
}
}
return archiveEntries;
}
}
protected void processArchivesEntriesForMultiRelease() {
int targetVersion = JreCompat.getInstance().jarFileRuntimeMajorVersion();
Map<String,VersionedJarEntry> versionedEntries = new HashMap<>();
Iterator<Entry<String,JarEntry>> iter = archiveEntries.entrySet().iterator();
while (iter.hasNext()) {
Entry<String,JarEntry> entry = iter.next();
String name = entry.getKey();
if (name.startsWith("META-INF/versions/")) {
// Remove the multi-release version
iter.remove();
// 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) {
VersionedJarEntry versionedJarEntry = versionedEntries.get(baseName);
if (versionedJarEntry == null) {
// No versioned entry found for this name. Create
// one.
versionedEntries.put(baseName,
new VersionedJarEntry(version, entry.getValue()));
} else {
// Ignore any entry for which we have already found
// a later version
if (version > versionedJarEntry.getVersion()) {
// Replace the entry targeted at an earlier
// version
versionedEntries.put(baseName,
new VersionedJarEntry(version, entry.getValue()));
}
}
}
}
}
}
for (Entry<String,VersionedJarEntry> versionedJarEntry : versionedEntries.entrySet()) {
archiveEntries.put(versionedJarEntry.getKey(),
versionedJarEntry.getValue().getJarEntry());
}
}
/**
* {@inheritDoc}
* <p>
* Should never be called since {@link #getArchiveEntries(boolean)} always
* returns a Map.
*/
@Override
protected JarEntry getArchiveEntry(String pathInArchive) {
throw new IllegalStateException(sm.getString("jarWarResourceSet.codingError"));
}
@Override
protected boolean isMultiRelease() {
// This always returns false otherwise the superclass will call
// #getArchiveEntry(String)
return false;
}
//-------------------------------------------------------- Lifecycle methods
@Override
protected void initInternal() throws LifecycleException {
try (JarFile warFile = new JarFile(getBase())) {
JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
InputStream jarFileIs = warFile.getInputStream(jarFileInWar);
try (JarInputStream jarIs = new JarInputStream(jarFileIs)) {
setManifest(jarIs.getManifest());
}
} catch (IOException ioe) {
throw new IllegalArgumentException(ioe);
}
try {
setBaseUrl(UriUtil.buildJarSafeUrl(new File(getBase())));
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}
private static final class VersionedJarEntry {
private final int version;
private final JarEntry jarEntry;
public VersionedJarEntry(int version, JarEntry jarEntry) {
this.version = version;
this.jarEntry = jarEntry;
}
public int getVersion() {
return version;
}
public JarEntry getJarEntry() {
return jarEntry;
}
}
}

View File

@@ -0,0 +1,59 @@
# 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.
abstractArchiveResourceSet.setReadOnlyFalse=Archive based WebResourceSets such as those based on JARs are hard-coded to be read-only and may not be configured to be read-write
abstractResource.getContentFail=Unable to return [{0}] as a byte array
abstractResource.getContentTooLarge=Unable to return [{0}] as a byte array since the resource is [{1}] bytes in size which is larger than the maximum size of a byte array
abstractResourceSet.checkPath=The requested path [{0}] is not valid. It must begin with "/".
cache.addFail=Unable to add the resource at [{0}] to the cache for web application [{1}] because there was insufficient free space available after evicting expired cache entries - consider increasing the maximum size of the cache
cache.backgroundEvictFail=The background cache eviction process was unable to free [{0}] percent of the cache for Context [{1}] - consider increasing the maximum size of the cache. After eviction approximately [{2}] KB of data remained in the cache.
cache.objectMaxSizeTooBig=The value of [{0}]kB for objectMaxSize is larger than the limit of maxSize/20 so has been reduced to [{1}]kB
cache.objectMaxSizeTooBigBytes=The value specified for the maximum object size to cache [{0}]kB is greater than Integer.MAX_VALUE bytes which is the maximum size that can be cached. The limit will be set to Integer.MAX_VALUE bytes.
cachedResource.invalidURL=Unable to create an instance of CachedResourceURLStreamHandler because the URL [{0}] is malformed
classpathUrlStreamHandler.notFound=Unable to load the resource [{0}] using the thread context class loader or the current class''s class loader
dirResourceSet.manifestFail=Failed to read manifest from [{0}]
dirResourceSet.notDirectory=The directory specified by base and internal path [{0}]{1}[{2}] does not exist.
dirResourceSet.writeNpe=The input stream may not be null
extractingRoot.jarFailed=Failed to extract the JAR file [{0}]
extractingRoot.targetFailed=Failed to create the directory [{0}] for extracted JAR files
fileResource.getCanonicalPathFail=Unable to determine the canonical path for the resource [{0}]
fileResource.getCreationFail=Unable to determine the creation time for the resource [{0}]
fileResource.getUrlFail=Unable to determine a URL for the resource [{0}]
fileResourceSet.notFile=The file specified by base and internal path [{0}]{1}[{2}] does not exist.
jarResource.getInputStreamFail=Unable to obtain an InputStream for the resource [{0}] located in the JAR [{1}]
jarResourceRoot.invalidWebAppPath=This resource always refers to a directory so the supplied webAppPath must end with / but the provided webAppPath was [{0}]
jarWarResourceSet.codingError=Coding error
standardRoot.checkStateNotStarted=The resources may not be accessed if they are not currently started
standardRoot.createInvalidFile=Unable to create WebResourceSet from [{0}]
standardRoot.createUnknownType=Unable to create WebResourceSet of unknown type [{0}]
standardRoot.invalidPath=The resource path [{0}] is not valid
standardRoot.invalidPathNormal=The resource path [{0}] has been normalized to [{1}] which is not valid
standardRoot.lockedFile=The web application [{0}] failed to close the file [{1}] opened via the following stack trace
standardRoot.noContext=A Context has not been configured for this WebResourceRoot
standardRoot.startInvalidMain=The main resource set specified [{0}] is not valid
standardRoot.unsupportedProtocol=The URL protocol [{0}] is not supported by this web resources implementation

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.
extractingRoot.targetFailed=Konnte Verzeichnis [{0}] zum entpacken einer JAR-Datei nicht anlegen

View File

@@ -0,0 +1,22 @@
# 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.
cache.addFail=Imposible adicionar recursos a [{0}] de la cache para la applicación web [{1}] porque no hay suficiente espacio libre luego de eliminar los datos expirados de la caché - considere incrementar el tamaño máximo de la chaché
dirResourceSet.notDirectory=El directorio especificado por la base y el camino interno [{0}]{1}[{2}] no existe.\n
extractingRoot.targetFailed=Fallo al crear directorio [{0}] para los archivos JAR extraidos
standardRoot.createUnknownType=Imposible crear WebResourceSet de tipo desconocido [{0}]\n

View File

@@ -0,0 +1,57 @@
# 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.
abstractArchiveResourceSet.setReadOnlyFalse=Les archives basées sur WebResourceSets telles que celles des JARs sont fixées comme étant en lecture seule et ne peuvent être configurées en lecture écriture
abstractResource.getContentFail=Impossible de retourner [{0}] en tant que tableau d''octets
abstractResource.getContentTooLarge=Impossible de retourner [{0}] comme tableau d''octets car la ressource a une taille de [{1}] octets qui est supérieure à la taille maximale d''un tableau d''octets
abstractResourceSet.checkPath=Le chemin demandé [{0}] n''est pas valide, il doit commencer par ''/''
cache.addFail=Incapable d''ajouter la ressource située [{0}] au cache de l''application web [{1}] parce qu''il n''y avait pas assez d''espace libre disponible après l''éviction des entrées de cache expirées - envisagez d''augmenter la taille maximale du cache
cache.backgroundEvictFail=Le processus d''arrière plan d''éviction du cache n''a pas pu nettoyer [{0}] pourcents du cache pour le contexte [{1}], il faudrait augmenter la taille maximale du cache; après l''éviction, approximativement [{2}] KO de données restaient dans le cache
cache.objectMaxSizeTooBig=La valeur [{0}]kB pour l''objectMaxSize est plus grade que la limite de maxSize/20 son elle a été réduite à [{1}]kB\n
cache.objectMaxSizeTooBigBytes=La valeur de taille d''objet maximale pouvant être mis en cache de [{0}]kB est supérieure à Integer.MAX_VALUE qui est le maximum, la limite a donc été fixée à Integer.MAX_VALUE octets
classpathUrlStreamHandler.notFound=Impossible de charger la ressource [{0}] en utilisant le chargeur de classe de contexte du thread ou celui de la classe actuelle
dirResourceSet.manifestFail=Impossible de lire le manifeste depuis [{0}]
dirResourceSet.notDirectory=Le répertoire qui a été spécifié pour la base et le chemin interne [{0}]{1}[{2}] n''existe pas
dirResourceSet.writeNpe=Le flux d'entrée ne peut pas être null
extractingRoot.jarFailed=Echec de lextraction du fichier JAR [{0}]
extractingRoot.targetFailed=Echec de la création du répertoire [{0}] pour l''extraction des fichiers contenus dans le JAR
fileResource.getCanonicalPathFail=Impossible de déterminer le chemin canonique pour la ressource [{0}]
fileResource.getCreationFail=Impossible de déterminer la date de création de la ressource [{0}]
fileResource.getUrlFail=Impossible de déterminer l''URL pour la ressource [{0}]
fileResourceSet.notFile=Le fichier spécifié par ses chemins de base et internes [{0}]{1}[{2}] n''existe pas
jarResource.getInputStreamFail=Impossible d''obtenir une InputStream pour la ressource [{0}] située dans le JAR [{1}]
jarResourceRoot.invalidWebAppPath=Cette ressource se réfère toujours à un répertoire donc le webAppPath fourni doit se terminer avec ''/'' mais il était [{0}]
jarWarResourceSet.codingError=Erreur de programmation
standardRoot.checkStateNotStarted=Les resources ne peuvent pas être accédées tant qu'elles ne sont pas démarrées
standardRoot.createInvalidFile=Impossible de créer WebResourceSet à partir de [{0}]
standardRoot.createUnknownType=Impossible de créer un WebResourceSet pour le type inconnu [{0}]
standardRoot.invalidPath=Le chemin de ressources [{0}] est invalide
standardRoot.invalidPathNormal=Le chemin de ressource [{0}] a été normalisé en [{1}] ce qui est invalide
standardRoot.lockedFile=L''application web [{0}] n''a pas fermé le fichier [{1}] ouvert à partir de la trace
standardRoot.noContext=Un contexte n'a pas été configuré pour ce WebResourceRoot
standardRoot.startInvalidMain=L''ensemble de ressources principal [{0}] est invalide
standardRoot.unsupportedProtocol=Le protocole [{0}] de l''URL n''est pas supporté par cette implémentation des ressources web

View File

@@ -0,0 +1,57 @@
# 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.
abstractArchiveResourceSet.setReadOnlyFalse=JARに基づくWebResourceSetなどのアーカイブベースのWebResourceSetは、読み取り専用にハードコードされており、読み取り/書き込み可能に構成されていない可能性があります。
abstractResource.getContentFail=[{0}]をバイト配列として返すことができません。
abstractResource.getContentTooLarge=リソースがバイト配列の最大サイズよりも大きいサイズの[{1}]バイトであるため、[{0}]をバイト配列として返すことができません。
abstractResourceSet.checkPath=リクエストパス[{0}]が無効です。 "/"で始まる必要があります。
cache.addFail=有効期限切れの項目を破棄しても利用可能な領域が不足するため、Web アプリケーション [{1}] のキャッシュにリソース [{0}] を追加できません。最大キャッシュサイズの増加を検討してください。
cache.backgroundEvictFail=コンテキスト [{1}] のバックグラウンドキャッシュ削除処理は全体の [{0}] % を解放できませんでした。キャッシュサイズの最大値の増加を検討してください。現在は約 [{2}] kB のデータがキャッシュに残存しています。
cache.objectMaxSizeTooBig=objectMaxSizeの[{0}] kBの値がmaxSize / 20の制限より大きいため、[{1}] kBに減少しました。
cache.objectMaxSizeTooBigBytes=キャッシュ可能なオブジェクトサイズの最大値に指定された [{0}]kB は Integer.MAX_VALUE バイトを越えています。最大値に Integer.MAX_VALUE を設定します。
classpathUrlStreamHandler.notFound=スレッドコンテキストクラスローダー、あるいは、現在のクラスのクラスローダーでリソース [{0}] を読み込みできません。
dirResourceSet.manifestFail=[{0}]からマニフェストを読み込めませんでした。
dirResourceSet.notDirectory=ベースパスと内部パスで指定した [{0}][{1}][{2}] にディレクトリがありません。
dirResourceSet.writeNpe=入力ストリームには null を指定できません。
extractingRoot.jarFailed=JARファイル[{0}]の抽出に失敗しました
extractingRoot.targetFailed=JAR ファイルを展開するためのディレクトリ [{0}] を作成できません。
fileResource.getCanonicalPathFail=リソース [{0}] の正規化パスを取得できません。
fileResource.getCreationFail=リソース[{0}]の作成時間を特定できません。
fileResource.getUrlFail=リソース [{0}] の URL を取得できません。
fileResourceSet.notFile=基本パスおよび内部パスで指定されたファイル [{0}]{1}[{2}] がありません。
jarResource.getInputStreamFail=JAR ファイル [{1}] のリソース [{0}] の入力ストリームを取得できません。
jarResourceRoot.invalidWebAppPath=このリソースは常にディレクトリを参照するため、指定されたwebAppPathは/で終了する必要がありますが、指定されたwebAppPathは[{0}]です。
jarWarResourceSet.codingError=コーディングエラー
standardRoot.checkStateNotStarted=リソースは、現在起動されていない場合はアクセスできない場合があります
standardRoot.createInvalidFile=[{0}]からWebResourceSetを作成できません。
standardRoot.createUnknownType=未知のクラス [{0}] の WebResourceSet を作成できません。
standardRoot.invalidPath=不正なリソースパス [{0}]
standardRoot.invalidPathNormal=リソースパス[{0}]は有効ではない[{1}]に正規化されています。
standardRoot.lockedFile=Webアプリケーション[{0}]は、次のスタックトレースによって開かれたファイル[{1}]を閉じることに失敗しました。
standardRoot.noContext=この WebResourceRoot にはContext が構成されていません。
standardRoot.startInvalidMain=指定された主リソースセット[{0}]は無効です。
standardRoot.unsupportedProtocol=URLプロトコル[{0}]はこのWebリソース実装ではサポートされていません。

View File

@@ -0,0 +1,57 @@
# 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.
abstractArchiveResourceSet.setReadOnlyFalse=JAR 파일들에 기반한 것들과 같은 아카이브 기반 WebResourceSet들은, 읽기 전용으로 하드코드되어 있으며, 읽기 및 쓰기 용으로 설정될 수 없습니다.
abstractResource.getContentFail=[{0}]을(를) 바이트 배열로 반환할 수 없습니다.
abstractResource.getContentTooLarge=리소스의 크기가 [{1}] 바이트로서, 이는 바이트 배열 최대 크기보다 크기 때문에, [{0}]을(를) 바이트 배열로서 반환할 수 없습니다.
abstractResourceSet.checkPath=요청된 경로 [{0}]은(는) 유효하지 않습니다. 반드시 "/"로 시작해야 합니다.
cache.addFail=[{0}]에 위치한 리소스를 웹 애플리케이션 [{1}]을(를) 위한 캐시에 추가할 수 없습니다. 왜냐하면 만료된 캐시 엔트리들을 없애버린 이후에도 여유 공간이 충분하지 않기 때문입니다. 캐시의 최대 크기를 증가시키는 것을 고려해 보십시오.
cache.backgroundEvictFail=백그라운드 캐시 퇴거 (cache eviction) 프로세스가, 컨텍스트 [{1}]을(를) 위한 캐시의 [{0}] 퍼센트를 해제시킬 수 없었습니다. 캐시의 최대 크기를 증가시킬 것을 고려해 보십시오. 캐시 퇴거 작업 이후, 대략 [{2}] KB의 데이터가 캐시에 남아 있습니다.
cache.objectMaxSizeTooBig=objectMaxSize를 위한 값 [{0}]kB이, maxSize/20인 최대한계값 보다 커서, [{1}]kB로 줄여졌습니다.
cache.objectMaxSizeTooBigBytes=[{0}]kB를 캐시하기 위해, 최대 객체 크기로서 지정된 값이 Integer.MAX_VALUE 바이트보다 큰데, Integer.MAX_VALUE는 캐시될 수 있는 최대 크기입니다. 한계 값을 Integer.MAX_VALUE 바이트로 설정하겠습니다.
classpathUrlStreamHandler.notFound=쓰레드 컨텍스트 클래스로더 또는 현재 클래스의 클래스로더를 사용하여, 리소스 [{0}]을(를) 로드할 수 없습니다.
dirResourceSet.manifestFail=[{0}](으)로부터 manifest를 읽지 못했습니다.
dirResourceSet.notDirectory=base와 internal path [{0}]{1}[{2}](으)로 지정된 디렉토리가 존재하지 않습니다.
dirResourceSet.writeNpe=입력 스트림이 널일 수는 없습니다.
extractingRoot.jarFailed=JAR 파일 [{0}]을(를) 추출하지 못했습니다.
extractingRoot.targetFailed=JAR 파일들의 압축을 풀기 위한 디렉토리 [{0}]을(를) 생성할 수 없습니다.
fileResource.getCanonicalPathFail=리소스 [{0}]에 대한 canonical 경로를 결정할 수 없습니다.
fileResource.getCreationFail=리소스 [{0}]의 생성 시간을 결정할 수 없습니다.
fileResource.getUrlFail=리소스 [{0}]을(를) 위한 URL을 결정할 수 없습니다.
fileResourceSet.notFile=base와 내부 경로 [{0}]{1}[{2}]에 의해 지정된 파일이 존재하지 않습니다.
jarResource.getInputStreamFail=JAR [{1}] 내의 리소스 [{0}]을(를) 위한 InputStream을 얻을 수 없습니다.
jarResourceRoot.invalidWebAppPath=이 리소스는 언제나 디렉토리를 가리켜서, 제공된 webAppPath가 반드시 ''/'' 로 끝나야 하지만, 제공된 webAppPath는 [{0}]이었습니다.
jarWarResourceSet.codingError=코딩 오류
standardRoot.checkStateNotStarted=현재 시작되어 있는 상태가 아니라면, 리소스들은 접근될 수 없습니다.
standardRoot.createInvalidFile=[{0}](으)로부터 WebResourceSet을 생성할 수 없습니다.
standardRoot.createUnknownType=알 수 없는 타입 [{0}]의 WebResourceSet을 생성할 수 없습니다.
standardRoot.invalidPath=리소스 경로 [{0}]은(는) 유효하지 않습니다.
standardRoot.invalidPathNormal=리소스 경로 [{0}]이(가) [{1}](으)로 정규화되어 있는데, 이는 유효하지 않습니다.
standardRoot.lockedFile=웹 애플리케이션 [{0}]이(가) 파일 [{1}]을(를) 닫지 못했습니다. 해당 파일은 다음과 같은 스택 트레이스 내에서 열렸었습니다.
standardRoot.noContext=컨텍스트가 이 WebResourceRoot를 위해 설정되지 않았습니다.
standardRoot.startInvalidMain=지정된 주요 리소스셋 [{0}]은(는) 유효하지 않습니다.
standardRoot.unsupportedProtocol=URL 프로토콜 [{0}]은(는), 이 웹 리소스 구현에 의해 지원되지 않습니다.

View File

@@ -0,0 +1,35 @@
# 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.
abstractArchiveResourceSet.setReadOnlyFalse=基于存档的WebResourceSets 如基于jar的WebResourceSets 硬编码为只读,并且不能配置为读写
cache.addFail=无法将位于[{0}]的资源添加到Web应用程序[{1}]的缓存中,因为在清除过期缓存条目后可用空间仍不足 - 请考虑增加缓存的最大空间。
dirResourceSet.notDirectory=基本和内部路径[{0}] {1} [{2}]指定的目录不存在。
extractingRoot.jarFailed=解压JAR文件[{0}]失败
extractingRoot.targetFailed=无法为提取的 JAR 文件创建目录 [{0}]
fileResource.getCanonicalPathFail=不能判断资源的标准路径[{0}]
fileResource.getUrlFail=不能决定一个url 为资源[{0}]
jarResource.getInputStreamFail=无法获取JAR[{1}]中的资源文件[{0}]的一个InputStream
standardRoot.checkStateNotStarted=如果当前未启动资源,则可能无法访问这些资源
standardRoot.createUnknownType=无法为未知类型[{0}]创建WebResourceSet。
standardRoot.invalidPathNormal=资源路径[{0}]已规范化为无效的[{1}]
standardRoot.lockedFile=Web应用程序[{0}]无法关闭通过以下堆栈跟踪打开的文件[{1}]
standardRoot.noContext=尚未为WebResourceRoot配置上下文
standardRoot.startInvalidMain=指定的主资源集 [{0}] 无效

View File

@@ -0,0 +1,864 @@
/*
* 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.catalina.webresources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.management.ObjectName;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.TrackedWebResource;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.WebResourceSet;
import org.apache.catalina.util.LifecycleMBeanBase;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.http.RequestUtil;
import org.apache.tomcat.util.res.StringManager;
/**
* <p>
* Provides the resources implementation for a web application. The
* {@link org.apache.catalina.Lifecycle} of this class should be aligned with
* that of the associated {@link Context}.
* </p><p>
* This implementation assumes that the base attribute supplied to {@link
* StandardRoot#createWebResourceSet(
* org.apache.catalina.WebResourceRoot.ResourceSetType, String, String, String,
* String)} represents the absolute path to a file.
* </p>
*/
public class StandardRoot extends LifecycleMBeanBase implements WebResourceRoot {
private static final Log log = LogFactory.getLog(StandardRoot.class);
protected static final StringManager sm = StringManager.getManager(StandardRoot.class);
private Context context;
private boolean allowLinking = false;
private final List<WebResourceSet> preResources = new ArrayList<>();
private WebResourceSet main;
private final List<WebResourceSet> classResources = new ArrayList<>();
private final List<WebResourceSet> jarResources = new ArrayList<>();
private final List<WebResourceSet> postResources = new ArrayList<>();
private final Cache cache = new Cache(this);
private boolean cachingAllowed = true;
private ObjectName cacheJmxName = null;
private boolean trackLockedFiles = false;
private final Set<TrackedWebResource> trackedResources =
Collections.newSetFromMap(new ConcurrentHashMap<TrackedWebResource,Boolean>());
// Constructs to make iteration over all WebResourceSets simpler
private final List<WebResourceSet> mainResources = new ArrayList<>();
private final List<List<WebResourceSet>> allResources =
new ArrayList<>();
{
allResources.add(preResources);
allResources.add(mainResources);
allResources.add(classResources);
allResources.add(jarResources);
allResources.add(postResources);
}
/**
* Creates a new standard implementation of {@link WebResourceRoot}. A no
* argument constructor is required for this to work with the digester.
* {@link #setContext(Context)} must be called before this component is
* initialized.
*/
public StandardRoot() {
// NO-OP
}
public StandardRoot(Context context) {
this.context = context;
}
@Override
public String[] list(String path) {
return list(path, true);
}
private String[] list(String path, boolean validate) {
if (validate) {
path = validate(path);
}
// Set because we don't want duplicates
// LinkedHashSet to retain the order. It is the order of the
// WebResourceSet that matters but it is simpler to retain the order
// over all of the JARs.
HashSet<String> result = new LinkedHashSet<>();
for (List<WebResourceSet> list : allResources) {
for (WebResourceSet webResourceSet : list) {
if (!webResourceSet.getClassLoaderOnly()) {
String[] entries = webResourceSet.list(path);
for (String entry : entries) {
result.add(entry);
}
}
}
}
return result.toArray(new String[result.size()]);
}
@Override
public Set<String> listWebAppPaths(String path) {
path = validate(path);
// Set because we don't want duplicates
HashSet<String> result = new HashSet<>();
for (List<WebResourceSet> list : allResources) {
for (WebResourceSet webResourceSet : list) {
if (!webResourceSet.getClassLoaderOnly()) {
result.addAll(webResourceSet.listWebAppPaths(path));
}
}
}
if (result.size() == 0) {
return null;
}
return result;
}
@Override
public boolean mkdir(String path) {
path = validate(path);
if (preResourceExists(path)) {
return false;
}
boolean mkdirResult = main.mkdir(path);
if (mkdirResult && isCachingAllowed()) {
// Remove the entry from the cache so the new directory is visible
cache.removeCacheEntry(path);
}
return mkdirResult;
}
@Override
public boolean write(String path, InputStream is, boolean overwrite) {
path = validate(path);
if (!overwrite && preResourceExists(path)) {
return false;
}
boolean writeResult = main.write(path, is, overwrite);
if (writeResult && isCachingAllowed()) {
// Remove the entry from the cache so the new resource is visible
cache.removeCacheEntry(path);
}
return writeResult;
}
private boolean preResourceExists(String path) {
for (WebResourceSet webResourceSet : preResources) {
WebResource webResource = webResourceSet.getResource(path);
if (webResource.exists()) {
return true;
}
}
return false;
}
@Override
public WebResource getResource(String path) {
return getResource(path, true, false);
}
protected WebResource getResource(String path, boolean validate,
boolean useClassLoaderResources) {
if (validate) {
path = validate(path);
}
if (isCachingAllowed()) {
return cache.getResource(path, useClassLoaderResources);
} else {
return getResourceInternal(path, useClassLoaderResources);
}
}
@Override
public WebResource getClassLoaderResource(String path) {
return getResource("/WEB-INF/classes" + path, true, true);
}
@Override
public WebResource[] getClassLoaderResources(String path) {
return getResources("/WEB-INF/classes" + path, true);
}
/**
* Ensures that this object is in a valid state to serve resources, checks
* that the path is a String that starts with '/' and checks that the path
* can be normalized without stepping outside of the root.
*
* @param path
* @return the normalized path
*/
private String validate(String path) {
if (!getState().isAvailable()) {
throw new IllegalStateException(
sm.getString("standardRoot.checkStateNotStarted"));
}
if (path == null || path.length() == 0 || !path.startsWith("/")) {
throw new IllegalArgumentException(
sm.getString("standardRoot.invalidPath", path));
}
String result;
if (File.separatorChar == '\\') {
// On Windows '\\' is a separator so in case a Windows style
// separator has managed to make it into the path, replace it.
result = RequestUtil.normalize(path, true);
} else {
// On UNIX and similar systems, '\\' is a valid file name so do not
// convert it to '/'
result = RequestUtil.normalize(path, false);
}
if (result == null || result.length() == 0 || !result.startsWith("/")) {
throw new IllegalArgumentException(
sm.getString("standardRoot.invalidPathNormal", path, result));
}
return result;
}
protected final WebResource getResourceInternal(String path,
boolean useClassLoaderResources) {
WebResource result = null;
WebResource virtual = null;
WebResource mainEmpty = null;
for (List<WebResourceSet> list : allResources) {
for (WebResourceSet webResourceSet : list) {
if (!useClassLoaderResources && !webResourceSet.getClassLoaderOnly() ||
useClassLoaderResources && !webResourceSet.getStaticOnly()) {
result = webResourceSet.getResource(path);
if (result.exists()) {
return result;
}
if (virtual == null) {
if (result.isVirtual()) {
virtual = result;
} else if (main.equals(webResourceSet)) {
mainEmpty = result;
}
}
}
}
}
// Use the first virtual result if no real result was found
if (virtual != null) {
return virtual;
}
// Default is empty resource in main resources
return mainEmpty;
}
@Override
public WebResource[] getResources(String path) {
return getResources(path, false);
}
private WebResource[] getResources(String path,
boolean useClassLoaderResources) {
path = validate(path);
if (isCachingAllowed()) {
return cache.getResources(path, useClassLoaderResources);
} else {
return getResourcesInternal(path, useClassLoaderResources);
}
}
protected WebResource[] getResourcesInternal(String path,
boolean useClassLoaderResources) {
List<WebResource> result = new ArrayList<>();
for (List<WebResourceSet> list : allResources) {
for (WebResourceSet webResourceSet : list) {
if (useClassLoaderResources || !webResourceSet.getClassLoaderOnly()) {
WebResource webResource = webResourceSet.getResource(path);
if (webResource.exists()) {
result.add(webResource);
}
}
}
}
if (result.size() == 0) {
result.add(main.getResource(path));
}
return result.toArray(new WebResource[result.size()]);
}
@Override
public WebResource[] listResources(String path) {
return listResources(path, true);
}
protected WebResource[] listResources(String path, boolean validate) {
if (validate) {
path = validate(path);
}
String[] resources = list(path, false);
WebResource[] result = new WebResource[resources.length];
for (int i = 0; i < resources.length; i++) {
if (path.charAt(path.length() - 1) == '/') {
result[i] = getResource(path + resources[i], false, false);
} else {
result[i] = getResource(path + '/' + resources[i], false, false);
}
}
return result;
}
// TODO: Should the createWebResourceSet() methods be removed to some
// utility class for file system based resource sets?
@Override
public void createWebResourceSet(ResourceSetType type, String webAppMount,
URL url, String internalPath) {
BaseLocation baseLocation = new BaseLocation(url);
createWebResourceSet(type, webAppMount, baseLocation.getBasePath(),
baseLocation.getArchivePath(), internalPath);
}
@Override
public void createWebResourceSet(ResourceSetType type, String webAppMount,
String base, String archivePath, String internalPath) {
List<WebResourceSet> resourceList;
WebResourceSet resourceSet;
switch (type) {
case PRE:
resourceList = preResources;
break;
case CLASSES_JAR:
resourceList = classResources;
break;
case RESOURCE_JAR:
resourceList = jarResources;
break;
case POST:
resourceList = postResources;
break;
default:
throw new IllegalArgumentException(
sm.getString("standardRoot.createUnknownType", type));
}
// This implementation assumes that the base for all resources will be a
// file.
File file = new File(base);
if (file.isFile()) {
if (archivePath != null) {
// Must be a JAR nested inside a WAR if archivePath is non-null
resourceSet = new JarWarResourceSet(this, webAppMount, base,
archivePath, internalPath);
} else if (file.getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) {
resourceSet = new JarResourceSet(this, webAppMount, base,
internalPath);
} else {
resourceSet = new FileResourceSet(this, webAppMount, base,
internalPath);
}
} else if (file.isDirectory()) {
resourceSet =
new DirResourceSet(this, webAppMount, base, internalPath);
} else {
throw new IllegalArgumentException(
sm.getString("standardRoot.createInvalidFile", file));
}
if (type.equals(ResourceSetType.CLASSES_JAR)) {
resourceSet.setClassLoaderOnly(true);
} else if (type.equals(ResourceSetType.RESOURCE_JAR)) {
resourceSet.setStaticOnly(true);
}
resourceList.add(resourceSet);
}
@Override
public void addPreResources(WebResourceSet webResourceSet) {
webResourceSet.setRoot(this);
preResources.add(webResourceSet);
}
@Override
public WebResourceSet[] getPreResources() {
return preResources.toArray(new WebResourceSet[preResources.size()]);
}
@Override
public void addJarResources(WebResourceSet webResourceSet) {
webResourceSet.setRoot(this);
jarResources.add(webResourceSet);
}
@Override
public WebResourceSet[] getJarResources() {
return jarResources.toArray(new WebResourceSet[jarResources.size()]);
}
@Override
public void addPostResources(WebResourceSet webResourceSet) {
webResourceSet.setRoot(this);
postResources.add(webResourceSet);
}
@Override
public WebResourceSet[] getPostResources() {
return postResources.toArray(new WebResourceSet[postResources.size()]);
}
protected WebResourceSet[] getClassResources() {
return classResources.toArray(new WebResourceSet[classResources.size()]);
}
protected void addClassResources(WebResourceSet webResourceSet) {
webResourceSet.setRoot(this);
classResources.add(webResourceSet);
}
@Override
public void setAllowLinking(boolean allowLinking) {
if (this.allowLinking != allowLinking && cachingAllowed) {
// If allow linking changes, invalidate the cache.
cache.clear();
}
this.allowLinking = allowLinking;
}
@Override
public boolean getAllowLinking() {
return allowLinking;
}
@Override
public void setCachingAllowed(boolean cachingAllowed) {
this.cachingAllowed = cachingAllowed;
if (!cachingAllowed) {
cache.clear();
}
}
@Override
public boolean isCachingAllowed() {
return cachingAllowed;
}
@Override
public long getCacheTtl() {
return cache.getTtl();
}
@Override
public void setCacheTtl(long cacheTtl) {
cache.setTtl(cacheTtl);
}
@Override
public long getCacheMaxSize() {
return cache.getMaxSize();
}
@Override
public void setCacheMaxSize(long cacheMaxSize) {
cache.setMaxSize(cacheMaxSize);
}
@Override
public void setCacheObjectMaxSize(int cacheObjectMaxSize) {
cache.setObjectMaxSize(cacheObjectMaxSize);
// Don't enforce the limit when not running as attributes may get set in
// any order.
if (getState().isAvailable()) {
cache.enforceObjectMaxSizeLimit();
}
}
@Override
public int getCacheObjectMaxSize() {
return cache.getObjectMaxSize();
}
@Override
public void setTrackLockedFiles(boolean trackLockedFiles) {
this.trackLockedFiles = trackLockedFiles;
if (!trackLockedFiles) {
trackedResources.clear();
}
}
@Override
public boolean getTrackLockedFiles() {
return trackLockedFiles;
}
public List<String> getTrackedResources() {
List<String> result = new ArrayList<>(trackedResources.size());
for (TrackedWebResource resource : trackedResources) {
result.add(resource.toString());
}
return result;
}
@Override
public Context getContext() {
return context;
}
@Override
public void setContext(Context context) {
this.context = context;
}
/**
* Class loader resources are handled by treating JARs in WEB-INF/lib as
* resource JARs (without the internal META-INF/resources/ prefix) mounted
* at WEB-INF/classes (rather than the web app root). This enables reuse
* of the resource handling plumbing.
*
* These resources are marked as class loader only so they are only used in
* the methods that are explicitly defined to return class loader resources.
* This prevents calls to getResource("/WEB-INF/classes") returning from one
* or more of the JAR files.
*
* @throws LifecycleException If an error occurs that should stop the web
* application from starting
*/
protected void processWebInfLib() throws LifecycleException {
WebResource[] possibleJars = listResources("/WEB-INF/lib", false);
for (WebResource possibleJar : possibleJars) {
if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) {
createWebResourceSet(ResourceSetType.CLASSES_JAR,
"/WEB-INF/classes", possibleJar.getURL(), "/");
}
}
}
/**
* For unit testing.
* @param main The main resources
*/
protected final void setMainResources(WebResourceSet main) {
this.main = main;
mainResources.clear();
mainResources.add(main);
}
@Override
public void backgroundProcess() {
cache.backgroundProcess();
gc();
}
@Override
public void gc() {
for (List<WebResourceSet> list : allResources) {
for (WebResourceSet webResourceSet : list) {
webResourceSet.gc();
}
}
}
@Override
public void registerTrackedResource(TrackedWebResource trackedResource) {
trackedResources.add(trackedResource);
}
@Override
public void deregisterTrackedResource(TrackedWebResource trackedResource) {
trackedResources.remove(trackedResource);
}
@Override
public List<URL> getBaseUrls() {
List<URL> result = new ArrayList<>();
for (List<WebResourceSet> list : allResources) {
for (WebResourceSet webResourceSet : list) {
if (!webResourceSet.getClassLoaderOnly()) {
URL url = webResourceSet.getBaseUrl();
if (url != null) {
result.add(url);
}
}
}
}
return result;
}
/*
* Returns true if and only if all the resources for this web application
* are provided via a packed WAR file. It is used to optimise cache
* validation in this case on the basis that the WAR file will not change.
*/
protected boolean isPackedWarFile() {
return main instanceof WarResourceSet && preResources.isEmpty() && postResources.isEmpty();
}
// ----------------------------------------------------------- JMX Lifecycle
@Override
protected String getDomainInternal() {
return context.getDomain();
}
@Override
protected String getObjectNameKeyProperties() {
StringBuilder keyProperties = new StringBuilder("type=WebResourceRoot");
keyProperties.append(context.getMBeanKeyProperties());
return keyProperties.toString();
}
// --------------------------------------------------------------- Lifecycle
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
cacheJmxName = register(cache, getObjectNameKeyProperties() + ",name=Cache");
registerURLStreamHandlerFactory();
if (context == null) {
throw new IllegalStateException(
sm.getString("standardRoot.noContext"));
}
for (List<WebResourceSet> list : allResources) {
for (WebResourceSet webResourceSet : list) {
webResourceSet.init();
}
}
}
protected void registerURLStreamHandlerFactory() {
// Ensure support for jar:war:file:/ URLs will be available (required
// for resource JARs in packed WAR files).
TomcatURLStreamHandlerFactory.register();
}
@Override
protected void startInternal() throws LifecycleException {
mainResources.clear();
main = createMainResourceSet();
mainResources.add(main);
for (List<WebResourceSet> list : allResources) {
// Skip class resources since they are started below
if (list != classResources) {
for (WebResourceSet webResourceSet : list) {
webResourceSet.start();
}
}
}
// This has to be called after the other resources have been started
// else it won't find all the matching resources
processWebInfLib();
// Need to start the newly found resources
for (WebResourceSet classResource : classResources) {
classResource.start();
}
cache.enforceObjectMaxSizeLimit();
setState(LifecycleState.STARTING);
}
protected WebResourceSet createMainResourceSet() {
String docBase = context.getDocBase();
WebResourceSet mainResourceSet;
if (docBase == null) {
mainResourceSet = new EmptyResourceSet(this);
} else {
File f = new File(docBase);
if (!f.isAbsolute()) {
f = new File(((Host)context.getParent()).getAppBaseFile(), f.getPath());
}
if (f.isDirectory()) {
mainResourceSet = new DirResourceSet(this, "/", f.getAbsolutePath(), "/");
} else if(f.isFile() && docBase.endsWith(".war")) {
mainResourceSet = new WarResourceSet(this, "/", f.getAbsolutePath());
} else {
throw new IllegalArgumentException(
sm.getString("standardRoot.startInvalidMain",
f.getAbsolutePath()));
}
}
return mainResourceSet;
}
@Override
protected void stopInternal() throws LifecycleException {
for (List<WebResourceSet> list : allResources) {
for (WebResourceSet webResourceSet : list) {
webResourceSet.stop();
}
}
if (main != null) {
main.destroy();
}
mainResources.clear();
for (WebResourceSet webResourceSet : jarResources) {
webResourceSet.destroy();
}
jarResources.clear();
for (WebResourceSet webResourceSet : classResources) {
webResourceSet.destroy();
}
classResources.clear();
for (TrackedWebResource trackedResource : trackedResources) {
log.error(sm.getString("standardRoot.lockedFile",
context.getName(),
trackedResource.getName()),
trackedResource.getCreatedBy());
try {
trackedResource.close();
} catch (IOException e) {
// Ignore
}
}
cache.clear();
setState(LifecycleState.STOPPING);
}
@Override
protected void destroyInternal() throws LifecycleException {
for (List<WebResourceSet> list : allResources) {
for (WebResourceSet webResourceSet : list) {
webResourceSet.destroy();
}
}
unregister(cacheJmxName);
super.destroyInternal();
}
// Unit tests need to access this class
static class BaseLocation {
private final String basePath;
private final String archivePath;
BaseLocation(URL url) {
File f = null;
if ("jar".equals(url.getProtocol()) || "war".equals(url.getProtocol())) {
String jarUrl = url.toString();
int endOfFileUrl = -1;
if ("jar".equals(url.getProtocol())) {
endOfFileUrl = jarUrl.indexOf("!/");
} else {
endOfFileUrl = jarUrl.indexOf(UriUtil.getWarSeparator());
}
String fileUrl = jarUrl.substring(4, endOfFileUrl);
try {
f = new File(new URL(fileUrl).toURI());
} catch (MalformedURLException | URISyntaxException e) {
throw new IllegalArgumentException(e);
}
int startOfArchivePath = endOfFileUrl + 2;
if (jarUrl.length() > startOfArchivePath) {
archivePath = jarUrl.substring(startOfArchivePath);
} else {
archivePath = null;
}
} else if ("file".equals(url.getProtocol())){
try {
f = new File(url.toURI());
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
archivePath = null;
} else {
throw new IllegalArgumentException(sm.getString(
"standardRoot.unsupportedProtocol", url.getProtocol()));
}
basePath = f.getAbsolutePath();
}
String getBasePath() {
return basePath;
}
String getArchivePath() {
return archivePath;
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.catalina.webresources;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.zip.ZipEntry;
/**
* The purpose of this sub-class is to obtain references to the JarEntry objects
* for META-INF/ and META-INF/MANIFEST.MF that are otherwise swallowed by the
* JarInputStream implementation.
*/
public class TomcatJarInputStream extends JarInputStream {
private JarEntry metaInfEntry;
private JarEntry manifestEntry;
TomcatJarInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected ZipEntry createZipEntry(String name) {
ZipEntry ze = super.createZipEntry(name);
if (metaInfEntry == null && "META-INF/".equals(name)) {
metaInfEntry = (JarEntry) ze;
} else if (manifestEntry == null && "META-INF/MANIFESR.MF".equals(name)) {
manifestEntry = (JarEntry) ze;
}
return ze;
}
JarEntry getMetaInfEntry() {
return metaInfEntry;
}
JarEntry getManifestEntry() {
return manifestEntry;
}
}

View File

@@ -0,0 +1,177 @@
/*
* 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.catalina.webresources;
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.catalina.webresources.war.Handler;
public class TomcatURLStreamHandlerFactory implements URLStreamHandlerFactory {
private static final String WAR_PROTOCOL = "war";
private static final String CLASSPATH_PROTOCOL = "classpath";
// Singleton instance
private static volatile TomcatURLStreamHandlerFactory instance = null;
/**
* Obtain a reference to the singleton instance. It is recommended that
* callers check the value of {@link #isRegistered()} before using the
* returned instance.
*
* @return A reference to the singleton instance
*/
public static TomcatURLStreamHandlerFactory getInstance() {
getInstanceInternal(true);
return instance;
}
private static TomcatURLStreamHandlerFactory getInstanceInternal(boolean register) {
// Double checked locking. OK because instance is volatile.
if (instance == null) {
synchronized (TomcatURLStreamHandlerFactory.class) {
if (instance == null) {
instance = new TomcatURLStreamHandlerFactory(register);
}
}
}
return instance;
}
private final boolean registered;
// List of factories for application defined stream handler factories.
private final List<URLStreamHandlerFactory> userFactories =
new CopyOnWriteArrayList<>();
/**
* Register this factory with the JVM. May be called more than once. The
* implementation ensures that registration only occurs once.
*
* @return <code>true</code> if the factory is already registered with the
* JVM or was successfully registered as a result of this call.
* <code>false</code> if the factory was disabled prior to this
* call.
*/
public static boolean register() {
return getInstanceInternal(true).isRegistered();
}
/**
* Prevent this this factory from registering with the JVM. May be called
* more than once.
*
* @return <code>true</code> if the factory is already disabled or was
* successfully disabled as a result of this call.
* <code>false</code> if the factory was already registered prior
* to this call.
*/
public static boolean disable() {
return !getInstanceInternal(false).isRegistered();
}
/**
* Release references to any user provided factories that have been loaded
* using the provided class loader. Called during web application stop to
* prevent memory leaks.
*
* @param classLoader The class loader to release
*/
public static void release(ClassLoader classLoader) {
if (instance == null) {
return;
}
List<URLStreamHandlerFactory> factories = instance.userFactories;
for (URLStreamHandlerFactory factory : factories) {
ClassLoader factoryLoader = factory.getClass().getClassLoader();
while (factoryLoader != null) {
if (classLoader.equals(factoryLoader)) {
// Implementation note: userFactories is a
// CopyOnWriteArrayList, so items are removed with
// List.remove() instead of usual Iterator.remove()
factories.remove(factory);
break;
}
factoryLoader = factoryLoader.getParent();
}
}
}
private TomcatURLStreamHandlerFactory(boolean register) {
// Hide default constructor
// Singleton pattern to ensure there is only one instance of this
// factory
this.registered = register;
if (register) {
URL.setURLStreamHandlerFactory(this);
}
}
public boolean isRegistered() {
return registered;
}
/**
* Since the JVM only allows a single call to
* {@link URL#setURLStreamHandlerFactory(URLStreamHandlerFactory)} and
* Tomcat needs to register a handler, provide a mechanism to allow
* applications to register their own handlers.
*
* @param factory The user provided factory to add to the factories Tomcat
* has already registered
*/
public void addUserFactory(URLStreamHandlerFactory factory) {
userFactories.add(factory);
}
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
// Tomcat's handler always takes priority so applications can't override
// it.
if (WAR_PROTOCOL.equals(protocol)) {
return new Handler();
} else if (CLASSPATH_PROTOCOL.equals(protocol)) {
return new ClasspathURLStreamHandler();
}
// Application handlers
for (URLStreamHandlerFactory factory : userFactories) {
URLStreamHandler handler =
factory.createURLStreamHandler(protocol);
if (handler != null) {
return handler;
}
}
// Unknown protocol
return null;
}
}

View File

@@ -0,0 +1,112 @@
/*
* 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.catalina.webresources;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.catalina.TrackedWebResource;
import org.apache.catalina.WebResourceRoot;
class TrackedInputStream extends InputStream implements TrackedWebResource {
private final WebResourceRoot root;
private final String name;
private final InputStream is;
private final Exception creation;
TrackedInputStream(WebResourceRoot root, String name, InputStream is) {
this.root = root;
this.name = name;
this.is = is;
this.creation = new Exception();
root.registerTrackedResource(this);
}
@Override
public int read() throws IOException {
return is.read();
}
@Override
public int read(byte[] b) throws IOException {
return is.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return is.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
return is.skip(n);
}
@Override
public int available() throws IOException {
return is.available();
}
@Override
public void close() throws IOException {
root.deregisterTrackedResource(this);
is.close();
}
@Override
public synchronized void mark(int readlimit) {
is.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
is.reset();
}
@Override
public boolean markSupported() {
return is.markSupported();
}
@Override
public String getName() {
return name;
}
@Override
public Exception getCreatedBy() {
return creation;
}
@Override
public String toString() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
sw.append('[');
sw.append(name);
sw.append(']');
sw.append(System.lineSeparator());
creation.printStackTrace(pw);
pw.flush();
return sw.toString();
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.catalina.webresources;
import org.apache.catalina.WebResourceRoot;
public class VirtualResource extends EmptyResource {
private final String name;
public VirtualResource(WebResourceRoot root, String webAppPath,
String name) {
super(root, webAppPath);
this.name = name;
}
@Override
public boolean isVirtual() {
return true;
}
@Override
public boolean isDirectory() {
return true;
}
@Override
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.catalina.webresources;
import java.util.jar.JarEntry;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.UriUtil;
/**
* Represents a single resource (file or directory) that is located within a
* WAR.
*/
public class WarResource extends AbstractSingleArchiveResource {
private static final Log log = LogFactory.getLog(WarResource.class);
public WarResource(AbstractArchiveResourceSet archiveResourceSet, String webAppPath,
String baseUrl, JarEntry jarEntry) {
super(archiveResourceSet, webAppPath, "war:" + baseUrl + UriUtil.getWarSeparator(),
jarEntry, baseUrl);
}
@Override
protected Log getLog() {
return log;
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.catalina.webresources;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;
import org.apache.catalina.WebResource;
import org.apache.catalina.WebResourceRoot;
/**
* Represents a {@link org.apache.catalina.WebResourceSet} based on a WAR file.
*/
public class WarResourceSet extends AbstractSingleArchiveResourceSet {
/**
* A no argument constructor is required for this to work with the digester.
*/
public WarResourceSet() {
}
/**
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a WAR
* file.
*
* @param root The {@link WebResourceRoot} this new
* {@link org.apache.catalina.WebResourceSet} will
* be added to.
* @param webAppMount The path within the web application at which this
* {@link org.apache.catalina.WebResourceSet} will
* be mounted.
* @param base The absolute path to the WAR file on the file system
* from which the resources will be served.
*
* @throws IllegalArgumentException if the webAppMount is not valid (valid
* paths must start with '/')
*/
public WarResourceSet(WebResourceRoot root, String webAppMount, String base)
throws IllegalArgumentException {
super(root, webAppMount, base, "/");
}
@Override
protected WebResource createArchiveResource(JarEntry jarEntry,
String webAppPath, Manifest manifest) {
return new WarResource(this, webAppPath, getBaseUrlString(), jarEntry);
}
}

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<mbeans-descriptors>
<mbean name="StandardRoot"
description="Provides the resources implementation for a web application"
domain="Catalina"
group="WebResourceRoot"
type="org.apache.catalina.webresources.StandardRoot">
<attribute name="allowLinking"
description="Does this resources implementation allow the use of symbolic links?"
type="boolean"
writeable="true"/>
<attribute name="cachingAllowed"
description="Is in memory caching of resource content and metadata enabled?"
type="boolean"
is="true"
writeable="true"/>
<attribute name="stateName"
description="The current Lifecycle state of this object"
type="java.lang.String"
writeable="false"/>
<attribute name="trackLockedFiles"
description="Does this resources implementation track requests that lock files?"
type="boolean"
writeable="true"/>
<attribute name="trackedResources"
description="List of resources currently being tracked for possible resource leaks"
type="java.util.List"
writeable="false"/>
</mbean>
<mbean name="Cache"
description="Provides caching of resource metadata and content"
domain="Catalina"
group="WebResourceRoot"
type="org.apache.catalina.webresources.Cache">
<attribute name="hitCount"
description="The number of requests for resources that were served from the cache"
type="long"
writeable="false"/>
<attribute name="lookupCount"
description="The number of requests for resources"
type="long"
writeable="false"/>
<attribute name="maxSize"
description="The maximum permitted size of the cache in kB"
type="long"
writeable="true"/>
<attribute name="objectMaxSize"
description="The maximum permitted size for a single object in the cache in kB"
type="int"
writeable="true"/>
<attribute name="size"
description="The current estimate of the cache size in kB"
type="long"
writeable="false"/>
<attribute name="ttl"
description="The time-to-live for cache entries in milliseconds"
type="long"
writeable="true"/>
<operation name="clear"
description="Clears all cached content from the cache."
impact="ACTION"
returnType="void">
</operation>
</mbean>
</mbeans-descriptors>

View File

@@ -0,0 +1,46 @@
/*
* 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.catalina.webresources.war;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public class Handler extends URLStreamHandler {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return new WarURLConnection(u);
}
@Override
protected void setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String path,
String query, String ref) {
if (path.startsWith("file:") && !path.startsWith("file:/")) {
// Work around a problem with the URLs in the security policy file.
// On Windows, the use of ${catalina.[home|base]} in the policy file
// results in codebase URLs of the form file:C:/... when they should
// be file:/C:/...
// For file: and jar: URLs, the JRE compensates for this. It does not
// compensate for this for war:file:... URLs. Therefore, we do that
// here
path = "file:/" + path.substring(5);
}
super.setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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.catalina.webresources.war;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.security.Permission;
import org.apache.tomcat.util.buf.UriUtil;
public class WarURLConnection extends URLConnection {
private final URLConnection wrappedJarUrlConnection;
private boolean connected;
protected WarURLConnection(URL url) throws IOException {
super(url);
URL innerJarUrl = UriUtil.warToJar(url);
wrappedJarUrlConnection = innerJarUrl.openConnection();
}
@Override
public void connect() throws IOException {
if (!connected) {
wrappedJarUrlConnection.connect();
connected = true;
}
}
@Override
public InputStream getInputStream() throws IOException {
connect();
return wrappedJarUrlConnection.getInputStream();
}
@Override
public Permission getPermission() throws IOException {
return wrappedJarUrlConnection.getPermission();
}
@Override
public long getLastModified() {
return wrappedJarUrlConnection.getLastModified();
}
@Override
public int getContentLength() {
return wrappedJarUrlConnection.getContentLength();
}
@Override
public long getContentLengthLong() {
return wrappedJarUrlConnection.getContentLengthLong();
}
}