88using Microsoft . AspNetCore . Http ;
99using Microsoft . AspNetCore . Server . Kestrel . Core . Internal . Http ;
1010using Microsoft . Extensions . Primitives ;
11+ using Microsoft . Net . Http . Headers ;
1112using Xunit ;
1213using static CodeGenerator . KnownHeaders ;
1314
@@ -307,11 +308,10 @@ public void AppendThrowsWhenHeaderNameContainsNonASCIICharacters()
307308 var headers = new HttpRequestHeaders ( ) ;
308309 const string key = "\u0014 1\u00F3 d\0 17c" ;
309310
310- var encoding = Encoding . GetEncoding ( "iso-8859-1" ) ;
311311#pragma warning disable CS0618 // Type or member is obsolete
312312 var exception = Assert . Throws < BadHttpRequestException > (
313313#pragma warning restore CS0618 // Type or member is obsolete
314- ( ) => headers . Append ( encoding . GetBytes ( key ) , Encoding . ASCII . GetBytes ( "value" ) ) ) ;
314+ ( ) => headers . Append ( Encoding . Latin1 . GetBytes ( key ) , Encoding . ASCII . GetBytes ( "value" ) ) ) ;
315315 Assert . Equal ( StatusCodes . Status400BadRequest , exception . StatusCode ) ;
316316 }
317317
@@ -473,7 +473,7 @@ public void ValueReuseLatin1NotConfusedForUtf16AndStillRejected(bool reuseValue,
473473 Assert . Throws < InvalidOperationException > ( ( ) =>
474474 {
475475 var headerName = Encoding . ASCII . GetBytes ( header . Name ) . AsSpan ( ) ;
476- var nextSpan = Encoding . GetEncoding ( "iso-8859-1" ) . GetBytes ( headerValueUtf16Latin1CrossOver ) . AsSpan ( ) ;
476+ var nextSpan = Encoding . Latin1 . GetBytes ( headerValueUtf16Latin1CrossOver ) . AsSpan ( ) ;
477477
478478 Assert . False ( nextSpan . SequenceEqual ( Encoding . ASCII . GetBytes ( headerValueUtf16Latin1CrossOver ) ) ) ;
479479
@@ -517,19 +517,24 @@ public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownH
517517 headerValueUtf16Latin1CrossOver = new string ( headerValue . AsSpan ( ) . Slice ( 0 , i + 1 ) ) ;
518518 }
519519
520- headers . Reset ( ) ;
521-
522520 var headerName = Encoding . ASCII . GetBytes ( header . Name ) . AsSpan ( ) ;
523- var latinValueSpan = Encoding . GetEncoding ( "iso-8859-1" ) . GetBytes ( headerValueUtf16Latin1CrossOver ) . AsSpan ( ) ;
521+ var latinValueSpan = Encoding . Latin1 . GetBytes ( headerValueUtf16Latin1CrossOver ) . AsSpan ( ) ;
524522
525523 Assert . False ( latinValueSpan . SequenceEqual ( Encoding . ASCII . GetBytes ( headerValueUtf16Latin1CrossOver ) ) ) ;
526524
525+ headers . Reset ( ) ;
526+ headers . Append ( headerName , latinValueSpan ) ;
527+ headers . OnHeadersComplete ( ) ;
528+ var parsedHeaderValue1 = ( ( IHeaderDictionary ) headers ) [ header . Name ] . ToString ( ) ;
529+
530+ headers . Reset ( ) ;
527531 headers . Append ( headerName , latinValueSpan ) ;
528532 headers . OnHeadersComplete ( ) ;
529- var parsedHeaderValue = ( ( IHeaderDictionary ) headers ) [ header . Name ] . ToString ( ) ;
533+ var parsedHeaderValue2 = ( ( IHeaderDictionary ) headers ) [ header . Name ] . ToString ( ) ;
530534
531- Assert . Equal ( headerValueUtf16Latin1CrossOver , parsedHeaderValue ) ;
532- Assert . NotSame ( headerValueUtf16Latin1CrossOver , parsedHeaderValue ) ;
535+ Assert . Equal ( headerValueUtf16Latin1CrossOver , parsedHeaderValue1 ) ;
536+ Assert . Equal ( parsedHeaderValue1 , parsedHeaderValue2 ) ;
537+ Assert . NotSame ( parsedHeaderValue1 , parsedHeaderValue2 ) ;
533538 }
534539
535540 // Reset back to Ascii
@@ -541,11 +546,12 @@ public void Latin1ValuesAcceptedInLatin1ModeButNotReused(bool reuseValue, KnownH
541546 [ MemberData ( nameof ( KnownRequestHeaders ) ) ]
542547 public void NullCharactersRejectedInUTF8AndLatin1Mode ( bool useLatin1 , KnownHeader header )
543548 {
544- var selector = useLatin1 ?
545- KestrelServerOptions . DefaultLatin1RequestHeaderEncodingSelector :
546- KestrelServerOptions . DefaultRequestHeaderEncodingSelector ;
549+ var kso = new KestrelServerOptions
550+ {
551+ Latin1RequestHeaders = useLatin1 ,
552+ } ;
547553
548- var headers = new HttpRequestHeaders ( encodingSelector : selector ) ;
554+ var headers = new HttpRequestHeaders ( encodingSelector : kso . GetRequestHeaderEncodingSelector ( ) ) ;
549555
550556 var valueArray = new char [ 127 ] ; // 64 + 32 + 16 + 8 + 4 + 2 + 1
551557 for ( var i = 0 ; i < valueArray . Length ; i ++ )
@@ -573,6 +579,53 @@ public void NullCharactersRejectedInUTF8AndLatin1Mode(bool useLatin1, KnownHeade
573579 }
574580 }
575581
582+ [ Fact ]
583+ public void CanSpecifyEncodingBasedOnHeaderName ( )
584+ {
585+ const string headerValue = "Hello \u03a0 " ;
586+ var acceptNameBytes = Encoding . ASCII . GetBytes ( HeaderNames . Accept ) ;
587+ var cookieNameBytes = Encoding . ASCII . GetBytes ( HeaderNames . Cookie ) ;
588+ var headerValueBytes = Encoding . UTF8 . GetBytes ( headerValue ) ;
589+
590+ var headers = new HttpRequestHeaders ( encodingSelector : headerName =>
591+ {
592+ // For known headers, the HeaderNames value is passed in.
593+ if ( ReferenceEquals ( headerName , HeaderNames . Accept ) )
594+ {
595+ return Encoding . GetEncoding ( "ASCII" , EncoderFallback . ExceptionFallback , DecoderFallback . ExceptionFallback ) ;
596+ }
597+
598+ return Encoding . UTF8 ;
599+ } ) ;
600+
601+ Assert . Throws < InvalidOperationException > ( ( ) => headers . Append ( acceptNameBytes , headerValueBytes ) ) ;
602+ headers . Append ( cookieNameBytes , headerValueBytes ) ;
603+ headers . OnHeadersComplete ( ) ;
604+
605+ var parsedAcceptHeaderValue = ( ( IHeaderDictionary ) headers ) [ HeaderNames . Accept ] . ToString ( ) ;
606+ var parsedCookieHeaderValue = ( ( IHeaderDictionary ) headers ) [ HeaderNames . Cookie ] . ToString ( ) ;
607+
608+ Assert . Empty ( parsedAcceptHeaderValue ) ;
609+ Assert . Equal ( headerValue , parsedCookieHeaderValue ) ;
610+ }
611+
612+ [ Fact ]
613+ public void CanSpecifyEncodingForContentLength ( )
614+ {
615+ var contentLengthNameBytes = Encoding . ASCII . GetBytes ( HeaderNames . ContentLength ) ;
616+ // Always 32 bits per code point, so not a superset of ASCII
617+ var contentLengthValueBytes = Encoding . UTF32 . GetBytes ( "1337" ) ;
618+
619+ var headers = new HttpRequestHeaders ( encodingSelector : _ => Encoding . UTF32 ) ;
620+ headers . Append ( contentLengthNameBytes , contentLengthValueBytes ) ;
621+ headers . OnHeadersComplete ( ) ;
622+
623+ Assert . Equal ( 1337 , headers . ContentLength ) ;
624+
625+ Assert . Throws < InvalidOperationException > ( ( ) =>
626+ new HttpRequestHeaders ( ) . Append ( contentLengthNameBytes , contentLengthValueBytes ) ) ;
627+ }
628+
576629 [ Fact ]
577630 public void ValueReuseNeverWhenUnknownHeader ( )
578631 {
0 commit comments