HTTP/2: validate request headers prior to starting new stream.

This commit is contained in:
Cesar Blum Silveira 2017-10-10 13:12:34 -07:00 committed by GitHub
parent 08c6c38667
commit d46d2ce193
3 changed files with 551 additions and 30 deletions

View File

@ -18,8 +18,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2Connection : ITimeoutControl, IHttp2StreamLifetimeHandler, IHttpHeadersHandler
{
private enum RequestHeaderParsingState
{
Ready,
PseudoHeaderFields,
Headers,
Trailers
}
[Flags]
private enum PseudoHeaderFields
{
None = 0x0,
Authority = 0x1,
Method = 0x2,
Path = 0x4,
Scheme = 0x8,
Status = 0x10,
Unknown = 0x40000000
}
public static byte[] ClientPreface { get; } = Encoding.ASCII.GetBytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
private static readonly PseudoHeaderFields _mandatoryRequestPseudoHeaderFields =
PseudoHeaderFields.Method | PseudoHeaderFields.Path | PseudoHeaderFields.Scheme;
private static readonly byte[] _authorityBytes = Encoding.ASCII.GetBytes("authority");
private static readonly byte[] _methodBytes = Encoding.ASCII.GetBytes("method");
private static readonly byte[] _pathBytes = Encoding.ASCII.GetBytes("path");
private static readonly byte[] _schemeBytes = Encoding.ASCII.GetBytes("scheme");
private static readonly byte[] _statusBytes = Encoding.ASCII.GetBytes("status");
private static readonly byte[] _connectionBytes = Encoding.ASCII.GetBytes("connection");
private static readonly byte[] _teBytes = Encoding.ASCII.GetBytes("te");
private static readonly byte[] _trailersBytes = Encoding.ASCII.GetBytes("trailers");
private static readonly byte[] _connectBytes = Encoding.ASCII.GetBytes("CONNECT");
private readonly Http2ConnectionContext _context;
private readonly Http2FrameWriter _frameWriter;
private readonly HPackDecoder _hpackDecoder;
@ -30,6 +63,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
private readonly Http2Frame _incomingFrame = new Http2Frame();
private Http2Stream _currentHeadersStream;
private RequestHeaderParsingState _requestHeaderParsingState;
private PseudoHeaderFields _parsedPseudoHeaderFields;
private bool _isMethodConnect;
private int _highestOpenedStreamId;
private bool _stopping;
@ -318,6 +354,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
throw new Http2ConnectionErrorException(Http2ErrorCode.STREAM_CLOSED);
}
// TODO: trailers
}
else if (_incomingFrame.StreamId <= _highestOpenedStreamId)
@ -354,17 +391,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
_currentHeadersStream.Reset();
_streams[_incomingFrame.StreamId] = _currentHeadersStream;
var endHeaders = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS;
_hpackDecoder.Decode(_incomingFrame.HeadersPayload, endHeaders, handler: this);
if (endHeaders)
{
_highestOpenedStreamId = _incomingFrame.StreamId;
_ = _currentHeadersStream.ProcessRequestsAsync();
_currentHeadersStream = null;
}
await DecodeHeadersAsync(endHeaders, _incomingFrame.HeadersPayload);
}
}
@ -533,16 +561,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
}
var endHeaders = (_incomingFrame.ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS;
_hpackDecoder.Decode(_incomingFrame.HeadersPayload, endHeaders, handler: this);
if (endHeaders)
{
_highestOpenedStreamId = _currentHeadersStream.StreamId;
_ = _currentHeadersStream.ProcessRequestsAsync();
_currentHeadersStream = null;
}
return Task.CompletedTask;
return DecodeHeadersAsync(endHeaders, _incomingFrame.Payload);
}
private Task ProcessUnknownFrameAsync()
@ -555,6 +575,54 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
return Task.CompletedTask;
}
private Task DecodeHeadersAsync(bool endHeaders, Span<byte> payload)
{
try
{
_hpackDecoder.Decode(payload, endHeaders, handler: this);
if (endHeaders)
{
StartStream();
ResetRequestHeaderParsingState();
}
}
catch (Http2StreamErrorException ex)
{
ResetRequestHeaderParsingState();
return _frameWriter.WriteRstStreamAsync(ex.StreamId, ex.ErrorCode);
}
return Task.CompletedTask;
}
private void StartStream()
{
if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
{
// All HTTP/2 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header
// fields, unless it is a CONNECT request (Section 8.3). An HTTP request that omits mandatory pseudo-header
// fields is malformed (Section 8.1.2.6).
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR);
}
_streams[_incomingFrame.StreamId] = _currentHeadersStream;
_ = _currentHeadersStream.ProcessRequestsAsync();
}
private void ResetRequestHeaderParsingState()
{
if (_requestHeaderParsingState != RequestHeaderParsingState.Trailers)
{
_highestOpenedStreamId = _currentHeadersStream.StreamId;
}
_currentHeadersStream = null;
_requestHeaderParsingState = RequestHeaderParsingState.Ready;
_parsedPseudoHeaderFields = PseudoHeaderFields.None;
_isMethodConnect = false;
}
private void ThrowIfIncomingFrameSentToIdleStream()
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
@ -581,9 +649,122 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public void OnHeader(Span<byte> name, Span<byte> value)
{
ValidateHeader(name, value);
_currentHeadersStream.OnHeader(name, value);
}
private void ValidateHeader(Span<byte> name, Span<byte> value)
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.1
if (IsPseudoHeaderField(name, out var headerField))
{
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.
// 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, Http2ErrorCode.PROTOCOL_ERROR);
}
_requestHeaderParsingState = RequestHeaderParsingState.PseudoHeaderFields;
if (headerField == PseudoHeaderFields.Unknown)
{
// 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, 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, 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, Http2ErrorCode.PROTOCOL_ERROR);
}
if (headerField == PseudoHeaderFields.Method)
{
_isMethodConnect = value.SequenceEqual(_connectBytes);
}
_parsedPseudoHeaderFields |= headerField;
}
else if (_requestHeaderParsingState != RequestHeaderParsingState.Trailers)
{
_requestHeaderParsingState = RequestHeaderParsingState.Headers;
}
if (IsConnectionSpecificHeaderField(name, value))
{
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR);
}
// http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2
// A request or response containing uppercase header field names MUST be treated as malformed (Section 8.1.2.6).
for (var i = 0; i < name.Length; i++)
{
if (name[i] >= 65 && name[i] <= 90)
{
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, Http2ErrorCode.PROTOCOL_ERROR);
}
}
}
private bool IsPseudoHeaderField(Span<byte> name, out PseudoHeaderFields headerField)
{
headerField = PseudoHeaderFields.None;
if (name.IsEmpty || name[0] != (byte)':')
{
return false;
}
// Skip ':'
name = name.Slice(1);
if (name.SequenceEqual(_pathBytes))
{
headerField = PseudoHeaderFields.Path;
}
else if (name.SequenceEqual(_methodBytes))
{
headerField = PseudoHeaderFields.Method;
}
else if (name.SequenceEqual(_schemeBytes))
{
headerField = PseudoHeaderFields.Scheme;
}
else if (name.SequenceEqual(_statusBytes))
{
headerField = PseudoHeaderFields.Status;
}
else if (name.SequenceEqual(_authorityBytes))
{
headerField = PseudoHeaderFields.Authority;
}
else
{
headerField = PseudoHeaderFields.Unknown;
}
return true;
}
private static bool IsConnectionSpecificHeaderField(Span<byte> name, Span<byte> value)
{
return name.SequenceEqual(_connectionBytes) || (name.SequenceEqual(_teBytes) && !value.SequenceEqual(_trailersBytes));
}
void ITimeoutControl.SetTimeout(long ticks, TimeoutAction timeoutAction)
{
}

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2StreamErrorException : Exception
{
public Http2StreamErrorException(int streamId, Http2ErrorCode errorCode)
: base($"HTTP/2 stream ID {streamId} error: {errorCode}")
{
StreamId = streamId;
ErrorCode = errorCode;
}
public int StreamId { get; }
public Http2ErrorCode ErrorCode { get; }
}
}

View File

@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Primitives;
using Xunit;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
@ -29,16 +28,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
new KeyValuePair<string, string>(":method", "POST"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":authority", "127.0.0.1"),
new KeyValuePair<string, string>(":scheme", "https"),
new KeyValuePair<string, string>(":scheme", "http"),
};
private static readonly IEnumerable<KeyValuePair<string, string>> _browserRequestHeaders = new[]
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":authority", "127.0.0.1"),
new KeyValuePair<string, string>(":scheme", "https"),
new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>("user-agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"),
new KeyValuePair<string, string>("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
new KeyValuePair<string, string>("accept-language", "en-US,en;q=0.5"),
@ -50,8 +47,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":authority", "127.0.0.1"),
new KeyValuePair<string, string>(":scheme", "https"),
new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>("a", _largeHeaderValue),
new KeyValuePair<string, string>("b", _largeHeaderValue),
new KeyValuePair<string, string>("c", _largeHeaderValue),
@ -62,8 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":authority", "127.0.0.1"),
new KeyValuePair<string, string>(":scheme", "https"),
new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>("a", _largeHeaderValue),
new KeyValuePair<string, string>("b", _largeHeaderValue),
new KeyValuePair<string, string>("c", _largeHeaderValue),
@ -770,7 +765,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
[Fact]
public async Task HEADERS_Received_IncompleteHeaderBlockFragment_ConnectionError()
public async Task HEADERS_Received_IncompleteHeaderBlock_ConnectionError()
{
await InitializeConnectionAsync(_noopApplication);
@ -779,6 +774,143 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, ignoreNonGoAwayFrames: false);
}
[Theory]
[MemberData(nameof(UpperCaseHeaderNameData))]
public async Task HEADERS_Received_HeaderNameContainsUpperCaseCharacter_StreamError(byte[] headerBlock)
{
await InitializeConnectionAsync(_noopApplication);
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headerBlock);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, ignoreNonRstStreamFrames: false);
// Verify that the stream ID can't be re-used
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false);
}
[Fact]
public Task HEADERS_Received_HeaderBlockContainsUnknownPseudoHeaderField_StreamError()
{
var headers = new[]
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>(":unknown", "0"),
};
return HEADERS_Received_InvalidHeaderFields_StreamError(headers);
}
[Fact]
public Task HEADERS_Received_HeaderBlockContainsResponsePseudoHeaderField_StreamError()
{
var headers = new[]
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>(":status", "200"),
};
return HEADERS_Received_InvalidHeaderFields_StreamError(headers);
}
[Theory]
[MemberData(nameof(DuplicatePseudoHeaderFieldData))]
public Task HEADERS_Received_HeaderBlockContainsDuplicatePseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
{
return HEADERS_Received_InvalidHeaderFields_StreamError(headers);
}
[Theory]
[MemberData(nameof(MissingPseudoHeaderFieldData))]
public Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
{
return HEADERS_Received_InvalidHeaderFields_StreamError(headers);
}
[Theory]
[MemberData(nameof(ConnectMissingPseudoHeaderFieldData))]
public async Task HEADERS_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_MethodIsCONNECT_NoError(IEnumerable<KeyValuePair<string, string>> headers)
{
await InitializeConnectionAsync(_noopApplication);
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Theory]
[MemberData(nameof(PseudoHeaderFieldAfterRegularHeadersData))]
public Task HEADERS_Received_HeaderBlockContainsPseudoHeaderFieldAfterRegularHeaders_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
{
return HEADERS_Received_InvalidHeaderFields_StreamError(headers);
}
private async Task HEADERS_Received_InvalidHeaderFields_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
{
await InitializeConnectionAsync(_noopApplication);
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, ignoreNonRstStreamFrames: false);
// Verify that the stream ID can't be re-used
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, _browserRequestHeaders);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false);
}
[Fact]
public Task HEADERS_Received_HeaderBlockContainsConnectionSpecificHeader_StreamError()
{
var headers = new[]
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>("connection", "keep-alive")
};
return HEADERS_Received_InvalidHeaderFields_StreamError(headers);
}
[Fact]
public Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsNotTrailers_StreamError()
{
var headers = new[]
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>("te", "trailers, deflate")
};
return HEADERS_Received_InvalidHeaderFields_StreamError(headers);
}
[Fact]
public Task HEADERS_Received_HeaderBlockContainsTEHeader_ValueIsTrailers_NoError()
{
var headers = new[]
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>("te", "trailers, deflate")
};
return HEADERS_Received_InvalidHeaderFields_StreamError(headers);
}
[Fact]
public async Task PRIORITY_Received_StreamIdZero_ConnectionError()
{
@ -1220,7 +1352,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
[Fact]
public async Task CONTINUATION_Received_IncompleteHeaderBlockFragment_ConnectionError()
public async Task CONTINUATION_Received_IncompleteHeaderBlock_ConnectionError()
{
await InitializeConnectionAsync(_noopApplication);
@ -1230,6 +1362,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
await WaitForConnectionErrorAsync(expectedLastStreamId: 0, expectedErrorCode: Http2ErrorCode.COMPRESSION_ERROR, ignoreNonGoAwayFrames: false);
}
[Theory]
[MemberData(nameof(MissingPseudoHeaderFieldData))]
public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_StreamError(IEnumerable<KeyValuePair<string, string>> headers)
{
await InitializeConnectionAsync(_noopApplication);
Assert.True(await SendHeadersAsync(1, Http2HeadersFrameFlags.NONE, headers));
await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, ignoreNonRstStreamFrames: false);
// Verify that the stream ID can't be re-used
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS, headers);
await WaitForConnectionErrorAsync(expectedLastStreamId: 1, expectedErrorCode: Http2ErrorCode.STREAM_CLOSED, ignoreNonGoAwayFrames: false);
}
[Theory]
[MemberData(nameof(ConnectMissingPseudoHeaderFieldData))]
public async Task CONTINUATION_Received_HeaderBlockDoesNotContainMandatoryPseudoHeaderField_MethodIsCONNECT_NoError(IEnumerable<KeyValuePair<string, string>> headers)
{
await InitializeConnectionAsync(_noopApplication);
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_STREAM, headers);
await SendEmptyContinuationFrameAsync(1, Http2ContinuationFrameFlags.END_HEADERS);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 55,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task CONTINUATION_Sent_WhenHeadersLargerThanFrameLength()
{
@ -1552,6 +1721,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
return done;
}
private Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, byte[] headerBlock)
{
var frame = new Http2Frame();
frame.PrepareHeaders(flags, streamId);
frame.Length = headerBlock.Length;
headerBlock.CopyTo(frame.HeadersPayload);
return SendAsync(frame.Raw);
}
private Task SendInvalidHeadersFrameAsync(int streamId, int frameLength, byte padLength)
{
Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame.");
@ -1596,6 +1776,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
return done;
}
private Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags)
{
var frame = new Http2Frame();
frame.PrepareContinuation(flags, streamId);
frame.Length = 0;
return SendAsync(frame.Raw);
}
private Task SendIncompleteContinuationFrameAsync(int streamId)
{
var frame = new Http2Frame();
@ -1859,5 +2049,134 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Assert.Equal(header.Value, value, ignoreCase: true);
}
}
public static TheoryData<byte[]> UpperCaseHeaderNameData
{
get
{
// We can't use HPackEncoder here because it will convert header names to lowercase
var headerName = "abcdefghijklmnopqrstuvwxyz";
var headerBlockStart = new byte[]
{
0x82, // Indexed Header Field - :method: GET
0x84, // Indexed Header Field - :path: /
0x86, // Indexed Header Field - :scheme: http
0x00, // Literal Header Field without Indexing - New Name
(byte)headerName.Length, // Header name length
};
var headerBlockEnd = new byte[]
{
0x01, // Header value length
0x30 // "0"
};
var data = new TheoryData<byte[]>();
for (var i = 0; i < headerName.Length; i++)
{
var bytes = Encoding.ASCII.GetBytes(headerName);
bytes[i] &= 0xdf;
var headerBlock = headerBlockStart.Concat(bytes).Concat(headerBlockEnd).ToArray();
data.Add(headerBlock);
}
return data;
}
}
public static TheoryData<IEnumerable<KeyValuePair<string, string>>> DuplicatePseudoHeaderFieldData
{
get
{
var data = new TheoryData<IEnumerable<KeyValuePair<string, string>>>();
var requestHeaders = new[]
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":authority", "127.0.0.1"),
new KeyValuePair<string, string>(":scheme", "http"),
};
foreach (var headerField in requestHeaders)
{
var headers = requestHeaders.Concat(new[] { new KeyValuePair<string, string>(headerField.Key, headerField.Value) });
data.Add(headers);
}
return data;
}
}
public static TheoryData<IEnumerable<KeyValuePair<string, string>>> MissingPseudoHeaderFieldData
{
get
{
var data = new TheoryData<IEnumerable<KeyValuePair<string, string>>>();
var requestHeaders = new[]
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":scheme", "http"),
};
foreach (var headerField in requestHeaders)
{
var headers = requestHeaders.Except(new[] { headerField });
data.Add(headers);
}
return data;
}
}
public static TheoryData<IEnumerable<KeyValuePair<string, string>>> ConnectMissingPseudoHeaderFieldData
{
get
{
var data = new TheoryData<IEnumerable<KeyValuePair<string, string>>>();
var methodHeader = new[] { new KeyValuePair<string, string>(":method", "CONNECT") };
var requestHeaders = new[]
{
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>(":authority", "127.0.0.1"),
};
foreach (var headerField in requestHeaders)
{
var headers = methodHeader.Concat(requestHeaders.Except(new[] { headerField }));
data.Add(headers);
}
return data;
}
}
public static TheoryData<IEnumerable<KeyValuePair<string, string>>> PseudoHeaderFieldAfterRegularHeadersData
{
get
{
var data = new TheoryData<IEnumerable<KeyValuePair<string, string>>>();
var requestHeaders = new[]
{
new KeyValuePair<string, string>(":method", "GET"),
new KeyValuePair<string, string>(":path", "/"),
new KeyValuePair<string, string>(":authority", "127.0.0.1"),
new KeyValuePair<string, string>(":scheme", "http"),
new KeyValuePair<string, string>("content-length", "0")
};
foreach (var headerField in requestHeaders.Where(h => h.Key.StartsWith(":")))
{
var headers = requestHeaders.Except(new[] { headerField }).Concat(new[] { headerField });
data.Add(headers);
}
return data;
}
}
}
}