Skip to content
Merged
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
18 changes: 13 additions & 5 deletions src/Servers/HttpSys/src/RequestProcessing/RequestStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,11 @@ private void ValidateReadBuffer(byte[] buffer, int offset, int size)
{
throw new ArgumentNullException(nameof(buffer));
}
if (offset < 0 || offset > buffer.Length)
if ((uint)offset > (uint)buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
}
if (size <= 0 || size > buffer.Length - offset)
if ((uint)size > (uint)(buffer.Length - offset))
{
throw new ArgumentOutOfRangeException(nameof(size), size, string.Empty);
}
Expand Down Expand Up @@ -163,7 +163,14 @@ public override unsafe int Read([In, Out] byte[] buffer, int offset, int size)

dataRead += extraDataRead;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)

// Zero-byte reads
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA && size == 0)
{
// extraDataRead returns 1 to let us know there's data available. Don't count it against the request body size yet.
dataRead = 0;
}
else if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
Log.ErrorWhileRead(Logger, exception);
Expand All @@ -183,7 +190,8 @@ public override unsafe int Read([In, Out] byte[] buffer, int offset, int size)

internal void UpdateAfterRead(uint statusCode, uint dataRead)
{
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF || dataRead == 0)
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF
|| statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA && dataRead == 0)
{
Dispose();
}
Expand Down Expand Up @@ -244,7 +252,7 @@ public override unsafe Task<int> ReadAsync(byte[] buffer, int offset, int size,
cancellationRegistration = RequestContext.RegisterForCancellation(cancellationToken);
}

asyncResult = new RequestStreamAsyncResult(this, null, null, buffer, offset, dataRead, cancellationRegistration);
asyncResult = new RequestStreamAsyncResult(this, null, null, buffer, offset, size, dataRead, cancellationRegistration);
uint bytesReturned;

try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,24 @@ internal unsafe class RequestStreamAsyncResult : IAsyncResult, IDisposable

private readonly SafeNativeOverlapped? _overlapped;
private readonly IntPtr _pinnedBuffer;
private readonly int _size;
private readonly uint _dataAlreadyRead;
private readonly TaskCompletionSource<int> _tcs;
private readonly RequestStream _requestStream;
private readonly AsyncCallback? _callback;
private readonly CancellationTokenRegistration _cancellationRegistration;

internal RequestStreamAsyncResult(RequestStream requestStream, object? userState, AsyncCallback? callback)
internal RequestStreamAsyncResult(RequestStream requestStream, object? userState, AsyncCallback? callback, byte[] buffer, int offset, int size, uint dataAlreadyRead, CancellationTokenRegistration cancellationRegistration)
{
_requestStream = requestStream;
_tcs = new TaskCompletionSource<int>(userState);
_callback = callback;
}

internal RequestStreamAsyncResult(RequestStream requestStream, object? userState, AsyncCallback? callback, uint dataAlreadyRead)
: this(requestStream, userState, callback)
{
_dataAlreadyRead = dataAlreadyRead;
}

internal RequestStreamAsyncResult(RequestStream requestStream, object? userState, AsyncCallback? callback, byte[] buffer, int offset, uint dataAlreadyRead)
: this(requestStream, userState, callback, buffer, offset, dataAlreadyRead, new CancellationTokenRegistration())
{
}

internal RequestStreamAsyncResult(RequestStream requestStream, object? userState, AsyncCallback? callback, byte[] buffer, int offset, uint dataAlreadyRead, CancellationTokenRegistration cancellationRegistration)
: this(requestStream, userState, callback)
{
_dataAlreadyRead = dataAlreadyRead;
var boundHandle = requestStream.RequestContext.Server.RequestQueue.BoundHandle;
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, buffer));
_pinnedBuffer = (Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
_size = size;
_cancellationRegistration = cancellationRegistration;
}

Expand Down Expand Up @@ -87,7 +73,13 @@ private static void IOCompleted(RequestStreamAsyncResult asyncResult, uint error
{
try
{
if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
// Zero-byte reads
if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA && asyncResult._size == 0)
{
// numBytes returns 1 to let us know there's data available. Don't count it against the request body size yet.
asyncResult.Complete(0, errorCode);
}
else if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
asyncResult.Fail(new IOException(string.Empty, new HttpSysException((int)errorCode)));
}
Expand Down
46 changes: 45 additions & 1 deletion src/Servers/HttpSys/test/FunctionalTests/RequestBodyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,28 @@ public async Task RequestBody_ReadSync_Success()
}
}

[ConditionalFact]
public async Task RequestBody_Read0ByteSync_Success()
{
string address;
using (Utilities.CreateHttpServer(out address, httpContext =>
{
Assert.True(httpContext.Request.CanHaveBody());
byte[] input = new byte[100];
httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
int read = httpContext.Request.Body.Read(input, 0, 0);
Assert.Equal(0, read);
read = httpContext.Request.Body.Read(input, 0, input.Length);
httpContext.Response.ContentLength = read;
httpContext.Response.Body.Write(input, 0, read);
return Task.FromResult(0);
}))
{
string response = await SendRequestAsync(address, "Hello World");
Assert.Equal("Hello World", response);
}
}

[ConditionalFact]
public async Task RequestBody_ReadAsync_Success()
{
Expand All @@ -57,6 +79,29 @@ public async Task RequestBody_ReadAsync_Success()
}
}

[ConditionalFact]
public async Task RequestBody_Read0ByteAsync_Success()
{
string address;
using (Utilities.CreateHttpServer(out address, async httpContext =>
{
Assert.True(httpContext.Request.CanHaveBody());
byte[] input = new byte[100];
await Task.Delay(1000);
int read = await httpContext.Request.Body.ReadAsync(input, 0, 1);
Assert.Equal(1, read);
read = await httpContext.Request.Body.ReadAsync(input, 0, 0);
Assert.Equal(0, read);
read = await httpContext.Request.Body.ReadAsync(input, 1, input.Length - 1);
httpContext.Response.ContentLength = read + 1;
await httpContext.Response.Body.WriteAsync(input, 0, read + 1);
}))
{
string response = await SendRequestAsync(address, "Hello World");
Assert.Equal("Hello World", response);
}
}

[ConditionalFact]
public async Task RequestBody_ReadBeginEnd_Success()
{
Expand Down Expand Up @@ -87,7 +132,6 @@ public async Task RequestBody_InvalidBuffer_ArgumentException()
Assert.Throws<ArgumentOutOfRangeException>("offset", () => httpContext.Request.Body.Read(input, -1, 1));
Assert.Throws<ArgumentOutOfRangeException>("offset", () => httpContext.Request.Body.Read(input, input.Length + 1, 1));
Assert.Throws<ArgumentOutOfRangeException>("size", () => httpContext.Request.Body.Read(input, 10, -1));
Assert.Throws<ArgumentOutOfRangeException>("size", () => httpContext.Request.Body.Read(input, 0, 0));
Assert.Throws<ArgumentOutOfRangeException>("size", () => httpContext.Request.Body.Read(input, 1, input.Length));
Assert.Throws<ArgumentOutOfRangeException>("size", () => httpContext.Request.Body.Read(input, 0, input.Length + 1));
return Task.FromResult(0);
Expand Down