Move QueryStringDecoder.decodeHexByte into ByteBufUtil
Motivations: 1. There are duplicated implementations of decoding hex strings. #6797 2. ByteBufUtil.HexUtil.decodeHexDump does not handle substring start index properly and does not decode hex byte rigorously. Modifications: 1. Function decodeHexByte is moved from QueryStringDecoder into ByteBufUtil. 2. ByteBufUtil.HexUtil.decodeHexDump is changed to use decodeHexByte. 3. Tests are Updated accordingly. Result: Fixed #6797 and made hex decoding functions more robust.
This commit is contained in:
parent
ed5fcbb773
commit
629b83e0a5
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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++) {
|
||||
|
@ -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праздник", "случайный праздник",
|
||||
|
Loading…
Reference in New Issue
Block a user