|
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; |
@@ -775,54 +774,60 @@ public static boolean pathEquals(String path1, String path2) { |
775 | 774 | } |
776 | 775 |
|
777 | 776 | /** |
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 |
788 | 782 | * @return the decoded value |
789 | 783 | * @throws IllegalArgumentException when the given source contains invalid encoded sequences |
790 | 784 | * @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 |
792 | 786 | */ |
793 | 787 | public static String uriDecode(String source, Charset charset) { |
794 | 788 | int length = source.length(); |
795 | | - if (length == 0) { |
| 789 | + int firstPercentIndex = source.indexOf('%'); |
| 790 | + if (length == 0 || firstPercentIndex < 0) { |
796 | 791 | return source; |
797 | 792 | } |
798 | | - Assert.notNull(charset, "Charset must not be null"); |
799 | 793 |
|
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); |
804 | 800 | 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]; |
812 | 804 | } |
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)); |
816 | 820 | } |
817 | | - else { |
| 821 | + catch (NumberFormatException ex) { |
818 | 822 | throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); |
819 | 823 | } |
820 | 824 | } |
821 | 825 | else { |
822 | | - baos.write(ch); |
| 826 | + output.append(ch); |
| 827 | + i++; |
823 | 828 | } |
824 | 829 | } |
825 | | - return (changed ? StreamUtils.copyToString(baos, charset) : source); |
| 830 | + return output.toString(); |
826 | 831 | } |
827 | 832 |
|
828 | 833 | /** |
@@ -1430,4 +1435,14 @@ public static String truncate(CharSequence charSequence, int threshold) { |
1430 | 1435 | return charSequence.toString(); |
1431 | 1436 | } |
1432 | 1437 |
|
| 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 | + |
1433 | 1448 | } |
0 commit comments