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

rmaucher 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 875c79e494 Fix incorrect window size when upgrading from HTTP/1.1
875c79e494 is described below

commit 875c79e4940c570731f7068dbb20f979a3b18d1c
Author: remm <[email protected]>
AuthorDate: Mon Aug 31 14:14:32 2026 +0200

    Fix incorrect window size when upgrading from HTTP/1.1
    
    Co authored with OpenCode, found in code review.
---
 .../apache/coyote/http2/Http2UpgradeHandler.java   | 11 +++++
 .../coyote/http2/TestHttp2UpgradeHandler.java      | 56 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         |  4 ++
 3 files changed, 71 insertions(+)

diff --git a/java/org/apache/coyote/http2/Http2UpgradeHandler.java 
b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
index 262ca60cb0..a1ba6f8110 100644
--- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java
+++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
@@ -283,6 +283,8 @@ class Http2UpgradeHandler extends AbstractStream implements 
InternalHttpUpgradeH
                 // Settings are only valid on stream 0
                 FrameType.SETTINGS.check(0, settings.length);
 
+                long oldInitialWindowSize = 
remoteSettings.getInitialWindowSize();
+
                 for (int i = 0; i < settings.length / 6; i++) {
                     int id = ByteUtil.getTwoBytes(settings, i * 6);
                     long value = ByteUtil.getFourBytes(settings, (i * 6) + 2);
@@ -293,6 +295,15 @@ class Http2UpgradeHandler extends AbstractStream 
implements InternalHttpUpgradeH
                     }
                     remoteSettings.set(key, value);
                 }
+
+                // Stream 1 was created before the client settings were
+                // processed and so was given the default initial window
+                // size. Apply any change in the initial window size to
+                // stream 1 (mirrors setting()).
+                long delta = remoteSettings.getInitialWindowSize() - 
oldInitialWindowSize;
+                if (delta != 0) {
+                    stream.incrementWindowSize((int) delta);
+                }
             } catch (IllegalArgumentException | Http2Exception e) {
                 throw new 
ProtocolException(sm.getString("upgradeHandler.upgrade.fail", connectionId), e);
             }
diff --git a/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java 
b/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
index d754e6e721..5f7ad85939 100644
--- a/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
+++ b/test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
@@ -18,6 +18,7 @@ package org.apache.coyote.http2;
 
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.util.Base64;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -194,6 +195,61 @@ public class TestHttp2UpgradeHandler extends Http2TestBase 
{
     }
 
 
+    @Test
+    public void testUpgradeAdvertisedInitialWindowSize() throws Exception {
+        enableHttp2();
+
+        Tomcat tomcat = getTomcatInstance();
+
+        Context ctxt = getProgrammaticRootContext();
+        Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
+        ctxt.addServletMapping("/simple", "simple");
+
+        tomcat.start();
+
+        openClientConnection();
+
+        // Advertise an initial window size that is smaller than the default
+        int initialWindowSize = 4096;
+        byte[] settingsPayload = new byte[6];
+        ByteUtil.setTwoBytes(settingsPayload, 0, 
Setting.INITIAL_WINDOW_SIZE.getId());
+        ByteUtil.setFourBytes(settingsPayload, 2, initialWindowSize);
+        String settingsHeader = "HTTP2-Settings: " + 
Base64.getUrlEncoder().encodeToString(settingsPayload) + "\r\n";
+
+        doHttpUpgrade(DEFAULT_CONNECTION_HEADER_VALUE, "h2c", settingsHeader, 
true);
+
+        sendClientPreface();
+
+        // - 101 response acts as acknowledgement of the HTTP2-Settings header
+        // Need to read 5 frames
+        // - settings (server settings - must be first)
+        // - settings ack (for the settings frame in the client preface)
+        // - ping
+        // - headers (for response)
+        // - data (for response body, limited to the advertised initial window)
+        parser.readFrame();
+        parser.readFrame();
+        parser.readFrame();
+        parser.readFrame();
+        parser.readFrame();
+
+        Assert.assertEquals("0-Settings-[3]-[200]\n" + "0-Settings-End\n" + 
"0-Settings-Ack\n" +
+                "0-Ping-[0,0,0,0,0,0,0,1]\n" + "1-HeadersStart\n" + 
"1-Header-[:status]-[200]\n" +
+                "1-Header-[content-type]-[application/octet-stream]\n" + 
"1-Header-[content-length]-[" +
+                SimpleServlet.CONTENT_LENGTH + "]\n" + "1-Header-[date]-[" + 
DEFAULT_DATE + "]\n" + "1-HeadersEnd\n" +
+                "1-Body-" + initialWindowSize + "\n", output.getTrace());
+        output.clearTrace();
+
+        // The advertised stream window is now exhausted. Expand it and the
+        // rest of the response should follow.
+        sendWindowUpdate(1, initialWindowSize);
+        parser.readFrame();
+
+        Assert.assertEquals("1-Body-" + (SimpleServlet.CONTENT_LENGTH - 
initialWindowSize) + "\n" + "1-EndOfStream\n",
+                output.getTrace());
+    }
+
+
     @Test
     public void testActiveConnectionCountAndClientTimeout() throws Exception {
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 44dffdd6ef..4b9e608d19 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -345,6 +345,10 @@
         In HTTP/2 after half closed (remote), any unexpected frame should be
         a stream error. (remm)
       </fix>
+      <fix>
+        Fix incorrect initial window size calculation when upgrading to HTTP/2.
+        (remm)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Jasper">


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

Reply via email to