From dd888ed8139ab1ae16d7ee6b5628bab202d3e649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Tue, 1 Apr 2025 16:14:32 +0200 Subject: [PATCH] Refine StringUtils#uriDecode and update documentation This commit adds another optimization mainly for the use case where there is no encoded sequence, and updates the Javadoc of both StringUtils#uriDecode and UriUtils#decode to match the implementation. Closes gh-34673 --- .../org/springframework/util/StringUtils.java | 28 ++++++++----------- .../springframework/web/util/UriUtils.java | 13 +++++---- .../web/util/UriUtilsTests.java | 4 ++- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index c76ce4c9ef..b2a2633fe2 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -800,32 +800,27 @@ public abstract class StringUtils { } /** - * Decode the given encoded URI component value. Based on the following rules: - * - * @param source the encoded String - * @param charset the character set + * Decode the given encoded URI component value by replacing "{@code %xy}" sequences + * by an hexadecimal representation of the character in the specified charset, letting other + * characters unchanged. + * @param source the encoded {@code String} + * @param charset the character encoding to use to decode the "{@code %xy}" sequences * @return the decoded value * @throws IllegalArgumentException when the given source contains invalid encoded sequences * @since 5.0 - * @see java.net.URLDecoder#decode(String, String) + * @see java.net.URLDecoder#decode(String, String) java.net.URLDecoder#decode for HTML form decoding */ public static String uriDecode(String source, Charset charset) { - Assert.notNull(charset, "Charset must not be null"); int length = source.length(); - if (length == 0) { + int firstPercentIndex = source.indexOf('%'); + if (length == 0 || firstPercentIndex < 0) { return source; } StringBuilder output = new StringBuilder(length); - boolean changed = false; + output.append(source, 0, firstPercentIndex); byte[] bytes = null; - int i = 0; + int i = firstPercentIndex; while (i < length) { char ch = source.charAt(i); if (ch == '%') { @@ -848,7 +843,6 @@ public abstract class StringUtils { } output.append(new String(bytes, 0, pos, charset)); - changed = true; } catch (NumberFormatException ex) { throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); @@ -859,7 +853,7 @@ public abstract class StringUtils { i++; } } - return (changed ? output.toString() : source); + return output.toString(); } /** diff --git a/spring-web/src/main/java/org/springframework/web/util/UriUtils.java b/spring-web/src/main/java/org/springframework/web/util/UriUtils.java index de159baad4..a0ef085a07 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -374,15 +374,16 @@ public abstract class UriUtils { } /** - * Decode the given encoded URI component. - *

See {@link StringUtils#uriDecode(String, Charset)} for the decoding rules. - * @param source the encoded String - * @param charset the character encoding to use + * Decode the given encoded URI component value by replacing "{@code %xy}" sequences + * by an hexadecimal representation of the character in the specified charset, letting other + * characters unchanged. + * @param source the encoded {@code String} + * @param charset the character encoding to use to decode the "{@code %xy}" sequences * @return the decoded value * @throws IllegalArgumentException when the given source contains invalid encoded sequences * @since 5.0 * @see StringUtils#uriDecode(String, Charset) - * @see java.net.URLDecoder#decode(String, String) + * @see java.net.URLDecoder#decode(String, String) java.net.URLDecoder#decode for HTML form decoding */ public static String decode(String source, Charset charset) { return StringUtils.uriDecode(source, charset); diff --git a/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java b/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java index 4edcd64430..edb148b6ea 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -108,6 +108,8 @@ class UriUtilsTests { assertThat(UriUtils.decode("/Z%C3%BCrich", CHARSET)).as("Invalid encoded result").isEqualTo("/Z\u00fcrich"); assertThat(UriUtils.decode("T\u014dky\u014d", CHARSET)).as("Invalid encoded result").isEqualTo("T\u014dky\u014d"); assertThat(UriUtils.decode("%20\u2019", CHARSET)).as("Invalid encoded result").isEqualTo(" \u2019"); + assertThat(UriUtils.decode("\u015bp\u0159\u00ec\u0144\u0121", CHARSET)).as("Invalid encoded result").isEqualTo("śpřìńġ"); + assertThat(UriUtils.decode("%20\u015bp\u0159\u00ec\u0144\u0121", CHARSET)).as("Invalid encoded result").isEqualTo(" śpřìńġ"); } @Test