diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/FileID.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/FileID.java
index a237c8734dd5..b531b35486e6 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/FileID.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/FileID.java
@@ -74,7 +74,29 @@ public static String getFileName(String path)
return "";
int idx = path.lastIndexOf('/');
if (idx >= 0)
+ {
+ if (idx == path.length() - 1)
+ {
+ // we found the trailing slash
+ // eg: file:/path/to/dir/
+ // we want to return the "dir" segment here
+ int previousSlash = path.lastIndexOf('/', idx - 1);
+ if (previousSlash >= 0)
+ {
+ // we have a previous slash
+ // so return the segment without the trailing slash
+ return path.substring(previousSlash + 1, idx);
+ }
+ else
+ {
+ // we have no previous slash
+ // this input string is something like "foo/"
+ // so return the segment without trailing slash
+ return path.substring(0, idx);
+ }
+ }
return path.substring(idx + 1);
+ }
return path;
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
index a98a12f0dcf0..0305d60b22ae 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
@@ -143,7 +143,8 @@ public long length()
*
*
This is the last segment of the path.
*
- * @return the filename of the resource, or "" if there are no path segments (eg: path of "/"), or null if not backed by a Path
+ * @return the filename of the resource, or "" if there are no path segments (eg: path of "/"), or null if resource has no path.
+ * @see Path#getFileName()
*/
public abstract String getFileName();
diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/FileIDTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/FileIDTest.java
index 326130eef583..e6293cda3a33 100644
--- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/FileIDTest.java
+++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/FileIDTest.java
@@ -101,13 +101,14 @@ public static Stream fileNameSource()
{
return Stream.of(
Arguments.of(null, ""),
+ Arguments.of(URI.create("foo:bar"), "bar"),
Arguments.of(URI.create("file:/"), ""),
Arguments.of(URI.create("file:///"), ""),
- Arguments.of(URI.create("file:zed/"), ""),
+ Arguments.of(URI.create("file:zed/"), "zed"),
Arguments.of(URI.create("file:///path/to/test.txt"), "test.txt"),
- Arguments.of(URI.create("file:///path/to/dir/"), ""),
+ Arguments.of(URI.create("file:///path/to/dir/"), "dir"),
Arguments.of(URI.create("jar:file:///home/user/libs/jetty-server-12.jar!/org/eclipse/jetty/server/jetty-dir.css"), "jetty-dir.css"),
- Arguments.of(URI.create("http://eclipse.org/jetty/"), ""),
+ Arguments.of(URI.create("http://eclipse.org/jetty/"), "jetty"),
Arguments.of(URI.create("http://eclipse.org/jetty/index.html"), "index.html"),
Arguments.of(URI.create("http://eclipse.org/jetty/docs.html?query=val#anchor"), "docs.html")
);
diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java
index 429404ef9e31..8043d769c191 100644
--- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java
+++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/PathResourceTest.java
@@ -27,6 +27,7 @@
import java.util.HashMap;
import java.util.Map;
+import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
@@ -353,6 +354,25 @@ public void testNullCharEndingFilename(WorkDir workDir) throws Exception
}
}
+ @Test
+ public void testGetFileName(WorkDir workDir) throws IOException
+ {
+ Path tmpPath = workDir.getEmptyPathDir();
+ Path dir = tmpPath.resolve("foo-dir");
+ FS.ensureDirExists(dir);
+ Path file = dir.resolve("bar.txt");
+ Files.writeString(file, "This is bar.txt", StandardCharsets.UTF_8);
+
+ Resource baseResource = new PathResource(tmpPath);
+ assertThat(baseResource.getFileName(), is(tmpPath.getFileName().toString()));
+
+ Resource dirResource = baseResource.resolve("foo-dir");
+ assertThat(dirResource.getFileName(), is(dir.getFileName().toString()));
+
+ Resource fileResource = dirResource.resolve("bar.txt");
+ assertThat(fileResource.getFileName(), is(file.getFileName().toString()));
+ }
+
@Test
public void testSymlink(WorkDir workDir) throws Exception
{
diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java
index fdd81844f4b4..36037417d46e 100644
--- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java
+++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/resource/UrlResourceFactoryTest.java
@@ -27,15 +27,20 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
@@ -44,6 +49,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+@ExtendWith(WorkDirExtension.class)
public class UrlResourceFactoryTest
{
@Test
@@ -77,7 +83,7 @@ public void testHttps() throws IOException
assertThat(blogs.lastModified().toEpochMilli(), not(Instant.EPOCH));
assertThat(blogs.length(), not(-1));
assertTrue(blogs.isDirectory());
- assertThat(blogs.getFileName(), is(""));
+ assertThat(blogs.getFileName(), is("blog"));
Resource favicon = resource.resolve("favicon.ico");
assertThat(favicon, notNullValue());
@@ -93,6 +99,27 @@ public void testHttps() throws IOException
}
}
+ @Test
+ public void testGetFileName(WorkDir workDir) throws IOException
+ {
+ Path tmpPath = workDir.getEmptyPathDir();
+ Path dir = tmpPath.resolve("foo-dir");
+ FS.ensureDirExists(dir);
+ Path file = dir.resolve("bar.txt");
+ Files.writeString(file, "This is bar.txt", StandardCharsets.UTF_8);
+
+ URLResourceFactory urlResourceFactory = new URLResourceFactory();
+
+ Resource baseResource = urlResourceFactory.newResource(tmpPath);
+ assertThat(baseResource.getFileName(), endsWith(""));
+
+ Resource dirResource = baseResource.resolve("foo-dir/");
+ assertThat(dirResource.getFileName(), endsWith("foo-dir"));
+
+ Resource fileResource = dirResource.resolve("bar.txt");
+ assertThat(fileResource.getFileName(), endsWith("bar.txt"));
+ }
+
@Test
public void testInputStreamCleanedUp() throws Exception
{
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
index f5b18df21409..45f4637c2f3f 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
@@ -20,6 +20,7 @@
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
@@ -810,12 +811,15 @@ public Set getResourcePaths(String path)
Resource resource = getResource(path);
if (!path.endsWith("/"))
- path = path + "/";
+ path = path + '/';
HashSet set = new HashSet<>();
for (Resource item: resource.list())
{
- set.add(path + item.getFileName());
+ String entry = path + item.getFileName();
+ if (item.isDirectory())
+ entry = entry + '/';
+ set.add(entry);
}
return set;
}
@@ -2768,7 +2772,12 @@ else if (path.charAt(0) != '/')
{
Path resourcePath = r.getPath();
if (resourcePath != null)
- return resourcePath.normalize().toString();
+ {
+ String realPath = resourcePath.normalize().toString();
+ if (Files.isDirectory(resourcePath))
+ realPath = realPath + "/";
+ return realPath;
+ }
}
}
diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ServletRequestListenerTest.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ServletRequestListenerTest.java
index 1beae0705bf0..db9ab23eaec8 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ServletRequestListenerTest.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ServletRequestListenerTest.java
@@ -15,7 +15,6 @@
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.function.Consumer;
@@ -46,6 +45,7 @@
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
public class ServletRequestListenerTest
@@ -316,7 +316,7 @@ private void assertEventsEmpty()
private void assertEvents(String... events)
{
- assertThat(_events, equalTo(Arrays.asList(events)));
+ assertThat(_events, contains(events));
_events.clear();
}
}
diff --git a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/WebAppContextTest.java b/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/WebAppContextTest.java
index 14238ec39952..ad0b0f4937a7 100644
--- a/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/WebAppContextTest.java
+++ b/jetty-ee10/jetty-ee10-webapp/src/test/java/org/eclipse/jetty/ee10/webapp/WebAppContextTest.java
@@ -22,6 +22,7 @@
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -512,8 +513,8 @@ public void testGetResourceFromCollection() throws Exception
WebAppContext context = new WebAppContext();
context.setContextPath("/");
context.setBaseResource(ResourceFactory.combine(
- ResourceFactory.root().newResource(MavenTestingUtils.getTestResourcePath("wars/layer0/")),
- ResourceFactory.root().newResource(MavenTestingUtils.getTestResourcePath("wars/layer1/"))));
+ context.getResourceFactory().newResource(MavenTestingUtils.getTestResourcePath("wars/layer0/")),
+ context.getResourceFactory().newResource(MavenTestingUtils.getTestResourcePath("wars/layer1/"))));
server.setHandler(context);
server.start();
@@ -530,13 +531,39 @@ public void testGetResourcePathsFromCollection() throws Exception
WebAppContext context = new WebAppContext();
context.setContextPath("/");
context.setBaseResource(ResourceFactory.combine(
- ResourceFactory.root().newResource(MavenTestingUtils.getTestResourcePath("wars/layer0/")),
- ResourceFactory.root().newResource(MavenTestingUtils.getTestResourcePath("wars/layer1/"))));
+ context.getResourceFactory().newResource(MavenTestingUtils.getTestResourcePath("wars/layer0/")),
+ context.getResourceFactory().newResource(MavenTestingUtils.getTestResourcePath("wars/layer1/"))));
server.setHandler(context);
server.start();
ServletContext servletContext = context.getServletContext();
- assertThat(servletContext.getResourcePaths("/WEB-INF"), containsInAnyOrder("/WEB-INF/zero.xml", "/WEB-INF/one.xml"));
+ assertThat(servletContext.getResourcePaths("/WEB-INF/"), containsInAnyOrder("/WEB-INF/zero.xml", "/WEB-INF/one.xml"));
+ }
+
+ @Test
+ public void testGetResourcePathsWithDirsFromCollection() throws Exception
+ {
+ Server server = newServer();
+
+ WebAppContext context = new WebAppContext();
+ context.setContextPath("/");
+ context.setBaseResource(ResourceFactory.combine(
+ context.getResourceFactory().newResource(MavenTestingUtils.getTestResourcePath("wars/layer0/")),
+ context.getResourceFactory().newResource(MavenTestingUtils.getTestResourcePath("wars/layer1/")),
+ context.getResourceFactory().newResource(MavenTestingUtils.getTestResourcePath("wars/with_dirs/"))
+ ));
+ server.setHandler(context);
+ server.start();
+
+ ServletContext servletContext = context.getServletContext();
+ Set results = servletContext.getResourcePaths("/WEB-INF/");
+ String[] expected = {
+ "/WEB-INF/zero.xml",
+ "/WEB-INF/one.xml",
+ "/WEB-INF/bar/",
+ "/WEB-INF/foo/"
+ };
+ assertThat(results, containsInAnyOrder(expected));
}
@Test
@@ -552,18 +579,21 @@ public void testGetResourcePaths() throws Exception
ServletContext servletContext = context.getServletContext();
- List resourcePaths = List.copyOf(servletContext.getResourcePaths("/"));
+ Set resourcePaths = servletContext.getResourcePaths("/");
+ String[] expected = {
+ "/WEB-INF/",
+ "/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt",
+ };
assertThat(resourcePaths.size(), is(2));
- assertThat(resourcePaths.get(0), is("/WEB-INF"));
- assertThat(resourcePaths.get(1), is("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"));
+ assertThat(resourcePaths, containsInAnyOrder(expected));
String realPath = servletContext.getRealPath("/");
assertThat(realPath, notNullValue());
- assertThat(servletContext.getRealPath(resourcePaths.get(0)), endsWith("/WEB-INF"));
+ assertThat(servletContext.getRealPath("/WEB-INF/"), endsWith("/WEB-INF/"));
// TODO the following assertion fails because of a bug in the JDK (see JDK-8311079 and MountedPathResourceTest.testJarFileResourceAccessBackSlash())
//assertThat(servletContext.getRealPath(resourcePaths.get(1)), endsWith("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"));
- assertThat(servletContext.getResource("/WEB-INF"), notNullValue());
+ assertThat(servletContext.getResource("/WEB-INF/"), notNullValue());
// TODO the following assertion fails because of a bug in the JDK (see JDK-8311079 and MountedPathResourceTest.testJarFileResourceAccessBackSlash())
//assertThat(servletContext.getResource("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"), notNullValue());
diff --git a/jetty-ee10/jetty-ee10-webapp/src/test/resources/wars/with_dirs/WEB-INF/bar/main.txt b/jetty-ee10/jetty-ee10-webapp/src/test/resources/wars/with_dirs/WEB-INF/bar/main.txt
new file mode 100644
index 000000000000..2af19ef08b60
--- /dev/null
+++ b/jetty-ee10/jetty-ee10-webapp/src/test/resources/wars/with_dirs/WEB-INF/bar/main.txt
@@ -0,0 +1 @@
+This is the "main.txt" in the /bar/ directory for war "with_dirs"
\ No newline at end of file
diff --git a/jetty-ee10/jetty-ee10-webapp/src/test/resources/wars/with_dirs/WEB-INF/foo/alt.txt b/jetty-ee10/jetty-ee10-webapp/src/test/resources/wars/with_dirs/WEB-INF/foo/alt.txt
new file mode 100644
index 000000000000..d2302efab927
--- /dev/null
+++ b/jetty-ee10/jetty-ee10-webapp/src/test/resources/wars/with_dirs/WEB-INF/foo/alt.txt
@@ -0,0 +1 @@
+This is the "alt.txt" in the /foo/ directory for war "with_dirs"
\ No newline at end of file
diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
index db70e7398dcf..1871df9d5513 100644
--- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
+++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
@@ -18,6 +18,7 @@
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1568,16 +1569,16 @@ public Set getResourcePaths(String path)
Resource resource = getResource(path);
if (!path.endsWith("/"))
- path = path + "/";
+ path = path + '/';
HashSet set = new HashSet<>();
- for (Resource r: resource)
+ for (Resource item: resource.list())
{
- for (Resource item: r.list())
- {
- set.add(path + item.getFileName());
- }
+ String entry = path + item.getFileName();
+ if (item.isDirectory())
+ entry = entry + '/';
+ set.add(entry);
}
return set;
}
@@ -1907,9 +1908,24 @@ else if (path.charAt(0) != '/')
Resource resource = ContextHandler.this.getResource(path);
if (resource != null)
{
- Path resourcePath = resource.getPath();
- if (resourcePath != null)
- return resourcePath.toAbsolutePath().normalize().toString();
+ for (Resource r : resource)
+ {
+ // return first
+ if (Resources.exists(r))
+ {
+ Path resourcePath = r.getPath();
+ if (resourcePath != null)
+ {
+ String realPath = resourcePath.normalize().toString();
+ if (Files.isDirectory(resourcePath))
+ realPath = realPath + "/";
+ return realPath;
+ }
+ }
+ }
+
+ // A Resource was returned, but did not exist
+ return null;
}
}
catch (Exception e)
diff --git a/jetty-ee9/jetty-ee9-webapp/pom.xml b/jetty-ee9/jetty-ee9-webapp/pom.xml
index 0dee5f1711ed..24b7f3244b09 100644
--- a/jetty-ee9/jetty-ee9-webapp/pom.xml
+++ b/jetty-ee9/jetty-ee9-webapp/pom.xml
@@ -37,6 +37,10 @@
src/test/webapp
webapp
+
+ src/test/webapp-with-resources
+ webapp-with-resources
+
src/test/webapp-alt-jsp
webapp-alt-jsp
@@ -50,6 +54,8 @@
@{argLine} ${jetty.surefire.argLine}
+ --add-exports org.eclipse.jetty.ee9.webapp/org.acme.webapp=org.eclipse.jetty.ee9.servlet
+ --add-exports org.eclipse.jetty.ee9.webapp/org.acme.webapp=org.eclipse.jetty.ee9.nested
false
diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/java/org/acme/webapp/GetRealPathsServlet.java b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/acme/webapp/GetRealPathsServlet.java
new file mode 100644
index 000000000000..a99fabdbf90d
--- /dev/null
+++ b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/acme/webapp/GetRealPathsServlet.java
@@ -0,0 +1,49 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.acme.webapp;
+
+import java.io.IOException;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+public class GetRealPathsServlet extends HttpServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
+ {
+ collectResourcePaths("/").stream()
+ .map(p -> getServletContext().getRealPath(p))
+ .forEach(resp.getWriter()::println);
+ resp.getWriter().flush();
+ }
+
+ private Set collectResourcePaths(String path)
+ {
+ Set allResourcePaths = new LinkedHashSet<>();
+ Set pathsForPath = getServletContext().getResourcePaths(path);
+ if (pathsForPath != null)
+ {
+ for (String resourcePath : pathsForPath)
+ {
+ allResourcePaths.add(resourcePath);
+ allResourcePaths.addAll(collectResourcePaths(resourcePath));
+ }
+ }
+ return allResourcePaths;
+ }
+}
diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/java/org/acme/webapp/GetResourcePathsServlet.java b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/acme/webapp/GetResourcePathsServlet.java
new file mode 100644
index 000000000000..2c7aebf9049b
--- /dev/null
+++ b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/acme/webapp/GetResourcePathsServlet.java
@@ -0,0 +1,47 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.acme.webapp;
+
+import java.io.IOException;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+public class GetResourcePathsServlet extends HttpServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
+ {
+ collectResourcePaths("/").forEach(resp.getWriter()::println);
+ resp.getWriter().flush();
+ }
+
+ private Set collectResourcePaths(String path)
+ {
+ Set allResourcePaths = new LinkedHashSet<>();
+ Set pathsForPath = getServletContext().getResourcePaths(path);
+ if (pathsForPath != null)
+ {
+ for (String resourcePath : pathsForPath)
+ {
+ allResourcePaths.add(resourcePath);
+ allResourcePaths.addAll(collectResourcePaths(resourcePath));
+ }
+ }
+ return allResourcePaths;
+ }
+}
diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/WebAppContextTest.java b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/WebAppContextTest.java
index 62679431c8c8..99f9c2b81485 100644
--- a/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/WebAppContextTest.java
+++ b/jetty-ee9/jetty-ee9-webapp/src/test/java/org/eclipse/jetty/ee9/webapp/WebAppContextTest.java
@@ -22,6 +22,7 @@
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -49,6 +50,7 @@
import org.eclipse.jetty.util.FileID;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.resource.FileSystemPool;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
@@ -68,8 +70,13 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -86,7 +93,7 @@ public class WebAppContextTest
@BeforeEach
public void beforeEach()
{
- //assertThat(FileSystemPool.INSTANCE.mounts(), empty());
+ assertThat(FileSystemPool.INSTANCE.mounts(), empty());
}
@AfterEach
@@ -94,7 +101,7 @@ public void tearDown()
{
lifeCycles.forEach(LifeCycle::stop);
Configurations.cleanKnown();
- //assertThat(FileSystemPool.INSTANCE.mounts(), empty());
+ assertThat(FileSystemPool.INSTANCE.mounts(), empty());
}
private Server newServer()
@@ -349,7 +356,7 @@ public void testAlias(WorkDir workDir) throws Exception
FS.touch(someClass);
WebAppContext context = new WebAppContext();
- context.setBaseResource(ResourceFactory.root().newResource(tempDir));
+ context.setBaseResource(context.getResourceFactory().newResource(tempDir));
context.setResourceAlias("/WEB-INF/classes/", "/classes/");
@@ -380,7 +387,7 @@ public void testProtectedTarget() throws Exception
WebAppContext context = new WebAppContext();
Path testWebapp = MavenTestingUtils.getTargetPath("test-classes/webapp");
- context.setBaseResource(ResourceFactory.root().newResource(testWebapp));
+ context.setBaseResource(context.getResourceFactory().newResource(testWebapp));
context.setContextPath("/");
contexts.addHandler(context);
@@ -441,7 +448,7 @@ public void testProtectedTargetFailure(String path) throws Exception
WebAppContext context = new WebAppContext();
Path testWebapp = MavenTestingUtils.getTargetPath("test-classes/webapp");
- context.setBaseResource(ResourceFactory.root().newResource(testWebapp));
+ context.setBaseResource(context.getResourceFactory().newResource(testWebapp));
context.setContextPath("/");
contexts.addHandler(context);
@@ -495,7 +502,7 @@ public void testNullSessionAndSecurityHandler() throws Exception
ServletContextHandler.NO_SESSIONS | ServletContextHandler.NO_SECURITY);
context.setContextPath("/");
Path testWebapp = MavenTestingUtils.getTargetPath("test-classes/webapp");
- context.setBaseResource(ResourceFactory.root().newResource(testWebapp));
+ context.setBaseResource(context.getResourceFactory().newResource(testWebapp));
contexts.addHandler(context);
LocalConnector connector = new LocalConnector(server);
@@ -546,6 +553,132 @@ public void testBaseResourceAbsolutePath() throws Exception
assertTrue(context.isAvailable(), "WebAppContext should be available");
}
+ @Test
+ public void testGetResourceFromCollection() throws Exception
+ {
+ Server server = newServer();
+
+ WebAppContext context = new WebAppContext();
+ context.setContextPath("/");
+ context.setBaseResource(ResourceFactory.combine(
+ context.getResourceFactory().newResource(MavenPaths.findTestResourceDir("wars/layer0/")),
+ context.getResourceFactory().newResource(MavenPaths.findTestResourceDir("wars/layer1/"))));
+ server.setHandler(context);
+ server.start();
+
+ ServletContext servletContext = context.getServletContext();
+ assertThat(servletContext.getResource("/WEB-INF/zero.xml"), notNullValue());
+ assertThat(servletContext.getResource("/WEB-INF/one.xml"), notNullValue());
+ }
+
+ @Test
+ public void testGetResourcePathsFromCollection() throws Exception
+ {
+ Server server = newServer();
+
+ WebAppContext context = new WebAppContext();
+ context.setContextPath("/");
+ context.setBaseResource(ResourceFactory.combine(
+ context.getResourceFactory().newResource(MavenPaths.findTestResourceDir("wars/layer0/")),
+ context.getResourceFactory().newResource(MavenPaths.findTestResourceDir("wars/layer1/"))));
+ server.setHandler(context);
+ server.start();
+
+ ServletContext servletContext = context.getServletContext();
+ assertThat(servletContext.getResourcePaths("/WEB-INF/"), containsInAnyOrder("/WEB-INF/zero.xml", "/WEB-INF/one.xml"));
+ }
+
+ @Test
+ public void testGetResourcePathsWithDirsFromCollection() throws Exception
+ {
+ Server server = newServer();
+
+ WebAppContext context = new WebAppContext();
+ context.setContextPath("/");
+ context.setBaseResource(ResourceFactory.combine(
+ context.getResourceFactory().newResource(MavenPaths.findTestResourceDir("wars/layer0/")),
+ context.getResourceFactory().newResource(MavenPaths.findTestResourceDir("wars/layer1/")),
+ context.getResourceFactory().newResource(MavenPaths.findTestResourceDir("wars/with_dirs/"))
+ ));
+ server.setHandler(context);
+ server.start();
+
+ ServletContext servletContext = context.getServletContext();
+ Set results = servletContext.getResourcePaths("/WEB-INF/");
+ String[] expected = {
+ "/WEB-INF/zero.xml",
+ "/WEB-INF/one.xml",
+ "/WEB-INF/bar/",
+ "/WEB-INF/foo/"
+ };
+ assertThat(results, containsInAnyOrder(expected));
+ }
+
+ @Test
+ @Disabled("There is extra decoding of the nested-reserved paths that is getting in the way")
+ public void testGetResourcePaths() throws Exception
+ {
+ Server server = newServer();
+ LocalConnector connector = new LocalConnector(server);
+ server.addConnector(connector);
+
+ Path warRoot = MavenPaths.findTestResourceDir("webapp-with-resources");
+ assertTrue(Files.isDirectory(warRoot), "Unable to find directory: " + warRoot);
+ WebAppContext context = new WebAppContext();
+ Resource warResource = context.getResourceFactory().newResource(warRoot);
+ context.setWarResource(warResource);
+ context.setContextPath("/");
+ server.setHandler(context);
+ server.start();
+
+ ServletContext servletContext = context.getServletContext();
+
+ Set resourcePaths = servletContext.getResourcePaths("/");
+ String[] expected = {
+ "/WEB-INF/",
+ "/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt",
+ };
+ assertThat(resourcePaths.size(), is(2));
+ assertThat(resourcePaths, containsInAnyOrder(expected));
+
+ String realPath = servletContext.getRealPath("/");
+ assertThat(realPath, notNullValue());
+ assertThat(servletContext.getRealPath("/WEB-INF/"), endsWith("/WEB-INF/"));
+ // TODO the following assertion fails because of a bug in the JDK (see JDK-8311079 and MountedPathResourceTest.testJarFileResourceAccessBackSlash())
+ //assertThat(servletContext.getRealPath(resourcePaths.get(1)), endsWith("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"));
+
+ assertThat(servletContext.getResource("/WEB-INF/"), notNullValue());
+ // TODO the following assertion fails because of a bug in the JDK (see JDK-8311079 and MountedPathResourceTest.testJarFileResourceAccessBackSlash())
+ //assertThat(servletContext.getResource("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"), notNullValue());
+
+ HttpTester.Response response1 = HttpTester.parseResponse(connector.getResponse("""
+ GET /resource HTTP/1.1\r
+ Host: local\r
+ Connection: close\r
+ \r
+ """));
+
+ assertThat(response1.getStatus(), is(HttpStatus.OK_200));
+ assertThat(response1.getContent(), containsString("/WEB-INF"));
+ assertThat(response1.getContent(), containsString("/WEB-INF/lib"));
+ assertThat(response1.getContent(), containsString("/WEB-INF/lib/odd-resource.jar"));
+ assertThat(response1.getContent(), containsString("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"));
+
+ HttpTester.Response response2 = HttpTester.parseResponse(connector.getResponse("""
+ GET /real HTTP/1.1\r
+ Host: local\r
+ Connection: close\r
+ \r
+ """));
+
+ assertThat(response2.getStatus(), is(HttpStatus.OK_200));
+ assertThat(response2.getContent(), containsString("/WEB-INF"));
+ assertThat(response2.getContent(), containsString("/WEB-INF/lib"));
+ assertThat(response2.getContent(), containsString("/WEB-INF/lib/odd-resource.jar"));
+ // TODO the following assertion fails because of a bug in the JDK (see JDK-8311079 and MountedPathResourceTest.testJarFileResourceAccessBackSlash())
+ //assertThat(response2.getContent(), containsString("/nested-reserved-!#\\\\$%&()*+,:=?@[]-meta-inf-resource.txt"));
+ }
+
public static Stream extraClasspathGlob()
{
List references = new ArrayList<>();
diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/resources/wars/layer0/WEB-INF/zero.xml b/jetty-ee9/jetty-ee9-webapp/src/test/resources/wars/layer0/WEB-INF/zero.xml
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/resources/wars/layer1/WEB-INF/one.xml b/jetty-ee9/jetty-ee9-webapp/src/test/resources/wars/layer1/WEB-INF/one.xml
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/resources/wars/with_dirs/WEB-INF/bar/main.txt b/jetty-ee9/jetty-ee9-webapp/src/test/resources/wars/with_dirs/WEB-INF/bar/main.txt
new file mode 100644
index 000000000000..2af19ef08b60
--- /dev/null
+++ b/jetty-ee9/jetty-ee9-webapp/src/test/resources/wars/with_dirs/WEB-INF/bar/main.txt
@@ -0,0 +1 @@
+This is the "main.txt" in the /bar/ directory for war "with_dirs"
\ No newline at end of file
diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/resources/wars/with_dirs/WEB-INF/foo/alt.txt b/jetty-ee9/jetty-ee9-webapp/src/test/resources/wars/with_dirs/WEB-INF/foo/alt.txt
new file mode 100644
index 000000000000..d2302efab927
--- /dev/null
+++ b/jetty-ee9/jetty-ee9-webapp/src/test/resources/wars/with_dirs/WEB-INF/foo/alt.txt
@@ -0,0 +1 @@
+This is the "alt.txt" in the /foo/ directory for war "with_dirs"
\ No newline at end of file
diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/webapp-with-resources/WEB-INF/lib/odd-resource.jar b/jetty-ee9/jetty-ee9-webapp/src/test/webapp-with-resources/WEB-INF/lib/odd-resource.jar
new file mode 100644
index 000000000000..5c78785fd6a3
Binary files /dev/null and b/jetty-ee9/jetty-ee9-webapp/src/test/webapp-with-resources/WEB-INF/lib/odd-resource.jar differ
diff --git a/jetty-ee9/jetty-ee9-webapp/src/test/webapp-with-resources/WEB-INF/web.xml b/jetty-ee9/jetty-ee9-webapp/src/test/webapp-with-resources/WEB-INF/web.xml
new file mode 100644
index 000000000000..612b39d55f58
--- /dev/null
+++ b/jetty-ee9/jetty-ee9-webapp/src/test/webapp-with-resources/WEB-INF/web.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ GetResourcePathsServlet
+ org.acme.webapp.GetResourcePathsServlet
+ 1
+
+
+ GetRealPathsServlet
+ org.acme.webapp.GetRealPathsServlet
+ 1
+
+
+
+ GetResourcePathsServlet
+ /resource/*
+
+
+ GetRealPathsServlet
+ /real/*
+
+
+
+