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 . Diagnostics ;
5
6
using System . Diagnostics . CodeAnalysis ;
6
7
using System . Globalization ;
@@ -13,6 +14,12 @@ namespace System.Web.Util
13
14
internal static class HttpEncoder
14
15
{
15
16
private const int MaxStackAllocUrlLength = 256 ;
17
+ private const int StackallocThreshold = 512 ;
18
+
19
+ // Set of safe chars, from RFC 1738.4 minus '+'
20
+ private static readonly SearchValues < byte > s_urlSafeBytes = SearchValues . Create (
21
+ "!()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"u8 ) ;
22
+
16
23
private static void AppendCharAsUnicodeJavaScript ( StringBuilder builder , char c )
17
24
{
18
25
builder . Append ( $ "\\ u{ ( int ) c : x4} ") ;
@@ -218,8 +225,6 @@ internal static string JavaScriptStringEncode(string? value)
218
225
219
226
internal static byte [ ] UrlDecode ( ReadOnlySpan < byte > bytes )
220
227
{
221
- const int StackallocThreshold = 512 ;
222
-
223
228
int decodedBytesCount = 0 ;
224
229
int count = bytes . Length ;
225
230
Span < byte > decodedBytes = count <= StackallocThreshold ? stackalloc byte [ StackallocThreshold ] : new byte [ count ] ;
@@ -401,71 +406,40 @@ internal static string UrlDecode(ReadOnlySpan<char> value, Encoding encoding)
401
406
}
402
407
403
408
[ return : NotNullIfNotNull ( nameof ( bytes ) ) ]
404
- internal static byte [ ] ? UrlEncode ( byte [ ] ? bytes , int offset , int count , bool alwaysCreateNewReturnValue )
405
- {
406
- byte [ ] ? encoded = UrlEncode ( bytes , offset , count ) ;
407
-
408
- return ( alwaysCreateNewReturnValue && ( encoded != null ) && ( encoded == bytes ) )
409
- ? ( byte [ ] ) encoded . Clone ( )
410
- : encoded ;
411
- }
412
-
413
- [ return : NotNullIfNotNull ( nameof ( bytes ) ) ]
414
- private static byte [ ] ? UrlEncode ( byte [ ] ? bytes , int offset , int count )
409
+ internal static byte [ ] ? UrlEncode ( byte [ ] ? bytes , int offset , int count )
415
410
{
416
411
if ( ! ValidateUrlEncodingParameters ( bytes , offset , count ) )
417
412
{
418
413
return null ;
419
414
}
420
415
421
- int cSpaces = 0 ;
422
- int cUnsafe = 0 ;
423
-
424
- // count them first
425
- for ( int i = 0 ; i < count ; i ++ )
426
- {
427
- char ch = ( char ) bytes [ offset + i ] ;
428
-
429
- if ( ch == ' ' )
430
- {
431
- cSpaces ++ ;
432
- }
433
- else if ( ! HttpEncoderUtility . IsUrlSafeChar ( ch ) )
434
- {
435
- cUnsafe ++ ;
436
- }
437
- }
416
+ return UrlEncode ( bytes . AsSpan ( offset , count ) ) ;
417
+ }
438
418
419
+ private static byte [ ] UrlEncode ( ReadOnlySpan < byte > bytes )
420
+ {
439
421
// nothing to expand?
440
- if ( cSpaces == 0 && cUnsafe == 0 )
422
+ if ( ! NeedsEncoding ( bytes , out int cUnsafe ) )
441
423
{
442
- // DevDiv 912606: respect "offset" and "count"
443
- if ( 0 == offset && bytes . Length == count )
444
- {
445
- return bytes ;
446
- }
447
- else
448
- {
449
- byte [ ] subarray = new byte [ count ] ;
450
- Buffer . BlockCopy ( bytes , offset , subarray , 0 , count ) ;
451
- return subarray ;
452
- }
424
+ return bytes . ToArray ( ) ;
453
425
}
454
426
427
+ return UrlEncode ( bytes , cUnsafe ) ;
428
+ }
429
+
430
+ private static byte [ ] UrlEncode ( ReadOnlySpan < byte > bytes , int cUnsafe )
431
+ {
455
432
// expand not 'safe' characters into %XX, spaces to +s
456
- byte [ ] expandedBytes = new byte [ count + cUnsafe * 2 ] ;
433
+ byte [ ] expandedBytes = new byte [ bytes . Length + cUnsafe * 2 ] ;
457
434
int pos = 0 ;
458
435
459
- for ( int i = 0 ; i < count ; i ++ )
436
+ foreach ( byte b in bytes )
460
437
{
461
- byte b = bytes [ offset + i ] ;
462
- char ch = ( char ) b ;
463
-
464
- if ( HttpEncoderUtility . IsUrlSafeChar ( ch ) )
438
+ if ( s_urlSafeBytes . Contains ( b ) )
465
439
{
466
440
expandedBytes [ pos ++ ] = b ;
467
441
}
468
- else if ( ch == ' ' )
442
+ else if ( b == ' ' )
469
443
{
470
444
expandedBytes [ pos ++ ] = ( byte ) '+' ;
471
445
}
@@ -480,6 +454,43 @@ internal static string UrlDecode(ReadOnlySpan<char> value, Encoding encoding)
480
454
return expandedBytes ;
481
455
}
482
456
457
+ private static bool NeedsEncoding ( ReadOnlySpan < byte > bytes , out int cUnsafe )
458
+ {
459
+ cUnsafe = 0 ;
460
+
461
+ int i = bytes . IndexOfAnyExcept ( s_urlSafeBytes ) ;
462
+ if ( i < 0 )
463
+ {
464
+ return false ;
465
+ }
466
+
467
+ foreach ( byte b in bytes . Slice ( i ) )
468
+ {
469
+ if ( ! s_urlSafeBytes . Contains ( b ) && b != ' ' )
470
+ {
471
+ cUnsafe ++ ;
472
+ }
473
+ }
474
+
475
+ return true ;
476
+ }
477
+
478
+ internal static byte [ ] UrlEncode ( string str , Encoding e )
479
+ {
480
+ if ( e . GetMaxByteCount ( str . Length ) <= StackallocThreshold )
481
+ {
482
+ Span < byte > byteSpan = stackalloc byte [ StackallocThreshold ] ;
483
+ int encodedBytes = e . GetBytes ( str , byteSpan ) ;
484
+
485
+ return UrlEncode ( byteSpan . Slice ( 0 , encodedBytes ) ) ;
486
+ }
487
+
488
+ byte [ ] bytes = e . GetBytes ( str ) ;
489
+ return NeedsEncoding ( bytes , out int cUnsafe )
490
+ ? UrlEncode ( bytes , cUnsafe )
491
+ : bytes ;
492
+ }
493
+
483
494
// Helper to encode the non-ASCII url characters only
484
495
private static string UrlEncodeNonAscii ( string str , Encoding e )
485
496
{
@@ -550,7 +561,7 @@ private static byte[] UrlEncodeNonAscii(byte[] bytes, int offset, int count)
550
561
551
562
if ( ( ch & 0xff80 ) == 0 )
552
563
{ // 7 bit?
553
- if ( HttpEncoderUtility . IsUrlSafeChar ( ch ) )
564
+ if ( s_urlSafeBytes . Contains ( ( byte ) ch ) )
554
565
{
555
566
sb . Append ( ch ) ;
556
567
}
0 commit comments