This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 8.5.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit ab34aa9a338c3de113dfe4dda1ebbd49defb7d04 Author: Mark Thomas <ma...@apache.org> AuthorDate: Wed Jan 25 16:28:34 2023 +0000 Fix BZ 66442 - h2 responses without bodies should not use DATA frames https://bz.apache.org/bugzilla/show_bug.cgi?id=66442 --- java/org/apache/coyote/http2/Stream.java | 7 ++++ java/org/apache/coyote/http2/StreamProcessor.java | 4 ++ test/org/apache/coyote/http2/Http2TestBase.java | 12 ++++++ .../apache/coyote/http2/TestStreamProcessor.java | 49 ++++++++++++++++++++++ webapps/docs/changelog.xml | 5 +++ 5 files changed, 77 insertions(+) diff --git a/java/org/apache/coyote/http2/Stream.java b/java/org/apache/coyote/http2/Stream.java index 51f9f72f76..2c05ff688f 100644 --- a/java/org/apache/coyote/http2/Stream.java +++ b/java/org/apache/coyote/http2/Stream.java @@ -34,6 +34,7 @@ import org.apache.coyote.Response; import org.apache.coyote.http11.HttpOutputBuffer; import org.apache.coyote.http11.OutputFilter; import org.apache.coyote.http11.filters.SavedRequestInputFilter; +import org.apache.coyote.http11.filters.VoidOutputFilter; import org.apache.coyote.http2.HpackDecoder.HeaderEmitter; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -425,6 +426,12 @@ class Stream extends AbstractNonZeroStream implements HeaderEmitter { } + void configureVoidOutputFilter() { + addOutputFilter(new VoidOutputFilter()); + // Prevent further writes by the application + streamOutputBuffer.closed = true; + } + private void parseAuthority(String value, boolean host) throws HpackException { int i; try { diff --git a/java/org/apache/coyote/http2/StreamProcessor.java b/java/org/apache/coyote/http2/StreamProcessor.java index d6e48624b3..d51ad971e1 100644 --- a/java/org/apache/coyote/http2/StreamProcessor.java +++ b/java/org/apache/coyote/http2/StreamProcessor.java @@ -182,6 +182,10 @@ class StreamProcessor extends AbstractProcessor { headers.addValue("content-length").setLong(contentLength); } } else { + // Disable response body + if (stream != null) { + stream.configureVoidOutputFilter(); + } if (statusCode == 205) { // RFC 7231 requires the server to explicitly signal an empty // response in this case diff --git a/test/org/apache/coyote/http2/Http2TestBase.java b/test/org/apache/coyote/http2/Http2TestBase.java index e6f60a2f3a..5d4b2d9dae 100644 --- a/test/org/apache/coyote/http2/Http2TestBase.java +++ b/test/org/apache/coyote/http2/Http2TestBase.java @@ -1269,6 +1269,18 @@ public abstract class Http2TestBase extends TomcatBaseTest { } + public static class NoContentServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + } + + protected static class SimpleServlet extends HttpServlet { private static final long serialVersionUID = 1L; diff --git a/test/org/apache/coyote/http2/TestStreamProcessor.java b/test/org/apache/coyote/http2/TestStreamProcessor.java index 21ec994ba7..ff3f7d07cf 100644 --- a/test/org/apache/coyote/http2/TestStreamProcessor.java +++ b/test/org/apache/coyote/http2/TestStreamProcessor.java @@ -165,6 +165,55 @@ public class TestStreamProcessor extends Http2TestBase { } expected.append("3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n"); expected.append("3-HeadersEnd\n"); + expected.append("3-EndOfStream\n"); + + Assert.assertEquals(expected.toString(), output.getTrace()); + } + + + @Test + public void testPrepareHeadersNoContent() throws Exception { + enableHttp2(); + + Tomcat tomcat = getTomcatInstance(); + + File appDir = new File("test/webapp"); + Context ctxt = tomcat.addWebapp(null, "", appDir.getAbsolutePath()); + + Tomcat.addServlet(ctxt, "simple", new SimpleServlet()); + ctxt.addServletMappingDecoded("/simple", "simple"); + Tomcat.addServlet(ctxt, "noContent", new NoContentServlet()); + ctxt.addServletMappingDecoded("/noContent", "noContent"); + + + tomcat.start(); + + openClientConnection(); + doHttpUpgrade(); + sendClientPreface(); + validateHttp2InitialResponse(); + + byte[] frameHeader = new byte[9]; + ByteBuffer headersPayload = ByteBuffer.allocate(128); + + List<Header> headers = new ArrayList<>(3); + headers.add(new Header(":method", "GET")); + headers.add(new Header(":scheme", "http")); + headers.add(new Header(":path", "/noContent")); + headers.add(new Header(":authority", "localhost:" + getPort())); + + buildGetRequest(frameHeader, headersPayload, null, headers, 3); + + writeFrame(frameHeader, headersPayload); + + parser.readFrame(); + + StringBuilder expected = new StringBuilder(); + expected.append("3-HeadersStart\n"); + expected.append("3-Header-[:status]-[204]\n"); + expected.append("3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n"); + expected.append("3-HeadersEnd\n"); + expected.append("3-EndOfStream\n"); Assert.assertEquals(expected.toString(), output.getTrace()); } diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 5bcc002b2a..9a0cee9c51 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -119,6 +119,11 @@ Log basic information for each configured TLS certificate when Tomcat starts. (markt) </add> + <fix> + <bug>66442</bug>: When an HTTP/2 response must not include a body, + ensure that the end of stream flag is set on the headers frame and that + no data frame is sent. (markt) + </fix> </changelog> </subsection> <subsection name="Jasper"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org