1616
1717package org .springframework .boot .loader .net .util ;
1818
19- import java .nio .ByteBuffer ;
20- import java .nio .CharBuffer ;
21- import java .nio .charset .CharsetDecoder ;
22- import java .nio .charset .CoderResult ;
23- import java .nio .charset .CodingErrorAction ;
19+ import java .nio .charset .Charset ;
2420import java .nio .charset .StandardCharsets ;
21+ import java .util .HexFormat ;
2522
2623/**
27- * Utility to decode URL strings.
24+ * Utility to decode URL strings. Copied frm Spring Framework's {@code StringUtils} as we
25+ * cannot depend on it in the loader.
2826 *
2927 * @author Phillip Webb
28+ * @author Stephane Nicoll
3029 * @since 3.2.0
3130 */
3231public final class UrlDecoder {
@@ -35,73 +34,69 @@ private UrlDecoder() {
3534 }
3635
3736 /**
38- * Decode the given string by decoding URL {@code '%'} escapes. This method should be
39- * identical in behavior to the {@code decode} method in the internal
40- * {@code sun.net.www.ParseUtil} JDK class .
41- * @param string the string to decode
42- * @return the decoded string
37+ * Decode the given encoded URI component value by replacing each "<i> {@code %xy}</i>"
38+ * sequence with a hexadecimal representation of the character in
39+ * {@link StandardCharsets#UTF_8 UTF-8}, leaving other characters unmodified .
40+ * @param source the encoded URI component value
41+ * @return the decoded value
4342 */
44- public static String decode (String string ) {
45- int length = string .length ();
46- if ((length == 0 ) || (string .indexOf ('%' ) < 0 )) {
47- return string ;
48- }
49- StringBuilder result = new StringBuilder (length );
50- ByteBuffer byteBuffer = ByteBuffer .allocate (length );
51- CharBuffer charBuffer = CharBuffer .allocate (length );
52- CharsetDecoder decoder = StandardCharsets .UTF_8 .newDecoder ()
53- .onMalformedInput (CodingErrorAction .REPORT )
54- .onUnmappableCharacter (CodingErrorAction .REPORT );
55- int index = 0 ;
56- while (index < length ) {
57- char ch = string .charAt (index );
58- if (ch != '%' ) {
59- result .append (ch );
60- if (index + 1 >= length ) {
61- return result .toString ();
62- }
63- index ++;
64- continue ;
65- }
66- index = fillByteBuffer (byteBuffer , string , index , length );
67- decodeToCharBuffer (byteBuffer , charBuffer , decoder );
68- result .append (charBuffer .flip ());
69-
70- }
71- return result .toString ();
43+ public static String decode (String source ) {
44+ return decode (source , StandardCharsets .UTF_8 );
7245 }
7346
74- private static int fillByteBuffer (ByteBuffer byteBuffer , String string , int index , int length ) {
75- byteBuffer .clear ();
76- do {
77- byteBuffer .put (unescape (string , index ));
78- index += 3 ;
47+ /**
48+ * Decode the given encoded URI component value by replacing each "<i>{@code %xy}</i>"
49+ * sequence with a hexadecimal representation of the character in the specified
50+ * character encoding, leaving other characters unmodified.
51+ * @param source the encoded URI component value
52+ * @param charset the character encoding to use to decode the "<i>{@code %xy}</i>"
53+ * sequences
54+ * @return the decoded value
55+ */
56+ public static String decode (String source , Charset charset ) {
57+ int length = source .length ();
58+ int firstPercentIndex = source .indexOf ('%' );
59+ if (length == 0 || firstPercentIndex < 0 ) {
60+ return source ;
7961 }
80- while (index < length && string .charAt (index ) == '%' );
81- byteBuffer .flip ();
82- return index ;
83- }
8462
85- private static byte unescape (String string , int index ) {
86- try {
87- return (byte ) Integer .parseInt (string , index + 1 , index + 3 , 16 );
88- }
89- catch (NumberFormatException ex ) {
90- throw new IllegalArgumentException ();
91- }
92- }
63+ StringBuilder output = new StringBuilder (length );
64+ output .append (source , 0 , firstPercentIndex );
65+ byte [] bytes = null ;
66+ int i = firstPercentIndex ;
67+ while (i < length ) {
68+ char ch = source .charAt (i );
69+ if (ch == '%' ) {
70+ try {
71+ if (bytes == null ) {
72+ bytes = new byte [(length - i ) / 3 ];
73+ }
9374
94- private static void decodeToCharBuffer (ByteBuffer byteBuffer , CharBuffer charBuffer , CharsetDecoder decoder ) {
95- decoder .reset ();
96- charBuffer .clear ();
97- assertNoError (decoder .decode (byteBuffer , charBuffer , true ));
98- assertNoError (decoder .flush (charBuffer ));
99- }
75+ int pos = 0 ;
76+ while (i + 2 < length && ch == '%' ) {
77+ bytes [pos ++] = (byte ) HexFormat .fromHexDigits (source , i + 1 , i + 3 );
78+ i += 3 ;
79+ if (i < length ) {
80+ ch = source .charAt (i );
81+ }
82+ }
83+
84+ if (i < length && ch == '%' ) {
85+ throw new IllegalArgumentException ("Incomplete trailing escape (%) pattern" );
86+ }
10087
101- private static void assertNoError (CoderResult result ) {
102- if (result .isError ()) {
103- throw new IllegalArgumentException ("Error decoding percent encoded characters" );
88+ output .append (new String (bytes , 0 , pos , charset ));
89+ }
90+ catch (NumberFormatException ex ) {
91+ throw new IllegalArgumentException ("Invalid encoded sequence \" " + source .substring (i ) + "\" " );
92+ }
93+ }
94+ else {
95+ output .append (ch );
96+ i ++;
97+ }
10498 }
99+ return output .toString ();
105100 }
106101
107102}
0 commit comments