Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -596,10 +596,16 @@ int Decode(ref byte[] dst)
{
if (_huffman)
{
return Huffman.Decode(new ReadOnlySpan<byte>(_stringOctets, 0, _stringLength), ref dst);
int decodedLength = Huffman.Decode(new ReadOnlySpan<byte>(_stringOctets, 0, _stringLength), ref dst);
if (decodedLength > _maxHeadersLength)
{
throw new HPackDecodingException(SR.Format(SR.net_http_headers_exceeded_length, _maxHeadersLength));
}
return decodedLength;
}
else
{
Debug.Assert(_stringLength <= _maxHeadersLength, "String length should have been checked prior to decode.");
EnsureStringCapacity(ref dst);
Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength);
return _stringLength;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,10 +634,16 @@ int Decode(ref byte[]? dst)

if (_huffman)
{
return Huffman.Decode(new ReadOnlySpan<byte>(_stringOctets, 0, _stringLength), ref dst);
int decodedLength = Huffman.Decode(new ReadOnlySpan<byte>(_stringOctets, 0, _stringLength), ref dst);
if (decodedLength > _maxHeadersLength)
{
throw new QPackDecodingException(SR.Format(SR.net_http_headers_exceeded_length, _maxHeadersLength));
}
return decodedLength;
}
else
{
Debug.Assert(_stringLength <= _maxHeadersLength, "String length should have been checked prior to decode.");
Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength);
return _stringLength;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,45 @@ public void DecodesStringLength_GreaterThanLimit_Error()
Assert.Empty(_handler.DecodedHeaders);
}

[Fact]
public void HuffmanDecodedHeaderName_ExceedsLimitAfterDecoding_Throws()
{
// '0' (ASCII 48) has a 5-bit Huffman code (00000).
// 16 '0' characters = 80 Huffman bits = 10 encoded bytes, but decodes to 16 bytes.
// Encoded length (10) passes the pre-decode check (<= 10), but decoded length (16) exceeds the limit.
HPackDecoder decoder = new HPackDecoder(DynamicTableInitialMaxSize, maxHeadersLength: 10);

byte[] encoded = [
0x00, // Literal header field without indexing, new name
0x8a, // Huffman flag set (0x80) + string length 10 (7-bit prefix)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10 bytes Huffman -> 16 '0' chars
0x01, // String length = 1 (no Huffman)
0x61 // Value = "a"
];

HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => decoder.Decode(encoded, endHeaders: true, handler: _handler));
Assert.Equal(SR.Format(SR.net_http_headers_exceeded_length, 10), exception.Message);
Assert.Empty(_handler.DecodedHeaders);
}

[Fact]
public void HuffmanDecodedHeaderValue_ExceedsLimitAfterDecoding_Throws()
{
HPackDecoder decoder = new HPackDecoder(DynamicTableInitialMaxSize, maxHeadersLength: 10);

byte[] encoded = [
0x00, // Literal header field without indexing, new name
0x01, // String length = 1 (no Huffman)
0x61, // Name = "a"
0x8a, // Huffman flag set (0x80) + string length 10 (7-bit prefix)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 10 bytes Huffman -> 16 '0' chars
];

HPackDecodingException exception = Assert.Throws<HPackDecodingException>(() => decoder.Decode(encoded, endHeaders: true, handler: _handler));
Assert.Equal(SR.Format(SR.net_http_headers_exceeded_length, 10), exception.Message);
Assert.Empty(_handler.DecodedHeaders);
}

[Fact]
public void DecodesStringLength_LimitConfigurable()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,49 @@ public void LiteralFieldWithoutNameReference_ValueBrokenIntoSeparateBuffers()
Assert.Equal(_headerValueString, _handler.DecodedHeaders[_literalHeaderFieldString]);
}

[Fact]
public void HuffmanDecodedHeaderName_ExceedsLimitAfterDecoding_Throws()
{
// '0' (ASCII 48) has a 5-bit Huffman code (00000).
// 16 '0' characters = 80 Huffman bits = 10 encoded bytes, but decodes to 16 bytes.
// Encoded length (10) passes the pre-decode check (<= 10), but decoded length (16) exceeds the limit.
using QPackDecoder decoder = new QPackDecoder(maxHeadersLength: 10);
TestHttpHeadersHandler handler = new TestHttpHeadersHandler();

byte[] encoded = new byte[]
{
0x00, 0x00, // Required insert count (0) and base (0)
0x2f, 0x03, // Literal field with literal name: N=0, H=1, name length = 10 (3-bit prefix: 7 + 3)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 10 bytes Huffman -> 16 '0' chars
0x01, // Value length = 1 (no Huffman)
0x61 // Value = "a"
};

QPackDecodingException exception = Assert.Throws<QPackDecodingException>(() => decoder.Decode(encoded, endHeaders: true, handler: handler));
Assert.Equal(SR.Format(SR.net_http_headers_exceeded_length, 10), exception.Message);
Assert.Empty(handler.DecodedHeaders);
}

[Fact]
public void HuffmanDecodedHeaderValue_ExceedsLimitAfterDecoding_Throws()
{
using QPackDecoder decoder = new QPackDecoder(maxHeadersLength: 10);
TestHttpHeadersHandler handler = new TestHttpHeadersHandler();

byte[] encoded = new byte[]
{
0x00, 0x00, // Required insert count (0) and base (0)
0x21, // Literal field with literal name: N=0, H=0, name length = 1 (3-bit prefix)
0x61, // Name = "a"
0x8a, // Huffman flag set (0x80) + value length 10 (7-bit prefix)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 10 bytes Huffman -> 16 '0' chars
};

QPackDecodingException exception = Assert.Throws<QPackDecodingException>(() => decoder.Decode(encoded, endHeaders: true, handler: handler));
Assert.Equal(SR.Format(SR.net_http_headers_exceeded_length, 10), exception.Message);
Assert.Empty(handler.DecodedHeaders);
}

public static readonly TheoryData<byte[]> _incompleteHeaderBlockData = new TheoryData<byte[]>
{
// Incomplete header
Expand Down
Loading