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 ef6ecb18a4b4..d95056e48cbd 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs @@ -7714,12 +7714,11 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value, boo } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public unsafe bool TryHPackAppend(int index, ReadOnlySpan value) + public unsafe bool TryHPackAppend(int index, ReadOnlySpan value, bool checkForNewlineChars) { ref StringValues values = ref Unsafe.AsRef(null); var nameStr = string.Empty; var flag = 0L; - var checkForNewlineChars = true; // Does the HPack static index match any "known" headers switch (index) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs index 9881ee7ba498..b8265cda4fad 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs @@ -1265,7 +1265,12 @@ private void UpdateConnectionState() public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) { - OnHeaderCore(index: null, indexedValue: false, name, value); + OnHeaderCore(HeaderType.NameAndValue, staticTableIndex: null, name, value); + } + + public void OnDynamicIndexedHeader(int? index, ReadOnlySpan name, ReadOnlySpan value) + { + OnHeaderCore(HeaderType.Dynamic, index, name, value); } public void OnStaticIndexedHeader(int index) @@ -1273,20 +1278,28 @@ public void OnStaticIndexedHeader(int index) Debug.Assert(index <= H2StaticTable.Count); ref readonly var entry = ref H2StaticTable.Get(index - 1); - OnHeaderCore(index, indexedValue: true, entry.Name, entry.Value); + OnHeaderCore(HeaderType.Static, index, entry.Name, entry.Value); } public void OnStaticIndexedHeader(int index, ReadOnlySpan value) { Debug.Assert(index <= H2StaticTable.Count); - OnHeaderCore(index, indexedValue: false, H2StaticTable.Get(index - 1).Name, value); + OnHeaderCore(HeaderType.StaticAndValue, index, H2StaticTable.Get(index - 1).Name, value); + } + + private enum HeaderType + { + Static, + StaticAndValue, + Dynamic, + NameAndValue } // 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. - private void OnHeaderCore(int? index, bool indexedValue, ReadOnlySpan name, ReadOnlySpan value) + private void OnHeaderCore(HeaderType headerType, int? staticTableIndex, ReadOnlySpan name, ReadOnlySpan value) { Debug.Assert(_currentHeadersStream != null); @@ -1298,32 +1311,59 @@ private void OnHeaderCore(int? index, bool indexedValue, ReadOnlySpan name throw new Http2ConnectionErrorException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http2ErrorCode.PROTOCOL_ERROR); } - if (index != null) - { - ValidateStaticHeader(index.Value, value); - } - else - { - ValidateHeader(name, value); - } - try { if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) { + // Just use name + value bytes and do full validation for request trailers. + // Potential performance improvement here to check for indexed headers and optimize validation. + UpdateHeaderParsingState(value, GetPseudoHeaderField(name)); + ValidateHeaderContent(name, value); + _currentHeadersStream.OnTrailer(name, value); } else { // Throws BadRequest for header count limit breaches. // Throws InvalidOperation for bad encoding. - if (index != null) + switch (headerType) { - _currentHeadersStream.OnHeader(index.Value, indexedValue, name, value); - } - else - { - _currentHeadersStream.OnHeader(name, value, checkForNewlineChars : true); + case HeaderType.Static: + UpdateHeaderParsingState(value, GetPseudoHeaderField(staticTableIndex.GetValueOrDefault())); + + _currentHeadersStream.OnHeader(staticTableIndex.GetValueOrDefault(), indexedValue: true, name, value); + break; + case HeaderType.StaticAndValue: + UpdateHeaderParsingState(value, GetPseudoHeaderField(staticTableIndex.GetValueOrDefault())); + + // Value is new will get validated (i.e. check value doesn't contain newlines) + _currentHeadersStream.OnHeader(staticTableIndex.GetValueOrDefault(), indexedValue: false, name, value); + break; + case HeaderType.Dynamic: + // It is faster to set a header using a static table index than a name. + if (staticTableIndex != null) + { + UpdateHeaderParsingState(value, GetPseudoHeaderField(staticTableIndex.GetValueOrDefault())); + + _currentHeadersStream.OnHeader(staticTableIndex.GetValueOrDefault(), indexedValue: true, name, value); + } + else + { + UpdateHeaderParsingState(value, GetPseudoHeaderField(name)); + + _currentHeadersStream.OnHeader(name, value, checkForNewlineChars: false); + } + break; + case HeaderType.NameAndValue: + UpdateHeaderParsingState(value, GetPseudoHeaderField(name)); + + // Header and value are new and will get validated (i.e. check name is lower-case, check value doesn't contain newlines) + ValidateHeaderContent(name, value); + _currentHeadersStream.OnHeader(name, value, checkForNewlineChars: true); + break; + default: + Debug.Fail($"Unexpected header type: {headerType}"); + break; } } } @@ -1340,12 +1380,8 @@ private void OnHeaderCore(int? index, bool indexedValue, ReadOnlySpan name public void OnHeadersComplete(bool endStream) => _currentHeadersStream!.OnHeadersComplete(); - private void ValidateHeader(ReadOnlySpan name, ReadOnlySpan value) + private void ValidateHeaderContent(ReadOnlySpan name, ReadOnlySpan value) { - // Clients will normally send pseudo headers as an indexed header. - // Because pseudo headers can still be sent by name we need to check for them. - UpdateHeaderParsingState(value, GetPseudoHeaderField(name)); - if (IsConnectionSpecificHeaderField(name, value)) { throw new Http2ConnectionErrorException(CoreStrings.HttpErrorConnectionSpecificHeaderField, Http2ErrorCode.PROTOCOL_ERROR); @@ -1369,33 +1405,6 @@ private void ValidateHeader(ReadOnlySpan name, ReadOnlySpan value) } } - private void ValidateStaticHeader(int index, ReadOnlySpan value) - { - var headerField = index switch - { - 1 => PseudoHeaderFields.Authority, - 2 => PseudoHeaderFields.Method, - 3 => PseudoHeaderFields.Method, - 4 => PseudoHeaderFields.Path, - 5 => PseudoHeaderFields.Path, - 6 => PseudoHeaderFields.Scheme, - 7 => PseudoHeaderFields.Scheme, - 8 => PseudoHeaderFields.Status, - 9 => PseudoHeaderFields.Status, - 10 => PseudoHeaderFields.Status, - 11 => PseudoHeaderFields.Status, - 12 => PseudoHeaderFields.Status, - 13 => PseudoHeaderFields.Status, - 14 => PseudoHeaderFields.Status, - _ => PseudoHeaderFields.None - }; - - UpdateHeaderParsingState(value, headerField); - - // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2 - // No need to validate if header name if it is specified using a static index. - } - private void UpdateHeaderParsingState(ReadOnlySpan value, PseudoHeaderFields headerField) { // http://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.1 @@ -1464,6 +1473,32 @@ implementations to these vulnerabilities.*/ } } + private static PseudoHeaderFields GetPseudoHeaderField(int staticTableIndex) + { + Debug.Assert(staticTableIndex > 0, "Static table starts at 1."); + + var headerField = staticTableIndex switch + { + 1 => PseudoHeaderFields.Authority, + 2 => PseudoHeaderFields.Method, + 3 => PseudoHeaderFields.Method, + 4 => PseudoHeaderFields.Path, + 5 => PseudoHeaderFields.Path, + 6 => PseudoHeaderFields.Scheme, + 7 => PseudoHeaderFields.Scheme, + 8 => PseudoHeaderFields.Status, + 9 => PseudoHeaderFields.Status, + 10 => PseudoHeaderFields.Status, + 11 => PseudoHeaderFields.Status, + 12 => PseudoHeaderFields.Status, + 13 => PseudoHeaderFields.Status, + 14 => PseudoHeaderFields.Status, + _ => PseudoHeaderFields.None + }; + + return headerField; + } + private static PseudoHeaderFields GetPseudoHeaderField(ReadOnlySpan name) { if (name.IsEmpty || name[0] != (byte)':') diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs index 39d3539276cc..fa58b86e56a5 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs @@ -675,7 +675,9 @@ public override void OnHeader(int index, bool indexedValue, ReadOnlySpan n // HPack append will return false if the index is not a known request header. // For example, someone could send the index of "Server" (a response header) in the request. // If that happens then fallback to using Append with the name bytes. - if (!HttpRequestHeaders.TryHPackAppend(index, value)) + // + // If the value is indexed then we know it doesn't contain new lines and can skip checking. + if (!HttpRequestHeaders.TryHPackAppend(index, value, checkForNewlineChars: !indexedValue)) { AppendHeader(name, value); } diff --git a/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt b/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..50a85cd62928 100644 --- a/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt +++ b/src/Servers/Kestrel/Core/src/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.IHttpHeadersHandler.OnDynamicIndexedHeader(int? index, System.ReadOnlySpan name, System.ReadOnlySpan value) -> void diff --git a/src/Servers/Kestrel/shared/KnownHeaders.cs b/src/Servers/Kestrel/shared/KnownHeaders.cs index 73a3e6e8310d..dd307915080c 100644 --- a/src/Servers/Kestrel/shared/KnownHeaders.cs +++ b/src/Servers/Kestrel/shared/KnownHeaders.cs @@ -1303,12 +1303,11 @@ public unsafe void Append(ReadOnlySpan name, ReadOnlySpan value, boo }} [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public unsafe bool TryHPackAppend(int index, ReadOnlySpan value) + public unsafe bool TryHPackAppend(int index, ReadOnlySpan value, bool checkForNewlineChars) {{ ref StringValues values = ref Unsafe.AsRef(null); var nameStr = string.Empty; var flag = 0L; - var checkForNewlineChars = true; // Does the HPack static index match any ""known"" headers {AppendHPackSwitch(GroupHPack(loop.Headers))} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs index ccaf3aa9ad17..ed87f2a05be2 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs @@ -26,6 +26,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests; public class Http2StreamTests : Http2TestBase { + [Theory] + [InlineData("\r")] + [InlineData("\n")] + [InlineData("\r\n")] + public async Task HEADERS_Received_NewLineCharactersInValue_ConnectionError(string headerValue) + { + var headers = new[] + { + new KeyValuePair(HeaderNames.Method, "GET"), + new KeyValuePair(HeaderNames.Path, "/"), + new KeyValuePair(HeaderNames.Scheme, "http"), + new KeyValuePair(HeaderNames.Authority, "localhost:80"), + new KeyValuePair("TestHeader", headerValue), + }; + await InitializeConnectionAsync(_noopApplication); + + await StartStreamAsync(1, headers, endStream: true); + + await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: false, 1, Http2ErrorCode.PROTOCOL_ERROR, "Malformed request: invalid headers."); + } + [Fact] public async Task HEADERS_Received_EmptyMethod_Reset() { diff --git a/src/Shared/runtime/Http2/Hpack/DynamicTable.cs b/src/Shared/runtime/Http2/Hpack/DynamicTable.cs index 7b496baf83ee..d57242b0521d 100644 --- a/src/Shared/runtime/Http2/Hpack/DynamicTable.cs +++ b/src/Shared/runtime/Http2/Hpack/DynamicTable.cs @@ -46,6 +46,11 @@ public ref readonly HeaderField this[int index] } public void Insert(ReadOnlySpan name, ReadOnlySpan value) + { + Insert(staticTableIndex: null, name, value); + } + + public void Insert(int? staticTableIndex, ReadOnlySpan name, ReadOnlySpan value) { int entryLength = HeaderField.GetLength(name.Length, value.Length); EnsureAvailable(entryLength); @@ -59,7 +64,7 @@ public void Insert(ReadOnlySpan name, ReadOnlySpan value) return; } - var entry = new HeaderField(name, value); + var entry = new HeaderField(staticTableIndex, name, value); _buffer[_insertIndex] = entry; _insertIndex = (_insertIndex + 1) % _buffer.Length; _size += entry.Length; diff --git a/src/Shared/runtime/Http2/Hpack/H2StaticTable.Http2.cs b/src/Shared/runtime/Http2/Hpack/H2StaticTable.Http2.cs index a4db64ab5862..29ee89f034e2 100644 --- a/src/Shared/runtime/Http2/Hpack/H2StaticTable.Http2.cs +++ b/src/Shared/runtime/Http2/Hpack/H2StaticTable.Http2.cs @@ -30,74 +30,75 @@ public static bool TryGetStatusIndex(int status, out int index) private static readonly HeaderField[] s_staticDecoderTable = new HeaderField[] { - CreateHeaderField(":authority", ""), - CreateHeaderField(":method", "GET"), - CreateHeaderField(":method", "POST"), - CreateHeaderField(":path", "/"), - CreateHeaderField(":path", "/index.html"), - CreateHeaderField(":scheme", "http"), - CreateHeaderField(":scheme", "https"), - CreateHeaderField(":status", "200"), - CreateHeaderField(":status", "204"), - CreateHeaderField(":status", "206"), - CreateHeaderField(":status", "304"), - CreateHeaderField(":status", "400"), - CreateHeaderField(":status", "404"), - CreateHeaderField(":status", "500"), - CreateHeaderField("accept-charset", ""), - CreateHeaderField("accept-encoding", "gzip, deflate"), - CreateHeaderField("accept-language", ""), - CreateHeaderField("accept-ranges", ""), - CreateHeaderField("accept", ""), - CreateHeaderField("access-control-allow-origin", ""), - CreateHeaderField("age", ""), - CreateHeaderField("allow", ""), - CreateHeaderField("authorization", ""), - CreateHeaderField("cache-control", ""), - CreateHeaderField("content-disposition", ""), - CreateHeaderField("content-encoding", ""), - CreateHeaderField("content-language", ""), - CreateHeaderField("content-length", ""), - CreateHeaderField("content-location", ""), - CreateHeaderField("content-range", ""), - CreateHeaderField("content-type", ""), - CreateHeaderField("cookie", ""), - CreateHeaderField("date", ""), - CreateHeaderField("etag", ""), - CreateHeaderField("expect", ""), - CreateHeaderField("expires", ""), - CreateHeaderField("from", ""), - CreateHeaderField("host", ""), - CreateHeaderField("if-match", ""), - CreateHeaderField("if-modified-since", ""), - CreateHeaderField("if-none-match", ""), - CreateHeaderField("if-range", ""), - CreateHeaderField("if-unmodified-since", ""), - CreateHeaderField("last-modified", ""), - CreateHeaderField("link", ""), - CreateHeaderField("location", ""), - CreateHeaderField("max-forwards", ""), - CreateHeaderField("proxy-authenticate", ""), - CreateHeaderField("proxy-authorization", ""), - CreateHeaderField("range", ""), - CreateHeaderField("referer", ""), - CreateHeaderField("refresh", ""), - CreateHeaderField("retry-after", ""), - CreateHeaderField("server", ""), - CreateHeaderField("set-cookie", ""), - CreateHeaderField("strict-transport-security", ""), - CreateHeaderField("transfer-encoding", ""), - CreateHeaderField("user-agent", ""), - CreateHeaderField("vary", ""), - CreateHeaderField("via", ""), - CreateHeaderField("www-authenticate", "") + CreateHeaderField(1, ":authority", ""), + CreateHeaderField(2, ":method", "GET"), + CreateHeaderField(3, ":method", "POST"), + CreateHeaderField(4, ":path", "/"), + CreateHeaderField(5, ":path", "/index.html"), + CreateHeaderField(6, ":scheme", "http"), + CreateHeaderField(7, ":scheme", "https"), + CreateHeaderField(8, ":status", "200"), + CreateHeaderField(9, ":status", "204"), + CreateHeaderField(10, ":status", "206"), + CreateHeaderField(11, ":status", "304"), + CreateHeaderField(12, ":status", "400"), + CreateHeaderField(13, ":status", "404"), + CreateHeaderField(14, ":status", "500"), + CreateHeaderField(15, "accept-charset", ""), + CreateHeaderField(16, "accept-encoding", "gzip, deflate"), + CreateHeaderField(17, "accept-language", ""), + CreateHeaderField(18, "accept-ranges", ""), + CreateHeaderField(19, "accept", ""), + CreateHeaderField(20, "access-control-allow-origin", ""), + CreateHeaderField(21, "age", ""), + CreateHeaderField(22, "allow", ""), + CreateHeaderField(23, "authorization", ""), + CreateHeaderField(24, "cache-control", ""), + CreateHeaderField(25, "content-disposition", ""), + CreateHeaderField(26, "content-encoding", ""), + CreateHeaderField(27, "content-language", ""), + CreateHeaderField(28, "content-length", ""), + CreateHeaderField(29, "content-location", ""), + CreateHeaderField(30, "content-range", ""), + CreateHeaderField(31, "content-type", ""), + CreateHeaderField(32, "cookie", ""), + CreateHeaderField(33, "date", ""), + CreateHeaderField(34, "etag", ""), + CreateHeaderField(35, "expect", ""), + CreateHeaderField(36, "expires", ""), + CreateHeaderField(37, "from", ""), + CreateHeaderField(38, "host", ""), + CreateHeaderField(39, "if-match", ""), + CreateHeaderField(40, "if-modified-since", ""), + CreateHeaderField(41, "if-none-match", ""), + CreateHeaderField(42, "if-range", ""), + CreateHeaderField(43, "if-unmodified-since", ""), + CreateHeaderField(44, "last-modified", ""), + CreateHeaderField(45, "link", ""), + CreateHeaderField(46, "location", ""), + CreateHeaderField(47, "max-forwards", ""), + CreateHeaderField(48, "proxy-authenticate", ""), + CreateHeaderField(49, "proxy-authorization", ""), + CreateHeaderField(50, "range", ""), + CreateHeaderField(51, "referer", ""), + CreateHeaderField(52, "refresh", ""), + CreateHeaderField(53, "retry-after", ""), + CreateHeaderField(54, "server", ""), + CreateHeaderField(55, "set-cookie", ""), + CreateHeaderField(56, "strict-transport-security", ""), + CreateHeaderField(57, "transfer-encoding", ""), + CreateHeaderField(58, "user-agent", ""), + CreateHeaderField(59, "vary", ""), + CreateHeaderField(60, "via", ""), + CreateHeaderField(61, "www-authenticate", "") }; // TODO: The HeaderField constructor will allocate and copy again. We should avoid this. // Tackle as part of header table allocation strategy in general (see note in HeaderField constructor). - private static HeaderField CreateHeaderField(string name, string value) => + private static HeaderField CreateHeaderField(int staticTableIndex, string name, string value) => new HeaderField( + staticTableIndex, Encoding.ASCII.GetBytes(name), value.Length != 0 ? Encoding.ASCII.GetBytes(value) : Array.Empty()); } diff --git a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs index 526defaf5e19..c287478bbee7 100644 --- a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs +++ b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs @@ -508,7 +508,7 @@ private void ProcessHeaderValue(ReadOnlySpan data, IHttpHeadersHandler han if (_index) { - _dynamicTable.Insert(H2StaticTable.Get(_headerStaticIndex - 1).Name, headerValueSpan); + _dynamicTable.Insert(_headerStaticIndex, H2StaticTable.Get(_headerStaticIndex - 1).Name, headerValueSpan); } } else @@ -548,7 +548,7 @@ private void OnIndexedHeaderField(int index, IHttpHeadersHandler handler) else { ref readonly HeaderField header = ref GetDynamicHeader(index); - handler.OnHeader(header.Name, header.Value); + handler.OnDynamicIndexedHeader(header.StaticTableIndex, header.Name, header.Value); } _state = State.Ready; diff --git a/src/Shared/runtime/Http2/Hpack/HeaderField.cs b/src/Shared/runtime/Http2/Hpack/HeaderField.cs index aff43658c3ab..957574c77c1b 100644 --- a/src/Shared/runtime/Http2/Hpack/HeaderField.cs +++ b/src/Shared/runtime/Http2/Hpack/HeaderField.cs @@ -11,8 +11,12 @@ internal readonly struct HeaderField // http://httpwg.org/specs/rfc7541.html#rfc.section.4.1 public const int RfcOverhead = 32; - public HeaderField(ReadOnlySpan name, ReadOnlySpan value) + public HeaderField(int? staticTableIndex, ReadOnlySpan name, ReadOnlySpan value) { + // Store the static table index (if there is one) for the header field. + // ASP.NET Core has a fast path that sets a header value using the static table index instead of the name. + StaticTableIndex = staticTableIndex; + Debug.Assert(name.Length > 0); // TODO: We're allocating here on every new table entry. @@ -24,6 +28,8 @@ public HeaderField(ReadOnlySpan name, ReadOnlySpan value) Value = value.ToArray(); } + public int? StaticTableIndex { get; } + public byte[] Name { get; } public byte[] Value { get; } diff --git a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs index 6e5822cd4ad4..9b7e6a2716ec 100644 --- a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs +++ b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs @@ -308,7 +308,7 @@ private void ParseHeaderName(ReadOnlySpan data, ref int currentIndex, IHtt // Copy string to temporary buffer. EnsureStringCapacity(ref _stringOctets, _stringIndex + count, existingLength: _stringIndex); data.Slice(currentIndex, count).CopyTo(_stringOctets.AsSpan(_stringIndex)); - + _stringIndex += count; currentIndex += count; diff --git a/src/Shared/runtime/IHttpHeadersHandler.cs b/src/Shared/runtime/IHttpHeadersHandler.cs index 824889cf79da..96dfae364252 100644 --- a/src/Shared/runtime/IHttpHeadersHandler.cs +++ b/src/Shared/runtime/IHttpHeadersHandler.cs @@ -21,5 +21,11 @@ interface IHttpHeadersHandler void OnStaticIndexedHeader(int index, ReadOnlySpan value); void OnHeader(ReadOnlySpan name, ReadOnlySpan value); void OnHeadersComplete(bool endStream); + + // DIM to avoid breaking change on public interface (for Kestrel). + public void OnDynamicIndexedHeader(int? index, ReadOnlySpan name, ReadOnlySpan value) + { + OnHeader(name, value); + } } } diff --git a/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs b/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs index eaa9c6cb8c2e..9576a9e67b5f 100644 --- a/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs +++ b/src/Shared/test/Shared.Tests/runtime/Http2/DynamicTableTest.cs @@ -13,8 +13,8 @@ namespace System.Net.Http.Unit.Tests.HPack { public class DynamicTableTest { - private readonly HeaderField _header1 = new HeaderField(Encoding.ASCII.GetBytes("header-1"), Encoding.ASCII.GetBytes("value1")); - private readonly HeaderField _header2 = new HeaderField(Encoding.ASCII.GetBytes("header-02"), Encoding.ASCII.GetBytes("value_2")); + private readonly HeaderField _header1 = new HeaderField(staticTableIndex: null, Encoding.ASCII.GetBytes("header-1"), Encoding.ASCII.GetBytes("value1")); + private readonly HeaderField _header2 = new HeaderField(staticTableIndex: null, Encoding.ASCII.GetBytes("header-02"), Encoding.ASCII.GetBytes("value_2")); [Fact] public void DynamicTable_IsInitiallyEmpty()