|
2 | 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. |
3 | 3 |
|
4 | 4 | using System; |
| 5 | +using System.Buffers.Binary; |
5 | 6 | using System.Collections; |
6 | 7 | using System.Collections.Generic; |
7 | 8 | using System.Linq; |
8 | 9 | using System.Runtime.CompilerServices; |
9 | | -using System.Runtime.Intrinsics.X86; |
| 10 | +using System.Runtime.InteropServices; |
10 | 11 | using Microsoft.AspNetCore.Http; |
11 | 12 | using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; |
12 | 13 | using Microsoft.Extensions.Primitives; |
13 | | -using Microsoft.Net.Http.Headers; |
14 | 14 |
|
15 | 15 | namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http |
16 | 16 | { |
@@ -275,108 +275,174 @@ public static void ValidateHeaderNameCharacters(string headerCharacters) |
275 | 275 | } |
276 | 276 | } |
277 | 277 |
|
278 | | - public static unsafe ConnectionOptions ParseConnection(StringValues connection) |
| 278 | + public static ConnectionOptions ParseConnection(StringValues connection) |
279 | 279 | { |
| 280 | + // Keep-alive |
| 281 | + const ulong lowerCaseKeep = 0x0000_0020_0020_0020; // Don't lowercase hyphen |
| 282 | + const ulong keepAliveStart = 0x002d_0070_0065_0065; // 4 chars "eep-" |
| 283 | + const ulong keepAliveMiddle = 0x0076_0069_006c_0061; // 4 chars "aliv" |
| 284 | + const ushort keppAliveEnd = 0x0065; // 1 char "e" |
| 285 | + // Upgrade |
| 286 | + const ulong upgradeStart = 0x0061_0072_0067_0070; // 4 chars "pgra" |
| 287 | + const uint upgradeEnd = 0x0065_0064; // 2 chars "de" |
| 288 | + // Close |
| 289 | + const ulong closeEnd = 0x0065_0073_006f_006c; // 4 chars "lose" |
| 290 | + |
280 | 291 | var connectionOptions = ConnectionOptions.None; |
281 | 292 |
|
282 | 293 | var connectionCount = connection.Count; |
283 | 294 | for (var i = 0; i < connectionCount; i++) |
284 | 295 | { |
285 | | - var value = connection[i]; |
286 | | - fixed (char* ptr = value) |
| 296 | + var value = connection[i].AsSpan(); |
| 297 | + while (value.Length > 0) |
287 | 298 | { |
288 | | - var ch = ptr; |
289 | | - var tokenEnd = ch; |
290 | | - var end = ch + value.Length; |
291 | | - |
292 | | - while (ch < end) |
| 299 | + int offset; |
| 300 | + char c = '\0'; |
| 301 | + // Skip any spaces and empty values. |
| 302 | + for (offset = 0; offset < value.Length; offset++) |
293 | 303 | { |
294 | | - while (tokenEnd < end && *tokenEnd != ',') |
| 304 | + c = value[offset]; |
| 305 | + if (c == ' ' || c == ',') |
295 | 306 | { |
296 | | - tokenEnd++; |
| 307 | + continue; |
297 | 308 | } |
298 | 309 |
|
299 | | - while (ch < tokenEnd && *ch == ' ') |
300 | | - { |
301 | | - ch++; |
302 | | - } |
| 310 | + break; |
| 311 | + } |
303 | 312 |
|
304 | | - var tokenLength = tokenEnd - ch; |
| 313 | + // Skip last read char. |
| 314 | + offset++; |
| 315 | + if ((uint)offset > (uint)value.Length) |
| 316 | + { |
| 317 | + // Consumed enitre string, move to next. |
| 318 | + break; |
| 319 | + } |
| 320 | + |
| 321 | + // Remove leading spaces or empty values. |
| 322 | + value = value.Slice(offset); |
| 323 | + c = ToLowerCase(c); |
| 324 | + |
| 325 | + var byteValue = MemoryMarshal.AsBytes(value); |
305 | 326 |
|
306 | | - if (tokenLength >= 9 && (*ch | 0x20) == 'k') |
| 327 | + offset = 0; |
| 328 | + var potentialConnectionOptions = ConnectionOptions.None; |
| 329 | + |
| 330 | + if (c == 'k' && byteValue.Length >= (2 * sizeof(ulong) + sizeof(ushort))) |
| 331 | + { |
| 332 | + if ((BinaryPrimitives.ReadUInt64LittleEndian(byteValue) | lowerCaseKeep) == keepAliveStart) |
307 | 333 | { |
308 | | - if ((*++ch | 0x20) == 'e' && |
309 | | - (*++ch | 0x20) == 'e' && |
310 | | - (*++ch | 0x20) == 'p' && |
311 | | - *++ch == '-' && |
312 | | - (*++ch | 0x20) == 'a' && |
313 | | - (*++ch | 0x20) == 'l' && |
314 | | - (*++ch | 0x20) == 'i' && |
315 | | - (*++ch | 0x20) == 'v' && |
316 | | - (*++ch | 0x20) == 'e') |
| 334 | + offset += sizeof(ulong) / 2; |
| 335 | + byteValue = byteValue.Slice(sizeof(ulong)); |
| 336 | + |
| 337 | + if (ReadLowerCaseUInt64(byteValue) == keepAliveMiddle) |
317 | 338 | { |
318 | | - ch++; |
319 | | - while (ch < tokenEnd && *ch == ' ') |
320 | | - { |
321 | | - ch++; |
322 | | - } |
| 339 | + offset += sizeof(ulong) / 2; |
| 340 | + byteValue = byteValue.Slice(sizeof(ulong)); |
323 | 341 |
|
324 | | - if (ch == tokenEnd) |
| 342 | + if (ReadLowerCaseUInt16(byteValue) == keppAliveEnd) |
325 | 343 | { |
326 | | - connectionOptions |= ConnectionOptions.KeepAlive; |
| 344 | + offset += sizeof(ushort) / 2; |
| 345 | + potentialConnectionOptions = ConnectionOptions.KeepAlive; |
327 | 346 | } |
328 | 347 | } |
329 | 348 | } |
330 | | - else if (tokenLength >= 7 && (*ch | 0x20) == 'u') |
| 349 | + } |
| 350 | + else if (c == 'u' && byteValue.Length >= (sizeof(ulong) + sizeof(uint))) |
| 351 | + { |
| 352 | + if (ReadLowerCaseUInt64(byteValue) == upgradeStart) |
331 | 353 | { |
332 | | - if ((*++ch | 0x20) == 'p' && |
333 | | - (*++ch | 0x20) == 'g' && |
334 | | - (*++ch | 0x20) == 'r' && |
335 | | - (*++ch | 0x20) == 'a' && |
336 | | - (*++ch | 0x20) == 'd' && |
337 | | - (*++ch | 0x20) == 'e') |
338 | | - { |
339 | | - ch++; |
340 | | - while (ch < tokenEnd && *ch == ' ') |
341 | | - { |
342 | | - ch++; |
343 | | - } |
| 354 | + offset += sizeof(ulong) / 2; |
| 355 | + byteValue = byteValue.Slice(sizeof(ulong)); |
344 | 356 |
|
345 | | - if (ch == tokenEnd) |
346 | | - { |
347 | | - connectionOptions |= ConnectionOptions.Upgrade; |
348 | | - } |
| 357 | + if (ReadLowerCaseUInt32(byteValue) == upgradeEnd) |
| 358 | + { |
| 359 | + offset += sizeof(uint) / 2; |
| 360 | + potentialConnectionOptions = ConnectionOptions.Upgrade; |
349 | 361 | } |
350 | 362 | } |
351 | | - else if (tokenLength >= 5 && (*ch | 0x20) == 'c') |
| 363 | + } |
| 364 | + else if (c == 'c' && byteValue.Length >= sizeof(ulong)) |
| 365 | + { |
| 366 | + if (ReadLowerCaseUInt64(byteValue) == closeEnd) |
352 | 367 | { |
353 | | - if ((*++ch | 0x20) == 'l' && |
354 | | - (*++ch | 0x20) == 'o' && |
355 | | - (*++ch | 0x20) == 's' && |
356 | | - (*++ch | 0x20) == 'e') |
357 | | - { |
358 | | - ch++; |
359 | | - while (ch < tokenEnd && *ch == ' ') |
360 | | - { |
361 | | - ch++; |
362 | | - } |
| 368 | + offset += sizeof(ulong) / 2; |
| 369 | + potentialConnectionOptions = ConnectionOptions.Close; |
| 370 | + } |
| 371 | + } |
363 | 372 |
|
364 | | - if (ch == tokenEnd) |
365 | | - { |
366 | | - connectionOptions |= ConnectionOptions.Close; |
367 | | - } |
368 | | - } |
| 373 | + if ((uint)offset >= (uint)value.Length) |
| 374 | + { |
| 375 | + // Consumed enitre string, move to next string. |
| 376 | + connectionOptions |= potentialConnectionOptions; |
| 377 | + break; |
| 378 | + } |
| 379 | + else |
| 380 | + { |
| 381 | + value = value.Slice(offset); |
| 382 | + } |
| 383 | + |
| 384 | + for (offset = 0; offset < value.Length; offset++) |
| 385 | + { |
| 386 | + c = value[offset]; |
| 387 | + if (c == ' ') |
| 388 | + { |
| 389 | + continue; |
369 | 390 | } |
| 391 | + if (c == ',') |
| 392 | + { |
| 393 | + break; |
| 394 | + } |
| 395 | + else |
| 396 | + { |
| 397 | + // Value contains extra chars; this is not the matched one. |
| 398 | + potentialConnectionOptions = ConnectionOptions.None; |
| 399 | + continue; |
| 400 | + } |
| 401 | + } |
370 | 402 |
|
371 | | - tokenEnd++; |
372 | | - ch = tokenEnd; |
| 403 | + if ((uint)offset >= (uint)value.Length) |
| 404 | + { |
| 405 | + // Consumed enitre string, move to next string. |
| 406 | + connectionOptions |= potentialConnectionOptions; |
| 407 | + break; |
| 408 | + } |
| 409 | + else if (c == ',') |
| 410 | + { |
| 411 | + // Consumed value corretly. |
| 412 | + connectionOptions |= potentialConnectionOptions; |
| 413 | + // Skip comma. |
| 414 | + offset++; |
| 415 | + if ((uint)offset >= (uint)value.Length) |
| 416 | + { |
| 417 | + // Consumed enitre string, move to next string. |
| 418 | + break; |
| 419 | + } |
| 420 | + else |
| 421 | + { |
| 422 | + // Move to next value. |
| 423 | + value = value.Slice(offset); |
| 424 | + } |
373 | 425 | } |
374 | 426 | } |
375 | 427 | } |
376 | 428 |
|
377 | 429 | return connectionOptions; |
378 | 430 | } |
379 | 431 |
|
| 432 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 433 | + private static ulong ReadLowerCaseUInt64(ReadOnlySpan<byte> value) |
| 434 | + => BinaryPrimitives.ReadUInt64LittleEndian(value) | 0x0020_0020_0020_0020; |
| 435 | + |
| 436 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 437 | + private static uint ReadLowerCaseUInt32(ReadOnlySpan<byte> value) |
| 438 | + => BinaryPrimitives.ReadUInt32LittleEndian(value) | 0x0020_0020; |
| 439 | + |
| 440 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 441 | + private static ushort ReadLowerCaseUInt16(ReadOnlySpan<byte> value) |
| 442 | + => (ushort)(BinaryPrimitives.ReadUInt16LittleEndian(value) | 0x0020); |
| 443 | + |
| 444 | + private static char ToLowerCase(char value) => (char)(value | (char)0x0020); |
| 445 | + |
380 | 446 | public static unsafe TransferCoding GetFinalTransferCoding(StringValues transferEncoding) |
381 | 447 | { |
382 | 448 | var transferEncodingOptions = TransferCoding.None; |
|
0 commit comments