aspnetcore/src/Kestrel.Core/Internal/Http2/Http2Connection.cs

999 lines
43 KiB
C#

// 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;
using System.Buffers;
using System.Collections.Concurrent;
using System.IO.Pipelines;
using System.Security.Authentication;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{
public class Http2Connection : ITimeoutControl, IHttp2StreamLifetimeHandler, IHttpHeadersHandler, IRequestProcessor
{
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(HeaderNames.Authority);
private static readonly byte[] _methodBytes = Encoding.ASCII.GetBytes(HeaderNames.Method);
private static readonly byte[] _pathBytes = Encoding.ASCII.GetBytes(HeaderNames.Path);
private static readonly byte[] _schemeBytes = Encoding.ASCII.GetBytes(HeaderNames.Scheme);
private static readonly byte[] _statusBytes = Encoding.ASCII.GetBytes(HeaderNames.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;
private readonly InputFlowControl _inputFlowControl = new InputFlowControl(Http2PeerSettings.DefaultInitialWindowSize, Http2PeerSettings.DefaultInitialWindowSize / 2);
private readonly OutputFlowControl _outputFlowControl = new OutputFlowControl(Http2PeerSettings.DefaultInitialWindowSize);
private readonly Http2PeerSettings _serverSettings = new Http2PeerSettings();
private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
private readonly Http2Frame _incomingFrame = new Http2Frame();
private Http2Stream _currentHeadersStream;
private RequestHeaderParsingState _requestHeaderParsingState;
private PseudoHeaderFields _parsedPseudoHeaderFields;
private Http2HeadersFrameFlags _headerFlags;
private bool _isMethodConnect;
private int _highestOpenedStreamId;
private bool _stopping;
private readonly ConcurrentDictionary<int, Http2Stream> _streams = new ConcurrentDictionary<int, Http2Stream>();
public Http2Connection(Http2ConnectionContext context)
{
_context = context;
_frameWriter = new Http2FrameWriter(context.Transport.Output, context.Application.Input, _outputFlowControl, this);
_hpackDecoder = new HPackDecoder((int)_serverSettings.HeaderTableSize);
}
public string ConnectionId => _context.ConnectionId;
public PipeReader Input => _context.Transport.Input;
public IKestrelTrace Log => _context.ServiceContext.Log;
public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures;
public void OnInputOrOutputCompleted()
{
_stopping = true;
_frameWriter.Complete();
}
public void Abort(ConnectionAbortedException ex)
{
_stopping = true;
_frameWriter.Abort(ex);
}
public void StopProcessingNextRequest()
{
_stopping = true;
Input.CancelPendingRead();
}
public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
{
Exception error = null;
var errorCode = Http2ErrorCode.NO_ERROR;
try
{
ValidateTlsRequirements();
if (!await TryReadPrefaceAsync())
{
return;
}
if (!_stopping)
{
await _frameWriter.WriteSettingsAsync(_serverSettings);
}
while (!_stopping)
{
var result = await Input.ReadAsync();
var readableBuffer = result.Buffer;
var consumed = readableBuffer.Start;
var examined = readableBuffer.End;
try
{
if (!readableBuffer.IsEmpty)
{
if (Http2FrameReader.ReadFrame(readableBuffer, _incomingFrame, _serverSettings.MaxFrameSize, out consumed, out examined))
{
Log.LogTrace($"Connection id {ConnectionId} received {_incomingFrame.Type} frame with flags 0x{_incomingFrame.Flags:x} and length {_incomingFrame.Length} for stream ID {_incomingFrame.StreamId}");
await ProcessFrameAsync(application);
}
}
if (result.IsCompleted)
{
return;
}
}
catch (Http2StreamErrorException ex)
{
Log.Http2StreamError(ConnectionId, ex);
AbortStream(_incomingFrame.StreamId, new ConnectionAbortedException(ex.Message, ex));
await _frameWriter.WriteRstStreamAsync(ex.StreamId, ex.ErrorCode);
}
finally
{
Input.AdvanceTo(consumed, examined);
}
}
}
catch (ConnectionResetException ex)
{
// Don't log ECONNRESET errors when there are no active streams on the connection. Browsers like IE will reset connections regularly.
if (_streams.Count > 0)
{
Log.RequestProcessingError(ConnectionId, ex);
}
error = ex;
}
catch (Http2ConnectionErrorException ex)
{
Log.Http2ConnectionError(ConnectionId, ex);
error = ex;
errorCode = ex.ErrorCode;
}
catch (HPackDecodingException ex)
{
Log.HPackDecodingError(ConnectionId, _currentHeadersStream.StreamId, ex);
error = ex;
errorCode = Http2ErrorCode.COMPRESSION_ERROR;
}
catch (Exception ex)
{
error = ex;
errorCode = Http2ErrorCode.INTERNAL_ERROR;
throw;
}
finally
{
var connectionError = error as ConnectionAbortedException
?? new ConnectionAbortedException(CoreStrings.Http2ConnectionFaulted, error);
try
{
// Ensure aborting each stream doesn't result in unnecessary WINDOW_UPDATE frames being sent.
_inputFlowControl.StopWindowUpdates();
foreach (var stream in _streams.Values)
{
stream.Abort(connectionError);
}
await _frameWriter.WriteGoAwayAsync(_highestOpenedStreamId, errorCode);
_frameWriter.Complete();
}
catch
{
_frameWriter.Abort(connectionError);
throw;
}
finally
{
Input.Complete();
}
}
}
// https://tools.ietf.org/html/rfc7540#section-9.2
// Some of these could not be checked in advance. Fail before using the connection.
private void ValidateTlsRequirements()
{
var tlsFeature = ConnectionFeatures.Get<ITlsHandshakeFeature>();
if (tlsFeature == null)
{
// Not using TLS at all.
return;
}
if (tlsFeature.Protocol < SslProtocols.Tls12)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorMinTlsVersion(tlsFeature.Protocol), Http2ErrorCode.INADEQUATE_SECURITY);
}
}
private async Task<bool> TryReadPrefaceAsync()
{
while (!_stopping)
{
var result = await Input.ReadAsync();
var readableBuffer = result.Buffer;
var consumed = readableBuffer.Start;
var examined = readableBuffer.End;
try
{
if (!readableBuffer.IsEmpty)
{
if (ParsePreface(readableBuffer, out consumed, out examined))
{
return true;
}
}
if (result.IsCompleted)
{
return false;
}
}
finally
{
Input.AdvanceTo(consumed, examined);
}
}
return false;
}
private bool ParsePreface(ReadOnlySequence<byte> readableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
consumed = readableBuffer.Start;
examined = readableBuffer.End;
if (readableBuffer.Length < ClientPreface.Length)
{
return false;
}
var span = readableBuffer.IsSingleSegment
? readableBuffer.First.Span
: readableBuffer.ToSpan();
for (var i = 0; i < ClientPreface.Length; i++)
{
if (ClientPreface[i] != span[i])
{
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorInvalidPreface, Http2ErrorCode.PROTOCOL_ERROR);
}
}
consumed = examined = readableBuffer.GetPosition(ClientPreface.Length);
return true;
}
private Task ProcessFrameAsync<TContext>(IHttpApplication<TContext> application)
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1
// Streams initiated by a client MUST use odd-numbered stream identifiers; ...
// An endpoint that receives an unexpected stream identifier MUST respond with
// a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
if (_incomingFrame.StreamId != 0 && (_incomingFrame.StreamId & 1) == 0)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdEven(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
switch (_incomingFrame.Type)
{
case Http2FrameType.DATA:
return ProcessDataFrameAsync();
case Http2FrameType.HEADERS:
return ProcessHeadersFrameAsync(application);
case Http2FrameType.PRIORITY:
return ProcessPriorityFrameAsync();
case Http2FrameType.RST_STREAM:
return ProcessRstStreamFrameAsync();
case Http2FrameType.SETTINGS:
return ProcessSettingsFrameAsync();
case Http2FrameType.PUSH_PROMISE:
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPushPromiseReceived, Http2ErrorCode.PROTOCOL_ERROR);
case Http2FrameType.PING:
return ProcessPingFrameAsync();
case Http2FrameType.GOAWAY:
return ProcessGoAwayFrameAsync();
case Http2FrameType.WINDOW_UPDATE:
return ProcessWindowUpdateFrameAsync();
case Http2FrameType.CONTINUATION:
return ProcessContinuationFrameAsync(application);
default:
return ProcessUnknownFrameAsync();
}
}
private Task ProcessDataFrameAsync()
{
if (_currentHeadersStream != null)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.StreamId == 0)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.DataHasPadding && _incomingFrame.DataPadLength >= _incomingFrame.Length)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
ThrowIfIncomingFrameSentToIdleStream();
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
{
if (stream.EndStreamReceived)
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
//
// ...an endpoint that receives any frames after receiving a frame with the
// 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.
//
// (The allowed frame types for this situation are WINDOW_UPDATE, RST_STREAM and PRIORITY)
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
}
return stream.OnDataAsync(_incomingFrame);
}
// If we couldn't find the stream, it was either alive previously but closed with
// END_STREAM or RST_STREAM, or it was implicitly closed when the client opened
// a new stream with a higher ID. Per the spec, we should send RST_STREAM if
// the stream was closed with RST_STREAM or implicitly, but the spec also says
// in http://httpwg.org/specs/rfc7540.html#rfc.section.5.4.1 that
//
// An endpoint can end a connection at any time. In particular, an endpoint MAY
// choose to treat a stream error as a connection error.
//
// We choose to do that here so we don't have to keep state to track implicitly closed
// streams vs. streams closed with END_STREAM or RST_STREAM.
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED);
}
private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application)
{
if (_currentHeadersStream != null)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.StreamId == 0)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.HeadersHasPadding && _incomingFrame.HeadersPadLength >= _incomingFrame.Length)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.HeadersHasPriority && _incomingFrame.HeadersStreamDependency == _incomingFrame.StreamId)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
//
// ...an endpoint that receives any frames after receiving a frame with the
// 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.
//
// (The allowed frame types after END_STREAM are WINDOW_UPDATE, RST_STREAM and PRIORITY)
if (stream.EndStreamReceived)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
}
// 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)
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1
//
// The first use of a new stream identifier implicitly closes all streams in the "idle"
// state that might have been initiated by that peer with a lower-valued stream identifier.
//
// If we couldn't find the stream, it was previously closed (either implicitly or with
// END_STREAM or RST_STREAM).
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED);
}
else
{
// Start a new stream
_currentHeadersStream = new Http2Stream(new Http2StreamContext
{
ConnectionId = ConnectionId,
StreamId = _incomingFrame.StreamId,
ServiceContext = _context.ServiceContext,
ConnectionFeatures = _context.ConnectionFeatures,
MemoryPool = _context.MemoryPool,
LocalEndPoint = _context.LocalEndPoint,
RemoteEndPoint = _context.RemoteEndPoint,
StreamLifetimeHandler = this,
ClientPeerSettings = _clientSettings,
FrameWriter = _frameWriter,
ConnectionInputFlowControl = _inputFlowControl,
ConnectionOutputFlowControl = _outputFlowControl,
TimeoutControl = this,
});
_currentHeadersStream.Reset();
_headerFlags = _incomingFrame.HeadersFlags;
var endHeaders = (_incomingFrame.HeadersFlags & Http2HeadersFrameFlags.END_HEADERS) == Http2HeadersFrameFlags.END_HEADERS;
await DecodeHeadersAsync(application, endHeaders, _incomingFrame.HeadersPayload);
}
}
private Task ProcessPriorityFrameAsync()
{
if (_currentHeadersStream != null)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.StreamId == 0)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.PriorityStreamDependency == _incomingFrame.StreamId)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamSelfDependency(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.Length != 5)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 5), Http2ErrorCode.FRAME_SIZE_ERROR);
}
return Task.CompletedTask;
}
private Task ProcessRstStreamFrameAsync()
{
if (_currentHeadersStream != null)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.StreamId == 0)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.Length != 4)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 4), Http2ErrorCode.FRAME_SIZE_ERROR);
}
ThrowIfIncomingFrameSentToIdleStream();
AbortStream(_incomingFrame.StreamId, new ConnectionAbortedException(CoreStrings.Http2StreamResetByClient));
return Task.CompletedTask;
}
private Task ProcessSettingsFrameAsync()
{
if (_currentHeadersStream != null)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.StreamId != 0)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if ((_incomingFrame.SettingsFlags & Http2SettingsFrameFlags.ACK) == Http2SettingsFrameFlags.ACK)
{
if (_incomingFrame.Length != 0)
{
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsAckLengthNotZero, Http2ErrorCode.FRAME_SIZE_ERROR);
}
return Task.CompletedTask;
}
if (_incomingFrame.Length % 6 != 0)
{
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorSettingsLengthNotMultipleOfSix, Http2ErrorCode.FRAME_SIZE_ERROR);
}
try
{
// ParseFrame will not parse an InitialWindowSize > int.MaxValue.
var previousInitialWindowSize = (int)_clientSettings.InitialWindowSize;
_clientSettings.ParseFrame(_incomingFrame);
// This difference can be negative.
var windowSizeDifference = (int)_clientSettings.InitialWindowSize - previousInitialWindowSize;
if (windowSizeDifference != 0)
{
foreach (var stream in _streams.Values)
{
if (!stream.TryUpdateOutputWindow(windowSizeDifference))
{
// This means that this caused a stream window to become larger than int.MaxValue.
// This can never happen with a well behaved client and MUST be treated as a connection error.
// https://httpwg.org/specs/rfc7540.html#rfc.section.6.9.2
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorInitialWindowSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR);
}
}
}
return _frameWriter.WriteSettingsAckAsync();
}
catch (Http2SettingsParameterOutOfRangeException ex)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorSettingsParameterOutOfRange(ex.Parameter), ex.Parameter == Http2SettingsParameter.SETTINGS_INITIAL_WINDOW_SIZE
? Http2ErrorCode.FLOW_CONTROL_ERROR
: Http2ErrorCode.PROTOCOL_ERROR);
}
}
private Task ProcessPingFrameAsync()
{
if (_currentHeadersStream != null)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.StreamId != 0)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.Length != 8)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 8), Http2ErrorCode.FRAME_SIZE_ERROR);
}
if ((_incomingFrame.PingFlags & Http2PingFrameFlags.ACK) == Http2PingFrameFlags.ACK)
{
// TODO: verify that payload is equal to the outgoing PING frame
return Task.CompletedTask;
}
return _frameWriter.WritePingAsync(Http2PingFrameFlags.ACK, _incomingFrame.Payload);
}
private Task ProcessGoAwayFrameAsync()
{
if (_currentHeadersStream != null)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.StreamId != 0)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdNotZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
}
StopProcessingNextRequest();
return Task.CompletedTask;
}
private Task ProcessWindowUpdateFrameAsync()
{
if (_currentHeadersStream != null)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.Length != 4)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(_incomingFrame.Type, 4), Http2ErrorCode.FRAME_SIZE_ERROR);
}
ThrowIfIncomingFrameSentToIdleStream();
if (_incomingFrame.WindowUpdateSizeIncrement == 0)
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.6.9
// A receiver MUST treat the receipt of a WINDOW_UPDATE
// frame with an flow-control window increment of 0 as a
// stream error (Section 5.4.2) of type PROTOCOL_ERROR;
// errors on the connection flow-control window MUST be
// treated as a connection error (Section 5.4.1).
//
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.4.1
// An endpoint can end a connection at any time. In
// particular, an endpoint MAY choose to treat a stream
// error as a connection error.
//
// Since server initiated stream resets are not yet properly
// implemented and tested, we treat all zero length window
// increments as connection errors for now.
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateIncrementZero, Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.StreamId == 0)
{
if (!_frameWriter.TryUpdateConnectionWindow(_incomingFrame.WindowUpdateSizeIncrement))
{
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR);
}
}
else if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
{
if (!stream.TryUpdateOutputWindow(_incomingFrame.WindowUpdateSizeIncrement))
{
throw new Http2StreamErrorException(_incomingFrame.StreamId, CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR);
}
}
else
{
// The stream was not found in the dictionary which means the stream was probably closed. This can
// happen when the client sends a window update for a stream right as the server closes the same stream
// Since this is an unavoidable race, we just ignore the window update frame.
}
return Task.CompletedTask;
}
private Task ProcessContinuationFrameAsync<TContext>(IHttpApplication<TContext> application)
{
if (_currentHeadersStream == null)
{
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorContinuationWithNoHeaders, Http2ErrorCode.PROTOCOL_ERROR);
}
if (_incomingFrame.StreamId != _currentHeadersStream.StreamId)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
var endHeaders = (_incomingFrame.ContinuationFlags & Http2ContinuationFrameFlags.END_HEADERS) == Http2ContinuationFrameFlags.END_HEADERS;
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
{
return DecodeTrailersAsync(endHeaders, _incomingFrame.Payload);
}
else
{
return DecodeHeadersAsync(application, endHeaders, _incomingFrame.Payload);
}
}
private Task ProcessUnknownFrameAsync()
{
if (_currentHeadersStream != null)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorHeadersInterleaved(_incomingFrame.Type, _incomingFrame.StreamId, _currentHeadersStream.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
return Task.CompletedTask;
}
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, Span<byte> payload)
{
try
{
_hpackDecoder.Decode(payload, endHeaders, handler: this);
if (endHeaders)
{
StartStream(application);
ResetRequestHeaderParsingState();
}
}
catch (Http2StreamErrorException)
{
ResetRequestHeaderParsingState();
throw;
}
return Task.CompletedTask;
}
private Task DecodeTrailersAsync(bool endHeaders, Span<byte> payload)
{
_hpackDecoder.Decode(payload, endHeaders, handler: this);
if (endHeaders)
{
_currentHeadersStream.OnEndStreamReceived();
ResetRequestHeaderParsingState();
}
return Task.CompletedTask;
}
private void StartStream<TContext>(IHttpApplication<TContext> application)
{
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, CoreStrings.Http2ErrorMissingMandatoryPseudoHeaderFields, Http2ErrorCode.PROTOCOL_ERROR);
}
// This must be initialized before we offload the request or else we may start processing request body frames without it.
_currentHeadersStream.InputRemaining = _currentHeadersStream.RequestHeaders.ContentLength;
// This must wait until we've received all of the headers so we can verify the content-length.
if ((_headerFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM)
{
_currentHeadersStream.OnEndStreamReceived();
}
_streams[_incomingFrame.StreamId] = _currentHeadersStream;
// Must not allow app code to block the connection handling loop.
ThreadPool.UnsafeQueueUserWorkItem(state =>
{
var (app, currentStream) = (Tuple<IHttpApplication<TContext>, Http2Stream>)state;
_ = currentStream.ProcessRequestsAsync(app);
},
new Tuple<IHttpApplication<TContext>, Http2Stream>(application, _currentHeadersStream));
}
private void ResetRequestHeaderParsingState()
{
if (_requestHeaderParsingState != RequestHeaderParsingState.Trailers)
{
_highestOpenedStreamId = _currentHeadersStream.StreamId;
}
_currentHeadersStream = null;
_requestHeaderParsingState = RequestHeaderParsingState.Ready;
_parsedPseudoHeaderFields = PseudoHeaderFields.None;
_headerFlags = Http2HeadersFrameFlags.NONE;
_isMethodConnect = false;
}
private void ThrowIfIncomingFrameSentToIdleStream()
{
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
// 5.1. Stream states
// ...
// idle:
// ...
// Receiving any frame other than HEADERS or PRIORITY on a stream in this state MUST be
// treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
//
// If the stream ID in the incoming frame is higher than the highest opened stream ID so
// far, then the incoming frame's target stream is in the idle state, which is the implicit
// initial state for all streams.
if (_incomingFrame.StreamId > _highestOpenedStreamId)
{
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdle(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.PROTOCOL_ERROR);
}
}
private void AbortStream(int streamId, ConnectionAbortedException error)
{
if (_streams.TryGetValue(streamId, out var stream))
{
stream.Abort(error);
}
}
void IHttp2StreamLifetimeHandler.OnStreamCompleted(int streamId)
{
_streams.TryRemove(streamId, out _);
}
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)
{
// 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);
}
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
{
// Pseudo-header fields MUST NOT appear in trailers.
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorTrailersContainPseudoHeaderField, 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, 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);
}
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);
}
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, CoreStrings.Http2ErrorConnectionSpecificHeaderField, 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)
{
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
{
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorTrailerNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
}
else
{
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorHeaderNameUppercase, Http2ErrorCode.PROTOCOL_ERROR);
}
}
}
}
private bool IsPseudoHeaderField(Span<byte> name, out PseudoHeaderFields headerField)
{
headerField = PseudoHeaderFields.None;
if (name.IsEmpty || name[0] != (byte)':')
{
return false;
}
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)
{
}
void ITimeoutControl.ResetTimeout(long ticks, TimeoutAction timeoutAction)
{
}
void ITimeoutControl.CancelTimeout()
{
}
void ITimeoutControl.StartTimingReads()
{
}
void ITimeoutControl.PauseTimingReads()
{
}
void ITimeoutControl.ResumeTimingReads()
{
}
void ITimeoutControl.StopTimingReads()
{
}
void ITimeoutControl.BytesRead(long count)
{
}
void ITimeoutControl.StartTimingWrite(long size)
{
}
void ITimeoutControl.StopTimingWrite()
{
}
}
}