Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/7.0] Enforce HttpClient limits on GetFromJsonAsync #80553

Merged
merged 2 commits into from
Feb 9, 2023
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
4 changes: 2 additions & 2 deletions src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ private async Task<List<byte[]>> ReadRequestHeaderBytesAsync()
public async Task WriteStringAsync(string s)
{
byte[] bytes = Encoding.ASCII.GetBytes(s);
await _stream.WriteAsync(bytes);
await _stream.WriteAsync(bytes, 0, bytes.Length);
}

public async Task SendResponseAsync(string response)
Expand All @@ -736,7 +736,7 @@ public async Task SendResponseAsync(string response)

public async Task SendResponseAsync(byte[] response)
{
await _stream.WriteAsync(response);
await _stream.WriteAsync(response, 0, response.Length);
}

public async Task SendResponseAsync(HttpStatusCode statusCode = HttpStatusCode.OK, string additionalHeaders = null, string content = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
<IsPackable>true</IsPackable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<ServicingVersion>1</ServicingVersion>
<PackageDescription>Provides extension methods for System.Net.Http.HttpClient and System.Net.Http.HttpContent that perform automatic serialization and deserialization using System.Text.Json.

Commonly Used Types:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static partial class HttpClientJsonExtensions
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, type, options, cancellationToken);
}

Expand All @@ -37,7 +37,7 @@ public static partial class HttpClientJsonExtensions
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, type, options, cancellationToken);
}

Expand All @@ -50,7 +50,7 @@ public static partial class HttpClientJsonExtensions
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken);
return GetFromJsonAsyncCore<TValue>(taskResponse, options, cancellationToken);
}

Expand All @@ -63,7 +63,7 @@ public static partial class HttpClientJsonExtensions
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken);
return GetFromJsonAsyncCore<TValue>(taskResponse, options, cancellationToken);
}

Expand All @@ -74,7 +74,7 @@ public static partial class HttpClientJsonExtensions
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, type, context, cancellationToken);
}

Expand All @@ -85,7 +85,7 @@ public static partial class HttpClientJsonExtensions
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, type, context, cancellationToken);
}

Expand All @@ -96,7 +96,7 @@ public static partial class HttpClientJsonExtensions
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken);
return GetFromJsonAsyncCore(taskResponse, jsonTypeInfo, cancellationToken);
}

Expand All @@ -107,7 +107,7 @@ public static partial class HttpClientJsonExtensions
throw new ArgumentNullException(nameof(client));
}

Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
Task<HttpResponseMessage> taskResponse = client.GetAsync(requestUri, HttpCompletionOption.ResponseContentRead, cancellationToken);
return GetFromJsonAsyncCore<TValue>(taskResponse, jsonTypeInfo, cancellationToken);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,5 +354,92 @@ await HttpMessageHandlerLoopbackServer.CreateClientAndServerAsync(
await server.HandleRequestAsync(content: json, headers: headers);
});
}

public static IEnumerable<object[]> GetFromJsonAsync_EnforcesMaxResponseContentBufferSize_MemberData() =>
from useDeleteAsync in new[] { true, false }
from limit in new[] { 2, 100, 100000 }
from contentLength in new[] { limit, limit + 1 }
from chunked in new[] { true, false }
select new object[] { useDeleteAsync, limit, contentLength, chunked };

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] // No Socket support
[MemberData(nameof(GetFromJsonAsync_EnforcesMaxResponseContentBufferSize_MemberData))]
public async Task GetFromJsonAsync_EnforcesMaxResponseContentBufferSize(bool useDeleteAsync, int limit, int contentLength, bool chunked)
{
await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
using var client = new HttpClient { MaxResponseContentBufferSize = limit };

Func<Task> testMethod = () => useDeleteAsync ? client.DeleteFromJsonAsync<string>(uri) : client.GetFromJsonAsync<string>(uri);

if (contentLength > limit)
{
Exception ex = await Assert.ThrowsAsync<HttpRequestException>(testMethod);
Assert.Contains(limit.ToString(), ex.Message);
}
else
{
await testMethod();
}
},
async server =>
{
List<HttpHeaderData> headers = new();
string content = $"\"{new string('a', contentLength - 2)}\"";

if (chunked)
{
headers.Add(new HttpHeaderData("Transfer-Encoding", "chunked"));
content = $"{Convert.ToString(contentLength, 16)}\r\n{content}\r\n0\r\n\r\n";
}

await server.HandleRequestAsync(headers: headers, content: content);
});
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] // No Socket support
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task GetFromJsonAsync_EnforcesTimeout(bool useDeleteAsync, bool slowHeaders)
{
TaskCompletionSource<byte> exceptionThrown = new(TaskCreationOptions.RunContinuationsAsynchronously);

await LoopbackServer.CreateClientAndServerAsync(async uri =>
{
using var client = new HttpClient { Timeout = TimeSpan.FromMilliseconds(100) };

Exception ex = await Assert.ThrowsAsync<TaskCanceledException>(() =>
useDeleteAsync ? client.DeleteFromJsonAsync<string>(uri) : client.GetFromJsonAsync<string>(uri));

#if NETCORE
Assert.Contains("HttpClient.Timeout", ex.Message);
Assert.IsType<TimeoutException>(ex.InnerException);
#endif

exceptionThrown.SetResult(0);
},
async server =>
{
// The client may timeout before even connecting the server
await Task.WhenAny(exceptionThrown.Task, Task.Run(async () =>
{
try
{
await server.AcceptConnectionAsync(async connection =>
{
if (!slowHeaders)
{
await connection.SendPartialResponseHeadersAsync(headers: new[] { new HttpHeaderData("Content-Length", "42") });
}

await exceptionThrown.Task;
});
}
catch { }
}));
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);net48</TargetFrameworks>
</PropertyGroup>
Expand All @@ -13,11 +13,13 @@
</ItemGroup>
<ItemGroup>
<Compile Include="$(CommonTestPath)System\Net\Capability.Security.cs" Link="Common\System\Net\Capability.Security.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.Certificates.cs" Link="Common\System\Net\Configuration.Certificates.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.cs" Link="Common\System\Net\Configuration.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.Http.cs" Link="Common\System\Net\Configuration.Http.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.Security.cs" Link="Common\System\Net\Configuration.Security.cs" />
<Compile Include="$(CommonTestPath)System\Net\Http\HttpMessageHandlerLoopbackServer.cs" Link="Common\System\Net\Http\HttpMessageHandlerLoopbackServer.cs" />
<Compile Include="$(CommonTestPath)System\Net\Http\GenericLoopbackServer.cs" Link="Common\System\Net\Http\GenericLoopbackServer.cs" />
<Compile Include="$(CommonTestPath)System\Net\Http\LoopbackServer.cs" Link="Common\System\Net\Http\LoopbackServer.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\PlatformSupport.cs" Link="CommonTest\System\Security\Cryptography\PlatformSupport.cs" />
<Compile Include="$(CommonTestPath)System\Threading\Tasks\TaskTimeoutExtensions.cs" Link="Common\System\Threading\Tasks\TaskTimeoutExtensions.cs" />
</ItemGroup>
Expand Down