3
3
4
4
#nullable enable
5
5
6
+ using System . Buffers ;
6
7
using System . Diagnostics ;
7
8
using System . Globalization ;
8
9
using System . Text ;
@@ -19,6 +20,11 @@ namespace Microsoft.AspNetCore.Mvc.Rendering;
19
20
[ DebuggerDisplay ( "{DebuggerToString()}" ) ]
20
21
public class TagBuilder : IHtmlContent
21
22
{
23
+ // Note '.' is valid according to the HTML 4.01 specification. Disallowed here to avoid
24
+ // confusion with CSS class selectors or when using jQuery.
25
+ private static readonly SearchValues < char > _html401IdChars =
26
+ SearchValues . Create ( "-0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" ) ;
27
+
22
28
private AttributeDictionary ? _attributes ;
23
29
private HtmlContentBuilder ? _innerHtml ;
24
30
@@ -154,51 +160,39 @@ public static string CreateSanitizedId(string? name, string invalidCharReplaceme
154
160
}
155
161
156
162
// If there are no invalid characters in the string, then we don't have to create the buffer.
157
- var firstIndexOfInvalidCharacter = 1 ;
158
- for ( ; firstIndexOfInvalidCharacter < name . Length ; firstIndexOfInvalidCharacter ++ )
163
+ var indexOfInvalidCharacter = name . AsSpan ( 1 ) . IndexOfAnyExcept ( _html401IdChars ) ;
164
+ var firstChar = name [ 0 ] ;
165
+ var startsWithAsciiLetter = char . IsAsciiLetter ( firstChar ) ;
166
+ if ( startsWithAsciiLetter && indexOfInvalidCharacter < 0 )
159
167
{
160
- if ( ! Html401IdUtil . IsValidIdCharacter ( name [ firstIndexOfInvalidCharacter ] ) )
161
- {
162
- break ;
163
- }
168
+ return name ;
164
169
}
165
170
166
- var firstChar = name [ 0 ] ;
167
- var startsWithAsciiLetter = char . IsAsciiLetter ( firstChar ) ;
168
171
if ( ! startsWithAsciiLetter )
169
172
{
170
173
// The first character must be a letter according to the HTML 4.01 specification.
171
174
firstChar = 'z' ;
172
175
}
173
176
174
- if ( firstIndexOfInvalidCharacter == name . Length && startsWithAsciiLetter )
175
- {
176
- return name ;
177
- }
178
-
179
177
var stringBuffer = new StringBuilder ( name . Length ) ;
180
178
stringBuffer . Append ( firstChar ) ;
179
+ var remainingName = name . AsSpan ( 1 ) ;
181
180
182
- // Characters until 'firstIndexOfInvalidCharacter' have already been checked for validity.
183
- // So just copy them. This avoids running them through Html401IdUtil.IsValidIdCharacter again.
184
- for ( var index = 1 ; index < firstIndexOfInvalidCharacter ; index ++ )
185
- {
186
- stringBuffer . Append ( name [ index ] ) ;
187
- }
188
-
189
- for ( var index = firstIndexOfInvalidCharacter ; index < name . Length ; index ++ )
181
+ // Copy values until an invalid character found. Replace the invalid character with the replacement string
182
+ // and search for the next invalid character.
183
+ while ( remainingName . Length > 0 )
190
184
{
191
- var thisChar = name [ index ] ;
192
- if ( Html401IdUtil . IsValidIdCharacter ( thisChar ) )
185
+ if ( indexOfInvalidCharacter < 0 )
193
186
{
194
- stringBuffer . Append ( thisChar ) ;
195
- }
196
- else
197
- {
198
- stringBuffer . Append ( invalidCharReplacement ) ;
187
+ stringBuffer . Append ( remainingName ) ;
188
+ break ;
199
189
}
200
- }
201
190
191
+ stringBuffer . Append ( remainingName . Slice ( 0 , indexOfInvalidCharacter ) ) ;
192
+ stringBuffer . Append ( invalidCharReplacement ) ;
193
+ remainingName = remainingName . Slice ( indexOfInvalidCharacter + 1 ) ;
194
+ indexOfInvalidCharacter = remainingName . IndexOfAnyExcept ( _html401IdChars ) ;
195
+ }
202
196
return stringBuffer . ToString ( ) ;
203
197
}
204
198
@@ -418,28 +412,4 @@ public void WriteTo(TextWriter writer, HtmlEncoder encoder)
418
412
TagBuilder . WriteTo ( _tagBuilder , writer , encoder , _tagRenderMode ) ;
419
413
}
420
414
}
421
-
422
- private static class Html401IdUtil
423
- {
424
- public static bool IsValidIdCharacter ( char testChar )
425
- {
426
- return char . IsAsciiLetterOrDigit ( testChar ) || IsAllowableSpecialCharacter ( testChar ) ;
427
- }
428
-
429
- private static bool IsAllowableSpecialCharacter ( char testChar )
430
- {
431
- switch ( testChar )
432
- {
433
- case '-' :
434
- case '_' :
435
- case ':' :
436
- // Note '.' is valid according to the HTML 4.01 specification. Disallowed here to avoid
437
- // confusion with CSS class selectors or when using jQuery.
438
- return true ;
439
-
440
- default :
441
- return false ;
442
- }
443
- }
444
- }
445
415
}
0 commit comments