diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs index 73a18545733e..d89e65f6062a 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs @@ -227,7 +227,14 @@ public override StringValues HeaderConnection } set { - _bits |= 0x2L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x2L; + } + else + { + _bits &= ~0x2L; + } _headers._Connection = value; } } @@ -244,7 +251,14 @@ public StringValues HeaderHost } set { - _bits |= 0x4L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x4L; + } + else + { + _bits &= ~0x4L; + } _headers._Host = value; } } @@ -261,7 +275,14 @@ public StringValues HeaderAuthority } set { - _bits |= 0x10L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x10L; + } + else + { + _bits &= ~0x10L; + } _headers._Authority = value; } } @@ -278,7 +299,14 @@ public StringValues HeaderMethod } set { - _bits |= 0x20L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x20L; + } + else + { + _bits &= ~0x20L; + } _headers._Method = value; } } @@ -295,7 +323,14 @@ public StringValues HeaderPath } set { - _bits |= 0x40L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x40L; + } + else + { + _bits &= ~0x40L; + } _headers._Path = value; } } @@ -312,7 +347,14 @@ public StringValues HeaderScheme } set { - _bits |= 0x80L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x80L; + } + else + { + _bits &= ~0x80L; + } _headers._Scheme = value; } } @@ -329,7 +371,14 @@ public StringValues HeaderTransferEncoding } set { - _bits |= 0x20000000000L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x20000000000L; + } + else + { + _bits &= ~0x20000000000L; + } _headers._TransferEncoding = value; } } @@ -8234,7 +8283,14 @@ public override StringValues HeaderConnection } set { - _bits |= 0x1L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x1L; + } + else + { + _bits &= ~0x1L; + } _headers._Connection = value; _headers._rawConnection = null; } @@ -8252,7 +8308,14 @@ public StringValues HeaderAllow } set { - _bits |= 0x1000L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x1000L; + } + else + { + _bits &= ~0x1000L; + } _headers._Allow = value; } } @@ -8269,7 +8332,14 @@ public StringValues HeaderAltSvc } set { - _bits |= 0x2000L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x2000L; + } + else + { + _bits &= ~0x2000L; + } _headers._AltSvc = value; _headers._rawAltSvc = null; } @@ -8287,7 +8357,14 @@ public StringValues HeaderTransferEncoding } set { - _bits |= 0x100000000L; + if (!StringValues.IsNullOrEmpty(value)) + { + _bits |= 0x100000000L; + } + else + { + _bits &= ~0x100000000L; + } _headers._TransferEncoding = value; _headers._rawTransferEncoding = null; } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs index f3808c90ea30..eb5294b2a134 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs @@ -248,13 +248,14 @@ private bool TryValidatePseudoHeaders() // enabling the use of HTTP to interact with non - HTTP services. // A common example is TLS termination. var headerScheme = HttpRequestHeaders.HeaderScheme.ToString(); + HttpRequestHeaders.HeaderScheme = default; // Suppress pseduo headers from the public headers collection. if (!ReferenceEquals(headerScheme, Scheme) && !string.Equals(headerScheme, Scheme, StringComparison.OrdinalIgnoreCase)) { if (!ServerOptions.AllowAlternateSchemes || !Uri.CheckSchemeName(headerScheme)) { ResetAndAbort(new ConnectionAbortedException( - CoreStrings.FormatHttp2StreamErrorSchemeMismatch(HttpRequestHeaders.HeaderScheme, Scheme)), Http2ErrorCode.PROTOCOL_ERROR); + CoreStrings.FormatHttp2StreamErrorSchemeMismatch(headerScheme, Scheme)), Http2ErrorCode.PROTOCOL_ERROR); return false; } @@ -264,6 +265,7 @@ private bool TryValidatePseudoHeaders() // :path (and query) - Required // Must start with / except may be * for OPTIONS var path = HttpRequestHeaders.HeaderPath.ToString(); + HttpRequestHeaders.HeaderPath = default; // Suppress pseduo headers from the public headers collection. RawTarget = path; // OPTIONS - https://tools.ietf.org/html/rfc7540#section-8.1.2.3 @@ -301,6 +303,7 @@ private bool TryValidateMethod() { // :method _methodText = HttpRequestHeaders.HeaderMethod.ToString(); + HttpRequestHeaders.HeaderMethod = default; // Suppress pseduo headers from the public headers collection. Method = HttpUtilities.GetKnownMethod(_methodText); if (Method == HttpMethod.None) @@ -327,6 +330,7 @@ private bool TryValidateAuthorityAndHost(out string hostText) // Prefer this over Host var authority = HttpRequestHeaders.HeaderAuthority; + HttpRequestHeaders.HeaderAuthority = default; // Suppress pseduo headers from the public headers collection. var host = HttpRequestHeaders.HeaderHost; if (!StringValues.IsNullOrEmpty(authority)) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index c388a0735ba8..ea69197bc27e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -787,12 +787,13 @@ private bool TryValidatePseudoHeaders() // proxy or gateway can translate requests for non - HTTP schemes, // enabling the use of HTTP to interact with non - HTTP services. var headerScheme = HttpRequestHeaders.HeaderScheme.ToString(); + HttpRequestHeaders.HeaderScheme = default; // Suppress pseduo headers from the public headers collection. if (!ReferenceEquals(headerScheme, Scheme) && !string.Equals(headerScheme, Scheme, StringComparison.OrdinalIgnoreCase)) { if (!ServerOptions.AllowAlternateSchemes || !Uri.CheckSchemeName(headerScheme)) { - var str = CoreStrings.FormatHttp3StreamErrorSchemeMismatch(RequestHeaders[HeaderNames.Scheme], Scheme); + var str = CoreStrings.FormatHttp3StreamErrorSchemeMismatch(headerScheme, Scheme); Abort(new ConnectionAbortedException(str), Http3ErrorCode.ProtocolError); return false; } @@ -802,7 +803,8 @@ private bool TryValidatePseudoHeaders() // :path (and query) - Required // Must start with / except may be * for OPTIONS - var path = RequestHeaders[HeaderNames.Path].ToString(); + var path = HttpRequestHeaders.HeaderPath.ToString(); + HttpRequestHeaders.HeaderPath = default; // Suppress pseduo headers from the public headers collection. RawTarget = path; // OPTIONS - https://tools.ietf.org/html/rfc7540#section-8.1.2.3 @@ -840,7 +842,8 @@ private bool TryValidatePseudoHeaders() private bool TryValidateMethod() { // :method - _methodText = RequestHeaders[HeaderNames.Method].ToString(); + _methodText = HttpRequestHeaders.HeaderMethod.ToString(); + HttpRequestHeaders.HeaderMethod = default; // Suppress pseduo headers from the public headers collection. Method = HttpUtilities.GetKnownMethod(_methodText); if (Method == Http.HttpMethod.None) @@ -866,7 +869,8 @@ private bool TryValidateAuthorityAndHost(out string hostText) // :authority (optional) // Prefer this over Host - var authority = RequestHeaders[HeaderNames.Authority]; + var authority = HttpRequestHeaders.HeaderAuthority; + HttpRequestHeaders.HeaderAuthority = default; // Suppress pseduo headers from the public headers collection. var host = HttpRequestHeaders.HeaderHost; if (!StringValues.IsNullOrEmpty(authority)) { diff --git a/src/Servers/Kestrel/shared/KnownHeaders.cs b/src/Servers/Kestrel/shared/KnownHeaders.cs index 42ebc67bf276..eedebdcd9118 100644 --- a/src/Servers/Kestrel/shared/KnownHeaders.cs +++ b/src/Servers/Kestrel/shared/KnownHeaders.cs @@ -843,7 +843,14 @@ internal partial class {loop.ClassName} : IHeaderDictionary }} set {{ - {header.SetBit()}; + if (!StringValues.IsNullOrEmpty(value)) + {{ + {header.SetBit()}; + }} + else + {{ + {header.ClearBit()}; + }} _headers._{header.Identifier} = value; {(header.EnhancedSetter == false ? "" : $@" _headers._raw{header.Identifier} = null;")} }}")} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index d90501723df9..284440c1c3b0 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -432,6 +432,7 @@ public async Task HEADERS_Received_SchemeMismatchAllowed_Processed(string scheme await InitializeConnectionAsync(context => { Assert.Equal(scheme, context.Request.Scheme); + Assert.False(context.Request.Headers.ContainsKey(HeaderNames.Scheme)); return Task.CompletedTask; }); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index cd313b6b38a4..e8dafc496451 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -136,6 +136,7 @@ protected static IEnumerable> ReadRateRequestHeader protected readonly Dictionary _receivedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); protected readonly Dictionary _receivedTrailers = new Dictionary(StringComparer.OrdinalIgnoreCase); protected readonly Dictionary _decodedHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + protected readonly RequestFields _receivedRequestFields = new RequestFields(); protected readonly HashSet _abortedStreamIds = new HashSet(); protected readonly object _abortedStreamIdsLock = new object(); protected readonly TaskCompletionSource _closingStateReached = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -195,6 +196,10 @@ public Http2TestBase() _readHeadersApplication = context => { + _receivedRequestFields.Method = context.Request.Method; + _receivedRequestFields.Scheme = context.Request.Scheme; + _receivedRequestFields.Path = context.Request.Path.Value; + _receivedRequestFields.RawTarget = context.Features.Get().RawTarget; foreach (var header in context.Request.Headers) { _receivedHeaders[header.Key] = header.Value.ToString(); @@ -217,6 +222,10 @@ public Http2TestBase() Assert.True(context.Request.SupportsTrailers(), "SupportsTrailers"); Assert.True(context.Request.CheckTrailersAvailable(), "SupportsTrailers"); + _receivedRequestFields.Method = context.Request.Method; + _receivedRequestFields.Scheme = context.Request.Scheme; + _receivedRequestFields.Path = context.Request.Path.Value; + _receivedRequestFields.RawTarget = context.Features.Get().RawTarget; foreach (var header in context.Request.Headers) { _receivedHeaders[header.Key] = header.Value.ToString(); @@ -350,6 +359,7 @@ public Http2TestBase() _echoMethodNoBody = context => { Assert.False(context.Request.CanHaveBody()); + Assert.False(context.Request.Headers.ContainsKey(HeaderNames.Method)); context.Response.Headers["Method"] = context.Request.Method; return Task.CompletedTask; @@ -357,6 +367,7 @@ public Http2TestBase() _echoHost = context => { + Assert.False(context.Request.Headers.ContainsKey(HeaderNames.Authority)); context.Response.Headers.Host = context.Request.Headers.Host; return Task.CompletedTask; @@ -364,6 +375,7 @@ public Http2TestBase() _echoPath = context => { + Assert.False(context.Request.Headers.ContainsKey(HeaderNames.Path)); context.Response.Headers["path"] = context.Request.Path.ToString(); context.Response.Headers["rawtarget"] = context.Features.Get().RawTarget; @@ -1240,8 +1252,28 @@ protected void VerifyDecodedRequestHeaders(IEnumerable