diff --git a/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java b/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java index 6b5c508eb95e..fd9a15d54db2 100644 --- a/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java +++ b/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java @@ -2241,8 +2241,8 @@ public static OpaqueHost parse(String input, WhatWgUrlParser p) { // If input contains a U+0025 (%) and the two code points following it // are not ASCII hex digits, invalid-URL-unit validation error. if (p.validate() && ch == '%' && - (input.length() - i < 2 || !isAsciiDigit(input.codePointAt(i + 1)) || - !isAsciiDigit(input.codePointAt(i + 2)))) { + (input.length() - i < 3 || !isAsciiHexDigit(input.codePointAt(i + 1)) || + !isAsciiHexDigit(input.codePointAt(i + 2)))) { p.validationError("Code point \"" + ch + "\" is not a URL unit."); } } diff --git a/spring-web/src/test/java/org/springframework/web/util/WhatWgUrlParserTests.java b/spring-web/src/test/java/org/springframework/web/util/WhatWgUrlParserTests.java index ad4c4e697a3b..e29b0638b0d2 100644 --- a/spring-web/src/test/java/org/springframework/web/util/WhatWgUrlParserTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/WhatWgUrlParserTests.java @@ -16,6 +16,9 @@ package org.springframework.web.util; +import java.util.ArrayList; +import java.util.List; + import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; @@ -63,6 +66,21 @@ void parseAsciiHost() { testParse("file://xn--/p", "file", "xn--", null, "/p", null, null); } + @Test + void parseOpaqueHostTruncatedPercentEscape() { + // A truncated or non-hex percent-escape in an opaque host is a validation error, + // not a failure, and must not read past the end of the input + // (see https://url.spec.whatwg.org/#concept-opaque-host-parser). + List errors = new ArrayList<>(); + WhatWgUrlParser.UrlRecord record = WhatWgUrlParser.parse("foo://%4", EMPTY_URL_RECORD, null, errors::add); + assertThat(record.host().toString()).isEqualTo("%4"); + + record = WhatWgUrlParser.parse("foo://%4x", EMPTY_URL_RECORD, null, errors::add); + assertThat(record.host().toString()).isEqualTo("%4x"); + + assertThat(errors).isNotEmpty(); + } + private void testParse(String input, String scheme, @Nullable String host, @Nullable String port, String path, @Nullable String query, @Nullable String fragment) { WhatWgUrlParser.UrlRecord result = WhatWgUrlParser.parse(input, EMPTY_URL_RECORD, null, null); assertThat(result.scheme()).as("Invalid scheme").isEqualTo(scheme);