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
commit decfd3e92d34dcc497a43e2ae85e763d4268f8e8 Author: opencode <[email protected]> AuthorDate: Wed Oct 14 21:28:28 2026 +0200 Treat an empty port when parsing a host value as an absent port RFC 3986 (section 3.2.3) allows a ':' separator that is not followed by any digits. HttpParser.validatePort() already accepted such a value but AbstractProcessor parsed zero digits for the port, associating a port of zero with the request. AbstractProcessor now treats an empty port as equivalent to an absent port, keeping the port already associated with the request rather than overwriting it with zero. Document the empty port behaviour of validatePort() for its callers and add tests for host values with an empty port. --- java/org/apache/coyote/AbstractProcessor.java | 36 +++++++++++++--------- .../apache/tomcat/util/http/parser/HttpParser.java | 4 +++ .../util/http/parser/TestHttpParserHost.java | 9 ++++++ 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/java/org/apache/coyote/AbstractProcessor.java b/java/org/apache/coyote/AbstractProcessor.java index 2fdfee6b2e..4e49da8ea9 100644 --- a/java/org/apache/coyote/AbstractProcessor.java +++ b/java/org/apache/coyote/AbstractProcessor.java @@ -353,23 +353,29 @@ public abstract class AbstractProcessor extends AbstractProcessorLight implement // Extract the port information first, if any if (colonPos != -1) { - int port = 0; - for (int i = colonPos + 1; i < valueL; i++) { - char c = (char) valueB[i + valueS]; - if (c < '0' || c > '9') { - response.setStatus(400); - setErrorState(ErrorState.CLOSE_CLEAN, null); - return; + // A ':' not followed by digits is an empty port, which the + // grammar allows (RFC 3986, section 3.2.3). Treat it like an + // absent port: keep the port already associated with the + // request rather than storing zero for the absent digits. + if (colonPos + 1 < valueL) { + int port = 0; + for (int i = colonPos + 1; i < valueL; i++) { + char c = (char) valueB[i + valueS]; + if (c < '0' || c > '9') { + response.setStatus(400); + setErrorState(ErrorState.CLOSE_CLEAN, null); + return; + } + int digit = c - '0'; + if (port > (Integer.MAX_VALUE - digit) / 10) { + response.setStatus(400); + setErrorState(ErrorState.CLOSE_CLEAN, null); + return; + } + port = port * 10 + digit; } - int digit = c - '0'; - if (port > (Integer.MAX_VALUE - digit) / 10) { - response.setStatus(400); - setErrorState(ErrorState.CLOSE_CLEAN, null); - return; - } - port = port * 10 + digit; + request.setServerPort(port); } - request.setServerPort(port); // Only need to copy the host name up to the : valueL = colonPos; diff --git a/java/org/apache/tomcat/util/http/parser/HttpParser.java b/java/org/apache/tomcat/util/http/parser/HttpParser.java index 617da26043..45f63f7b0f 100644 --- a/java/org/apache/tomcat/util/http/parser/HttpParser.java +++ b/java/org/apache/tomcat/util/http/parser/HttpParser.java @@ -1107,6 +1107,10 @@ public class HttpParser { static int validatePort(Reader reader, int colonPosition) throws IOException { // Remaining characters should be numeric ... + // An empty port (a ':' not followed by any digits) is permitted by the + // grammar (RFC 3986, section 3.2.3). It is treated as equivalent to no + // port being present: the colon position is returned and the caller + // must not overwrite the port already associated with the request. readInteger(reader); // ... followed by EOS if (reader.read() == -1) { diff --git a/test/org/apache/tomcat/util/http/parser/TestHttpParserHost.java b/test/org/apache/tomcat/util/http/parser/TestHttpParserHost.java index 4c0452f846..b09cb7da1c 100644 --- a/test/org/apache/tomcat/util/http/parser/TestHttpParserHost.java +++ b/test/org/apache/tomcat/util/http/parser/TestHttpParserHost.java @@ -228,6 +228,15 @@ public class TestHttpParserHost { // IPv6 - invalid port result.add(new Object[] { TestType.IPv4, "[::1]:x", Integer.valueOf(-1), IAE} ); result.add(new Object[] { TestType.IPv4, "[::1]:-1", Integer.valueOf(-1), IAE} ); + // Domain name - empty port (permitted, treated as no port) + result.add(new Object[] { TestType.IPv4, "localhost:", Integer.valueOf(9), null} ); + result.add(new Object[] { TestType.IPv4, "foo.bar.:", Integer.valueOf(8), null} ); + // IPv4 - empty port (permitted, treated as no port) + result.add(new Object[] { TestType.IPv4, "127.0.0.1:", Integer.valueOf(9), null} ); + // IPv6 - empty port (permitted, treated as no port). Only the IPv6 + // test type is valid here: readHostIPv4() rejects the brackets before + // the empty port is reached. + result.add(new Object[] { TestType.IPv6, "[::1]:", Integer.valueOf(5), null} ); return result; } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
