Skip to content

Commit a8ed561

Browse files
Allow leading CR/LF in HTTP request lines in Kestrel. (#40859)
1 parent bcac6ee commit a8ed561

File tree

2 files changed

+58
-0
lines changed

2 files changed

+58
-0
lines changed

src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ public HttpParser(bool showErrorDetails)
3838

3939
public bool ParseRequestLine(TRequestHandler handler, ref SequenceReader<byte> reader)
4040
{
41+
// Skip any leading \r or \n on the request line. This is not technically allowed,
42+
// but apparently there are enough clients relying on this that it's worth allowing.
43+
// Peek first as a minor performance optimization; it's a quick inlined check.
44+
if (reader.TryPeek(out byte b) && (b == ByteCR || b == ByteLF))
45+
{
46+
reader.AdvancePastAny(ByteCR, ByteLF);
47+
}
48+
4149
if (reader.TryReadTo(out ReadOnlySpan<byte> requestLine, ByteLF, advancePastDelimiter: true))
4250
{
4351
ParseRequestLine(handler, requestLine);

src/Servers/Kestrel/Core/test/StartLineTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO.Pipelines;
77
using System.Text;
88
using Microsoft.AspNetCore.Connections;
9+
using Microsoft.AspNetCore.Http;
910
using Microsoft.AspNetCore.Http.Features;
1011
using Microsoft.AspNetCore.Server.Kestrel.Core;
1112
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
@@ -516,6 +517,55 @@ public void AuthorityForms(string rawTarget, string path, string query)
516517
DifferentFormsWorkTogether();
517518
}
518519

520+
public static IEnumerable<object[]> GetCrLfAndMethodCombinations()
521+
{
522+
// HTTP methods to test
523+
var methods = new string[] {
524+
HttpMethods.Connect,
525+
HttpMethods.Delete,
526+
HttpMethods.Get,
527+
HttpMethods.Head,
528+
HttpMethods.Options,
529+
HttpMethods.Patch,
530+
HttpMethods.Post,
531+
HttpMethods.Put,
532+
HttpMethods.Trace
533+
};
534+
535+
// Prefixes to test
536+
var crLfPrefixes = new string[] {
537+
"\r",
538+
"\n",
539+
"\r\r\r\r\r",
540+
"\r\n",
541+
"\n\r"
542+
};
543+
544+
foreach (var method in methods)
545+
{
546+
foreach (var prefix in crLfPrefixes)
547+
{
548+
yield return new object[] { prefix, method };
549+
}
550+
}
551+
}
552+
553+
[Theory]
554+
[MemberData(nameof(GetCrLfAndMethodCombinations))]
555+
public void LeadingCrLfAreAllowed(string startOfRequestLine, string httpMethod)
556+
{
557+
var rawTarget = "http://localhost/path1?q=123&w=xyzw";
558+
Http1Connection.Reset();
559+
// RawTarget, Path, QueryString are null after reset
560+
Assert.Null(Http1Connection.RawTarget);
561+
Assert.Null(Http1Connection.Path);
562+
Assert.Null(Http1Connection.QueryString);
563+
564+
var ros = new ReadOnlySequence<byte>(Encoding.ASCII.GetBytes($"{startOfRequestLine}{httpMethod} {rawTarget} HTTP/1.1\r\n"));
565+
var reader = new SequenceReader<byte>(ros);
566+
Assert.True(Parser.ParseRequestLine(ParsingHandler, ref reader));
567+
}
568+
519569
public StartLineTests()
520570
{
521571
MemoryPool = PinnedBlockMemoryPoolFactory.Create();

0 commit comments

Comments
 (0)