diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs
index e9c0380603c2..28e287007378 100644
--- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs
+++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs
@@ -22,8 +22,6 @@ private sealed class ChunkedEncodingReadStream : HttpContentReadStream
/// infinite chunk length is sent. This value is arbitrary and can be changed as needed.
///
private const int MaxChunkBytesAllowed = 16*1024;
- /// How long a trailing header can be. This value is arbitrary and can be changed as needed.
- private const int MaxTrailingHeaderLength = 16*1024;
/// The number of bytes remaining in the chunk.
private ulong _chunkBytesRemaining;
/// The current state of the parsing state machine for the chunked response.
@@ -298,6 +296,9 @@ private ReadOnlyMemory ReadChunkFromConnectionBuffer(int maxBytesToRead, C
else
{
_state = ParsingState.ConsumeTrailers;
+ // Apply the MaxResponseHeadersLength limit to all trailing headers.
+ // The limit is applied to regular response headers and trailing headers separately.
+ _connection._allowedReadLineBytes = _connection.MaxResponseHeadersLength;
goto case ParsingState.ConsumeTrailers;
}
@@ -345,7 +346,6 @@ private ReadOnlyMemory ReadChunkFromConnectionBuffer(int maxBytesToRead, C
while (true)
{
// TODO: Consider adding folded trailing header support #35769.
- _connection._allowedReadLineBytes = MaxTrailingHeaderLength;
if (!_connection.TryReadNextLine(out currentLine))
{
break;
diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs
index fe87ca09567e..b329fa9f72cc 100644
--- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs
+++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs
@@ -205,6 +205,8 @@ public bool EnsureReadAheadAndPollRead()
public HttpConnectionKind Kind => _pool.Kind;
+ private int MaxResponseHeadersLength => (int)Math.Min(int.MaxValue, _pool.Settings._maxResponseHeadersLength * 1024L);
+
private int ReadBufferSize => _readBuffer.Length;
private ReadOnlyMemory RemainingBuffer => new ReadOnlyMemory(_readBuffer, _readOffset, _readLength - _readOffset);
@@ -480,7 +482,7 @@ public async Task SendAsyncCore(HttpRequestMessage request,
}
// Start to read response.
- _allowedReadLineBytes = (int)Math.Min(int.MaxValue, _pool.Settings._maxResponseHeadersLength * 1024L);
+ _allowedReadLineBytes = MaxResponseHeadersLength;
// We should not have any buffered data here; if there was, it should have been treated as an error
// by the previous request handling. (Note we do not support HTTP pipelining.)
@@ -1243,7 +1245,7 @@ private bool TryReadNextLine(out ReadOnlySpan line)
{
if (_allowedReadLineBytes < buffer.Length)
{
- throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _pool.Settings._maxResponseHeadersLength * 1024L));
+ throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, MaxResponseHeadersLength));
}
line = default;
@@ -1354,7 +1356,7 @@ private void ThrowIfExceededAllowedReadLineBytes()
{
if (_allowedReadLineBytes < 0)
{
- throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, _pool.Settings._maxResponseHeadersLength * 1024L));
+ throw new HttpRequestException(SR.Format(SR.net_http_response_headers_exceeded_length, MaxResponseHeadersLength));
}
}
diff --git a/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
index c8e50ca46af1..1bf1c13f6c95 100644
--- a/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
+++ b/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
@@ -767,6 +767,60 @@ await TestHelper.WhenAllCompletedOrAnyFailed(
}
});
}
+
+ [Theory]
+ [InlineData(1024, 64, false)]
+ [InlineData(1024, 1024 - 2, false)] // we need at least 2 spare bytes for the next CRLF
+ [InlineData(1024, 1024 - 1, true)]
+ [InlineData(1024, 1024, true)]
+ [InlineData(1024, 1024 + 1, true)]
+ [InlineData(1024 * 1024, 1024 * 1024 - 2, false)]
+ [InlineData(1024 * 1024, 1024 * 1024 - 1, true)]
+ [InlineData(1024 * 1024, 1024 * 1024, true)]
+ public async Task GetAsync_MaxResponseHeadersLength_EnforcedOnTrailingHeaders(int maxResponseHeadersLength, int trailersLength, bool shouldThrow)
+ {
+ await LoopbackServer.CreateClientAndServerAsync(
+ async uri =>
+ {
+ using HttpClientHandler handler = CreateHttpClientHandler();
+ using HttpClient client = CreateHttpClient(handler);
+
+ handler.MaxResponseHeadersLength = maxResponseHeadersLength / 1024;
+
+ if (shouldThrow)
+ {
+ await Assert.ThrowsAsync(() => client.GetAsync(uri));
+ }
+ else
+ {
+ (await client.GetAsync(uri)).Dispose();
+ }
+ },
+ async server =>
+ {
+ try
+ {
+ const string TrailerName1 = "My-Trailer-1";
+ const string TrailerName2 = "My-Trailer-2";
+
+ int trailerOneLength = trailersLength / 2;
+ int trailerTwoLength = trailersLength - trailerOneLength;
+
+ await server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ "HTTP/1.1 200 OK\r\n" +
+ "Connection: close\r\n" +
+ "Transfer-Encoding: chunked\r\n" +
+ "\r\n" +
+ "4\r\n" +
+ "data\r\n" +
+ "0\r\n" +
+ $"{TrailerName1}: {new string('a', trailerOneLength - TrailerName1.Length - 4)}\r\n" +
+ $"{TrailerName2}: {new string('b', trailerTwoLength - TrailerName2.Length - 4)}\r\n" +
+ "\r\n");
+ }
+ catch { }
+ });
+ }
}
public sealed class SocketsHttpHandler_Http2_TrailingHeaders_Test : SocketsHttpHandler_TrailingHeaders_Test