diff --git a/java/org/apache/catalina/webresources/AbstractFileResourceSet.java b/java/org/apache/catalina/webresources/AbstractFileResourceSet.java index 6d7961c956d0..3dbcfa654813 100644 --- a/java/org/apache/catalina/webresources/AbstractFileResourceSet.java +++ b/java/org/apache/catalina/webresources/AbstractFileResourceSet.java @@ -17,9 +17,11 @@ package org.apache.catalina.webresources; import java.io.File; -import java.io.IOException; +import java.io.IOError; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import org.apache.catalina.LifecycleException; import org.apache.juli.logging.Log; @@ -33,16 +35,20 @@ public abstract class AbstractFileResourceSet extends AbstractResourceSet { protected static final String[] EMPTY_STRING_ARRAY = new String[0]; - private File fileBase; + private Path fileBase; private String absoluteBase; - private String canonicalBase; private boolean readOnly = false; protected AbstractFileResourceSet(String internalPath) { setInternalPath(internalPath); } + @Deprecated protected final File getFileBase() { + return fileBase.toFile(); + } + + protected final Path getPathBase() { return fileBase; } @@ -56,23 +62,32 @@ public boolean isReadOnly() { return readOnly; } + /** + * @deprecated use {@link #path(String, boolean)} instead. + */ + @Deprecated protected final File file(String name, boolean mustExist) { + final var path = path(name, mustExist); + return path == null ? null : path.toFile(); + } + + protected final Path path(String name, boolean mustExist) { if (name.equals("/")) { name = ""; } - File file = new File(fileBase, name); + Path file = fileBase.resolve(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()) { + if (name.endsWith("/") && Files.isRegularFile(file)) { 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()) { + if (mustExist && !Files.isReadable(file)) { return null; } @@ -91,11 +106,11 @@ protected final File file(String name, boolean mustExist) { // Check that this file is located under the WebResourceSet's base String canPath = null; try { - canPath = file.getCanonicalPath(); - } catch (IOException e) { + canPath = file.toAbsolutePath().toString(); + } catch (IOError e) { // Ignore } - if (canPath == null || !canPath.startsWith(canonicalBase)) { + if (canPath == null || !canPath.startsWith(absoluteBase)) { return null; } @@ -106,7 +121,7 @@ protected final File file(String name, boolean mustExist) { // 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()); + String absPath = normalize(canPath); if (absoluteBase.length() > absPath.length()) { return null; } @@ -115,10 +130,9 @@ protected final File file(String name, boolean mustExist) { // 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()); // The remaining request path must start with '/' if it has non-zero length - if (canPath.length() > 0 && canPath.charAt(0) != File.separatorChar) { + if (absPath.length() > 0 && absPath.charAt(0) != File.separatorChar) { return null; } @@ -225,26 +239,26 @@ public void gc() { @Override protected void initInternal() throws LifecycleException { - fileBase = new File(getBase(), getInternalPath()); + fileBase = getInternalBase().resolve(getInternalPath()); checkType(fileBase); - this.absoluteBase = normalize(fileBase.getAbsolutePath()); - - try { - this.canonicalBase = fileBase.getCanonicalPath(); - } catch (IOException e) { - throw new IllegalArgumentException(e); - } + this.absoluteBase = normalize(fileBase.toAbsolutePath().toString()); // Need to handle mapping of the file system root as a special case if ("/".equals(this.absoluteBase)) { this.absoluteBase = ""; } - if ("/".equals(this.canonicalBase)) { - this.canonicalBase = ""; - } } - protected abstract void checkType(File file); + @Deprecated + protected void checkType(File file) { + checkType(file.toPath()); + } + + protected abstract void checkType(Path file); + + protected Path getInternalBase() { // absolute Path.of() can fail on windows depending the path in some version + return new File(getBase()).toPath(); + } } diff --git a/java/org/apache/catalina/webresources/DirResourceSet.java b/java/org/apache/catalina/webresources/DirResourceSet.java index 3bd0245f0a04..316975523ccb 100644 --- a/java/org/apache/catalina/webresources/DirResourceSet.java +++ b/java/org/apache/catalina/webresources/DirResourceSet.java @@ -17,10 +17,10 @@ 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.Path; import java.nio.file.StandardCopyOption; import java.util.Set; import java.util.jar.Manifest; @@ -33,6 +33,8 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; +import static java.util.stream.Collectors.toSet; + /** * Represents a {@link org.apache.catalina.WebResourceSet} based on a directory. */ @@ -67,11 +69,9 @@ public DirResourceSet(WebResourceRoot root, String webAppMount, String base, Str 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, "/"); + final Path f = getInternalBase().resolve(internalPath).resolve("/WEB-INF/classes/META-INF/resources"); + if (Files.isDirectory(f)) { + root.createWebResourceSet(ResourceSetType.RESOURCE_JAR, "/", f.toAbsolutePath().toString(), null, "/"); } } @@ -84,21 +84,20 @@ public DirResourceSet(WebResourceRoot root, String webAppMount, String base, Str } } - @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); + Path f = path(path.substring(webAppMount.length()), false); if (f == null) { return new EmptyResource(root, path); } - if (!f.exists()) { + if (Files.notExists(f)) { return new EmptyResource(root, path, f); } - if (f.isDirectory() && path.charAt(path.length() - 1) != '/') { + if (Files.isDirectory(f) && path.charAt(path.length() - 1) != '/') { path = path + '/'; } return new FileResource(root, path, f, isReadOnly(), getManifest()); @@ -112,15 +111,14 @@ public String[] list(String path) { checkPath(path); String webAppMount = getWebAppMount(); if (path.startsWith(webAppMount)) { - File f = file(path.substring(webAppMount.length()), true); + Path f = path(path.substring(webAppMount.length()), true); if (f == null) { return EMPTY_STRING_ARRAY; } - String[] result = f.list(); - if (result == null) { + try (final var result = Files.list(f)) { + return result.map(Path::getFileName).map(Path::toString).toArray(String[]::new); + } catch (final IOException e) { return EMPTY_STRING_ARRAY; - } else { - return result; } } else { if (!path.endsWith("/")) { @@ -139,64 +137,63 @@ public String[] list(String path) { } @Override - public Set listWebAppPaths(String path) { + public Set listWebAppPaths(final String path) { checkPath(path); String webAppMount = getWebAppMount(); ResourceSet result = new ResourceSet<>(); if (path.startsWith(webAppMount)) { - File f = file(path.substring(webAppMount.length()), true); + Path f = path(path.substring(webAppMount.length()), true); if (f != null) { - File[] list = f.listFiles(); - if (list != null) { - for (File entry : list) { - // f has already been validated so the following checks - // can be much simpler than those in file() - if (!getRoot().getAllowLinking()) { - // allow linking is disabled so need to check for - // symlinks - boolean symlink = true; - String absPath = null; - String canPath = null; - try { + try (final var list = Files.list(f)) { + return list + .filter(entry -> { + if (!getRoot().getAllowLinking()) { + // allow linking is disabled so need to check for + // symlinks + boolean symlink = true; + String absPath = null; + String canPath = null; // We know that 'f' must be valid since it will // have been checked in the call to file() // above. Therefore strip off the path of the // path that was contributed by 'f' and check // that what is left does not contain a symlink. - absPath = entry.getAbsolutePath().substring(f.getAbsolutePath().length()); - if (entry.getCanonicalPath().length() >= f.getCanonicalPath().length()) { - canPath = entry.getCanonicalPath().substring(f.getCanonicalPath().length()); + final var entryPath = entry.toAbsolutePath().normalize().toString(); + final var fPath = f.toAbsolutePath().normalize().toString(); + absPath = entryPath.substring(fPath.length()); + if (entryPath.length() >= fPath.length()) { + canPath = entryPath.substring(fPath.length()); if (absPath.equals(canPath)) { symlink = false; } } - } catch (IOException ioe) { - // Ignore the exception. Assume we have a symlink. - canPath = "Unknown"; + if (symlink) { + logIgnoredSymlink(getRoot().getContext().getName(), absPath, canPath); + return false; + } + } + return true; + }) + .map(entry -> { + final StringBuilder sb = new StringBuilder(path); + if (path.charAt(path.length() - 1) != '/') { + sb.append('/'); } - if (symlink) { - logIgnoredSymlink(getRoot().getContext().getName(), absPath, canPath); - continue; + sb.append(entry.getFileName().toString()); + if (Files.isDirectory(entry)) { + sb.append('/'); } - } - 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()); - } + return sb.toString(); + }) + .collect(toSet()); + } catch (IOException e) { + throw new IllegalStateException(e); } } } else { - if (!path.endsWith("/")) { - path = path + "/"; - } - if (webAppMount.startsWith(path)) { - int i = webAppMount.indexOf('/', path.length()); + final var fixedPath = path.endsWith("/") ? path : (path + '/'); + if (webAppMount.startsWith(fixedPath)) { + int i = webAppMount.indexOf('/', fixedPath.length()); if (i == -1) { result.add(webAppMount + "/"); } else { @@ -216,11 +213,16 @@ public boolean mkdir(String path) { } String webAppMount = getWebAppMount(); if (path.startsWith(webAppMount)) { - File f = file(path.substring(webAppMount.length()), false); + Path f = path(path.substring(webAppMount.length()), false); if (f == null) { return false; } - return f.mkdir(); + try { + Files.createDirectory(f); + return true; + } catch (final IOException e) { + return false; + } } else { return false; } @@ -244,10 +246,10 @@ public boolean write(String path, InputStream is, boolean overwrite) { return false; } - File dest = null; + Path dest = null; String webAppMount = getWebAppMount(); if (path.startsWith(webAppMount)) { - dest = file(path.substring(webAppMount.length()), false); + dest = path(path.substring(webAppMount.length()), false); if (dest == null) { return false; } @@ -255,15 +257,15 @@ public boolean write(String path, InputStream is, boolean overwrite) { return false; } - if (dest.exists() && !overwrite) { + if (Files.exists(dest) && !overwrite) { return false; } try { if (overwrite) { - Files.copy(is, dest.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(is, dest, StandardCopyOption.REPLACE_EXISTING); } else { - Files.copy(is, dest.toPath()); + Files.copy(is, dest); } } catch (IOException ioe) { return false; @@ -273,8 +275,8 @@ public boolean write(String path, InputStream is, boolean overwrite) { } @Override - protected void checkType(File file) { - if (file.isDirectory() == false) { + protected void checkType(Path file) { + if (!Files.isDirectory(file)) { throw new IllegalArgumentException( sm.getString("dirResourceSet.notDirectory", getBase(), File.separator, getInternalPath())); } @@ -287,12 +289,12 @@ protected void initInternal() throws LifecycleException { // 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)) { + Path mf = path("META-INF/MANIFEST.MF", true); + if (mf != null && Files.isRegularFile(mf)) { + try (final var fis = Files.newInputStream(mf)) { setManifest(new Manifest(fis)); } catch (IOException e) { - log.warn(sm.getString("dirResourceSet.manifestFail", mf.getAbsolutePath()), e); + log.warn(sm.getString("dirResourceSet.manifestFail", mf.toString()), e); } } } diff --git a/java/org/apache/catalina/webresources/EmptyResource.java b/java/org/apache/catalina/webresources/EmptyResource.java index 99393fe1f5cd..58c410cf5531 100644 --- a/java/org/apache/catalina/webresources/EmptyResource.java +++ b/java/org/apache/catalina/webresources/EmptyResource.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.nio.file.Path; import java.security.cert.Certificate; import java.util.jar.Manifest; @@ -30,13 +31,21 @@ public class EmptyResource implements WebResource { private final WebResourceRoot root; private final String webAppPath; - private final File file; + private final Path file; public EmptyResource(WebResourceRoot root, String webAppPath) { - this(root, webAppPath, null); + this(root, webAppPath, (Path) null); } + /** + * @deprecated use Path flavor. + */ + @Deprecated public EmptyResource(WebResourceRoot root, String webAppPath, File file) { + this(root, webAppPath, file.toPath()); + } + + public EmptyResource(WebResourceRoot root, String webAppPath, Path file) { this.root = root; this.webAppPath = webAppPath; this.file = file; @@ -96,13 +105,8 @@ public long getContentLength() { public String getCanonicalPath() { if (file == null) { return null; - } else { - try { - return file.getCanonicalPath(); - } catch (IOException e) { - return null; - } } + return file.toAbsolutePath().toString(); } @Override diff --git a/java/org/apache/catalina/webresources/FileResource.java b/java/org/apache/catalina/webresources/FileResource.java index f1b674450bcf..7462b1c8846c 100644 --- a/java/org/apache/catalina/webresources/FileResource.java +++ b/java/org/apache/catalina/webresources/FileResource.java @@ -18,8 +18,6 @@ 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; @@ -27,6 +25,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.security.cert.Certificate; import java.util.jar.Manifest; @@ -57,20 +56,24 @@ public class FileResource extends AbstractResource { } - private final File resource; + private final Path 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) { + this(root, webAppPath, resource.toPath(), readOnly, manifest); + } + + public FileResource(WebResourceRoot root, String webAppPath, Path resource, boolean readOnly, Manifest manifest) { super(root, webAppPath); this.resource = resource; if (webAppPath.charAt(webAppPath.length() - 1) == '/') { - String realName = resource.getName() + '/'; + String realName = resource.getFileName().toString() + '/'; if (webAppPath.endsWith(realName)) { - name = resource.getName(); + name = resource.getFileName().toString(); } else { // This is the root directory of a mounted ResourceSet // Need to return the mounted name, not the real name @@ -79,7 +82,7 @@ public FileResource(WebResourceRoot root, String webAppPath, File resource, bool } } else { // Must be a file - name = resource.getName(); + name = resource.getFileName().toString(); } this.readOnly = readOnly; @@ -89,12 +92,16 @@ public FileResource(WebResourceRoot root, String webAppPath, File resource, bool @Override public long getLastModified() { - return resource.lastModified(); + try { + return Files.getLastModifiedTime(resource).toMillis(); + } catch (final IOException e) { + return -1; + } } @Override public boolean exists() { - return resource.exists(); + return Files.exists(resource); } @Override @@ -104,12 +111,12 @@ public boolean isVirtual() { @Override public boolean isDirectory() { - return resource.isDirectory(); + return Files.isDirectory(resource); } @Override public boolean isFile() { - return resource.isFile(); + return Files.isRegularFile(resource); } @Override @@ -117,7 +124,12 @@ public boolean delete() { if (readOnly) { return false; } - return resource.delete(); + try { + Files.delete(resource); + return true; + } catch (final IOException e) { + return false; + } } @Override @@ -144,24 +156,21 @@ private long getContentLengthInternal(boolean convert) { return -1; } - return resource.length(); + try { + return Files.size(resource); + } catch (IOException e) { + return -1; + } } @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; - } + return resource.toAbsolutePath().normalize().toString(); } @Override public boolean canRead() { - return resource.canRead(); + return Files.isReadable(resource); } @Override @@ -175,8 +184,8 @@ protected InputStream doGetInputStream() { } } try { - return new FileInputStream(resource); - } catch (FileNotFoundException fnfe) { + return Files.newInputStream(resource); + } catch (IOException fnfe) { // Race condition (file has been deleted) - not an error return null; } @@ -202,7 +211,7 @@ public final byte[] getContent() { byte[] result = new byte[size]; int pos = 0; - try (InputStream is = new FileInputStream(resource)) { + try (InputStream is = Files.newInputStream(resource)) { while (pos < size) { int n = is.read(result, pos, size - pos); if (n < 0) { @@ -236,11 +245,11 @@ public final byte[] getContent() { @Override public long getCreation() { try { - BasicFileAttributes attrs = Files.readAttributes(resource.toPath(), BasicFileAttributes.class); + BasicFileAttributes attrs = Files.readAttributes(resource, BasicFileAttributes.class); return attrs.creationTime().toMillis(); } catch (IOException e) { if (log.isDebugEnabled()) { - log.debug(sm.getString("fileResource.getCreationFail", resource.getPath()), e); + log.debug(sm.getString("fileResource.getCreationFail", resource.toString()), e); } return 0; } @@ -248,12 +257,12 @@ public long getCreation() { @Override public URL getURL() { - if (resource.exists()) { + if (Files.exists(resource)) { try { - return resource.toURI().toURL(); + return resource.toUri().toURL(); } catch (MalformedURLException e) { if (log.isDebugEnabled()) { - log.debug(sm.getString("fileResource.getUrlFail", resource.getPath()), e); + log.debug(sm.getString("fileResource.getUrlFail", resource.toString()), e); } return null; } diff --git a/java/org/apache/catalina/webresources/FileResourceSet.java b/java/org/apache/catalina/webresources/FileResourceSet.java index 035e2f7a4e0a..d58b5e58566f 100644 --- a/java/org/apache/catalina/webresources/FileResourceSet.java +++ b/java/org/apache/catalina/webresources/FileResourceSet.java @@ -18,6 +18,8 @@ import java.io.File; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Set; import org.apache.catalina.LifecycleException; @@ -159,8 +161,8 @@ public boolean write(String path, InputStream is, boolean overwrite) { } @Override - protected void checkType(File file) { - if (file.isFile() == false) { + protected void checkType(Path file) { + if (!Files.isRegularFile(file)) { throw new IllegalArgumentException( sm.getString("fileResourceSet.notFile", getBase(), File.separator, getInternalPath())); }