/* * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.incubator.http.internal.hpack; import org.testng.annotations.Test; import java.io.UncheckedIOException; import java.net.ProtocolException; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.function.Supplier; import java.util.stream.Collectors; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static jdk.incubator.http.internal.hpack.TestHelper.*; // // Tests whose names start with "testX" are the ones captured from real HPACK // use cases // public final class DecoderTest { // // http://tools.ietf.org/html/rfc7541#appendix-C.2.1 // @Test public void example1() { // @formatter:off test("400a 6375 7374 6f6d 2d6b 6579 0d63 7573\n" + "746f 6d2d 6865 6164 6572", "[ 1] (s = 55) custom-key: custom-header\n" + " Table size: 55", "custom-key: custom-header"); // @formatter:on } // // http://tools.ietf.org/html/rfc7541#appendix-C.2.2 // @Test public void example2() { // @formatter:off test("040c 2f73 616d 706c 652f 7061 7468", "empty.", ":path: /sample/path"); // @formatter:on } // // http://tools.ietf.org/html/rfc7541#appendix-C.2.3 // @Test public void example3() { // @formatter:off test("1008 7061 7373 776f 7264 0673 6563 7265\n" + "74", "empty.", "password: secret"); // @formatter:on } // // http://tools.ietf.org/html/rfc7541#appendix-C.2.4 // @Test public void example4() { // @formatter:off test("82", "empty.", ":method: GET"); // @formatter:on } // // http://tools.ietf.org/html/rfc7541#appendix-C.3 // @Test public void example5() { // @formatter:off Decoder d = new Decoder(256); test(d, "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" + "2e63 6f6d", "[ 1] (s = 57) :authority: www.example.com\n" + " Table size: 57", ":method: GET\n" + ":scheme: http\n" + ":path: /\n" + ":authority: www.example.com"); test(d, "8286 84be 5808 6e6f 2d63 6163 6865", "[ 1] (s = 53) cache-control: no-cache\n" + "[ 2] (s = 57) :authority: www.example.com\n" + " Table size: 110", ":method: GET\n" + ":scheme: http\n" + ":path: /\n" + ":authority: www.example.com\n" + "cache-control: no-cache"); test(d, "8287 85bf 400a 6375 7374 6f6d 2d6b 6579\n" + "0c63 7573 746f 6d2d 7661 6c75 65", "[ 1] (s = 54) custom-key: custom-value\n" + "[ 2] (s = 53) cache-control: no-cache\n" + "[ 3] (s = 57) :authority: www.example.com\n" + " Table size: 164", ":method: GET\n" + ":scheme: https\n" + ":path: /index.html\n" + ":authority: www.example.com\n" + "custom-key: custom-value"); // @formatter:on } @Test public void example5AllSplits() { // @formatter:off testAllSplits( "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" + "2e63 6f6d", "[ 1] (s = 57) :authority: www.example.com\n" + " Table size: 57", ":method: GET\n" + ":scheme: http\n" + ":path: /\n" + ":authority: www.example.com"); // @formatter:on } // // http://tools.ietf.org/html/rfc7541#appendix-C.4 // @Test public void example6() { // @formatter:off Decoder d = new Decoder(256); test(d, "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4\n" + "ff", "[ 1] (s = 57) :authority: www.example.com\n" + " Table size: 57", ":method: GET\n" + ":scheme: http\n" + ":path: /\n" + ":authority: www.example.com"); test(d, "8286 84be 5886 a8eb 1064 9cbf", "[ 1] (s = 53) cache-control: no-cache\n" + "[ 2] (s = 57) :authority: www.example.com\n" + " Table size: 110", ":method: GET\n" + ":scheme: http\n" + ":path: /\n" + ":authority: www.example.com\n" + "cache-control: no-cache"); test(d, "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925\n" + "a849 e95b b8e8 b4bf", "[ 1] (s = 54) custom-key: custom-value\n" + "[ 2] (s = 53) cache-control: no-cache\n" + "[ 3] (s = 57) :authority: www.example.com\n" + " Table size: 164", ":method: GET\n" + ":scheme: https\n" + ":path: /index.html\n" + ":authority: www.example.com\n" + "custom-key: custom-value"); // @formatter:on } // // http://tools.ietf.org/html/rfc7541#appendix-C.5 // @Test public void example7() { // @formatter:off Decoder d = new Decoder(256); test(d, "4803 3330 3258 0770 7269 7661 7465 611d\n" + "4d6f 6e2c 2032 3120 4f63 7420 3230 3133\n" + "2032 303a 3133 3a32 3120 474d 546e 1768\n" + "7474 7073 3a2f 2f77 7777 2e65 7861 6d70\n" + "6c65 2e63 6f6d", "[ 1] (s = 63) location: https://www.example.com\n" + "[ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" + "[ 3] (s = 52) cache-control: private\n" + "[ 4] (s = 42) :status: 302\n" + " Table size: 222", ":status: 302\n" + "cache-control: private\n" + "date: Mon, 21 Oct 2013 20:13:21 GMT\n" + "location: https://www.example.com"); test(d, "4803 3330 37c1 c0bf", "[ 1] (s = 42) :status: 307\n" + "[ 2] (s = 63) location: https://www.example.com\n" + "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" + "[ 4] (s = 52) cache-control: private\n" + " Table size: 222", ":status: 307\n" + "cache-control: private\n" + "date: Mon, 21 Oct 2013 20:13:21 GMT\n" + "location: https://www.example.com"); test(d, "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420\n" + "3230 3133 2032 303a 3133 3a32 3220 474d\n" + "54c0 5a04 677a 6970 7738 666f 6f3d 4153\n" + "444a 4b48 514b 425a 584f 5157 454f 5049\n" + "5541 5851 5745 4f49 553b 206d 6178 2d61\n" + "6765 3d33 3630 303b 2076 6572 7369 6f6e\n" + "3d31", "[ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" + "[ 2] (s = 52) content-encoding: gzip\n" + "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" + " Table size: 215", ":status: 200\n" + "cache-control: private\n" + "date: Mon, 21 Oct 2013 20:13:22 GMT\n" + "location: https://www.example.com\n" + "content-encoding: gzip\n" + "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"); // @formatter:on } // // http://tools.ietf.org/html/rfc7541#appendix-C.6 // @Test public void example8() { // @formatter:off Decoder d = new Decoder(256); test(d, "4882 6402 5885 aec3 771a 4b61 96d0 7abe\n" + "9410 54d4 44a8 2005 9504 0b81 66e0 82a6\n" + "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8\n" + "e9ae 82ae 43d3", "[ 1] (s = 63) location: https://www.example.com\n" + "[ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" + "[ 3] (s = 52) cache-control: private\n" + "[ 4] (s = 42) :status: 302\n" + " Table size: 222", ":status: 302\n" + "cache-control: private\n" + "date: Mon, 21 Oct 2013 20:13:21 GMT\n" + "location: https://www.example.com"); test(d, "4883 640e ffc1 c0bf", "[ 1] (s = 42) :status: 307\n" + "[ 2] (s = 63) location: https://www.example.com\n" + "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" + "[ 4] (s = 52) cache-control: private\n" + " Table size: 222", ":status: 307\n" + "cache-control: private\n" + "date: Mon, 21 Oct 2013 20:13:21 GMT\n" + "location: https://www.example.com"); test(d, "88c1 6196 d07a be94 1054 d444 a820 0595\n" + "040b 8166 e084 a62d 1bff c05a 839b d9ab\n" + "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b\n" + "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f\n" + "9587 3160 65c0 03ed 4ee5 b106 3d50 07", "[ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" + "[ 2] (s = 52) content-encoding: gzip\n" + "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" + " Table size: 215", ":status: 200\n" + "cache-control: private\n" + "date: Mon, 21 Oct 2013 20:13:22 GMT\n" + "location: https://www.example.com\n" + "content-encoding: gzip\n" + "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"); // @formatter:on } @Test // One of responses from Apache Server that helped to catch a bug public void testX() { Decoder d = new Decoder(4096); // @formatter:off test(d, "3fe1 1f88 6196 d07a be94 03ea 693f 7504\n" + "00b6 a05c b827 2e32 fa98 b46f 769e 86b1\n" + "9272 b025 da5c 2ea9 fd70 a8de 7fb5 3556\n" + "5ab7 6ece c057 02e2 2ad2 17bf 6c96 d07a\n" + "be94 0854 cb6d 4a08 0075 40bd 71b6 6e05\n" + "a531 68df 0f13 8efe 4522 cd32 21b6 5686\n" + "eb23 781f cf52 848f d24a 8f0f 0d02 3435\n" + "5f87 497c a589 d34d 1f", "[ 1] (s = 53) content-type: text/html\n" + "[ 2] (s = 50) accept-ranges: bytes\n" + "[ 3] (s = 74) last-modified: Mon, 11 Jun 2007 18:53:14 GMT\n" + "[ 4] (s = 77) server: Apache/2.4.17 (Unix) OpenSSL/1.0.2e-dev\n" + "[ 5] (s = 65) date: Mon, 09 Nov 2015 16:26:39 GMT\n" + " Table size: 319", ":status: 200\n" + "date: Mon, 09 Nov 2015 16:26:39 GMT\n" + "server: Apache/2.4.17 (Unix) OpenSSL/1.0.2e-dev\n" + "last-modified: Mon, 11 Jun 2007 18:53:14 GMT\n" + "etag: \"2d-432a5e4a73a80\"\n" + "accept-ranges: bytes\n" + "content-length: 45\n" + "content-type: text/html"); // @formatter:on } @Test public void testX1() { // Supplier of a decoder with a particular state Supplier<Decoder> s = () -> { Decoder d = new Decoder(4096); // @formatter:off test(d, "88 76 92 ca 54 a7 d7 f4 fa ec af ed 6d da 61 d7 bb 1e ad ff" + "df 61 97 c3 61 be 94 13 4a 65 b6 a5 04 00 b8 a0 5a b8 db 77" + "1b 71 4c 5a 37 ff 0f 0d 84 08 00 00 03", "[ 1] (s = 65) date: Fri, 24 Jun 2016 14:55:56 GMT\n" + "[ 2] (s = 59) server: Jetty(9.3.z-SNAPSHOT)\n" + " Table size: 124", ":status: 200\n" + "server: Jetty(9.3.z-SNAPSHOT)\n" + "date: Fri, 24 Jun 2016 14:55:56 GMT\n" + "content-length: 100000" ); // @formatter:on return d; }; // For all splits of the following data fed to the supplied decoder we // must get what's expected // @formatter:off testAllSplits(s, "88 bf be 0f 0d 84 08 00 00 03", "[ 1] (s = 65) date: Fri, 24 Jun 2016 14:55:56 GMT\n" + "[ 2] (s = 59) server: Jetty(9.3.z-SNAPSHOT)\n" + " Table size: 124", ":status: 200\n" + "server: Jetty(9.3.z-SNAPSHOT)\n" + "date: Fri, 24 Jun 2016 14:55:56 GMT\n" + "content-length: 100000"); // @formatter:on } // // This test is missing in the spec // @Test public void sizeUpdate() { Decoder d = new Decoder(4096); assertEquals(d.getTable().maxSize(), 4096); d.decode(ByteBuffer.wrap(new byte[]{0b00111110}), true, nopCallback()); // newSize = 30 assertEquals(d.getTable().maxSize(), 30); } @Test public void incorrectSizeUpdate() { ByteBuffer b = ByteBuffer.allocate(8); Encoder e = new Encoder(8192) { @Override protected int calculateCapacity(int maxCapacity) { return maxCapacity; } }; e.header("a", "b"); e.encode(b); b.flip(); { Decoder d = new Decoder(4096); UncheckedIOException ex = assertVoidThrows(UncheckedIOException.class, () -> d.decode(b, true, (name, value) -> { })); assertNotNull(ex.getCause()); assertEquals(ex.getCause().getClass(), ProtocolException.class); } b.flip(); { Decoder d = new Decoder(4096); UncheckedIOException ex = assertVoidThrows(UncheckedIOException.class, () -> d.decode(b, false, (name, value) -> { })); assertNotNull(ex.getCause()); assertEquals(ex.getCause().getClass(), ProtocolException.class); } } @Test public void corruptedHeaderBlockInteger() { Decoder d = new Decoder(4096); ByteBuffer data = ByteBuffer.wrap(new byte[]{ (byte) 0b11111111, // indexed (byte) 0b10011010 // 25 + ... }); UncheckedIOException e = assertVoidThrows(UncheckedIOException.class, () -> d.decode(data, true, nopCallback())); assertNotNull(e.getCause()); assertEquals(e.getCause().getClass(), ProtocolException.class); assertExceptionMessageContains(e, "Unexpected end of header block"); } // 5.1. Integer Representation // ... // Integer encodings that exceed implementation limits -- in value or octet // length -- MUST be treated as decoding errors. Different limits can // be set for each of the different uses of integers, based on // implementation constraints. @Test public void headerBlockIntegerNoOverflow() { Decoder d = new Decoder(4096); ByteBuffer data = ByteBuffer.wrap(new byte[]{ (byte) 0b11111111, // indexed + 127 // Integer.MAX_VALUE - 127 (base 128, little-endian): (byte) 0b10000000, (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111111, (byte) 0b00000111 }); IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class, () -> d.decode(data, true, nopCallback())); assertExceptionMessageContains(e, "index=2147483647"); } @Test public void headerBlockIntegerOverflow() { Decoder d = new Decoder(4096); ByteBuffer data = ByteBuffer.wrap(new byte[]{ (byte) 0b11111111, // indexed + 127 // Integer.MAX_VALUE - 127 + 1 (base 128, little endian): (byte) 0b10000001, (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111111, (byte) 0b00000111 }); IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class, () -> d.decode(data, true, nopCallback())); assertExceptionMessageContains(e, "Integer overflow"); } @Test public void corruptedHeaderBlockString1() { Decoder d = new Decoder(4096); ByteBuffer data = ByteBuffer.wrap(new byte[]{ 0b00001111, // literal, index=15 0b00000000, 0b00001000, // huffman=false, length=8 0b00000000, // \ 0b00000000, // but only 3 octets available... 0b00000000 // / }); UncheckedIOException e = assertVoidThrows(UncheckedIOException.class, () -> d.decode(data, true, nopCallback())); assertNotNull(e.getCause()); assertEquals(e.getCause().getClass(), ProtocolException.class); assertExceptionMessageContains(e, "Unexpected end of header block"); } @Test public void corruptedHeaderBlockString2() { Decoder d = new Decoder(4096); ByteBuffer data = ByteBuffer.wrap(new byte[]{ 0b00001111, // literal, index=15 0b00000000, (byte) 0b10001000, // huffman=true, length=8 0b00000000, // \ 0b00000000, // \ 0b00000000, // but only 5 octets available... 0b00000000, // / 0b00000000 // / }); UncheckedIOException e = assertVoidThrows(UncheckedIOException.class, () -> d.decode(data, true, nopCallback())); assertNotNull(e.getCause()); assertEquals(e.getCause().getClass(), ProtocolException.class); assertExceptionMessageContains(e, "Unexpected end of header block"); } // 5.2. String Literal Representation // ...A Huffman-encoded string literal containing the EOS symbol MUST be // treated as a decoding error... @Test public void corruptedHeaderBlockHuffmanStringEOS() { Decoder d = new Decoder(4096); ByteBuffer data = ByteBuffer.wrap(new byte[]{ 0b00001111, // literal, index=15 0b00000000, (byte) 0b10000110, // huffman=true, length=6 0b00011001, 0b01001101, (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111100 }); IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class, () -> d.decode(data, true, nopCallback())); assertExceptionMessageContains(e, "Encountered EOS"); } // 5.2. String Literal Representation // ...A padding strictly longer than 7 bits MUST be treated as a decoding // error... @Test public void corruptedHeaderBlockHuffmanStringLongPadding1() { Decoder d = new Decoder(4096); ByteBuffer data = ByteBuffer.wrap(new byte[]{ 0b00001111, // literal, index=15 0b00000000, (byte) 0b10000011, // huffman=true, length=3 0b00011001, 0b01001101, (byte) 0b11111111 // len("aei") + len(padding) = (5 + 5 + 5) + (9) }); IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class, () -> d.decode(data, true, nopCallback())); assertExceptionMessageContains(e, "Padding is too long", "len=9"); } @Test public void corruptedHeaderBlockHuffmanStringLongPadding2() { Decoder d = new Decoder(4096); ByteBuffer data = ByteBuffer.wrap(new byte[]{ 0b00001111, // literal, index=15 0b00000000, (byte) 0b10000011, // huffman=true, length=3 0b00011001, 0b01111010, (byte) 0b11111111 // len("aek") + len(padding) = (5 + 5 + 7) + (7) }); assertVoidDoesNotThrow(() -> d.decode(data, true, nopCallback())); } // 5.2. String Literal Representation // ...A padding not corresponding to the most significant bits of the code // for the EOS symbol MUST be treated as a decoding error... @Test public void corruptedHeaderBlockHuffmanStringNotEOSPadding() { Decoder d = new Decoder(4096); ByteBuffer data = ByteBuffer.wrap(new byte[]{ 0b00001111, // literal, index=15 0b00000000, (byte) 0b10000011, // huffman=true, length=3 0b00011001, 0b01111010, (byte) 0b11111110 }); IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class, () -> d.decode(data, true, nopCallback())); assertExceptionMessageContains(e, "Not a EOS prefix"); } @Test public void argsTestBiConsumerIsNull() { Decoder decoder = new Decoder(4096); assertVoidThrows(NullPointerException.class, () -> decoder.decode(ByteBuffer.allocate(16), true, null)); } @Test public void argsTestByteBufferIsNull() { Decoder decoder = new Decoder(4096); assertVoidThrows(NullPointerException.class, () -> decoder.decode(null, true, nopCallback())); } @Test public void argsTestBothAreNull() { Decoder decoder = new Decoder(4096); assertVoidThrows(NullPointerException.class, () -> decoder.decode(null, true, null)); } private static void test(String hexdump, String headerTable, String headerList) { test(new Decoder(4096), hexdump, headerTable, headerList); } private static void testAllSplits(String hexdump, String expectedHeaderTable, String expectedHeaderList) { testAllSplits(() -> new Decoder(256), hexdump, expectedHeaderTable, expectedHeaderList); } private static void testAllSplits(Supplier<Decoder> supplier, String hexdump, String expectedHeaderTable, String expectedHeaderList) { ByteBuffer source = SpecHelper.toBytes(hexdump); BuffersTestingKit.forEachSplit(source, iterable -> { List<String> actual = new LinkedList<>(); Iterator<? extends ByteBuffer> i = iterable.iterator(); if (!i.hasNext()) { return; } Decoder d = supplier.get(); do { ByteBuffer n = i.next(); d.decode(n, !i.hasNext(), (name, value) -> { if (value == null) { actual.add(name.toString()); } else { actual.add(name + ": " + value); } }); } while (i.hasNext()); assertEquals(d.getTable().getStateString(), expectedHeaderTable); assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList); }); } // // Sometimes we need to keep the same decoder along several runs, // as it models the same connection // private static void test(Decoder d, String hexdump, String expectedHeaderTable, String expectedHeaderList) { ByteBuffer source = SpecHelper.toBytes(hexdump); List<String> actual = new LinkedList<>(); d.decode(source, true, (name, value) -> { if (value == null) { actual.add(name.toString()); } else { actual.add(name + ": " + value); } }); assertEquals(d.getTable().getStateString(), expectedHeaderTable); assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList); } private static DecodingCallback nopCallback() { return (t, u) -> { }; } }