@@ -50,6 +50,10 @@ internal sealed partial class Http2Connection : HttpConnectionBase
5050 private readonly Channel < WriteQueueEntry > _writeChannel ;
5151 private bool _lastPendingWriterShouldFlush ;
5252
53+ // Server-advertised SETTINGS_MAX_HEADER_LIST_SIZE
54+ // https://www.rfc-editor.org/rfc/rfc9113.html#section-6.5.2-2.12.1
55+ private uint _maxHeaderListSize = uint . MaxValue ; // Defaults to infinite
56+
5357 // This flag indicates that the connection is shutting down and cannot accept new requests, because of one of the following conditions:
5458 // (1) We received a GOAWAY frame from the server
5559 // (2) We have exhaustead StreamIds (i.e. _nextStream == MaxStreamId)
@@ -156,6 +160,14 @@ public Http2Connection(HttpConnectionPool pool, Stream stream)
156160 _nextPingRequestTimestamp = Environment . TickCount64 + _keepAlivePingDelay ;
157161 _keepAlivePingPolicy = _pool . Settings . _keepAlivePingPolicy ;
158162
163+ uint maxHeaderListSize = _pool . _lastSeenHttp2MaxHeaderListSize ;
164+ if ( maxHeaderListSize > 0 )
165+ {
166+ // Previous connections to the same host advertised a limit.
167+ // Use this as an initial value before we receive the SETTINGS frame.
168+ _maxHeaderListSize = maxHeaderListSize ;
169+ }
170+
159171 if ( HttpTelemetry . Log . IsEnabled ( ) )
160172 {
161173 HttpTelemetry . Log . Http20ConnectionEstablished ( ) ;
@@ -800,6 +812,8 @@ private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = f
800812 uint settingValue = BinaryPrimitives . ReadUInt32BigEndian ( settings ) ;
801813 settings = settings . Slice ( 4 ) ;
802814
815+ if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( $ "Applying setting { ( SettingId ) settingId } ={ settingValue } ") ;
816+
803817 switch ( ( SettingId ) settingId )
804818 {
805819 case SettingId . MaxConcurrentStreams :
@@ -825,6 +839,11 @@ private void ProcessSettingsFrame(FrameHeader frameHeader, bool initialFrame = f
825839 // We don't actually store this value; we always send frames of the minimum size (16K).
826840 break ;
827841
842+ case SettingId . MaxHeaderListSize :
843+ _maxHeaderListSize = settingValue ;
844+ _pool . _lastSeenHttp2MaxHeaderListSize = _maxHeaderListSize ;
845+ break ;
846+
828847 default :
829848 // All others are ignored because we don't care about them.
830849 // Note, per RFC, unknown settings IDs should be ignored.
@@ -1332,17 +1351,19 @@ private void WriteBytes(ReadOnlySpan<byte> bytes, ref ArrayBuffer headerBuffer)
13321351 headerBuffer . Commit ( bytes . Length ) ;
13331352 }
13341353
1335- private void WriteHeaderCollection ( HttpRequestMessage request , HttpHeaders headers , ref ArrayBuffer headerBuffer )
1354+ private int WriteHeaderCollection ( HttpRequestMessage request , HttpHeaders headers , ref ArrayBuffer headerBuffer )
13361355 {
13371356 if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( "" ) ;
13381357
13391358 if ( headers . HeaderStore is null )
13401359 {
1341- return ;
1360+ return 0 ;
13421361 }
13431362
13441363 HeaderEncodingSelector < HttpRequestMessage > ? encodingSelector = _pool . Settings . _requestHeaderEncodingSelector ;
13451364
1365+ int headerListSize = headers . HeaderStore . Count * HeaderField . RfcOverhead ;
1366+
13461367 ref string [ ] ? tmpHeaderValuesArray = ref t_headerValues ;
13471368 foreach ( KeyValuePair < HeaderDescriptor , object > header in headers . HeaderStore )
13481369 {
@@ -1360,6 +1381,10 @@ private void WriteHeaderCollection(HttpRequestMessage request, HttpHeaders heade
13601381 // The Connection, Upgrade and ProxyConnection headers are also not supported in HTTP2.
13611382 if ( knownHeader != KnownHeaders . Host && knownHeader != KnownHeaders . Connection && knownHeader != KnownHeaders . Upgrade && knownHeader != KnownHeaders . ProxyConnection )
13621383 {
1384+ // The length of the encoded name may be shorter than the actual name.
1385+ // Ensure that headerListSize is always >= of the actual size.
1386+ headerListSize += knownHeader . Name . Length ;
1387+
13631388 if ( header . Key . KnownHeader == KnownHeaders . TE )
13641389 {
13651390 // HTTP/2 allows only 'trailers' TE header. rfc7540 8.1.2.2
@@ -1400,6 +1425,8 @@ private void WriteHeaderCollection(HttpRequestMessage request, HttpHeaders heade
14001425 WriteLiteralHeader ( header . Key . Name , headerValues , valueEncoding , ref headerBuffer ) ;
14011426 }
14021427 }
1428+
1429+ return headerListSize ;
14031430 }
14041431
14051432 private void WriteHeaders ( HttpRequestMessage request , ref ArrayBuffer headerBuffer )
@@ -1430,9 +1457,9 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
14301457
14311458 WriteIndexedHeader ( _stream is SslStream ? H2StaticTable . SchemeHttps : H2StaticTable . SchemeHttp , ref headerBuffer ) ;
14321459
1433- if ( request . HasHeaders && request . Headers . Host != null )
1460+ if ( request . HasHeaders && request . Headers . Host is string host )
14341461 {
1435- WriteIndexedHeader ( H2StaticTable . Authority , request . Headers . Host , ref headerBuffer ) ;
1462+ WriteIndexedHeader ( H2StaticTable . Authority , host , ref headerBuffer ) ;
14361463 }
14371464 else
14381465 {
@@ -1450,9 +1477,11 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
14501477 WriteIndexedHeader ( H2StaticTable . PathSlash , pathAndQuery , ref headerBuffer ) ;
14511478 }
14521479
1480+ int headerListSize = 3 * HeaderField . RfcOverhead ; // Method, Authority, Path
1481+
14531482 if ( request . HasHeaders )
14541483 {
1455- WriteHeaderCollection ( request , request . Headers , ref headerBuffer ) ;
1484+ headerListSize += WriteHeaderCollection ( request , request . Headers , ref headerBuffer ) ;
14561485 }
14571486
14581487 // Determine cookies to send.
@@ -1462,9 +1491,9 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
14621491 if ( cookiesFromContainer != string . Empty )
14631492 {
14641493 WriteBytes ( KnownHeaders . Cookie . Http2EncodedName , ref headerBuffer ) ;
1465-
14661494 Encoding ? cookieEncoding = _pool . Settings . _requestHeaderEncodingSelector ? . Invoke ( KnownHeaders . Cookie . Name , request ) ;
14671495 WriteLiteralHeaderValue ( cookiesFromContainer , cookieEncoding , ref headerBuffer ) ;
1496+ headerListSize += HttpKnownHeaderNames . Cookie . Length + HeaderField . RfcOverhead ;
14681497 }
14691498 }
14701499
@@ -1476,11 +1505,24 @@ private void WriteHeaders(HttpRequestMessage request, ref ArrayBuffer headerBuff
14761505 {
14771506 WriteBytes ( KnownHeaders . ContentLength . Http2EncodedName , ref headerBuffer ) ;
14781507 WriteLiteralHeaderValue ( "0" , valueEncoding : null , ref headerBuffer ) ;
1508+ headerListSize += HttpKnownHeaderNames . ContentLength . Length + HeaderField . RfcOverhead ;
14791509 }
14801510 }
14811511 else
14821512 {
1483- WriteHeaderCollection ( request , request . Content . Headers , ref headerBuffer ) ;
1513+ headerListSize += WriteHeaderCollection ( request , request . Content . Headers , ref headerBuffer ) ;
1514+ }
1515+
1516+ // The headerListSize is an approximation of the total header length.
1517+ // This is acceptable as long as the value is always >= the actual length.
1518+ // We must avoid ever sending more than the server allowed.
1519+ // This approach must be revisted if we ever support the dynamic table or compression when sending requests.
1520+ headerListSize += headerBuffer . ActiveLength ;
1521+
1522+ uint maxHeaderListSize = _maxHeaderListSize ;
1523+ if ( ( uint ) headerListSize > maxHeaderListSize )
1524+ {
1525+ throw new HttpRequestException ( SR . Format ( SR . net_http_request_headers_exceeded_length , maxHeaderListSize ) ) ;
14841526 }
14851527 }
14861528
@@ -1553,10 +1595,10 @@ private async ValueTask<Http2Stream> SendHeadersAsync(HttpRequestMessage request
15531595 // streams are created and started in order.
15541596 await PerformWriteAsync ( totalSize , ( thisRef : this , http2Stream , headerBytes , endStream : ( request . Content == null ) , mustFlush ) , static ( s , writeBuffer ) =>
15551597 {
1556- if ( NetEventSource . Log . IsEnabled ( ) ) s . thisRef . Trace ( s . http2Stream . StreamId , $ "Started writing. Total header bytes={ s . headerBytes . Length } ") ;
1557-
15581598 s . thisRef . AddStream ( s . http2Stream ) ;
15591599
1600+ if ( NetEventSource . Log . IsEnabled ( ) ) s . thisRef . Trace ( s . http2Stream . StreamId , $ "Started writing. Total header bytes={ s . headerBytes . Length } ") ;
1601+
15601602 Span < byte > span = writeBuffer . Span ;
15611603
15621604 // Copy the HEADERS frame.
0 commit comments