11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4+ using System . Buffers ;
45using System . ComponentModel ;
56using System . Diagnostics ;
67using System . Diagnostics . CodeAnalysis ;
@@ -18,6 +19,9 @@ namespace Microsoft.AspNetCore.Http;
1819[ DebuggerDisplay ( "{Value}" ) ]
1920public readonly struct PathString : IEquatable < PathString >
2021{
22+ private static readonly SearchValues < char > s_validPathChars =
23+ SearchValues . Create ( "!$&'()*+,-./0123456789:;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~" ) ;
24+
2125 internal const int StackAllocThreshold = 128 ;
2226
2327 /// <summary>
@@ -68,27 +72,18 @@ public override string ToString()
6872 /// <returns>The escaped path value</returns>
6973 public string ToUriComponent ( )
7074 {
71- if ( ! HasValue )
72- {
73- return string . Empty ;
74- }
75-
7675 var value = Value ;
77- var i = 0 ;
78- for ( ; i < value . Length ; i ++ )
79- {
80- if ( ! PathStringHelper . IsValidPathChar ( value [ i ] ) || PathStringHelper . IsPercentEncodedChar ( value , i ) )
81- {
82- break ;
83- }
84- }
8576
86- if ( i < value . Length )
77+ if ( string . IsNullOrEmpty ( value ) )
8778 {
88- return ToEscapedUriComponent ( value , i ) ;
79+ return string . Empty ;
8980 }
9081
91- return value ;
82+ var indexOfInvalidChar = value . AsSpan ( ) . IndexOfAnyExcept ( s_validPathChars ) ;
83+
84+ return indexOfInvalidChar < 0
85+ ? value
86+ : ToEscapedUriComponent ( value , indexOfInvalidChar ) ;
9287 }
9388
9489 private static string ToEscapedUriComponent ( string value , int i )
@@ -99,10 +94,10 @@ private static string ToEscapedUriComponent(string value, int i)
9994 var count = i ;
10095 var requiresEscaping = false ;
10196
102- while ( i < value . Length )
97+ while ( ( uint ) i < ( uint ) value . Length )
10398 {
104- var isPercentEncodedChar = PathStringHelper . IsPercentEncodedChar ( value , i ) ;
105- if ( PathStringHelper . IsValidPathChar ( value [ i ] ) || isPercentEncodedChar )
99+ var isPercentEncodedChar = false ;
100+ if ( s_validPathChars . Contains ( value [ i ] ) || ( isPercentEncodedChar = Uri . IsHexEncoding ( value , i ) ) )
106101 {
107102 if ( requiresEscaping )
108103 {
@@ -122,8 +117,18 @@ private static string ToEscapedUriComponent(string value, int i)
122117 }
123118 else
124119 {
125- count ++ ;
126- i ++ ;
120+ // We just saw a character we don't want to escape. It's likely there are more, do a vectorized search.
121+ var charsToSkip = value . AsSpan ( i ) . IndexOfAnyExcept ( s_validPathChars ) ;
122+
123+ if ( charsToSkip < 0 )
124+ {
125+ // Only valid characters remain
126+ count += value . Length - i ;
127+ break ;
128+ }
129+
130+ count += charsToSkip ;
131+ i += charsToSkip ;
127132 }
128133 }
129134 else
@@ -150,21 +155,19 @@ private static string ToEscapedUriComponent(string value, int i)
150155 }
151156 else
152157 {
153- if ( count > 0 )
154- {
155- buffer ??= new StringBuilder ( value . Length * 3 ) ;
158+ Debug . Assert ( count > 0 ) ;
159+ Debug . Assert ( buffer is not null ) ;
156160
157- if ( requiresEscaping )
158- {
159- buffer . Append ( Uri . EscapeDataString ( value . Substring ( start , count ) ) ) ;
160- }
161- else
162- {
163- buffer . Append ( value , start , count ) ;
164- }
161+ if ( requiresEscaping )
162+ {
163+ buffer . Append ( Uri . EscapeDataString ( value . Substring ( start , count ) ) ) ;
164+ }
165+ else
166+ {
167+ buffer . Append ( value , start , count ) ;
165168 }
166169
167- return buffer ? . ToString ( ) ?? string . Empty ;
170+ return buffer . ToString ( ) ;
168171 }
169172 }
170173
0 commit comments