This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new f902edf085 Fix BZ 69302 Client reset should trigger 
ReadListener.onError ...
f902edf085 is described below

commit f902edf085c0c73139a66d1bfc4d5790a416b130
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Sep 3 15:08:34 2024 +0100

    Fix BZ 69302 Client reset should trigger ReadListener.onError ...
    
    ... if the request body has not been fully read.
    
    https://bz.apache.org/bugzilla/show_bug.cgi?id=69302
---
 java/org/apache/coyote/AbstractProcessor.java      |  4 +++
 java/org/apache/coyote/ActionCode.java             |  6 +++++
 .../apache/coyote/http2/LocalStrings.properties    |  1 +
 java/org/apache/coyote/http2/Stream.java           | 30 +++++++++++++++++++++-
 java/org/apache/tomcat/util/net/DispatchType.java  |  3 ++-
 .../apache/coyote/http2/TestAsyncReadListener.java | 19 +++++++++++++-
 .../apache/coyote/http2/TestHttp2Section_5_1.java  |  6 ++---
 webapps/docs/changelog.xml                         |  5 ++++
 8 files changed, 68 insertions(+), 6 deletions(-)

diff --git a/java/org/apache/coyote/AbstractProcessor.java 
b/java/org/apache/coyote/AbstractProcessor.java
index 6e750dbbba..bddca91c3e 100644
--- a/java/org/apache/coyote/AbstractProcessor.java
+++ b/java/org/apache/coyote/AbstractProcessor.java
@@ -602,6 +602,10 @@ public abstract class AbstractProcessor extends 
AbstractProcessorLight implement
                 addDispatch(DispatchType.NON_BLOCKING_WRITE);
                 break;
             }
+            case DISPATCH_ERROR: {
+                addDispatch(DispatchType.NON_BLOCKING_ERROR);
+                break;
+            }
             case DISPATCH_EXECUTE: {
                 executeDispatches();
                 break;
diff --git a/java/org/apache/coyote/ActionCode.java 
b/java/org/apache/coyote/ActionCode.java
index 62d236633a..179e02d7da 100644
--- a/java/org/apache/coyote/ActionCode.java
+++ b/java/org/apache/coyote/ActionCode.java
@@ -237,6 +237,12 @@ public enum ActionCode {
      */
     DISPATCH_WRITE,
 
+    /**
+     * Indicates that the container needs to trigger a call to onError() for 
the registered non-blocking write and/or
+     * read listener(s).
+     */
+    DISPATCH_ERROR,
+
     /**
      * Execute any non-blocking dispatches that have been registered via 
{@link #DISPATCH_READ} or
      * {@link #DISPATCH_WRITE}. Typically required when the non-blocking 
listeners are configured on a thread where the
diff --git a/java/org/apache/coyote/http2/LocalStrings.properties 
b/java/org/apache/coyote/http2/LocalStrings.properties
index c9bcb28a01..6bdb05696a 100644
--- a/java/org/apache/coyote/http2/LocalStrings.properties
+++ b/java/org/apache/coyote/http2/LocalStrings.properties
@@ -85,6 +85,7 @@ http2Protocol.jmxRegistration.fail=JMX registration for the 
HTTP/2 protocol fail
 
 pingManager.roundTripTime=Connection [{0}] Round trip time measured as [{1}]ns
 
+stream.clientResetRequest=Client reset the stream before the request was fully 
read
 stream.closed=Connection [{0}], Stream [{1}], Unable to write to stream once 
it has been closed
 stream.header.case=Connection [{0}], Stream [{1}], HTTP header name [{2}] must 
be in lower case
 stream.header.connection=Connection [{0}], Stream [{1}], HTTP header [{2}] is 
not permitted in an HTTP/2 request
diff --git a/java/org/apache/coyote/http2/Stream.java 
b/java/org/apache/coyote/http2/Stream.java
index 876b85158a..2b81241f4d 100644
--- a/java/org/apache/coyote/http2/Stream.java
+++ b/java/org/apache/coyote/http2/Stream.java
@@ -33,6 +33,8 @@ import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Supplier;
 
+import jakarta.servlet.RequestDispatcher;
+
 import org.apache.coyote.ActionCode;
 import org.apache.coyote.CloseNowException;
 import org.apache.coyote.InputBuffer;
@@ -1238,7 +1240,7 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
         // If readInterest is true, data must be available to read no later 
than this time.
         private volatile long readTimeoutExpiry;
         private volatile boolean closed;
-        private boolean resetReceived;
+        private volatile boolean resetReceived;
 
         @SuppressWarnings("deprecation")
         @Override
@@ -1334,6 +1336,16 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
                     return true;
                 }
 
+                if (resetReceived) {
+                    // Trigger ReadListener.onError()
+                    
getCoyoteRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,
+                            new 
IOException(sm.getString("stream.clientResetRequest")));
+                    coyoteRequest.action(ActionCode.DISPATCH_ERROR, null);
+                    coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
+
+                    return false;
+                }
+
                 if (!isRequestBodyFullyRead()) {
                     readInterest = true;
                     long readTimeout = 
handler.getProtocol().getStreamReadTimeout();
@@ -1455,6 +1467,22 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
                     inBuffer.notifyAll();
                 }
             }
+
+            // If a read is in progress, cancel it.
+            readStateLock.lock();
+            try {
+                if (readInterest) {
+                    readInterest = false;
+                }
+            } finally {
+                readStateLock.unlock();
+            }
+
+            // Trigger ReadListener.onError()
+            getCoyoteRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,
+                    new 
IOException(sm.getString("stream.clientResetRequest")));
+            coyoteRequest.action(ActionCode.DISPATCH_ERROR, null);
+            coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
         }
 
         @Override
diff --git a/java/org/apache/tomcat/util/net/DispatchType.java 
b/java/org/apache/tomcat/util/net/DispatchType.java
index 5914c87044..ee04a0555e 100644
--- a/java/org/apache/tomcat/util/net/DispatchType.java
+++ b/java/org/apache/tomcat/util/net/DispatchType.java
@@ -24,7 +24,8 @@ package org.apache.tomcat.util.net;
 public enum DispatchType {
 
     NON_BLOCKING_READ(SocketEvent.OPEN_READ),
-    NON_BLOCKING_WRITE(SocketEvent.OPEN_WRITE);
+    NON_BLOCKING_WRITE(SocketEvent.OPEN_WRITE),
+    NON_BLOCKING_ERROR(SocketEvent.ERROR);
 
     private final SocketEvent status;
 
diff --git a/test/org/apache/coyote/http2/TestAsyncReadListener.java 
b/test/org/apache/coyote/http2/TestAsyncReadListener.java
index 8b9a9bc9b8..2c02223770 100644
--- a/test/org/apache/coyote/http2/TestAsyncReadListener.java
+++ b/test/org/apache/coyote/http2/TestAsyncReadListener.java
@@ -47,10 +47,27 @@ public class TestAsyncReadListener extends Http2TestBase {
         asyncServlet = new AsyncServlet();
     }
 
+
+    @Test
+    public void testEmptyWindowDefaultReadTimeout() throws Exception {
+        doTestEmptyWindowMaximumTimeout(false);
+    }
+
+
     @Test
-    public void testEmptyWindow() throws Exception {
+    public void testEmptyWindowMaximumReadTimeout() throws Exception {
+        doTestEmptyWindowMaximumTimeout(true);
+    }
+
+
+    public void doTestEmptyWindowMaximumTimeout(boolean useMaxReadTimeout) 
throws Exception {
         http2Connect();
 
+        if (useMaxReadTimeout) {
+            http2Protocol.setStreamReadTimeout(Integer.MAX_VALUE);
+            http2Protocol.setReadTimeout(Integer.MAX_VALUE);
+        }
+
         byte[] headersFrameHeader = new byte[9];
         ByteBuffer headersPayload = ByteBuffer.allocate(128);
         byte[] dataFrameHeader = new byte[9];
diff --git a/test/org/apache/coyote/http2/TestHttp2Section_5_1.java 
b/test/org/apache/coyote/http2/TestHttp2Section_5_1.java
index 391ce865b1..312d5c97be 100644
--- a/test/org/apache/coyote/http2/TestHttp2Section_5_1.java
+++ b/test/org/apache/coyote/http2/TestHttp2Section_5_1.java
@@ -296,10 +296,10 @@ public class TestHttp2Section_5_1 extends Http2TestBase {
 
         // Reset stream 3 (client cancel)
         sendRst(3, Http2Error.NO_ERROR.getCode());
-        // Client reset triggers a write error which in turn triggers a server
-        // reset
+        // Client reset triggers both a read error and a write error which in 
turn trigger two server resets
         parser.readFrame();
-        Assert.assertEquals("3-RST-[5]\n", output.getTrace());
+        parser.readFrame();
+        Assert.assertEquals("3-RST-[5]\n3-RST-[5]\n", output.getTrace());
         output.clearTrace();
 
         // Open up the connection window.
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 7ae424d8a4..fab0e6ca0b 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -138,6 +138,11 @@
         writing response headers to the access log. Based on a patch and test
         case provided by hypnoce. (markt)
       </fix>
+      <fix>
+        <bug>69302</bug>: If an HTTP/2 client resets a stream before the 
request
+        body is fully written, ensure that any <code>ReadListener</code> is
+        notified via a call to <code>ReadListener.onErrror()</code>. (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

Reply via email to