blob: 34eb5103bf8ac4e6cf7d9226519341643de9032b [file] [log] [blame]
/*
* Copyright (c) 2015, 2018, 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.internal.net.http.hpack;
import org.testng.annotations.Test;
import java.io.IOException;
import java.io.UncheckedIOException;
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.internal.net.http.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() throws IOException {
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);
assertVoidThrows(IOException.class,
() -> d.decode(b, true, (name, value) -> { }));
}
b.flip();
{
Decoder d = new Decoder(4096);
assertVoidThrows(IOException.class,
() -> d.decode(b, false, (name, value) -> { }));
}
}
@Test
public void corruptedHeaderBlockInteger() {
Decoder d = new Decoder(4096);
ByteBuffer data = ByteBuffer.wrap(new byte[]{
(byte) 0b11111111, // indexed
(byte) 0b10011010 // 25 + ...
});
IOException e = assertVoidThrows(IOException.class,
() -> d.decode(data, true, nopCallback()));
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
});
IOException e = assertVoidThrows(IOException.class,
() -> d.decode(data, true, nopCallback()));
assertExceptionMessageContains(e.getCause(), "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
});
IOException e = assertVoidThrows(IOException.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 // /
});
IOException e = assertVoidThrows(IOException.class,
() -> d.decode(data, true, nopCallback()));
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 // /
});
IOException e = assertVoidThrows(IOException.class,
() -> d.decode(data, true, nopCallback()));
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
});
IOException e = assertVoidThrows(IOException.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)
});
IOException e = assertVoidThrows(IOException.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
});
IOException e = assertVoidThrows(IOException.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();
try {
d.decode(n, !i.hasNext(), (name, value) -> {
if (value == null) {
actual.add(name.toString());
} else {
actual.add(name + ": " + value);
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} while (i.hasNext());
assertEquals(d.getTable().getStateString(), expectedHeaderTable);
assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
});
// Now introduce last ByteBuffer which is empty and EOF (mimics idiom
// I've found in HttpClient code)
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();
try {
d.decode(n, false, (name, value) -> {
if (value == null) {
actual.add(name.toString());
} else {
actual.add(name + ": " + value);
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} while (i.hasNext());
try {
d.decode(ByteBuffer.allocate(0), false, (name, value) -> {
if (value == null) {
actual.add(name.toString());
} else {
actual.add(name + ": " + value);
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
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<>();
try {
d.decode(source, true, (name, value) -> {
if (value == null) {
actual.add(name.toString());
} else {
actual.add(name + ": " + value);
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
assertEquals(d.getTable().getStateString(), expectedHeaderTable);
assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
}
private static DecodingCallback nopCallback() {
return (t, u) -> { };
}
}