This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 6e4427a0b515d516d4421734938b870291815502 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 f6be6a863b..cfa2f8c16f 100644 --- a/java/org/apache/coyote/AbstractProcessor.java +++ b/java/org/apache/coyote/AbstractProcessor.java @@ -354,23 +354,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 505ca7d789..ed5f3ab9e1 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 fa6e0634a8..04c058a76d 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]
