@@ -56,6 +56,10 @@ internal sealed partial class Http2Connection : HttpConnectionBase
56
56
private readonly Channel < WriteQueueEntry > _writeChannel ;
57
57
private bool _lastPendingWriterShouldFlush ;
58
58
59
+ // Server-advertised SETTINGS_MAX_HEADER_LIST_SIZE
60
+ // https://www.rfc-editor.org/rfc/rfc9113.html#section-6.5.2-2.12.1
61
+ private uint _maxHeaderListSize = uint . MaxValue ; // Defaults to infinite
62
+
59
63
// This flag indicates that the connection is shutting down and cannot accept new requests, because of one of the following conditions:
60
64
// (1) We received a GOAWAY frame from the server
61
65
// (2) We have exhaustead StreamIds (i.e. _nextStream == MaxStreamId)
@@ -162,6 +166,14 @@ public Http2Connection(HttpConnectionPool pool, Stream stream)
162
166
_nextPingRequestTimestamp = Environment . TickCount64 + _keepAlivePingDelay ;
163
167
_keepAlivePingPolicy = _pool . Settings . _keepAlivePingPolicy ;
164
168
169
+ uint maxHeaderListSize = _pool . _lastSeenHttp2MaxHeaderListSize ;
170
+ if ( maxHeaderListSize > 0 )
171
+ {
172
+ // Previous connections to the same host advertised a limit.
173
+ // Use this as an initial value before we receive the SETTINGS frame.
174
+ _maxHeaderListSize = maxHeaderListSize ;
175
+ }
176
+
165
177
if ( HttpTelemetry . Log . IsEnabled ( ) )
166
178
{
167
179
HttpTelemetry . Log . Http20ConnectionEstablished ( ) ;
@@ -822,6 +834,8 @@ private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = f
822
834
uint settingValue = BinaryPrimitives . ReadUInt32BigEndian ( settings ) ;
823
835
settings = settings . Slice ( 4 ) ;
824
836
837
+ if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( $ "Applying setting { ( SettingId ) settingId } ={ settingValue } ") ;
838
+
825
839
switch ( ( SettingId ) settingId )
826
840
{
827
841
case SettingId . MaxConcurrentStreams :
@@ -861,6 +875,11 @@ private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = f
861
875
}
862
876
break ;
863
877
878
+ case SettingId . MaxHeaderListSize :
879
+ _maxHeaderListSize = settingValue ;
880
+ _pool . _lastSeenHttp2MaxHeaderListSize = _maxHeaderListSize ;
881
+ break ;
882
+
864
883
default :
865
884
// All others are ignored because we don't care about them.
866
885
// Note, per RFC, unknown settings IDs should be ignored.
@@ -1379,14 +1398,18 @@ private void WriteBytes(ReadOnlySpan<byte> bytes, ref ArrayBuffer headerBuffer)
1379
1398
headerBuffer . Commit ( bytes . Length ) ;
1380
1399
}
1381
1400
1382
- private void WriteHeaderCollection ( HttpRequestMessage request , HttpHeaders headers , ref ArrayBuffer headerBuffer )
1401
+ private int WriteHeaderCollection ( HttpRequestMessage request , HttpHeaders headers , ref ArrayBuffer headerBuffer )
1383
1402
{
1384
1403
if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( "" ) ;
1385
1404
1386
1405
HeaderEncodingSelector < HttpRequestMessage > ? encodingSelector = _pool . Settings . _requestHeaderEncodingSelector ;
1387
1406
1388
1407
ref string [ ] ? tmpHeaderValuesArray = ref t_headerValues ;
1389
- foreach ( HeaderEntry header in headers . GetEntries ( ) )
1408
+
1409
+ ReadOnlySpan < HeaderEntry > entries = headers . GetEntries ( ) ;
1410
+ int headerListSize = entries . Length * HeaderField . RfcOverhead ;
1411
+
1412
+ foreach ( HeaderEntry header in entries )
1390
1413
{
1391
1414
int headerValuesCount = HttpHeaders . GetStoreValuesIntoStringArray ( header . Key , header . Value , ref tmpHeaderValuesArray ) ;
1392
1415
Debug . Assert ( headerValuesCount > 0 , "No values for header??" ) ;
@@ -1402,6 +1425,10 @@ private void WriteHeaderCollection(HttpRequestMessage request, HttpHeaders heade
1402
1425
// The Connection, Upgrade and ProxyConnection headers are also not supported in HTTP2.
1403
1426
if ( knownHeader != KnownHeaders . Host && knownHeader != KnownHeaders . Connection && knownHeader != KnownHeaders . Upgrade && knownHeader != KnownHeaders . ProxyConnection )
1404
1427
{
1428
+ // The length of the encoded name may be shorter than the actual name.
1429
+ // Ensure that headerListSize is always >= of the actual size.
1430
+ headerListSize += knownHeader . Name . Length ;
1431
+
1405
1432
if ( knownHeader == KnownHeaders . TE )
1406
1433
{
1407
1434
// HTTP/2 allows only 'trailers' TE header. rfc7540 8.1.2.2
@@ -1442,6 +1469,8 @@ private void WriteHeaderCollection(HttpRequestMessage request, HttpHeaders heade
1442
1469
WriteLiteralHeader ( header . Key . Name , headerValues , valueEncoding , ref headerBuffer ) ;
1443
1470
}
1444
1471
}
1472
+
1473
+ return headerListSize ;
1445
1474
}
1446
1475
1447
1476
private void WriteHeaders ( HttpRequestMessage request , ref ArrayBuffer headerBuffer )
@@ -1472,9 +1501,9 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
1472
1501
1473
1502
WriteIndexedHeader ( _pool . IsSecure ? H2StaticTable . SchemeHttps : H2StaticTable . SchemeHttp , ref headerBuffer ) ;
1474
1503
1475
- if ( request . HasHeaders && request . Headers . Host != null )
1504
+ if ( request . HasHeaders && request . Headers . Host is string host )
1476
1505
{
1477
- WriteIndexedHeader ( H2StaticTable . Authority , request . Headers . Host , ref headerBuffer ) ;
1506
+ WriteIndexedHeader ( H2StaticTable . Authority , host , ref headerBuffer ) ;
1478
1507
}
1479
1508
else
1480
1509
{
@@ -1492,16 +1521,19 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
1492
1521
WriteIndexedHeader ( H2StaticTable . PathSlash , pathAndQuery , ref headerBuffer ) ;
1493
1522
}
1494
1523
1524
+ int headerListSize = 3 * HeaderField . RfcOverhead ; // Method, Authority, Path
1525
+
1495
1526
if ( request . HasHeaders )
1496
1527
{
1497
1528
if ( request . Headers . Protocol != null )
1498
1529
{
1499
1530
WriteBytes ( ProtocolLiteralHeaderBytes , ref headerBuffer ) ;
1500
1531
Encoding ? protocolEncoding = _pool . Settings . _requestHeaderEncodingSelector ? . Invoke ( ":protocol" , request ) ;
1501
1532
WriteLiteralHeaderValue ( request . Headers . Protocol , protocolEncoding , ref headerBuffer ) ;
1533
+ headerListSize += HeaderField . RfcOverhead ;
1502
1534
}
1503
1535
1504
- WriteHeaderCollection ( request , request . Headers , ref headerBuffer ) ;
1536
+ headerListSize += WriteHeaderCollection ( request , request . Headers , ref headerBuffer ) ;
1505
1537
}
1506
1538
1507
1539
// Determine cookies to send.
@@ -1511,9 +1543,9 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
1511
1543
if ( cookiesFromContainer != string . Empty )
1512
1544
{
1513
1545
WriteBytes ( KnownHeaders . Cookie . Http2EncodedName , ref headerBuffer ) ;
1514
-
1515
1546
Encoding ? cookieEncoding = _pool . Settings . _requestHeaderEncodingSelector ? . Invoke ( KnownHeaders . Cookie . Name , request ) ;
1516
1547
WriteLiteralHeaderValue ( cookiesFromContainer , cookieEncoding , ref headerBuffer ) ;
1548
+ headerListSize += HttpKnownHeaderNames . Cookie . Length + HeaderField . RfcOverhead ;
1517
1549
}
1518
1550
}
1519
1551
@@ -1525,11 +1557,24 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
1525
1557
{
1526
1558
WriteBytes ( KnownHeaders . ContentLength . Http2EncodedName , ref headerBuffer ) ;
1527
1559
WriteLiteralHeaderValue ( "0" , valueEncoding : null , ref headerBuffer ) ;
1560
+ headerListSize += HttpKnownHeaderNames . ContentLength . Length + HeaderField . RfcOverhead ;
1528
1561
}
1529
1562
}
1530
1563
else
1531
1564
{
1532
- WriteHeaderCollection ( request , request . Content . Headers , ref headerBuffer ) ;
1565
+ headerListSize += WriteHeaderCollection ( request , request . Content . Headers , ref headerBuffer ) ;
1566
+ }
1567
+
1568
+ // The headerListSize is an approximation of the total header length.
1569
+ // This is acceptable as long as the value is always >= the actual length.
1570
+ // We must avoid ever sending more than the server allowed.
1571
+ // This approach must be revisted if we ever support the dynamic table or compression when sending requests.
1572
+ headerListSize += headerBuffer . ActiveLength ;
1573
+
1574
+ uint maxHeaderListSize = _maxHeaderListSize ;
1575
+ if ( ( uint ) headerListSize > maxHeaderListSize )
1576
+ {
1577
+ throw new HttpRequestException ( SR . Format ( SR . net_http_request_headers_exceeded_length , maxHeaderListSize ) ) ;
1533
1578
}
1534
1579
}
1535
1580
@@ -1602,10 +1647,10 @@ private async ValueTask<Http2Stream> SendHeadersAsync(HttpRequestMessage request
1602
1647
// streams are created and started in order.
1603
1648
await PerformWriteAsync ( totalSize , ( thisRef : this , http2Stream , headerBytes , endStream : ( request . Content == null && ! request . IsExtendedConnectRequest ) , mustFlush ) , static ( s , writeBuffer ) =>
1604
1649
{
1605
- if ( NetEventSource . Log . IsEnabled ( ) ) s . thisRef . Trace ( s . http2Stream . StreamId , $ "Started writing. Total header bytes={ s . headerBytes . Length } ") ;
1606
-
1607
1650
s . thisRef . AddStream ( s . http2Stream ) ;
1608
1651
1652
+ if ( NetEventSource . Log . IsEnabled ( ) ) s . thisRef . Trace ( s . http2Stream . StreamId , $ "Started writing. Total header bytes={ s . headerBytes . Length } ") ;
1653
+
1609
1654
Span < byte > span = writeBuffer . Span ;
1610
1655
1611
1656
// Copy the HEADERS frame.
0 commit comments