diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs index 9db412405a..0a9eb6870c 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Http/Frame.cs @@ -37,6 +37,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http private static Vector _vectorCRs = new Vector((byte)'\r'); private static Vector _vectorColons = new Vector((byte)':'); private static Vector _vectorSpaces = new Vector((byte)' '); + private static Vector _vectorTabs = new Vector((byte)'\t'); private static Vector _vectorQuestionMarks = new Vector((byte)'?'); private static Vector _vectorPercentages = new Vector((byte)'%'); @@ -1033,10 +1034,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http var consumed = scan; try { - int chFirst; - int chSecond; while (!scan.IsEnd) { + var ch = scan.Peek(); + if (ch == -1) + { + return false; + } + else if (ch == '\r') + { + // Check for final CRLF. + scan.Take(); + ch = scan.Take(); + + if (ch == -1) + { + return false; + } + else if (ch == '\n') + { + consumed = scan; + return true; + } + + // Headers don't end in CRLF line. + RejectRequest("Headers corrupted, invalid header sequence."); + } + else if (ch == ' ' || ch == '\t') + { + RejectRequest("Header line must not start with whitespace."); + } + var beginName = scan; if (scan.Seek(ref _vectorColons, ref _vectorCRs) == -1) { @@ -1044,118 +1072,113 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http } var endName = scan; - chFirst = scan.Take(); - var beginValue = scan; - chSecond = scan.Take(); + ch = scan.Take(); + if (ch != ':') + { + RejectRequest("No ':' character found in header line."); + } - if (chFirst == -1 || chSecond == -1) + var validateName = beginName; + if (validateName.Seek(ref _vectorSpaces, ref _vectorTabs, ref _vectorColons) != ':') + { + RejectRequest("Whitespace is not allowed in header name."); + } + + var beginValue = scan; + ch = scan.Peek(); + + if (ch == -1) { return false; } - if (chFirst == '\r') + + // Skip header value leading whitespace. + while (ch == ' ' || ch == '\t') { - if (chSecond == '\n') - { - consumed = scan; - return true; - } - - RejectRequest("Headers corrupted, invalid header sequence."); - // Headers corrupted, parsing headers is complete - return true; - } - - while ( - chSecond == ' ' || - chSecond == '\t' || - chSecond == '\r' || - chSecond == '\n') - { - if (chSecond == '\r') - { - var scanAhead = scan; - var chAhead = scanAhead.Take(); - if (chAhead == -1) - { - return false; - } - else if (chAhead == '\n') - { - chAhead = scanAhead.Take(); - if (chAhead == -1) - { - return false; - } - else if (chAhead != ' ' && chAhead != '\t') - { - // If the "\r\n" isn't part of "linear whitespace", - // then this header has no value. - break; - } - } - } - + scan.Take(); beginValue = scan; - chSecond = scan.Take(); - if (chSecond == -1) + ch = scan.Peek(); + if (ch == -1) { return false; } } + scan = beginValue; - - var wrapping = false; - while (!scan.IsEnd) + if (scan.Seek(ref _vectorCRs) == -1) { - if (scan.Seek(ref _vectorCRs) == -1) - { - // no "\r" in sight, burn used bytes and go back to await more data - return false; - } - - var endValue = scan; - chFirst = scan.Take(); // expecting: \r - chSecond = scan.Take(); // expecting: \n - - if (chSecond == -1) - { - return false; - } - else if (chSecond != '\n') - { - // "\r" was all by itself, move just after it and try again - scan = endValue; - scan.Take(); - continue; - } - - var chThird = scan.Peek(); - if (chThird == -1) - { - return false; - } - else if (chThird == ' ' || chThird == '\t') - { - // special case, "\r\n " or "\r\n\t". - // this is considered wrapping"linear whitespace" and is actually part of the header value - // continue past this for the next - wrapping = true; - continue; - } - - var name = beginName.GetArraySegment(endName); - var value = beginValue.GetAsciiString(endValue); - if (wrapping) - { - value = value.Replace("\r\n", " "); - } - - consumed = scan; - requestHeaders.Append(name.Array, name.Offset, name.Count, value); - break; + // no "\r" in sight, burn used bytes and go back to await more data + return false; } + + scan.Take(); // we know this is '\r' + ch = scan.Take(); // expecting '\n' + + if (ch == -1) + { + return false; + } + else if (ch != '\n') + { + RejectRequest("Header line must end in CRLF; only CR found."); + } + + var next = scan.Peek(); + if (next == -1) + { + return false; + } + else if (next == ' ' || next == '\t') + { + // From https://tools.ietf.org/html/rfc7230#section-3.2.4: + // + // Historically, HTTP header field values could be extended over + // multiple lines by preceding each extra line with at least one space + // or horizontal tab (obs-fold). This specification deprecates such + // line folding except within the message/http media type + // (Section 8.3.1). A sender MUST NOT generate a message that includes + // line folding (i.e., that has any field-value that contains a match to + // the obs-fold rule) unless the message is intended for packaging + // within the message/http media type. + // + // A server that receives an obs-fold in a request message that is not + // within a message/http container MUST either reject the message by + // sending a 400 (Bad Request), preferably with a representation + // explaining that obsolete line folding is unacceptable, or replace + // each received obs-fold with one or more SP octets prior to + // interpreting the field value or forwarding the message downstream. + RejectRequest("Header value line folding not supported."); + } + + // Trim trailing whitespace from header value by repeatedly advancing to next + // whitespace or CR. + // + // - If CR is found, this is the end of the header value. + // - If whitespace is found, this is the _tentative_ end of the header value. + // If non-whitespace is found after it and it's not CR, seek again to the next + // whitespace or CR for a new (possibly tentative) end of value. + var ws = beginValue; + var endValue = scan; + do + { + ws.Seek(ref _vectorSpaces, ref _vectorTabs, ref _vectorCRs); + endValue = ws; + + ch = ws.Take(); + while (ch == ' ' || ch == '\t') + { + ch = ws.Take(); + } + } while (ch != '\r'); + + var name = beginName.GetArraySegment(endName); + var value = beginValue.GetAsciiString(endValue); + + consumed = scan; + requestHeaders.Append(name.Array, name.Offset, name.Count, value); } + return false; } finally diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/BadHttpRequestTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/BadHttpRequestTests.cs index dd3380be64..8d1a0574a6 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/BadHttpRequestTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/BadHttpRequestTests.cs @@ -136,6 +136,42 @@ namespace Microsoft.AspNetCore.Server.KestrelTests } } + [Theory] + // Missing final CRLF + [InlineData("Header-1: value1\r\nHeader-2: value2\r\n")] + // Leading whitespace + [InlineData(" Header-1: value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("\tHeader-1: value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\n Header-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\n\tHeader-2: value2\r\n\r\n")] + // Missing LF + [InlineData("Header-1: value1\rHeader-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader-2: value2\r\r\n")] + // Line folding + [InlineData("Header-1: multi\r\n line\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader-2: multi\r\n line\r\n\r\n")] + // Missing ':' + [InlineData("Header-1 value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader-2 value2\r\n\r\n")] + // Whitespace in header name + [InlineData("Header 1: value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader 2: value2\r\n\r\n")] + [InlineData("Header-1 : value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header-1\t: value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader-2 : value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader-2\t: value2\r\n\r\n")] + public async Task TestInvalidHeaders(string rawHeaders) + { + using (var server = new TestServer(context => TaskUtilities.CompletedTask)) + { + using (var connection = server.CreateConnection()) + { + await connection.SendAllEnd($"GET / HTTP/1.1\r\n{rawHeaders}"); + await ReceiveBadRequestResponse(connection); + } + } + } + private async Task ReceiveBadRequestResponse(TestConnection connection) { await connection.Receive( diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs index 5146a9040e..62ac9c5164 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/FrameTests.cs @@ -7,6 +7,7 @@ using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel; +using Microsoft.AspNetCore.Server.Kestrel.Exceptions; using Microsoft.AspNetCore.Server.Kestrel.Http; using Microsoft.AspNetCore.Server.Kestrel.Infrastructure; using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers; @@ -16,15 +17,338 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { public class FrameTests { + [Fact] + public void CanReadHeaderValueWithoutLeadingWhitespace() + { + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new LoggingThreadPool(trace); + using (var pool = new MemoryPool()) + using (var socketInput = new SocketInput(pool, ltp)) + { + var connectionContext = new ConnectionContext() + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerAddress = ServerAddress.FromUrl("http://localhost:5000") + }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); + + var headerArray = Encoding.ASCII.GetBytes("Header:value\r\n\r\n"); + socketInput.IncomingData(headerArray, 0, headerArray.Length); + + var success = frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders); + + Assert.True(success); + Assert.Equal(1, frame.RequestHeaders.Count); + Assert.Equal("value", frame.RequestHeaders["Header"]); + + // Assert TakeMessageHeaders consumed all the input + var scan = socketInput.ConsumingStart(); + Assert.True(scan.IsEnd); + } + } + + [Theory] + [InlineData("Header: value\r\n\r\n")] + [InlineData("Header: value\r\n\r\n")] + [InlineData("Header:\tvalue\r\n\r\n")] + [InlineData("Header: \tvalue\r\n\r\n")] + [InlineData("Header:\t value\r\n\r\n")] + [InlineData("Header:\t\tvalue\r\n\r\n")] + [InlineData("Header:\t\t value\r\n\r\n")] + [InlineData("Header: \t\tvalue\r\n\r\n")] + [InlineData("Header: \t\t value\r\n\r\n")] + [InlineData("Header: \t \t value\r\n\r\n")] + public void LeadingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders) + { + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new LoggingThreadPool(trace); + using (var pool = new MemoryPool()) + using (var socketInput = new SocketInput(pool, ltp)) + { + var connectionContext = new ConnectionContext() + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerAddress = ServerAddress.FromUrl("http://localhost:5000") + }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); + + var headerArray = Encoding.ASCII.GetBytes(rawHeaders); + socketInput.IncomingData(headerArray, 0, headerArray.Length); + + var success = frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders); + + Assert.True(success); + Assert.Equal(1, frame.RequestHeaders.Count); + Assert.Equal("value", frame.RequestHeaders["Header"]); + + // Assert TakeMessageHeaders consumed all the input + var scan = socketInput.ConsumingStart(); + Assert.True(scan.IsEnd); + } + } + + [Theory] + [InlineData("Header: value \r\n\r\n")] + [InlineData("Header: value\t\r\n\r\n")] + [InlineData("Header: value \t\r\n\r\n")] + [InlineData("Header: value\t \r\n\r\n")] + [InlineData("Header: value\t\t\r\n\r\n")] + [InlineData("Header: value\t\t \r\n\r\n")] + [InlineData("Header: value \t\t\r\n\r\n")] + [InlineData("Header: value \t\t \r\n\r\n")] + [InlineData("Header: value \t \t \r\n\r\n")] + public void TrailingWhitespaceIsNotIncludedInHeaderValue(string rawHeaders) + { + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new LoggingThreadPool(trace); + using (var pool = new MemoryPool()) + using (var socketInput = new SocketInput(pool, ltp)) + { + var connectionContext = new ConnectionContext() + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); + + var headerArray = Encoding.ASCII.GetBytes(rawHeaders); + socketInput.IncomingData(headerArray, 0, headerArray.Length); + + var success = frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders); + + Assert.True(success); + Assert.Equal(1, frame.RequestHeaders.Count); + Assert.Equal("value", frame.RequestHeaders["Header"]); + + // Assert TakeMessageHeaders consumed all the input + var scan = socketInput.ConsumingStart(); + Assert.True(scan.IsEnd); + } + } + + [Theory] + [InlineData("Header: one two three\r\n\r\n", "one two three")] + [InlineData("Header: one two three\r\n\r\n", "one two three")] + [InlineData("Header: one\ttwo\tthree\r\n\r\n", "one\ttwo\tthree")] + [InlineData("Header: one two\tthree\r\n\r\n", "one two\tthree")] + [InlineData("Header: one\ttwo three\r\n\r\n", "one\ttwo three")] + [InlineData("Header: one \ttwo \tthree\r\n\r\n", "one \ttwo \tthree")] + [InlineData("Header: one\t two\t three\r\n\r\n", "one\t two\t three")] + [InlineData("Header: one \ttwo\t three\r\n\r\n", "one \ttwo\t three")] + public void WhitespaceWithinHeaderValueIsPreserved(string rawHeaders, string expectedValue) + { + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new LoggingThreadPool(trace); + using (var pool = new MemoryPool()) + using (var socketInput = new SocketInput(pool, ltp)) + { + var connectionContext = new ConnectionContext() + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); + + var headerArray = Encoding.ASCII.GetBytes(rawHeaders); + socketInput.IncomingData(headerArray, 0, headerArray.Length); + + var success = frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders); + + Assert.True(success); + Assert.Equal(1, frame.RequestHeaders.Count); + Assert.Equal(expectedValue, frame.RequestHeaders["Header"]); + + // Assert TakeMessageHeaders consumed all the input + var scan = socketInput.ConsumingStart(); + Assert.True(scan.IsEnd); + } + } + + [Theory] + [InlineData("Header: line1\r\n line2\r\n\r\n")] + [InlineData("Header: line1\r\n\tline2\r\n\r\n")] + [InlineData("Header: line1\r\n line2\r\n\r\n")] + [InlineData("Header: line1\r\n \tline2\r\n\r\n")] + [InlineData("Header: line1\r\n\t line2\r\n\r\n")] + [InlineData("Header: line1\r\n\t\tline2\r\n\r\n")] + [InlineData("Header: line1\r\n \t\t line2\r\n\r\n")] + [InlineData("Header: line1\r\n \t \t line2\r\n\r\n")] + public void ThrowsOnHeaderValueWithLineFolding(string rawHeaders) + { + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new LoggingThreadPool(trace); + using (var pool = new MemoryPool()) + using (var socketInput = new SocketInput(pool, ltp)) + { + var connectionContext = new ConnectionContext() + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + Log = trace + }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); + + var headerArray = Encoding.ASCII.GetBytes(rawHeaders); + socketInput.IncomingData(headerArray, 0, headerArray.Length); + + Assert.Throws(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders)); + } + } + + [Theory] + [InlineData("Header-1: value1\r\r\n")] + [InlineData("Header-1: value1\rHeader-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader-2: value2\r\r\n")] + public void ThrowsOnHeaderLineNotEndingInCRLF(string rawHeaders) + { + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new LoggingThreadPool(trace); + using (var pool = new MemoryPool()) + using (var socketInput = new SocketInput(pool, ltp)) + { + var connectionContext = new ConnectionContext() + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + Log = trace + }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); + + var headerArray = Encoding.ASCII.GetBytes(rawHeaders); + socketInput.IncomingData(headerArray, 0, headerArray.Length); + + Assert.Throws(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders)); + } + } + + [Theory] + [InlineData("Header-1 value1\r\n\r\n")] + [InlineData("Header-1 value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader-2 value2\r\n\r\n")] + public void ThrowsOnHeaderLineMissingColon(string rawHeaders) + { + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new LoggingThreadPool(trace); + using (var pool = new MemoryPool()) + using (var socketInput = new SocketInput(pool, ltp)) + { + var connectionContext = new ConnectionContext() + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + Log = trace + }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); + + var headerArray = Encoding.ASCII.GetBytes(rawHeaders); + socketInput.IncomingData(headerArray, 0, headerArray.Length); + + Assert.Throws(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders)); + } + } + + [Theory] + [InlineData(" Header: value\r\n\r\n")] + [InlineData("\tHeader: value\r\n\r\n")] + [InlineData(" Header-1: value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("\tHeader-1: value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\n Header-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\n\tHeader-2: value2\r\n\r\n")] + public void ThrowsOnHeaderLineStartingWithWhitespace(string rawHeaders) + { + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new LoggingThreadPool(trace); + using (var pool = new MemoryPool()) + using (var socketInput = new SocketInput(pool, ltp)) + { + var connectionContext = new ConnectionContext() + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + Log = trace + }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); + + var headerArray = Encoding.ASCII.GetBytes(rawHeaders); + socketInput.IncomingData(headerArray, 0, headerArray.Length); + + Assert.Throws(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders)); + } + } + + [Theory] + [InlineData("Header : value\r\n\r\n")] + [InlineData("Header\t: value\r\n\r\n")] + [InlineData("Header 1: value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header 1 : value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header 1\t: value1\r\nHeader-2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader 2: value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader-2 : value2\r\n\r\n")] + [InlineData("Header-1: value1\r\nHeader-2\t: value2\r\n\r\n")] + public void ThrowsOnWhitespaceInHeaderName(string rawHeaders) + { + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new LoggingThreadPool(trace); + using (var pool = new MemoryPool()) + using (var socketInput = new SocketInput(pool, ltp)) + { + var connectionContext = new ConnectionContext() + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + Log = trace + }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); + + var headerArray = Encoding.ASCII.GetBytes(rawHeaders); + socketInput.IncomingData(headerArray, 0, headerArray.Length); + + Assert.Throws(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders)); + } + } + + [Theory] + [InlineData("Header-1: value1\r\nHeader-2: value2\r\n\r ")] + [InlineData("Header-1: value1\r\nHeader-2: value2\r\nEnd\r\n")] + public void ThrowsOnHeadersNotEndingInCRLFLine(string rawHeaders) + { + var trace = new KestrelTrace(new TestKestrelTrace()); + var ltp = new LoggingThreadPool(trace); + using (var pool = new MemoryPool()) + using (var socketInput = new SocketInput(pool, ltp)) + { + var connectionContext = new ConnectionContext() + { + DateHeaderValueManager = new DateHeaderValueManager(), + ServerAddress = ServerAddress.FromUrl("http://localhost:5000"), + Log = trace + }; + var frame = new Frame(application: null, context: connectionContext); + frame.InitializeHeaders(); + + var headerArray = Encoding.ASCII.GetBytes(rawHeaders); + socketInput.IncomingData(headerArray, 0, headerArray.Length); + + Assert.Throws(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders)); + } + } + [Theory] [InlineData("Cookie: \r\n\r\n", 1)] [InlineData("Cookie:\r\n\r\n", 1)] - [InlineData("Cookie:\r\n value\r\n\r\n", 1)] - [InlineData("Cookie\r\n", 0)] [InlineData("Cookie: \r\nConnection: close\r\n\r\n", 2)] + [InlineData("Cookie:\r\nConnection: close\r\n\r\n", 2)] [InlineData("Connection: close\r\nCookie: \r\n\r\n", 2)] - [InlineData("Connection: close\r\nCookie \r\n", 1)] - [InlineData("Connection:\r\n \r\nCookie \r\n", 1)] + [InlineData("Connection: close\r\nCookie:\r\n\r\n", 2)] public void EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders) { var trace = new KestrelTrace(new TestKestrelTrace()); @@ -38,15 +362,15 @@ namespace Microsoft.AspNetCore.Server.KestrelTests ServerAddress = ServerAddress.FromUrl("http://localhost:5000") }; var frame = new Frame(application: null, context: connectionContext); - var headerCollection = new FrameRequestHeaders(); + frame.InitializeHeaders(); var headerArray = Encoding.ASCII.GetBytes(rawHeaders); socketInput.IncomingData(headerArray, 0, headerArray.Length); - var success = frame.TakeMessageHeaders(socketInput, headerCollection); + var success = frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders); Assert.True(success); - Assert.Equal(numHeaders, headerCollection.Count()); + Assert.Equal(numHeaders, frame.RequestHeaders.Count); // Assert TakeMessageHeaders consumed all the input var scan = socketInput.ConsumingStart();