Skip to content

Commit 7f1bc20

Browse files
kilinksdeleuze
authored andcommitted
Refine StringUtils#uriDecode
Refine the StringUtils#uriDecode method in the following ways: - Use a StringBuilder instead of ByteArrayOutputStream, and only decode %-encoded sequences. - Use HexFormat.fromHexDigits to decode hex sequences. - Decode to a byte array that is only allocated if encoded sequences are encountered. Signed-off-by: Patrick Strawderman <[email protected]> See gh-34673
1 parent 0b92a51 commit 7f1bc20

File tree

2 files changed

+36
-17
lines changed

2 files changed

+36
-17
lines changed

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

+29-17
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.util;
1818

19-
import java.io.ByteArrayOutputStream;
2019
import java.nio.charset.Charset;
2120
import java.util.ArrayDeque;
2221
import java.util.ArrayList;
@@ -25,6 +24,7 @@
2524
import java.util.Collections;
2625
import java.util.Deque;
2726
import java.util.Enumeration;
27+
import java.util.HexFormat;
2828
import java.util.Iterator;
2929
import java.util.LinkedHashSet;
3030
import java.util.List;
@@ -816,38 +816,50 @@ public static boolean pathEquals(String path1, String path2) {
816816
* @see java.net.URLDecoder#decode(String, String)
817817
*/
818818
public static String uriDecode(String source, Charset charset) {
819+
Assert.notNull(charset, "Charset must not be null");
819820
int length = source.length();
820821
if (length == 0) {
821822
return source;
822823
}
823-
Assert.notNull(charset, "Charset must not be null");
824824

825-
ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
825+
StringBuilder output = new StringBuilder(length);
826826
boolean changed = false;
827-
for (int i = 0; i < length; i++) {
828-
int ch = source.charAt(i);
827+
byte[] bytes = null;
828+
int i = 0;
829+
while (i < length) {
830+
char ch = source.charAt(i);
829831
if (ch == '%') {
830-
if (i + 2 < length) {
831-
char hex1 = source.charAt(i + 1);
832-
char hex2 = source.charAt(i + 2);
833-
int u = Character.digit(hex1, 16);
834-
int l = Character.digit(hex2, 16);
835-
if (u == -1 || l == -1) {
836-
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
832+
try {
833+
if (bytes == null) {
834+
bytes = new byte[(length - i) / 3];
835+
}
836+
837+
int pos = 0;
838+
while (i + 2 < length && ch == '%') {
839+
bytes[pos++] = (byte) HexFormat.fromHexDigits(source, i + 1, i + 3);
840+
i += 3;
841+
if (i < length) {
842+
ch = source.charAt(i);
843+
}
837844
}
838-
baos.write((char) ((u << 4) + l));
839-
i += 2;
845+
846+
if (i < length && ch == '%') {
847+
throw new IllegalArgumentException("Incomplete trailing escape (%) pattern");
848+
}
849+
850+
output.append(new String(bytes, 0, pos, charset));
840851
changed = true;
841852
}
842-
else {
853+
catch (NumberFormatException ex) {
843854
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
844855
}
845856
}
846857
else {
847-
baos.write(ch);
858+
output.append(ch);
859+
i++;
848860
}
849861
}
850-
return (changed ? StreamUtils.copyToString(baos, charset) : source);
862+
return (changed ? output.toString() : source);
851863
}
852864

853865
/**

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

+7
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,19 @@ void decode() {
107107
assertThat(UriUtils.decode("T%C5%8Dky%C5%8D", CHARSET)).as("Invalid encoded result").isEqualTo("T\u014dky\u014d");
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");
110+
assertThat(UriUtils.decode("%20\u2019", CHARSET)).as("Invalid encoded result").isEqualTo(" \u2019");
110111
}
111112

112113
@Test
113114
void decodeInvalidSequence() {
114115
assertThatIllegalArgumentException().isThrownBy(() ->
115116
UriUtils.decode("foo%2", CHARSET));
117+
assertThatIllegalArgumentException().isThrownBy(() ->
118+
UriUtils.decode("foo%", CHARSET));
119+
assertThatIllegalArgumentException().isThrownBy(() ->
120+
UriUtils.decode("%", CHARSET));
121+
assertThatIllegalArgumentException().isThrownBy(() ->
122+
UriUtils.decode("%zz", CHARSET));
116123
}
117124

118125
@Test

0 commit comments

Comments
 (0)