Make connection options parsing "safe" (#21004)
This commit is contained in:
parent
9b44d28df7
commit
3af92e29b5
|
|
@ -124,8 +124,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
var connectionOptions = HttpHeaders.ParseConnection(headers.HeaderConnection);
|
var connectionOptions = HttpHeaders.ParseConnection(headers.HeaderConnection);
|
||||||
|
|
||||||
upgrade = (connectionOptions & ConnectionOptions.Upgrade) == ConnectionOptions.Upgrade;
|
upgrade = (connectionOptions & ConnectionOptions.Upgrade) != 0;
|
||||||
keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
|
keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (upgrade)
|
if (upgrade)
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,9 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Intrinsics.X86;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
|
|
@ -277,101 +275,146 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unsafe ConnectionOptions ParseConnection(StringValues connection)
|
public static ConnectionOptions ParseConnection(StringValues connection)
|
||||||
{
|
{
|
||||||
|
// Keep-alive
|
||||||
|
const ulong lowerCaseKeep = 0x0000_0020_0020_0020; // Don't lowercase hyphen
|
||||||
|
const ulong keepAliveStart = 0x002d_0070_0065_0065; // 4 chars "eep-"
|
||||||
|
const ulong keepAliveMiddle = 0x0076_0069_006c_0061; // 4 chars "aliv"
|
||||||
|
const ushort keepAliveEnd = 0x0065; // 1 char "e"
|
||||||
|
// Upgrade
|
||||||
|
const ulong upgradeStart = 0x0061_0072_0067_0070; // 4 chars "pgra"
|
||||||
|
const uint upgradeEnd = 0x0065_0064; // 2 chars "de"
|
||||||
|
// Close
|
||||||
|
const ulong closeEnd = 0x0065_0073_006f_006c; // 4 chars "lose"
|
||||||
|
|
||||||
var connectionOptions = ConnectionOptions.None;
|
var connectionOptions = ConnectionOptions.None;
|
||||||
|
|
||||||
var connectionCount = connection.Count;
|
var connectionCount = connection.Count;
|
||||||
for (var i = 0; i < connectionCount; i++)
|
for (var i = 0; i < connectionCount; i++)
|
||||||
{
|
{
|
||||||
var value = connection[i];
|
var value = connection[i].AsSpan();
|
||||||
fixed (char* ptr = value)
|
while (value.Length > 0)
|
||||||
{
|
{
|
||||||
var ch = ptr;
|
int offset;
|
||||||
var tokenEnd = ch;
|
char c = '\0';
|
||||||
var end = ch + value.Length;
|
// Skip any spaces and empty values.
|
||||||
|
for (offset = 0; offset < value.Length; offset++)
|
||||||
while (ch < end)
|
|
||||||
{
|
{
|
||||||
while (tokenEnd < end && *tokenEnd != ',')
|
c = value[offset];
|
||||||
|
if (c != ' ' && c != ',')
|
||||||
{
|
{
|
||||||
tokenEnd++;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (ch < tokenEnd && *ch == ' ')
|
// Skip last read char.
|
||||||
|
offset++;
|
||||||
|
if ((uint)offset > (uint)value.Length)
|
||||||
|
{
|
||||||
|
// Consumed enitre string, move to next.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading spaces or empty values.
|
||||||
|
value = value.Slice(offset);
|
||||||
|
c = ToLowerCase(c);
|
||||||
|
|
||||||
|
var byteValue = MemoryMarshal.AsBytes(value);
|
||||||
|
|
||||||
|
offset = 0;
|
||||||
|
var potentialConnectionOptions = ConnectionOptions.None;
|
||||||
|
|
||||||
|
if (c == 'k' && byteValue.Length >= (2 * sizeof(ulong) + sizeof(ushort)))
|
||||||
|
{
|
||||||
|
if ((BinaryPrimitives.ReadUInt64LittleEndian(byteValue) | lowerCaseKeep) == keepAliveStart)
|
||||||
{
|
{
|
||||||
ch++;
|
offset += sizeof(ulong) / 2;
|
||||||
}
|
byteValue = byteValue.Slice(sizeof(ulong));
|
||||||
|
|
||||||
var tokenLength = tokenEnd - ch;
|
if (ReadLowerCaseUInt64(byteValue) == keepAliveMiddle)
|
||||||
|
|
||||||
if (tokenLength >= 9 && (*ch | 0x20) == 'k')
|
|
||||||
{
|
|
||||||
if ((*++ch | 0x20) == 'e' &&
|
|
||||||
(*++ch | 0x20) == 'e' &&
|
|
||||||
(*++ch | 0x20) == 'p' &&
|
|
||||||
*++ch == '-' &&
|
|
||||||
(*++ch | 0x20) == 'a' &&
|
|
||||||
(*++ch | 0x20) == 'l' &&
|
|
||||||
(*++ch | 0x20) == 'i' &&
|
|
||||||
(*++ch | 0x20) == 'v' &&
|
|
||||||
(*++ch | 0x20) == 'e')
|
|
||||||
{
|
{
|
||||||
ch++;
|
offset += sizeof(ulong) / 2;
|
||||||
while (ch < tokenEnd && *ch == ' ')
|
byteValue = byteValue.Slice(sizeof(ulong));
|
||||||
{
|
|
||||||
ch++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == tokenEnd)
|
if (ReadLowerCaseUInt16(byteValue) == keepAliveEnd)
|
||||||
{
|
{
|
||||||
connectionOptions |= ConnectionOptions.KeepAlive;
|
offset += sizeof(ushort) / 2;
|
||||||
|
potentialConnectionOptions = ConnectionOptions.KeepAlive;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (tokenLength >= 7 && (*ch | 0x20) == 'u')
|
}
|
||||||
|
else if (c == 'u' && byteValue.Length >= (sizeof(ulong) + sizeof(uint)))
|
||||||
|
{
|
||||||
|
if (ReadLowerCaseUInt64(byteValue) == upgradeStart)
|
||||||
{
|
{
|
||||||
if ((*++ch | 0x20) == 'p' &&
|
offset += sizeof(ulong) / 2;
|
||||||
(*++ch | 0x20) == 'g' &&
|
byteValue = byteValue.Slice(sizeof(ulong));
|
||||||
(*++ch | 0x20) == 'r' &&
|
|
||||||
(*++ch | 0x20) == 'a' &&
|
|
||||||
(*++ch | 0x20) == 'd' &&
|
|
||||||
(*++ch | 0x20) == 'e')
|
|
||||||
{
|
|
||||||
ch++;
|
|
||||||
while (ch < tokenEnd && *ch == ' ')
|
|
||||||
{
|
|
||||||
ch++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == tokenEnd)
|
if (ReadLowerCaseUInt32(byteValue) == upgradeEnd)
|
||||||
{
|
{
|
||||||
connectionOptions |= ConnectionOptions.Upgrade;
|
offset += sizeof(uint) / 2;
|
||||||
}
|
potentialConnectionOptions = ConnectionOptions.Upgrade;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (tokenLength >= 5 && (*ch | 0x20) == 'c')
|
}
|
||||||
|
else if (c == 'c' && byteValue.Length >= sizeof(ulong))
|
||||||
|
{
|
||||||
|
if (ReadLowerCaseUInt64(byteValue) == closeEnd)
|
||||||
{
|
{
|
||||||
if ((*++ch | 0x20) == 'l' &&
|
offset += sizeof(ulong) / 2;
|
||||||
(*++ch | 0x20) == 'o' &&
|
potentialConnectionOptions = ConnectionOptions.Close;
|
||||||
(*++ch | 0x20) == 's' &&
|
|
||||||
(*++ch | 0x20) == 'e')
|
|
||||||
{
|
|
||||||
ch++;
|
|
||||||
while (ch < tokenEnd && *ch == ' ')
|
|
||||||
{
|
|
||||||
ch++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == tokenEnd)
|
|
||||||
{
|
|
||||||
connectionOptions |= ConnectionOptions.Close;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tokenEnd++;
|
if ((uint)offset >= (uint)value.Length)
|
||||||
ch = tokenEnd;
|
{
|
||||||
|
// Consumed enitre string, move to next string.
|
||||||
|
connectionOptions |= potentialConnectionOptions;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = value.Slice(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (offset = 0; offset < value.Length; offset++)
|
||||||
|
{
|
||||||
|
c = value[offset];
|
||||||
|
if (c == ',')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (c != ' ')
|
||||||
|
{
|
||||||
|
// Value contains extra chars; this is not the matched one.
|
||||||
|
potentialConnectionOptions = ConnectionOptions.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((uint)offset >= (uint)value.Length)
|
||||||
|
{
|
||||||
|
// Consumed enitre string, move to next string.
|
||||||
|
connectionOptions |= potentialConnectionOptions;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (c == ',')
|
||||||
|
{
|
||||||
|
// Consumed value corretly.
|
||||||
|
connectionOptions |= potentialConnectionOptions;
|
||||||
|
// Skip comma.
|
||||||
|
offset++;
|
||||||
|
if ((uint)offset >= (uint)value.Length)
|
||||||
|
{
|
||||||
|
// Consumed enitre string, move to next string.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Move to next value.
|
||||||
|
value = value.Slice(offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -379,6 +422,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
return connectionOptions;
|
return connectionOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong ReadLowerCaseUInt64(ReadOnlySpan<byte> value)
|
||||||
|
=> BinaryPrimitives.ReadUInt64LittleEndian(value) | 0x0020_0020_0020_0020;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static uint ReadLowerCaseUInt32(ReadOnlySpan<byte> value)
|
||||||
|
=> BinaryPrimitives.ReadUInt32LittleEndian(value) | 0x0020_0020;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ushort ReadLowerCaseUInt16(ReadOnlySpan<byte> value)
|
||||||
|
=> (ushort)(BinaryPrimitives.ReadUInt16LittleEndian(value) | 0x0020);
|
||||||
|
|
||||||
private static char ToLowerCase(char value) => (char)(value | (char)0x0020);
|
private static char ToLowerCase(char value) => (char)(value | (char)0x0020);
|
||||||
|
|
||||||
public static TransferCoding GetFinalTransferCoding(StringValues transferEncoding)
|
public static TransferCoding GetFinalTransferCoding(StringValues transferEncoding)
|
||||||
|
|
|
||||||
|
|
@ -1070,10 +1070,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
var responseHeaders = HttpResponseHeaders;
|
var responseHeaders = HttpResponseHeaders;
|
||||||
var hasConnection = responseHeaders.HasConnection;
|
var hasConnection = responseHeaders.HasConnection;
|
||||||
var connectionOptions = HttpHeaders.ParseConnection(responseHeaders.HeaderConnection);
|
|
||||||
var hasTransferEncoding = responseHeaders.HasTransferEncoding;
|
var hasTransferEncoding = responseHeaders.HasTransferEncoding;
|
||||||
|
|
||||||
if (_keepAlive && hasConnection && (connectionOptions & ConnectionOptions.KeepAlive) != ConnectionOptions.KeepAlive)
|
if (_keepAlive &&
|
||||||
|
hasConnection &&
|
||||||
|
(HttpHeaders.ParseConnection(responseHeaders.HeaderConnection) & ConnectionOptions.KeepAlive) == 0)
|
||||||
{
|
{
|
||||||
_keepAlive = false;
|
_keepAlive = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[InlineData(",, ", (int)(ConnectionOptions.None))]
|
[InlineData(",, ", (int)(ConnectionOptions.None))]
|
||||||
[InlineData(" , ,", (int)(ConnectionOptions.None))]
|
[InlineData(" , ,", (int)(ConnectionOptions.None))]
|
||||||
[InlineData(" , , ", (int)(ConnectionOptions.None))]
|
[InlineData(" , , ", (int)(ConnectionOptions.None))]
|
||||||
|
[InlineData("KEEP-ALIVE", (int)(ConnectionOptions.KeepAlive))]
|
||||||
[InlineData("keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
[InlineData("keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
||||||
[InlineData("keep-alive, upgrade", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
|
[InlineData("keep-alive, upgrade", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
|
||||||
[InlineData("keep-alive,upgrade", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
|
[InlineData("keep-alive,upgrade", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
|
||||||
|
|
@ -40,6 +41,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[InlineData(", ,keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
[InlineData(", ,keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
||||||
[InlineData(",, keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
[InlineData(",, keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
||||||
[InlineData(", , keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
[InlineData(", , keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
||||||
|
[InlineData("UPGRADE", (int)(ConnectionOptions.Upgrade))]
|
||||||
|
[InlineData("upgrade", (int)(ConnectionOptions.Upgrade))]
|
||||||
[InlineData("upgrade,", (int)(ConnectionOptions.Upgrade))]
|
[InlineData("upgrade,", (int)(ConnectionOptions.Upgrade))]
|
||||||
[InlineData("upgrade,,", (int)(ConnectionOptions.Upgrade))]
|
[InlineData("upgrade,,", (int)(ConnectionOptions.Upgrade))]
|
||||||
[InlineData("upgrade, ", (int)(ConnectionOptions.Upgrade))]
|
[InlineData("upgrade, ", (int)(ConnectionOptions.Upgrade))]
|
||||||
|
|
@ -72,6 +75,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[InlineData("u,keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
[InlineData("u,keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
||||||
[InlineData("ke,upgrade", (int)(ConnectionOptions.Upgrade))]
|
[InlineData("ke,upgrade", (int)(ConnectionOptions.Upgrade))]
|
||||||
[InlineData("up,keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
[InlineData("up,keep-alive", (int)(ConnectionOptions.KeepAlive))]
|
||||||
|
[InlineData("CLOSE", (int)(ConnectionOptions.Close))]
|
||||||
[InlineData("close", (int)(ConnectionOptions.Close))]
|
[InlineData("close", (int)(ConnectionOptions.Close))]
|
||||||
[InlineData("upgrade,close", (int)(ConnectionOptions.Close | ConnectionOptions.Upgrade))]
|
[InlineData("upgrade,close", (int)(ConnectionOptions.Close | ConnectionOptions.Upgrade))]
|
||||||
[InlineData("close,upgrade", (int)(ConnectionOptions.Close | ConnectionOptions.Upgrade))]
|
[InlineData("close,upgrade", (int)(ConnectionOptions.Close | ConnectionOptions.Upgrade))]
|
||||||
|
|
@ -87,6 +91,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
[InlineData("close2 ", (int)(ConnectionOptions.None))]
|
[InlineData("close2 ", (int)(ConnectionOptions.None))]
|
||||||
[InlineData("close2 ,", (int)(ConnectionOptions.None))]
|
[InlineData("close2 ,", (int)(ConnectionOptions.None))]
|
||||||
[InlineData("close2,", (int)(ConnectionOptions.None))]
|
[InlineData("close2,", (int)(ConnectionOptions.None))]
|
||||||
|
[InlineData("close close", (int)(ConnectionOptions.None))]
|
||||||
|
[InlineData("close dclose", (int)(ConnectionOptions.None))]
|
||||||
[InlineData("keep-alivekeep-alive", (int)(ConnectionOptions.None))]
|
[InlineData("keep-alivekeep-alive", (int)(ConnectionOptions.None))]
|
||||||
[InlineData("keep-aliveupgrade", (int)(ConnectionOptions.None))]
|
[InlineData("keep-aliveupgrade", (int)(ConnectionOptions.None))]
|
||||||
[InlineData("upgradeupgrade", (int)(ConnectionOptions.None))]
|
[InlineData("upgradeupgrade", (int)(ConnectionOptions.None))]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue