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

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

commit cfe280e221757fe443a4e82d854bf2b2bb93b821
Author: opencode <[email protected]>
AuthorDate: Tue Oct 13 14:06:45 2026 +0200

    Fix zero-length read handling in the input stream of an upgraded connection
    
    read(byte[], int, int) on UpgradeServletInputStream passed a
    zero-length request straight through to the socket wrapper. In
    blocking mode that meant the call could block until the client sent
    data (the read timeout is infinite for upgraded connections) and, at
    the end of the input, consume the end-of-input and return -1, even
    though the contract requires a zero-length read to read no bytes and
    return zero.
    
    Return zero immediately, after the usual read checks, so the input is
    not touched, mirroring the existing handling in readLine().
---
 .../http11/upgrade/UpgradeServletInputStream.java  |  5 +++
 .../apache/coyote/http11/upgrade/TestUpgrade.java  | 49 ++++++++++++++++++++++
 2 files changed, 54 insertions(+)

diff --git 
a/java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java 
b/java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java
index 1c78fdbe80..93d0913b29 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java
+++ b/java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java
@@ -199,6 +199,11 @@ public class UpgradeServletInputStream extends 
ServletInputStream {
     public final int read(byte[] b, int off, int len) throws IOException {
         preReadChecks();
 
+        // The input must not be touched if the caller requested zero bytes.
+        if (len <= 0) {
+            return 0;
+        }
+
         try {
             int result = socketWrapper.read(listener == null, b, off, len);
             if (result == -1) {
diff --git a/test/org/apache/coyote/http11/upgrade/TestUpgrade.java 
b/test/org/apache/coyote/http11/upgrade/TestUpgrade.java
index 56672744ed..0f88c70af0 100644
--- a/test/org/apache/coyote/http11/upgrade/TestUpgrade.java
+++ b/test/org/apache/coyote/http11/upgrade/TestUpgrade.java
@@ -311,6 +311,20 @@ public class TestUpgrade extends TomcatBaseTest {
         uc.shutdownOutput();
     }
 
+    @Test
+    public void testZeroLengthReadReturnsZero() throws Exception {
+        UpgradeConnection uc = doUpgrade(ZeroLengthReadBlocking.class);
+
+        // Half-close the connection from the client side. The zero-length read
+        // performed by the handler must neither block waiting for the end of
+        // the input nor report the end of the input.
+        uc.shutdownOutput();
+
+        Assert.assertEquals(ZeroLengthReadBlocking.ZERO_READ_MARKER, 
uc.getReader().read());
+
+        uc.shutdownInput();
+    }
+
 
     @SuppressWarnings("unchecked")
     private static Set<UpgradeInfo> getUpgradeInfos(UpgradeGroupInfo 
groupInfo) throws Exception {
@@ -536,6 +550,41 @@ public class TestUpgrade extends TomcatBaseTest {
     }
 
 
+    public static class ZeroLengthReadBlocking implements HttpUpgradeHandler {
+
+        public static final char ZERO_READ_MARKER = '0';
+
+        @Override
+        public void init(WebConnection connection) {
+
+            try (ServletInputStream sis = connection.getInputStream();
+                    ServletOutputStream sos = connection.getOutputStream()) {
+
+                // A zero-length read is defined to read no bytes and return
+                // zero, independent of the state of the input.
+                int result = sis.read(new byte[8], 0, 0);
+
+                sos.write(result == 0 ? (byte) ZERO_READ_MARKER : (byte) 'E');
+                sos.flush();
+
+                // Drain the input so the connection is closed cleanly once the
+                // client has finished sending.
+                byte[] buffer = new byte[128];
+                while (sis.read(buffer) >= 0) {
+                    // Drain
+                }
+            } catch (IOException ioe) {
+                throw new IllegalStateException(ioe);
+            }
+        }
+
+        @Override
+        public void destroy() {
+            // NO-OP
+        }
+    }
+
+
     public static class EchoNonBlocking implements HttpUpgradeHandler {
 
         @Override


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to