Skip to content

Commit 78516ae

Browse files
committed
Allow HttpSys zero-byte reads #41305
1 parent c9af606 commit 78516ae

File tree

3 files changed

+68
-24
lines changed

3 files changed

+68
-24
lines changed

src/Servers/HttpSys/src/RequestProcessing/RequestStream.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ private static void ValidateReadBuffer(byte[] buffer, int offset, int size)
100100
{
101101
throw new ArgumentNullException(nameof(buffer));
102102
}
103-
if (offset < 0 || offset > buffer.Length)
103+
if ((uint)offset > (uint)buffer.Length)
104104
{
105105
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
106106
}
107-
if (size <= 0 || size > buffer.Length - offset)
107+
if ((uint)size > (uint)(buffer.Length - offset))
108108
{
109109
throw new ArgumentOutOfRangeException(nameof(size), size, string.Empty);
110110
}
@@ -161,7 +161,14 @@ public override unsafe int Read([In, Out] byte[] buffer, int offset, int size)
161161

162162
dataRead += extraDataRead;
163163
}
164-
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
164+
165+
// Zero-byte reads
166+
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA && size == 0)
167+
{
168+
// extraDataRead returns 1 to let us know there's data available. Don't count it against the request body size yet.
169+
dataRead = 0;
170+
}
171+
else if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
165172
{
166173
Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
167174
Log.ErrorWhileRead(Logger, exception);
@@ -181,7 +188,8 @@ public override unsafe int Read([In, Out] byte[] buffer, int offset, int size)
181188

182189
internal void UpdateAfterRead(uint statusCode, uint dataRead)
183190
{
184-
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF || dataRead == 0)
191+
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF
192+
|| statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA && dataRead == 0)
185193
{
186194
Dispose();
187195
}
@@ -242,7 +250,7 @@ public override unsafe Task<int> ReadAsync(byte[] buffer, int offset, int size,
242250
cancellationRegistration = RequestContext.RegisterForCancellation(cancellationToken);
243251
}
244252

245-
asyncResult = new RequestStreamAsyncResult(this, null, null, buffer, offset, dataRead, cancellationRegistration);
253+
asyncResult = new RequestStreamAsyncResult(this, null, null, buffer, offset, size, dataRead, cancellationRegistration);
246254
uint bytesReturned;
247255

248256
try

src/Servers/HttpSys/src/RequestProcessing/RequestStreamAsyncResult.cs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,24 @@ internal unsafe class RequestStreamAsyncResult : IAsyncResult, IDisposable
1313

1414
private readonly SafeNativeOverlapped? _overlapped;
1515
private readonly IntPtr _pinnedBuffer;
16+
private readonly int _size;
1617
private readonly uint _dataAlreadyRead;
1718
private readonly TaskCompletionSource<int> _tcs;
1819
private readonly RequestStream _requestStream;
1920
private readonly AsyncCallback? _callback;
2021
private readonly CancellationTokenRegistration _cancellationRegistration;
2122

22-
internal RequestStreamAsyncResult(RequestStream requestStream, object? userState, AsyncCallback? callback)
23+
internal RequestStreamAsyncResult(RequestStream requestStream, object? userState, AsyncCallback? callback, byte[] buffer, int offset, int size, uint dataAlreadyRead, CancellationTokenRegistration cancellationRegistration)
2324
{
2425
_requestStream = requestStream;
2526
_tcs = new TaskCompletionSource<int>(userState);
2627
_callback = callback;
27-
}
28-
29-
internal RequestStreamAsyncResult(RequestStream requestStream, object? userState, AsyncCallback? callback, uint dataAlreadyRead)
30-
: this(requestStream, userState, callback)
31-
{
32-
_dataAlreadyRead = dataAlreadyRead;
33-
}
34-
35-
internal RequestStreamAsyncResult(RequestStream requestStream, object? userState, AsyncCallback? callback, byte[] buffer, int offset, uint dataAlreadyRead)
36-
: this(requestStream, userState, callback, buffer, offset, dataAlreadyRead, new CancellationTokenRegistration())
37-
{
38-
}
39-
40-
internal RequestStreamAsyncResult(RequestStream requestStream, object? userState, AsyncCallback? callback, byte[] buffer, int offset, uint dataAlreadyRead, CancellationTokenRegistration cancellationRegistration)
41-
: this(requestStream, userState, callback)
42-
{
4328
_dataAlreadyRead = dataAlreadyRead;
4429
var boundHandle = requestStream.RequestContext.Server.RequestQueue.BoundHandle;
4530
_overlapped = new SafeNativeOverlapped(boundHandle,
4631
boundHandle.AllocateNativeOverlapped(IOCallback, this, buffer));
4732
_pinnedBuffer = (Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset));
33+
_size = size;
4834
_cancellationRegistration = cancellationRegistration;
4935
}
5036

@@ -83,7 +69,13 @@ private static void IOCompleted(RequestStreamAsyncResult asyncResult, uint error
8369
{
8470
try
8571
{
86-
if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
72+
// Zero-byte reads
73+
if (errorCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_MORE_DATA && asyncResult._size == 0)
74+
{
75+
// numBytes returns 1 to let us know there's data available. Don't count it against the request body size yet.
76+
asyncResult.Complete(0, errorCode);
77+
}
78+
else if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
8779
{
8880
asyncResult.Fail(new IOException(string.Empty, new HttpSysException((int)errorCode)));
8981
}

src/Servers/HttpSys/test/FunctionalTests/RequestBodyTests.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,28 @@ public async Task RequestBody_ReadSync_Success()
3939
}
4040
}
4141

42+
[ConditionalFact]
43+
public async Task RequestBody_Read0ByteSync_Success()
44+
{
45+
string address;
46+
using (Utilities.CreateHttpServer(out address, httpContext =>
47+
{
48+
Assert.True(httpContext.Request.CanHaveBody());
49+
byte[] input = new byte[100];
50+
httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
51+
int read = httpContext.Request.Body.Read(input, 0, 0);
52+
Assert.Equal(0, read);
53+
read = httpContext.Request.Body.Read(input, 0, input.Length);
54+
httpContext.Response.ContentLength = read;
55+
httpContext.Response.Body.Write(input, 0, read);
56+
return Task.FromResult(0);
57+
}))
58+
{
59+
string response = await SendRequestAsync(address, "Hello World");
60+
Assert.Equal("Hello World", response);
61+
}
62+
}
63+
4264
[ConditionalFact]
4365
public async Task RequestBody_ReadAsync_Success()
4466
{
@@ -57,6 +79,29 @@ public async Task RequestBody_ReadAsync_Success()
5779
}
5880
}
5981

82+
[ConditionalFact]
83+
public async Task RequestBody_Read0ByteAsync_Success()
84+
{
85+
string address;
86+
using (Utilities.CreateHttpServer(out address, async httpContext =>
87+
{
88+
Assert.True(httpContext.Request.CanHaveBody());
89+
byte[] input = new byte[100];
90+
await Task.Delay(1000);
91+
int read = await httpContext.Request.Body.ReadAsync(input, 0, 1);
92+
Assert.Equal(1, read);
93+
read = await httpContext.Request.Body.ReadAsync(input, 0, 0);
94+
Assert.Equal(0, read);
95+
read = await httpContext.Request.Body.ReadAsync(input, 1, input.Length - 1);
96+
httpContext.Response.ContentLength = read + 1;
97+
await httpContext.Response.Body.WriteAsync(input, 0, read + 1);
98+
}))
99+
{
100+
string response = await SendRequestAsync(address, "Hello World");
101+
Assert.Equal("Hello World", response);
102+
}
103+
}
104+
60105
[ConditionalFact]
61106
public async Task RequestBody_ReadBeginEnd_Success()
62107
{
@@ -87,7 +132,6 @@ public async Task RequestBody_InvalidBuffer_ArgumentException()
87132
Assert.Throws<ArgumentOutOfRangeException>("offset", () => httpContext.Request.Body.Read(input, -1, 1));
88133
Assert.Throws<ArgumentOutOfRangeException>("offset", () => httpContext.Request.Body.Read(input, input.Length + 1, 1));
89134
Assert.Throws<ArgumentOutOfRangeException>("size", () => httpContext.Request.Body.Read(input, 10, -1));
90-
Assert.Throws<ArgumentOutOfRangeException>("size", () => httpContext.Request.Body.Read(input, 0, 0));
91135
Assert.Throws<ArgumentOutOfRangeException>("size", () => httpContext.Request.Body.Read(input, 1, input.Length));
92136
Assert.Throws<ArgumentOutOfRangeException>("size", () => httpContext.Request.Body.Read(input, 0, input.Length + 1));
93137
return Task.FromResult(0);

0 commit comments

Comments
 (0)