HTTP/2: support trailers.
This commit is contained in:
parent
66a3c9496a
commit
9dfffd14bb
|
|
@ -358,13 +358,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
// END_STREAM flag set MUST treat that as a connection error (Section 5.4.1)
|
// END_STREAM flag set MUST treat that as a connection error (Section 5.4.1)
|
||||||
// of type STREAM_CLOSED, unless the frame is permitted as described below.
|
// of type STREAM_CLOSED, unless the frame is permitted as described below.
|
||||||
//
|
//
|
||||||
// (The allowed frame types for this situation are WINDOW_UPDATE, RST_STREAM and PRIORITY)
|
// (The allowed frame types after END_STREAM are WINDOW_UPDATE, RST_STREAM and PRIORITY)
|
||||||
if (stream.EndStreamReceived)
|
if (stream.EndStreamReceived)
|
||||||
{
|
{
|
||||||
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
|
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: trailers
|
// This is the last chance for the client to send END_STREAM
|
||||||
|
if ((_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == 0)
|
||||||
|
{
|
||||||
|
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream, Http2ErrorCode.PROTOCOL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we found an active stream, this HEADERS frame contains trailers
|
||||||
|
_currentHeadersStream = stream;
|
||||||
|
_requestHeaderParsingState = RequestHeaderParsingState.Trailers;
|
||||||
|
|
||||||
|
var endHeaders = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS;
|
||||||
|
await DecodeTrailersAsync(endHeaders, _incomingFrame.HeadersPayload);
|
||||||
}
|
}
|
||||||
else if (_incomingFrame.StreamId <= _highestOpenedStreamId)
|
else if (_incomingFrame.StreamId <= _highestOpenedStreamId)
|
||||||
{
|
{
|
||||||
|
|
@ -585,8 +596,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
var endHeaders = (_incomingFrame.ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS;
|
var endHeaders = (_incomingFrame.ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS;
|
||||||
|
|
||||||
|
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
||||||
|
{
|
||||||
|
return DecodeTrailersAsync(endHeaders, _incomingFrame.Payload);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return DecodeHeadersAsync(endHeaders, _incomingFrame.Payload);
|
return DecodeHeadersAsync(endHeaders, _incomingFrame.Payload);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Task ProcessUnknownFrameAsync()
|
private Task ProcessUnknownFrameAsync()
|
||||||
{
|
{
|
||||||
|
|
@ -620,6 +638,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task DecodeTrailersAsync(bool endHeaders, Span<byte> payload)
|
||||||
|
{
|
||||||
|
_hpackDecoder.Decode(payload, endHeaders, handler: this);
|
||||||
|
|
||||||
|
if (endHeaders)
|
||||||
|
{
|
||||||
|
var endStreamTask = _currentHeadersStream.OnDataAsync(Constants.EmptyData, endStream: true);
|
||||||
|
ResetRequestHeaderParsingState();
|
||||||
|
return endStreamTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private void StartStream()
|
private void StartStream()
|
||||||
{
|
{
|
||||||
if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
|
if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
|
||||||
|
|
@ -682,17 +714,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.1
|
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.1
|
||||||
if (IsPseudoHeaderField(name, out var headerField))
|
if (IsPseudoHeaderField(name, out var headerField))
|
||||||
{
|
{
|
||||||
if (_requestHeaderParsingState == RequestHeaderParsingState.Headers ||
|
if (_requestHeaderParsingState == RequestHeaderParsingState.Headers)
|
||||||
_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
|
||||||
{
|
{
|
||||||
// Pseudo-header fields MUST NOT appear in trailers.
|
|
||||||
// ...
|
|
||||||
// All pseudo-header fields MUST appear in the header block before regular header fields.
|
// 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
|
// 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).
|
// 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 Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorPseudoHeaderFieldAfterRegularHeaders, Http2ErrorCode.PROTOCOL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
||||||
|
{
|
||||||
|
// Pseudo-header fields MUST NOT appear in trailers.
|
||||||
|
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorTrailersContainPseudoHeaderField, Http2ErrorCode.PROTOCOL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
_requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields;
|
_requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields;
|
||||||
|
|
||||||
if (headerField == PseudoHeaderFields.Unknown)
|
if (headerField == PseudoHeaderFields.Unknown)
|
||||||
|
|
@ -738,11 +773,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
for (var i = 0; i < name.Length; i++)
|
for (var i = 0; i < name.Length; i++)
|
||||||
{
|
{
|
||||||
if (name[i] >= 65 && name[i] <= 90)
|
if (name[i] >= 65 && name[i] <= 90)
|
||||||
|
{
|
||||||
|
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
||||||
|
{
|
||||||
|
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorTrailerNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
|
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsPseudoHeaderField(Span<byte> name, out PseudoHeaderFields headerField)
|
private bool IsPseudoHeaderField(Span<byte> name, out PseudoHeaderFields headerField)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -44,6 +45,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
new KeyValuePair<string, string>("upgrade-insecure-requests", "1"),
|
new KeyValuePair<string, string>("upgrade-insecure-requests", "1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly IEnumerable<KeyValuePair<string, string>> _requestTrailers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("trailer-one", "1"),
|
||||||
|
new KeyValuePair<string, string>("trailer-two", "2"),
|
||||||
|
};
|
||||||
|
|
||||||
private static readonly IEnumerable<KeyValuePair<string, string>> _oneContinuationRequestHeaders = new[]
|
private static readonly IEnumerable<KeyValuePair<string, string>> _oneContinuationRequestHeaders = new[]
|
||||||
{
|
{
|
||||||
new KeyValuePair<string, string>(":method", "GET"),
|
new KeyValuePair<string, string>(":method", "GET"),
|
||||||
|
|
@ -93,6 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
|
|
||||||
private readonly RequestDelegate _noopApplication;
|
private readonly RequestDelegate _noopApplication;
|
||||||
private readonly RequestDelegate _readHeadersApplication;
|
private readonly RequestDelegate _readHeadersApplication;
|
||||||
|
private readonly RequestDelegate _readTrailersApplication;
|
||||||
private readonly RequestDelegate _bufferingApplication;
|
private readonly RequestDelegate _bufferingApplication;
|
||||||
private readonly RequestDelegate _echoApplication;
|
private readonly RequestDelegate _echoApplication;
|
||||||
private readonly RequestDelegate _echoWaitForAbortApplication;
|
private readonly RequestDelegate _echoWaitForAbortApplication;
|
||||||
|
|
@ -118,6 +126,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_readTrailersApplication = async context =>
|
||||||
|
{
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
// Consuming the entire request body guarantees trailers will be available
|
||||||
|
await context.Request.Body.CopyToAsync(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var header in context.Request.Headers)
|
||||||
|
{
|
||||||
|
_receivedHeaders[header.Key] = header.Value.ToString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_bufferingApplication = async context =>
|
_bufferingApplication = async context =>
|
||||||
{
|
{
|
||||||
var data = new List<byte>();
|
var data = new List<byte>();
|
||||||
|
|
@ -687,6 +709,52 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public async Task HEADERS_Received_WithTrailers_Decoded(bool sendData)
|
||||||
|
{
|
||||||
|
await InitializeConnectionAsync(_readTrailersApplication);
|
||||||
|
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
|
||||||
|
|
||||||
|
// Initialize another stream with a higher stream ID, and verify that after trailers are
|
||||||
|
// decoded by the other stream, the highest opened stream ID is not reset to the lower ID
|
||||||
|
// (the highest opened stream ID is sent by the server in the GOAWAY frame when shutting
|
||||||
|
// down the connection).
|
||||||
|
await SendHeadersAsync(3, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _browserRequestHeaders);
|
||||||
|
|
||||||
|
// The second stream should end first, since the first one is waiting for the request body.
|
||||||
|
await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 55,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 3);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 3);
|
||||||
|
|
||||||
|
if (sendData)
|
||||||
|
{
|
||||||
|
await SendDataAsync(1, _helloBytes, endStream: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _requestTrailers);
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 55,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 1);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
VerifyDecodedRequestHeaders(_browserRequestHeaders.Concat(_requestTrailers));
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HEADERS_Received_StreamIdZero_ConnectionError()
|
public async Task HEADERS_Received_StreamIdZero_ConnectionError()
|
||||||
{
|
{
|
||||||
|
|
@ -861,6 +929,39 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock);
|
expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(IllegalTrailerData))]
|
||||||
|
public async Task HEADERS_Received_WithTrailers_ContainsIllegalTrailer_ConnectionError(byte[] trailers, string expectedErrorMessage)
|
||||||
|
{
|
||||||
|
await InitializeConnectionAsync(_readTrailersApplication);
|
||||||
|
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, trailers);
|
||||||
|
|
||||||
|
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
|
||||||
|
ignoreNonGoAwayFrames: false,
|
||||||
|
expectedLastStreamId: 1,
|
||||||
|
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
|
||||||
|
expectedErrorMessage: expectedErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(Http2HeadersFrameFlags.NONE)]
|
||||||
|
[InlineData(Http2HeadersFrameFlags.END_HEADERS)]
|
||||||
|
public async Task HEADERS_Received_WithTrailers_EndStreamNotSet_ConnectionError(Http2HeadersFrameFlags flags)
|
||||||
|
{
|
||||||
|
await InitializeConnectionAsync(_readTrailersApplication);
|
||||||
|
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
|
||||||
|
await SendHeadersAsync(1, flags, _requestTrailers);
|
||||||
|
|
||||||
|
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
|
||||||
|
ignoreNonGoAwayFrames: false,
|
||||||
|
expectedLastStreamId: 1,
|
||||||
|
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
|
||||||
|
expectedErrorMessage: CoreStrings.Http2ErrorHeadersWithTrailersNoEndStream);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(UpperCaseHeaderNameData))]
|
[MemberData(nameof(UpperCaseHeaderNameData))]
|
||||||
public async Task HEADERS_Received_HeaderNameContainsUpperCaseCharacter_StreamError(byte[] headerBlock)
|
public async Task HEADERS_Received_HeaderNameContainsUpperCaseCharacter_StreamError(byte[] headerBlock)
|
||||||
|
|
@ -1562,6 +1663,67 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public async Task CONTINUATION_Received_WithTrailers_Decoded(bool sendData)
|
||||||
|
{
|
||||||
|
await InitializeConnectionAsync(_readTrailersApplication);
|
||||||
|
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
|
||||||
|
|
||||||
|
// Initialize another stream with a higher stream ID, and verify that after trailers are
|
||||||
|
// decoded by the other stream, the highest opened stream ID is not reset to the lower ID
|
||||||
|
// (the highest opened stream ID is sent by the server in the GOAWAY frame when shutting
|
||||||
|
// down the connection).
|
||||||
|
await SendHeadersAsync(3, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _browserRequestHeaders);
|
||||||
|
|
||||||
|
// The second stream should end first, since the first one is waiting for the request body.
|
||||||
|
await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 55,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 3);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||||
|
withStreamId: 3);
|
||||||
|
|
||||||
|
if (sendData)
|
||||||
|
{
|
||||||
|
await SendDataAsync(1, _helloBytes, endStream: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trailers encoded as Literal Header Field without Indexing - New Name
|
||||||
|
// trailer-1: 1
|
||||||
|
// trailer-2: 2
|
||||||
|
var trailers = new byte[] { 0x00, 0x09 }
|
||||||
|
.Concat(Encoding.ASCII.GetBytes("trailer-1"))
|
||||||
|
.Concat(new byte[] { 0x01, (byte)'1' })
|
||||||
|
.Concat(new byte[] { 0x00, 0x09 })
|
||||||
|
.Concat(Encoding.ASCII.GetBytes("trailer-2"))
|
||||||
|
.Concat(new byte[] { 0x01, (byte)'2' })
|
||||||
|
.ToArray();
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, new byte[0]);
|
||||||
|
await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, trailers);
|
||||||
|
|
||||||
|
await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 55,
|
||||||
|
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||||
|
withStreamId: 1);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
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")
|
||||||
|
}));
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CONTINUATION_Received_StreamIdMismatch_ConnectionError()
|
public async Task CONTINUATION_Received_StreamIdMismatch_ConnectionError()
|
||||||
{
|
{
|
||||||
|
|
@ -1592,6 +1754,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock);
|
expectedErrorMessage: CoreStrings.HPackErrorIncompleteHeaderBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(IllegalTrailerData))]
|
||||||
|
public async Task CONTINUATION_Received_WithTrailers_ContainsIllegalTrailer_ConnectionError(byte[] trailers, string expectedErrorMessage)
|
||||||
|
{
|
||||||
|
await InitializeConnectionAsync(_readTrailersApplication);
|
||||||
|
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, new byte[0]);
|
||||||
|
await SendContinuationAsync(1, Http2ContinuationFrameFlags.END_HEADERS, trailers);
|
||||||
|
|
||||||
|
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
|
||||||
|
ignoreNonGoAwayFrames: false,
|
||||||
|
expectedLastStreamId: 1,
|
||||||
|
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
|
||||||
|
expectedErrorMessage: expectedErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(MissingPseudoHeaderFieldData))]
|
[MemberData(nameof(MissingPseudoHeaderFieldData))]
|
||||||
public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
|
public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
|
||||||
|
|
@ -2048,6 +2227,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
return done;
|
return done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags, byte[] payload)
|
||||||
|
{
|
||||||
|
var frame = new Http2Frame();
|
||||||
|
|
||||||
|
frame.PrepareContinuation(flags, streamId);
|
||||||
|
frame.Length = payload.Length;
|
||||||
|
payload.CopyTo(frame.Payload);
|
||||||
|
|
||||||
|
await SendAsync(frame.Raw);
|
||||||
|
}
|
||||||
|
|
||||||
private Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags)
|
private Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags)
|
||||||
{
|
{
|
||||||
var frame = new Http2Frame();
|
var frame = new Http2Frame();
|
||||||
|
|
@ -2463,5 +2653,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TheoryData<byte[], string> IllegalTrailerData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// We can't use HPackEncoder here because it will convert header names to lowercase
|
||||||
|
var data = new TheoryData<byte[], string>();
|
||||||
|
|
||||||
|
// Indexed Header Field - :method: GET
|
||||||
|
data.Add(new byte[] { 0x82 }, CoreStrings.Http2ErrorTrailersContainPseudoHeaderField);
|
||||||
|
|
||||||
|
// Indexed Header Field - :path: /
|
||||||
|
data.Add(new byte[] { 0x84 }, CoreStrings.Http2ErrorTrailersContainPseudoHeaderField);
|
||||||
|
|
||||||
|
// Indexed Header Field - :scheme: http
|
||||||
|
data.Add(new byte[] { 0x86 }, CoreStrings.Http2ErrorTrailersContainPseudoHeaderField);
|
||||||
|
|
||||||
|
// Literal Header Field without Indexing - Indexed Name - :authority: 127.0.0.1
|
||||||
|
data.Add(new byte[] { 0x01, 0x09 }.Concat(Encoding.ASCII.GetBytes("127.0.0.1")).ToArray(), CoreStrings.Http2ErrorTrailersContainPseudoHeaderField);
|
||||||
|
|
||||||
|
// Literal Header Field without Indexing - New Name - contains-Uppercase: 0
|
||||||
|
data.Add(new byte[] { 0x00, 0x12 }
|
||||||
|
.Concat(Encoding.ASCII.GetBytes("contains-Uppercase"))
|
||||||
|
.Concat(new byte[] { 0x01, (byte)'0' })
|
||||||
|
.ToArray(), CoreStrings.Http2ErrorTrailerNameUppercase);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue