|
16 | 16 |
|
17 | 17 | package org.springframework.util; |
18 | 18 |
|
19 | | -import java.io.ByteArrayOutputStream; |
20 | 19 | import java.nio.charset.Charset; |
21 | 20 | import java.util.ArrayDeque; |
22 | 21 | import java.util.ArrayList; |
|
25 | 24 | import java.util.Collections; |
26 | 25 | import java.util.Deque; |
27 | 26 | import java.util.Enumeration; |
| 27 | +import java.util.HexFormat; |
28 | 28 | import java.util.Iterator; |
29 | 29 | import java.util.LinkedHashSet; |
30 | 30 | import java.util.List; |
@@ -803,54 +803,60 @@ public static boolean pathEquals(String path1, String path2) { |
803 | 803 | } |
804 | 804 |
|
805 | 805 | /** |
806 | | - * Decode the given encoded URI component value. Based on the following rules: |
807 | | - * <ul> |
808 | | - * <li>Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, |
809 | | - * and {@code "0"} through {@code "9"} stay the same.</li> |
810 | | - * <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.</li> |
811 | | - * <li>A sequence "<i>{@code %xy}</i>" is interpreted as a hexadecimal representation of the character.</li> |
812 | | - * <li>For all other characters (including those already decoded), the output is undefined.</li> |
813 | | - * </ul> |
814 | | - * @param source the encoded String |
815 | | - * @param charset the character set |
| 806 | + * Decode the given encoded URI component value by replacing "<i>{@code %xy}</i>" sequences |
| 807 | + * by an hexadecimal representation of the character in the specified charset, letting other |
| 808 | + * characters unchanged. |
| 809 | + * @param source the encoded {@code String} |
| 810 | + * @param charset the character encoding to use to decode the "<i>{@code %xy}</i>" sequences |
816 | 811 | * @return the decoded value |
817 | 812 | * @throws IllegalArgumentException when the given source contains invalid encoded sequences |
818 | 813 | * @since 5.0 |
819 | | - * @see java.net.URLDecoder#decode(String, String) |
| 814 | + * @see java.net.URLDecoder#decode(String, String) java.net.URLDecoder#decode for HTML form decoding |
820 | 815 | */ |
821 | 816 | public static String uriDecode(String source, Charset charset) { |
822 | 817 | int length = source.length(); |
823 | | - if (length == 0) { |
| 818 | + int firstPercentIndex = source.indexOf('%'); |
| 819 | + if (length == 0 || firstPercentIndex < 0) { |
824 | 820 | return source; |
825 | 821 | } |
826 | | - Assert.notNull(charset, "Charset must not be null"); |
827 | 822 |
|
828 | | - ByteArrayOutputStream baos = new ByteArrayOutputStream(length); |
829 | | - boolean changed = false; |
830 | | - for (int i = 0; i < length; i++) { |
831 | | - int ch = source.charAt(i); |
| 823 | + StringBuilder output = new StringBuilder(length); |
| 824 | + output.append(source, 0, firstPercentIndex); |
| 825 | + byte[] bytes = null; |
| 826 | + int i = firstPercentIndex; |
| 827 | + while (i < length) { |
| 828 | + char ch = source.charAt(i); |
832 | 829 | if (ch == '%') { |
833 | | - if (i + 2 < length) { |
834 | | - char hex1 = source.charAt(i + 1); |
835 | | - char hex2 = source.charAt(i + 2); |
836 | | - int u = Character.digit(hex1, 16); |
837 | | - int l = Character.digit(hex2, 16); |
838 | | - if (u == -1 || l == -1) { |
839 | | - throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); |
| 830 | + try { |
| 831 | + if (bytes == null) { |
| 832 | + bytes = new byte[(length - i) / 3]; |
840 | 833 | } |
841 | | - baos.write((char) ((u << 4) + l)); |
842 | | - i += 2; |
843 | | - changed = true; |
| 834 | + |
| 835 | + int pos = 0; |
| 836 | + while (i + 2 < length && ch == '%') { |
| 837 | + bytes[pos++] = (byte) HexFormat.fromHexDigits(source, i + 1, i + 3); |
| 838 | + i += 3; |
| 839 | + if (i < length) { |
| 840 | + ch = source.charAt(i); |
| 841 | + } |
| 842 | + } |
| 843 | + |
| 844 | + if (i < length && ch == '%') { |
| 845 | + throw new IllegalArgumentException("Incomplete trailing escape (%) pattern"); |
| 846 | + } |
| 847 | + |
| 848 | + output.append(new String(bytes, 0, pos, charset)); |
844 | 849 | } |
845 | | - else { |
| 850 | + catch (NumberFormatException ex) { |
846 | 851 | throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); |
847 | 852 | } |
848 | 853 | } |
849 | 854 | else { |
850 | | - baos.write(ch); |
| 855 | + output.append(ch); |
| 856 | + i++; |
851 | 857 | } |
852 | 858 | } |
853 | | - return (changed ? StreamUtils.copyToString(baos, charset) : source); |
| 859 | + return output.toString(); |
854 | 860 | } |
855 | 861 |
|
856 | 862 | /** |
|
0 commit comments