This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit c93e9ea3f696674445845ddfaaa7b2f1aa0bd94f Author: Mark Thomas <ma...@apache.org> AuthorDate: Fri Jan 27 12:36:40 2023 +0000 Context root mapping applies with or without trailing / Clarification from Jakarta Servlet project --- java/org/apache/catalina/mapper/Mapper.java | 20 +++- .../apache/catalina/mapper/TestMapperWebapps.java | 117 +++++++++++++++++---- webapps/docs/changelog.xml | 5 + 3 files changed, 118 insertions(+), 24 deletions(-) diff --git a/java/org/apache/catalina/mapper/Mapper.java b/java/org/apache/catalina/mapper/Mapper.java index c18554c8f6..d8a35c9203 100644 --- a/java/org/apache/catalina/mapper/Mapper.java +++ b/java/org/apache/catalina/mapper/Mapper.java @@ -51,9 +51,21 @@ public final class Mapper { private static final StringManager sm = StringManager.getManager(Mapper.class); - // ----------------------------------------------------- Instance Variables + private static final CharChunk CONTEXT_ROOT_MAPPED_PATH_CHAR_CHUNK; + + static { + CONTEXT_ROOT_MAPPED_PATH_CHAR_CHUNK = new CharChunk(1); + try { + CONTEXT_ROOT_MAPPED_PATH_CHAR_CHUNK.append('/'); + } catch (IOException ioe) { + // Should never happen. Convert to a runtime exception if it does. + throw new IllegalStateException(ioe); + } + } + // ----------------------------------------------------- Instance Variables + /** * Array containing the virtual hosts definitions. */ @@ -995,6 +1007,12 @@ public final class Mapper { * Exact mapping. */ private final void internalMapExactWrapper(MappedWrapper[] wrappers, CharChunk path, MappingData mappingData) { + if (path.length() == 0) { + /* + * Looking for a context root mapped servlet but that will be stored under the name "/" + */ + path = CONTEXT_ROOT_MAPPED_PATH_CHAR_CHUNK; + } MappedWrapper wrapper = exactFind(wrappers, path); if (wrapper != null) { mappingData.requestPath.setString(wrapper.name); diff --git a/test/org/apache/catalina/mapper/TestMapperWebapps.java b/test/org/apache/catalina/mapper/TestMapperWebapps.java index 91262d6397..39b4b64e1b 100644 --- a/test/org/apache/catalina/mapper/TestMapperWebapps.java +++ b/test/org/apache/catalina/mapper/TestMapperWebapps.java @@ -18,6 +18,8 @@ package org.apache.catalina.mapper; import java.io.File; import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; @@ -27,7 +29,6 @@ import jakarta.servlet.http.HttpServletResponse; import org.junit.Assert; import org.junit.Test; -import org.apache.catalina.Context; import org.apache.catalina.core.StandardContext; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; @@ -38,49 +39,119 @@ import org.apache.tomcat.util.descriptor.web.SecurityConstraint; import org.apache.tomcat.websocket.server.WsContextListener; /** - * Mapper tests that use real web applications on a running Tomcat. + * Mapper tests that use web applications on a running Tomcat instance. */ public class TestMapperWebapps extends TomcatBaseTest { @Test - public void testContextRoot_Bug53339() throws Exception { + public void testContextRootMapping01() throws Exception { + doTestContextRootMapping("", false, false); + } + + + @Test + public void testContextRootMapping02() throws Exception { + doTestContextRootMapping("", true, false); + } + + + @Test + public void testContextRootMapping03() throws Exception { + doTestContextRootMapping("", false, true); + } + + + @Test + public void testContextRootMapping04() throws Exception { + doTestContextRootMapping("", true, true); + } + + + @Test + public void testContextRootMapping05() throws Exception { + doTestContextRootMapping("/somepath", false, false); + } + + + @Test + public void testContextRootMapping06() throws Exception { + doTestContextRootMapping("/somepath", true, false); + } + + + @Test + public void testContextRootMapping07() throws Exception { + doTestContextRootMapping("/somepath", false, true); + } + + + @Test + public void testContextRootMapping08() throws Exception { + doTestContextRootMapping("/somepath", true, true); + } + + + @Test + public void testContextRootMapping09() throws Exception { + doTestContextRootMapping("/some/other/path", false, false); + } + + + @Test + public void testContextRootMapping10() throws Exception { + doTestContextRootMapping("/some/other/path", true, false); + } + + + @Test + public void testContextRootMapping11() throws Exception { + doTestContextRootMapping("/some/other/path", false, true); + } + + + @Test + public void testContextRootMapping12() throws Exception { + doTestContextRootMapping("/some/other/path", true, true); + } + + + private void doTestContextRootMapping(String contextPath, boolean trailingSlash, boolean rootRedirect) + throws Exception { Tomcat tomcat = getTomcatInstance(); tomcat.enableNaming(); // No file system docBase required - Context ctx = tomcat.addContext("", null); + StandardContext ctx = (StandardContext) tomcat.addContext(contextPath, null); + ctx.setMapperContextRootRedirectEnabled(rootRedirect); - Tomcat.addServlet(ctx, "Bug53356", new Bug53356Servlet()); - ctx.addServletMappingDecoded("", "Bug53356"); + Tomcat.addServlet(ctx, "ReportMapping", new ReportMappingServlet()); + ctx.addServletMappingDecoded("", "ReportMapping"); tomcat.start(); - ByteChunk body = getUrl("http://localhost:" + getPort()); + ByteChunk bc = getUrl("http://localhost:" + getPort() + contextPath + (trailingSlash ? "/" : "")); - Assert.assertEquals("OK", body.toString()); + String body = bc.toString(); + System.out.println(body); + Assert.assertTrue(body, body.contains("ContextPath: [" + contextPath + "]")); + Assert.assertTrue(body, body.contains("ServletPath: []")); + Assert.assertTrue(body, body.contains("PathInfo: [/]")); } - private static class Bug53356Servlet extends HttpServlet { + + private static class ReportMappingServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - // Confirm behaviour as per Servlet 12.2 - boolean pass = "/".equals(req.getPathInfo()); - if (pass) { - pass = "".equals(req.getServletPath()); - } - if (pass) { - pass = "".equals(req.getContextPath()); - } - + resp.setCharacterEncoding(StandardCharsets.UTF_8); resp.setContentType("text/plain"); - if (pass) { - resp.getWriter().write("OK"); - } else { - resp.getWriter().write("FAIL"); - } + PrintWriter pw = resp.getWriter(); + + pw.print("ContextPath: [" + req.getContextPath() + "]\n"); + pw.print("ServletPath: [" + req.getServletPath() + "]\n"); + pw.print("PathInfo: [" + req.getPathInfo() + "]\n"); } } diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index ede9540a77..7f09b0c3d2 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -143,6 +143,11 @@ <code>AtomicInteger</code> to track request count and error count for servlets. (markt) </fix> + <fix> + Implement clarification from Jakarta Servlet project that Servlets + mapped to the context root should be mapped for requests to the + context root with or without the trailing <code>/</code>. (markt) + </fix> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org