diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/CrossContextDispatcherTest.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/CrossContextDispatcherTest.java index 7ebd4b6f73d1..c69db29de266 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/CrossContextDispatcherTest.java +++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/CrossContextDispatcherTest.java @@ -51,7 +51,9 @@ import jakarta.servlet.http.HttpServletResponseWrapper; import jakarta.servlet.http.Part; import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; @@ -60,6 +62,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.UrlEncoded; import org.eclipse.jetty.util.resource.ResourceFactory; import org.junit.jupiter.api.AfterEach; @@ -295,6 +298,56 @@ public void testSimpleCrossContextForward() throws Exception assertThat(content, containsString("REQUEST_URI=/foreign/verify/pinfo")); } + @Test + public void testEncodedCrossContextForward() throws Exception + { + _server.stop(); + _targetServletContextHandler.addServlet(VerifyForwardServlet.class, "/verify/*"); + _targetServletContextHandler.getServletHandler().setDecodeAmbiguousURIs(true); + _contextHandler.addServlet(CrossContextDispatchServlet.class, "/dispatch/*"); + _contextHandler.getServletHandler().setDecodeAmbiguousURIs(true); + _server.getContainedBeans(HttpConnectionFactory.class).forEach(f -> f.getHttpConfiguration().setUriCompliance(UriCompliance.DEFAULT.with("test", UriCompliance.Violation.AMBIGUOUS_PATH_ENCODING))); + _server.start(); + + String rawResponse = _connector.getResponse(""" + GET /context/dispatch/?forward=/verify/%25%20test HTTP/1.1\r + Host: localhost\r + Connection: close\r + \r + """); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + String content = response.getContent(); + String[] contentLines = content.split("\\n"); + + //verify forward attributes + assertThat(content, containsString("Verified!")); + assertThat(content, containsString("jakarta.servlet.forward.context_path=/context")); + assertThat(content, containsString("jakarta.servlet.forward.servlet_path=/dispatch")); + assertThat(content, containsString("jakarta.servlet.forward.path_info=/")); + + String forwardMapping = extractLine(contentLines, "jakarta.servlet.forward.mapping="); + assertNotNull(forwardMapping); + assertThat(forwardMapping, containsString("CrossContextDispatchServlet")); + assertThat(content, containsString("jakarta.servlet.forward.query_string=forward=/verify")); + assertThat(content, containsString("jakarta.servlet.forward.request_uri=/context/dispatch/")); + //verify request values + assertThat(content, containsString("REQUEST_URL=http://localhost/foreign/")); + assertThat(content, containsString("CONTEXT_PATH=/foreign")); + assertThat(content, containsString("SERVLET_PATH=/verify")); + assertThat(content, containsString("PATH_INFO=/% test/pinfo")); + String mapping = extractLine(contentLines, "MAPPING="); + assertNotNull(mapping); + assertThat(mapping, containsString("VerifyForwardServlet")); + String params = extractLine(contentLines, "PARAMS="); + assertNotNull(params); + params = params.substring(params.indexOf("=") + 1); + params = params.substring(1, params.length() - 1); //dump leading, trailing [ ] + assertThat(Arrays.asList(StringUtil.csvSplit(params)), containsInAnyOrder("a", "forward")); + assertThat(content, containsString("REQUEST_URI=/foreign/verify/%25%20test/pinfo")); + } + @Test public void testSimpleCrossContextInclude() throws Exception { @@ -831,7 +884,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t { ServletContext foreign = getServletContext().getContext(ctx); assertNotNull(foreign); - dispatcher = foreign.getRequestDispatcher(request.getParameter("forward") + "/pinfo?a=b"); + dispatcher = foreign.getRequestDispatcher(URIUtil.encodePath(request.getParameter("forward")) + "/pinfo?a=b"); if (dispatcher == null) response.sendError(404, "No dispatcher for forward"); @@ -994,7 +1047,7 @@ public void service(ServletRequest req, ServletResponse res) throws ServletExcep res.getWriter().println("----------- FORWARD ATTRIBUTES"); res.getWriter().println(RequestDispatcher.FORWARD_CONTEXT_PATH + "=" + req.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH)); res.getWriter().println(RequestDispatcher.FORWARD_SERVLET_PATH + "=" + req.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH)); - res.getWriter().println(RequestDispatcher.FORWARD_PATH_INFO + "=" + req.getAttribute(RequestDispatcher.FORWARD_PATH_INFO)); + res.getWriter().println(RequestDispatcher.FORWARD_PATH_INFO + "=" + req.getAttribute(RequestDispatcher.FORWARD_PATH_INFO)); res.getWriter().println(RequestDispatcher.FORWARD_MAPPING + "=" + req.getAttribute(RequestDispatcher.FORWARD_MAPPING)); res.getWriter().println(RequestDispatcher.FORWARD_QUERY_STRING + "=" + req.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING)); res.getWriter().println(RequestDispatcher.FORWARD_REQUEST_URI + "=" + req.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI)); diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java index 7e52ebebad13..080c6ad62ca4 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java @@ -1915,14 +1915,14 @@ public RequestDispatcher getRequestDispatcher(String uriInContext) String contextPath = getContextPath(); // uriInContext is canonicalized by HttpURI. HttpURI.Mutable uri = HttpURI.build(uriInContext); - String pathInfo = uri.getCanonicalPath(); + String pathInfo = uri.getDecodedPath(); if (StringUtil.isEmpty(pathInfo)) return null; if (!StringUtil.isEmpty(contextPath)) { uri.path(URIUtil.addPaths(contextPath, uri.getPath())); - pathInfo = uri.getCanonicalPath().substring(contextPath.length()); + pathInfo = uri.getDecodedPath().substring(contextPath.length()); } return new Dispatcher(ContextHandler.this, uri, pathInfo); } diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Dispatcher.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Dispatcher.java index 6824fd4fbddf..4d58e53e2ef0 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Dispatcher.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Dispatcher.java @@ -31,7 +31,6 @@ import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.Fields; -import org.eclipse.jetty.util.URIUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -187,9 +186,8 @@ protected void forward(ServletRequest request, ServletResponse response, Dispatc if (query == null) query = old_uri.getQuery(); - String decodedPathInContext = URIUtil.decodePath(_pathInContext); baseRequest.setHttpURI(HttpURI.build(old_uri, _uri.getPath(), _uri.getParam(), query)); - baseRequest.setContext(_contextHandler.getServletContext(), decodedPathInContext); + baseRequest.setContext(_contextHandler.getServletContext(), _pathInContext); if (_uri.getQuery() != null || old_uri.getQuery() != null) { @@ -212,7 +210,7 @@ protected void forward(ServletRequest request, ServletResponse response, Dispatc } } - _contextHandler.handle(decodedPathInContext, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); + _contextHandler.handle(_pathInContext, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); // If we are not async and not closed already, then close via the possibly wrapped response. if (!baseRequest.getHttpChannelState().isAsync() && !baseResponse.getHttpOutput().isClosed()) diff --git a/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/CrossContextDispatcherTest.java b/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/CrossContextDispatcherTest.java index 6155caa40930..c98be9a65c4b 100644 --- a/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/CrossContextDispatcherTest.java +++ b/jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/CrossContextDispatcherTest.java @@ -53,7 +53,9 @@ import org.eclipse.jetty.ee9.nested.Dispatcher; import org.eclipse.jetty.ee9.nested.Request; import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; @@ -62,6 +64,7 @@ import org.eclipse.jetty.toolchain.test.MavenPaths; import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.UrlEncoded; import org.eclipse.jetty.util.resource.ResourceFactory; import org.junit.jupiter.api.AfterEach; @@ -298,6 +301,54 @@ public void testSimpleCrossContextForward() throws Exception assertThat(content, containsString("TYPE=org.eclipse.jetty.ee9.servlet.CrossContextDispatcherTest$MyHttpServletRequestWrapper")); } + @Test + public void testEncodedCrossContextForward() throws Exception + { + _server.stop(); + _targetServletContextHandler.addServlet(VerifyForwardServlet.class, "/verify/*"); + _contextHandler.addServlet(CrossContextDispatchServlet.class, "/dispatch/*"); + _server.getContainedBeans(HttpConnectionFactory.class).forEach(f -> f.getHttpConfiguration().setUriCompliance(UriCompliance.DEFAULT.with("test", UriCompliance.Violation.AMBIGUOUS_PATH_ENCODING))); + _server.start(); + + String rawResponse = _connector.getResponse(""" + GET /context/dispatch/?forward=/verify/%25%20test HTTP/1.1\r + Host: localhost\r + Connection: close\r + \r + """); + + HttpTester.Response response = HttpTester.parseResponse(rawResponse); + + String content = response.getContent(); + String[] contentLines = content.split("\\n"); + + //verify forward attributes + assertThat(content, containsString("Verified!")); + assertThat(content, containsString("jakarta.servlet.forward.context_path=/context")); + assertThat(content, containsString("jakarta.servlet.forward.servlet_path=/dispatch")); + assertThat(content, containsString("jakarta.servlet.forward.path_info=/")); + + String forwardMapping = extractLine(contentLines, "jakarta.servlet.forward.mapping="); + assertNotNull(forwardMapping); + assertThat(forwardMapping, containsString("CrossContextDispatchServlet")); + assertThat(content, containsString("jakarta.servlet.forward.query_string=forward=/verify")); + assertThat(content, containsString("jakarta.servlet.forward.request_uri=/context/dispatch/")); + //verify request values + assertThat(content, containsString("REQUEST_URL=http://localhost/foreign/")); + assertThat(content, containsString("CONTEXT_PATH=/foreign")); + assertThat(content, containsString("SERVLET_PATH=/verify")); + assertThat(content, containsString("PATH_INFO=/% test/pinfo")); + String mapping = extractLine(contentLines, "MAPPING="); + assertNotNull(mapping); + assertThat(mapping, containsString("VerifyForwardServlet")); + String params = extractLine(contentLines, "PARAMS="); + assertNotNull(params); + params = params.substring(params.indexOf("=") + 1); + params = params.substring(1, params.length() - 1); //dump leading, trailing [ ] + assertThat(Arrays.asList(StringUtil.csvSplit(params)), containsInAnyOrder("a", "forward")); + assertThat(content, containsString("REQUEST_URI=/foreign/verify/%25%20test/pinfo")); + } + @Test public void testSimpleCrossContextInclude() throws Exception { @@ -847,7 +898,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t { ServletContext foreign = getServletContext().getContext(ctx); assertNotNull(foreign); - dispatcher = foreign.getRequestDispatcher(request.getParameter("forward") + "/pinfo?a=b"); + dispatcher = foreign.getRequestDispatcher(URIUtil.encodePath(request.getParameter("forward")) + "/pinfo?a=b"); if (dispatcher == null) response.sendError(404, "No dispatcher for forward"); @@ -1020,7 +1071,7 @@ public void service(ServletRequest req, ServletResponse res) throws ServletExcep res.getWriter().println("----------- FORWARD ATTRIBUTES"); res.getWriter().println(RequestDispatcher.FORWARD_CONTEXT_PATH + "=" + req.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH)); res.getWriter().println(RequestDispatcher.FORWARD_SERVLET_PATH + "=" + req.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH)); - res.getWriter().println(RequestDispatcher.FORWARD_PATH_INFO + "=" + req.getAttribute(RequestDispatcher.FORWARD_PATH_INFO)); + res.getWriter().println(RequestDispatcher.FORWARD_PATH_INFO + "=" + req.getAttribute(RequestDispatcher.FORWARD_PATH_INFO)); res.getWriter().println(RequestDispatcher.FORWARD_MAPPING + "=" + req.getAttribute(RequestDispatcher.FORWARD_MAPPING)); res.getWriter().println(RequestDispatcher.FORWARD_QUERY_STRING + "=" + req.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING)); res.getWriter().println(RequestDispatcher.FORWARD_REQUEST_URI + "=" + req.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI));