16
16
17
17
package org .springframework .boot .loader .net .util ;
18
18
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 ;
24
20
import java .nio .charset .StandardCharsets ;
21
+ import java .util .HexFormat ;
25
22
26
23
/**
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.
28
26
*
29
27
* @author Phillip Webb
28
+ * @author Stephane Nicoll
30
29
* @since 3.2.0
31
30
*/
32
31
public final class UrlDecoder {
@@ -35,73 +34,69 @@ private UrlDecoder() {
35
34
}
36
35
37
36
/**
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
43
42
*/
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 );
72
45
}
73
46
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 ;
79
61
}
80
- while (index < length && string .charAt (index ) == '%' );
81
- byteBuffer .flip ();
82
- return index ;
83
- }
84
62
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
+ }
93
74
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
+ }
100
87
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
+ }
104
98
}
99
+ return output .toString ();
105
100
}
106
101
107
102
}
0 commit comments