Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Commit

Permalink
Vectorize ValidateHeaderCharacters
Browse files Browse the repository at this point in the history
  • Loading branch information
benaadams committed Jan 10, 2017
1 parent 57b3685 commit 3297a9a
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 11 deletions.
Expand Up @@ -7683,7 +7683,7 @@ protected override bool TryGetValueFast(string key, out StringValues value)
}
protected override void SetValueFast(string key, StringValues value)
{
ValidateHeaderCharacters(value);
ValidateHeaderCharacters(ref value);
switch (key.Length)
{
case 13:
Expand Down Expand Up @@ -8025,7 +8025,7 @@ protected override void SetValueFast(string key, StringValues value)
}
protected override void AddValueFast(string key, StringValues value)
{
ValidateHeaderCharacters(value);
ValidateHeaderCharacters(ref value);
switch (key.Length)
{
case 13:
Expand Down
Expand Up @@ -5,6 +5,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
Expand Down Expand Up @@ -211,25 +213,80 @@ IEnumerator IEnumerable.GetEnumerator()
return TryGetValueFast(key, out value);
}

public static void ValidateHeaderCharacters(StringValues headerValues)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateHeaderCharacters(ref StringValues headerValues)
{
var count = headerValues.Count;
for (var i = 0; i < count; i++)

{
ValidateHeaderCharacters(headerValues[i]);
}
}

public static void ValidateHeaderCharacters(string headerCharacters)
public static unsafe void ValidateHeaderCharacters(string headerCharacters)
{
if (headerCharacters != null)
{
foreach (var ch in headerCharacters)
fixed (char* header = headerCharacters)
{
if (ch < 0x20 || ch > 0x7E)
// offsets and lengths are handled in byte* sizes
var pHeader = (byte*)header;
var offset = 0;
var length = headerCharacters.Length * 2;

if (Vector.IsHardwareAccelerated && Vector<byte>.Count <= length)
{
var vSub = Vector.AsVectorUInt16(new Vector<uint>(0x00200020u));
// 0x7e as highest ascii (don't include DEL)
// 0x20 as lowerest ascii (space)
var vTest = Vector.AsVectorUInt16(new Vector<uint>(0x007e007eu - 0x00200020u));

do
{
var stringVector = Unsafe.Read<Vector<ushort>>(pHeader + offset);
offset += Vector<byte>.Count;
if (Vector.GreaterThanAny(stringVector - vSub, vTest))
{
ThrowInvalidHeaderCharacter(pHeader + offset, Vector<byte>.Count);
}
} while (offset + Vector<byte>.Count <= length);
}

// Non-vector testing:
// Flag > 0x007f => Use value directly, already flagged
// Flag 0x7f => Add 0x0001 to each char so DEL (0x7f) will set a high bit
// Flag < 0x20 => Sub 0x0020 from each char so high bit will be set in previous char bit
// Bitwise | or the above three together
// Bitwise & and each char with 0xff80; result should be 0 if all tests pass
if (offset + sizeof(ulong) <= length)
{
do
{
var stringUlong = (ulong*)(pHeader + offset);
offset += sizeof(ulong);
if (((*stringUlong | (*stringUlong + 0x0001000100010001UL) | (*stringUlong - 0x0020002000200020UL)) & 0xff80ff80ff80ff80UL) != 0)
{
ThrowInvalidHeaderCharacter(pHeader + offset, sizeof(ulong));
}
} while (offset + sizeof(ulong) <= length);
}
if (offset + sizeof(uint) <= length)
{
ThrowInvalidHeaderCharacter(ch);
var stringUint = (uint*)(pHeader + offset);
offset += sizeof(uint);
if (((*stringUint | (*stringUint + 0x00010001u) | (*stringUint - 0x00200020u)) & 0xff80ff80u) != 0)
{
ThrowInvalidHeaderCharacter(pHeader + offset, sizeof(uint));
}
}
if (offset + sizeof(ushort) <= length)
{
var stringUshort = (ushort*)(pHeader + offset);
offset += sizeof(ushort);
if (((*stringUshort | (*stringUshort + 0x0001u) | (*stringUshort - 0x0020u)) & 0xff80u) != 0)
{
ThrowInvalidHeaderCharacter(pHeader + offset, sizeof(ushort));
}
}
}
}
Expand Down Expand Up @@ -417,9 +474,26 @@ private static void ThrowInvalidContentLengthException(string value)
throw new InvalidOperationException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number.");
}

private static void ThrowInvalidHeaderCharacter(char ch)
private unsafe static Exception GetInvalidHeaderCharacterException(byte* end, int byteCount)
{
var start = end - byteCount;
while (start < end)
{
var ch = *(char*)start;
if (ch < 0x20 || ch >= 0x7f)
{
return new InvalidOperationException(string.Format("Invalid non-ASCII or control character in header: 0x{0:X4}", (ushort)ch));
}
start += sizeof(char);
}

// Should never be reached, use different exception type so unit tests can pick it up
return new ArgumentException("Invalid non-ASCII or control character in header");
}

private unsafe static void ThrowInvalidHeaderCharacter(byte* end, int byteCount)
{
throw new InvalidOperationException(string.Format("Invalid non-ASCII or control character in header: 0x{0:X4}", (ushort)ch));
throw GetInvalidHeaderCharacterException(end, byteCount);
}
}
}
Expand Up @@ -77,6 +77,16 @@ public void InitialDictionaryIsEmpty()
[InlineData("Server", "Dašta")]
[InlineData("Unknownš-Header", "Data")]
[InlineData("Seršver", "Data")]
[InlineData("Cookie", "ZGVmYXVsdC1zcmM\0gJ25vbmUnOyBiYXNlLXVyaSAnc2VsZic7IGJsb2NrLWFsbC1taXhlZC1jb250ZW50OyBjaGlsZC1zcmMgcmVuZGVyLmdpdGh1YnVzZXJjb250ZW50LmNvbTsgY29ubmVjdC1zcmMgJ3NlbGYnIHVwbG9hZHMuZ2l0aHViLmNvbSBzdGF0dXMuZ2l0aHViLmNvbSBhcGkuZ2l0aHViLmNvbSB3d3cuZ29vZ2xlLWFuYWx5dGljcy5jb20gZ2l0aHViLWNsb3VkLnMzLmFtYXpvbmF3cy5jb20gYXBpLmJyYWludHJlZWdhdGV3YXkuY29tIGNsaWVudC1hbmFseXRpY3MuYnJhaW50cmVlZ2F0ZXdheS5jb20gd3NzOi8vbGl2ZS5naXRodWIuY29tOyBmb250LXNyYyBhc3NldHMtY2RuLmdpdGh1Yi5jb207IGZvcm0tYWN0aW9uICdzZWxmJyBnaXRodWIuY29tIGdpc3QuZ2l0aHViLmNvbTsgZnJhbWUtYW5jZXN0b3JzICdub25lJzsgZnJhbWUtc3JjIHJlbmRlci5naXRodWJ1c2VyY29udGVudC5jb207IGltZy1zcmMgJ3NlbGYnIGRhdGE6IGFzc2V0cy1jZG4uZ2l0aHViLmNvbSBpZGVudGljb25zLmdpdGh1Yi5jb20gd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tIGNvbGxlY3Rvci5naXRodWJhcHAuY29tICouZ3JhdmF0YXIuY29tICoud3AuY29tIGNoZWNrb3V0LnBheXBhbC5jb20gKi5naXRodWJ1c2VyY29udGVudC5jb207IG1lZGlhLXNyYyAnbm9uZSc7IG9iamVjdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBwbHVnaW4tdHlwZXMgYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2g7IHNjcmlwdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBzdHlsZS1zcmMgJ3Vuc2FmZS1pbmxpbmUnIGFzc2V0cy1jZG4uZ2l0aHViLmNvbTsgcmVwb3J0LXVyaSBodHRwczovL2FwaS5naXRodWIuY29tL19wcml2YXRlL2Jyb3dzZXIvZXJyb3Jz")]
[InlineData("Cookie", "ZGVmYXVsdC1zcmMg\0J25vbmUnOyBiYXNlLXVyaSAnc2VsZic7IGJsb2NrLWFsbC1taXhlZC1jb250ZW50OyBjaGlsZC1zcmMgcmVuZGVyLmdpdGh1YnVzZXJjb250ZW50LmNvbTsgY29ubmVjdC1zcmMgJ3NlbGYnIHVwbG9hZHMuZ2l0aHViLmNvbSBzdGF0dXMuZ2l0aHViLmNvbSBhcGkuZ2l0aHViLmNvbSB3d3cuZ29vZ2xlLWFuYWx5dGljcy5jb20gZ2l0aHViLWNsb3VkLnMzLmFtYXpvbmF3cy5jb20gYXBpLmJyYWludHJlZWdhdGV3YXkuY29tIGNsaWVudC1hbmFseXRpY3MuYnJhaW50cmVlZ2F0ZXdheS5jb20gd3NzOi8vbGl2ZS5naXRodWIuY29tOyBmb250LXNyYyBhc3NldHMtY2RuLmdpdGh1Yi5jb207IGZvcm0tYWN0aW9uICdzZWxmJyBnaXRodWIuY29tIGdpc3QuZ2l0aHViLmNvbTsgZnJhbWUtYW5jZXN0b3JzICdub25lJzsgZnJhbWUtc3JjIHJlbmRlci5naXRodWJ1c2VyY29udGVudC5jb207IGltZy1zcmMgJ3NlbGYnIGRhdGE6IGFzc2V0cy1jZG4uZ2l0aHViLmNvbSBpZGVudGljb25zLmdpdGh1Yi5jb20gd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tIGNvbGxlY3Rvci5naXRodWJhcHAuY29tICouZ3JhdmF0YXIuY29tICoud3AuY29tIGNoZWNrb3V0LnBheXBhbC5jb20gKi5naXRodWJ1c2VyY29udGVudC5jb207IG1lZGlhLXNyYyAnbm9uZSc7IG9iamVjdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBwbHVnaW4tdHlwZXMgYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2g7IHNjcmlwdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBzdHlsZS1zcmMgJ3Vuc2FmZS1pbmxpbmUnIGFzc2V0cy1jZG4uZ2l0aHViLmNvbTsgcmVwb3J0LXVyaSBodHRwczovL2FwaS5naXRodWIuY29tL19wcml2YXRlL2Jyb3dzZXIvZXJyb3Jz")]
[InlineData("Cookie", "ZGVmYXVsdC1zcmMgJ\025vbmUnOyBiYXNlLXVyaSAnc2VsZic7IGJsb2NrLWFsbC1taXhlZC1jb250ZW50OyBjaGlsZC1zcmMgcmVuZGVyLmdpdGh1YnVzZXJjb250ZW50LmNvbTsgY29ubmVjdC1zcmMgJ3NlbGYnIHVwbG9hZHMuZ2l0aHViLmNvbSBzdGF0dXMuZ2l0aHViLmNvbSBhcGkuZ2l0aHViLmNvbSB3d3cuZ29vZ2xlLWFuYWx5dGljcy5jb20gZ2l0aHViLWNsb3VkLnMzLmFtYXpvbmF3cy5jb20gYXBpLmJyYWludHJlZWdhdGV3YXkuY29tIGNsaWVudC1hbmFseXRpY3MuYnJhaW50cmVlZ2F0ZXdheS5jb20gd3NzOi8vbGl2ZS5naXRodWIuY29tOyBmb250LXNyYyBhc3NldHMtY2RuLmdpdGh1Yi5jb207IGZvcm0tYWN0aW9uICdzZWxmJyBnaXRodWIuY29tIGdpc3QuZ2l0aHViLmNvbTsgZnJhbWUtYW5jZXN0b3JzICdub25lJzsgZnJhbWUtc3JjIHJlbmRlci5naXRodWJ1c2VyY29udGVudC5jb207IGltZy1zcmMgJ3NlbGYnIGRhdGE6IGFzc2V0cy1jZG4uZ2l0aHViLmNvbSBpZGVudGljb25zLmdpdGh1Yi5jb20gd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tIGNvbGxlY3Rvci5naXRodWJhcHAuY29tICouZ3JhdmF0YXIuY29tICoud3AuY29tIGNoZWNrb3V0LnBheXBhbC5jb20gKi5naXRodWJ1c2VyY29udGVudC5jb207IG1lZGlhLXNyYyAnbm9uZSc7IG9iamVjdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBwbHVnaW4tdHlwZXMgYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2g7IHNjcmlwdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBzdHlsZS1zcmMgJ3Vuc2FmZS1pbmxpbmUnIGFzc2V0cy1jZG4uZ2l0aHViLmNvbTsgcmVwb3J0LXVyaSBodHRwczovL2FwaS5naXRodWIuY29tL19wcml2YXRlL2Jyb3dzZXIvZXJyb3Jz")]
[InlineData("Cookie", "ZGVmYXVsdC1zcmMgJ25vbmUnOyBiYXNlLXVyaSAnc2VsZic7IGJsb2NrLWFsbC1taXhlZC1jb250ZW50OyBjaGlsZC1zcmMgcmVuZGVyLmdpdGh1YnVzZXJjb250ZW50LmNvbTsgY29ubmVjdC1zcmMgJ3NlbGYnIHVwbG9hZHMuZ2l0aHViLmNvbSBzdGF0dXMuZ2l0aHViLmNvbSBhcGkuZ2l0aHViLmNvbSB3d3cuZ29vZ2xlLWFuYWx5dGljcy5jb20gZ2l0aHViLWNsb3VkLnMzLmFtYXpvbmF3cy5jb20gYXBpLmJyYWludHJlZWdhdGV3YXkuY29tIGNsaWVudC1hbmFseXRpY3MuYnJhaW50cmVlZ2F0ZXdheS5jb20gd3NzOi8vbGl2ZS5naXRodWIuY29tOyBmb250LXNyYyBhc3NldHMtY2RuLmdpdGh1Yi5jb207IGZvcm0tYWN0aW9uICdzZWxmJyBnaXRodWIuY29tIGdpc3QuZ2l0aHViLmNvbTsgZnJhbWUtYW5jZXN0b3JzICdub25lJzsgZnJhbWUtc3JjIHJlbmRlci5naXRodWJ1c2VyY29udGVudC5jb207IGltZy1zcmMgJ3NlbGYnIGRhdGE6IGFzc2V0cy1jZG4uZ2l0aHViLmNvbSBpZGVudGljb25zLmdpdGh1Yi5jb20gd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tIGNvbGxlY3Rvci5naXRodWJhcHAuY29tICouZ3JhdmF0YXIuY29tICoud3AuY29tIGNoZWNrb3V0LnBheXBhbC5jb20gKi5naXRodWJ1c2VyY29udGVudC5jb207IG1lZGlhLXNyYyAnbm9uZSc7IG9iamVjdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBwbHVnaW4tdHlwZXMgYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2g7IHNjcmlwdC1zcmMgYXNzZXRzLWNkbi5naXRodWIuY29tOyBzdHlsZS1zcmMgJ3Vuc2FmZS1pbmxpbmUnIGFzc2V0cy1jZG4uZ2l0aHViLmNvbTsgcmVwb3J0LXVyaSBodHRwczovL2FwaS5naXRodWIuY29tL19wcml2YXRlL2Jyb3dzZXIvZXJyb3Jz\0")]
[InlineData("Content-Security-Polic\0y", "default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors")]
[InlineData("Content-Securit\0y-Policy", "default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors")]
[InlineData("Content-Security-Policy", "default-src 'none'; base-uri 'self'; \0block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors")]
[InlineData("Content-Security-Policy", "default-src 'none'; base-uri 'self'; šblock-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors")]
[InlineData("Content-Security-Policy", "default-src 'none'; base-uri 'self'; \u0080block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errors")]
[InlineData("Content-Security-Policy", "default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src render.githubusercontent.com; connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com github-cloud.s3.amazonaws.com api.braintreegateway.com client-analytics.braintreegateway.com wss://live.github.com; font-src assets-cdn.github.com; form-action 'self' github.com gist.github.com; frame-ancestors 'none'; frame-src render.githubusercontent.com; img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com checkout.paypal.com *.githubusercontent.com; media-src 'none'; object-src assets-cdn.github.com; plugin-types application/x-shockwave-flash; script-src assets-cdn.github.com; style-src 'unsafe-inline' assets-cdn.github.com; report-uri https://api.github.com/_private/browser/errorsš")]
public void AddingControlOrNonAsciiCharactersToHeadersThrows(string key, string value)
{
var responseHeaders = new FrameResponseHeaders();
Expand Down
Expand Up @@ -368,7 +368,7 @@ protected override bool TryGetValueFast(string key, out StringValues value)
}}
protected override void SetValueFast(string key, StringValues value)
{{
{(loop.ClassName == "FrameResponseHeaders" ? "ValidateHeaderCharacters(value);" : "")}
{(loop.ClassName == "FrameResponseHeaders" ? "ValidateHeaderCharacters(ref value);" : "")}
switch (key.Length)
{{{Each(loop.HeadersByLength, byLength => $@"
case {byLength.Key}:
Expand Down

0 comments on commit 3297a9a

Please sign in to comment.