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 67b01c13bc Add limit to HTTP response header size in upgrade to 
WebSocket
67b01c13bc is described below

commit 67b01c13bc489d5c8ce2b9e006ccb52161815316
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                         |  6 ++++++
 webapps/docs/web-socket-howto.xml                  |  7 +++++++
 5 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/java/org/apache/tomcat/websocket/Constants.java 
b/java/org/apache/tomcat/websocket/Constants.java
index 7841f6b010..4e529b8b68 100644
--- a/java/org/apache/tomcat/websocket/Constants.java
+++ b/java/org/apache/tomcat/websocket/Constants.java
@@ -71,6 +71,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 f1f7c16e0d..a520f332cf 100644
--- a/java/org/apache/tomcat/websocket/LocalStrings.properties
+++ b/java/org/apache/tomcat/websocket/LocalStrings.properties
@@ -165,6 +165,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.sslEngineFail=Unable to create SSLEngine to support 
SSL/TLS connections
 wsWebSocketContainer.unsupportedAuthScheme=Failed to handle HTTP response code 
[{0}]. Unsupported Authentication scheme [{1}] returned in response
diff --git a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java 
b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
index 252e287677..495ef6c1b7 100644
--- a/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
+++ b/java/org/apache/tomcat/websocket/WsWebSocketContainer.java
@@ -301,6 +301,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);
+        }
+
         HandshakeResponse handshakeResponse = EMPTY_HANDSHAKE_RESPONSE;
         try {
             // Open the connection
@@ -311,7 +318,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,
@@ -354,7 +361,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;
@@ -850,8 +857,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<>();
 
@@ -860,6 +868,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.
@@ -879,6 +888,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)) {
@@ -897,6 +907,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 fe15f828b2..3bf0614603 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -415,6 +415,12 @@
         <code>Writer</code> and <code>OutputStream</code>. (markt)
       </fix>
       <!-- Entries for backport and removal before 12.0.0-M1 below this line 
-->
+      <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>
   <subsection name="Web applications">
diff --git a/webapps/docs/web-socket-howto.xml 
b/webapps/docs/web-socket-howto.xml
index c159f1dc99..00d910b7e7 100644
--- a/webapps/docs/web-socket-howto.xml
+++ b/webapps/docs/web-socket-howto.xml
@@ -120,6 +120,13 @@
    timeout as a <code>String</code> in milliseconds. The default is 5000 (5
    seconds).</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 server endpoints, the number 
of
    HTTP redirects that the client will follow is controlled by the
    <code>userProperties</code> of the provided


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

Reply via email to