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
The following commit(s) were added to refs/heads/main by this push: new 1692b97445 Error dispatch must now always use GET 1692b97445 is described below commit 1692b97445506db6599824c6509b7f59e9bf81e1 Author: Mark Thomas <ma...@apache.org> AuthorDate: Thu Nov 23 16:25:02 2023 +0000 Error dispatch must now always use GET --- java/jakarta/servlet/RequestDispatcher.java | 8 ++ java/jakarta/servlet/jsp/ErrorData.java | 19 ++- java/jakarta/servlet/jsp/PageContext.java | 1 + .../catalina/core/ApplicationDispatcher.java | 14 +++ .../catalina/core/ApplicationHttpRequest.java | 22 ++++ .../apache/catalina/core/StandardHostValve.java | 2 + .../org/apache/jasper/runtime/PageContextImpl.java | 1 + .../catalina/core/TestApplicationDispatcher.java | 133 +++++++++++++++++++++ test/webapp/dispatch/error.jsp | 23 ++++ test/webapp/dispatch/trigger-with-error-page.jsp | 20 ++++ .../webapp/dispatch/trigger-without-error-page.jsp | 19 +++ webapps/docs/changelog.xml | 5 + 12 files changed, 264 insertions(+), 3 deletions(-) diff --git a/java/jakarta/servlet/RequestDispatcher.java b/java/jakarta/servlet/RequestDispatcher.java index bfcdfdd981..c378d9e32a 100644 --- a/java/jakarta/servlet/RequestDispatcher.java +++ b/java/jakarta/servlet/RequestDispatcher.java @@ -185,6 +185,14 @@ public interface RequestDispatcher { */ String ERROR_MESSAGE = "jakarta.servlet.error.message"; + /** + * The name of the request attribute under which the method of the request whose processing caused the error is + * propagated during an error dispatch. + * + * @since Servlet 6.1 + */ + String ERROR_METHOD = "jakarta.servlet.error.method"; + /** * The name of the request attribute that should be set by the container when custom error-handling servlet or JSP * page is invoked. The value of the attribute is of type {@code String}. See the chapter "Error Handling" in the diff --git a/java/jakarta/servlet/jsp/ErrorData.java b/java/jakarta/servlet/jsp/ErrorData.java index a1d445b059..3137071b15 100644 --- a/java/jakarta/servlet/jsp/ErrorData.java +++ b/java/jakarta/servlet/jsp/ErrorData.java @@ -29,6 +29,7 @@ public final class ErrorData { private final Throwable throwable; private final int statusCode; + private final String method; private final String uri; private final String servletName; private final String queryString; @@ -41,11 +42,11 @@ public final class ErrorData { * @param uri The request URI * @param servletName The name of the servlet invoked * - * @deprecated Use {#link {@link ErrorData#ErrorData(Throwable, int, String, String, String)} + * @deprecated Use {#link {@link ErrorData#ErrorData(Throwable, int, String, String, String, String)} */ @Deprecated(since = "4.0", forRemoval = true) public ErrorData(Throwable throwable, int statusCode, String uri, String servletName) { - this(throwable, statusCode, uri, servletName, null); + this(throwable, statusCode, null, uri, servletName, null); } /** @@ -53,15 +54,18 @@ public final class ErrorData { * * @param throwable The Throwable that is the cause of the error * @param statusCode The status code of the error + * @param method The request method * @param uri The request URI * @param servletName The name of the servlet invoked * @param queryString The request query string * * @since JSP 4.0 */ - public ErrorData(Throwable throwable, int statusCode, String uri, String servletName, String queryString) { + public ErrorData(Throwable throwable, int statusCode, String method, String uri, String servletName, + String queryString) { this.throwable = throwable; this.statusCode = statusCode; + this.method = method; this.uri = uri; this.servletName = servletName; this.queryString = queryString; @@ -85,6 +89,15 @@ public final class ErrorData { return this.statusCode; } + /** + * Returns the request method. + * + * @return The request method + */ + public String getMethod() { + return this.method; + } + /** * Returns the request URI. * diff --git a/java/jakarta/servlet/jsp/PageContext.java b/java/jakarta/servlet/jsp/PageContext.java index edc93bab8a..7a7df8d355 100644 --- a/java/jakarta/servlet/jsp/PageContext.java +++ b/java/jakarta/servlet/jsp/PageContext.java @@ -438,6 +438,7 @@ public abstract class PageContext extends JspContext { } return new ErrorData((Throwable) getRequest().getAttribute(RequestDispatcher.ERROR_EXCEPTION), status, + (String) getRequest().getAttribute(RequestDispatcher.ERROR_METHOD), (String) getRequest().getAttribute(RequestDispatcher.ERROR_REQUEST_URI), (String) getRequest().getAttribute(RequestDispatcher.ERROR_SERVLET_NAME), (String) getRequest().getAttribute(RequestDispatcher.ERROR_QUERY_STRING)); diff --git a/java/org/apache/catalina/core/ApplicationDispatcher.java b/java/org/apache/catalina/core/ApplicationDispatcher.java index 420286ad9b..ebc5111ecd 100644 --- a/java/org/apache/catalina/core/ApplicationDispatcher.java +++ b/java/org/apache/catalina/core/ApplicationDispatcher.java @@ -234,6 +234,13 @@ final class ApplicationDispatcher implements AsyncDispatcher, RequestDispatcher ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state); HttpServletRequest hrequest = state.hrequest; + /* + * All ERROR dispatches must be GET requests. Use the presence of ERROR_METHOD to determine if this is an + * error dispatch as not all components (JSP) set the dispatcher type. + */ + if (request.getAttribute(ERROR_METHOD) != null) { + wrequest.setMethod("GET"); + } wrequest.setRequestURI(hrequest.getRequestURI()); wrequest.setContextPath(hrequest.getContextPath()); wrequest.setServletPath(hrequest.getServletPath()); @@ -257,6 +264,13 @@ final class ApplicationDispatcher implements AsyncDispatcher, RequestDispatcher wrequest.setAttribute(FORWARD_MAPPING, hrequest.getHttpServletMapping()); } + /* + * All ERROR dispatches must be GET requests. Use the presence of ERROR_METHOD to determine if this is an + * error dispatch as not all components (JSP) set the dispatcher type. + */ + if (request.getAttribute(ERROR_METHOD) != null) { + wrequest.setMethod("GET"); + } wrequest.setContextPath(context.getEncodedPath()); wrequest.setRequestURI(requestURI); wrequest.setServletPath(servletPath); diff --git a/java/org/apache/catalina/core/ApplicationHttpRequest.java b/java/org/apache/catalina/core/ApplicationHttpRequest.java index 9b60f94814..b518dbe454 100644 --- a/java/org/apache/catalina/core/ApplicationHttpRequest.java +++ b/java/org/apache/catalina/core/ApplicationHttpRequest.java @@ -115,6 +115,10 @@ class ApplicationHttpRequest extends HttpServletRequestWrapper { */ protected DispatcherType dispatcherType = null; + /** + * The HTTP method for this request. + */ + private String method; /** * The request parameters for this request. This is initialized from the wrapped request. @@ -372,6 +376,13 @@ class ApplicationHttpRequest extends HttpServletRequestWrapper { } + + @Override + public String getMethod() { + return method; + } + + /** * Override the <code>getParameter()</code> method of the wrapped request. * @@ -651,6 +662,7 @@ class ApplicationHttpRequest extends HttpServletRequestWrapper { // Initialize the path elements for this request contextPath = request.getContextPath(); + method = request.getMethod(); pathInfo = request.getPathInfo(); queryString = request.getQueryString(); requestURI = request.getRequestURI(); @@ -659,6 +671,16 @@ class ApplicationHttpRequest extends HttpServletRequestWrapper { } + /** + * Set the request method for this request. + * + * @param method The new request method + */ + void setMethod(String method) { + this.method = method; + } + + /** * Set the request URI for this request. * diff --git a/java/org/apache/catalina/core/StandardHostValve.java b/java/org/apache/catalina/core/StandardHostValve.java index 7b5dacbd72..b4be2e6b6f 100644 --- a/java/org/apache/catalina/core/StandardHostValve.java +++ b/java/org/apache/catalina/core/StandardHostValve.java @@ -218,6 +218,7 @@ final class StandardHostValve extends ValveBase { if (wrapper != null) { request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, wrapper.getName()); } + request.setAttribute(RequestDispatcher.ERROR_METHOD, request.getMethod()); request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI()); request.setAttribute(RequestDispatcher.ERROR_QUERY_STRING, request.getQueryString()); if (custom(request, response, errorPage)) { @@ -283,6 +284,7 @@ final class StandardHostValve extends ValveBase { if (wrapper != null) { request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, wrapper.getName()); } + request.setAttribute(RequestDispatcher.ERROR_METHOD, request.getMethod()); request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI()); request.setAttribute(RequestDispatcher.ERROR_QUERY_STRING, request.getQueryString()); request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, realError.getClass()); diff --git a/java/org/apache/jasper/runtime/PageContextImpl.java b/java/org/apache/jasper/runtime/PageContextImpl.java index a07f9c1c9c..b38ce953f4 100644 --- a/java/org/apache/jasper/runtime/PageContextImpl.java +++ b/java/org/apache/jasper/runtime/PageContextImpl.java @@ -595,6 +595,7 @@ public class PageContextImpl extends PageContext { request.setAttribute(EXCEPTION, t); request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, Integer.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)); + request.setAttribute(RequestDispatcher.ERROR_METHOD, ((HttpServletRequest) request).getMethod()); request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, ((HttpServletRequest) request).getRequestURI()); request.setAttribute(RequestDispatcher.ERROR_QUERY_STRING, ((HttpServletRequest) request).getQueryString()); request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, config.getServletName()); diff --git a/test/org/apache/catalina/core/TestApplicationDispatcher.java b/test/org/apache/catalina/core/TestApplicationDispatcher.java new file mode 100644 index 0000000000..12e838c7a9 --- /dev/null +++ b/test/org/apache/catalina/core/TestApplicationDispatcher.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.catalina.core; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; + +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.descriptor.web.ErrorPage; + + +public class TestApplicationDispatcher extends TomcatBaseTest { + + @Test + public void testDispatchErrorToServlet() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = tomcat.addContext("", null); + + Tomcat.addServlet(ctx, "errorTrigger", new ErrorTriggerServlet()); + ctx.addServletMappingDecoded("/trigger", "errorTrigger"); + + Tomcat.addServlet(ctx, "errorHandling", new ErrorHandlingServlet()); + ctx.addServletMappingDecoded("/error", "errorHandling"); + + ErrorPage ep = new ErrorPage(); + ep.setExceptionType(Throwable.class.getName()); + ep.setLocation("/error"); + ctx.addErrorPage(ep); + + tomcat.start(); + + ByteChunk response = new ByteChunk(); + int rc = postUrl(new byte[0], "http://localhost:" + getPort() + "/trigger", response, null); + + Assert.assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, rc); + Assert.assertEquals("OK", response.toString()); + } + + + @Test + public void testDispatchErrorToJspWithErrorPage() throws Exception { + getTomcatInstanceTestWebapp(false, true); + + ByteChunk response = new ByteChunk(); + int rc = postUrl(new byte[0], "http://localhost:" + getPort() + "/test/dispatch/trigger-with-error-page.jsp", + response, null); + + Assert.assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, rc); + Assert.assertEquals("OK", response.toString().trim()); + } + + + @Test + public void testDispatchErrorToJspWithoutErrorPage() throws Exception { + Tomcat tomcat = getTomcatInstanceTestWebapp(false, false); + Context ctx = (Context) tomcat.getHost().findChildren()[0]; + + Tomcat.addServlet(ctx, "errorHandling", new ErrorHandlingServlet()); + ctx.addServletMappingDecoded("/error", "errorHandling"); + + ErrorPage ep = new ErrorPage(); + ep.setExceptionType(Throwable.class.getName()); + ep.setLocation("/error"); + ctx.addErrorPage(ep); + + tomcat.start(); + + ByteChunk response = new ByteChunk(); + int rc = postUrl(new byte[0], "http://localhost:" + getPort() + "/test/dispatch/trigger-without-error-page.jsp", + response, null); + + Assert.assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, rc); + Assert.assertEquals("OK", response.toString().trim()); + } + + + private static class ErrorTriggerServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + throw new RuntimeException("Forced failure"); + } + } + + + private static class ErrorHandlingServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setContentType("text/plain"); + resp.setCharacterEncoding(StandardCharsets.UTF_8); + PrintWriter pw = resp.getWriter(); + + if ("POST".equals(req.getAttribute(RequestDispatcher.ERROR_METHOD)) && + "GET".equals(req.getMethod())) { + pw.print("OK"); + } else { + pw.print("FAIL"); + } + } + } +} diff --git a/test/webapp/dispatch/error.jsp b/test/webapp/dispatch/error.jsp new file mode 100644 index 0000000000..d45f08f13b --- /dev/null +++ b/test/webapp/dispatch/error.jsp @@ -0,0 +1,23 @@ +<%-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--%> +<%@ page isErrorPage="true" %><% +if ("POST".equals(request.getAttribute(RequestDispatcher.ERROR_METHOD)) && + "GET".equals(request.getMethod())) { + out.print("OK"); +} else { + out.print("FAIL"); +}%> \ No newline at end of file diff --git a/test/webapp/dispatch/trigger-with-error-page.jsp b/test/webapp/dispatch/trigger-with-error-page.jsp new file mode 100644 index 0000000000..d3289fed2c --- /dev/null +++ b/test/webapp/dispatch/trigger-with-error-page.jsp @@ -0,0 +1,20 @@ +<%-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--%> +<%@ page errorPage="/dispatch/error.jsp" %> +<% + throw new RuntimeException("Forced failure"); +%> \ No newline at end of file diff --git a/test/webapp/dispatch/trigger-without-error-page.jsp b/test/webapp/dispatch/trigger-without-error-page.jsp new file mode 100644 index 0000000000..8b0fb9bcfc --- /dev/null +++ b/test/webapp/dispatch/trigger-without-error-page.jsp @@ -0,0 +1,19 @@ +<%-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--%> +<% + throw new RuntimeException("Forced failure"); +%> \ No newline at end of file diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 9a7e836924..e4f507a131 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -117,6 +117,11 @@ replaces the now deprecated Tomcat specific request attribute <code>org.apache.tomcat.util.net.secure_protocol_version</code>. (markt) </add> + <add> + Align behaviour with the latest addition to the Servlet 6.1 + specification that requires that all HTTP error dispatches use the GET + method. (markt) + </add> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org