Add configurability for max header field size in HPACK
This commit is contained in:
parent
a142c01a1c
commit
ceaa3c86fc
|
|
@ -14,11 +14,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
private int _maxStreamsPerConnection = 100;
|
||||
private int _headerTableSize = (int)Http2PeerSettings.DefaultHeaderTableSize;
|
||||
private int _maxFrameSize = (int)Http2PeerSettings.DefaultMaxFrameSize;
|
||||
private int _maxRequestHeaderFieldSize = 8192;
|
||||
|
||||
/// <summary>
|
||||
/// Limits the number of concurrent request streams per HTTP/2 connection. Excess streams will be refused.
|
||||
/// <para>
|
||||
/// Defaults to 100
|
||||
/// Value must be greater than 0, defaults to 100
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public int MaxStreamsPerConnection
|
||||
|
|
@ -74,5 +75,25 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
_maxFrameSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the size of the maximum allowed size of a request header field sequence. This limit applies to both name and value sequences in their compressed and uncompressed representations.
|
||||
/// <para>
|
||||
/// Value must be greater than 0, defaults to 8192
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public int MaxRequestHeaderFieldSize
|
||||
{
|
||||
get => _maxRequestHeaderFieldSize;
|
||||
set
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), value, CoreStrings.GreaterThanZeroRequired);
|
||||
}
|
||||
|
||||
_maxRequestHeaderFieldSize = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
|||
DynamicTableSizeUpdate
|
||||
}
|
||||
|
||||
// TODO: add new configurable limit
|
||||
public const int MaxStringOctets = 4096;
|
||||
|
||||
// http://httpwg.org/specs/rfc7541.html#rfc.section.6.1
|
||||
// 0 1 2 3 4 5 6 7
|
||||
// +---+---+---+---+---+---+---+---+
|
||||
|
|
@ -83,9 +80,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
|||
private readonly int _maxDynamicTableSize;
|
||||
private readonly DynamicTable _dynamicTable;
|
||||
private readonly IntegerDecoder _integerDecoder = new IntegerDecoder();
|
||||
private readonly byte[] _stringOctets = new byte[MaxStringOctets];
|
||||
private readonly byte[] _headerNameOctets = new byte[MaxStringOctets];
|
||||
private readonly byte[] _headerValueOctets = new byte[MaxStringOctets];
|
||||
private readonly byte[] _stringOctets;
|
||||
private readonly byte[] _headerNameOctets;
|
||||
private readonly byte[] _headerValueOctets;
|
||||
|
||||
private State _state = State.Ready;
|
||||
private byte[] _headerName;
|
||||
|
|
@ -97,17 +94,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
|||
private bool _huffman;
|
||||
private bool _headersObserved;
|
||||
|
||||
public HPackDecoder(int maxDynamicTableSize)
|
||||
: this(maxDynamicTableSize, new DynamicTable(maxDynamicTableSize))
|
||||
{
|
||||
_maxDynamicTableSize = maxDynamicTableSize;
|
||||
}
|
||||
public HPackDecoder(int maxDynamicTableSize, int maxRequestHeaderFieldSize)
|
||||
: this(maxDynamicTableSize, maxRequestHeaderFieldSize, new DynamicTable(maxDynamicTableSize)) { }
|
||||
|
||||
// For testing.
|
||||
internal HPackDecoder(int maxDynamicTableSize, DynamicTable dynamicTable)
|
||||
internal HPackDecoder(int maxDynamicTableSize, int maxRequestHeaderFieldSize, DynamicTable dynamicTable)
|
||||
{
|
||||
_maxDynamicTableSize = maxDynamicTableSize;
|
||||
_dynamicTable = dynamicTable;
|
||||
|
||||
_stringOctets = new byte[maxRequestHeaderFieldSize];
|
||||
_headerNameOctets = new byte[maxRequestHeaderFieldSize];
|
||||
_headerValueOctets = new byte[maxRequestHeaderFieldSize];
|
||||
}
|
||||
|
||||
public void Decode(ReadOnlySequence<byte> data, bool endHeaders, IHttpHeadersHandler handler)
|
||||
|
|
|
|||
|
|
@ -86,13 +86,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
public Http2Connection(Http2ConnectionContext context)
|
||||
{
|
||||
var httpLimits = context.ServiceContext.ServerOptions.Limits;
|
||||
var http2Limits = httpLimits.Http2;
|
||||
|
||||
_context = context;
|
||||
_frameWriter = new Http2FrameWriter(context.Transport.Output, context.ConnectionContext, _outputFlowControl, this, context.ConnectionId, context.ServiceContext.Log);
|
||||
_serverSettings.MaxConcurrentStreams = (uint)context.ServiceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection;
|
||||
_serverSettings.MaxFrameSize = (uint)context.ServiceContext.ServerOptions.Limits.Http2.MaxFrameSize;
|
||||
_serverSettings.HeaderTableSize = (uint)context.ServiceContext.ServerOptions.Limits.Http2.HeaderTableSize;
|
||||
_hpackDecoder = new HPackDecoder((int)_serverSettings.HeaderTableSize);
|
||||
_serverSettings.MaxHeaderListSize = (uint)context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize;
|
||||
_serverSettings.MaxConcurrentStreams = (uint)http2Limits.MaxStreamsPerConnection;
|
||||
_serverSettings.MaxFrameSize = (uint)http2Limits.MaxFrameSize;
|
||||
_serverSettings.HeaderTableSize = (uint)http2Limits.HeaderTableSize;
|
||||
_hpackDecoder = new HPackDecoder(http2Limits.HeaderTableSize, http2Limits.MaxRequestHeaderFieldSize);
|
||||
_serverSettings.MaxHeaderListSize = (uint)httpLimits.MaxRequestHeadersTotalSize;
|
||||
}
|
||||
|
||||
public string ConnectionId => _context.ConnectionId;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
public class HPackDecoderTests : IHttpHeadersHandler
|
||||
{
|
||||
private const int DynamicTableInitialMaxSize = 4096;
|
||||
private const int MaxRequestHeaderFieldSize = 8192;
|
||||
|
||||
// Indexed Header Field Representation - Static Table - Index 2 (:method: GET)
|
||||
private static readonly byte[] _indexedHeaderStatic = new byte[] { 0x82 };
|
||||
|
|
@ -94,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
public HPackDecoderTests()
|
||||
{
|
||||
_dynamicTable = new DynamicTable(DynamicTableInitialMaxSize);
|
||||
_decoder = new HPackDecoder(DynamicTableInitialMaxSize, _dynamicTable);
|
||||
_decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxRequestHeaderFieldSize, _dynamicTable);
|
||||
}
|
||||
|
||||
void IHttpHeadersHandler.OnHeader(Span<byte> name, Span<byte> value)
|
||||
|
|
@ -438,14 +439,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
public void DecodesStringLength_GreaterThanLimit_Error()
|
||||
{
|
||||
var encoded = _literalHeaderFieldWithoutIndexingNewName
|
||||
.Concat(new byte[] { 0xff, 0x82, 0x1f }) // 4097 encoded with 7-bit prefix
|
||||
.Concat(new byte[] { 0xff, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix
|
||||
.ToArray();
|
||||
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.FormatHPackStringLengthTooLarge(4097, HPackDecoder.MaxStringOctets), exception.Message);
|
||||
Assert.Equal(CoreStrings.FormatHPackStringLengthTooLarge(MaxRequestHeaderFieldSize + 1, MaxRequestHeaderFieldSize), exception.Message);
|
||||
Assert.Empty(_decodedHeaders);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DecodesStringLength_LimitConfigurable()
|
||||
{
|
||||
var decoder = new HPackDecoder(DynamicTableInitialMaxSize, MaxRequestHeaderFieldSize + 1);
|
||||
var string8193 = new string('a', MaxRequestHeaderFieldSize + 1);
|
||||
|
||||
var encoded = _literalHeaderFieldWithoutIndexingNewName
|
||||
.Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding
|
||||
.Concat(Encoding.ASCII.GetBytes(string8193))
|
||||
.Concat(new byte[] { 0x7f, 0x82, 0x3f }) // 8193 encoded with 7-bit prefix, no Huffman encoding
|
||||
.Concat(Encoding.ASCII.GetBytes(string8193))
|
||||
.ToArray();
|
||||
|
||||
decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this);
|
||||
|
||||
Assert.Equal(string8193, _decodedHeaders[string8193]);
|
||||
}
|
||||
|
||||
public static readonly TheoryData<byte[]> _incompleteHeaderBlockData = new TheoryData<byte[]>
|
||||
{
|
||||
// Indexed Header Field Representation - incomplete index encoding
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1 << 14 - 1)]
|
||||
[InlineData((1 << 14) - 1)]
|
||||
[InlineData(1 << 24)]
|
||||
[InlineData(-1)]
|
||||
public void Http2MaxFrameSizeInvalid(int value)
|
||||
|
|
@ -331,12 +331,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(4097)]
|
||||
[InlineData(int.MinValue)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(0)]
|
||||
public void Http2HeaderTableSizeInvalid(int value)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits().Http2.MaxFrameSize = value);
|
||||
Assert.Contains("A value between", ex.Message);
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits().Http2.HeaderTableSize = value);
|
||||
Assert.StartsWith(CoreStrings.GreaterThanZeroRequired, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Http2MaxRequestHeaderFieldSizeDefault()
|
||||
{
|
||||
Assert.Equal(8192, new KestrelServerLimits().Http2.MaxRequestHeaderFieldSize);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(int.MinValue)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(0)]
|
||||
public void Http2MaxRequestHeaderFieldSizeInvalid(int value)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => new KestrelServerLimits().Http2.MaxRequestHeaderFieldSize = value);
|
||||
Assert.StartsWith(CoreStrings.GreaterThanZeroRequired, ex.Message);
|
||||
}
|
||||
|
||||
public static TheoryData<TimeSpan> TimeoutValidData => new TheoryData<TimeSpan>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
public class Http2TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable, IHttpHeadersHandler
|
||||
{
|
||||
protected static readonly string _4kHeaderValue = new string('a', HPackDecoder.MaxStringOctets);
|
||||
protected static readonly int MaxRequestHeaderFieldSize = 8192;
|
||||
protected static readonly string _4kHeaderValue = new string('a', 4096);
|
||||
|
||||
protected static readonly IEnumerable<KeyValuePair<string, string>> _browserRequestHeaders = new[]
|
||||
{
|
||||
|
|
@ -99,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
);
|
||||
|
||||
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
|
||||
_hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize);
|
||||
_hpackDecoder = new HPackDecoder((int)_clientSettings.HeaderTableSize, MaxRequestHeaderFieldSize);
|
||||
|
||||
_noopApplication = context => Task.CompletedTask;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue