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 b2b86f64f9 Respond with 414 when the request target is too long (#1147)
b2b86f64f9 is described below

commit b2b86f64f9365fd1134414e28b3b0d171dab4fbf
Author: Arturo Bernal <[email protected]>
AuthorDate: Fri Nov 13 13:30:48 2026 +0100

    Respond with 414 when the request target is too long (#1147)
    
    RFC 9112, section 3 requires a server that receives a request-target
    longer than any URI it wishes to parse to respond with a 414 (URI Too
    Long) status code. When the request target did not fit in the buffer used
    to read the request line, Tomcat responded with a 400 status. Respond with
    414 in that case. A request header field that is too large continues to
    trigger a 400 response.
---
 .../apache/coyote/http11/Http11InputBuffer.java    |  4 +
 java/org/apache/coyote/http11/Http11Processor.java |  9 +-
 .../apache/coyote/http11/LocalStrings.properties   |  1 +
 .../http11/RequestTargetTooLongException.java      | 40 +++++++++
 .../apache/catalina/startup/SimpleHttpClient.java  |  5 ++
 .../coyote/http11/TestHttp11InputBuffer.java       | 95 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         |  5 ++
 7 files changed, 156 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/coyote/http11/Http11InputBuffer.java 
b/java/org/apache/coyote/http11/Http11InputBuffer.java
index 05ebe9df94..916c9cd53c 100644
--- a/java/org/apache/coyote/http11/Http11InputBuffer.java
+++ b/java/org/apache/coyote/http11/Http11InputBuffer.java
@@ -746,6 +746,10 @@ public class Http11InputBuffer implements InputBuffer, 
ApplicationBufferHandler,
                 if (parsingRequestLine) {
                     // Avoid unknown protocol triggering an additional error
                     request.protocol().setString(Constants.HTTP_11);
+                    if (parsingRequestLinePhase == 4) {
+                        // The buffer filled while reading the request target 
(RFC 9112, section 3)
+                        throw new 
RequestTargetTooLongException(sm.getString("iib.requesttargettoolong.error"));
+                    }
                 }
                 throw new 
IllegalArgumentException(sm.getString("iib.requestheadertoolarge.error"));
             }
diff --git a/java/org/apache/coyote/http11/Http11Processor.java 
b/java/org/apache/coyote/http11/Http11Processor.java
index 3fd7c93608..6981d4541e 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -325,8 +325,8 @@ public class Http11Processor extends AbstractProcessor {
                             log.debug(message, t);
                     }
                 }
-                // 400 - Bad Request
-                response.setStatus(400);
+                // 414 - URI Too Long (RFC 9112, section 3) or 400 - Bad 
Request
+                response.setStatus(t instanceof RequestTargetTooLongException 
? 414 : 400);
                 setErrorState(ErrorState.CLOSE_CLEAN, t);
             }
 
@@ -903,7 +903,10 @@ public class Http11Processor extends AbstractProcessor {
 
 
     private void badRequest(String errorKey) {
-        response.setStatus(400);
+        if (!getErrorState().isError()) {
+            // Do not replace the status of an error that has already been 
identified
+            response.setStatus(400);
+        }
         setErrorState(ErrorState.CLOSE_CLEAN, null);
         if (log.isDebugEnabled()) {
             log.debug(sm.getString(errorKey));
diff --git a/java/org/apache/coyote/http11/LocalStrings.properties 
b/java/org/apache/coyote/http11/LocalStrings.properties
index 309e9ac105..a3c5c883f9 100644
--- a/java/org/apache/coyote/http11/LocalStrings.properties
+++ b/java/org/apache/coyote/http11/LocalStrings.properties
@@ -58,6 +58,7 @@ iib.invalidmethod=Invalid character found in method name 
[{0}]. HTTP method name
 iib.parseheaders.ise.error=Unexpected state: headers already parsed. Buffer 
not recycled?
 iib.readtimeout=Timeout attempting to read data from the socket
 iib.requestheadertoolarge.error=Request header is too large
+iib.requesttargettoolong.error=Request target is too long
 
 iob.failedwrite=Failed write
 iob.failedwrite.ack=Failed to send HTTP 100 continue response
diff --git a/java/org/apache/coyote/http11/RequestTargetTooLongException.java 
b/java/org/apache/coyote/http11/RequestTargetTooLongException.java
new file mode 100644
index 0000000000..51e05086e8
--- /dev/null
+++ b/java/org/apache/coyote/http11/RequestTargetTooLongException.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.coyote.http11;
+
+import java.io.Serial;
+
+/**
+ * Exception used to mark the specific error condition of the request target 
exceeding the size of the buffer used to
+ * read the request line.
+ *
+ * @since 12.0.x
+ */
+public class RequestTargetTooLongException extends IllegalArgumentException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Construct an exception with the specified detail message.
+     *
+     * @param message The detail message
+     */
+    public RequestTargetTooLongException(String message) {
+        super(message);
+    }
+}
diff --git a/test/org/apache/catalina/startup/SimpleHttpClient.java 
b/test/org/apache/catalina/startup/SimpleHttpClient.java
index 770c0c778a..c06aea0750 100644
--- a/test/org/apache/catalina/startup/SimpleHttpClient.java
+++ b/test/org/apache/catalina/startup/SimpleHttpClient.java
@@ -67,6 +67,7 @@ public abstract class SimpleHttpClient {
     public static final String FAIL_405 = "HTTP/1.1 405 ";
     public static final String TIMEOUT_408 = "HTTP/1.1 408 ";
     public static final String FAIL_413 = "HTTP/1.1 413 ";
+    public static final String FAIL_414 = "HTTP/1.1 414 ";
     public static final String FAIL_417 = "HTTP/1.1 417 ";
     public static final String FAIL_50X = "HTTP/1.1 50";
     public static final String FAIL_500 = "HTTP/1.1 500 ";
@@ -496,6 +497,10 @@ public abstract class SimpleHttpClient {
         return responseLineStartsWith(FAIL_413);
     }
 
+    public boolean isResponse414() {
+        return responseLineStartsWith(FAIL_414);
+    }
+
     public boolean isResponse417() {
         return responseLineStartsWith(FAIL_417);
     }
diff --git a/test/org/apache/coyote/http11/TestHttp11InputBuffer.java 
b/test/org/apache/coyote/http11/TestHttp11InputBuffer.java
index 54f00dbdce..3c4eedba9d 100644
--- a/test/org/apache/coyote/http11/TestHttp11InputBuffer.java
+++ b/test/org/apache/coyote/http11/TestHttp11InputBuffer.java
@@ -793,4 +793,99 @@ public class TestHttp11InputBuffer extends TomcatBaseTest {
         // Expected response is a 400 response.
         Assert.assertTrue(client.getResponseLine(), client.isResponse400());
     }
+
+    /*
+     * RFC 9112, section 3: a server that receives a request-target longer 
than any URI it wishes to parse MUST respond
+     * with a 414 (URI Too Long) status code.
+     */
+    @Test
+    public void testRequestTargetTooLong() {
+        LongRequestClient client = new LongRequestClient("/test/" + 
"a".repeat(9000), "X-Test: a");
+
+        Exception e = client.doRequest();
+        if (e == null) {
+            Assert.assertTrue(client.getResponseLine(), 
client.isResponse414());
+        }
+        Assert.assertFalse(client.isResponseBodyOK());
+    }
+
+
+    @Test
+    public void testRequestTargetQueryTooLong() {
+        LongRequestClient client = new LongRequestClient("/test?q=" + 
"a".repeat(9000), "X-Test: a");
+
+        Exception e = client.doRequest();
+        if (e == null) {
+            Assert.assertTrue(client.getResponseLine(), 
client.isResponse414());
+        }
+        Assert.assertFalse(client.isResponseBodyOK());
+    }
+
+
+    /*
+     * A header field that is too large is not a request-target problem so the 
response remains a 400.
+     */
+    @Test
+    public void testRequestHeaderTooLarge() {
+        LongRequestClient client = new LongRequestClient("/test", "X-Test: " + 
"a".repeat(9000));
+
+        Exception e = client.doRequest();
+        if (e == null) {
+            Assert.assertTrue(client.getResponseLine(), 
client.isResponse400());
+        }
+        Assert.assertFalse(client.isResponseBodyOK());
+    }
+
+
+    private class LongRequestClient extends SimpleHttpClient {
+
+        private final String requestTarget;
+        private final String headerLine;
+
+        private LongRequestClient(String requestTarget, String headerLine) {
+            this.requestTarget = requestTarget;
+            this.headerLine = headerLine;
+        }
+
+        private Exception doRequest() {
+
+            Tomcat tomcat = getTomcatInstance();
+
+            Context root = tomcat.addContext("", TEMP_DIR);
+            Tomcat.addServlet(root, "test", new TesterServlet());
+            root.addServletMapping("/test", "test");
+
+            try {
+                tomcat.start();
+                setPort(tomcat.getConnector().getLocalPort());
+
+                // Open connection
+                connect();
+
+                String[] request = new String[1];
+                // @formatter:off
+                request[0] =
+                        "GET " + requestTarget + " HTTP/1.1" + CRLF +
+                        "Host: localhost:8080" + CRLF +
+                        headerLine + CRLF +
+                        "Connection: close" + CRLF +
+                        CRLF;
+                // @formatter:on
+
+                setRequest(request);
+                processRequest(); // blocks until response has been read
+
+                // Close the connection
+                disconnect();
+            } catch (Exception e) {
+                return e;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isResponseBodyOK() {
+            return false;
+        }
+    }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 569a354fd5..35895d7aac 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -284,6 +284,11 @@
         in the <code>Accept-Language</code> header) as case insensitive. Pull
         request <pr>1144</pr> provided by Arturo Bernal. (markt)
       </fix>
+      <fix>
+        Respond with a 414 (URI Too Long) status rather than a 400 (Bad 
Request)
+        status when the request target is too long to fit in the buffer used to
+        read the request line, as required by RFC 9112, section 3. (abernal)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Jasper">


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

Reply via email to