Fix failing h2spec tests 8.1.2.1 related to pseudo-headers validation

Motivation:

According to the spec:
All pseudo-header fields MUST appear in the header block before regular
header fields. Any request or response that contains a pseudo-header
field that appears in a header block after
a regular header field MUST be treated as malformed (Section 8.1.2.6).

Pseudo-header fields are only valid in the context in which they are defined.
Pseudo-header fields defined for requests MUST NOT appear in responses;
pseudo-header fields defined for responses MUST NOT appear in requests.
Pseudo-header fields MUST NOT appear in trailers.
Endpoints MUST treat a request or response that contains undefined or
invalid pseudo-header fields as malformed (Section 8.1.2.6).

Clients MUST NOT accept a malformed response. Note that these requirements
are intended to protect against several types of common attacks against HTTP;
they are deliberately strict because being permissive can expose
implementations to these vulnerabilities.

Modifications:

- Introduce validation in HPackDecoder

Result:

- Requests with unknown pseudo-field headers are rejected
- Requests with containing response specific pseudo-headers are rejected
- Requests where pseudo-header appear after regular header are rejected
- h2spec 8.1.2.1 pass
This commit is contained in:
Julien Hoarau 2017-12-13 21:17:57 +11:00 committed by Scott Mitchell
parent c795e8897b
commit 3e6b54bb59
12 changed files with 361 additions and 51 deletions

View File

@ -21,16 +21,19 @@ import io.netty.util.ByteProcessor;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.UnstableApi;
import static io.netty.handler.codec.http2.Http2Error.*;
import static io.netty.handler.codec.http2.Http2Exception.*;
import static io.netty.util.AsciiString.*;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.hasPseudoHeaderFormat;
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
import static io.netty.util.AsciiString.isUpperCase;
@UnstableApi
public class DefaultHttp2Headers
extends DefaultHeaders<CharSequence, CharSequence, Http2Headers> implements Http2Headers {
private static final ByteProcessor HTTP2_NAME_VALIDATOR_PROCESSOR = new ByteProcessor() {
@Override
public boolean process(byte value) throws Exception {
public boolean process(byte value) {
return !isUpperCase(value);
}
};
@ -207,7 +210,7 @@ public class DefaultHttp2Headers
this.next = next;
// Make sure the pseudo headers fields are first in iteration order
if (key.length() != 0 && key.charAt(0) == ':') {
if (hasPseudoHeaderFormat(key)) {
after = firstNonPseudo;
before = firstNonPseudo.before();
} else {

View File

@ -115,7 +115,7 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea
public Http2Headers decodeHeaders(int streamId, ByteBuf headerBlock) throws Http2Exception {
try {
final Http2Headers headers = newHeaders();
hpackDecoder.decode(streamId, headerBlock, headers);
hpackDecoder.decode(streamId, headerBlock, headers, validateHeaders);
headerArraySizeAccumulator = HEADERS_COUNT_WEIGHT_NEW * headers.size() +
HEADERS_COUNT_WEIGHT_HISTORICAL * headerArraySizeAccumulator;
return headers;

View File

@ -45,6 +45,8 @@ import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.getPseudoHeader;
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.hasPseudoHeaderFormat;
import static io.netty.util.AsciiString.EMPTY_STRING;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import static io.netty.util.internal.ThrowableUtil.unknownStackTrace;
@ -119,7 +121,7 @@ final class HpackDecoder {
* <p>
* This method assumes the entire header block is contained in {@code in}.
*/
public void decode(int streamId, ByteBuf in, Http2Headers headers) throws Http2Exception {
public void decode(int streamId, ByteBuf in, Http2Headers headers, boolean validateHeaders) throws Http2Exception {
int index = 0;
long headersLength = 0;
int nameLength = 0;
@ -127,6 +129,7 @@ final class HpackDecoder {
byte state = READ_HEADER_REPRESENTATION;
boolean huffmanEncoded = false;
CharSequence name = null;
HeaderType headerType = null;
IndexType indexType = IndexType.NONE;
while (in.isReadable()) {
switch (state) {
@ -146,7 +149,10 @@ final class HpackDecoder {
state = READ_INDEXED_HEADER;
break;
default:
headersLength = indexHeader(index, headers, headersLength);
HpackHeaderField indexedHeader = getIndexedHeader(index);
headerType = validate(indexedHeader.name, headerType, validateHeaders);
headersLength = addHeader(headers, indexedHeader.name, indexedHeader.value,
headersLength);
}
} else if ((b & 0x40) == 0x40) {
// Literal Header Field with Incremental Indexing
@ -162,6 +168,7 @@ final class HpackDecoder {
default:
// Index was stored as the prefix
name = readName(index);
headerType = validate(name, headerType, validateHeaders);
nameLength = name.length();
state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
}
@ -188,6 +195,7 @@ final class HpackDecoder {
default:
// Index was stored as the prefix
name = readName(index);
headerType = validate(name, headerType, validateHeaders);
nameLength = name.length();
state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
}
@ -200,13 +208,16 @@ final class HpackDecoder {
break;
case READ_INDEXED_HEADER:
headersLength = indexHeader(decodeULE128(in, index), headers, headersLength);
HpackHeaderField indexedHeader = getIndexedHeader(decodeULE128(in, index));
headerType = validate(indexedHeader.name, headerType, validateHeaders);
headersLength = addHeader(headers, indexedHeader.name, indexedHeader.value, headersLength);
state = READ_HEADER_REPRESENTATION;
break;
case READ_INDEXED_HEADER_NAME:
// Header Name matches an entry in the Header Table
name = readName(decodeULE128(in, index));
headerType = validate(name, headerType, validateHeaders);
nameLength = name.length();
state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
break;
@ -243,6 +254,7 @@ final class HpackDecoder {
}
name = readStringLiteral(in, nameLength, huffmanEncoded);
headerType = validate(name, headerType, validateHeaders);
state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
break;
@ -256,6 +268,7 @@ final class HpackDecoder {
state = READ_LITERAL_HEADER_VALUE_LENGTH;
break;
case 0:
headerType = validate(name, headerType, validateHeaders);
headersLength = insertHeader(headers, name, EMPTY_STRING, indexType, headersLength);
state = READ_HEADER_REPRESENTATION;
break;
@ -288,6 +301,7 @@ final class HpackDecoder {
}
CharSequence value = readStringLiteral(in, valueLength, huffmanEncoded);
headerType = validate(name, headerType, validateHeaders);
headersLength = insertHeader(headers, name, value, indexType, headersLength);
state = READ_HEADER_REPRESENTATION;
break;
@ -386,6 +400,34 @@ final class HpackDecoder {
hpackDynamicTable.setCapacity(dynamicTableSize);
}
private HeaderType validate(CharSequence name, HeaderType previousHeaderType,
final boolean validateHeaders) throws Http2Exception {
if (!validateHeaders) {
return null;
}
if (hasPseudoHeaderFormat(name)) {
if (previousHeaderType == HeaderType.REGULAR_HEADER) {
throw connectionError(PROTOCOL_ERROR, "Pseudo-header field '%s' found after regular header.", name);
}
final Http2Headers.PseudoHeaderName pseudoHeader = getPseudoHeader(name);
if (pseudoHeader == null) {
throw connectionError(PROTOCOL_ERROR, "Invalid HTTP/2 pseudo-header '%s' encountered.", name);
}
final HeaderType currentHeaderType = pseudoHeader.isRequestOnly() ?
HeaderType.REQUEST_PSEUDO_HEADER : HeaderType.RESPONSE_PSEUDO_HEADER;
if (previousHeaderType != null && currentHeaderType != previousHeaderType) {
throw connectionError(PROTOCOL_ERROR, "Mix of request and response pseudo-headers.");
}
return currentHeaderType;
}
return HeaderType.REGULAR_HEADER;
}
private CharSequence readName(int index) throws Http2Exception {
if (index <= HpackStaticTable.length) {
HpackHeaderField hpackHeaderField = HpackStaticTable.getEntry(index);
@ -398,14 +440,12 @@ final class HpackDecoder {
throw READ_NAME_ILLEGAL_INDEX_VALUE;
}
private long indexHeader(int index, Http2Headers headers, long headersLength) throws Http2Exception {
private HpackHeaderField getIndexedHeader(int index) throws Http2Exception {
if (index <= HpackStaticTable.length) {
HpackHeaderField hpackHeaderField = HpackStaticTable.getEntry(index);
return addHeader(headers, hpackHeaderField.name, hpackHeaderField.value, headersLength);
return HpackStaticTable.getEntry(index);
}
if (index - HpackStaticTable.length <= hpackDynamicTable.length()) {
HpackHeaderField hpackHeaderField = hpackDynamicTable.getEntry(index - HpackStaticTable.length);
return addHeader(headers, hpackHeaderField.name, hpackHeaderField.value, headersLength);
return hpackDynamicTable.getEntry(index - HpackStaticTable.length);
}
throw INDEX_HEADER_ILLEGAL_INDEX_VALUE;
}
@ -504,4 +544,13 @@ final class HpackDecoder {
throw DECODE_ULE_128_DECOMPRESSION_EXCEPTION;
}
/**
* HTTP/2 header types.
*/
private enum HeaderType {
REGULAR_HEADER,
REQUEST_PSEUDO_HEADER,
RESPONSE_PSEUDO_HEADER
}
}

View File

@ -35,38 +35,44 @@ public interface Http2Headers extends Headers<CharSequence, CharSequence, Http2H
/**
* {@code :method}.
*/
METHOD(":method"),
METHOD(":method", true),
/**
* {@code :scheme}.
*/
SCHEME(":scheme"),
SCHEME(":scheme", true),
/**
* {@code :authority}.
*/
AUTHORITY(":authority"),
AUTHORITY(":authority", true),
/**
* {@code :path}.
*/
PATH(":path"),
PATH(":path", true),
/**
* {@code :status}.
*/
STATUS(":status");
STATUS(":status", false);
private static final char PSEUDO_HEADER_PREFIX = ':';
private static final byte PSEUDO_HEADER_PREFIX_BYTE = (byte) PSEUDO_HEADER_PREFIX;
private final AsciiString value;
private static final CharSequenceMap<AsciiString> PSEUDO_HEADERS = new CharSequenceMap<AsciiString>();
private final boolean requestOnly;
private static final CharSequenceMap<PseudoHeaderName> PSEUDO_HEADERS = new CharSequenceMap<PseudoHeaderName>();
static {
for (PseudoHeaderName pseudoHeader : PseudoHeaderName.values()) {
PSEUDO_HEADERS.add(pseudoHeader.value(), AsciiString.EMPTY_STRING);
PSEUDO_HEADERS.add(pseudoHeader.value(), pseudoHeader);
}
}
PseudoHeaderName(String value) {
PseudoHeaderName(String value, boolean requestOnly) {
this.value = AsciiString.cached(value);
this.requestOnly = requestOnly;
}
public AsciiString value() {
@ -74,12 +80,44 @@ public interface Http2Headers extends Headers<CharSequence, CharSequence, Http2H
return value;
}
/**
* Indicates whether the specified header follows the pseudo-header format (begins with ':' character)
*
* @return {@code true} if the header follow the pseudo-header format
*/
public static boolean hasPseudoHeaderFormat(CharSequence headerName) {
if (headerName instanceof AsciiString) {
final AsciiString asciiHeaderName = (AsciiString) headerName;
return asciiHeaderName.length() > 0 && asciiHeaderName.byteAt(0) == PSEUDO_HEADER_PREFIX_BYTE;
} else {
return headerName.length() > 0 && headerName.charAt(0) == PSEUDO_HEADER_PREFIX;
}
}
/**
* Indicates whether the given header name is a valid HTTP/2 pseudo header.
*/
public static boolean isPseudoHeader(CharSequence header) {
return PSEUDO_HEADERS.contains(header);
}
/**
* Returns the {@link PseudoHeaderName} corresponding to the specified header name.
*
* @return corresponding {@link PseudoHeaderName} if any, {@code null} otherwise.
*/
public static PseudoHeaderName getPseudoHeader(CharSequence header) {
return PSEUDO_HEADERS.get(header);
}
/**
* Indicates whether the pseudo-header is to be used in a request context.
*
* @return {@code true} if the pseudo-header is to be used in a request context
*/
public boolean isRequestOnly() {
return requestOnly;
}
}
/**

View File

@ -71,7 +71,7 @@ public class HpackDecoderTest {
byte[] b = StringUtil.decodeHexDump(encoded);
ByteBuf in = Unpooled.wrappedBuffer(b);
try {
hpackDecoder.decode(0, in, mockHeaders);
hpackDecoder.decode(0, in, mockHeaders, true);
} finally {
in.release();
}
@ -167,7 +167,7 @@ public class HpackDecoderTest {
try {
final long expectedHeaderSize = 4026531870L; // based on the input above
hpackDecoder.setMaxHeaderTableSize(expectedHeaderSize);
hpackDecoder.decode(0, in, mockHeaders);
hpackDecoder.decode(0, in, mockHeaders, true);
assertEquals(expectedHeaderSize, hpackDecoder.getMaxHeaderTableSize());
} finally {
in.release();
@ -180,7 +180,7 @@ public class HpackDecoderTest {
ByteBuf in = Unpooled.wrappedBuffer(input);
try {
hpackDecoder.setMaxHeaderTableSize(4026531870L - 1); // based on the input above ... 1 less than is above.
hpackDecoder.decode(0, in, mockHeaders);
hpackDecoder.decode(0, in, mockHeaders, true);
} finally {
in.release();
}
@ -191,7 +191,7 @@ public class HpackDecoderTest {
byte[] input = {0, (byte) 0x80, 0};
ByteBuf in = Unpooled.wrappedBuffer(input);
try {
hpackDecoder.decode(0, in, mockHeaders);
hpackDecoder.decode(0, in, mockHeaders, true);
verify(mockHeaders, times(1)).add(EMPTY_STRING, EMPTY_STRING);
} finally {
in.release();
@ -203,7 +203,7 @@ public class HpackDecoderTest {
byte[] input = {0, (byte) 0x81, -1};
ByteBuf in = Unpooled.wrappedBuffer(input);
try {
hpackDecoder.decode(0, in, mockHeaders);
hpackDecoder.decode(0, in, mockHeaders, true);
} finally {
in.release();
}
@ -214,7 +214,7 @@ public class HpackDecoderTest {
byte[] input = {0, (byte) 0x84, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
ByteBuf in = Unpooled.wrappedBuffer(input);
try {
hpackDecoder.decode(0, in, mockHeaders);
hpackDecoder.decode(0, in, mockHeaders, true);
} finally {
in.release();
}
@ -225,7 +225,7 @@ public class HpackDecoderTest {
byte[] input = {0, (byte) 0x81, 0};
ByteBuf in = Unpooled.wrappedBuffer(input);
try {
hpackDecoder.decode(0, in, mockHeaders);
hpackDecoder.decode(0, in, mockHeaders, true);
} finally {
in.release();
}
@ -236,9 +236,9 @@ public class HpackDecoderTest {
byte[] compressed = StringUtil.decodeHexDump("FFF0");
ByteBuf in = Unpooled.wrappedBuffer(compressed);
try {
hpackDecoder.decode(0, in, mockHeaders);
hpackDecoder.decode(0, in, mockHeaders, true);
assertEquals(1, in.readableBytes());
hpackDecoder.decode(0, in, mockHeaders);
hpackDecoder.decode(0, in, mockHeaders, true);
} finally {
in.release();
}
@ -432,7 +432,7 @@ public class HpackDecoderTest {
@Test
public void testDecodeLargerThanMaxHeaderListSizeButSmallerThanMaxHeaderListSizeUpdatesDynamicTable()
throws Http2Exception {
throws Http2Exception {
ByteBuf in = Unpooled.buffer(300);
try {
hpackDecoder.setMaxHeaderListSize(200, 300);
@ -451,7 +451,7 @@ public class HpackDecoderTest {
// the decoded headers object should contain all of the headers
Http2Headers decoded = new DefaultHttp2Headers();
try {
hpackDecoder.decode(1, in, decoded);
hpackDecoder.decode(1, in, decoded, true);
fail();
} catch (Http2Exception e) {
assertTrue(e instanceof Http2Exception.HeaderListSizeException);
@ -479,7 +479,7 @@ public class HpackDecoderTest {
hpackEncoder.encodeHeaders(1, in, toEncode, NEVER_SENSITIVE);
Http2Headers decoded = new DefaultHttp2Headers();
hpackDecoder.decode(1, in, decoded);
hpackDecoder.decode(1, in, decoded, true);
assertEquals(2, decoded.size());
} finally {
in.release();
@ -507,7 +507,7 @@ public class HpackDecoderTest {
// ... but decode should fail because we add some overhead for each header entry
expectedException.expect(Http2Exception.HeaderListSizeException.class);
hpackDecoder.decode(1, in, decoded);
hpackDecoder.decode(1, in, decoded, true);
} finally {
in.release();
}
@ -520,7 +520,110 @@ public class HpackDecoderTest {
ByteBuf in = Unpooled.wrappedBuffer(input);
try {
expectedException.expect(Http2Exception.class);
hpackDecoder.decode(0, in, mockHeaders);
hpackDecoder.decode(0, in, mockHeaders, true);
} finally {
in.release();
}
}
@Test
public void unknownPseudoHeader() throws Exception {
ByteBuf in = Unpooled.buffer(200);
try {
HpackEncoder hpackEncoder = new HpackEncoder(true);
Http2Headers toEncode = new DefaultHttp2Headers();
toEncode.add(":test", "1");
hpackEncoder.encodeHeaders(1, in, toEncode, NEVER_SENSITIVE);
Http2Headers decoded = new DefaultHttp2Headers();
expectedException.expect(Http2Exception.class);
hpackDecoder.decode(1, in, decoded, true);
} finally {
in.release();
}
}
@Test
public void disableHeaderValidation() throws Exception {
ByteBuf in = Unpooled.buffer(200);
try {
HpackEncoder hpackEncoder = new HpackEncoder(true);
Http2Headers toEncode = new DefaultHttp2Headers();
toEncode.add(":test", "1");
toEncode.add(":status", "200");
toEncode.add(":method", "GET");
hpackEncoder.encodeHeaders(1, in, toEncode, NEVER_SENSITIVE);
Http2Headers decoded = new DefaultHttp2Headers();
hpackDecoder.decode(1, in, decoded, false);
assertThat(decoded.valueIterator(":test").next().toString(), is("1"));
assertThat(decoded.status().toString(), is("200"));
assertThat(decoded.method().toString(), is("GET"));
} finally {
in.release();
}
}
@Test
public void requestPseudoHeaderInResponse() throws Exception {
ByteBuf in = Unpooled.buffer(200);
try {
HpackEncoder hpackEncoder = new HpackEncoder(true);
Http2Headers toEncode = new DefaultHttp2Headers();
toEncode.add(":status", "200");
toEncode.add(":method", "GET");
hpackEncoder.encodeHeaders(1, in, toEncode, NEVER_SENSITIVE);
Http2Headers decoded = new DefaultHttp2Headers();
expectedException.expect(Http2Exception.class);
hpackDecoder.decode(1, in, decoded, true);
} finally {
in.release();
}
}
@Test
public void responsePseudoHeaderInRequest() throws Exception {
ByteBuf in = Unpooled.buffer(200);
try {
HpackEncoder hpackEncoder = new HpackEncoder(true);
Http2Headers toEncode = new DefaultHttp2Headers();
toEncode.add(":method", "GET");
toEncode.add(":status", "200");
hpackEncoder.encodeHeaders(1, in, toEncode, NEVER_SENSITIVE);
Http2Headers decoded = new DefaultHttp2Headers();
expectedException.expect(Http2Exception.class);
hpackDecoder.decode(1, in, decoded, true);
} finally {
in.release();
}
}
@Test
public void pseudoHeaderAfterRegularHeader() throws Exception {
ByteBuf in = Unpooled.buffer(200);
try {
HpackEncoder hpackEncoder = new HpackEncoder(true);
Http2Headers toEncode = new InOrderHttp2Headers();
toEncode.add("test", "1");
toEncode.add(":method", "GET");
hpackEncoder.encodeHeaders(1, in, toEncode, NEVER_SENSITIVE);
Http2Headers decoded = new DefaultHttp2Headers();
expectedException.expect(Http2Exception.class);
hpackDecoder.decode(1, in, decoded, true);
} finally {
in.release();
}

View File

@ -31,7 +31,7 @@ public class HpackEncoderTest {
private Http2Headers mockHeaders;
@Before
public void setUp() throws Http2Exception {
public void setUp() {
hpackEncoder = new HpackEncoder();
hpackDecoder = new HpackDecoder(DEFAULT_HEADER_LIST_SIZE, 32);
mockHeaders = mock(Http2Headers.class);
@ -42,7 +42,7 @@ public class HpackEncoderTest {
ByteBuf buf = Unpooled.buffer();
hpackEncoder.setMaxHeaderTableSize(buf, MAX_HEADER_TABLE_SIZE);
hpackDecoder.setMaxHeaderTableSize(MAX_HEADER_TABLE_SIZE);
hpackDecoder.decode(0, buf, mockHeaders);
hpackDecoder.decode(0, buf, mockHeaders, true);
assertEquals(MAX_HEADER_TABLE_SIZE, hpackDecoder.getMaxHeaderTableSize());
buf.release();
}

View File

@ -214,7 +214,7 @@ final class HpackTestCase {
try {
List<HpackHeaderField> headers = new ArrayList<HpackHeaderField>();
TestHeaderListener listener = new TestHeaderListener(headers);
hpackDecoder.decode(0, in, listener);
hpackDecoder.decode(0, in, listener, true);
return headers;
} finally {
in.release();

View File

@ -0,0 +1,104 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http2;
import io.netty.handler.codec.CharSequenceValueConverter;
import io.netty.handler.codec.DefaultHeaders;
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
/**
* Http2Headers implementation that preserves headers insertion order.
*/
public class InOrderHttp2Headers
extends DefaultHeaders<CharSequence, CharSequence, Http2Headers> implements Http2Headers {
InOrderHttp2Headers() {
super(CharSequenceValueConverter.INSTANCE);
}
@Override
public boolean equals(Object o) {
return o instanceof Http2Headers && equals((Http2Headers) o, CASE_SENSITIVE_HASHER);
}
@Override
public int hashCode() {
return hashCode(CASE_SENSITIVE_HASHER);
}
@Override
public Http2Headers method(CharSequence value) {
set(PseudoHeaderName.METHOD.value(), value);
return this;
}
@Override
public Http2Headers scheme(CharSequence value) {
set(PseudoHeaderName.SCHEME.value(), value);
return this;
}
@Override
public Http2Headers authority(CharSequence value) {
set(PseudoHeaderName.AUTHORITY.value(), value);
return this;
}
@Override
public Http2Headers path(CharSequence value) {
set(PseudoHeaderName.PATH.value(), value);
return this;
}
@Override
public Http2Headers status(CharSequence value) {
set(PseudoHeaderName.STATUS.value(), value);
return this;
}
@Override
public CharSequence method() {
return get(PseudoHeaderName.METHOD.value());
}
@Override
public CharSequence scheme() {
return get(PseudoHeaderName.SCHEME.value());
}
@Override
public CharSequence authority() {
return get(PseudoHeaderName.AUTHORITY.value());
}
@Override
public CharSequence path() {
return get(PseudoHeaderName.PATH.value());
}
@Override
public CharSequence status() {
return get(PseudoHeaderName.STATUS.value());
}
@Override
public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) {
return contains(name, value, caseInsensitive ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER);
}
}

View File

@ -10,13 +10,6 @@
{ ":path": "/index.html" },
{ ":scheme": "http" },
{ ":scheme": "https" },
{ ":status": "200" },
{ ":status": "204" },
{ ":status": "206" },
{ ":status": "304" },
{ ":status": "400" },
{ ":status": "404" },
{ ":status": "500" },
{ "accept-charset": "" },
{ "accept-encoding": "gzip, deflate" },
{ "accept-language": "" },
@ -66,7 +59,7 @@
{ "www-authenticate": "" }
],
"encoded": [
"8182 8384 8586 8788 898a 8b8c 8d8e 8f90",
"8182 8384 8586 87 8f90",
"9192 9394 9596 9798 999a 9b9c 9d9e 9fa0",
"a1a2 a3a4 a5a6 a7a8 a9aa abac adae afb0",
"b1b2 b3b4 b5b6 b7b8 b9ba bbbc bd"

View File

@ -0,0 +1,23 @@
{
"header_blocks":
[
{
"headers": [
{ ":status": "200" },
{ ":status": "204" },
{ ":status": "206" },
{ ":status": "304" },
{ ":status": "400" },
{ ":status": "404" },
{ ":status": "500" }
],
"encoded": [
"8889 8a8b 8c8d 8e"
],
"dynamic_table": [
],
"table_size": 0
}
]
}

View File

@ -82,7 +82,7 @@ public class HpackDecoderBenchmark extends AbstractMicrobenchmark {
return this;
}
};
hpackDecoder.decode(0, input.duplicate(), headers);
hpackDecoder.decode(0, input.duplicate(), headers, true);
}
private byte[] getSerializedHeaders(Http2Headers headers, boolean sensitive) throws Http2Exception {

View File

@ -72,9 +72,6 @@
<excludeSpec>5.1 - closed: Sends a HEADERS frame</excludeSpec>
<excludeSpec>5.1.1 - Sends stream identifier that is numerically smaller than previous</excludeSpec>
<excludeSpec>7 - Sends a GOAWAY frame with unknown error code</excludeSpec>
<excludeSpec>8.1.2.1 - Sends a HEADERS frame that contains a unknown pseudo-header field</excludeSpec>
<excludeSpec>8.1.2.1 - Sends a HEADERS frame that contains the pseudo-header field defined for response</excludeSpec>
<excludeSpec>8.1.2.1 - Sends a HEADERS frame that contains a pseudo-header field that appears in a header block after a regular header field</excludeSpec>
<excludeSpec>8.1.2.2 - Sends a HEADERS frame that contains the connection-specific header field</excludeSpec>
<excludeSpec>8.1.2.2 - Sends a HEADERS frame that contains the TE header field with any value other than "trailers"</excludeSpec>
<excludeSpec>8.1.2.3 - Sends a HEADERS frame with empty ":path" pseudo-header field</excludeSpec>