Improved handling of whitespace in headers.
This commit is contained in:
parent
6e46d9757d
commit
de022b6051
|
|
@ -37,6 +37,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
private static Vector<byte> _vectorCRs = new Vector<byte>((byte)'\r');
|
private static Vector<byte> _vectorCRs = new Vector<byte>((byte)'\r');
|
||||||
private static Vector<byte> _vectorColons = new Vector<byte>((byte)':');
|
private static Vector<byte> _vectorColons = new Vector<byte>((byte)':');
|
||||||
private static Vector<byte> _vectorSpaces = new Vector<byte>((byte)' ');
|
private static Vector<byte> _vectorSpaces = new Vector<byte>((byte)' ');
|
||||||
|
private static Vector<byte> _vectorTabs = new Vector<byte>((byte)'\t');
|
||||||
private static Vector<byte> _vectorQuestionMarks = new Vector<byte>((byte)'?');
|
private static Vector<byte> _vectorQuestionMarks = new Vector<byte>((byte)'?');
|
||||||
private static Vector<byte> _vectorPercentages = new Vector<byte>((byte)'%');
|
private static Vector<byte> _vectorPercentages = new Vector<byte>((byte)'%');
|
||||||
|
|
||||||
|
|
@ -1033,10 +1034,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
var consumed = scan;
|
var consumed = scan;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int chFirst;
|
|
||||||
int chSecond;
|
|
||||||
while (!scan.IsEnd)
|
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;
|
var beginName = scan;
|
||||||
if (scan.Seek(ref _vectorColons, ref _vectorCRs) == -1)
|
if (scan.Seek(ref _vectorColons, ref _vectorCRs) == -1)
|
||||||
{
|
{
|
||||||
|
|
@ -1044,118 +1072,113 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Http
|
||||||
}
|
}
|
||||||
var endName = scan;
|
var endName = scan;
|
||||||
|
|
||||||
chFirst = scan.Take();
|
ch = scan.Take();
|
||||||
var beginValue = scan;
|
if (ch != ':')
|
||||||
chSecond = scan.Take();
|
{
|
||||||
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (chFirst == '\r')
|
|
||||||
|
// Skip header value leading whitespace.
|
||||||
|
while (ch == ' ' || ch == '\t')
|
||||||
{
|
{
|
||||||
if (chSecond == '\n')
|
scan.Take();
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beginValue = scan;
|
beginValue = scan;
|
||||||
chSecond = scan.Take();
|
|
||||||
|
|
||||||
if (chSecond == -1)
|
ch = scan.Peek();
|
||||||
|
if (ch == -1)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scan = beginValue;
|
scan = beginValue;
|
||||||
|
if (scan.Seek(ref _vectorCRs) == -1)
|
||||||
var wrapping = false;
|
|
||||||
while (!scan.IsEnd)
|
|
||||||
{
|
{
|
||||||
if (scan.Seek(ref _vectorCRs) == -1)
|
// no "\r" in sight, burn used bytes and go back to await more data
|
||||||
{
|
return false;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
||||||
|
|
@ -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)
|
private async Task ReceiveBadRequestResponse(TestConnection connection)
|
||||||
{
|
{
|
||||||
await connection.Receive(
|
await connection.Receive(
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Text;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel;
|
using Microsoft.AspNetCore.Server.Kestrel;
|
||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Exceptions;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
using Microsoft.AspNetCore.Server.Kestrel.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
using Microsoft.AspNetCore.Server.KestrelTests.TestHelpers;
|
||||||
|
|
@ -16,15 +17,338 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
{
|
{
|
||||||
public class FrameTests
|
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<object>(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<object>(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<object>(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<object>(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<object>(application: null, context: connectionContext);
|
||||||
|
frame.InitializeHeaders();
|
||||||
|
|
||||||
|
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||||
|
socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||||
|
|
||||||
|
Assert.Throws<BadHttpRequestException>(() => 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<object>(application: null, context: connectionContext);
|
||||||
|
frame.InitializeHeaders();
|
||||||
|
|
||||||
|
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||||
|
socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||||
|
|
||||||
|
Assert.Throws<BadHttpRequestException>(() => 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<object>(application: null, context: connectionContext);
|
||||||
|
frame.InitializeHeaders();
|
||||||
|
|
||||||
|
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||||
|
socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||||
|
|
||||||
|
Assert.Throws<BadHttpRequestException>(() => 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<object>(application: null, context: connectionContext);
|
||||||
|
frame.InitializeHeaders();
|
||||||
|
|
||||||
|
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||||
|
socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||||
|
|
||||||
|
Assert.Throws<BadHttpRequestException>(() => 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<object>(application: null, context: connectionContext);
|
||||||
|
frame.InitializeHeaders();
|
||||||
|
|
||||||
|
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||||
|
socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||||
|
|
||||||
|
Assert.Throws<BadHttpRequestException>(() => 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<object>(application: null, context: connectionContext);
|
||||||
|
frame.InitializeHeaders();
|
||||||
|
|
||||||
|
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||||
|
socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||||
|
|
||||||
|
Assert.Throws<BadHttpRequestException>(() => frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("Cookie: \r\n\r\n", 1)]
|
[InlineData("Cookie: \r\n\r\n", 1)]
|
||||||
[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("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\r\n", 2)]
|
||||||
[InlineData("Connection: close\r\nCookie \r\n", 1)]
|
[InlineData("Connection: close\r\nCookie:\r\n\r\n", 2)]
|
||||||
[InlineData("Connection:\r\n \r\nCookie \r\n", 1)]
|
|
||||||
public void EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders)
|
public void EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders)
|
||||||
{
|
{
|
||||||
var trace = new KestrelTrace(new TestKestrelTrace());
|
var trace = new KestrelTrace(new TestKestrelTrace());
|
||||||
|
|
@ -38,15 +362,15 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||||
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
ServerAddress = ServerAddress.FromUrl("http://localhost:5000")
|
||||||
};
|
};
|
||||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||||
var headerCollection = new FrameRequestHeaders();
|
frame.InitializeHeaders();
|
||||||
|
|
||||||
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
var headerArray = Encoding.ASCII.GetBytes(rawHeaders);
|
||||||
socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
socketInput.IncomingData(headerArray, 0, headerArray.Length);
|
||||||
|
|
||||||
var success = frame.TakeMessageHeaders(socketInput, headerCollection);
|
var success = frame.TakeMessageHeaders(socketInput, (FrameRequestHeaders)frame.RequestHeaders);
|
||||||
|
|
||||||
Assert.True(success);
|
Assert.True(success);
|
||||||
Assert.Equal(numHeaders, headerCollection.Count());
|
Assert.Equal(numHeaders, frame.RequestHeaders.Count);
|
||||||
|
|
||||||
// Assert TakeMessageHeaders consumed all the input
|
// Assert TakeMessageHeaders consumed all the input
|
||||||
var scan = socketInput.ConsumingStart();
|
var scan = socketInput.ConsumingStart();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue