diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ResourceManagerTestCase.java b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ResourceManagerTestCase.java new file mode 100644 index 0000000000000..5bd77e6640383 --- /dev/null +++ b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ResourceManagerTestCase.java @@ -0,0 +1,59 @@ +package io.quarkus.undertow.test; + +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ResourceManagerTestCase { + + private static final String CONTEXT_PATH = "/foo"; + public static final String META_INF_RESOURCES = "META-INF/resources/"; + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(ContextPathServlet.class) + .addAsResource(new StringAsset("index.html"), "META-INF/resources/index.html") + .addAsResource(new StringAsset("foo/foo.html"), "META-INF/resources/foo/foo.html") + .addAsResource(new StringAsset("foo/bar/bar.html"), "META-INF/resources/foo/bar/bar.html")); + + @Test + public void testServlet() { + RestAssured.when().get("/").then() + .statusCode(200) + .body(is("[foo, index.html]")); + RestAssured.when().get("/foo").then() + .statusCode(200) + .body(is("[foo/bar, foo/foo.html]")); + RestAssured.when().get("/foo/bar").then() + .statusCode(200) + .body(is("[foo/bar/bar.html]")); + } + + @WebServlet(urlPatterns = "/*") + public static class ContextPathServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + var paths = req.getServletContext().getResourcePaths(req.getPathInfo() == null ? "/" : req.getPathInfo()); + resp.getWriter().write(String.valueOf(new TreeSet<>( + paths.stream().map(s -> s.substring(s.lastIndexOf(META_INF_RESOURCES) + META_INF_RESOURCES.length())) + .collect(Collectors.toSet())))); + } + } +} \ No newline at end of file diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java index eae7915b6a41a..fe426c649bf77 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/KnownPathResourceManager.java @@ -3,15 +3,17 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.io.UncheckedIOException; import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.NavigableSet; import java.util.Set; +import java.util.SortedSet; import java.util.TreeSet; import io.undertow.httpcore.OutputChannel; @@ -23,8 +25,10 @@ public class KnownPathResourceManager implements ResourceManager { + public static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows"); + private final NavigableSet files; - private final Set directories; + private final NavigableSet directories; private final ResourceManager underlying; public KnownPathResourceManager(Set files, Set directories, ResourceManager underlying) { @@ -51,7 +55,7 @@ public KnownPathResourceManager(Set files, Set directories, Reso tmp.add(i); } tmp.add(""); - this.directories = Collections.unmodifiableSet(tmp); + this.directories = new TreeSet<>(tmp); } @Override @@ -78,7 +82,11 @@ private class DirectoryResource implements Resource { private final String path; private DirectoryResource(String path) { - this.path = path; + this.path = evaluatePath(path); + } + + private String evaluatePath(String path) { + return IS_WINDOWS ? path.replaceAll("\\\\", "/") : path; } @Override @@ -118,18 +126,38 @@ public boolean isDirectory() { @Override public List list() { List ret = new ArrayList<>(); - String slashPath = path + "/"; - for (String i : files.headSet(path)) { - if (i.startsWith(slashPath)) { - try { - ret.add(underlying.getResource(i)); - } catch (IOException e) { - throw new RuntimeException(e); + String slashPath = path.isEmpty() ? path : path + "/"; + if (IS_WINDOWS) { + slashPath = slashPath.replaceAll("/", "\\\\"); // correct Windows paths + } + SortedSet fileSet = files.tailSet(slashPath); + SortedSet dirSet = directories.tailSet(slashPath); + + for (var s : List.of(fileSet, dirSet)) { + for (String i : s) { + if (i.equals(slashPath)) { + continue; + } + if (i.startsWith(slashPath)) { + i = evaluatePath(i); + if (!i.substring(slashPath.length()).contains("/")) { + try { + Resource resource = underlying.getResource(i); + if (resource == null) { + throw new RuntimeException("Unable to get listed resource " + i + " from directory " + path + + " for path " + slashPath + " from underlying manager " + underlying); + } + ret.add(resource); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } else { + break; } - } else { - break; } } + return ret; } @@ -184,4 +212,4 @@ public URL getUrl() { return null; } } -} +} \ No newline at end of file