Implement MaxRequestHeadersTotalSize for HTTP/2 #2812
This commit is contained in:
parent
edc1935475
commit
384a518bda
|
|
@ -75,6 +75,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
private RequestHeaderParsingState _requestHeaderParsingState;
|
||||
private PseudoHeaderFields _parsedPseudoHeaderFields;
|
||||
private Http2HeadersFrameFlags _headerFlags;
|
||||
private int _totalParsedHeaderSize;
|
||||
private bool _isMethodConnect;
|
||||
private readonly object _stateLock = new object();
|
||||
private int _highestOpenedStreamId;
|
||||
|
|
@ -92,6 +93,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
_serverSettings.HeaderTableSize = (uint)context.ServiceContext.ServerOptions.Limits.Http2.HeaderTableSize;
|
||||
_hpackDecoder = new HPackDecoder((int)_serverSettings.HeaderTableSize);
|
||||
_incomingFrame = new Http2Frame(_serverSettings.MaxFrameSize);
|
||||
_serverSettings.MaxHeaderListSize = (uint)context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize;
|
||||
}
|
||||
|
||||
public string ConnectionId => _context.ConnectionId;
|
||||
|
|
@ -888,6 +890,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
_parsedPseudoHeaderFields = PseudoHeaderFields.None;
|
||||
_headerFlags = Http2HeadersFrameFlags.NONE;
|
||||
_isMethodConnect = false;
|
||||
_totalParsedHeaderSize = 0;
|
||||
}
|
||||
|
||||
private void ThrowIfIncomingFrameSentToIdleStream()
|
||||
|
|
@ -934,7 +937,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
Input.CancelPendingRead();
|
||||
}
|
||||
|
||||
|
||||
if (_state != Http2ConnectionState.Open)
|
||||
{
|
||||
// Complete the task waiting on all streams to finish
|
||||
|
|
@ -944,15 +946,57 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
}
|
||||
}
|
||||
|
||||
// We can't throw a Http2StreamErrorException here, it interrupts the header decompression state and may corrupt subsequent header frames on other streams.
|
||||
// For now these either need to be connection errors or BadRequests. If we want to downgrade any of them to stream errors later then we need to
|
||||
// rework the flow so that the remaining headers are drained and the decompression state is maintained.
|
||||
public void OnHeader(Span<byte> name, Span<byte> value)
|
||||
{
|
||||
// https://tools.ietf.org/html/rfc7540#section-6.5.2
|
||||
// "The value is based on the uncompressed size of header fields, including the length of the name and value in octets plus an overhead of 32 octets for each header field.";
|
||||
_totalParsedHeaderSize += 32 + name.Length + value.Length;
|
||||
if (_totalParsedHeaderSize > _context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize)
|
||||
{
|
||||
throw new Http2ConnectionErrorException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
ValidateHeader(name, value);
|
||||
_currentHeadersStream.OnHeader(name, value);
|
||||
try
|
||||
{
|
||||
// Drop trailers for now. Adding them to the request headers is not thread safe.
|
||||
// https://github.com/aspnet/KestrelHttpServer/issues/2051
|
||||
if (_requestHeaderParsingState != RequestHeaderParsingState.Trailers)
|
||||
{
|
||||
// Throws BadReqeust for header count limit breaches.
|
||||
// Throws InvalidOperation for bad encoding.
|
||||
_currentHeadersStream.OnHeader(name, value);
|
||||
}
|
||||
}
|
||||
catch (BadHttpRequestException bre)
|
||||
{
|
||||
throw new Http2ConnectionErrorException(bre.Message, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
throw new Http2ConnectionErrorException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateHeader(Span<byte> name, Span<byte> value)
|
||||
{
|
||||
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.1
|
||||
/*
|
||||
Intermediaries that process HTTP requests or responses (i.e., any
|
||||
intermediary not acting as a tunnel) MUST NOT forward a malformed
|
||||
request or response. Malformed requests or responses that are
|
||||
detected MUST be treated as a stream error (Section 5.4.2) of type
|
||||
PROTOCOL_ERROR.
|
||||
|
||||
For malformed requests, a server MAY send an HTTP response prior to
|
||||
closing or resetting the stream. 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.*/
|
||||
if (IsPseudoHeaderField(name, out var headerField))
|
||||
{
|
||||
if (_requestHeaderParsingState == RequestHeaderParsingState.Headers)
|
||||
|
|
@ -960,7 +1004,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
// 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).
|
||||
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
||||
|
|
@ -975,21 +1019,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
{
|
||||
// Endpoints MUST treat a request or response that contains undefined or invalid pseudo-header
|
||||
// fields as malformed (Section 8.1.2.6).
|
||||
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorUnknownPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorUnknownPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
if (headerField == PseudoHeaderFields.Status)
|
||||
{
|
||||
// Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header fields
|
||||
// defined for responses MUST NOT appear in requests.
|
||||
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorResponsePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorResponsePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
if ((_parsedPseudoHeaderFields & headerField) == headerField)
|
||||
{
|
||||
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.3
|
||||
// All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header fields
|
||||
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorDuplicatePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorDuplicatePseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
if (headerField == PseudoHeaderFields.Method)
|
||||
|
|
@ -1006,7 +1050,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
if (IsConnectionSpecificHeaderField(name, value))
|
||||
{
|
||||
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorConnectionSpecificHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorConnectionSpecificHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2
|
||||
|
|
@ -1021,7 +1065,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
}
|
||||
else
|
||||
{
|
||||
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,10 +51,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||
new KeyValuePair<string, string>("a", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _largeHeaderValue)
|
||||
new KeyValuePair<string, string>("a", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _4kHeaderValue)
|
||||
};
|
||||
|
||||
private static readonly IEnumerable<KeyValuePair<string, string>> _twoContinuationsRequestHeaders = new[]
|
||||
|
|
@ -63,14 +63,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||
new KeyValuePair<string, string>("a", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("e", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("f", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("g", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("h", _largeHeaderValue)
|
||||
new KeyValuePair<string, string>("a", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("e", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("f", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("g", _4kHeaderValue),
|
||||
};
|
||||
|
||||
private static readonly byte[] _helloBytes = Encoding.ASCII.GetBytes("hello");
|
||||
|
|
@ -102,7 +101,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_connectionContext.ServiceContext.ServerOptions.Limits.Http2.MaxFrameSize = length;
|
||||
_connection = new Http2Connection(_connectionContext);
|
||||
|
||||
await InitializeConnectionAsync(_echoApplication, expectedSettingsLegnth: 12);
|
||||
await InitializeConnectionAsync(_echoApplication, expectedSettingsCount: 3);
|
||||
|
||||
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
|
||||
await SendDataAsync(1, new byte[length].AsSpan(), endStream: true);
|
||||
|
|
@ -1067,7 +1066,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task HEADERS_Received_WithTrailers_Decoded(bool sendData)
|
||||
public async Task HEADERS_Received_WithTrailers_Discarded(bool sendData)
|
||||
{
|
||||
await InitializeConnectionAsync(_readTrailersApplication);
|
||||
|
||||
|
|
@ -1105,7 +1104,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
VerifyDecodedRequestHeaders(_browserRequestHeaders.Concat(_requestTrailers));
|
||||
VerifyDecodedRequestHeaders(_browserRequestHeaders);
|
||||
|
||||
// Make sure the trailers are missing. https://github.com/aspnet/KestrelHttpServer/issues/2630
|
||||
foreach (var header in _requestTrailers)
|
||||
{
|
||||
Assert.False(_receivedHeaders.ContainsKey(header.Key));
|
||||
}
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
|
@ -1454,27 +1459,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(UpperCaseHeaderNameData))]
|
||||
public async Task HEADERS_Received_HeaderNameContainsUpperCaseCharacter_StreamError(byte[] headerBlock)
|
||||
public async Task HEADERS_Received_HeaderNameContainsUpperCaseCharacter_ConnectionError(byte[] headerBlock)
|
||||
{
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
|
||||
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headerBlock);
|
||||
await WaitForStreamErrorAsync(
|
||||
expectedStreamId: 1,
|
||||
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
|
||||
expectedErrorMessage: CoreStrings.Http2ErrorHeaderNameUppercase);
|
||||
|
||||
// Verify that the stream ID can't be re-used
|
||||
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
|
||||
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
|
||||
ignoreNonGoAwayFrames: false,
|
||||
expectedLastStreamId: 1,
|
||||
expectedErrorCode: Http2ErrorCode.STREAM_CLOSED,
|
||||
expectedErrorMessage: CoreStrings.FormatHttp2ErrorStreamClosed(Http2FrameType.HEADERS, streamId: 1));
|
||||
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
|
||||
expectedErrorMessage: CoreStrings.Http2ErrorHeaderNameUppercase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task HEADERS_Received_HeaderBlockContainsUnknownPseudoHeaderField_StreamError()
|
||||
public Task HEADERS_Received_HeaderBlockContainsUnknownPseudoHeaderField_ConnectionError()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
|
|
@ -1484,11 +1482,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(":unknown", "0"),
|
||||
};
|
||||
|
||||
return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorUnknownPseudoHeaderField);
|
||||
return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, expectedErrorMessage: CoreStrings.Http2ErrorUnknownPseudoHeaderField);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task HEADERS_Received_HeaderBlockContainsResponsePseudoHeaderField_StreamError()
|
||||
public Task HEADERS_Received_HeaderBlockContainsResponsePseudoHeaderField_ConnectionError()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
|
|
@ -1498,21 +1496,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(HeaderNames.Status, "200"),
|
||||
};
|
||||
|
||||
return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorResponsePseudoHeaderField);
|
||||
return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, expectedErrorMessage: CoreStrings.Http2ErrorResponsePseudoHeaderField);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DuplicatePseudoHeaderFieldData))]
|
||||
public Task HEADERS_Received_HeaderBlockContainsDuplicatePseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
|
||||
public Task HEADERS_Received_HeaderBlockContainsDuplicatePseudoHeaderField_ConnectionError(IEnumerable<KeyValuePair<string, string>> headers)
|
||||
{
|
||||
return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorDuplicatePseudoHeaderField);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MissingPseudoHeaderFieldData))]
|
||||
public Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
|
||||
{
|
||||
return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields);
|
||||
return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, expectedErrorMessage: CoreStrings.Http2ErrorDuplicatePseudoHeaderField);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -1537,20 +1528,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(PseudoHeaderFieldAfterRegularHeadersData))]
|
||||
public Task HEADERS_Received_HeaderBlockContainsPseudoHeaderFieldAfterRegularHeaders_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
|
||||
public Task HEADERS_Received_HeaderBlockContainsPseudoHeaderFieldAfterRegularHeaders_ConnectionError(IEnumerable<KeyValuePair<string, string>> headers)
|
||||
{
|
||||
return HEADERS_Received_InvalidHeaderFields_StreamError(headers, expectedErrorMessage: CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders);
|
||||
return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, expectedErrorMessage: CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders);
|
||||
}
|
||||
|
||||
private async Task HEADERS_Received_InvalidHeaderFields_StreamError(IEnumerable<KeyValuePair<string, string>> headers, string expectedErrorMessage)
|
||||
private async Task HEADERS_Received_InvalidHeaderFields_ConnectionError(IEnumerable<KeyValuePair<string, string>> headers, string expectedErrorMessage)
|
||||
{
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
await StartStreamAsync(1, headers, endStream: true);
|
||||
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
|
||||
ignoreNonGoAwayFrames: false,
|
||||
expectedLastStreamId: 1,
|
||||
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
|
||||
expectedErrorMessage: expectedErrorMessage);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MissingPseudoHeaderFieldData))]
|
||||
public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
|
||||
{
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
|
||||
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers);
|
||||
await WaitForStreamErrorAsync(
|
||||
expectedStreamId: 1,
|
||||
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
|
||||
expectedErrorMessage: expectedErrorMessage);
|
||||
expectedStreamId: 1,
|
||||
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
|
||||
expectedErrorMessage: CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields);
|
||||
|
||||
// Verify that the stream ID can't be re-used
|
||||
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
|
||||
|
|
@ -1562,7 +1566,62 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public Task HEADERS_Received_HeaderBlockContainsConnectionHeader_StreamError()
|
||||
public Task HEADERS_Received_HeaderBlockOverLimit_ConnectionError()
|
||||
{
|
||||
// > 32kb
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>("a", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("e", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("f", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("g", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("h", _4kHeaderValue),
|
||||
};
|
||||
|
||||
return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.BadRequest_HeadersExceedMaxTotalSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task HEADERS_Received_TooManyHeaders_ConnectionError()
|
||||
{
|
||||
// > MaxRequestHeaderCount (100
|
||||
var headers = new List<KeyValuePair<string, string>>();
|
||||
headers.AddRange(new []
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
});
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
headers.Add(new KeyValuePair<string, string>(i.ToString(), i.ToString()));
|
||||
}
|
||||
|
||||
return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.BadRequest_TooManyHeaders);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task HEADERS_Received_InvalidCharacters_ConnectionError()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
new KeyValuePair<string, string>(HeaderNames.Method, "GET"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>("Custom", "val\0ue"),
|
||||
};
|
||||
|
||||
return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.BadRequest_MalformedRequestInvalidHeaders);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task HEADERS_Received_HeaderBlockContainsConnectionHeader_ConnectionError()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
|
|
@ -1572,11 +1631,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>("connection", "keep-alive")
|
||||
};
|
||||
|
||||
return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField);
|
||||
return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsNotTrailers_StreamError()
|
||||
public Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsNotTrailers_ConnectionError()
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
|
|
@ -1586,7 +1645,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>("te", "trailers, deflate")
|
||||
};
|
||||
|
||||
return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField);
|
||||
return HEADERS_Received_InvalidHeaderFields_ConnectionError(headers, CoreStrings.Http2ErrorConnectionSpecificHeaderField);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -2005,18 +2064,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await SendSettingsAsync();
|
||||
|
||||
var frame = await ExpectAsync(Http2FrameType.SETTINGS,
|
||||
withLength: 6,
|
||||
withLength: 6 * 2,
|
||||
withFlags: 0,
|
||||
withStreamId: 0);
|
||||
|
||||
// Only non protocol defaults are sent
|
||||
Assert.Equal(1, frame.SettingsCount);
|
||||
Assert.Equal(2, frame.SettingsCount);
|
||||
var settings = frame.GetSettings();
|
||||
Assert.Equal(1, settings.Count);
|
||||
Assert.Equal(2, settings.Count);
|
||||
|
||||
var setting = settings[0];
|
||||
Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_CONCURRENT_STREAMS, setting.Parameter);
|
||||
Assert.Equal(100u, setting.Value);
|
||||
|
||||
setting = settings[1];
|
||||
Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_HEADER_LIST_SIZE, setting.Parameter);
|
||||
Assert.Equal(32 * 1024u, setting.Value);
|
||||
|
||||
await ExpectAsync(Http2FrameType.SETTINGS,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2SettingsFrameFlags.ACK,
|
||||
|
|
@ -2029,6 +2093,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
public async Task SETTINGS_Custom_Sent()
|
||||
{
|
||||
_connection.ServerSettings.MaxConcurrentStreams = 1;
|
||||
_connection.ServerSettings.MaxHeaderListSize = 4 * 1024;
|
||||
|
||||
_connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(_noopApplication));
|
||||
|
||||
|
|
@ -2036,18 +2101,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await SendSettingsAsync();
|
||||
|
||||
var frame = await ExpectAsync(Http2FrameType.SETTINGS,
|
||||
withLength: 6,
|
||||
withLength: 6 * 2,
|
||||
withFlags: 0,
|
||||
withStreamId: 0);
|
||||
|
||||
// Only non protocol defaults are sent
|
||||
Assert.Equal(1, frame.SettingsCount);
|
||||
Assert.Equal(2, frame.SettingsCount);
|
||||
var settings = frame.GetSettings();
|
||||
Assert.Equal(1, settings.Count);
|
||||
Assert.Equal(2, settings.Count);
|
||||
|
||||
var setting = settings[0];
|
||||
Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_CONCURRENT_STREAMS, setting.Parameter);
|
||||
Assert.Equal(1u, setting.Value);
|
||||
|
||||
setting = settings[1];
|
||||
Assert.Equal(Http2SettingsParameter.SETTINGS_MAX_HEADER_LIST_SIZE, setting.Parameter);
|
||||
Assert.Equal(4 * 1024u, setting.Value);
|
||||
|
||||
await ExpectAsync(Http2FrameType.SETTINGS,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2SettingsFrameFlags.ACK,
|
||||
|
|
@ -2945,7 +3015,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task CONTINUATION_Received_WithTrailers_Decoded(bool sendData)
|
||||
public async Task CONTINUATION_Received_WithTrailers_Discarded(bool sendData)
|
||||
{
|
||||
await InitializeConnectionAsync(_readTrailersApplication);
|
||||
|
||||
|
|
@ -2994,11 +3064,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
VerifyDecodedRequestHeaders(_browserRequestHeaders.Concat(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("trailer-1", "1"),
|
||||
new KeyValuePair<string, string>("trailer-2", "2")
|
||||
}));
|
||||
VerifyDecodedRequestHeaders(_browserRequestHeaders);
|
||||
|
||||
// Make sure the trailers are missing. https://github.com/aspnet/KestrelHttpServer/issues/2630
|
||||
Assert.False(_receivedHeaders.ContainsKey("trailer-1"));
|
||||
Assert.False(_receivedHeaders.ContainsKey("trailer-2"));
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
|
@ -3128,14 +3198,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||
Assert.Equal("0", _decodedHeaders["content-length"]);
|
||||
Assert.Equal(_largeHeaderValue, _decodedHeaders["a"]);
|
||||
Assert.Equal(_largeHeaderValue, _decodedHeaders["b"]);
|
||||
Assert.Equal(_largeHeaderValue, _decodedHeaders["c"]);
|
||||
Assert.Equal(_largeHeaderValue, _decodedHeaders["d"]);
|
||||
Assert.Equal(_largeHeaderValue, _decodedHeaders["e"]);
|
||||
Assert.Equal(_largeHeaderValue, _decodedHeaders["f"]);
|
||||
Assert.Equal(_largeHeaderValue, _decodedHeaders["g"]);
|
||||
Assert.Equal(_largeHeaderValue, _decodedHeaders["h"]);
|
||||
Assert.Equal(_4kHeaderValue, _decodedHeaders["a"]);
|
||||
Assert.Equal(_4kHeaderValue, _decodedHeaders["b"]);
|
||||
Assert.Equal(_4kHeaderValue, _decodedHeaders["c"]);
|
||||
Assert.Equal(_4kHeaderValue, _decodedHeaders["d"]);
|
||||
Assert.Equal(_4kHeaderValue, _decodedHeaders["e"]);
|
||||
Assert.Equal(_4kHeaderValue, _decodedHeaders["f"]);
|
||||
Assert.Equal(_4kHeaderValue, _decodedHeaders["g"]);
|
||||
Assert.Equal(_4kHeaderValue, _decodedHeaders["h"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -623,10 +623,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>("a", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("a", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
|
|
@ -726,10 +726,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||
new KeyValuePair<string, string>("a", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _largeHeaderValue),
|
||||
new KeyValuePair<string, string>("a", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("b", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("c", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>("d", _4kHeaderValue),
|
||||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
public class Http2TestBase : TestApplicationErrorLoggerLoggedTest, IDisposable, IHttpHeadersHandler
|
||||
{
|
||||
protected static readonly string _largeHeaderValue = new string('a', HPackDecoder.MaxStringOctets);
|
||||
protected static readonly string _4kHeaderValue = new string('a', HPackDecoder.MaxStringOctets);
|
||||
|
||||
protected static readonly IEnumerable<KeyValuePair<string, string>> _browserRequestHeaders = new[]
|
||||
{
|
||||
|
|
@ -174,7 +174,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
foreach (var name in new[] { "a", "b", "c", "d", "e", "f", "g", "h" })
|
||||
{
|
||||
context.Response.Headers[name] = _largeHeaderValue;
|
||||
context.Response.Headers[name] = _4kHeaderValue;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
|
@ -307,7 +307,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_decodedHeaders[name.GetAsciiStringNonNullCharacters()] = value.GetAsciiOrUTF8StringNonNullCharacters();
|
||||
}
|
||||
|
||||
protected async Task InitializeConnectionAsync(RequestDelegate application, int expectedSettingsLegnth = 6)
|
||||
protected async Task InitializeConnectionAsync(RequestDelegate application, int expectedSettingsCount = 2)
|
||||
{
|
||||
_connectionTask = _connection.ProcessRequestsAsync(new DummyApplication(application));
|
||||
|
||||
|
|
@ -315,7 +315,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await SendSettingsAsync();
|
||||
|
||||
await ExpectAsync(Http2FrameType.SETTINGS,
|
||||
withLength: expectedSettingsLegnth,
|
||||
withLength: expectedSettingsCount * 6,
|
||||
withFlags: 0,
|
||||
withStreamId: 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,11 +35,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public Task Server_Http2Only_Cleartext_Success()
|
||||
public async Task Server_Http2Only_Cleartext_Success()
|
||||
{
|
||||
// Expect a SETTINGS frame (type 0x4) with default settings
|
||||
return TestSuccess(HttpProtocols.Http2, Encoding.ASCII.GetString(Http2Connection.ClientPreface),
|
||||
"\x00\x00\x06\x04\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x64");
|
||||
var expected = new byte[]
|
||||
{
|
||||
0x00, 0x00, 0x0C, // Payload Length (6 * settings count)
|
||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // SETTINGS frame (type 0x04)
|
||||
0x00, 0x03, 0x00, 0x00, 0x00, 0x64, // Connection limit
|
||||
0x00, 0x06, 0x00, 0x00, 0x80, 0x00 // Header size limit
|
||||
};
|
||||
var testContext = new TestServiceContext(LoggerFactory);
|
||||
var listenOptions = new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0))
|
||||
{
|
||||
Protocols = HttpProtocols.Http2
|
||||
};
|
||||
|
||||
using (var server = new TestServer(context => Task.CompletedTask, testContext, listenOptions))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send(Encoding.ASCII.GetString(Http2Connection.ClientPreface));
|
||||
// Can't use Receive when expecting binary data
|
||||
var actual = new byte[expected.Length];
|
||||
var read = await connection.Stream.ReadAsync(actual, 0, actual.Length);
|
||||
Assert.Equal(expected.Length, read);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TestSuccess(HttpProtocols serverProtocols, string request, string expectedResponse)
|
||||
|
|
|
|||
Loading…
Reference in New Issue