Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 25 additions & 14 deletions src/Kestrel.Core/Internal/Http/Http1Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -360,23 +360,37 @@ internal void EnsureHostHeaderExists()
// request message that contains more than one Host header field or a
// Host header field with an invalid field-value.

var host = HttpRequestHeaders.HeaderHost;
var hostText = host.ToString();
if (host.Count <= 0)
var hostCount = HttpRequestHeaders.HostCount;
var hostText = HttpRequestHeaders.HeaderHost.ToString();
if (hostCount <= 0)
{
if (_httpVersion == Http.HttpVersion.Http10)
{
return;
}
BadHttpRequestException.Throw(RequestRejectionReason.MissingHostHeader);
}
else if (host.Count > 1)
else if (hostCount > 1)
{
BadHttpRequestException.Throw(RequestRejectionReason.MultipleHostHeaders);
}
else if (_requestTargetForm == HttpRequestTarget.AuthorityForm)
else if (_requestTargetForm != HttpRequestTarget.OriginForm)
{
if (!host.Equals(RawTarget))
// Tail call
ValidateNonOrginHostHeader(hostText);
}
else
{
// Tail call
HttpUtilities.ValidateHostHeader(hostText);
}
}

private void ValidateNonOrginHostHeader(string hostText)
{
if (_requestTargetForm == HttpRequestTarget.AuthorityForm)
{
if (hostText != RawTarget)
{
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
Expand All @@ -390,20 +404,18 @@ internal void EnsureHostHeaderExists()

// System.Uri doesn't not tell us if the port was in the original string or not.
// When IsDefaultPort = true, we will allow Host: with or without the default port
if (host != _absoluteRequestTarget.Authority)
if (hostText != _absoluteRequestTarget.Authority)
{
if (!_absoluteRequestTarget.IsDefaultPort
|| host != _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port.ToString(CultureInfo.InvariantCulture))
|| hostText != _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port.ToString(CultureInfo.InvariantCulture))
{
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}
}

if (!HttpUtilities.IsValidHostHeader(hostText))
{
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
// Tail call
HttpUtilities.ValidateHostHeader(hostText);
}

protected override void OnReset()
Expand Down Expand Up @@ -454,8 +466,7 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio
{
if (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders)
{
BadHttpRequestException.Throw(RequestRejectionReason
.MalformedRequestInvalidHeaders);
BadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders);
}
throw;
}
Expand Down
11 changes: 5 additions & 6 deletions src/Kestrel.Core/Internal/Http/Http1MessageBody.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,10 @@ public static MessageBody For(
// see also http://tools.ietf.org/html/rfc2616#section-4.4
var keepAlive = httpVersion != HttpVersion.Http10;

var connection = headers.HeaderConnection;
var upgrade = false;
if (connection.Count > 0)
if (headers.HasConnection)
{
var connectionOptions = HttpHeaders.ParseConnection(connection);
var connectionOptions = HttpHeaders.ParseConnection(headers.HeaderConnection);

upgrade = (connectionOptions & ConnectionOptions.Upgrade) == ConnectionOptions.Upgrade;
keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
Expand All @@ -233,10 +232,10 @@ public static MessageBody For(
return new ForUpgrade(context);
}

var transferEncoding = headers.HeaderTransferEncoding;
if (transferEncoding.Count > 0)
if (headers.HasTransferEncoding)
{
var transferCoding = HttpHeaders.GetFinalTransferCoding(headers.HeaderTransferEncoding);
var transferEncoding = headers.HeaderTransferEncoding;
var transferCoding = HttpHeaders.GetFinalTransferCoding(transferEncoding);

// https://tools.ietf.org/html/rfc7230#section-3.3.3
// If a Transfer-Encoding header field
Expand Down
11 changes: 11 additions & 0 deletions src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ public partial class HttpRequestHeaders

private long _bits = 0;
private HeaderReferences _headers;

public bool HasConnection => (_bits & 2L) != 0;
public bool HasTransferEncoding => (_bits & 64L) != 0;

public int HostCount => _headers._Host.Count;

public StringValues HeaderCacheControl
{
Expand Down Expand Up @@ -4794,6 +4799,12 @@ public partial class HttpResponseHeaders

private long _bits = 0;
private HeaderReferences _headers;

public bool HasConnection => (_bits & 2L) != 0;
public bool HasDate => (_bits & 4L) != 0;
public bool HasTransferEncoding => (_bits & 64L) != 0;
public bool HasServer => (_bits & 33554432L) != 0;


public StringValues HeaderCacheControl
{
Expand Down
11 changes: 9 additions & 2 deletions src/Kestrel.Core/Internal/Http/HttpHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ StringValues IHeaderDictionary.this[string key]
{
ThrowHeadersReadOnlyException();
}
SetValueFast(key, value);
if (value.Count == 0)
{
RemoveFast(key);
}
else
{
SetValueFast(key, value);
}
}
}

Expand Down Expand Up @@ -164,7 +171,7 @@ void IDictionary<string, StringValues>.Add(string key, StringValues value)
ThrowHeadersReadOnlyException();
}

if (!AddValueFast(key, value))
if (value.Count > 0 && !AddValueFast(key, value))
{
ThrowDuplicateKeyException();
}
Expand Down
4 changes: 2 additions & 2 deletions src/Kestrel.Core/Internal/Http/HttpProtocol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1111,7 +1111,6 @@ private void CreateResponseHeader(bool appCompleted)
var hasConnection = responseHeaders.HasConnection;
var connectionOptions = HttpHeaders.ParseConnection(responseHeaders.HeaderConnection);
var hasTransferEncoding = responseHeaders.HasTransferEncoding;
var transferCoding = HttpHeaders.GetFinalTransferCoding(responseHeaders.HeaderTransferEncoding);

if (_keepAlive && hasConnection && (connectionOptions & ConnectionOptions.KeepAlive) != ConnectionOptions.KeepAlive)
{
Expand All @@ -1123,7 +1122,8 @@ private void CreateResponseHeader(bool appCompleted)
// chunked is applied to a response payload body, the sender MUST either
// apply chunked as the final transfer coding or terminate the message
// by closing the connection.
if (hasTransferEncoding && transferCoding != TransferCoding.Chunked)
if (hasTransferEncoding &&
HttpHeaders.GetFinalTransferCoding(responseHeaders.HeaderTransferEncoding) != TransferCoding.Chunked)
{
_keepAlive = false;
}
Expand Down
8 changes: 0 additions & 8 deletions src/Kestrel.Core/Internal/Http/HttpResponseHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,6 @@ public partial class HttpResponseHeaders : HttpHeaders
private static readonly byte[] _CrLf = new[] { (byte)'\r', (byte)'\n' };
private static readonly byte[] _colonSpace = new[] { (byte)':', (byte)' ' };

public bool HasConnection => HeaderConnection.Count != 0;

public bool HasTransferEncoding => HeaderTransferEncoding.Count != 0;

public bool HasServer => HeaderServer.Count != 0;

public bool HasDate => HeaderDate.Count != 0;

public Enumerator GetEnumerator()
{
return new Enumerator(this);
Expand Down
5 changes: 1 addition & 4 deletions src/Kestrel.Core/Internal/Http2/Http2Stream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,7 @@ protected override bool TryParseRequest(ReadResult result, out bool endConnectio
}

var hostText = host.ToString();
if (!HttpUtilities.IsValidHostHeader(hostText))
{
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
HttpUtilities.ValidateHostHeader(hostText);

endConnection = false;
return true;
Expand Down
98 changes: 54 additions & 44 deletions src/Kestrel.Core/Internal/Infrastructure/HttpUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -426,45 +426,44 @@ public static string SchemeToString(HttpScheme scheme)
}
}

public static bool IsValidHostHeader(string hostText)
public static void ValidateHostHeader(string hostText)
{
// The spec allows empty values
if (string.IsNullOrEmpty(hostText))
{
return true;
// The spec allows empty values
return;
}

if (hostText[0] == '[')
var firstChar = hostText[0];
if (firstChar == '[')
{
return IsValidIPv6Host(hostText);
// Tail call
ValidateIPv6Host(hostText);
}

if (hostText[0] == ':')
else
{
// Only a port
return false;
}
if (firstChar == ':')
{
// Only a port
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}

var i = 0;
for (; i < hostText.Length; i++)
{
if (!IsValidHostChar(hostText[i]))
// Enregister array
var hostCharValidity = HostCharValidity;
for (var i = 0; i < hostText.Length; i++)
{
break;
if (!hostCharValidity[hostText[i]])
{
// Tail call
ValidateHostPort(hostText, i);
return;
}
}
}
return IsValidHostPort(hostText, i);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsValidHostChar(char ch)
{
return ch < HostCharValidity.Length && HostCharValidity[ch];
}

// The lead '[' was already checked
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsValidIPv6Host(string hostText)
private static void ValidateIPv6Host(string hostText)
{
for (var i = 1; i < hostText.Length; i++)
{
Expand All @@ -474,58 +473,69 @@ private static bool IsValidIPv6Host(string hostText)
// [::1] is the shortest valid IPv6 host
if (i < 4)
{
return false;
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
else if (i + 1 < hostText.Length)
{
// Tail call
ValidateHostPort(hostText, i + 1);
}
return IsValidHostPort(hostText, i + 1);
return;
}

if (!IsHex(ch) && ch != ':' && ch != '.')
{
return false;
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}

// Must contain a ']'
return false;
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsValidHostPort(string hostText, int offset)
private static void ValidateHostPort(string hostText, int offset)
{
if (offset == hostText.Length)
{
return true;
}

if (hostText[offset] != ':' || hostText.Length == offset + 1)
var firstChar = hostText[offset];
offset++;
if (firstChar != ':' || offset == hostText.Length)
{
// Must have at least one number after the colon if present.
return false;
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}

for (var i = offset + 1; i < hostText.Length; i++)
for (var i = offset; i < hostText.Length; i++)
{
if (!IsNumeric(hostText[i]))
{
return false;
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}

return true;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsNumeric(char ch)
{
return '0' <= ch && ch <= '9';
// '0' <= ch && ch <= '9'
// (uint)(ch - '0') <= (uint)('9' - '0')

// Subtract start of range '0'
// Cast to uint to change negative numbers to large numbers
// Check if less than 10 representing chars '0' - '9'
return (uint)(ch - '0') < 10u;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsHex(char ch)
{
return IsNumeric(ch)
|| ('a' <= ch && ch <= 'f')
|| ('A' <= ch && ch <= 'F');
// || ('a' <= ch && ch <= 'f')
// || ('A' <= ch && ch <= 'F');

// Lowercase indiscriminately (or with 32)
// Subtract start of range 'a'
// Cast to uint to change negative numbers to large numbers
// Check if less than 6 representing chars 'a' - 'f'
|| (uint)((ch | 32) - 'a') < 6u;
}
}
}
5 changes: 3 additions & 2 deletions test/Kestrel.Core.Tests/HttpUtilitiesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ public static TheoryData<string> HostHeaderData
[MemberData(nameof(HostHeaderData))]
public void ValidHostHeadersParsed(string host)
{
Assert.True(HttpUtilities.IsValidHostHeader(host));
HttpUtilities.ValidateHostHeader(host);
// Shouldn't throw
}

public static TheoryData<string> HostHeaderInvalidData
Expand Down Expand Up @@ -224,7 +225,7 @@ public static TheoryData<string> HostHeaderInvalidData
[MemberData(nameof(HostHeaderInvalidData))]
public void InvalidHostHeadersRejected(string host)
{
Assert.False(HttpUtilities.IsValidHostHeader(host));
Assert.Throws<BadHttpRequestException>(() => HttpUtilities.ValidateHostHeader(host));
}
}
}
Loading