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

markt-asf 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 910d3adc9e Add limit to HTTP response header size in upgrade to 
WebSocket
910d3adc9e is described below

commit 910d3adc9efcc5c2ddba4a6ccbbdd62b6484f60f
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Jul 24 17:12:38 2026 +0100

    Add limit to HTTP response header size in upgrade to WebSocket
---
 java/org/apache/tomcat/websocket/Constants.java    | 11 ++++++++++
 .../tomcat/websocket/LocalStrings.properties       |  1 +
 .../tomcat/websocket/WsWebSocketContainer.java     | 24 ++++++++++++++++++----
 webapps/docs/changelog.xml                         | 10 +++++++++
 webapps/docs/web-socket-howto.xml                  |  7 +++++++
 5 files changed, 49 insertions(+), 4 deletions(-)

diff --git a/java/org/apache/tomcat/websocket/Constants.java 
b/java/org/apache/tomcat/websocket/Constants.java
index ca25057797..b82530da22 100644
--- a/java/org/apache/tomcat/websocket/Constants.java
+++ b/java/org/apache/tomcat/websocket/Constants.java
@@ -94,6 +94,17 @@ public class Constants {
      * Default I/O timeout in milliseconds for WebSocket client connections.
      */
     public static final long IO_TIMEOUT_MS_DEFAULT = 5000;
+    /**
+     * Property name to set to configure the maximum number of bytes that will 
be allowed for the HTTP response to a
+     * WebSocket handshake request. The default is {@link 
#MAX_HTTP_RESPONSE_HEADER_BYTES_DEFAULT}.
+     */
+    public static final String MAX_HTTP_RESPONSE_HEADER_BYTES_PROPERTY =
+            "org.apache.tomcat.websocket.MAX_HTTP_RESPONSE_HEADER_BYTES";
+    /**
+     * Default maximum number of bytes that will be allowed for the HTTP 
response to a
+     * WebSocket handshake request.
+     */
+    public static final long MAX_HTTP_RESPONSE_HEADER_BYTES_DEFAULT = 8192;
 
     // RFC 2068 recommended a limit of 5
     // Most browsers have a default limit of 20
diff --git a/java/org/apache/tomcat/websocket/LocalStrings.properties 
b/java/org/apache/tomcat/websocket/LocalStrings.properties
index 34fb844a6c..9d2ee88932 100644
--- a/java/org/apache/tomcat/websocket/LocalStrings.properties
+++ b/java/org/apache/tomcat/websocket/LocalStrings.properties
@@ -151,6 +151,7 @@ wsWebSocketContainer.pathWrongScheme=The scheme [{0}] is 
not supported. The supp
 wsWebSocketContainer.proxyConnectFail=Failed to connect to the configured 
Proxy [{0}]. The HTTP response code was [{1}]
 wsWebSocketContainer.redirectThreshold=Cyclic Location header [{0}] detected / 
reached max number of redirects [{1}] of max [{2}]
 wsWebSocketContainer.responseFail=The HTTP upgrade to WebSocket failed but 
partial data may have been received: Status Code [{0}], HTTP headers [{1}]
+wsWebSocketContainer.responseHeadersLimit=The HTTP upgrade to WebSocket failed 
because the size of the HTTP response headers exceeded the limit
 wsWebSocketContainer.sessionCloseFail=Session with ID [{0}] did not close 
cleanly
 wsWebSocketContainer.shutdown=The web application is stopping
 wsWebSocketContainer.sslEngineFail=Unable to create SSLEngine to support 
SSL/TLS connections
diff --git a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java 
b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
index cb832674f1..4a80443c64 100644
--- a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
+++ b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
@@ -306,6 +306,13 @@ public class WsWebSocketContainer implements 
WebSocketContainer, BackgroundProce
         Transformation transformation = null;
         AsyncChannelWrapper channel = null;
 
+        long maxHttpResponseHeaderBytes = 
Constants.MAX_HTTP_RESPONSE_HEADER_BYTES_DEFAULT;
+        String maxHttpResponseHeaderBytesValue =
+                (String) 
userProperties.get(Constants.MAX_HTTP_RESPONSE_HEADER_BYTES_PROPERTY);
+        if (maxHttpResponseHeaderBytesValue != null) {
+            maxHttpResponseHeaderBytes = 
Long.parseLong(maxHttpResponseHeaderBytesValue);
+        }
+
         try {
             // Open the connection
             Future<Void> fConnect = socketChannel.connect(sa);
@@ -315,7 +322,7 @@ public class WsWebSocketContainer implements 
WebSocketContainer, BackgroundProce
                 // Proxy CONNECT is clear text
                 channel = new AsyncChannelWrapperNonSecure(socketChannel);
                 writeRequest(channel, proxyConnect, timeout);
-                HttpResponse httpResponse = processResponse(response, channel, 
timeout);
+                HttpResponse httpResponse = processResponse(response, channel, 
timeout, maxHttpResponseHeaderBytes);
                 if (httpResponse.status == 
Constants.PROXY_AUTHENTICATION_REQUIRED) {
                     return 
processAuthenticationChallenge(clientEndpointHolder, 
clientEndpointConfiguration,
                             serverEndpointUri, redirectSet, userProperties, 
Method.CONNECT,
@@ -358,7 +365,7 @@ public class WsWebSocketContainer implements 
WebSocketContainer, BackgroundProce
             }
             writeRequest(channel, upgradeRequest, timeout);
 
-            HttpResponse httpResponse = processResponse(response, channel, 
timeout);
+            HttpResponse httpResponse = processResponse(response, channel, 
timeout, maxHttpResponseHeaderBytes);
 
             // Check maximum permitted redirects
             int maxRedirects = Constants.MAX_REDIRECTIONS_DEFAULT;
@@ -854,8 +861,9 @@ public class WsWebSocketContainer implements 
WebSocketContainer, BackgroundProce
      * @throws DeploymentException  if the response status line is not 
correctly formatted
      * @throws TimeoutException     if the response was not read within the 
expected timeout
      */
-    private HttpResponse processResponse(ByteBuffer response, 
AsyncChannelWrapper channel, long timeout)
-            throws InterruptedException, ExecutionException, 
DeploymentException, EOFException, TimeoutException {
+    private HttpResponse processResponse(ByteBuffer response, 
AsyncChannelWrapper channel, long timeout,
+            long maxHttpResponseHeaderBytes) throws InterruptedException, 
ExecutionException, DeploymentException,
+            EOFException, TimeoutException {
 
         Map<String,List<String>> headers = new CaseInsensitiveKeyMap<>();
 
@@ -864,6 +872,7 @@ public class WsWebSocketContainer implements 
WebSocketContainer, BackgroundProce
         boolean readHeaders = false;
         StringBuilder lineBuffer = new StringBuilder();
         String line = null;
+        long headerByteCount = 0;
         while (!readHeaders) {
             // On entering loop buffer will be empty and at the start of a new
             // loop the buffer will have been fully read.
@@ -883,6 +892,7 @@ public class WsWebSocketContainer implements 
WebSocketContainer, BackgroundProce
                 throw new EOFException(
                         sm.getString("wsWebSocketContainer.responseFail", 
Integer.toString(status), headers));
             }
+            headerByteCount += bytesRead.intValue();
             response.flip();
             while (response.hasRemaining() && !readHeaders) {
                 if (readLine(response, lineBuffer)) {
@@ -901,6 +911,12 @@ public class WsWebSocketContainer implements 
WebSocketContainer, BackgroundProce
                     line = null;
                 }
             }
+            if (readHeaders) {
+                headerByteCount -= response.remaining();
+            }
+            if (headerByteCount > maxHttpResponseHeaderBytes) {
+                throw new 
DeploymentException(sm.getString("wsWebSocketContainer.responseHeadersLimit"));
+            }
         }
 
         return new HttpResponse(status, new WsHandshakeResponse(headers));
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index d717eb17c3..abc18f432d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -203,6 +203,16 @@
       </add>
     </changelog>
   </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <add>
+        Add a limit (defaults to 8KB) on the size of the HTTP response headers
+        accepted during a WebSocket HTTP upgrade. This is configured via the
+        <code>org.apache.tomcat.websocket.MAX_HTTP_RESPONSE_HEADER_BYTES</code>
+        user property. (markt)
+      </add>
+    </changelog>
+  </subsection>
 </section>
 <section name="Tomcat 10.1.57 (schultz)" rtext="2026-07-07">
   <subsection name="Catalina">
diff --git a/webapps/docs/web-socket-howto.xml 
b/webapps/docs/web-socket-howto.xml
index 6c0697029f..f24590fab6 100644
--- a/webapps/docs/web-socket-howto.xml
+++ b/webapps/docs/web-socket-howto.xml
@@ -159,6 +159,13 @@
    is 20. Redirection support can be disabled by configuring a value of zero.
    </p>
 
+<p>When using the WebSocket client to connect to server endpoints, the maximum
+   number of bytes permitted in the HTTP response headers is controlled by the
+   <code>userProperties</code> of the provided
+   <code>jakarta.websocket.ClientEndpointConfig</code>. The property is
+   <code>org.apache.tomcat.websocket.MAX_HTTP_RESPONSE_HEADER_BYTES</code>. The
+   default is 8192 (8KB).</p>
+
 <p>When using the WebSocket client to connect to a server endpoint that 
requires
    BASIC or DIGEST authentication, the following user properties must be set:
    </p>


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

Reply via email to