@@ -30,6 +30,7 @@ public class ContentDispositionHeaderValue
30
30
private const string ModificationDateString = "modification-date" ;
31
31
private const string ReadDateString = "read-date" ;
32
32
private const string SizeString = "size" ;
33
+ private const int MaxStackAllocSizeBytes = 256 ;
33
34
private static readonly char [ ] QuestionMark = new char [ ] { '?' } ;
34
35
private static readonly char [ ] SingleQuote = new char [ ] { '\' ' } ;
35
36
private static readonly char [ ] EscapeChars = new char [ ] { '\\ ' , '"' } ;
@@ -543,14 +544,17 @@ private static bool RequiresEncoding(StringSegment input)
543
544
544
545
// Encode using MIME encoding
545
546
// And adds surrounding quotes, Encoded data must always be quoted, the equals signs are invalid in tokens
547
+ [ SkipLocalsInit ]
546
548
private string EncodeMimeWithQuotes ( StringSegment input )
547
549
{
548
550
var requiredLength = MimePrefix . Length +
549
551
Base64 . GetMaxEncodedToUtf8Length ( Encoding . UTF8 . GetByteCount ( input . AsSpan ( ) ) ) +
550
552
MimeSuffix . Length ;
551
- Span < byte > buffer = requiredLength <= 256
552
- ? ( stackalloc byte [ 256 ] ) . Slice ( 0 , requiredLength )
553
- : new byte [ requiredLength ] ;
553
+ byte [ ] ? bufferFromPool = null ;
554
+ Span < byte > buffer = requiredLength <= MaxStackAllocSizeBytes
555
+ ? stackalloc byte [ MaxStackAllocSizeBytes ]
556
+ : bufferFromPool = ArrayPool < byte > . Shared . Rent ( requiredLength ) ;
557
+ buffer = buffer [ ..requiredLength ] ;
554
558
555
559
MimePrefix . CopyTo ( buffer ) ;
556
560
var bufferContent = buffer . Slice ( MimePrefix . Length ) ;
@@ -560,7 +564,14 @@ private string EncodeMimeWithQuotes(StringSegment input)
560
564
561
565
MimeSuffix . CopyTo ( bufferContent . Slice ( base64ContentLength ) ) ;
562
566
563
- return Encoding . UTF8 . GetString ( buffer . Slice ( 0 , MimePrefix . Length + base64ContentLength + MimeSuffix . Length ) ) ;
567
+ var result = Encoding . UTF8 . GetString ( buffer . Slice ( 0 , MimePrefix . Length + base64ContentLength + MimeSuffix . Length ) ) ;
568
+
569
+ if ( bufferFromPool is not null )
570
+ {
571
+ ArrayPool < byte > . Shared . Return ( bufferFromPool ) ;
572
+ }
573
+
574
+ return result ;
564
575
}
565
576
566
577
// Attempt to decode MIME encoded strings
@@ -607,32 +618,60 @@ private bool TryDecodeMime(StringSegment input, [NotNullWhen(true)] out string?
607
618
608
619
// Encode a string using RFC 5987 encoding
609
620
// encoding'lang'PercentEncodedSpecials
621
+ [ SkipLocalsInit ]
610
622
private static string Encode5987 ( StringSegment input )
611
623
{
612
624
var builder = new StringBuilder ( "UTF-8\' \' " ) ;
613
- for ( int i = 0 ; i < input . Length ; i ++ )
625
+
626
+ var maxInputBytes = Encoding . UTF8 . GetMaxByteCount ( input . Length ) ;
627
+ byte [ ] ? bufferFromPool = null ;
628
+ Span < byte > inputBytes = maxInputBytes <= MaxStackAllocSizeBytes
629
+ ? stackalloc byte [ MaxStackAllocSizeBytes ]
630
+ : bufferFromPool = ArrayPool < byte > . Shared . Rent ( maxInputBytes ) ;
631
+
632
+ var bytesWritten = Encoding . UTF8 . GetBytes ( input , inputBytes ) ;
633
+ inputBytes = inputBytes [ ..bytesWritten ] ;
634
+
635
+ int totalBytesConsumed = 0 ;
636
+ while ( totalBytesConsumed < inputBytes . Length )
614
637
{
615
- var c = input [ i ] ;
616
- // attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
617
- // ; token except ( "*" / "'" / "%" )
618
- if ( c > 0x7F ) // Encodes as multiple utf-8 bytes
638
+ if ( inputBytes [ totalBytesConsumed ] <= 0x7F )
619
639
{
620
- var bytes = Encoding . UTF8 . GetBytes ( c . ToString ( ) ) ;
621
- foreach ( byte b in bytes )
640
+ // This is an ASCII char. Let's handle it ourselves.
641
+
642
+ char c = ( char ) inputBytes [ totalBytesConsumed ] ;
643
+ if ( ! HttpRuleParser . IsTokenChar ( c ) || c == '*' || c == '\' ' || c == '%' )
622
644
{
623
- HexEscape ( builder , ( char ) b ) ;
645
+ HexEscape ( builder , c ) ;
624
646
}
625
- }
626
- else if ( ! HttpRuleParser . IsTokenChar ( c ) || c == '*' || c == '\' ' || c == '%' )
627
- {
628
- // ASCII - Only one encoded byte
629
- HexEscape ( builder , c ) ;
647
+ else
648
+ {
649
+ builder . Append ( c ) ;
650
+ }
651
+
652
+ totalBytesConsumed ++ ;
630
653
}
631
654
else
632
655
{
633
- builder . Append ( c ) ;
656
+ // Non-ASCII, let's rely on Rune to decode it.
657
+
658
+ Rune . DecodeFromUtf8 ( inputBytes . Slice ( totalBytesConsumed ) , out Rune r , out int bytesConsumedForRune ) ;
659
+ Contract . Assert ( ! r . IsAscii , "We shouldn't have gotten here if the Rune is ASCII." ) ;
660
+
661
+ for ( int i = 0 ; i < bytesConsumedForRune ; i ++ )
662
+ {
663
+ HexEscape ( builder , ( char ) inputBytes [ totalBytesConsumed + i ] ) ;
664
+ }
665
+
666
+ totalBytesConsumed += bytesConsumedForRune ;
634
667
}
635
668
}
669
+
670
+ if ( bufferFromPool is not null )
671
+ {
672
+ ArrayPool < byte > . Shared . Return ( bufferFromPool ) ;
673
+ }
674
+
636
675
return builder . ToString ( ) ;
637
676
}
638
677
0 commit comments