From 4f7d7216c2bed747194ff0efc804d8180c84e826 Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 17 May 2022 13:45:55 -0700 Subject: [PATCH] Handle spaces after request line #41824 --- .../Core/src/Internal/Http/HttpParser.cs | 32 +++++++++++++++++++ .../Kestrel/Core/test/HttpParserTests.cs | 25 ++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs index 3c66964cafd4..7cce863b4d52 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs @@ -16,14 +16,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http public class HttpParser : IHttpParser where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler { private readonly bool _showErrorDetails; + private readonly bool _allowSpaceAfterRequestLine; public HttpParser() : this(showErrorDetails: true) { } public HttpParser(bool showErrorDetails) + : this (showErrorDetails, CheckAllowSpaceAfterRequestLine()) + { + } + + internal HttpParser(bool showErrorDetails, bool allowSpaceAfterRequestLine) { _showErrorDetails = showErrorDetails; + _allowSpaceAfterRequestLine = allowSpaceAfterRequestLine; + } + + private static bool CheckAllowSpaceAfterRequestLine() + { + // This mitigation is temporary and 6.0 specific, we do not anticipate porting this feature to later versions. + AppContext.TryGetSwitch("Microsoft.AspNetCore.Server.Kestrel.AllowSpaceAfterRequestLine", out var allowSpaceAfterRequestLine); + return allowSpaceAfterRequestLine; } // byte types don't have a data type annotation so we pre-cast them; to avoid in-place casts @@ -48,7 +62,25 @@ public bool ParseRequestLine(TRequestHandler handler, ref SequenceReader r if (reader.TryReadTo(out ReadOnlySpan requestLine, ByteLF, advancePastDelimiter: true)) { + if (_allowSpaceAfterRequestLine) + { + // Skip a space after the request line + if (reader.TryPeek(out byte s) && s == ByteSpace) + { + reader.Advance(1); + } + // Don't parse the request line until we've started receiving headers. + // Need to make sure we skipped an extra space if present. + else if (reader.End) + { + // Reset state so we can try again. + reader.Rewind(requestLine.Length + 1); + return false; + } + } + ParseRequestLine(handler, requestLine); + return true; } diff --git a/src/Servers/Kestrel/Core/test/HttpParserTests.cs b/src/Servers/Kestrel/Core/test/HttpParserTests.cs index c3c4c84d0a24..7cb66af28170 100644 --- a/src/Servers/Kestrel/Core/test/HttpParserTests.cs +++ b/src/Servers/Kestrel/Core/test/HttpParserTests.cs @@ -52,6 +52,28 @@ public void ParsesRequestLine( Assert.True(buffer.Slice(examined).IsEmpty); } + [Fact] + public void ParsesRequestLineWithTrailingSpace() + { + var parser = CreateParser(_nullTrace, allowSpaceAfterRequestLine: true); + var buffer = new ReadOnlySequence(Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\n ")); + var requestHandler = new RequestHandler(); + + Assert.False(ParseRequestLine(parser, requestHandler, buffer.Slice(0, buffer.Length - 1), out var consumed, out var examined)); + Assert.True(buffer.Slice(0, consumed).IsEmpty); + Assert.Equal(buffer.Length - 1, buffer.Slice(0, examined).Length); + + Assert.True(ParseRequestLine(parser, requestHandler, buffer, out consumed, out examined)); + Assert.True(buffer.Slice(consumed).IsEmpty); + Assert.True(buffer.Slice(examined).IsEmpty); + + Assert.Equal(HttpMethods.Get, requestHandler.Method); + Assert.Equal("HTTP/1.1", requestHandler.Version); + Assert.Equal("/", requestHandler.RawTarget); + Assert.Equal("/", requestHandler.RawPath); + Assert.Equal("HTTP/1.1", requestHandler.Version); + } + [Theory] [MemberData(nameof(RequestLineIncompleteData))] public void ParseRequestLineReturnsFalseWhenGivenIncompleteRequestLines(string requestLine) @@ -536,7 +558,8 @@ private void VerifyRawHeaders(string rawHeaders, IEnumerable expectedHea Assert.True(buffer.Slice(reader.Position).IsEmpty); } - private IHttpParser CreateParser(IKestrelTrace log) => new HttpParser(log.IsEnabled(LogLevel.Information)); + private IHttpParser CreateParser(IKestrelTrace log, bool allowSpaceAfterRequestLine = false) + => new HttpParser(log.IsEnabled(LogLevel.Information), allowSpaceAfterRequestLine); public static IEnumerable RequestLineValidData => HttpParsingData.RequestLineValidData;