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