Skip to content

Commit 0934ed5

Browse files
Merge pull request #17 from cesarhernandezgt/v.5.3.39.RELEASE-TT.x-patch
backport Refine StringUtils#uriDecode
2 parents 79484a6 + b622de8 commit 0934ed5

File tree

4 files changed

+62
-37
lines changed

4 files changed

+62
-37
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version=5.3.39.RELEASE-TT.4
1+
version=5.3.39.RELEASE-TT.5
22
org.gradle.jvmargs=-Xmx2048m
33
org.gradle.caching=true
44
org.gradle.parallel=true

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

Lines changed: 46 additions & 31 deletions
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;
@@ -775,54 +774,60 @@ public static boolean pathEquals(String path1, String path2) {
775774
}
776775

777776
/**
778-
* Decode the given encoded URI component value. Based on the following rules:
779-
* <ul>
780-
* <li>Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"},
781-
* and {@code "0"} through {@code "9"} stay the same.</li>
782-
* <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.</li>
783-
* <li>A sequence "{@code %<i>xy</i>}" is interpreted as a hexadecimal representation of the character.</li>
784-
* <li>For all other characters (including those already decoded), the output is undefined.</li>
785-
* </ul>
786-
* @param source the encoded String
787-
* @param charset the character set
777+
* Decode the given encoded URI component value by replacing "<i>{@code %xy}</i>" sequences
778+
* by an hexadecimal representation of the character in the specified charset, letting other
779+
* characters unchanged.
780+
* @param source the encoded {@code String}
781+
* @param charset the character encoding to use to decode the "<i>{@code %xy}</i>" sequences
788782
* @return the decoded value
789783
* @throws IllegalArgumentException when the given source contains invalid encoded sequences
790784
* @since 5.0
791-
* @see java.net.URLDecoder#decode(String, String)
785+
* @see java.net.URLDecoder#decode(String, String) java.net.URLDecoder#decode for HTML form decoding
792786
*/
793787
public static String uriDecode(String source, Charset charset) {
794788
int length = source.length();
795-
if (length == 0) {
789+
int firstPercentIndex = source.indexOf('%');
790+
if (length == 0 || firstPercentIndex < 0) {
796791
return source;
797792
}
798-
Assert.notNull(charset, "Charset must not be null");
799793

800-
ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
801-
boolean changed = false;
802-
for (int i = 0; i < length; i++) {
803-
int ch = source.charAt(i);
794+
StringBuilder output = new StringBuilder(length);
795+
output.append(source, 0, firstPercentIndex);
796+
byte[] bytes = null;
797+
int i = firstPercentIndex;
798+
while (i < length) {
799+
char ch = source.charAt(i);
804800
if (ch == '%') {
805-
if (i + 2 < length) {
806-
char hex1 = source.charAt(i + 1);
807-
char hex2 = source.charAt(i + 2);
808-
int u = Character.digit(hex1, 16);
809-
int l = Character.digit(hex2, 16);
810-
if (u == -1 || l == -1) {
811-
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
801+
try {
802+
if (bytes == null) {
803+
bytes = new byte[(length - i) / 3];
812804
}
813-
baos.write((char) ((u << 4) + l));
814-
i += 2;
815-
changed = true;
805+
806+
int pos = 0;
807+
while (i + 2 < length && ch == '%') {
808+
bytes[pos++] = (byte) fromHexPair(source, i + 1);
809+
i += 3;
810+
if (i < length) {
811+
ch = source.charAt(i);
812+
}
813+
}
814+
815+
if (i < length && ch == '%') {
816+
throw new IllegalArgumentException("Incomplete trailing escape (%) pattern");
817+
}
818+
819+
output.append(new String(bytes, 0, pos, charset));
816820
}
817-
else {
821+
catch (NumberFormatException ex) {
818822
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
819823
}
820824
}
821825
else {
822-
baos.write(ch);
826+
output.append(ch);
827+
i++;
823828
}
824829
}
825-
return (changed ? StreamUtils.copyToString(baos, charset) : source);
830+
return output.toString();
826831
}
827832

828833
/**
@@ -1430,4 +1435,14 @@ public static String truncate(CharSequence charSequence, int threshold) {
14301435
return charSequence.toString();
14311436
}
14321437

1438+
private static int fromHexPair(CharSequence s, int start) {
1439+
int hi = Character.digit(s.charAt(start), 16);
1440+
int lo = Character.digit(s.charAt(start + 1), 16);
1441+
if (hi < 0 || lo < 0) {
1442+
throw new NumberFormatException();
1443+
}
1444+
return (hi << 4) | lo;
1445+
}
1446+
1447+
14331448
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,15 +373,16 @@ public static String decode(String source, String encoding) {
373373
}
374374

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

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,21 @@ public 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");
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řìńġ");
110113
}
111114

112115
@Test
113116
public void decodeInvalidSequence() {
114117
assertThatIllegalArgumentException().isThrownBy(() ->
115118
UriUtils.decode("foo%2", CHARSET));
119+
assertThatIllegalArgumentException().isThrownBy(() ->
120+
UriUtils.decode("foo%", CHARSET));
121+
assertThatIllegalArgumentException().isThrownBy(() ->
122+
UriUtils.decode("%", CHARSET));
123+
assertThatIllegalArgumentException().isThrownBy(() ->
124+
UriUtils.decode("%zz", CHARSET));
116125
}
117126

118127
@Test

0 commit comments

Comments
 (0)