diff --git a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java index 2dda96bac2..9857450323 100644 --- a/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java +++ b/buffer/src/main/java/io/netty/buffer/ByteBufUtil.java @@ -125,6 +125,13 @@ public final class ByteBufUtil { return HexUtil.hexDump(array, fromIndex, length); } + /** + * Decode a 2-digit hex byte from within a string. + */ + public static byte decodeHexByte(CharSequence s, int pos) { + return HexUtil.decodeHexByte(s, pos); + } + /** * Decodes a string generated by {@link #hexDump(byte[])} */ @@ -132,6 +139,13 @@ public final class ByteBufUtil { return HexUtil.decodeHexDump(hexDump, 0, hexDump.length()); } + /** + * Decodes part of a string generated by {@link #hexDump(byte[])} + */ + public static byte[] decodeHexDump(CharSequence hexDump, int fromIndex, int length) { + return HexUtil.decodeHexDump(hexDump, fromIndex, length); + } + /** * Calculates the hash code of the specified buffer. This method is * useful when implementing a new buffer type. @@ -877,20 +891,50 @@ public final class ByteBufUtil { return new String(buf); } + /** + * Helper to decode half of a hexadecimal number from a string. + * @param c The ASCII character of the hexadecimal number to decode. + * Must be in the range {@code [0-9a-fA-F]}. + * @return The hexadecimal value represented in the ASCII character + * given, or {@code -1} if the character is invalid. + */ + private static int decodeHexNibble(final char c) { + // Character.digit() is not used here, as it addresses a larger + // set of characters (both ASCII and full-width latin letters). + if ('0' <= c && c <= '9') { + return c - '0'; + } + if ('a' <= c && c <= 'f') { + return c - 'a' + 0xA; + } + if ('A' <= c && c <= 'F') { + return c - 'A' + 0xA; + } + return -1; + } + + private static byte decodeHexByte(CharSequence s, int pos) { + int hi = decodeHexNibble(s.charAt(pos)); + int lo = decodeHexNibble(s.charAt(pos + 1)); + if (hi == -1 || lo == -1) { + throw new IllegalArgumentException(String.format( + "invalid hex byte '%s' at index %d of '%s'", s.subSequence(pos, pos + 2), pos, s)); + } + return (byte) ((hi << 4) + lo); + } + private static byte[] decodeHexDump(CharSequence hexDump, int fromIndex, int length) { - if (length < 0) { + if (length < 0 || (length & 1) != 0) { throw new IllegalArgumentException("length: " + length); } if (length == 0) { return EmptyArrays.EMPTY_BYTES; } - int endIndex = fromIndex + length - 1; byte[] bytes = new byte[length >>> 1]; - for (; fromIndex < endIndex; fromIndex += 2) { - bytes[fromIndex >>> 1] = (byte) ((Character.digit(hexDump.charAt(fromIndex), 16) << 4) + - Character.digit(hexDump.charAt(fromIndex + 1), 16)); + for (int i = 0; i < length; i += 2) { + bytes[i >>> 1] = decodeHexByte(hexDump, fromIndex + i); } return bytes; diff --git a/buffer/src/test/java/io/netty/buffer/ByteBufUtilTest.java b/buffer/src/test/java/io/netty/buffer/ByteBufUtilTest.java index e1bf859b2c..9865ed0109 100644 --- a/buffer/src/test/java/io/netty/buffer/ByteBufUtilTest.java +++ b/buffer/src/test/java/io/netty/buffer/ByteBufUtilTest.java @@ -20,6 +20,7 @@ import io.netty.util.CharsetUtil; import org.junit.Test; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.Random; import static io.netty.buffer.Unpooled.unreleasableBuffer; @@ -31,22 +32,35 @@ import static org.junit.Assert.fail; public class ByteBufUtilTest { @Test - public void decodeHexEvenLength() { - decodeHex(256); + public void decodeRandomHexBytesWithEvenLength() { + decodeRandomHexBytes(256); } @Test - public void decodeHexOddLength() { - decodeHex(257); + public void decodeRandomHexBytesWithOddLength() { + decodeRandomHexBytes(257); } - private static void decodeHex(int len) { + private static void decodeRandomHexBytes(int len) { byte[] b = new byte[len]; Random rand = new Random(); rand.nextBytes(b); String hexDump = ByteBufUtil.hexDump(b); - byte[] decodedBytes = ByteBufUtil.decodeHexDump(hexDump); - assertArrayEquals(b, decodedBytes); + for (int i = 0; i <= len; i++) { // going over sub-strings of various lengths including empty byte[]. + byte[] b2 = Arrays.copyOfRange(b, i, b.length); + byte[] decodedBytes = ByteBufUtil.decodeHexDump(hexDump, i * 2, (len - i) * 2); + assertArrayEquals(b2, decodedBytes); + } + } + + @Test(expected = IllegalArgumentException.class) + public void decodeHexDumpWithOddLength() { + ByteBufUtil.decodeHexDump("abc"); + } + + @Test(expected = IllegalArgumentException.class) + public void decodeHexDumpWithInvalidChar() { + ByteBufUtil.decodeHexDump("fg"); } @Test diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java index 815d779f4f..3df61e2ca4 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/QueryStringDecoder.java @@ -33,6 +33,7 @@ import java.util.Map; import static io.netty.util.internal.ObjectUtil.*; import static io.netty.util.internal.StringUtil.*; +import static io.netty.buffer.ByteBufUtil.decodeHexByte; /** * Splits an HTTP query string into a path string and key-value parameter pairs. @@ -330,7 +331,10 @@ public class QueryStringDecoder { byteBuf.clear(); do { - byteBuf.put(decodeHexByte(s, i, toExcluded)); + if (i + 3 > toExcluded) { + throw new IllegalArgumentException("unterminated escape sequence at index " + i + " of: " + s); + } + byteBuf.put(decodeHexByte(s, i + 1)); i += 3; } while (i < toExcluded && s.charAt(i) == '%'); i--; @@ -354,39 +358,6 @@ public class QueryStringDecoder { return strBuf.toString(); } - private static byte decodeHexByte(String s, int pos, int len) { - if (pos + 2 >= len) { - throw new IllegalArgumentException("unterminated escape sequence at index " + pos + " of: " + s); - } - int hi = decodeHexNibble(s.charAt(pos + 1)); - int lo = decodeHexNibble(s.charAt(pos + 2)); - if (hi == -1 || lo == -1) { - throw new IllegalArgumentException( - "invalid escape sequence '" + s.substring(pos, pos + 3) + "' at index " + pos + " of: " + s); - } - return (byte) ((hi << 4) + lo); - } - - /** - * Helper to decode half of a hexadecimal number from a string. - * @param c The ASCII character of the hexadecimal number to decode. - * Must be in the range {@code [0-9a-fA-F]}. - * @return The hexadecimal value represented in the ASCII character - * given, or {@code -1} if the character is invalid. - */ - private static int decodeHexNibble(final char c) { - if ('0' <= c && c <= '9') { - return c - '0'; - } - if ('a' <= c && c <= 'f') { - return c - 'a' + 0xA; - } - if ('A' <= c && c <= 'F') { - return c - 'A' + 0xA; - } - return -1; - } - private static int findPathEndIndex(String uri) { int len = uri.length(); for (int i = 0; i < len; i++) { diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java index 68eb4b808c..d0d5c1ce90 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/QueryStringDecoderTest.java @@ -205,8 +205,8 @@ public class QueryStringDecoderTest { "%42", "B", "%5f", "_", "f%4", "unterminated escape sequence at index 1 of: f%4", - "%x2", "invalid escape sequence '%x2' at index 0 of: %x2", - "%4x", "invalid escape sequence '%4x' at index 0 of: %4x", + "%x2", "invalid hex byte 'x2' at index 1 of '%x2'", + "%4x", "invalid hex byte '4x' at index 1 of '%4x'", "Caff%C3%A9", caffe, "случайный праздник", "случайный праздник", "случайный%20праздник", "случайный праздник",