From c5752bbc3c66b7d706ab6a8f02748bf28c43f187 Mon Sep 17 00:00:00 2001 From: gregw Date: Wed, 12 Jun 2024 16:31:30 +1000 Subject: [PATCH 01/18] Introduce ResourceServlet --- ...faultServlet.java => ResourceServlet.java} | 6 +- ...vletTest.java => ResourceServletTest.java} | 142 +++++++++--------- 2 files changed, 74 insertions(+), 74 deletions(-) rename jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/{DefaultServlet.java => ResourceServlet.java} (99%) rename jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/{DefaultServletTest.java => ResourceServletTest.java} (96%) diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java similarity index 99% rename from jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java rename to jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java index 7b1a0969bfff..9a729e73c22e 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java @@ -163,9 +163,9 @@ * * */ -public class DefaultServlet extends HttpServlet +public class ResourceServlet extends HttpServlet { - private static final Logger LOG = LoggerFactory.getLogger(DefaultServlet.class); + private static final Logger LOG = LoggerFactory.getLogger(ResourceServlet.class); public static final String CONTEXT_INIT = "org.eclipse.jetty.servlet.Default."; private ServletContextHandler _contextHandler; @@ -632,7 +632,7 @@ public String getWelcomeTarget(HttpContent content, Request coreRequest) ServletHandler.MappedServlet entry = _servletContextHandler.getServletHandler().getMappedServlet(welcomeInContext); // Is there a different Servlet that may serve the welcome resource? - if (entry != null && entry.getServletHolder().getServletInstance() != DefaultServlet.this) + if (entry != null && entry.getServletHolder().getServletInstance() != ResourceServlet.this) { if (_welcomeServletMode == WelcomeServletMode.MATCH || entry.getPathSpec().getDeclaration().equals(welcomeInContext)) { diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java similarity index 96% rename from jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java rename to jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java index c8b3e4c889c6..c6b8a9e54be3 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java +++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java @@ -103,7 +103,7 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; @ExtendWith(WorkDirExtension.class) -public class DefaultServletTest +public class ResourceServletTest { public WorkDir workDir; @@ -156,7 +156,7 @@ public void testGet() throws Exception { Path file = docRoot.resolve("file.txt"); - context.addServlet(DefaultServlet.class, "/"); + context.addServlet(ResourceServlet.class, "/"); String rawResponse; HttpTester.Response response; @@ -188,7 +188,7 @@ public void testHead() throws Exception { Path file = docRoot.resolve("file.txt"); - context.addServlet(DefaultServlet.class, "/"); + context.addServlet(ResourceServlet.class, "/"); String rawResponse; HttpTester.Response response; @@ -220,7 +220,7 @@ public void testPost() throws Exception { Path file = docRoot.resolve("file.txt"); - context.addServlet(DefaultServlet.class, "/"); + context.addServlet(ResourceServlet.class, "/"); String rawResponse; HttpTester.Response response; @@ -253,7 +253,7 @@ public void testPost() throws Exception @Test public void testTrace() throws Exception { - context.addServlet(DefaultServlet.class, "/"); + context.addServlet(ResourceServlet.class, "/"); String rawResponse; HttpTester.Response response; @@ -271,7 +271,7 @@ public void testTrace() throws Exception @Test public void testOptions() throws Exception { - context.addServlet(DefaultServlet.class, "/"); + context.addServlet(ResourceServlet.class, "/"); String rawResponse; HttpTester.Response response; @@ -303,7 +303,7 @@ public void testGetBinaryWithUtfResponseEncoding() throws Exception } context.setDefaultResponseCharacterEncoding("utf-8"); - context.addServlet(DefaultServlet.class, "/"); + context.addServlet(ResourceServlet.class, "/"); String rawResponse; HttpTester.Response response; @@ -329,7 +329,7 @@ public void testGetPercent2F() throws Exception Path file = docRoot.resolve("file.txt"); Files.writeString(file, "How now brown cow", UTF_8); - context.addServlet(DefaultServlet.class, "/"); + context.addServlet(ResourceServlet.class, "/"); String rawResponse; HttpTester.Response response; @@ -377,7 +377,7 @@ public void testGetPercent2F() throws Exception @Test public void testListingWithSession() throws Exception { - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "true"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("gzip", "false"); @@ -408,7 +408,7 @@ public void testListingWithSession() throws Exception @Test public void testListingXSS() throws Exception { - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "true"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("gzip", "false"); @@ -456,7 +456,7 @@ public void testListingXSS() throws Exception @Test public void testListingWithQuestionMarks() throws Exception { - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "true"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("gzip", "false"); @@ -484,7 +484,7 @@ public void testListingWithQuestionMarks() throws Exception @Test public void testSimpleListing() throws Exception { - ServletHolder defHolder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defHolder = context.addServlet(ResourceServlet.class, "/"); defHolder.setInitParameter("dirAllowed", "true"); String rawResponse = connector.getResponse(""" @@ -504,7 +504,7 @@ public void testSimpleListing() throws Exception @Test public void testIncludeListingAllowed() throws Exception { - ServletHolder defHolder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defHolder = context.addServlet(ResourceServlet.class, "/"); defHolder.setInitParameter("dirAllowed", "true"); /* create a file with a non-fully ASCII name in the docroot */ @@ -544,7 +544,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se @Test public void testIncludeListingForbidden() throws Exception { - ServletHolder defHolder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defHolder = context.addServlet(ResourceServlet.class, "/"); defHolder.setInitParameter("dirAllowed", "false"); ServletHolder incHolder = new ServletHolder(); @@ -584,7 +584,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se @Test public void testListingFilenamesOnly() throws Exception { - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "true"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("gzip", "false"); @@ -631,7 +631,7 @@ public void testListingFilenamesOnlyUrlResource() throws Exception String extraResourceBaseString = extraResource.toURI().toASCIIString(); extraResourceBaseString = extraResourceBaseString.substring(0, extraResourceBaseString.length() - "/one".length()); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/extra/*"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/extra/*"); defholder.setInitParameter("resourceBase", extraResourceBaseString); defholder.setInitParameter("dirAllowed", "true"); defholder.setInitParameter("redirectWelcome", "false"); @@ -722,7 +722,7 @@ public void testListingFilenamesOnlyUrlResource() throws Exception @Test public void testListingProperUrlEncoding() throws Exception { - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "true"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("gzip", "false"); @@ -1002,7 +1002,7 @@ public static Stream contextBreakoutScenarios() @MethodSource("contextBreakoutScenarios") public void testListingContextBreakout(Scenario scenario) throws Exception { - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "true"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("gzip", "false"); @@ -1121,7 +1121,7 @@ public void testWelcome(Scenario scenario) throws Exception Files.writeString(three.resolve("index.html"), "

Three Index

", UTF_8); Files.writeString(three.resolve("index.htm"), "

Three Inde

", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "false"); @@ -1155,21 +1155,21 @@ public void testWelcomeMultipleDefaultServletsDifferentBases() throws Exception Path altInde = altDir.resolve("index.htm"); Path altIndex = altDir.resolve("index.html"); - ServletHolder altholder = context.addServlet(DefaultServlet.class, "/alt/*"); + ServletHolder altholder = context.addServlet(ResourceServlet.class, "/alt/*"); altholder.setInitParameter("resourceBase", altRoot.toUri().toASCIIString()); altholder.setInitParameter("dirAllowed", "false"); altholder.setInitParameter("redirectWelcome", "false"); altholder.setInitParameter("welcomeServlets", "false"); altholder.setInitParameter("gzip", "false"); - ServletHolder otherholder = context.addServlet(DefaultServlet.class, "/other/*"); + ServletHolder otherholder = context.addServlet(ResourceServlet.class, "/other/*"); otherholder.setInitParameter("resourceBase", altRoot.toUri().toASCIIString()); otherholder.setInitParameter("dirAllowed", "true"); otherholder.setInitParameter("redirectWelcome", "false"); otherholder.setInitParameter("welcomeServlets", "false"); otherholder.setInitParameter("gzip", "false"); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "false"); @@ -1336,7 +1336,7 @@ public void testDifferentBaseAbsolute() throws Exception Path altRoot = workDir.getEmptyPathDir().resolve("altroot"); FS.ensureDirExists(altRoot); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/alt/*"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/alt/*"); defholder.setInitParameter("resourceBase", altRoot.toAbsolutePath().toUri().toASCIIString()); Path file = altRoot.resolve("file.txt"); @@ -1359,7 +1359,7 @@ public void testDifferentBaseRelative() throws Exception Path alt = docRoot.resolve("alt"); FS.ensureDirExists(alt); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/alt/*"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/alt/*"); defholder.setInitParameter("resourceBase", "alt"); Path file = alt.resolve("file.txt"); @@ -1382,7 +1382,7 @@ public void testIncludedWelcomeDifferentBase() throws Exception Path altRoot = workDir.getPath().resolve("altroot"); FS.ensureDirExists(altRoot); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/alt/*"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/alt/*"); defholder.setInitParameter("resourceBase", altRoot.toUri().toASCIIString()); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); @@ -1455,7 +1455,7 @@ public void testWelcomeRedirect() throws Exception Path inde = dir.resolve("index.htm"); Path index = dir.resolve("index.html"); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "true"); defholder.setInitParameter("welcomeServlets", "false"); @@ -1548,7 +1548,7 @@ public void testRelativeRedirect() throws Exception context.addAliasCheck((p, r) -> true); connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setRelativeRedirectAllowed(true); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "true"); defholder.setInitParameter("welcomeServlets", "false"); @@ -1604,7 +1604,7 @@ public void testWelcomeRedirectDirWithQuestion() throws Exception Path index = dir.resolve("index.html"); Files.writeString(index, "

Hello Index

", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "true"); defholder.setInitParameter("welcomeServlets", "false"); @@ -1655,7 +1655,7 @@ public void testWelcomeRedirectDirWithSemicolon() throws Exception Path index = dir.resolve("index.html"); Files.writeString(index, "

Hello Index

", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "true"); defholder.setInitParameter("welcomeServlets", "false"); @@ -1691,7 +1691,7 @@ public void testWelcomeServlet() throws Exception Path inde = docRoot.resolve("index.htm"); Path index = docRoot.resolve("index.html"); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "true"); @@ -1775,7 +1775,7 @@ public void testSymLinks() throws Exception Path rLink = dir.resolve("rlink.txt"); Files.writeString(foobar, "Foo Bar", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("gzip", "false"); String rawResponse; @@ -1956,7 +1956,7 @@ public void testWelcomeExactServlet(Scenario scenario) throws Exception Files.writeString(three.resolve("index.html"), "

Three Index

", UTF_8); Files.writeString(three.resolve("index.htm"), "

Three Inde

", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "exact"); @@ -1980,7 +1980,7 @@ public void testDirectFromResourceHttpContent() throws Exception Path index = docRoot.resolve("index.html"); Files.writeString(index, "

Hello World

", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "true"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("useFileMappedBuffer", "true"); @@ -2259,7 +2259,7 @@ public void testRangeRequests(Scenario scenario) throws Exception Path nofilesuffix = docRoot.resolve("nofilesuffix"); Files.writeString(nofilesuffix, "01234567890123456789012345678901234567890123456789012345678901234567890123456789", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "false"); @@ -2290,7 +2290,7 @@ public void testNotFiltered() throws Exception { setupFilteredContent(docRoot); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "false"); @@ -2321,7 +2321,7 @@ public void testOutputStreamAndCharsetFiltered() throws Exception { setupFilteredContent(docRoot); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "false"); @@ -2364,7 +2364,7 @@ public void testWriterAndCharsetFiltered() throws Exception { setupFilteredContent(docRoot); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "false"); @@ -2395,7 +2395,7 @@ public void testGzip() throws Exception Path file0gz = docRoot.resolve("data0.txt.gz"); Files.writeString(file0gz, "fake gzip", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "false"); @@ -2545,7 +2545,7 @@ public void testCachedGzip() throws Exception Path file0gz = docRoot.resolve("data0.txt.gz"); Files.writeString(file0gz, "fake gzip", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "false"); @@ -2668,7 +2668,7 @@ public void testBrotli() throws Exception Files.writeString(docRoot.resolve("data0.txt"), "Hello Text 0", UTF_8); Files.writeString(docRoot.resolve("data0.txt.br"), "fake brotli", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "false"); @@ -2805,7 +2805,7 @@ public void testCachedBrotli() throws Exception Files.writeString(docRoot.resolve("data0.txt"), "Hello Text 0", UTF_8); Files.writeString(docRoot.resolve("data0.txt.br"), "fake brotli", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "false"); @@ -2929,7 +2929,7 @@ public void testDefaultBrotliOverGzip() throws Exception Files.writeString(docRoot.resolve("data0.txt.br"), "fake brotli", UTF_8); Files.writeString(docRoot.resolve("data0.txt.gz"), "fake gzip", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("precompressed", "true"); defholder.setInitParameter("resourceBase", docRoot.toString()); @@ -2978,7 +2978,7 @@ public void testCustomCompressionFormats() throws Exception Files.writeString(docRoot.resolve("data0.txt.gz"), "fake gzip", UTF_8); Files.writeString(docRoot.resolve("data0.txt.bz2"), "fake bzip2", UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("precompressed", "bzip2=.bz2,gzip=.gz,br=.br"); defholder.setInitParameter("resourceBase", docRoot.toString()); @@ -3028,14 +3028,14 @@ public void testProgrammaticCustomCompressionFormats() throws Exception Files.writeString(docRoot.resolve("data0.txt.gz"), "fake gzip", UTF_8); Files.writeString(docRoot.resolve("data0.txt.bz2"), "fake bzip2", UTF_8); - DefaultServlet defaultServlet = new DefaultServlet(); - ServletHolder defholder = new ServletHolder(defaultServlet) + ResourceServlet resourceServlet = new ResourceServlet(); + ServletHolder defholder = new ServletHolder(resourceServlet) { @Override public void initialize() throws Exception { super.initialize(); - ResourceService resourceService = defaultServlet.getResourceService(); + ResourceService resourceService = resourceServlet.getResourceService(); resourceService.setPrecompressedFormats(List.of( new CompressedContentFormat("bzip2", ".bz2"), new CompressedContentFormat("gzip", ".gz"), @@ -3090,7 +3090,7 @@ public void testControlCharacter() throws Exception { connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE); FS.ensureDirExists(docRoot); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("resourceBase", docRoot.toFile().getAbsolutePath()); try (StacklessLogging ignore = new StacklessLogging(ResourceService.class)) @@ -3116,7 +3116,7 @@ public void testIfModified(String content) throws Exception { Path file = docRoot.resolve("file.txt"); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("maxCacheSize", "4096"); defholder.setInitParameter("maxCachedFileSize", "25"); @@ -3209,7 +3209,7 @@ public void testIfETag(String content) throws Exception { Files.writeString(docRoot.resolve("file.txt"), content, UTF_8); - ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + ServletHolder defholder = context.addServlet(ResourceServlet.class, "/"); defholder.setInitParameter("maxCacheSize", "4096"); defholder.setInitParameter("maxCachedFileSize", "25"); @@ -3317,7 +3317,7 @@ public void testGetUtf8NfcFile() throws Exception { FS.ensureEmpty(docRoot); - context.addServlet(DefaultServlet.class, "/"); + context.addServlet(ResourceServlet.class, "/"); context.addAliasCheck(new AllowedResourceAliasChecker(context)); // Create file with UTF-8 NFC format @@ -3367,7 +3367,7 @@ public void testGetUtf8NfdFile() throws Exception { FS.ensureEmpty(docRoot); - context.addServlet(DefaultServlet.class, "/"); + context.addServlet(ResourceServlet.class, "/"); context.addAliasCheck(new AllowedResourceAliasChecker(context)); // Create file with UTF-8 NFD format @@ -3459,14 +3459,14 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws Path pathTest = MavenTestingUtils.getTestResourcePath("pathTest"); Path defaultDir = pathTest.resolve("default"); - ServletHolder slashHolder = new ServletHolder("default", new DefaultServlet()); + ServletHolder slashHolder = new ServletHolder("default", new ResourceServlet()); slashHolder.setInitParameter("redirectWelcome", "false"); slashHolder.setInitParameter("welcomeServlets", "true"); slashHolder.setInitParameter("baseResource", defaultDir.toAbsolutePath().toString()); context.addServlet(slashHolder, "/"); Path rDir = pathTest.resolve("rdir"); - ServletHolder rHolder = new ServletHolder("rdefault", new DefaultServlet()); + ServletHolder rHolder = new ServletHolder("rdefault", new ResourceServlet()); rHolder.setInitParameter("redirectWelcome", "false"); rHolder.setInitParameter("welcomeServlets", "true"); rHolder.setInitParameter("baseResource", rDir.toAbsolutePath().toString()); @@ -3496,9 +3496,9 @@ public void testSuffixMappings() throws Exception ResourceFactory resourceFactory = ResourceFactory.of(context); context.setBaseResource(resourceFactory.newResource(suffixroot.toUri())); - ServletHolder holderAlt = new ServletHolder("static-js", DefaultServlet.class); + ServletHolder holderAlt = new ServletHolder("static-js", ResourceServlet.class); context.addServlet(holderAlt, "*.js"); - ServletHolder holderDef = new ServletHolder("default", DefaultServlet.class); + ServletHolder holderDef = new ServletHolder("default", ResourceServlet.class); holderDef.setInitParameter("dirAllowed", "true"); context.addServlet(holderDef, "/"); @@ -3511,9 +3511,9 @@ public void testSuffixMappings() throws Exception public void testMemoryResourceRange() throws Exception { Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt")); - DefaultServlet defaultServlet = new DefaultServlet(); - context.addServlet(new ServletHolder(defaultServlet), "/"); - defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")); + ResourceServlet resourceServlet = new ResourceServlet(); + context.addServlet(new ServletHolder(resourceServlet), "/"); + resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")); String rawResponse = connector.getResponse(""" GET /context/ HTTP/1.1\r @@ -3532,9 +3532,9 @@ public void testMemoryResourceRange() throws Exception public void testMemoryResourceRangeUsingBufferedHttpContent() throws Exception { Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt")); - DefaultServlet defaultServlet = new DefaultServlet(); - context.addServlet(new ServletHolder(defaultServlet), "/"); - defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain") + ResourceServlet resourceServlet = new ResourceServlet(); + context.addServlet(new ServletHolder(resourceServlet), "/"); + resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain") { final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer(); @@ -3562,9 +3562,9 @@ public ByteBuffer getByteBuffer() public void testMemoryResourceMultipleRanges() throws Exception { Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt")); - DefaultServlet defaultServlet = new DefaultServlet(); - context.addServlet(new ServletHolder(defaultServlet), "/"); - defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")); + ResourceServlet resourceServlet = new ResourceServlet(); + context.addServlet(new ServletHolder(resourceServlet), "/"); + resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")); String rawResponse = connector.getResponse(""" GET /context/ HTTP/1.1\r @@ -3586,9 +3586,9 @@ public void testMemoryResourceMultipleRanges() throws Exception public void testMemoryResourceMultipleRangesUsingBufferedHttpContent() throws Exception { Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt")); - DefaultServlet defaultServlet = new DefaultServlet(); - context.addServlet(new ServletHolder(defaultServlet), "/"); - defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain") + ResourceServlet resourceServlet = new ResourceServlet(); + context.addServlet(new ServletHolder(resourceServlet), "/"); + resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain") { final ByteBuffer buffer = IOResources.toRetainableByteBuffer(getResource(), ByteBufferPool.NON_POOLING, false).getByteBuffer(); @@ -3619,10 +3619,10 @@ public ByteBuffer getByteBuffer() public void testNotAcceptRanges() throws Exception { Resource memResource = ResourceFactory.of(context).newMemoryResource(getClass().getResource("/contextResources/test.txt")); - DefaultServlet defaultServlet = new DefaultServlet(); - context.addServlet(new ServletHolder(defaultServlet), "/"); - defaultServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")); - defaultServlet.getResourceService().setAcceptRanges(false); + ResourceServlet resourceServlet = new ResourceServlet(); + context.addServlet(new ServletHolder(resourceServlet), "/"); + resourceServlet.getResourceService().setHttpContentFactory(path -> new ResourceHttpContent(memResource, "text/plain")); + resourceServlet.getResourceService().setAcceptRanges(false); String rawResponse = connector.getResponse(""" GET /context/ HTTP/1.1\r From 4cb1391a66df18d925d39c4b1cdfd331bd203532 Mon Sep 17 00:00:00 2001 From: gregw Date: Wed, 12 Jun 2024 20:12:52 +1000 Subject: [PATCH 02/18] WIP ResourceServlet --- .../org/eclipse/jetty/server/Request.java | 6 + .../jetty/ee10/servlet/DefaultServlet.java | 159 ++++++++++++++++++ .../jetty/ee10/servlet/Dispatcher.java | 6 + .../jetty/ee10/servlet/ResourceServlet.java | 146 +++++++--------- .../jetty/ee10/servlet/ServletApiRequest.java | 6 + .../ee10/servlet/ResourceServletTest.java | 7 +- 6 files changed, 244 insertions(+), 86 deletions(-) create mode 100644 jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index c3ee715df200..492461cf77ff 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -885,6 +885,12 @@ public Request getWrapped() { return _request; } + + @Override + public String toString() + { + return "%s@%x{%s}".formatted(getClass().getSimpleName(), hashCode(), getWrapped()); + } } @SuppressWarnings("unchecked") diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java new file mode 100644 index 000000000000..e1748bb8f5c1 --- /dev/null +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java @@ -0,0 +1,159 @@ +// +// ======================================================================== +// 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.eclipse.jetty.ee10.servlet; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + *

The default Servlet, normally mapped to {@code /}, that handles static resources.

+ *

The following init parameters are supported:

+ *
+ *
acceptRanges
+ *
+ * Use {@code true} to accept range requests, defaults to {@code true}. + *
+ *
baseResource
+ *
+ * Defaults to the context's baseResource. + * The root directory to look for static resources. + *
+ *
cacheControl
+ *
+ * The value of the {@code Cache-Control} header. + * If omitted, no {@code Cache-Control} header is generated in responses. + * By default is omitted. + *
+ *
cacheValidationTime
+ *
+ * How long in milliseconds a resource is cached. + * If omitted, defaults to {@code 1000} ms. + * Use {@code -1} to cache forever or {@code 0} to not cache. + *
+ *
dirAllowed
+ *
+ * Use {@code true} to serve directory listing if no welcome file is found. + * Otherwise responds with {@code 403 Forbidden}. + * Defaults to {@code true}. + *
+ *
encodingHeaderCacheSize
+ *
+ * Max number of cached {@code Accept-Encoding} entries. + * Use {@code -1} for the default value (100), {@code 0} for no cache. + *
+ *
etags
+ *
+ * Use {@code true} to generate ETags in responses. + * Defaults to {@code false}. + *
+ *
maxCachedFiles
+ *
+ * The max number of cached static resources. + * Use {@code -1} for the default value (2048) or {@code 0} for no cache. + *
+ *
maxCachedFileSize
+ *
+ * The max size in bytes of a single cached static resource. + * Use {@code -1} for the default value (128 MiB) or {@code 0} for no cache. + *
+ *
maxCacheSize
+ *
+ * The max size in bytes of the cache for static resources. + * Use {@code -1} for the default value (256 MiB) or {@code 0} for no cache. + *
+ *
otherGzipFileExtensions
+ *
+ * A comma-separated list of extensions of files whose content is implicitly + * gzipped. + * Defaults to {@code .svgz}. + *
+ *
precompressed
+ *
+ * Omitted by default, so that no pre-compressed content will be served. + * If set to {@code true}, the default set of pre-compressed formats will be used. + * Otherwise can be set to a comma-separated list of {@code encoding=extension} pairs, + * such as: {@code br=.br,gzip=.gz,bzip2=.bz}, where {@code encoding} is used as the + * value for the {@code Content-Encoding} header. + *
+ *
redirectWelcome
+ *
+ * Use {@code true} to redirect welcome files, otherwise they are forwarded. + * Defaults to {@code false}. + *
+ *
stylesheet
+ *
+ * Defaults to the {@code Server}'s default stylesheet, {@code jetty-dir.css}. + * The path of a custom stylesheet to style the directory listing HTML. + *
+ *
useFileMappedBuffer
+ *
+ * Use {@code true} to use file mapping to serve static resources. + * Defaults to {@code false}. + *
+ *
welcomeServlets
+ *
+ * Use {@code false} to only serve welcome resources from the file system. + * Use {@code true} to dispatch welcome resources to a matching Servlet + * (for example mapped to {@code *.welcome}), when the welcome resources + * does not exist on file system. + * Use {@code exact} to dispatch welcome resource to a Servlet whose mapping + * is exactly the same as the welcome resource (for example {@code /index.welcome}), + * when the welcome resources does not exist on file system. + * Defaults to {@code false}. + *
+ *
+ */ +public class DefaultServlet extends ResourceServlet +{ + private static final Logger LOG = LoggerFactory.getLogger(DefaultServlet.class); + public static final String CONTEXT_INIT = "org.eclipse.jetty.servlet.Default."; + + /** + *

+ * Returns a {@code String} containing the value of the named initialization parameter, or null if the parameter does not exist. + *

+ * + *

+ * Parameter lookup first checks the {@link ServletContext#getInitParameter(String)} for the + * parameter prefixed with {@code org.eclipse.jetty.servlet.Default.}, then checks + * {@link jakarta.servlet.ServletConfig#getInitParameter(String)} for the actual value + *

+ * + * @param name a {@code String} specifying the name of the initialization parameter + * @return a {@code String} containing the value of the initialization parameter + */ + @Override + public String getInitParameter(String name) + { + String value = getServletContext().getInitParameter(CONTEXT_INIT + name); + if (value == null) + value = super.getInitParameter(name); + return value; + } + + @Override + protected String getEncodedPathInContext(HttpServletRequest request, boolean included) + { + String deprecatedPath = getEncodedPathInContext(request, (String)(included ? request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH) : null)); + return deprecatedPath == null ? super.getEncodedPathInContext(request, included) : deprecatedPath; + } + + @Deprecated(forRemoval = true) + protected String getEncodedPathInContext(HttpServletRequest req, String includedServletPath) + { + return null; + } +} diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/Dispatcher.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/Dispatcher.java index 70f610fc991a..3ecf4a3d6b45 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/Dispatcher.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/Dispatcher.java @@ -258,6 +258,12 @@ public String[] getParameterValues(String name) return null; return vals.toArray(new String[0]); } + + @Override + public String toString() + { + return "%s@%x{%s}".formatted(getClass().getSimpleName(), hashCode(), getRequest()); + } } private class ForwardRequest extends ParameterRequestWrapper diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java index 9a729e73c22e..549930a88946 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java @@ -31,9 +31,9 @@ import jakarta.servlet.ServletException; import jakarta.servlet.UnavailableException; import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletMapping; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.MappingMatch; import org.eclipse.jetty.http.CompressedContentFormat; import org.eclipse.jetty.http.HttpException; import org.eclipse.jetty.http.HttpField; @@ -56,15 +56,12 @@ import org.eclipse.jetty.util.Blocker; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.ExceptionUtil; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.eclipse.jetty.util.URIUtil.encodePath; - /** *

The default Servlet, normally mapped to {@code /}, that handles static resources.

*

The following init parameters are supported:

@@ -166,11 +163,10 @@ public class ResourceServlet extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(ResourceServlet.class); - public static final String CONTEXT_INIT = "org.eclipse.jetty.servlet.Default."; - private ServletContextHandler _contextHandler; private ServletResourceService _resourceService; private WelcomeServletMode _welcomeServletMode; + private boolean _pathInfoOnly = true; public ResourceService getResourceService() { @@ -180,17 +176,17 @@ public ResourceService getResourceService() @Override public void init() throws ServletException { - _contextHandler = initContextHandler(getServletContext()); - _resourceService = new ServletResourceService(_contextHandler); + ServletContextHandler contextHandler = initContextHandler(getServletContext()); + _resourceService = new ServletResourceService(contextHandler); _resourceService.setWelcomeFactory(_resourceService); - Resource baseResource = _contextHandler.getBaseResource(); + Resource baseResource = contextHandler.getBaseResource(); String rb = getInitParameter("baseResource", "resourceBase"); if (rb != null) { try { - baseResource = URIUtil.isRelative(rb) ? _contextHandler.getBaseResource().resolve(rb) : _contextHandler.newResource(rb); + baseResource = URIUtil.isRelative(rb) ? contextHandler.getBaseResource().resolve(rb) : contextHandler.newResource(rb); } catch (Exception e) { @@ -208,11 +204,11 @@ public void init() throws ServletException HttpContent.Factory contentFactory = (HttpContent.Factory)getServletContext().getAttribute(HttpContent.Factory.class.getName()); if (contentFactory == null) { - MimeTypes mimeTypes = _contextHandler.getMimeTypes(); + MimeTypes mimeTypes = contextHandler.getMimeTypes(); contentFactory = new ResourceHttpContentFactory(baseResource, mimeTypes); // Use the servers default stylesheet unless there is one explicitly set by an init param. - Resource styleSheet = _contextHandler.getServer().getDefaultStyleSheet(); + Resource styleSheet = contextHandler.getServer().getDefaultStyleSheet(); String stylesheetParam = getInitParameter("stylesheet"); if (stylesheetParam != null) { @@ -246,7 +242,7 @@ public void init() throws ServletException long cacheValidationTime = getInitParameter("cacheValidationTime") != null ? Long.parseLong(getInitParameter("cacheValidationTime")) : -2; if (maxCachedFiles != -2 || maxCacheSize != -2 || maxCachedFileSize != -2 || cacheValidationTime != -2) { - ByteBufferPool bufferPool = getByteBufferPool(_contextHandler); + ByteBufferPool bufferPool = getByteBufferPool(contextHandler); ValidatingCachingHttpContentFactory cached = new ValidatingCachingHttpContentFactory(contentFactory, (cacheValidationTime > -2) ? cacheValidationTime : Duration.ofSeconds(1).toMillis(), bufferPool); contentFactory = cached; @@ -260,8 +256,8 @@ public void init() throws ServletException } _resourceService.setHttpContentFactory(contentFactory); - if (_contextHandler.getWelcomeFiles() == null) - _contextHandler.setWelcomeFiles(new String[]{"index.html", "index.jsp"}); + if (contextHandler.getWelcomeFiles() == null) + contextHandler.setWelcomeFiles(new String[]{"index.html", "index.jsp"}); _resourceService.setAcceptRanges(getInitBoolean("acceptRanges", _resourceService.isAcceptRanges())); _resourceService.setDirAllowed(getInitBoolean("dirAllowed", _resourceService.isDirAllowed())); @@ -382,33 +378,10 @@ else if (gzip == Boolean.TRUE) return ret; } - /** - *

- * Returns a {@code String} containing the value of the named initialization parameter, or null if the parameter does not exist. - *

- * - *

- * Parameter lookup first checks the {@link ServletContext#getInitParameter(String)} for the - * parameter prefixed with {@code org.eclipse.jetty.servlet.Default.}, then checks - * {@link jakarta.servlet.ServletConfig#getInitParameter(String)} for the actual value - *

- * - * @param name a {@code String} specifying the name of the initialization parameter - * @return a {@code String} containing the value of the initialization parameter - */ - @Override - public String getInitParameter(String name) - { - String value = getServletContext().getInitParameter(CONTEXT_INIT + name); - if (value == null) - value = super.getInitParameter(name); - return value; - } - private Boolean getInitBoolean(String name) { String value = getInitParameter(name); - if (value == null || value.length() == 0) + if (value == null || value.isEmpty()) return null; return (value.startsWith("t") || value.startsWith("T") || @@ -425,7 +398,7 @@ private boolean getInitBoolean(String name, boolean dft) private int getInitInt(String name, int dft) { String value = getInitParameter(name); - if (value != null && value.length() > 0) + if (value != null && !value.isEmpty()) return Integer.parseInt(value); return dft; } @@ -446,9 +419,10 @@ protected ServletContextHandler initContextHandler(ServletContext servletContext @Override protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { - String includedServletPath = (String)httpServletRequest.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); - String encodedPathInContext = getEncodedPathInContext(httpServletRequest, includedServletPath); - boolean included = includedServletPath != null; + boolean included = httpServletRequest.getDispatcherType() == DispatcherType.INCLUDE; + String encodedPathInContext = getEncodedPathInContext(httpServletRequest, included); + + System.err.printf("%s <=%b= %s\n", encodedPathInContext, included, httpServletRequest); if (LOG.isDebugEnabled()) LOG.debug("doGet(hsReq={}, hsResp={}) pathInContext={}, included={}", httpServletRequest, httpServletResponse, encodedPathInContext, included); @@ -461,19 +435,7 @@ protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse if (content == null || Resources.missing(content.getResource())) { - if (included) - { - /* https://github.com/jakartaee/servlet/blob/6.0.0-RELEASE/spec/src/main/asciidoc/servlet-spec-body.adoc#93-the-include-method - * 9.3 - If the default servlet is the target of a RequestDispatch.include() and the requested - * resource does not exist, then the default servlet MUST throw FileNotFoundException. - * If the exception isn’t caught and handled, and the response - * hasn’t been committed, the status code MUST be set to 500. - */ - throw new FileNotFoundException(encodedPathInContext); - } - - // no content - httpServletResponse.sendError(404); + doNotFound(httpServletRequest, httpServletResponse, encodedPathInContext); } else { @@ -551,25 +513,34 @@ protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse } } - protected String getEncodedPathInContext(HttpServletRequest req, String includedServletPath) + protected String getEncodedPathInContext(HttpServletRequest request, boolean included) { - if (includedServletPath != null) - return encodePath(getIncludedPathInContext(req, includedServletPath, !isDefaultMapping(req))); - else if (!isDefaultMapping(req)) - { - //a match via an extension mapping will more than likely - //have no path info - String path = req.getPathInfo(); - if (StringUtil.isEmpty(path) && - MappingMatch.EXTENSION.equals(req.getHttpServletMapping().getMappingMatch())) - path = req.getServletPath(); - - return encodePath(path); - } - else if (req instanceof ServletApiRequest apiRequest) - return Context.getPathInContext(req.getContextPath(), apiRequest.getRequest().getHttpURI().getCanonicalPath()); - else - return Context.getPathInContext(req.getContextPath(), URIUtil.canonicalPath(req.getRequestURI())); + HttpServletMapping mapping = included ? (HttpServletMapping)request.getAttribute(Dispatcher.INCLUDE_MAPPING) : request.getHttpServletMapping(); + System.err.println(mapping); + return switch (mapping.getMappingMatch()) + { + case CONTEXT_ROOT -> "/"; + case DEFAULT, EXTENSION, EXACT -> included ? (String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH) : request.getServletPath(); + case PATH -> + { + if (_pathInfoOnly) + { + if (included) + yield URIUtil.encodePath((String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO)); + else + yield URIUtil.encodePath(request.getPathInfo()); + } + else + { + if (included) + yield URIUtil.encodePath(URIUtil.addPaths((String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH), (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO))); + else if (request instanceof ServletApiRequest apiRequest) + yield Context.getPathInContext(request.getContextPath(), apiRequest.getRequest().getHttpURI().getCanonicalPath()); + else + yield URIUtil.encodePath(URIUtil.addPaths(request.getServletPath(), request.getPathInfo())); + } + } + }; } @Override @@ -594,6 +565,23 @@ protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throw resp.setHeader("Allow", "GET, HEAD, OPTIONS"); } + protected void doNotFound(HttpServletRequest request, HttpServletResponse response, String encodedPathInContext) throws IOException + { + if (request.getDispatcherType() == DispatcherType.INCLUDE) + { + /* https://github.com/jakartaee/servlet/blob/6.0.0-RELEASE/spec/src/main/asciidoc/servlet-spec-body.adoc#93-the-include-method + * 9.3 - If the default servlet is the target of a RequestDispatch.include() and the requested + * resource does not exist, then the default servlet MUST throw FileNotFoundException. + * If the exception isn’t caught and handled, and the response + * hasn’t been committed, the status code MUST be set to 500. + */ + throw new FileNotFoundException(encodedPathInContext); + } + + // no content + response.sendError(404); + } + private class ServletResourceService extends ResourceService implements ResourceService.WelcomeFactory { private final ServletContextHandler _servletContextHandler; @@ -627,9 +615,6 @@ public String getWelcomeTarget(HttpContent content, Request coreRequest) // Check whether a Servlet may serve the welcome resource. if (_welcomeServletMode != WelcomeServletMode.NONE && welcomeTarget == null) { - if (!isDefaultMapping(getServletRequest(coreRequest)) && !isIncluded(getServletRequest(coreRequest))) - welcomeTarget = URIUtil.addPaths(getServletRequest(coreRequest).getPathInfo(), welcome); - ServletHandler.MappedServlet entry = _servletContextHandler.getServletHandler().getMappedServlet(welcomeInContext); // Is there a different Servlet that may serve the welcome resource? if (entry != null && entry.getServletHolder().getServletInstance() != ResourceServlet.this) @@ -786,13 +771,6 @@ private static boolean isIncluded(HttpServletRequest request) return request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null; } - protected boolean isDefaultMapping(HttpServletRequest req) - { - if (req.getHttpServletMapping().getMappingMatch() == MappingMatch.DEFAULT) - return true; - return (req.getDispatcherType() != DispatcherType.REQUEST) && "default".equals(getServletConfig().getServletName()); - } - /** * Wrap an existing HttpContent with one that takes has an unknown/unspecified length. */ diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java index 0a99cb9f30bf..4d7083accfc7 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java @@ -1433,6 +1433,12 @@ public Map getTrailerFields() return trailersMap; } + @Override + public String toString() + { + return "%s@%x{%s}".formatted(getClass().getSimpleName(), hashCode(), _servletContextRequest); + } + static class AmbiguousURI extends ServletApiRequest { private final String msg; diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java index c6b8a9e54be3..3b3b4a9a9eaf 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java +++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java @@ -38,6 +38,7 @@ import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.FilterConfig; +import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; @@ -1383,7 +1384,7 @@ public void testIncludedWelcomeDifferentBase() throws Exception FS.ensureDirExists(altRoot); ServletHolder defholder = context.addServlet(ResourceServlet.class, "/alt/*"); - defholder.setInitParameter("resourceBase", altRoot.toUri().toASCIIString()); + defholder.setInitParameter("baseResource", altRoot.toUri().toASCIIString()); defholder.setInitParameter("dirAllowed", "false"); defholder.setInitParameter("redirectWelcome", "false"); defholder.setInitParameter("welcomeServlets", "true"); @@ -1394,7 +1395,9 @@ public void testIncludedWelcomeDifferentBase() throws Exception protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String includeTarget = req.getParameter("includeTarget"); - req.getRequestDispatcher(includeTarget).include(req, resp); + RequestDispatcher requestDispatcher = req.getRequestDispatcher(includeTarget); + System.err.printf("include %s -> %s\n", includeTarget, requestDispatcher); + requestDispatcher.include(req, resp); } }); context.addServlet(gwholder, "/gateway"); From 1d1459c82e4ee042959c8d4b320c13c562a0589e Mon Sep 17 00:00:00 2001 From: gregw Date: Thu, 20 Jun 2024 12:48:12 +1000 Subject: [PATCH 03/18] Split DefaultServlet into ResourceServlet --- .../jetty/ee10/servlet/DefaultServlet.java | 30 ++++++++++++++- .../jetty/ee10/servlet/ResourceServlet.java | 37 ++++++++++++++++--- .../ee10/servlet/ResourceServletTest.java | 20 +++++----- .../src/test/resources/altroot/all/index.html | 1 + .../src/test/resources/altroot/dir/file.txt | 1 - .../src/test/resources/altroot/index.html | 1 + 6 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/all/index.html delete mode 100644 jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/dir/file.txt create mode 100644 jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/index.html diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java index e1748bb8f5c1..e40c68279648 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java @@ -13,8 +13,12 @@ package org.eclipse.jetty.ee10.servlet; +import java.util.concurrent.atomic.AtomicBoolean; + import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; +import org.eclipse.jetty.server.Context; +import org.eclipse.jetty.util.URIUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -120,6 +124,7 @@ public class DefaultServlet extends ResourceServlet { private static final Logger LOG = LoggerFactory.getLogger(DefaultServlet.class); public static final String CONTEXT_INIT = "org.eclipse.jetty.servlet.Default."; + private AtomicBoolean warned = new AtomicBoolean(false); /** *

@@ -148,7 +153,30 @@ public String getInitParameter(String name) protected String getEncodedPathInContext(HttpServletRequest request, boolean included) { String deprecatedPath = getEncodedPathInContext(request, (String)(included ? request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH) : null)); - return deprecatedPath == null ? super.getEncodedPathInContext(request, included) : deprecatedPath; + if (deprecatedPath != null) + return deprecatedPath; + + if (request.getPathInfo() != null) + { + if (warned.compareAndSet(false, true)) + LOG.warn("Incorrect mapping for DefaultServlet at %s. Use ResourceServlet".formatted(request.getHttpServletMapping().getPattern())); + return super.getEncodedPathInContext(request, included); + } + + if (included) + { + if (request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH) instanceof String servletPath) + return URIUtil.encodePath(servletPath); + + // must be an include of a named dispatcher. Just use the whole URI + return URIUtil.encodePath(request.getServletPath()); + } + + if (request instanceof ServletApiRequest apiRequest) + return Context.getPathInContext(request.getContextPath(), apiRequest.getRequest().getHttpURI().getCanonicalPath()); + + + return URIUtil.encodePath(request.getServletPath()); } @Deprecated(forRemoval = true) diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java index 549930a88946..0616dd3ee81a 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java @@ -124,6 +124,11 @@ * gzipped. * Defaults to {@code .svgz}. * + *

pathInfoOnly
+ *
+ * Use {@code true} to use only the pathInfo portion of a PATH (aka prefix) match. + * Defaults to {@code true}. + *
*
precompressed
*
* Omitted by default, so that no pre-compressed content will be served. @@ -166,7 +171,7 @@ public class ResourceServlet extends HttpServlet private ServletResourceService _resourceService; private WelcomeServletMode _welcomeServletMode; - private boolean _pathInfoOnly = true; + private boolean _pathInfoOnly; public ResourceService getResourceService() { @@ -306,6 +311,8 @@ public void init() throws ServletException } _resourceService.setGzipEquivalentFileExtensions(gzipEquivalentFileExtensions); + _pathInfoOnly = getInitBoolean("pathInfoOnly", true); + if (LOG.isDebugEnabled()) { LOG.debug(" .baseResource = {}", baseResource); @@ -422,8 +429,6 @@ protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse boolean included = httpServletRequest.getDispatcherType() == DispatcherType.INCLUDE; String encodedPathInContext = getEncodedPathInContext(httpServletRequest, included); - System.err.printf("%s <=%b= %s\n", encodedPathInContext, included, httpServletRequest); - if (LOG.isDebugEnabled()) LOG.debug("doGet(hsReq={}, hsResp={}) pathInContext={}, included={}", httpServletRequest, httpServletResponse, encodedPathInContext, included); @@ -515,12 +520,32 @@ protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse protected String getEncodedPathInContext(HttpServletRequest request, boolean included) { - HttpServletMapping mapping = included ? (HttpServletMapping)request.getAttribute(Dispatcher.INCLUDE_MAPPING) : request.getHttpServletMapping(); - System.err.println(mapping); + HttpServletMapping mapping = request.getHttpServletMapping(); + if (included) + { + if (request.getAttribute(Dispatcher.INCLUDE_MAPPING) instanceof HttpServletMapping httpServletMapping) + { + mapping = httpServletMapping; + } + else + { + // must be an include of a named dispatcher. Just use the whole URI + return URIUtil.encodePath(URIUtil.addPaths(request.getServletPath(), request.getPathInfo())); + } + } + return switch (mapping.getMappingMatch()) { case CONTEXT_ROOT -> "/"; - case DEFAULT, EXTENSION, EXACT -> included ? (String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH) : request.getServletPath(); + case DEFAULT, EXTENSION, EXACT -> + { + if (included) + yield URIUtil.encodePath((String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH)); + else if (request instanceof ServletApiRequest apiRequest) + yield Context.getPathInContext(request.getContextPath(), apiRequest.getRequest().getHttpURI().getCanonicalPath()); + else + yield URIUtil.encodePath(request.getServletPath()); + } case PATH -> { if (_pathInfoOnly) diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java index 3b3b4a9a9eaf..b34ed5e62cf7 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java +++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResourceServletTest.java @@ -3436,7 +3436,7 @@ public void destroy() } @Test - public void testPathInfoOnly() throws Exception + public void testNotPathInfoOnly() throws Exception { ServletContextHandler context = new ServletContextHandler("/c1", ServletContextHandler.NO_SESSIONS); context.setWelcomeFiles(new String[]{"index.y", "index.x"}); @@ -3459,27 +3459,27 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws context.getServletHandler().addServlet(indexServlet); context.getServletHandler().addServletMapping(indexMapping); - Path pathTest = MavenTestingUtils.getTestResourcePath("pathTest"); + Path docroot = MavenTestingUtils.getTestResourcePath("docroot"); - Path defaultDir = pathTest.resolve("default"); ServletHolder slashHolder = new ServletHolder("default", new ResourceServlet()); slashHolder.setInitParameter("redirectWelcome", "false"); slashHolder.setInitParameter("welcomeServlets", "true"); - slashHolder.setInitParameter("baseResource", defaultDir.toAbsolutePath().toString()); + slashHolder.setInitParameter("baseResource", docroot.toAbsolutePath().toString()); context.addServlet(slashHolder, "/"); - Path rDir = pathTest.resolve("rdir"); - ServletHolder rHolder = new ServletHolder("rdefault", new ResourceServlet()); + Path altroot = MavenTestingUtils.getTestResourcePath("altroot"); + ServletHolder rHolder = new ServletHolder("alt", new ResourceServlet()); rHolder.setInitParameter("redirectWelcome", "false"); rHolder.setInitParameter("welcomeServlets", "true"); - rHolder.setInitParameter("baseResource", rDir.toAbsolutePath().toString()); - context.addServlet(rHolder, "/r/*"); + rHolder.setInitParameter("pathInfoOnly", "false"); + rHolder.setInitParameter("baseResource", altroot.toAbsolutePath().toString()); + context.addServlet(rHolder, "/all/*"); server.stop(); server.setHandler(context); server.start(); String rawRequest = """ - GET /c1/r/ HTTP/1.1\r + GET /c1/all/index.html HTTP/1.1\r Host: localhost\r Connection: close\r \r @@ -3487,7 +3487,7 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws String rawResponse = connector.getResponse(rawRequest); HttpTester.Response response = HttpTester.parseResponse(rawResponse); - assertThat(response.getContent(), containsString("testPathInfoOnly-OK")); + assertThat(response.getContent(), containsString("this is alternate index content")); } @Test diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/all/index.html b/jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/all/index.html new file mode 100644 index 000000000000..001962768610 --- /dev/null +++ b/jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/all/index.html @@ -0,0 +1 @@ +this is alternate index content. \ No newline at end of file diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/dir/file.txt b/jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/dir/file.txt deleted file mode 100644 index 0c4830373381..000000000000 --- a/jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/dir/file.txt +++ /dev/null @@ -1 +0,0 @@ -this is alt content. \ No newline at end of file diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/index.html b/jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/index.html new file mode 100644 index 000000000000..d167fe96cc93 --- /dev/null +++ b/jetty-ee10/jetty-ee10-servlet/src/test/resources/altroot/index.html @@ -0,0 +1 @@ +THIS CANNOT BE SEEN \ No newline at end of file From 50e7dcf64a2a4ae21adfb0bc77ff1a68196ff715 Mon Sep 17 00:00:00 2001 From: gregw Date: Thu, 20 Jun 2024 15:06:24 +1000 Subject: [PATCH 04/18] Split DefaultServlet into ResourceServlet --- .../java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java | 5 +++-- .../java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java index e40c68279648..9dd50a924953 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java @@ -17,6 +17,7 @@ import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.MappingMatch; import org.eclipse.jetty.server.Context; import org.eclipse.jetty.util.URIUtil; import org.slf4j.Logger; @@ -156,7 +157,7 @@ protected String getEncodedPathInContext(HttpServletRequest request, boolean inc if (deprecatedPath != null) return deprecatedPath; - if (request.getPathInfo() != null) + if (request.getHttpServletMapping().getMappingMatch() != MappingMatch.DEFAULT) { if (warned.compareAndSet(false, true)) LOG.warn("Incorrect mapping for DefaultServlet at %s. Use ResourceServlet".formatted(request.getHttpServletMapping().getPattern())); @@ -173,9 +174,9 @@ protected String getEncodedPathInContext(HttpServletRequest request, boolean inc } if (request instanceof ServletApiRequest apiRequest) + // Strip the context path from the canonically encoded path, so no need to re-encode (and mess up %2F etc.) return Context.getPathInContext(request.getContextPath(), apiRequest.getRequest().getHttpURI().getCanonicalPath()); - return URIUtil.encodePath(request.getServletPath()); } diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java index 0616dd3ee81a..cc484e527ec2 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java @@ -542,6 +542,7 @@ protected String getEncodedPathInContext(HttpServletRequest request, boolean inc if (included) yield URIUtil.encodePath((String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH)); else if (request instanceof ServletApiRequest apiRequest) + // Strip the context path from the canonically encoded path, so no need to re-encode (and mess up %2F etc.) yield Context.getPathInContext(request.getContextPath(), apiRequest.getRequest().getHttpURI().getCanonicalPath()); else yield URIUtil.encodePath(request.getServletPath()); @@ -560,6 +561,7 @@ else if (request instanceof ServletApiRequest apiRequest) if (included) yield URIUtil.encodePath(URIUtil.addPaths((String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH), (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO))); else if (request instanceof ServletApiRequest apiRequest) + // Strip the context path from the canonically encoded path, so no need to re-encode (and mess up %2F etc.) yield Context.getPathInContext(request.getContextPath(), apiRequest.getRequest().getHttpURI().getCanonicalPath()); else yield URIUtil.encodePath(URIUtil.addPaths(request.getServletPath(), request.getPathInfo())); From 3f64c910337d6d8cd60d0b99a8bb99b41935d61d Mon Sep 17 00:00:00 2001 From: gregw Date: Tue, 25 Jun 2024 08:23:45 +1000 Subject: [PATCH 05/18] updates from review --- .../eclipse/jetty/ee10/servlet/DefaultServlet.java | 11 ++++++++++- .../eclipse/jetty/ee10/servlet/ResourceServlet.java | 7 +++---- .../jetty/ee10/servlet/ServletCoreRequest.java | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java index 9dd50a924953..62a97210c825 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java @@ -16,6 +16,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.MappingMatch; import org.eclipse.jetty.server.Context; @@ -125,7 +126,7 @@ public class DefaultServlet extends ResourceServlet { private static final Logger LOG = LoggerFactory.getLogger(DefaultServlet.class); public static final String CONTEXT_INIT = "org.eclipse.jetty.servlet.Default."; - private AtomicBoolean warned = new AtomicBoolean(false); + private final AtomicBoolean warned = new AtomicBoolean(false); /** *

@@ -150,6 +151,14 @@ public String getInitParameter(String name) return value; } + @Override + public void init() throws ServletException + { + if ("true".equalsIgnoreCase(getInitParameter("pathInfoOnly"))) + LOG.warn("DefaultServlet pathInfoOnly is set to true"); + super.init(); + } + @Override protected String getEncodedPathInContext(HttpServletRequest request, boolean included) { diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java index cc484e527ec2..2f9fac29d4ca 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java @@ -63,7 +63,7 @@ import org.slf4j.LoggerFactory; /** - *

The default Servlet, normally mapped to {@code /}, that handles static resources.

+ *

A Servlet that handles static resources.

*

The following init parameters are supported:

*
*
acceptRanges
@@ -786,11 +786,10 @@ private HttpServletResponse getServletResponse(Response response) } } - static String getIncludedPathInContext(HttpServletRequest request, String includedServletPath, boolean isPathInfoOnly) + static String getIncludedPathInContext(HttpServletRequest request, String includedServletPath) { - String servletPath = isPathInfoOnly ? "/" : includedServletPath; String pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); - return URIUtil.addPaths(servletPath, pathInfo); + return URIUtil.addPaths(includedServletPath, pathInfo); } private static boolean isIncluded(HttpServletRequest request) diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletCoreRequest.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletCoreRequest.java index 37d8b71efd78..1c1d8758b7e9 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletCoreRequest.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletCoreRequest.java @@ -100,7 +100,7 @@ public static Request wrap(HttpServletRequest httpServletRequest) .authority(request.getServerName(), request.getServerPort()); if (included) - builder.path(addEncodedPaths(request.getContextPath(), encodePath(DefaultServlet.getIncludedPathInContext(request, includedServletPath, false)))); + builder.path(addEncodedPaths(request.getContextPath(), encodePath(DefaultServlet.getIncludedPathInContext(request, includedServletPath)))); else if (request.getDispatcherType() != DispatcherType.REQUEST) builder.path(addEncodedPaths(request.getContextPath(), encodePath(URIUtil.addPaths(_servletRequest.getServletPath(), _servletRequest.getPathInfo())))); else From 9f893c5f444eae24d28295d8f4c65586dc90576a Mon Sep 17 00:00:00 2001 From: gregw Date: Thu, 27 Jun 2024 18:06:39 +1000 Subject: [PATCH 06/18] Added tests for Resource and Default Servlet --- .../jetty/ee10/servlet/ResourceServlet.java | 4 +- .../ee10/servlet/DefaultServletTest.java | 3788 +++++++++++++++++ .../ee10/servlet/ResourceServletTest.java | 459 +- 3 files changed, 4045 insertions(+), 206 deletions(-) create mode 100644 jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java index 2f9fac29d4ca..23f7da90ba40 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ResourceServlet.java @@ -191,7 +191,9 @@ public void init() throws ServletException { try { - baseResource = URIUtil.isRelative(rb) ? contextHandler.getBaseResource().resolve(rb) : contextHandler.newResource(rb); + baseResource = URIUtil.isRelative(rb) ? baseResource.resolve(rb) : contextHandler.newResource(rb); + if (baseResource.isAlias()) + baseResource = contextHandler.newResource(baseResource.getRealURI()); } catch (Exception e) { diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java new file mode 100644 index 000000000000..9144b51ac403 --- /dev/null +++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/DefaultServletTest.java @@ -0,0 +1,3788 @@ +// +// ======================================================================== +// 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.eclipse.jetty.ee10.servlet; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import jakarta.servlet.DispatcherType; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.CompressedContentFormat; +import org.eclipse.jetty.http.DateGenerator; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.http.UriCompliance; +import org.eclipse.jetty.http.content.ResourceHttpContent; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.IOResources; +import org.eclipse.jetty.logging.StacklessLogging; +import org.eclipse.jetty.server.AllowedResourceAliasChecker; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.ResourceService; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SymlinkAllowedResourceAliasChecker; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenPaths; +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.BufferUtil; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.jetty.http.tools.matchers.HttpFieldsMatchers.containsHeader; +import static org.eclipse.jetty.http.tools.matchers.HttpFieldsMatchers.containsHeaderValue; +import static org.eclipse.jetty.http.tools.matchers.HttpFieldsMatchers.headerValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +@ExtendWith(WorkDirExtension.class) +public class DefaultServletTest +{ + public WorkDir workDir; + + public Path docRoot; + + // The name of the odd-jar used for testing "jar:file://" based resource access. + private static final String ODD_JAR = "jar-resource-odd.jar"; + + private Server server; + private LocalConnector connector; + private ServletContextHandler context; + + @BeforeEach + public void init() throws Exception + { + docRoot = workDir.getEmptyPathDir().resolve("docroot"); + FS.ensureDirExists(docRoot); + + server = new Server(); + + connector = new LocalConnector(server); + connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false); + Path extraJarResources = MavenPaths.findTestResourceFile(ODD_JAR); + URL[] urls = new URL[]{extraJarResources.toUri().toURL()}; + + ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader(); + URLClassLoader extraClassLoader = new URLClassLoader(urls, parentClassLoader); + + context = new ServletContextHandler(); + context.setBaseResourceAsPath(docRoot); + context.setContextPath("/context"); + context.setWelcomeFiles(new String[]{"index.html", "index.jsp", "index.htm"}); + context.setClassLoader(extraClassLoader); + + server.setHandler(context); + server.addConnector(connector); + + server.start(); + } + + @AfterEach + public void destroy() throws Exception + { + server.stop(); + server.join(); + } + + @Test + public void testGet() throws Exception + { + Path file = docRoot.resolve("file.txt"); + + context.addServlet(DefaultServlet.class, "/"); + + String rawResponse; + HttpTester.Response response; + + rawResponse = connector.getResponse(""" + GET /context/file.txt HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.NOT_FOUND_404)); + + Files.writeString(file, "How now brown cow", UTF_8); + + rawResponse = connector.getResponse(""" + GET /context/file.txt HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.toString(), response.getContent(), is("How now brown cow")); + } + + @Test + public void testHead() throws Exception + { + Path file = docRoot.resolve("file.txt"); + + context.addServlet(DefaultServlet.class, "/"); + + String rawResponse; + HttpTester.Response response; + + rawResponse = connector.getResponse(""" + HEAD /context/file.txt HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.NOT_FOUND_404)); + + Files.writeString(file, "How now brown cow", UTF_8); + + rawResponse = connector.getResponse(""" + HEAD /context/file.txt HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.toString(), response.getContent(), emptyString()); + } + + @Test + public void testPost() throws Exception + { + Path file = docRoot.resolve("file.txt"); + + context.addServlet(DefaultServlet.class, "/"); + + String rawResponse; + HttpTester.Response response; + + rawResponse = connector.getResponse(""" + POST /context/file.txt HTTP/1.1\r + Host: local\r + Connection: close\r + Content-Length: 5\r + \r + abcde + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.METHOD_NOT_ALLOWED_405)); + + Files.writeString(file, "How now brown cow", UTF_8); + + rawResponse = connector.getResponse(""" + POST /context/file.txt HTTP/1.1\r + Host: local\r + Connection: close\r + Content-Length: 5\r + \r + abcde + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.METHOD_NOT_ALLOWED_405)); + } + + @Test + public void testTrace() throws Exception + { + context.addServlet(DefaultServlet.class, "/"); + + String rawResponse; + HttpTester.Response response; + + rawResponse = connector.getResponse(""" + TRACE /context/file.txt HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.METHOD_NOT_ALLOWED_405)); + } + + @Test + public void testOptions() throws Exception + { + context.addServlet(DefaultServlet.class, "/"); + + String rawResponse; + HttpTester.Response response; + + rawResponse = connector.getResponse(""" + OPTIONS /context/ HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.get(HttpHeader.ALLOW), is("GET, HEAD, OPTIONS")); + } + + @Test + public void testGetBinaryWithUtfResponseEncoding() throws Exception + { + Path path = docRoot.resolve("keystore.p12"); + byte[] originalBytes; + + try (InputStream is = getClass().getResourceAsStream("/keystore.p12"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStream fos = Files.newOutputStream(path)) + { + IO.copy(is, baos); + originalBytes = baos.toByteArray(); + fos.write(originalBytes); + } + + context.setDefaultResponseCharacterEncoding("utf-8"); + context.addServlet(DefaultServlet.class, "/"); + + String rawResponse; + HttpTester.Response response; + + rawResponse = connector.getResponse(""" + GET /context/keystore.p12 HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.OK_200)); + byte[] readContentBytes = response.getContentBytes(); + + assertThat(Arrays.equals(readContentBytes, originalBytes), is(true)); + } + + @Test + public void testGetPercent2F() throws Exception + { + connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.UNSAFE); + + Path file = docRoot.resolve("file.txt"); + Files.writeString(file, "How now brown cow", UTF_8); + + context.addServlet(DefaultServlet.class, "/"); + + String rawResponse; + HttpTester.Response response; + + // Access normally, in root of context + + rawResponse = connector.getResponse(""" + GET /context/file.txt HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.OK_200)); + assertThat(response.toString(), response.getContent(), is("How now brown cow")); + + // Attempt access using "%2F" instead of "/", should be a 404 (mainly because context isn't found) + + rawResponse = connector.getResponse(""" + GET /context%2Ffile.txt HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.NOT_FOUND_404)); + + Path dir = docRoot.resolve("dirFoo"); + Files.createDirectory(dir); + Path other = dir.resolve("other.txt"); + Files.writeString(other, "In a while", UTF_8); + + // Attempt access of content in sub-dir of context, using "%2F" instead of "/", should be a 404 + // as neither getServletPath and getPathInfo are used and thus they don't throw. + rawResponse = connector.getResponse(""" + GET /context/dirFoo%2Fother.txt HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + response = HttpTester.parseResponse(rawResponse); + assertThat(response.toString(), response.getStatus(), is(HttpStatus.NOT_FOUND_404)); + } + + @Test + public void testListingWithSession() throws Exception + { + ServletHolder defholder = context.addServlet(DefaultServlet.class, "/"); + defholder.setInitParameter("dirAllowed", "true"); + defholder.setInitParameter("redirectWelcome", "false"); + defholder.setInitParameter("gzip", "false"); + + /* create some content in the docroot */ + FS.ensureDirExists(docRoot.resolve("one")); + FS.ensureDirExists(docRoot.resolve("two")); + FS.ensureDirExists(docRoot.resolve("three")); + + String rawResponse = connector.getResponse(""" + GET /context/;JSESSIONID=1234567890 HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """); + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + assertThat(response.getStatus(), is(200)); + + String body = response.getContent(); + + assertThat(body, containsString("/one/;JSESSIONID=1234567890")); + assertThat(body, containsString("/two/;JSESSIONID=1234567890")); + assertThat(body, containsString("/three/;JSESSIONID=1234567890")); + + assertThat(body, not(containsString(" HTTP/1.1\r + Host: local\r + Connection: close\r + \r + """; + String rawResponse = connector.getResponse(req1); + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + String body = response.getContent(); + assertThat(body, not(containsString("