|
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