Skip to content

Commit 1c61e0c

Browse files
authored
Allow zero byte reads on the test server response body #41692 (#44020)
1 parent c5e3e52 commit 1c61e0c

File tree

2 files changed

+35
-17
lines changed

2 files changed

+35
-17
lines changed

src/Hosting/TestHost/src/ResponseBodyReaderStream.cs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public override int Read(byte[] buffer, int offset, int count)
7070

7171
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
7272
{
73-
VerifyBuffer(buffer, offset, count);
73+
ValidateBufferArguments(buffer, offset, count);
7474
CheckAborted();
7575

7676
if (_readerComplete)
@@ -101,22 +101,6 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
101101
return (int)actual;
102102
}
103103

104-
private static void VerifyBuffer(byte[] buffer, int offset, int count)
105-
{
106-
if (buffer == null)
107-
{
108-
throw new ArgumentNullException(nameof(buffer));
109-
}
110-
if (offset < 0 || offset > buffer.Length)
111-
{
112-
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
113-
}
114-
if (count <= 0 || count > buffer.Length - offset)
115-
{
116-
throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
117-
}
118-
}
119-
120104
internal void Cancel()
121105
{
122106
Abort(new OperationCanceledException());

src/Hosting/TestHost/test/ResponseBodyTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,40 @@ public async Task BodyStream_SyncEnabled_FlushSucceeds()
122122
Assert.Equal(contentBytes, responseBytes);
123123
}
124124

125+
[Fact]
126+
public async Task BodyStream_ZeroByteRead_Success()
127+
{
128+
var emptyReadStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
129+
130+
var contentBytes = new byte[] { 32 };
131+
using var host = await CreateHost(async httpContext =>
132+
{
133+
await httpContext.Response.Body.WriteAsync(contentBytes);
134+
await emptyReadStarted.Task;
135+
await httpContext.Response.Body.WriteAsync(contentBytes);
136+
});
137+
138+
var client = host.GetTestServer().CreateClient();
139+
var response = await client.GetAsync("/", HttpCompletionOption.ResponseHeadersRead);
140+
var stream = await response.Content.ReadAsStreamAsync();
141+
var bytes = new byte[5];
142+
var read = await stream.ReadAsync(bytes);
143+
Assert.Equal(1, read);
144+
Assert.Equal(contentBytes[0], bytes[0]);
145+
146+
// This will chain to the Memory overload, but that does less restrictive validation on the input.
147+
// https://github.com/dotnet/aspnetcore/issues/41692#issuecomment-1248714684
148+
var zeroByteRead = stream.ReadAsync(Array.Empty<byte>(), 0, 0);
149+
Assert.False(zeroByteRead.IsCompleted);
150+
emptyReadStarted.SetResult();
151+
read = await zeroByteRead;
152+
Assert.Equal(0, read);
153+
154+
read = await stream.ReadAsync(bytes);
155+
Assert.Equal(1, read);
156+
Assert.Equal(contentBytes[0], bytes[0]);
157+
}
158+
125159
private Task<IHost> CreateHost(RequestDelegate appDelegate)
126160
{
127161
return new HostBuilder()

0 commit comments

Comments
 (0)