diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java index 855430466f..3ee5926498 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/hpack/HPackDecoder.java @@ -267,11 +267,11 @@ HPackHeader decodeIndexedHeader(final ByteBuffer src) throws HPackException { } public Header decodeHeader(final ByteBuffer src) throws HPackException { - final HPackHeader header = decodeHPackHeader(src); + final HPackHeader header = decodeHPackHeader(src, true); return header != null ? new BasicHeader(header.getName(), header.getValue(), header.isSensitive()) : null; } - HPackHeader decodeHPackHeader(final ByteBuffer src) throws HPackException { + HPackHeader decodeHPackHeader(final ByteBuffer src, final boolean allowTableSizeUpdate) throws HPackException { try { while (src.hasRemaining()) { final int b = peekByte(src); @@ -284,6 +284,9 @@ HPackHeader decodeHPackHeader(final ByteBuffer src) throws HPackException { } else if ((b & 0xf0) == 0x10) { return decodeLiteralHeader(src, HPackRepresentation.NEVER_INDEXED); } else if ((b & 0xe0) == 0x20) { + if (!allowTableSizeUpdate) { + throw new HPackException("Dynamic table size update must appear at the beginning of a header block"); + } final int maxSize = decodeInt(src, 5); if (maxSize > this.maxTableSize) { throw new HPackException("Requested dynamic header table size exceeds maximum size: " + maxSize); @@ -302,13 +305,17 @@ HPackHeader decodeHPackHeader(final ByteBuffer src) throws HPackException { public List
decodeHeaders(final ByteBuffer src) throws HPackException { final boolean enforceSizeLimit = maxListSize < Integer.MAX_VALUE; int listSize = 0; + // RFC 7541 ยง4.2: dynamic table size updates are only allowed at the + // beginning of a header block (before the first header field). + boolean allowTableSizeUpdate = true; final List
list = new ArrayList<>(); while (src.hasRemaining()) { - final HPackHeader header = decodeHPackHeader(src); + final HPackHeader header = decodeHPackHeader(src, allowTableSizeUpdate); if (header == null) { break; } + allowTableSizeUpdate = false; if (enforceSizeLimit) { listSize += header.getTotalSize(); if (listSize >= maxListSize) { diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java index fde738a9d4..de72c0cd90 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/hpack/TestHPackCoding.java @@ -1178,5 +1178,51 @@ void decoderDynamicHeaderTableMaxSizeLimitedByConfig() throws Exception { Assertions.assertThrows(HPackException.class, () -> decoder.decodeHeaders(wrap(buf))); } + @Test + void decoderRejectsDynamicTableSizeUpdateMidHeaderBlock() { + final InboundDynamicTable dynamicTable = new InboundDynamicTable(4096); + final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); + + final ByteBuffer src = createByteBuffer( + 0x82, // :method: GET + 0x20 // dynamic table size update (0) - illegal after first header field + ); + + final HPackException ex = Assertions.assertThrows(HPackException.class, () -> decoder.decodeHeaders(src)); + Assertions.assertTrue(ex.getMessage().contains("beginning of a header block")); + } + + @Test + void decoderAllowsMultipleLeadingDynamicTableSizeUpdates() throws Exception { + final InboundDynamicTable dynamicTable = new InboundDynamicTable(4096); + final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); + + final ByteBuffer src = createByteBuffer( + 0x20, // dynamic table size update (0) + 0x2a, // dynamic table size update (10) + 0x82 // :method: GET + ); + + final List
headers = decoder.decodeHeaders(src); + Assertions.assertEquals(1, headers.size()); + assertHeaderEquals(new BasicHeader(":method", "GET"), headers.get(0)); + Assertions.assertEquals(10, dynamicTable.getMaxSize()); + } + + @Test + void decoderRejectsDynamicTableSizeUpdateAfterLeadingUpdateAndHeader() { + final InboundDynamicTable dynamicTable = new InboundDynamicTable(4096); + final HPackDecoder decoder = new HPackDecoder(dynamicTable, StandardCharsets.US_ASCII); + + final ByteBuffer src = createByteBuffer( + 0x20, // dynamic table size update (0) + 0x82, // :method: GET + 0x20 // dynamic table size update (0) - illegal after first header field + ); + + final HPackException ex = Assertions.assertThrows(HPackException.class, () -> decoder.decodeHeaders(src)); + Assertions.assertTrue(ex.getMessage().contains("beginning of a header block")); + } + }