From af1a97cd7c3b5d9177b9b342983b763ff53ce2b3 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Fri, 28 Mar 2014 10:36:25 -0700 Subject: [PATCH] WebListener: Normalize request read validation. 0 size is invalid. Return 0 if closed. --- .../RequestProcessing/RequestStream.cs | 49 +++------- .../RequestBodyTests.cs | 93 +++++++++++++++++++ 2 files changed, 107 insertions(+), 35 deletions(-) diff --git a/src/Microsoft.AspNet.Server.WebListener/RequestProcessing/RequestStream.cs b/src/Microsoft.AspNet.Server.WebListener/RequestProcessing/RequestStream.cs index 5c73f0d41c..e39f146c67 100644 --- a/src/Microsoft.AspNet.Server.WebListener/RequestProcessing/RequestStream.cs +++ b/src/Microsoft.AspNet.Server.WebListener/RequestProcessing/RequestStream.cs @@ -91,7 +91,7 @@ namespace Microsoft.AspNet.Server.WebListener throw new InvalidOperationException(Resources.Exception_ReadOnlyStream); } - public override unsafe int Read([In, Out] byte[] buffer, int offset, int size) + private void ValidateReadBuffer(byte[] buffer, int offset, int size) { if (buffer == null) { @@ -99,15 +99,19 @@ namespace Microsoft.AspNet.Server.WebListener } if (offset < 0 || offset > buffer.Length) { - throw new ArgumentOutOfRangeException("offset"); + throw new ArgumentOutOfRangeException("offset", offset, string.Empty); } - if (size < 0 || size > buffer.Length - offset) + if (size <= 0 || size > buffer.Length - offset) { - throw new ArgumentOutOfRangeException("size"); + throw new ArgumentOutOfRangeException("size", size, string.Empty); } - if (size == 0 || _closed) + } + + public override unsafe int Read([In, Out] byte[] buffer, int offset, int size) + { + ValidateReadBuffer(buffer, offset, size); + if (_closed) { - // TODO: zero sized buffer should be invalid. return 0; } // TODO: Verbose log parameters @@ -177,19 +181,8 @@ namespace Microsoft.AspNet.Server.WebListener public unsafe IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state) #endif { - if (buffer == null) - { - throw new ArgumentNullException("buffer"); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException("offset"); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException("size"); - } - if (size == 0 || _closed) + ValidateReadBuffer(buffer, offset, size); + if (_closed) { RequestStreamAsyncResult result = new RequestStreamAsyncResult(this, state, callback); result.Complete(0); @@ -303,26 +296,12 @@ namespace Microsoft.AspNet.Server.WebListener public override unsafe Task ReadAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken) { - if (buffer == null) - { - throw new ArgumentNullException("buffer"); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException("offset"); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException("size"); - } + ValidateReadBuffer(buffer, offset, size); if (_closed) - { - throw new ObjectDisposedException(GetType().FullName); - } - if (size == 0) { return Task.FromResult(0); } + // TODO: Needs full cancellation integration cancellationToken.ThrowIfCancellationRequested(); // TODO: Verbose log parameters diff --git a/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/RequestBodyTests.cs b/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/RequestBodyTests.cs index 81a90c6fcd..07ec7bf520 100644 --- a/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/RequestBodyTests.cs +++ b/test/Microsoft.AspNet.Server.WebListener.FunctionalTests/RequestBodyTests.cs @@ -4,6 +4,8 @@ using System; using System.IO; using System.Net; using System.Net.Http; +using System.Net.Sockets; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.FeatureModel; @@ -69,6 +71,29 @@ namespace Microsoft.AspNet.Server.WebListener } } #endif + + [Fact] + public async Task RequestBody_InvalidBuffer_ArgumentException() + { + using (Utilities.CreateHttpServer(env => + { + var httpContext = new DefaultHttpContext((IFeatureCollection)env); + byte[] input = new byte[100]; + Assert.Throws("buffer", () => httpContext.Request.Body.Read(null, 0, 1)); + Assert.Throws("offset", () => httpContext.Request.Body.Read(input, -1, 1)); + Assert.Throws("offset", () => httpContext.Request.Body.Read(input, input.Length + 1, 1)); + Assert.Throws("size", () => httpContext.Request.Body.Read(input, 10, -1)); + Assert.Throws("size", () => httpContext.Request.Body.Read(input, 0, 0)); + Assert.Throws("size", () => httpContext.Request.Body.Read(input, 1, input.Length)); + Assert.Throws("size", () => httpContext.Request.Body.Read(input, 0, input.Length + 1)); + return Task.FromResult(0); + })) + { + string response = await SendRequestAsync(Address, "Hello World"); + Assert.Equal(string.Empty, response); + } + } + [Fact] public async Task RequestBody_ReadSyncPartialBody_Success() { @@ -110,6 +135,29 @@ namespace Microsoft.AspNet.Server.WebListener } } + [Fact] + public async Task RequestBody_PostWithImidateBody_Success() + { + using (Utilities.CreateHttpServer(async env => + { + var httpContext = new DefaultHttpContext((IFeatureCollection)env); + byte[] input = new byte[11]; + int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length); + Assert.Equal(10, read); + read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length); + Assert.Equal(0, read); + httpContext.Response.ContentLength = 10; + await httpContext.Response.Body.WriteAsync(input, 0, 10); + })) + { + string response = await SendSocketRequestAsync(Address); + string[] lines = response.Split('\r', '\n'); + Assert.Equal(13, lines.Length); + Assert.Equal("HTTP/1.1 200 OK", lines[0]); + Assert.Equal("0123456789", lines[12]); + } + } + private Task SendRequestAsync(string uri, string upload) { return SendRequestAsync(uri, new StringContent(upload)); @@ -125,6 +173,51 @@ namespace Microsoft.AspNet.Server.WebListener } } + private async Task SendSocketRequestAsync(string address) + { + // Connect with a socket + Uri uri = new Uri(address); + TcpClient client = new TcpClient(); + try + { + await client.ConnectAsync(uri.Host, uri.Port); + NetworkStream stream = client.GetStream(); + + // Send an HTTP GET request + byte[] requestBytes = BuildPostRequest(uri); + await stream.WriteAsync(requestBytes, 0, requestBytes.Length); + StreamReader reader = new StreamReader(stream); + return await reader.ReadToEndAsync(); + } + catch (Exception) + { + client.Close(); + throw; + } + } + + private byte[] BuildPostRequest(Uri uri) + { + StringBuilder builder = new StringBuilder(); + builder.Append("POST"); + builder.Append(" "); + builder.Append(uri.PathAndQuery); + builder.Append(" HTTP/1.1"); + builder.AppendLine(); + + builder.Append("Host: "); + builder.Append(uri.Host); + builder.Append(':'); + builder.Append(uri.Port); + builder.AppendLine(); + + builder.AppendLine("Connection: close"); + builder.AppendLine("Content-Length: 10"); + builder.AppendLine(); + builder.Append("0123456789"); + return Encoding.ASCII.GetBytes(builder.ToString()); + } + private class StaggardContent : HttpContent { public StaggardContent()