1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System . Buffers ;
4
5
using System . ComponentModel ;
5
6
using System . Diagnostics ;
6
7
using System . Diagnostics . CodeAnalysis ;
@@ -18,6 +19,9 @@ namespace Microsoft.AspNetCore.Http;
18
19
[ DebuggerDisplay ( "{Value}" ) ]
19
20
public readonly struct PathString : IEquatable < PathString >
20
21
{
22
+ private static readonly SearchValues < char > s_validPathChars =
23
+ SearchValues . Create ( "!$&'()*+,-./0123456789:;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~" ) ;
24
+
21
25
internal const int StackAllocThreshold = 128 ;
22
26
23
27
/// <summary>
@@ -68,27 +72,18 @@ public override string ToString()
68
72
/// <returns>The escaped path value</returns>
69
73
public string ToUriComponent ( )
70
74
{
71
- if ( ! HasValue )
72
- {
73
- return string . Empty ;
74
- }
75
-
76
75
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
- }
85
76
86
- if ( i < value . Length )
77
+ if ( string . IsNullOrEmpty ( value ) )
87
78
{
88
- return ToEscapedUriComponent ( value , i ) ;
79
+ return string . Empty ;
89
80
}
90
81
91
- return value ;
82
+ var indexOfInvalidChar = value . AsSpan ( ) . IndexOfAnyExcept ( s_validPathChars ) ;
83
+
84
+ return indexOfInvalidChar < 0
85
+ ? value
86
+ : ToEscapedUriComponent ( value , indexOfInvalidChar ) ;
92
87
}
93
88
94
89
private static string ToEscapedUriComponent ( string value , int i )
@@ -99,10 +94,10 @@ private static string ToEscapedUriComponent(string value, int i)
99
94
var count = i ;
100
95
var requiresEscaping = false ;
101
96
102
- while ( i < value . Length )
97
+ while ( ( uint ) i < ( uint ) value . Length )
103
98
{
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 ) ) )
106
101
{
107
102
if ( requiresEscaping )
108
103
{
@@ -122,8 +117,18 @@ private static string ToEscapedUriComponent(string value, int i)
122
117
}
123
118
else
124
119
{
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 ;
127
132
}
128
133
}
129
134
else
@@ -150,21 +155,19 @@ private static string ToEscapedUriComponent(string value, int i)
150
155
}
151
156
else
152
157
{
153
- if ( count > 0 )
154
- {
155
- buffer ??= new StringBuilder ( value . Length * 3 ) ;
158
+ Debug . Assert ( count > 0 ) ;
159
+ Debug . Assert ( buffer is not null ) ;
156
160
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 ) ;
165
168
}
166
169
167
- return buffer ? . ToString ( ) ?? string . Empty ;
170
+ return buffer . ToString ( ) ;
168
171
}
169
172
}
170
173
0 commit comments