diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java index 67c58ee0a98..ddb2c4a97df 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/Huffman.java @@ -287,6 +287,7 @@ public class Huffman }; static final int[][] LCCODES = new int[CODES.length][]; + static final char EOS = 256; // Huffman decode tree stored in a flattened char array for good // locality of reference. @@ -344,24 +345,25 @@ public class Huffman } } - public static String decode(ByteBuffer buffer) + public static String decode(ByteBuffer buffer) throws HpackException.CompressionException { return decode(buffer,buffer.remaining()); } - public static String decode(ByteBuffer buffer,int length) + public static String decode(ByteBuffer buffer,int length) throws HpackException.CompressionException { StringBuilder out = new StringBuilder(length*2); int node = 0; int current = 0; int bits = 0; - + byte[] array = buffer.array(); int position=buffer.position(); int start=buffer.arrayOffset()+position; int end=start+length; buffer.position(position+length); - + + for (int i=start; i 0) { int c = (current << (8 - bits)) & 0xFF; + int lastNode = node; node = tree[node*256+c]; - if (rowbits[node]==0 || rowbits[node] > bits) + + if (rowbits[node]==0 || rowbits[node] > bits) + { + int requiredPadding = 0; + for(int i=0; i>(8-bits)) != requiredPadding) + throw new HpackException.CompressionException("Incorrect padding"); + + node = lastNode; break; - - if (rowbits[node]==0) - throw new IllegalStateException(); - + } + out.append(rowsym[node]); bits -= rowbits[node]; node = 0; } + if(node != 0) + throw new HpackException.CompressionException("Bad termination"); + return out.toString(); } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java index 8af7b0e11e5..d91caff99cf 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackContextTest.java @@ -411,7 +411,7 @@ public class HpackContextTest } @Test - public void testStaticHuffmanValues() + public void testStaticHuffmanValues() throws Exception { HpackContext ctx = new HpackContext(4096); for (int i=2;i<=14;i++) diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java index 37b65015f78..f912cf8d9d9 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java @@ -462,25 +462,124 @@ public class HpackDecoderTest } } - - /* - 8.1.2.6. Malformed Requests and Responses - × 1: Sends a HEADERS frame with the "content-length" header field which does not equal the DATA frame payload length - × 2: Sends a HEADERS frame with the "content-length" header field which does not equal the sum of the multiple DATA frames payload length - 5. Primitive Type Representations - 5.2. String Literal Representation - × 1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits - -> The endpoint MUST treat this as a decoding error. - Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR) - Connection closed - Actual: DATA Frame (length:687, flags:0x01, stream_id:1) - × 2: Sends a Huffman-encoded string literal representation padded by zero - -> The endpoint MUST treat this as a decoding error. - Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR) - Connection closed - Actual: DATA Frame (length:687, flags:0x01, stream_id:1) - ✔ 3: Sends a Huffman-encoded string literal representation containing the EOS symbol - */ - + @Test() + public void testHuffmanEncodedStandard() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "83" + "49509F"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + MetaData.Request request = (MetaData.Request)decoder.decode(buffer); + + assertEquals("GET", request.getMethod()); + assertEquals(HttpScheme.HTTP.asString(), request.getURI().getScheme()); + assertEquals("/", request.getURI().getPath()); + assertEquals("test", request.getURI().getHost()); + assertFalse(request.iterator().hasNext()); + } + + + /* 5.2.1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits */ + @Test() + public void testHuffmanEncodedExtraPadding() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "84" + "49509FFF"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch (CompressionException ex) + { + Assert.assertThat(ex.getMessage(), Matchers.containsString("Bad termination")); + } + } + + + /* 5.2.2: Sends a Huffman-encoded string literal representation padded by zero */ + @Test() + public void testHuffmanEncodedZeroPadding() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "83" + "495090"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch (CompressionException ex) + { + Assert.assertThat(ex.getMessage(), Matchers.containsString("Incorrect padding")); + } + } + + + /* 5.2.3: Sends a Huffman-encoded string literal representation containing the EOS symbol */ + @Test() + public void testHuffmanEncodedWithEOS() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "87" + "497FFFFFFF427F"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch (CompressionException ex) + { + Assert.assertThat(ex.getMessage(), Matchers.containsString("EOS in content")); + } + } + + + @Test() + public void testHuffmanEncodedOneIncompleteOctet() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "81" + "FE"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch (CompressionException ex) + { + Assert.assertThat(ex.getMessage(), Matchers.containsString("Bad termination")); + } + } + + + @Test() + public void testHuffmanEncodedTwoIncompleteOctet() throws Exception + { + HpackDecoder decoder = new HpackDecoder(4096, 8192); + + String encoded = "82868441" + "82" + "FFFE"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch (CompressionException ex) + { + Assert.assertThat(ex.getMessage(), Matchers.containsString("Bad termination")); + } + } } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java index 61a6f42e184..3b697ac0fe5 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HuffmanTest.java @@ -50,17 +50,6 @@ public class HuffmanTest } } - @Test - public void testDecodeTrailingFF() throws Exception - { - for (String[] test:tests) - { - byte[] encoded=TypeUtil.fromHexString(test[1]+"FF"); - String decoded=Huffman.decode(ByteBuffer.wrap(encoded)); - Assert.assertEquals(test[0],test[2],decoded); - } - } - @Test public void testEncode() throws Exception {