Skip to content

Commit 53a99f2

Browse files
Have kestrel filter out pseudo headers #30417 (#36287)
Co-authored-by: Chris R <[email protected]>
1 parent b821015 commit 53a99f2

File tree

6 files changed

+152
-19
lines changed

6 files changed

+152
-19
lines changed

src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,14 @@ public override StringValues HeaderConnection
227227
}
228228
set
229229
{
230-
_bits |= 0x2L;
230+
if (!StringValues.IsNullOrEmpty(value))
231+
{
232+
_bits |= 0x2L;
233+
}
234+
else
235+
{
236+
_bits &= ~0x2L;
237+
}
231238
_headers._Connection = value;
232239
}
233240
}
@@ -244,7 +251,14 @@ public StringValues HeaderHost
244251
}
245252
set
246253
{
247-
_bits |= 0x4L;
254+
if (!StringValues.IsNullOrEmpty(value))
255+
{
256+
_bits |= 0x4L;
257+
}
258+
else
259+
{
260+
_bits &= ~0x4L;
261+
}
248262
_headers._Host = value;
249263
}
250264
}
@@ -261,7 +275,14 @@ public StringValues HeaderAuthority
261275
}
262276
set
263277
{
264-
_bits |= 0x10L;
278+
if (!StringValues.IsNullOrEmpty(value))
279+
{
280+
_bits |= 0x10L;
281+
}
282+
else
283+
{
284+
_bits &= ~0x10L;
285+
}
265286
_headers._Authority = value;
266287
}
267288
}
@@ -278,7 +299,14 @@ public StringValues HeaderMethod
278299
}
279300
set
280301
{
281-
_bits |= 0x20L;
302+
if (!StringValues.IsNullOrEmpty(value))
303+
{
304+
_bits |= 0x20L;
305+
}
306+
else
307+
{
308+
_bits &= ~0x20L;
309+
}
282310
_headers._Method = value;
283311
}
284312
}
@@ -295,7 +323,14 @@ public StringValues HeaderPath
295323
}
296324
set
297325
{
298-
_bits |= 0x40L;
326+
if (!StringValues.IsNullOrEmpty(value))
327+
{
328+
_bits |= 0x40L;
329+
}
330+
else
331+
{
332+
_bits &= ~0x40L;
333+
}
299334
_headers._Path = value;
300335
}
301336
}
@@ -312,7 +347,14 @@ public StringValues HeaderScheme
312347
}
313348
set
314349
{
315-
_bits |= 0x80L;
350+
if (!StringValues.IsNullOrEmpty(value))
351+
{
352+
_bits |= 0x80L;
353+
}
354+
else
355+
{
356+
_bits &= ~0x80L;
357+
}
316358
_headers._Scheme = value;
317359
}
318360
}
@@ -329,7 +371,14 @@ public StringValues HeaderTransferEncoding
329371
}
330372
set
331373
{
332-
_bits |= 0x20000000000L;
374+
if (!StringValues.IsNullOrEmpty(value))
375+
{
376+
_bits |= 0x20000000000L;
377+
}
378+
else
379+
{
380+
_bits &= ~0x20000000000L;
381+
}
333382
_headers._TransferEncoding = value;
334383
}
335384
}
@@ -8234,7 +8283,14 @@ public override StringValues HeaderConnection
82348283
}
82358284
set
82368285
{
8237-
_bits |= 0x1L;
8286+
if (!StringValues.IsNullOrEmpty(value))
8287+
{
8288+
_bits |= 0x1L;
8289+
}
8290+
else
8291+
{
8292+
_bits &= ~0x1L;
8293+
}
82388294
_headers._Connection = value;
82398295
_headers._rawConnection = null;
82408296
}
@@ -8252,7 +8308,14 @@ public StringValues HeaderAllow
82528308
}
82538309
set
82548310
{
8255-
_bits |= 0x1000L;
8311+
if (!StringValues.IsNullOrEmpty(value))
8312+
{
8313+
_bits |= 0x1000L;
8314+
}
8315+
else
8316+
{
8317+
_bits &= ~0x1000L;
8318+
}
82568319
_headers._Allow = value;
82578320
}
82588321
}
@@ -8269,7 +8332,14 @@ public StringValues HeaderAltSvc
82698332
}
82708333
set
82718334
{
8272-
_bits |= 0x2000L;
8335+
if (!StringValues.IsNullOrEmpty(value))
8336+
{
8337+
_bits |= 0x2000L;
8338+
}
8339+
else
8340+
{
8341+
_bits &= ~0x2000L;
8342+
}
82738343
_headers._AltSvc = value;
82748344
_headers._rawAltSvc = null;
82758345
}
@@ -8287,7 +8357,14 @@ public StringValues HeaderTransferEncoding
82878357
}
82888358
set
82898359
{
8290-
_bits |= 0x100000000L;
8360+
if (!StringValues.IsNullOrEmpty(value))
8361+
{
8362+
_bits |= 0x100000000L;
8363+
}
8364+
else
8365+
{
8366+
_bits &= ~0x100000000L;
8367+
}
82918368
_headers._TransferEncoding = value;
82928369
_headers._rawTransferEncoding = null;
82938370
}

src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,14 @@ private bool TryValidatePseudoHeaders()
248248
// enabling the use of HTTP to interact with non - HTTP services.
249249
// A common example is TLS termination.
250250
var headerScheme = HttpRequestHeaders.HeaderScheme.ToString();
251+
HttpRequestHeaders.HeaderScheme = default; // Suppress pseduo headers from the public headers collection.
251252
if (!ReferenceEquals(headerScheme, Scheme) &&
252253
!string.Equals(headerScheme, Scheme, StringComparison.OrdinalIgnoreCase))
253254
{
254255
if (!ServerOptions.AllowAlternateSchemes || !Uri.CheckSchemeName(headerScheme))
255256
{
256257
ResetAndAbort(new ConnectionAbortedException(
257-
CoreStrings.FormatHttp2StreamErrorSchemeMismatch(HttpRequestHeaders.HeaderScheme, Scheme)), Http2ErrorCode.PROTOCOL_ERROR);
258+
CoreStrings.FormatHttp2StreamErrorSchemeMismatch(headerScheme, Scheme)), Http2ErrorCode.PROTOCOL_ERROR);
258259
return false;
259260
}
260261

@@ -264,6 +265,7 @@ private bool TryValidatePseudoHeaders()
264265
// :path (and query) - Required
265266
// Must start with / except may be * for OPTIONS
266267
var path = HttpRequestHeaders.HeaderPath.ToString();
268+
HttpRequestHeaders.HeaderPath = default; // Suppress pseduo headers from the public headers collection.
267269
RawTarget = path;
268270

269271
// OPTIONS - https://tools.ietf.org/html/rfc7540#section-8.1.2.3
@@ -301,6 +303,7 @@ private bool TryValidateMethod()
301303
{
302304
// :method
303305
_methodText = HttpRequestHeaders.HeaderMethod.ToString();
306+
HttpRequestHeaders.HeaderMethod = default; // Suppress pseduo headers from the public headers collection.
304307
Method = HttpUtilities.GetKnownMethod(_methodText);
305308

306309
if (Method == HttpMethod.None)
@@ -327,6 +330,7 @@ private bool TryValidateAuthorityAndHost(out string hostText)
327330
// Prefer this over Host
328331

329332
var authority = HttpRequestHeaders.HeaderAuthority;
333+
HttpRequestHeaders.HeaderAuthority = default; // Suppress pseduo headers from the public headers collection.
330334
var host = HttpRequestHeaders.HeaderHost;
331335
if (!StringValues.IsNullOrEmpty(authority))
332336
{

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -787,12 +787,13 @@ private bool TryValidatePseudoHeaders()
787787
// proxy or gateway can translate requests for non - HTTP schemes,
788788
// enabling the use of HTTP to interact with non - HTTP services.
789789
var headerScheme = HttpRequestHeaders.HeaderScheme.ToString();
790+
HttpRequestHeaders.HeaderScheme = default; // Suppress pseduo headers from the public headers collection.
790791
if (!ReferenceEquals(headerScheme, Scheme) &&
791792
!string.Equals(headerScheme, Scheme, StringComparison.OrdinalIgnoreCase))
792793
{
793794
if (!ServerOptions.AllowAlternateSchemes || !Uri.CheckSchemeName(headerScheme))
794795
{
795-
var str = CoreStrings.FormatHttp3StreamErrorSchemeMismatch(RequestHeaders[HeaderNames.Scheme], Scheme);
796+
var str = CoreStrings.FormatHttp3StreamErrorSchemeMismatch(headerScheme, Scheme);
796797
Abort(new ConnectionAbortedException(str), Http3ErrorCode.ProtocolError);
797798
return false;
798799
}
@@ -802,7 +803,8 @@ private bool TryValidatePseudoHeaders()
802803

803804
// :path (and query) - Required
804805
// Must start with / except may be * for OPTIONS
805-
var path = RequestHeaders[HeaderNames.Path].ToString();
806+
var path = HttpRequestHeaders.HeaderPath.ToString();
807+
HttpRequestHeaders.HeaderPath = default; // Suppress pseduo headers from the public headers collection.
806808
RawTarget = path;
807809

808810
// OPTIONS - https://tools.ietf.org/html/rfc7540#section-8.1.2.3
@@ -840,7 +842,8 @@ private bool TryValidatePseudoHeaders()
840842
private bool TryValidateMethod()
841843
{
842844
// :method
843-
_methodText = RequestHeaders[HeaderNames.Method].ToString();
845+
_methodText = HttpRequestHeaders.HeaderMethod.ToString();
846+
HttpRequestHeaders.HeaderMethod = default; // Suppress pseduo headers from the public headers collection.
844847
Method = HttpUtilities.GetKnownMethod(_methodText);
845848

846849
if (Method == Http.HttpMethod.None)
@@ -866,7 +869,8 @@ private bool TryValidateAuthorityAndHost(out string hostText)
866869
// :authority (optional)
867870
// Prefer this over Host
868871

869-
var authority = RequestHeaders[HeaderNames.Authority];
872+
var authority = HttpRequestHeaders.HeaderAuthority;
873+
HttpRequestHeaders.HeaderAuthority = default; // Suppress pseduo headers from the public headers collection.
870874
var host = HttpRequestHeaders.HeaderHost;
871875
if (!StringValues.IsNullOrEmpty(authority))
872876
{

src/Servers/Kestrel/shared/KnownHeaders.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,14 @@ internal partial class {loop.ClassName} : IHeaderDictionary
843843
}}
844844
set
845845
{{
846-
{header.SetBit()};
846+
if (!StringValues.IsNullOrEmpty(value))
847+
{{
848+
{header.SetBit()};
849+
}}
850+
else
851+
{{
852+
{header.ClearBit()};
853+
}}
847854
_headers._{header.Identifier} = value; {(header.EnhancedSetter == false ? "" : $@"
848855
_headers._raw{header.Identifier} = null;")}
849856
}}")}

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2StreamTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ public async Task HEADERS_Received_SchemeMismatchAllowed_Processed(string scheme
432432
await InitializeConnectionAsync(context =>
433433
{
434434
Assert.Equal(scheme, context.Request.Scheme);
435+
Assert.False(context.Request.Headers.ContainsKey(HeaderNames.Scheme));
435436
return Task.CompletedTask;
436437
});
437438

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ protected static IEnumerable<KeyValuePair<string, string>> ReadRateRequestHeader
136136
protected readonly Dictionary<string, string> _receivedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
137137
protected readonly Dictionary<string, string> _receivedTrailers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
138138
protected readonly Dictionary<string, string> _decodedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
139+
protected readonly RequestFields _receivedRequestFields = new RequestFields();
139140
protected readonly HashSet<int> _abortedStreamIds = new HashSet<int>();
140141
protected readonly object _abortedStreamIdsLock = new object();
141142
protected readonly TaskCompletionSource _closingStateReached = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
@@ -195,6 +196,10 @@ public Http2TestBase()
195196

196197
_readHeadersApplication = context =>
197198
{
199+
_receivedRequestFields.Method = context.Request.Method;
200+
_receivedRequestFields.Scheme = context.Request.Scheme;
201+
_receivedRequestFields.Path = context.Request.Path.Value;
202+
_receivedRequestFields.RawTarget = context.Features.Get<IHttpRequestFeature>().RawTarget;
198203
foreach (var header in context.Request.Headers)
199204
{
200205
_receivedHeaders[header.Key] = header.Value.ToString();
@@ -217,6 +222,10 @@ public Http2TestBase()
217222
Assert.True(context.Request.SupportsTrailers(), "SupportsTrailers");
218223
Assert.True(context.Request.CheckTrailersAvailable(), "SupportsTrailers");
219224

225+
_receivedRequestFields.Method = context.Request.Method;
226+
_receivedRequestFields.Scheme = context.Request.Scheme;
227+
_receivedRequestFields.Path = context.Request.Path.Value;
228+
_receivedRequestFields.RawTarget = context.Features.Get<IHttpRequestFeature>().RawTarget;
220229
foreach (var header in context.Request.Headers)
221230
{
222231
_receivedHeaders[header.Key] = header.Value.ToString();
@@ -350,20 +359,23 @@ public Http2TestBase()
350359
_echoMethodNoBody = context =>
351360
{
352361
Assert.False(context.Request.CanHaveBody());
362+
Assert.False(context.Request.Headers.ContainsKey(HeaderNames.Method));
353363
context.Response.Headers["Method"] = context.Request.Method;
354364

355365
return Task.CompletedTask;
356366
};
357367

358368
_echoHost = context =>
359369
{
370+
Assert.False(context.Request.Headers.ContainsKey(HeaderNames.Authority));
360371
context.Response.Headers.Host = context.Request.Headers.Host;
361372

362373
return Task.CompletedTask;
363374
};
364375

365376
_echoPath = context =>
366377
{
378+
Assert.False(context.Request.Headers.ContainsKey(HeaderNames.Path));
367379
context.Response.Headers["path"] = context.Request.Path.ToString();
368380
context.Response.Headers["rawtarget"] = context.Features.Get<IHttpRequestFeature>().RawTarget;
369381

@@ -1240,8 +1252,28 @@ protected void VerifyDecodedRequestHeaders(IEnumerable<KeyValuePair<string, stri
12401252
{
12411253
foreach (var header in expectedHeaders)
12421254
{
1243-
Assert.True(_receivedHeaders.TryGetValue(header.Key, out var value), header.Key);
1244-
Assert.Equal(header.Value, value, ignoreCase: true);
1255+
if (header.Key == HeaderNames.Method)
1256+
{
1257+
Assert.Equal(header.Value, _receivedRequestFields.Method);
1258+
}
1259+
else if (header.Key == HeaderNames.Authority)
1260+
{
1261+
Assert.True(_receivedHeaders.TryGetValue(HeaderNames.Host, out var host), header.Key);
1262+
Assert.Equal(header.Value, host);
1263+
}
1264+
else if (header.Key == HeaderNames.Scheme)
1265+
{
1266+
Assert.Equal(header.Value, _receivedRequestFields.Scheme);
1267+
}
1268+
else if (header.Key == HeaderNames.Path)
1269+
{
1270+
Assert.Equal(header.Value, _receivedRequestFields.RawTarget);
1271+
}
1272+
else
1273+
{
1274+
Assert.True(_receivedHeaders.TryGetValue(header.Key, out var value), header.Key);
1275+
Assert.Equal(header.Value, value, ignoreCase: true);
1276+
}
12451277
}
12461278
}
12471279

@@ -1390,5 +1422,13 @@ public long GetResponseDrainDeadline(long ticks, MinDataRate minRate)
13901422
return _realTimeoutControl.GetResponseDrainDeadline(ticks, minRate);
13911423
}
13921424
}
1425+
1426+
public class RequestFields
1427+
{
1428+
public string Method { get; set; }
1429+
public string Scheme { get; set; }
1430+
public string Path { get; set; }
1431+
public string RawTarget { get; set; }
1432+
}
13931433
}
13941434
}

0 commit comments

Comments
 (0)