|
5 | 5 | using System.Collections;
|
6 | 6 | using System.Collections.Generic;
|
7 | 7 | using System.Linq;
|
| 8 | +using System.Numerics; |
| 9 | +using System.Runtime.CompilerServices; |
8 | 10 | using Microsoft.AspNetCore.Http;
|
9 | 11 | using Microsoft.Extensions.Primitives;
|
10 | 12 | using Microsoft.Net.Http.Headers;
|
@@ -211,25 +213,80 @@ bool IDictionary<string, StringValues>.TryGetValue(string key, out StringValues
|
211 | 213 | return TryGetValueFast(key, out value);
|
212 | 214 | }
|
213 | 215 |
|
214 |
| - public static void ValidateHeaderCharacters(StringValues headerValues) |
| 216 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 217 | + public static void ValidateHeaderCharacters(ref StringValues headerValues) |
215 | 218 | {
|
216 | 219 | var count = headerValues.Count;
|
217 | 220 | for (var i = 0; i < count; i++)
|
218 |
| - |
219 | 221 | {
|
220 | 222 | ValidateHeaderCharacters(headerValues[i]);
|
221 | 223 | }
|
222 | 224 | }
|
223 | 225 |
|
224 |
| - public static void ValidateHeaderCharacters(string headerCharacters) |
| 226 | + public static unsafe void ValidateHeaderCharacters(string headerCharacters) |
225 | 227 | {
|
226 | 228 | if (headerCharacters != null)
|
227 | 229 | {
|
228 |
| - foreach (var ch in headerCharacters) |
| 230 | + fixed (char* header = headerCharacters) |
229 | 231 | {
|
230 |
| - if (ch < 0x20 || ch > 0x7E) |
| 232 | + // offsets and lengths are handled in byte* sizes |
| 233 | + var pHeader = (byte*)header; |
| 234 | + var offset = 0; |
| 235 | + var length = headerCharacters.Length * 2; |
| 236 | + |
| 237 | + if (Vector.IsHardwareAccelerated && Vector<byte>.Count <= length) |
| 238 | + { |
| 239 | + var vSub = Vector.AsVectorUInt16(new Vector<uint>(0x00200020u)); |
| 240 | + // 0x7e as highest ascii (don't include DEL) |
| 241 | + // 0x20 as lowerest ascii (space) |
| 242 | + var vTest = Vector.AsVectorUInt16(new Vector<uint>(0x007e007eu - 0x00200020u)); |
| 243 | + |
| 244 | + do |
| 245 | + { |
| 246 | + var stringVector = Unsafe.Read<Vector<ushort>>(pHeader + offset); |
| 247 | + offset += Vector<byte>.Count; |
| 248 | + if (Vector.GreaterThanAny(stringVector - vSub, vTest)) |
| 249 | + { |
| 250 | + ThrowInvalidHeaderCharacter(pHeader + offset, Vector<byte>.Count); |
| 251 | + } |
| 252 | + } while (offset + Vector<byte>.Count <= length); |
| 253 | + } |
| 254 | + |
| 255 | + // Non-vector testing: |
| 256 | + // Flag > 0x007f => Use value directly, already flagged |
| 257 | + // Flag 0x7f => Add 0x0001 to each char so DEL (0x7f) will set a high bit |
| 258 | + // Flag < 0x20 => Sub 0x0020 from each char so high bit will be set in previous char bit |
| 259 | + // Bitwise | or the above three together |
| 260 | + // Bitwise & and each char with 0xff80; result should be 0 if all tests pass |
| 261 | + if (offset + sizeof(ulong) <= length) |
| 262 | + { |
| 263 | + do |
| 264 | + { |
| 265 | + var stringUlong = (ulong*)(pHeader + offset); |
| 266 | + offset += sizeof(ulong); |
| 267 | + if (((*stringUlong | (*stringUlong + 0x0001000100010001UL) | (*stringUlong - 0x0020002000200020UL)) & 0xff80ff80ff80ff80UL) != 0) |
| 268 | + { |
| 269 | + ThrowInvalidHeaderCharacter(pHeader + offset, sizeof(ulong)); |
| 270 | + } |
| 271 | + } while (offset + sizeof(ulong) <= length); |
| 272 | + } |
| 273 | + if (offset + sizeof(uint) <= length) |
231 | 274 | {
|
232 |
| - ThrowInvalidHeaderCharacter(ch); |
| 275 | + var stringUint = (uint*)(pHeader + offset); |
| 276 | + offset += sizeof(uint); |
| 277 | + if (((*stringUint | (*stringUint + 0x00010001u) | (*stringUint - 0x00200020u)) & 0xff80ff80u) != 0) |
| 278 | + { |
| 279 | + ThrowInvalidHeaderCharacter(pHeader + offset, sizeof(uint)); |
| 280 | + } |
| 281 | + } |
| 282 | + if (offset + sizeof(ushort) <= length) |
| 283 | + { |
| 284 | + var stringUshort = (ushort*)(pHeader + offset); |
| 285 | + offset += sizeof(ushort); |
| 286 | + if (((*stringUshort | (*stringUshort + 0x0001u) | (*stringUshort - 0x0020u)) & 0xff80u) != 0) |
| 287 | + { |
| 288 | + ThrowInvalidHeaderCharacter(pHeader + offset, sizeof(ushort)); |
| 289 | + } |
233 | 290 | }
|
234 | 291 | }
|
235 | 292 | }
|
@@ -417,9 +474,26 @@ private static void ThrowInvalidContentLengthException(string value)
|
417 | 474 | throw new InvalidOperationException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number.");
|
418 | 475 | }
|
419 | 476 |
|
420 |
| - private static void ThrowInvalidHeaderCharacter(char ch) |
| 477 | + private unsafe static Exception GetInvalidHeaderCharacterException(byte* end, int byteCount) |
| 478 | + { |
| 479 | + var start = end - byteCount; |
| 480 | + while (start < end) |
| 481 | + { |
| 482 | + var ch = *(char*)start; |
| 483 | + if (ch < 0x20 || ch >= 0x7f) |
| 484 | + { |
| 485 | + return new InvalidOperationException(string.Format("Invalid non-ASCII or control character in header: 0x{0:X4}", (ushort)ch)); |
| 486 | + } |
| 487 | + start += sizeof(char); |
| 488 | + } |
| 489 | + |
| 490 | + // Should never be reached, use different exception type so unit tests can pick it up |
| 491 | + return new ArgumentException("Invalid non-ASCII or control character in header"); |
| 492 | + } |
| 493 | + |
| 494 | + private unsafe static void ThrowInvalidHeaderCharacter(byte* end, int byteCount) |
421 | 495 | {
|
422 |
| - throw new InvalidOperationException(string.Format("Invalid non-ASCII or control character in header: 0x{0:X4}", (ushort)ch)); |
| 496 | + throw GetInvalidHeaderCharacterException(end, byteCount); |
423 | 497 | }
|
424 | 498 | }
|
425 | 499 | }
|
0 commit comments