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

markt-asf 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 39aa41d7b3 Fix BZ 70091. Add allowSchemeMismatch to Http2Protocol
39aa41d7b3 is described below

commit 39aa41d7b3d3c1c576f4b6e68497685b5c7ff4e8
Author: Mark Thomas <[email protected]>
AuthorDate: Thu May 28 17:51:13 2026 +0100

    Fix BZ 70091. Add allowSchemeMismatch to Http2Protocol
---
 java/org/apache/coyote/http2/Http2Protocol.java    | 27 ++++++++++++--
 java/org/apache/coyote/http2/Stream.java           | 21 ++++++-----
 .../apache/coyote/http2/TestHttp2Section_8_3.java  | 41 +++++++++++++++++++---
 webapps/docs/changelog.xml                         |  5 +++
 webapps/docs/config/http2.xml                      |  8 +++++
 5 files changed, 87 insertions(+), 15 deletions(-)

diff --git a/java/org/apache/coyote/http2/Http2Protocol.java 
b/java/org/apache/coyote/http2/Http2Protocol.java
index ad5e4f590d..9f1fdaea8f 100644
--- a/java/org/apache/coyote/http2/Http2Protocol.java
+++ b/java/org/apache/coyote/http2/Http2Protocol.java
@@ -40,8 +40,8 @@ import org.apache.tomcat.util.net.SocketWrapperBase;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
- * HTTP/2 protocol handler. Implements the {@link UpgradeProtocol} interface 
to allow HTTP/2 to be used as an
- * upgrade from HTTP/1.1 or via ALPN.
+ * HTTP/2 protocol handler. Implements the {@link UpgradeProtocol} interface 
to allow HTTP/2 to be used as an upgrade
+ * from HTTP/1.1 or via ALPN.
  */
 public class Http2Protocol implements UpgradeProtocol {
 
@@ -107,6 +107,7 @@ public class Http2Protocol implements UpgradeProtocol {
 
     private boolean initiatePingDisabled = false;
     private boolean useSendfile = true;
+    private boolean allowSchemeMismatch = false;
     // Reference to HTTP/1.1 protocol that this instance is configured under
     private AbstractHttp11Protocol<?> http11Protocol = null;
 
@@ -193,6 +194,28 @@ public class Http2Protocol implements UpgradeProtocol {
     }
 
 
+    /**
+     * Are HTTP/2 streams allowed to provide a scheme that is inconsistent 
with the transport over which the stream was
+     * received?
+     *
+     * @return {@code true} if a mismatched scheme is permitted, otherwise 
{@code false}
+     */
+    public boolean getAllowSchemeMismatch() {
+        return allowSchemeMismatch;
+    }
+
+
+    /**
+     * Configure whether HTTP/2 streams atr allowed to provide a scheme that 
is inconsistent with the transport over
+     * which the stream was received
+     *
+     * @param allowSchemeMismatch {@code true} if a mismatched scheme is 
permitted, otherwise {@code false}
+     */
+    public void setAllowSchemeMismatch(boolean allowSchemeMismatch) {
+        this.allowSchemeMismatch = allowSchemeMismatch;
+    }
+
+
     /**
      * Returns the read timeout in milliseconds.
      *
diff --git a/java/org/apache/coyote/http2/Stream.java 
b/java/org/apache/coyote/http2/Stream.java
index afe697ba4a..4f6a603549 100644
--- a/java/org/apache/coyote/http2/Stream.java
+++ b/java/org/apache/coyote/http2/Stream.java
@@ -392,12 +392,16 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
             case ":scheme": {
                 if (coyoteRequest.scheme().isNull()) {
                     coyoteRequest.scheme().setString(value);
-                    // Check scheme is consistent with TLS usage
-                    if ("https".equals(value) != 
handler.getProtocol().getHttp11Protocol().isSSLEnabled()) {
-                        headerException = new StreamException(
-                                
sm.getString("stream.header.inconsistentScheme", getConnectionId(), 
getIdAsString(),
-                                value, 
Boolean.toString(handler.getProtocol().getHttp11Protocol().isSSLEnabled())),
-                                Http2Error.PROTOCOL_ERROR, getIdAsInt());
+                    // Check scheme is consistent with TLS usage when required 
to be
+                    if (!handler.getProtocol().getAllowSchemeMismatch() &&
+                            "https".equals(value) != 
handler.getProtocol().getHttp11Protocol().isSSLEnabled()) {
+                        headerException =
+                                new StreamException(
+                                        
sm.getString("stream.header.inconsistentScheme", getConnectionId(),
+                                                getIdAsString(), value,
+                                                Boolean.toString(
+                                                        
handler.getProtocol().getHttp11Protocol().isSSLEnabled())),
+                                        Http2Error.PROTOCOL_ERROR, 
getIdAsInt());
                     }
                 } else {
                     headerException = new StreamException(
@@ -585,8 +589,9 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
         } else if (Method.CONNECT.equals(coyoteRequest.getMethod())) {
             // CONNECT only
             if (!coyoteRequest.scheme().isNull() || 
!coyoteRequest.requestURI().isNull()) {
-                throw new 
StreamException(sm.getString("stream.header.invalidConnect", getConnectionId(),
-                        getIdAsString()), Http2Error.PROTOCOL_ERROR, 
getIdAsInt());
+                throw new StreamException(
+                        sm.getString("stream.header.invalidConnect", 
getConnectionId(), getIdAsString()),
+                        Http2Error.PROTOCOL_ERROR, getIdAsInt());
             }
             if (coyoteRequest.serverName().isNull()) {
                 missingHeader = true;
diff --git a/test/org/apache/coyote/http2/TestHttp2Section_8_3.java 
b/test/org/apache/coyote/http2/TestHttp2Section_8_3.java
index 790b409405..ae35f5ccb1 100644
--- a/test/org/apache/coyote/http2/TestHttp2Section_8_3.java
+++ b/test/org/apache/coyote/http2/TestHttp2Section_8_3.java
@@ -23,6 +23,7 @@ import java.util.List;
 import org.junit.Assert;
 import org.junit.Test;
 
+import org.apache.catalina.connector.Connector;
 import org.apache.tomcat.util.http.Method;
 
 /**
@@ -36,19 +37,42 @@ public class TestHttp2Section_8_3 extends Http2TestBase {
 
     @Test
     public void testSchemeInconsistencyNonTLS() throws Exception {
-        testSchemeInconsistency(false);
+        testSchemeInconsistency(false, false);
     }
 
 
     @Test
     public void testSchemeInconsistencyTLS() throws Exception {
-        testSchemeInconsistency(true);
+        testSchemeInconsistency(true, false);
     }
 
 
-    private void testSchemeInconsistency(boolean connectionUsesTls) throws 
Exception {
+    @Test
+    public void testSchemeInconsistencyNonTLSMismatchAllowed() throws 
Exception {
+        testSchemeInconsistency(false, true);
+    }
+
+
+    @Test
+    public void testSchemeInconsistencyTLSMismatchAllowed() throws Exception {
+        testSchemeInconsistency(true, true);
+    }
+
+
+    private void testSchemeInconsistency(boolean connectionUsesTls, boolean 
allowSchemeMismatch) throws Exception {
         // Start HTTP/2 over non-TLS connection
-        http2Connect(connectionUsesTls);
+        enableHttp2(connectionUsesTls);
+        if (allowSchemeMismatch) {
+            Connector connector = getTomcatInstance().getConnector();
+            Http2Protocol http2Protocol = (Http2Protocol) 
connector.findUpgradeProtocols()[0];
+            http2Protocol.setAllowSchemeMismatch(allowSchemeMismatch);
+        }
+
+        configureAndStartWebApplication();
+        openClientConnection(connectionUsesTls);
+        doHttpUpgrade();
+        sendClientPreface();
+        validateHttp2InitialResponse();
 
         byte[] frameHeader = new byte[9];
         ByteBuffer headersPayload = ByteBuffer.allocate(128);
@@ -67,8 +91,15 @@ public class TestHttp2Section_8_3 extends Http2TestBase {
 
         writeFrame(frameHeader, headersPayload);
 
+        // Read first response frame
         parser.readFrame();
 
-        Assert.assertEquals("3-RST-[1]\n", output.getTrace());
+        if (allowSchemeMismatch) {
+            // Normal response. There will be a data frame with the body too.
+            parser.readFrame();
+            Assert.assertEquals(output.getTrace(), getSimpleResponseTrace(3));
+        } else {
+            Assert.assertEquals("3-RST-[1]\n", output.getTrace());
+        }
     }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 3cb2bd072b..49f6f8d30c 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -391,6 +391,11 @@
       <fix>
         Avoid overflow scenarios in Asn1Parser. (remm)
       </fix>
+      <fix>
+        <bug>70091</bug>: Add a new attribute, <code>allowSchemeMismatch</code>
+        to <code>Http2Protocol</code> that allows the consistency check for the
+        scheme provided by the user agent to be bypassed. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Jasper">
diff --git a/webapps/docs/config/http2.xml b/webapps/docs/config/http2.xml
index d9b34c3b75..e152b71902 100644
--- a/webapps/docs/config/http2.xml
+++ b/webapps/docs/config/http2.xml
@@ -71,6 +71,14 @@
 
   <attributes>
 
+    <attribute name="allowSchemeMismatch" required="false">
+      <p>A boolean value which can be used to enable or disable the consistency
+      check for the scheme presented by the user agent in the HTTP/2 stream and
+      the channel over which the stream was received. This should only be set 
to
+      <code>true</code> where a trusted TLS terminating reverse proxy is used.
+      If not specified, this attribute is set to <code>false</code>.</p>
+    </attribute>
+
     <attribute name="discardRequestsAndResponses" required="false">
       <p>A boolean value which can be used to enable or disable the recycling
       of the container internal request and response processing objects. If set


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

Reply via email to