diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Managed/ChunkStream.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Managed/ChunkStream.cs index d942ce8cca2935..c4daddd7b83d33 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Managed/ChunkStream.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Managed/ChunkStream.cs @@ -77,7 +77,6 @@ public int Read(byte[] buffer, int offset, int size) private State _state; private readonly StringBuilder _saved; private bool _sawCR; - private bool _gotit; private int _trailerState; private readonly List _chunks; @@ -150,7 +149,6 @@ private void InternalWrite(byte[] buffer, ref int offset, int size) _saved.Length = 0; _sawCR = false; - _gotit = false; } if (_state == State.Body && offset < size) @@ -177,7 +175,6 @@ private void InternalWrite(byte[] buffer, ref int offset, int size) _saved.Length = 0; _sawCR = false; - _gotit = false; } if (offset < size) @@ -255,11 +252,7 @@ private State GetChunkSize(byte[] buffer, ref int offset, int size) if (_sawCR && c == '\n') break; - if (c == ' ') - _gotit = true; - - if (!_gotit) - _saved.Append(c); + _saved.Append(c); if (_saved.Length > 20) ThrowProtocolViolation("chunk size too long."); @@ -274,7 +267,7 @@ private State GetChunkSize(byte[] buffer, ref int offset, int size) { if (_saved.Length > 0) { - _chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + _chunkSize = ParseChunkSize(RemoveChunkExtension(_saved.ToString())); } } catch (Exception) @@ -288,7 +281,7 @@ private State GetChunkSize(byte[] buffer, ref int offset, int size) _chunkRead = 0; try { - _chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + _chunkSize = ParseChunkSize(RemoveChunkExtension(_saved.ToString())); } catch (Exception) { @@ -310,6 +303,15 @@ private static ReadOnlySpan RemoveChunkExtension(ReadOnlySpan input) return idx >= 0 ? input.Slice(0, idx) : input; } + private static int ParseChunkSize(ReadOnlySpan input) + { + uint chunkSize = uint.Parse(input, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); + if (chunkSize > int.MaxValue) + throw new OverflowException(); + + return (int)chunkSize; + } + private State ReadCRLF(byte[] buffer, ref int offset, int size) { if (!_sawCR) diff --git a/src/libraries/System.Net.HttpListener/tests/GetContextHelper.cs b/src/libraries/System.Net.HttpListener/tests/GetContextHelper.cs index 440f726a333efb..b88e360faf4989 100644 --- a/src/libraries/System.Net.HttpListener/tests/GetContextHelper.cs +++ b/src/libraries/System.Net.HttpListener/tests/GetContextHelper.cs @@ -48,7 +48,7 @@ public static class Helpers public static bool IsWindowsImplementation { get; } = (TypeExists("Interop+HttpApi") || TypeExists("System.Net.UnsafeNclNativeMethods")); // types only in Windows netcoreapp/netfx builds, respectively - public static bool IsManagedImplementation => TypeExists("System.Net.WebSockets.ManagedWebSocket"); // type only in managed build + public static bool IsManagedImplementation => !IsWindowsImplementation; private static bool TypeExists(string name) => typeof(HttpListener).Assembly.GetType(name, throwOnError: false, ignoreCase: false) != null; diff --git a/src/libraries/System.Net.HttpListener/tests/HttpRequestStreamTests.cs b/src/libraries/System.Net.HttpListener/tests/HttpRequestStreamTests.cs index 7e5cfdf878a016..2782cfc8bccdd8 100644 --- a/src/libraries/System.Net.HttpListener/tests/HttpRequestStreamTests.cs +++ b/src/libraries/System.Net.HttpListener/tests/HttpRequestStreamTests.cs @@ -701,5 +701,66 @@ public async Task Read_FromClosedConnectionSynchronously_ThrowsHttpListenerExcep Assert.Throws(() => context.Request.InputStream.Read(buffer, 0, buffer.Length)); } } + + // http.sys tolerates whitespace around the chunk size and delivers the body, so this + // is asserting the behavior of the managed implementation only. Gating on the + // implementation rather than the platform keeps it running on Windows when the + // managed implementation is force-enabled. + [ConditionalTheory(typeof(Helpers), nameof(Helpers.IsManagedImplementation))] + [InlineData("5 ")] + [InlineData(" 5")] + [InlineData(" 5 ")] + [InlineData("5\t")] + [InlineData("\t5")] + [InlineData("5 ;foo=bar")] + public async Task Read_ChunkSizeWithWhitespace_ThrowsHttpListenerException(string chunkSizeLine) + { + using (Socket client = _factory.GetConnectedSocket()) + { + Uri listeningUri = new Uri(_factory.ListeningUrl); + string request = + $"POST {listeningUri.PathAndQuery} HTTP/1.1\r\n" + + $"Host: {listeningUri.Host}\r\n" + + "Transfer-Encoding: chunked\r\n" + + "\r\n" + + $"{chunkSizeLine}\r\n" + + "Hello\r\n" + + "0\r\n" + + "\r\n"; + + await client.SendAsync(Encoding.ASCII.GetBytes(request)); + HttpListenerContext context = await _listener.GetContextAsync(); + + byte[] buffer = new byte[5]; + await Assert.ThrowsAsync(() => ReadLengthAsync(context.Request.InputStream, buffer, 0, buffer.Length)); + } + } + + [ConditionalTheory(typeof(Helpers), nameof(Helpers.IsManagedImplementation))] + [InlineData("80000000")] + [InlineData("FFFFFFFF")] + [InlineData("80000000;foo=bar")] + public async Task Read_ChunkSizeOverflow_ThrowsHttpListenerException(string chunkSizeLine) + { + using (Socket client = _factory.GetConnectedSocket()) + { + Uri listeningUri = new Uri(_factory.ListeningUrl); + string request = + $"POST {listeningUri.PathAndQuery} HTTP/1.1\r\n" + + $"Host: {listeningUri.Host}\r\n" + + "Transfer-Encoding: chunked\r\n" + + "\r\n" + + $"{chunkSizeLine}\r\n" + + "Hello\r\n" + + "0\r\n" + + "\r\n"; + + await client.SendAsync(Encoding.ASCII.GetBytes(request)); + HttpListenerContext context = await _listener.GetContextAsync(); + + byte[] buffer = new byte[5]; + await Assert.ThrowsAsync(() => ReadLengthAsync(context.Request.InputStream, buffer, 0, buffer.Length)); + } + } } }