Skip to content

Commit dd888ed

Browse files
committed
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
1 parent 7f1bc20 commit dd888ed

File tree

3 files changed

+21
-24
lines changed

3 files changed

+21
-24
lines changed

Diff for: spring-core/src/main/java/org/springframework/util/StringUtils.java

+11-17
Original file line numberDiff line numberDiff line change
@@ -800,32 +800,27 @@ public static boolean pathEquals(String path1, String path2) {
800800
}
801801

802802
/**
803-
* Decode the given encoded URI component value. Based on the following rules:
804-
* <ul>
805-
* <li>Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"},
806-
* and {@code "0"} through {@code "9"} stay the same.</li>
807-
* <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.</li>
808-
* <li>A sequence "<i>{@code %xy}</i>" is interpreted as a hexadecimal representation of the character.</li>
809-
* <li>For all other characters (including those already decoded), the output is undefined.</li>
810-
* </ul>
811-
* @param source the encoded String
812-
* @param charset the character set
803+
* Decode the given encoded URI component value by replacing "<i>{@code %xy}</i>" sequences
804+
* by an hexadecimal representation of the character in the specified charset, letting other
805+
* characters unchanged.
806+
* @param source the encoded {@code String}
807+
* @param charset the character encoding to use to decode the "<i>{@code %xy}</i>" sequences
813808
* @return the decoded value
814809
* @throws IllegalArgumentException when the given source contains invalid encoded sequences
815810
* @since 5.0
816-
* @see java.net.URLDecoder#decode(String, String)
811+
* @see java.net.URLDecoder#decode(String, String) java.net.URLDecoder#decode for HTML form decoding
817812
*/
818813
public static String uriDecode(String source, Charset charset) {
819-
Assert.notNull(charset, "Charset must not be null");
820814
int length = source.length();
821-
if (length == 0) {
815+
int firstPercentIndex = source.indexOf('%');
816+
if (length == 0 || firstPercentIndex < 0) {
822817
return source;
823818
}
824819

825820
StringBuilder output = new StringBuilder(length);
826-
boolean changed = false;
821+
output.append(source, 0, firstPercentIndex);
827822
byte[] bytes = null;
828-
int i = 0;
823+
int i = firstPercentIndex;
829824
while (i < length) {
830825
char ch = source.charAt(i);
831826
if (ch == '%') {
@@ -848,7 +843,6 @@ public static String uriDecode(String source, Charset charset) {
848843
}
849844

850845
output.append(new String(bytes, 0, pos, charset));
851-
changed = true;
852846
}
853847
catch (NumberFormatException ex) {
854848
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
@@ -859,7 +853,7 @@ public static String uriDecode(String source, Charset charset) {
859853
i++;
860854
}
861855
}
862-
return (changed ? output.toString() : source);
856+
return output.toString();
863857
}
864858

865859
/**

Diff for: spring-web/src/main/java/org/springframework/web/util/UriUtils.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -374,15 +374,16 @@ public static String decode(String source, String encoding) {
374374
}
375375

376376
/**
377-
* Decode the given encoded URI component.
378-
* <p>See {@link StringUtils#uriDecode(String, Charset)} for the decoding rules.
379-
* @param source the encoded String
380-
* @param charset the character encoding to use
377+
* Decode the given encoded URI component value by replacing "<i>{@code %xy}</i>" sequences
378+
* by an hexadecimal representation of the character in the specified charset, letting other
379+
* characters unchanged.
380+
* @param source the encoded {@code String}
381+
* @param charset the character encoding to use to decode the "<i>{@code %xy}</i>" sequences
381382
* @return the decoded value
382383
* @throws IllegalArgumentException when the given source contains invalid encoded sequences
383384
* @since 5.0
384385
* @see StringUtils#uriDecode(String, Charset)
385-
* @see java.net.URLDecoder#decode(String, String)
386+
* @see java.net.URLDecoder#decode(String, String) java.net.URLDecoder#decode for HTML form decoding
386387
*/
387388
public static String decode(String source, Charset charset) {
388389
return StringUtils.uriDecode(source, charset);

Diff for: spring-web/src/test/java/org/springframework/web/util/UriUtilsTests.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -108,6 +108,8 @@ void decode() {
108108
assertThat(UriUtils.decode("/Z%C3%BCrich", CHARSET)).as("Invalid encoded result").isEqualTo("/Z\u00fcrich");
109109
assertThat(UriUtils.decode("T\u014dky\u014d", CHARSET)).as("Invalid encoded result").isEqualTo("T\u014dky\u014d");
110110
assertThat(UriUtils.decode("%20\u2019", CHARSET)).as("Invalid encoded result").isEqualTo(" \u2019");
111+
assertThat(UriUtils.decode("\u015bp\u0159\u00ec\u0144\u0121", CHARSET)).as("Invalid encoded result").isEqualTo("śpřìńġ");
112+
assertThat(UriUtils.decode("%20\u015bp\u0159\u00ec\u0144\u0121", CHARSET)).as("Invalid encoded result").isEqualTo(" śpřìńġ");
111113
}
112114

113115
@Test

0 commit comments

Comments
 (0)