Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,14 @@ private State GetChunkSize(byte[] buffer, ref int offset, int size)
continue;
}

if (_sawCR && c == '\n')
if (_sawCR)
{
// The CR must be immediately followed by a LF. Don't drop a bare CR and join the digits around it.
if (c != '\n')
ThrowProtocolViolation("Missing \\n");

break;
}

_saved.Append(c);

Expand All @@ -260,9 +266,6 @@ private State GetChunkSize(byte[] buffer, ref int offset, int size)

if (!_sawCR || c != '\n')
{
if (offset < size)
ThrowProtocolViolation("Missing \\n");

try
{
if (_saved.Length > 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,13 @@ private bool ProcessInput(MemoryStream ms)
{
used++;
byte b = buffer[i];
if (_lineState == LineState.CR && b != 10)
{
// A CR must be immediately followed by a LF. Reject a bare CR, as http.sys does,
// rather than dropping it and joining the text on either side of it.
throw new ProtocolViolationException();
}

if (b == 13)
{
_lineState = LineState.CR;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,34 @@ public async Task Read_ChunkSizeWithWhitespace_ThrowsHttpListenerException(strin
}
}

// http.sys rejects this request with a 400 before a context is ever handed out, so there is
// no request stream to read from; this asserts the behavior of the managed implementation.
[ConditionalFact(typeof(Helpers), nameof(Helpers.IsManagedImplementation))]
public async Task Read_ChunkSizeWithBareCR_ThrowsHttpListenerException()
{
using (Socket client = _factory.GetConnectedSocket())
{
Uri listeningUri = new Uri(_factory.ListeningUrl);

// The CR in "1\r0" isn't followed by a LF, so this must not be read as a 0x10 byte chunk.
string request =
$"POST {listeningUri.PathAndQuery} HTTP/1.1\r\n" +
$"Host: {listeningUri.Host}\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"1\r0\n" +
"0123456789ABCDEF\r\n" +
"0\r\n" +
"\r\n";

await client.SendAsync(Encoding.ASCII.GetBytes(request));
HttpListenerContext context = await _listener.GetContextAsync();

byte[] buffer = new byte[16];
await Assert.ThrowsAsync<HttpListenerException>(() => ReadLengthAsync(context.Request.InputStream, buffer, 0, buffer.Length));
}
}

[ConditionalTheory(typeof(Helpers), nameof(Helpers.IsManagedImplementation))]
[InlineData("80000000")]
[InlineData("FFFFFFFF")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ public static IEnumerable<object[]> InvalidRequest_TestData()
yield return new object[] { "GET {path} HTTP/1.1", null, new string[] { "Header\t: value" }, null, "Bad Request" };
}

// Bare CR, i.e. a CR that isn't immediately followed by a LF
yield return new object[] { "GET {path}a\rb HTTP/1.1", null, null, null, "Bad Request" };
yield return new object[] { "GET {path} HTTP/1.1", null, new string[] { "Content-Le\rngth: 0" }, "\r\n", "Bad Request" };
yield return new object[] { "GET {path} HTTP/1.1", null, new string[] { "Header: val\rue" }, "\r\n", "Bad Request" };
yield return new object[] { "GET {path} HTTP/1.1", null, new string[] { "Header: value\r" }, "\r\n", "Bad Request" };

yield return new object[] { "GET {path} HTTP/1.1", "", null, null, "Bad Request" };
yield return new object[] { "GET {path} HTTP/1.1", "Host: \r\n", null, null, "Bad Request" };

Expand Down
Loading