@@ -24,12 +24,23 @@ public static class Base64TestHelper
24
24
52 , 53 , 54 , 55 , 56 , 57 , 43 , 47 //4..9, +, /
25
25
} ;
26
26
27
+ public static readonly byte [ ] s_urlEncodingMap = {
28
+ 65 , 66 , 67 , 68 , 69 , 70 , 71 , 72 , //A..H
29
+ 73 , 74 , 75 , 76 , 77 , 78 , 79 , 80 , //I..P
30
+ 81 , 82 , 83 , 84 , 85 , 86 , 87 , 88 , //Q..X
31
+ 89 , 90 , 97 , 98 , 99 , 100 , 101 , 102 , //Y..Z, a..f
32
+ 103 , 104 , 105 , 106 , 107 , 108 , 109 , 110 , //g..n
33
+ 111 , 112 , 113 , 114 , 115 , 116 , 117 , 118 , //o..v
34
+ 119 , 120 , 121 , 122 , 48 , 49 , 50 , 51 , //w..z, 0..3
35
+ 52 , 53 , 54 , 55 , 56 , 57 , 45 , 95 //4..9, -, _
36
+ } ;
37
+
27
38
// Pre-computing this table using a custom string(s_characters) and GenerateDecodingMapAndVerify (found in tests)
28
39
public static readonly sbyte [ ] s_decodingMap = {
29
40
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
30
41
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
31
42
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , 62 , - 1 , - 1 , - 1 , 63 , //62 is placed at index 43 (for +), 63 at index 47 (for /)
32
- 52 , 53 , 54 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , //52-61 are placed at index 48-57 (for 0-9), 64 at index 61 (for =)
43
+ 52 , 53 , 54 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , //52-61 are placed at index 48-57 (for 0-9)
33
44
- 1 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 ,
34
45
15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , - 1 , - 1 , - 1 , - 1 , - 1 , //0-25 are placed at index 65-90 (for A-Z)
35
46
- 1 , 26 , 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 ,
@@ -44,9 +55,29 @@ public static class Base64TestHelper
44
55
- 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
45
56
} ;
46
57
58
+ public static readonly sbyte [ ] s_urlDecodingMap = {
59
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
60
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
61
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , 62 , - 1 , - 1 , //62 is placed at index 45 (for -), 63 at index 95 (for _)
62
+ 52 , 53 , 54 , 55 , 56 , 57 , 58 , 59 , 60 , 61 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , //52-61 are placed at index 48-57 (for 0-9)
63
+ - 1 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 ,
64
+ 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 , 24 , 25 , - 1 , - 1 , - 1 , - 1 , 63 , //0-25 are placed at index 65-90 (for A-Z)
65
+ - 1 , 26 , 27 , 28 , 29 , 30 , 31 , 32 , 33 , 34 , 35 , 36 , 37 , 38 , 39 , 40 ,
66
+ 41 , 42 , 43 , 44 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , - 1 , - 1 , - 1 , - 1 , - 1 , //26-51 are placed at index 97-122 (for a-z)
67
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , // Bytes over 122 ('z') are invalid and cannot be decoded
68
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , // Hence, padding the map with 255, which indicates invalid input
69
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
70
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
71
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
72
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
73
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
74
+ - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 , - 1 ,
75
+ } ;
76
+
47
77
public static bool IsByteToBeIgnored ( byte charByte ) => charByte is ( byte ) ' ' or ( byte ) '\t ' or ( byte ) '\r ' or ( byte ) '\n ' ;
48
78
49
79
public const byte EncodingPad = ( byte ) '=' ; // '=', for padding
80
+ public const byte UrlEncodingPad = ( byte ) '%' ; // '%', for url padding
50
81
public const sbyte InvalidByte = - 1 ; // Designating -1 for invalid bytes in the decoding map
51
82
52
83
public static byte [ ] InvalidBytes
@@ -60,6 +91,17 @@ public static byte[] InvalidBytes
60
91
}
61
92
}
62
93
94
+ public static byte [ ] UrlInvalidBytes
95
+ {
96
+ get
97
+ {
98
+ int [ ] indices = s_urlDecodingMap . FindAllIndexOf ( InvalidByte ) ;
99
+ // Workaround for indices.Cast<byte>().ToArray() since it throws
100
+ // InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.Byte'
101
+ return indices . Select ( i => ( byte ) i ) . ToArray ( ) ;
102
+ }
103
+ }
104
+
63
105
internal static void InitializeBytes ( Span < byte > bytes , int seed = 100 )
64
106
{
65
107
var rnd = new Random ( seed ) ;
@@ -79,6 +121,26 @@ internal static void InitializeDecodableBytes(Span<byte> bytes, int seed = 100)
79
121
}
80
122
}
81
123
124
+ internal static void InitializeUrlDecodableChars ( Span < char > bytes , int seed = 100 )
125
+ {
126
+ var rnd = new Random ( seed ) ;
127
+ for ( int i = 0 ; i < bytes . Length ; i ++ )
128
+ {
129
+ int index = ( byte ) rnd . Next ( 0 , s_urlEncodingMap . Length ) ;
130
+ bytes [ i ] = ( char ) s_urlEncodingMap [ index ] ;
131
+ }
132
+ }
133
+
134
+ internal static void InitializeUrlDecodableBytes ( Span < byte > bytes , int seed = 100 )
135
+ {
136
+ var rnd = new Random ( seed ) ;
137
+ for ( int i = 0 ; i < bytes . Length ; i ++ )
138
+ {
139
+ int index = ( byte ) rnd . Next ( 0 , s_urlEncodingMap . Length ) ;
140
+ bytes [ i ] = s_urlEncodingMap [ index ] ;
141
+ }
142
+ }
143
+
82
144
[ Fact ]
83
145
public static void GenerateEncodingMapAndVerify ( )
84
146
{
@@ -112,16 +174,34 @@ public static int[] FindAllIndexOf<T>(this IEnumerable<T> values, T valueToFind)
112
174
113
175
public static bool VerifyEncodingCorrectness ( int expectedConsumed , int expectedWritten , Span < byte > source , Span < byte > encodedBytes )
114
176
{
115
- string expectedText = Convert . ToBase64String ( source . Slice ( 0 , expectedConsumed ) . ToArray ( ) ) ;
116
- string encodedText = Encoding . ASCII . GetString ( encodedBytes . Slice ( 0 , expectedWritten ) . ToArray ( ) ) ;
177
+ string expectedText = Convert . ToBase64String ( source . Slice ( 0 , expectedConsumed ) ) ;
178
+ string encodedText = Encoding . ASCII . GetString ( encodedBytes . Slice ( 0 , expectedWritten ) ) ;
179
+ return expectedText . Equals ( encodedText ) ;
180
+ }
181
+
182
+ public static bool VerifyUrlEncodingCorrectness ( int expectedConsumed , int expectedWritten , Span < byte > source , Span < byte > encodedBytes )
183
+ {
184
+ string expectedText = Convert . ToBase64String ( source . Slice ( 0 , expectedConsumed ) )
185
+ . Replace ( '+' , '-' ) . Replace ( '/' , '_' ) . TrimEnd ( '=' ) ;
186
+ string encodedText = Encoding . ASCII . GetString ( encodedBytes . Slice ( 0 , expectedWritten ) ) ;
117
187
return expectedText . Equals ( encodedText ) ;
118
188
}
119
189
120
190
public static bool VerifyDecodingCorrectness ( int expectedConsumed , int expectedWritten , Span < byte > source , Span < byte > decodedBytes )
121
191
{
122
- string sourceString = Encoding . ASCII . GetString ( source . Slice ( 0 , expectedConsumed ) . ToArray ( ) ) ;
192
+ string sourceString = Encoding . ASCII . GetString ( source . Slice ( 0 , expectedConsumed ) ) ;
123
193
byte [ ] expectedBytes = Convert . FromBase64String ( sourceString ) ;
124
194
return expectedBytes . AsSpan ( ) . SequenceEqual ( decodedBytes . Slice ( 0 , expectedWritten ) ) ;
125
195
}
196
+
197
+ public static bool VerifyUrlDecodingCorrectness ( int expectedConsumed , int expectedWritten , Span < byte > source , Span < byte > decodedBytes )
198
+ {
199
+ string sourceString = Encoding . ASCII . GetString ( source . Slice ( 0 , expectedConsumed ) ) ;
200
+ string padded = sourceString . Length % 4 == 0 ? sourceString :
201
+ sourceString . PadRight ( sourceString . Length + ( 4 - sourceString . Length % 4 ) , '=' ) ;
202
+ string base64 = padded . Replace ( '_' , '/' ) . Replace ( '-' , '+' ) . Replace ( '%' , '=' ) ;
203
+ byte [ ] expectedBytes = Convert . FromBase64String ( base64 ) ;
204
+ return expectedBytes . AsSpan ( ) . SequenceEqual ( decodedBytes . Slice ( 0 , expectedWritten ) ) ;
205
+ }
126
206
}
127
207
}
0 commit comments