Implement IHeaderDictionary.ContentLength
This commit is contained in:
parent
815db3d210
commit
ecca980c91
|
|
@ -43,6 +43,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel
|
|||
case RequestRejectionReason.MalformedRequestInvalidHeaders:
|
||||
ex = new BadHttpRequestException("Malformed request: invalid headers.", StatusCodes.Status400BadRequest);
|
||||
break;
|
||||
case RequestRejectionReason.MultipleContentLengths:
|
||||
ex = new BadHttpRequestException("Multiple Content-Length headers.", StatusCodes.Status400BadRequest);
|
||||
break;
|
||||
case RequestRejectionReason.UnexpectedEndOfRequestContent:
|
||||
ex = new BadHttpRequestException("Unexpected end of request content.", StatusCodes.Status400BadRequest);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
|
||||
private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked");
|
||||
private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 ");
|
||||
private static readonly byte[] _bytesContentLengthZero = Encoding.ASCII.GetBytes("\r\nContent-Length: 0");
|
||||
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n");
|
||||
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
|
||||
|
||||
|
|
@ -657,12 +656,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
if (responseHeaders != null &&
|
||||
!responseHeaders.HasTransferEncoding &&
|
||||
responseHeaders.HasContentLength &&
|
||||
_responseBytesWritten + count > responseHeaders.HeaderContentLengthValue.Value)
|
||||
responseHeaders.ContentLength.HasValue &&
|
||||
_responseBytesWritten + count > responseHeaders.ContentLength.Value)
|
||||
{
|
||||
_keepAlive = false;
|
||||
throw new InvalidOperationException(
|
||||
$"Response Content-Length mismatch: too many bytes written ({_responseBytesWritten + count} of {responseHeaders.HeaderContentLengthValue.Value}).");
|
||||
$"Response Content-Length mismatch: too many bytes written ({_responseBytesWritten + count} of {responseHeaders.ContentLength.Value}).");
|
||||
}
|
||||
|
||||
_responseBytesWritten += count;
|
||||
|
|
@ -679,8 +678,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
// Called after VerifyAndUpdateWrite(), so _responseBytesWritten has already been updated.
|
||||
if (responseHeaders != null &&
|
||||
!responseHeaders.HasTransferEncoding &&
|
||||
responseHeaders.HasContentLength &&
|
||||
_responseBytesWritten == responseHeaders.HeaderContentLengthValue.Value)
|
||||
responseHeaders.ContentLength.HasValue &&
|
||||
_responseBytesWritten == responseHeaders.ContentLength.Value)
|
||||
{
|
||||
_abortedCts = null;
|
||||
}
|
||||
|
|
@ -692,8 +691,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
if (!HttpMethods.IsHead(Method) &&
|
||||
!responseHeaders.HasTransferEncoding &&
|
||||
responseHeaders.HeaderContentLengthValue.HasValue &&
|
||||
_responseBytesWritten < responseHeaders.HeaderContentLengthValue.Value)
|
||||
responseHeaders.ContentLength.HasValue &&
|
||||
_responseBytesWritten < responseHeaders.ContentLength.Value)
|
||||
{
|
||||
// We need to close the connection if any bytes were written since the client
|
||||
// cannot be certain of how many bytes it will receive.
|
||||
|
|
@ -703,7 +702,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
|
||||
ReportApplicationError(new InvalidOperationException(
|
||||
$"Response Content-Length mismatch: too few bytes written ({_responseBytesWritten} of {responseHeaders.HeaderContentLengthValue.Value})."));
|
||||
$"Response Content-Length mismatch: too few bytes written ({_responseBytesWritten} of {responseHeaders.ContentLength.Value})."));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -920,13 +919,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
// automatically for HEAD requests or 204, 205, 304 responses.
|
||||
if (_canHaveBody)
|
||||
{
|
||||
if (!hasTransferEncoding && !responseHeaders.HasContentLength)
|
||||
if (!hasTransferEncoding && !responseHeaders.ContentLength.HasValue)
|
||||
{
|
||||
if (appCompleted && StatusCode != StatusCodes.Status101SwitchingProtocols)
|
||||
{
|
||||
// Since the app has completed and we are only now generating
|
||||
// the headers we can safely set the Content-Length to 0.
|
||||
responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
|
||||
responseHeaders.ContentLength = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1468,7 +1467,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
var dateHeaderValues = DateHeaderValueManager.GetDateHeaderValues();
|
||||
|
||||
responseHeaders.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
|
||||
responseHeaders.SetRawContentLength("0", _bytesContentLengthZero);
|
||||
responseHeaders.ContentLength = 0;
|
||||
|
||||
if (ServerOptions.AddServerHeader)
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -5,19 +5,32 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
public abstract class FrameHeaders : IHeaderDictionary
|
||||
{
|
||||
protected long? _contentLength;
|
||||
protected bool _isReadOnly;
|
||||
protected Dictionary<string, StringValues> MaybeUnknown;
|
||||
|
||||
protected Dictionary<string, StringValues> Unknown => MaybeUnknown ?? (MaybeUnknown = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase));
|
||||
|
||||
public long? ContentLength
|
||||
{
|
||||
get { return _contentLength; }
|
||||
set
|
||||
{
|
||||
if (value.HasValue && value.Value < 0)
|
||||
{
|
||||
ThrowInvalidContentLengthException(value.Value);
|
||||
}
|
||||
_contentLength = value;
|
||||
}
|
||||
}
|
||||
|
||||
StringValues IHeaderDictionary.this[string key]
|
||||
{
|
||||
get
|
||||
|
|
@ -41,7 +54,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
get
|
||||
{
|
||||
// Unlike the IHeaderDictionary version, this getter will throw a KeyNotFoundException.
|
||||
return GetValueFast(key);
|
||||
StringValues value;
|
||||
if (!TryGetValueFast(key, out value))
|
||||
{
|
||||
ThrowKeyNotFoundException();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
|
|
@ -88,6 +106,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
ClearFast();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
protected static StringValues AppendValue(StringValues existing, string append)
|
||||
{
|
||||
return StringValues.Concat(existing, append);
|
||||
|
|
@ -112,16 +131,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
protected virtual int GetCountFast()
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual StringValues GetValueFast(string key)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual bool TryGetValueFast(string key, out StringValues value)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual void SetValueFast(string key, StringValues value)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual void AddValueFast(string key, StringValues value)
|
||||
protected virtual bool AddValueFast(string key, StringValues value)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual bool RemoveFast(string key)
|
||||
|
|
@ -130,7 +146,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
protected virtual void ClearFast()
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual void CopyToFast(KeyValuePair<string, StringValues>[] array, int arrayIndex)
|
||||
protected virtual bool CopyToFast(KeyValuePair<string, StringValues>[] array, int arrayIndex)
|
||||
{ throw new NotImplementedException(); }
|
||||
|
||||
protected virtual IEnumerator<KeyValuePair<string, StringValues>> GetEnumeratorFast()
|
||||
|
|
@ -147,7 +163,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
ThrowHeadersReadOnlyException();
|
||||
}
|
||||
AddValueFast(key, value);
|
||||
|
||||
if (!AddValueFast(key, value))
|
||||
{
|
||||
ThrowDuplicateKeyException();
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, StringValues>>.Clear()
|
||||
|
|
@ -175,7 +195,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
void ICollection<KeyValuePair<string, StringValues>>.CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
|
||||
{
|
||||
CopyToFast(array, arrayIndex);
|
||||
if (!CopyToFast(array, arrayIndex))
|
||||
{
|
||||
ThrowArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
|
|
@ -235,17 +258,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
public static long ParseContentLength(StringValues value)
|
||||
{
|
||||
long parsed;
|
||||
if (!HeaderUtilities.TryParseInt64(value.ToString(), out parsed))
|
||||
{
|
||||
ThrowInvalidContentLengthException(value);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
public static unsafe ConnectionOptions ParseConnection(StringValues connection)
|
||||
{
|
||||
var connectionOptions = ConnectionOptions.None;
|
||||
|
|
@ -412,9 +424,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
return transferEncodingOptions;
|
||||
}
|
||||
|
||||
private static void ThrowInvalidContentLengthException(string value)
|
||||
private static void ThrowInvalidContentLengthException(long value)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number.");
|
||||
throw new ArgumentOutOfRangeException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number.");
|
||||
}
|
||||
|
||||
private static void ThrowInvalidHeaderCharacter(char ch)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,59 @@
|
|||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
public partial class FrameRequestHeaders : FrameHeaders
|
||||
{
|
||||
private static long ParseContentLength(string value)
|
||||
{
|
||||
long parsed;
|
||||
if (!HeaderUtilities.TryParseInt64(value, out parsed))
|
||||
{
|
||||
ThrowInvalidContentLengthException(value);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void SetValueUnknown(string key, StringValues value)
|
||||
{
|
||||
Unknown[key] = value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void AppendUnknownHeaders(byte* pKeyBytes, int keyLength, string value)
|
||||
{
|
||||
string key = new string('\0', keyLength);
|
||||
fixed (char* keyBuffer = key)
|
||||
{
|
||||
if (!AsciiUtilities.TryGetAsciiString(pKeyBytes, keyBuffer, keyLength))
|
||||
{
|
||||
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidCharactersInHeaderName);
|
||||
}
|
||||
}
|
||||
|
||||
StringValues existing;
|
||||
Unknown.TryGetValue(key, out existing);
|
||||
Unknown[key] = AppendValue(existing, value);
|
||||
}
|
||||
|
||||
private static void ThrowInvalidContentLengthException(string value)
|
||||
{
|
||||
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidContentLength, value);
|
||||
}
|
||||
|
||||
private static void ThrowMultipleContentLengthsException()
|
||||
{
|
||||
throw BadHttpRequestException.GetException(RequestRejectionReason.MultipleContentLengths);
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
|
|
@ -13,20 +16,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private static readonly byte[] _CrLf = new[] { (byte)'\r', (byte)'\n' };
|
||||
private static readonly byte[] _colonSpace = new[] { (byte)':', (byte)' ' };
|
||||
|
||||
private long? _contentLength;
|
||||
|
||||
public bool HasConnection => HeaderConnection.Count != 0;
|
||||
|
||||
public bool HasTransferEncoding => HeaderTransferEncoding.Count != 0;
|
||||
|
||||
public bool HasContentLength => HeaderContentLength.Count != 0;
|
||||
|
||||
public bool HasServer => HeaderServer.Count != 0;
|
||||
|
||||
public bool HasDate => HeaderDate.Count != 0;
|
||||
|
||||
public long? HeaderContentLengthValue => _contentLength;
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
|
|
@ -58,6 +55,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
private static long ParseContentLength(string value)
|
||||
{
|
||||
long parsed;
|
||||
if (!HeaderUtilities.TryParseInt64(value, out parsed))
|
||||
{
|
||||
ThrowInvalidContentLengthException(value);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void SetValueUnknown(string key, StringValues value)
|
||||
{
|
||||
ValidateHeaderCharacters(key);
|
||||
Unknown[key] = value;
|
||||
}
|
||||
|
||||
private static void ThrowInvalidContentLengthException(string value)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number.");
|
||||
}
|
||||
|
||||
public partial struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
|
||||
{
|
||||
private readonly FrameResponseHeaders _collection;
|
||||
|
|
@ -92,5 +112,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
_state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
public abstract class MessageBody
|
||||
{
|
||||
private static readonly MessageBody _zeroContentLengthClose = new ForZeroContentLength(keepAlive: false);
|
||||
private static readonly MessageBody _zeroContentLengthKeepAlive = new ForZeroContentLength(keepAlive: true);
|
||||
|
||||
private readonly Frame _context;
|
||||
private bool _send100Continue = true;
|
||||
|
||||
|
|
@ -266,18 +268,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
return new ForChunkedEncoding(keepAlive, headers, context);
|
||||
}
|
||||
|
||||
var unparsedContentLength = headers.HeaderContentLength;
|
||||
if (unparsedContentLength.Count > 0)
|
||||
if (headers.ContentLength.HasValue)
|
||||
{
|
||||
try
|
||||
var contentLength = headers.ContentLength.Value;
|
||||
if (contentLength == 0)
|
||||
{
|
||||
var contentLength = FrameHeaders.ParseContentLength(unparsedContentLength);
|
||||
return new ForContentLength(keepAlive, contentLength, context);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
context.RejectRequest(RequestRejectionReason.InvalidContentLength, unparsedContentLength);
|
||||
return keepAlive ? _zeroContentLengthKeepAlive : _zeroContentLengthClose;
|
||||
}
|
||||
|
||||
return new ForContentLength(keepAlive, contentLength, context);
|
||||
}
|
||||
|
||||
// Avoid slowing down most common case
|
||||
|
|
@ -292,7 +291,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
return new ForContentLength(keepAlive, 0, context);
|
||||
return keepAlive ? _zeroContentLengthKeepAlive : _zeroContentLengthClose;
|
||||
}
|
||||
|
||||
private class ForRemainingData : MessageBody
|
||||
|
|
@ -309,6 +308,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}
|
||||
}
|
||||
|
||||
private class ForZeroContentLength : MessageBody
|
||||
{
|
||||
public ForZeroContentLength(bool keepAlive)
|
||||
: base(null)
|
||||
{
|
||||
RequestKeepAlive = keepAlive;
|
||||
}
|
||||
|
||||
protected override ValueTask<ArraySegment<byte>> PeekAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return new ValueTask<ArraySegment<byte>>();
|
||||
}
|
||||
|
||||
protected override void OnConsumedBytes(int count)
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
throw new InvalidDataException("Consuming non-existent data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ForContentLength : MessageBody
|
||||
{
|
||||
private readonly long _contentLength;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
InvalidRequestLine,
|
||||
MalformedRequestInvalidHeaders,
|
||||
InvalidContentLength,
|
||||
MultipleContentLengths,
|
||||
UnexpectedEndOfRequestContent,
|
||||
BadChunkSuffix,
|
||||
BadChunkSizeData,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
|||
{
|
||||
public struct MemoryPoolIterator
|
||||
{
|
||||
private const int _maxULongByteLength = 20;
|
||||
private const ulong _xorPowerOfTwoToHighByte = (0x07ul |
|
||||
0x06ul << 8 |
|
||||
0x05ul << 16 |
|
||||
|
|
@ -23,6 +24,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
|||
|
||||
private static readonly int _vectorSpan = Vector<byte>.Count;
|
||||
|
||||
[ThreadStatic]
|
||||
private static byte[] _numericBytesScratch;
|
||||
|
||||
private MemoryPoolBlock _block;
|
||||
private int _index;
|
||||
|
||||
|
|
@ -1082,6 +1086,101 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
|||
_index = blockIndex;
|
||||
}
|
||||
|
||||
private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch();
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static byte[] CreateNumericBytesScratch()
|
||||
{
|
||||
var bytes = new byte[_maxULongByteLength];
|
||||
_numericBytesScratch = bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public unsafe void CopyFromNumeric(ulong value)
|
||||
{
|
||||
const byte AsciiDigitStart = (byte)'0';
|
||||
|
||||
var block = _block;
|
||||
if (block == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var blockIndex = _index;
|
||||
var bytesLeftInBlock = block.Data.Offset + block.Data.Count - blockIndex;
|
||||
var start = block.DataFixedPtr + blockIndex;
|
||||
|
||||
if (value < 10)
|
||||
{
|
||||
if (bytesLeftInBlock < 1)
|
||||
{
|
||||
CopyFromNumericOverflow(value);
|
||||
return;
|
||||
}
|
||||
_index = blockIndex + 1;
|
||||
block.End = blockIndex + 1;
|
||||
|
||||
*(start) = (byte)(((uint)value) + AsciiDigitStart);
|
||||
}
|
||||
else if (value < 100)
|
||||
{
|
||||
if (bytesLeftInBlock < 2)
|
||||
{
|
||||
CopyFromNumericOverflow(value);
|
||||
return;
|
||||
}
|
||||
_index = blockIndex + 2;
|
||||
block.End = blockIndex + 2;
|
||||
|
||||
var val = (uint)value;
|
||||
var tens = (byte)((val * 205u) >> 11); // div10, valid to 1028
|
||||
|
||||
*(start) = (byte)(tens + AsciiDigitStart);
|
||||
*(start + 1) = (byte)(val - (tens * 10) + AsciiDigitStart);
|
||||
}
|
||||
else if (value < 1000)
|
||||
{
|
||||
if (bytesLeftInBlock < 3)
|
||||
{
|
||||
CopyFromNumericOverflow(value);
|
||||
return;
|
||||
}
|
||||
_index = blockIndex + 3;
|
||||
block.End = blockIndex + 3;
|
||||
|
||||
var val = (uint)value;
|
||||
var digit0 = (byte)((val * 41u) >> 12); // div100, valid to 1098
|
||||
var digits01 = (byte)((val * 205u) >> 11); // div10, valid to 1028
|
||||
|
||||
*(start) = (byte)(digit0 + AsciiDigitStart);
|
||||
*(start + 1) = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart);
|
||||
*(start + 2) = (byte)(val - (digits01 * 10) + AsciiDigitStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
CopyFromNumericOverflow(value);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private unsafe void CopyFromNumericOverflow(ulong value)
|
||||
{
|
||||
const byte AsciiDigitStart = (byte)'0';
|
||||
|
||||
var position = _maxULongByteLength;
|
||||
var byteBuffer = NumericBytesScratch;
|
||||
do
|
||||
{
|
||||
// Consider using Math.DivRem() if available
|
||||
var quotient = value / 10;
|
||||
byteBuffer[--position] = (byte)(AsciiDigitStart + (value - quotient * 10)); // 0x30 = '0'
|
||||
value = quotient;
|
||||
}
|
||||
while (value != 0);
|
||||
|
||||
CopyFrom(byteBuffer, position, _maxULongByteLength - position);
|
||||
}
|
||||
|
||||
public unsafe string GetAsciiString(ref MemoryPoolIterator end)
|
||||
{
|
||||
var block = _block;
|
||||
|
|
@ -1253,6 +1352,5 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
|
|||
// https://github.com/dotnet/coreclr/issues/7459#issuecomment-253965670
|
||||
return Vector.AsVectorByte(new Vector<uint>(vectorByte * 0x01010101u));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
||||
{
|
||||
[Config(typeof(CoreConfig))]
|
||||
public class ResponseHeaders
|
||||
{
|
||||
private const int InnerLoopCount = 512;
|
||||
|
||||
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: Kestrel");
|
||||
private static readonly DateHeaderValueManager _dateHeaderValueManager = new DateHeaderValueManager();
|
||||
private static readonly MemoryPool _memoryPool = new MemoryPool();
|
||||
private FrameResponseHeaders _responseHeadersDirect;
|
||||
private HttpResponse _response;
|
||||
|
||||
[Params("ContentLengthNumeric", "ContentLengthString", "Plaintext", "Common", "Unknown")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[Benchmark(OperationsPerInvoke = InnerLoopCount)]
|
||||
public void SetHeaders()
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case "ContentLengthNumeric":
|
||||
ContentLengthNumeric(InnerLoopCount);
|
||||
break;
|
||||
case "ContentLengthString":
|
||||
ContentLengthString(InnerLoopCount);
|
||||
break;
|
||||
case "Plaintext":
|
||||
Plaintext(InnerLoopCount);
|
||||
break;
|
||||
case "Common":
|
||||
Common(InnerLoopCount);
|
||||
break;
|
||||
case "Unknown":
|
||||
Unknown(InnerLoopCount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(OperationsPerInvoke = InnerLoopCount)]
|
||||
public void OutputHeaders()
|
||||
{
|
||||
for (var i = 0; i < InnerLoopCount; i++)
|
||||
{
|
||||
var block = _memoryPool.Lease();
|
||||
var iter = new MemoryPoolIterator(block);
|
||||
_responseHeadersDirect.CopyTo(ref iter);
|
||||
|
||||
ReturnBlocks(block);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void ContentLengthNumeric(int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_responseHeadersDirect.Reset();
|
||||
|
||||
_response.ContentLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void ContentLengthString(int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_responseHeadersDirect.Reset();
|
||||
|
||||
_response.Headers["Content-Length"] = "0";
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void Plaintext(int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_responseHeadersDirect.Reset();
|
||||
|
||||
_response.StatusCode = 200;
|
||||
_response.ContentType = "text/plain";
|
||||
_response.ContentLength = 13;
|
||||
|
||||
var dateHeaderValues = _dateHeaderValueManager.GetDateHeaderValues();
|
||||
_responseHeadersDirect.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
|
||||
_responseHeadersDirect.SetRawServer("Kestrel", _bytesServer);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void Common(int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_responseHeadersDirect.Reset();
|
||||
|
||||
_response.StatusCode = 200;
|
||||
_response.ContentType = "text/css";
|
||||
_response.ContentLength = 421;
|
||||
|
||||
var headers = _response.Headers;
|
||||
|
||||
headers["Connection"] = "Close";
|
||||
headers["Cache-Control"] = "public, max-age=30672000";
|
||||
headers["Vary"] = "Accept-Encoding";
|
||||
headers["Content-Encoding"] = "gzip";
|
||||
headers["Expires"] = "Fri, 12 Jan 2018 22:01:55 GMT";
|
||||
headers["Last-Modified"] = "Wed, 22 Jun 2016 20:08:29 GMT";
|
||||
headers["Set-Cookie"] = "prov=20629ccd-8b0f-e8ef-2935-cd26609fc0bc; __qca=P0-1591065732-1479167353442; _ga=GA1.2.1298898376.1479167354; _gat=1; sgt=id=9519gfde_3347_4762_8762_df51458c8ec2; acct=t=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric&s=why-is-%e0%a5%a7%e0%a5%a8%e0%a5%a9-numeric";
|
||||
headers["ETag"] = "\"54ef7954-1078\"";
|
||||
headers["Transfer-Encoding"] = "chunked";
|
||||
headers["Content-Language"] = "en-gb";
|
||||
headers["Upgrade"] = "websocket";
|
||||
headers["Via"] = "1.1 varnish";
|
||||
headers["Access-Control-Allow-Origin"] = "*";
|
||||
headers["Access-Control-Allow-credentials"] = "true";
|
||||
headers["Access-Control-Expose-Headers"] = "Client-Protocol, Content-Length, Content-Type, X-Bandwidth-Est, X-Bandwidth-Est2, X-Bandwidth-Est-Comp, X-Bandwidth-Avg, X-Walltime-Ms, X-Sequence-Num";
|
||||
|
||||
var dateHeaderValues = _dateHeaderValueManager.GetDateHeaderValues();
|
||||
_responseHeadersDirect.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
|
||||
_responseHeadersDirect.SetRawServer("Kestrel", _bytesServer);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void Unknown(int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_responseHeadersDirect.Reset();
|
||||
|
||||
_response.StatusCode = 200;
|
||||
_response.ContentType = "text/plain";
|
||||
_response.ContentLength = 13;
|
||||
|
||||
var headers = _response.Headers;
|
||||
|
||||
headers["Link"] = "<https://www.gravatar.com/avatar/6ae816bfaad7bbc58b17fac49ef5cced?d=404&s=250>; rel=\"canonical\"";
|
||||
headers["X-Ua-Compatible"] = "IE=Edge";
|
||||
headers["X-Powered-By"] = "ASP.NET";
|
||||
headers["X-Content-Type-Options"] = "nosniff";
|
||||
headers["X-Xss-Protection"] = "1; mode=block";
|
||||
headers["X-Frame-Options"] = "SAMEORIGIN";
|
||||
headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload";
|
||||
headers["Content-Security-Policy"] = "default-src 'none'; script-src 'self' cdnjs.cloudflare.com code.jquery.com scotthelme.disqus.com a.disquscdn.com www.google-analytics.com go.disqus.com platform.twitter.com cdn.syndication.twimg.com; style-src 'self' a.disquscdn.com fonts.googleapis.com cdnjs.cloudflare.com platform.twitter.com; img-src 'self' data: www.gravatar.com www.google-analytics.com links.services.disqus.com referrer.disqus.com a.disquscdn.com cdn.syndication.twimg.com syndication.twitter.com pbs.twimg.com platform.twitter.com abs.twimg.com; child-src fusiontables.googleusercontent.com fusiontables.google.com www.google.com disqus.com www.youtube.com syndication.twitter.com platform.twitter.com; frame-src fusiontables.googleusercontent.com fusiontables.google.com www.google.com disqus.com www.youtube.com syndication.twitter.com platform.twitter.com; connect-src 'self' links.services.disqus.com; font-src 'self' cdnjs.cloudflare.com fonts.gstatic.com fonts.googleapis.com; form-action 'self'; upgrade-insecure-requests;";
|
||||
|
||||
var dateHeaderValues = _dateHeaderValueManager.GetDateHeaderValues();
|
||||
_responseHeadersDirect.SetRawDate(dateHeaderValues.String, dateHeaderValues.Bytes);
|
||||
_responseHeadersDirect.SetRawServer("Kestrel", _bytesServer);
|
||||
}
|
||||
}
|
||||
|
||||
[Setup]
|
||||
public void Setup()
|
||||
{
|
||||
var connectionContext = new MockConnection(new KestrelServerOptions());
|
||||
var frame = new Frame<object>(application: null, context: connectionContext);
|
||||
frame.Reset();
|
||||
frame.InitializeHeaders();
|
||||
_responseHeadersDirect = (FrameResponseHeaders)frame.ResponseHeaders;
|
||||
var context = new DefaultHttpContext(frame);
|
||||
_response = new DefaultHttpResponse(context);
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case "ContentLengthNumeric":
|
||||
ContentLengthNumeric(1);
|
||||
break;
|
||||
case "ContentLengthString":
|
||||
ContentLengthString(1);
|
||||
break;
|
||||
case "Plaintext":
|
||||
Plaintext(1);
|
||||
break;
|
||||
case "Common":
|
||||
Common(1);
|
||||
break;
|
||||
case "Unknown":
|
||||
Unknown(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReturnBlocks(MemoryPoolBlock block)
|
||||
{
|
||||
while (block != null)
|
||||
{
|
||||
var returningBlock = block;
|
||||
block = returningBlock.Next;
|
||||
|
||||
returningBlock.Pool.Return(returningBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
|
|||
|
||||
public string GetValue(Summary summary, Benchmark benchmark)
|
||||
{
|
||||
var totalNanos = summary.Reports.First(r => r.Benchmark == benchmark).ResultStatistics.Mean;
|
||||
var totalNanos = summary.Reports.First(r => r.Benchmark == benchmark)?.ResultStatistics?.Mean ?? 0;
|
||||
// Make sure we don't divide by zero!!
|
||||
return Math.Abs(totalNanos) > 0.0 ? (NanosPerSecond / totalNanos).ToString("N2") : "N/A";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,6 +202,21 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("NaN")]
|
||||
[InlineData("-1")]
|
||||
public async Task BadRequestIfContentLengthInvalid(string contentLength)
|
||||
{
|
||||
using (var server = new TestServer(context => { return Task.FromResult(0); }))
|
||||
{
|
||||
using (var connection = server.CreateConnection())
|
||||
{
|
||||
await connection.Send($"GET / HTTP/1.1\r\nContent-Length: {contentLength}\r\n\r\n");
|
||||
await ReceiveBadRequestResponse(connection, "400 Bad Request", server.Context.DateHeaderValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceiveBadRequestResponse(TestConnection connection, string expectedResponseStatusCode, string expectedDateHeaderValue)
|
||||
{
|
||||
await connection.ReceiveForcedEnd(
|
||||
|
|
|
|||
|
|
@ -32,14 +32,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
await connection.ReceiveEnd(
|
||||
"HTTP/1.1 200 OK",
|
||||
$"Date: {testContext.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"Server: Kestrel",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"HTTP/1.1 200 OK",
|
||||
"Connection: close",
|
||||
$"Date: {testContext.DateHeaderValue}",
|
||||
"Content-Length: 0",
|
||||
"Server: Kestrel",
|
||||
"Content-Length: 0",
|
||||
"",
|
||||
"");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
|
|
@ -223,5 +226,65 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
var transferEncodingOptions = FrameHeaders.GetFinalTransferCoding(transferEncoding);
|
||||
Assert.Equal(expectedTransferEncodingOptions, transferEncodingOptions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidContentLengthsAccepted()
|
||||
{
|
||||
ValidContentLengthsAccepted(new FrameRequestHeaders());
|
||||
ValidContentLengthsAccepted(new FrameResponseHeaders());
|
||||
}
|
||||
|
||||
private static void ValidContentLengthsAccepted(FrameHeaders frameHeaders)
|
||||
{
|
||||
IDictionary<string, StringValues> headers = frameHeaders;
|
||||
|
||||
StringValues value;
|
||||
|
||||
Assert.False(headers.TryGetValue("Content-Length", out value));
|
||||
Assert.Equal(null, frameHeaders.ContentLength);
|
||||
Assert.False(frameHeaders.ContentLength.HasValue);
|
||||
|
||||
frameHeaders.ContentLength = 1;
|
||||
Assert.True(headers.TryGetValue("Content-Length", out value));
|
||||
Assert.Equal("1", value[0]);
|
||||
Assert.Equal(1, frameHeaders.ContentLength);
|
||||
Assert.True(frameHeaders.ContentLength.HasValue);
|
||||
|
||||
frameHeaders.ContentLength = long.MaxValue;
|
||||
Assert.True(headers.TryGetValue("Content-Length", out value));
|
||||
Assert.Equal(HeaderUtilities.FormatInt64(long.MaxValue), value[0]);
|
||||
Assert.Equal(long.MaxValue, frameHeaders.ContentLength);
|
||||
Assert.True(frameHeaders.ContentLength.HasValue);
|
||||
|
||||
frameHeaders.ContentLength = null;
|
||||
Assert.False(headers.TryGetValue("Content-Length", out value));
|
||||
Assert.Equal(null, frameHeaders.ContentLength);
|
||||
Assert.False(frameHeaders.ContentLength.HasValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidContentLengthsRejected()
|
||||
{
|
||||
InvalidContentLengthsRejected(new FrameRequestHeaders());
|
||||
InvalidContentLengthsRejected(new FrameResponseHeaders());
|
||||
}
|
||||
|
||||
private static void InvalidContentLengthsRejected(FrameHeaders frameHeaders)
|
||||
{
|
||||
IDictionary<string, StringValues> headers = frameHeaders;
|
||||
|
||||
StringValues value;
|
||||
|
||||
Assert.False(headers.TryGetValue("Content-Length", out value));
|
||||
Assert.Equal(null, frameHeaders.ContentLength);
|
||||
Assert.False(frameHeaders.ContentLength.HasValue);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => frameHeaders.ContentLength = -1);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => frameHeaders.ContentLength = long.MinValue);
|
||||
|
||||
Assert.False(headers.TryGetValue("Content-Length", out value));
|
||||
Assert.Equal(null, frameHeaders.ContentLength);
|
||||
Assert.False(frameHeaders.ContentLength.HasValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,10 +41,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
IDictionary<string, StringValues> headers = new FrameRequestHeaders();
|
||||
|
||||
headers["host"] = new[] { "value" };
|
||||
headers["content-length"] = new[] { "0" };
|
||||
|
||||
Assert.NotNull(headers["host"]);
|
||||
Assert.NotNull(headers["content-length"]);
|
||||
Assert.Equal(1, headers["host"].Count);
|
||||
Assert.Equal(1, headers["content-length"].Count);
|
||||
Assert.Equal("value", headers["host"][0]);
|
||||
Assert.Equal("0", headers["content-length"][0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -54,8 +58,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
headers["host"] = new[] { "value" };
|
||||
headers["custom"] = new[] { "value" };
|
||||
headers["Content-Length"] = new[] { "0" };
|
||||
|
||||
Assert.Equal(2, headers.Count);
|
||||
Assert.Equal(3, headers.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -66,14 +71,22 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
StringValues value;
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
Assert.False(headers.TryGetValue("Content-Length", out value));
|
||||
|
||||
headers["host"] = new[] { "value" };
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
Assert.False(headers.TryGetValue("Content-Length", out value));
|
||||
|
||||
headers["custom"] = new[] { "value" };
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
Assert.False(headers.TryGetValue("Content-Length", out value));
|
||||
|
||||
headers["Content-Length"] = new[] { "0" };
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
Assert.True(headers.TryGetValue("Content-Length", out value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -83,6 +96,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
Assert.Throws<KeyNotFoundException>(() => headers["custom"]);
|
||||
Assert.Throws<KeyNotFoundException>(() => headers["host"]);
|
||||
Assert.Throws<KeyNotFoundException>(() => headers["Content-Length"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -90,14 +104,17 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
IDictionary<string, StringValues> headers = new FrameRequestHeaders();
|
||||
var v1 = new[] { "localhost" };
|
||||
var v2 = new[] { "value" };
|
||||
var v2 = new[] { "0" };
|
||||
var v3 = new[] { "value" };
|
||||
headers["host"] = v1;
|
||||
headers["custom"] = v2;
|
||||
headers["Content-Length"] = v2;
|
||||
headers["custom"] = v3;
|
||||
|
||||
Assert.Equal(
|
||||
new[] {
|
||||
new KeyValuePair<string, StringValues>("Host", v1),
|
||||
new KeyValuePair<string, StringValues>("custom", v2),
|
||||
new KeyValuePair<string, StringValues>("Content-Length", v2),
|
||||
new KeyValuePair<string, StringValues>("custom", v3),
|
||||
},
|
||||
headers);
|
||||
}
|
||||
|
|
@ -107,16 +124,18 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
IDictionary<string, StringValues> headers = new FrameRequestHeaders();
|
||||
StringValues v1 = new[] { "localhost" };
|
||||
StringValues v2 = new[] { "value" };
|
||||
StringValues v2 = new[] { "0" };
|
||||
StringValues v3 = new[] { "value" };
|
||||
headers["host"] = v1;
|
||||
headers["custom"] = v2;
|
||||
headers["Content-Length"] = v2;
|
||||
headers["custom"] = v3;
|
||||
|
||||
Assert.Equal<string>(
|
||||
new[] { "Host", "custom" },
|
||||
new[] { "Host", "Content-Length", "custom" },
|
||||
headers.Keys);
|
||||
|
||||
Assert.Equal<StringValues>(
|
||||
new[] { v1, v2 },
|
||||
new[] { v1, v2, v3 },
|
||||
headers.Values);
|
||||
}
|
||||
|
||||
|
|
@ -126,29 +145,50 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
IDictionary<string, StringValues> headers = new FrameRequestHeaders();
|
||||
var kv1 = new KeyValuePair<string, StringValues>("host", new[] { "localhost" });
|
||||
var kv2 = new KeyValuePair<string, StringValues>("custom", new[] { "value" });
|
||||
var kv3 = new KeyValuePair<string, StringValues>("Content-Length", new[] { "0" });
|
||||
var kv1b = new KeyValuePair<string, StringValues>("host", new[] { "not-localhost" });
|
||||
var kv2b = new KeyValuePair<string, StringValues>("custom", new[] { "not-value" });
|
||||
var kv3b = new KeyValuePair<string, StringValues>("Content-Length", new[] { "1" });
|
||||
|
||||
Assert.False(headers.ContainsKey("host"));
|
||||
Assert.False(headers.ContainsKey("custom"));
|
||||
Assert.False(headers.ContainsKey("Content-Length"));
|
||||
Assert.False(headers.Contains(kv1));
|
||||
Assert.False(headers.Contains(kv2));
|
||||
Assert.False(headers.Contains(kv3));
|
||||
|
||||
headers["host"] = kv1.Value;
|
||||
Assert.True(headers.ContainsKey("host"));
|
||||
Assert.False(headers.ContainsKey("custom"));
|
||||
Assert.False(headers.ContainsKey("Content-Length"));
|
||||
Assert.True(headers.Contains(kv1));
|
||||
Assert.False(headers.Contains(kv2));
|
||||
Assert.False(headers.Contains(kv3));
|
||||
Assert.False(headers.Contains(kv1b));
|
||||
Assert.False(headers.Contains(kv2b));
|
||||
Assert.False(headers.Contains(kv3b));
|
||||
|
||||
headers["custom"] = kv2.Value;
|
||||
Assert.True(headers.ContainsKey("host"));
|
||||
Assert.True(headers.ContainsKey("custom"));
|
||||
Assert.False(headers.ContainsKey("Content-Length"));
|
||||
Assert.True(headers.Contains(kv1));
|
||||
Assert.True(headers.Contains(kv2));
|
||||
Assert.False(headers.Contains(kv3));
|
||||
Assert.False(headers.Contains(kv1b));
|
||||
Assert.False(headers.Contains(kv2b));
|
||||
Assert.False(headers.Contains(kv3b));
|
||||
|
||||
headers["Content-Length"] = kv3.Value;
|
||||
Assert.True(headers.ContainsKey("host"));
|
||||
Assert.True(headers.ContainsKey("custom"));
|
||||
Assert.True(headers.ContainsKey("Content-Length"));
|
||||
Assert.True(headers.Contains(kv1));
|
||||
Assert.True(headers.Contains(kv2));
|
||||
Assert.True(headers.Contains(kv3));
|
||||
Assert.False(headers.Contains(kv1b));
|
||||
Assert.False(headers.Contains(kv2b));
|
||||
Assert.False(headers.Contains(kv3b));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -159,16 +199,21 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
StringValues value;
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
Assert.False(headers.TryGetValue("Content-Length", out value));
|
||||
|
||||
headers.Add("host", new[] { "localhost" });
|
||||
headers.Add("custom", new[] { "value" });
|
||||
headers.Add("Content-Length", new[] { "0" });
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
Assert.True(headers.TryGetValue("Content-Length", out value));
|
||||
|
||||
Assert.Throws<ArgumentException>(() => headers.Add("host", new[] { "localhost" }));
|
||||
Assert.Throws<ArgumentException>(() => headers.Add("custom", new[] { "value" }));
|
||||
Assert.Throws<ArgumentException>(() => headers.Add("Content-Length", new[] { "0" }));
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
Assert.True(headers.TryGetValue("Content-Length", out value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -177,17 +222,20 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
IDictionary<string, StringValues> headers = new FrameRequestHeaders();
|
||||
headers.Add("host", new[] { "localhost" });
|
||||
headers.Add("custom", new[] { "value" });
|
||||
headers.Add("Content-Length", new[] { "0" });
|
||||
|
||||
StringValues value;
|
||||
Assert.Equal(2, headers.Count);
|
||||
Assert.Equal(3, headers.Count);
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
Assert.True(headers.TryGetValue("Content-Length", out value));
|
||||
|
||||
headers.Clear();
|
||||
|
||||
Assert.Equal(0, headers.Count);
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
Assert.False(headers.TryGetValue("Content-Length", out value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -196,25 +244,36 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
IDictionary<string, StringValues> headers = new FrameRequestHeaders();
|
||||
headers.Add("host", new[] { "localhost" });
|
||||
headers.Add("custom", new[] { "value" });
|
||||
headers.Add("Content-Length", new[] { "0" });
|
||||
|
||||
StringValues value;
|
||||
Assert.Equal(2, headers.Count);
|
||||
Assert.Equal(3, headers.Count);
|
||||
Assert.True(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
Assert.True(headers.TryGetValue("Content-Length", out value));
|
||||
|
||||
Assert.True(headers.Remove("host"));
|
||||
Assert.False(headers.Remove("host"));
|
||||
|
||||
Assert.Equal(1, headers.Count);
|
||||
Assert.Equal(2, headers.Count);
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.True(headers.TryGetValue("custom", out value));
|
||||
|
||||
Assert.True(headers.Remove("custom"));
|
||||
Assert.False(headers.Remove("custom"));
|
||||
|
||||
Assert.Equal(1, headers.Count);
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
Assert.True(headers.TryGetValue("Content-Length", out value));
|
||||
|
||||
Assert.True(headers.Remove("Content-Length"));
|
||||
Assert.False(headers.Remove("Content-Length"));
|
||||
|
||||
Assert.Equal(0, headers.Count);
|
||||
Assert.False(headers.TryGetValue("host", out value));
|
||||
Assert.False(headers.TryGetValue("custom", out value));
|
||||
Assert.False(headers.TryGetValue("Content-Length", out value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -222,9 +281,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
{
|
||||
IDictionary<string, StringValues> headers = new FrameRequestHeaders();
|
||||
headers.Add("host", new[] { "localhost" });
|
||||
headers.Add("Content-Length", new[] { "0" });
|
||||
headers.Add("custom", new[] { "value" });
|
||||
|
||||
var entries = new KeyValuePair<string, StringValues>[4];
|
||||
var entries = new KeyValuePair<string, StringValues>[5];
|
||||
headers.CopyTo(entries, 1);
|
||||
|
||||
Assert.Null(entries[0].Key);
|
||||
|
|
@ -233,11 +293,14 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
Assert.Equal("Host", entries[1].Key);
|
||||
Assert.Equal(new[] { "localhost" }, entries[1].Value);
|
||||
|
||||
Assert.Equal("custom", entries[2].Key);
|
||||
Assert.Equal(new[] { "value" }, entries[2].Value);
|
||||
Assert.Equal("Content-Length", entries[2].Key);
|
||||
Assert.Equal(new[] { "0" }, entries[2].Value);
|
||||
|
||||
Assert.Null(entries[3].Key);
|
||||
Assert.Equal(new StringValues(), entries[0].Value);
|
||||
Assert.Equal("custom", entries[3].Key);
|
||||
Assert.Equal(new[] { "value" }, entries[3].Value);
|
||||
|
||||
Assert.Null(entries[4].Key);
|
||||
Assert.Equal(new StringValues(), entries[4].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Server.Kestrel;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
{
|
||||
|
|
@ -173,16 +175,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
Assert.Equal($"Invalid Content-Length: \"{contentLength}\". Value must be a positive integral number.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(BadContentLengths))]
|
||||
public void ThrowsWhenSettingRawContentLengthToNonNumericValue(string contentLength)
|
||||
{
|
||||
var headers = new FrameResponseHeaders();
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => headers.SetRawContentLength(contentLength, Encoding.ASCII.GetBytes(contentLength)));
|
||||
Assert.Equal($"Invalid Content-Length: \"{contentLength}\". Value must be a positive integral number.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(BadContentLengths))]
|
||||
public void ThrowsWhenAssigningHeaderContentLengthToNonNumericValue(string contentLength)
|
||||
|
|
@ -201,7 +193,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
var dictionary = (IDictionary<string, StringValues>)headers;
|
||||
dictionary.Add("Content-Length", contentLength);
|
||||
|
||||
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
|
||||
Assert.Equal(ParseLong(contentLength), headers.ContentLength);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -212,17 +204,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
var dictionary = (IDictionary<string, StringValues>)headers;
|
||||
dictionary["Content-Length"] = contentLength;
|
||||
|
||||
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GoodContentLengths))]
|
||||
public void ContentLengthValueCanBeReadAsLongAfterSettingRawHeader(string contentLength)
|
||||
{
|
||||
var headers = new FrameResponseHeaders();
|
||||
headers.SetRawContentLength(contentLength, Encoding.ASCII.GetBytes(contentLength));
|
||||
|
||||
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
|
||||
Assert.Equal(ParseLong(contentLength), headers.ContentLength);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -232,7 +214,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
var headers = new FrameResponseHeaders();
|
||||
headers.HeaderContentLength = contentLength;
|
||||
|
||||
Assert.Equal(ParseLong(contentLength), headers.HeaderContentLengthValue);
|
||||
Assert.Equal(ParseLong(contentLength), headers.ContentLength);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -244,7 +226,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
dictionary.Remove("Content-Length");
|
||||
|
||||
Assert.Equal(null, headers.HeaderContentLengthValue);
|
||||
Assert.Equal(null, headers.ContentLength);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -256,7 +238,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
|
||||
dictionary.Clear();
|
||||
|
||||
Assert.Equal(null, headers.HeaderContentLengthValue);
|
||||
Assert.Equal(null, headers.ContentLength);
|
||||
}
|
||||
|
||||
private static long ParseLong(string value)
|
||||
|
|
|
|||
|
|
@ -1214,6 +1214,86 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CorrectContentLengthsOutput()
|
||||
{
|
||||
using (var pool = new MemoryPool())
|
||||
{
|
||||
var block = pool.Lease();
|
||||
try
|
||||
{
|
||||
for (var i = 0u; i <= 9u; i++)
|
||||
{
|
||||
block.Reset();
|
||||
var iter = new MemoryPoolIterator(block);
|
||||
iter.CopyFromNumeric(i);
|
||||
|
||||
Assert.Equal(block.Array[block.Start], (byte)(i + '0'));
|
||||
Assert.Equal(block.End, block.Start + 1);
|
||||
Assert.Equal(iter.Index, block.End);
|
||||
}
|
||||
for (var i = 10u; i <= 99u; i++)
|
||||
{
|
||||
block.Reset();
|
||||
var iter = new MemoryPoolIterator(block);
|
||||
iter.CopyFromNumeric(i);
|
||||
|
||||
Assert.Equal(block.Array[block.Start], (byte)((i / 10) + '0'));
|
||||
Assert.Equal(block.Array[block.Start + 1], (byte)((i % 10) + '0'));
|
||||
|
||||
Assert.Equal(block.End, block.Start + 2);
|
||||
Assert.Equal(iter.Index, block.End);
|
||||
}
|
||||
for (var i = 100u; i <= 999u; i++)
|
||||
{
|
||||
block.Reset();
|
||||
var iter = new MemoryPoolIterator(block);
|
||||
iter.CopyFromNumeric(i);
|
||||
|
||||
Assert.Equal(block.Array[block.Start], (byte)((i / 100) + '0'));
|
||||
Assert.Equal(block.Array[block.Start + 1], (byte)(((i % 100) / 10) + '0'));
|
||||
Assert.Equal(block.Array[block.Start + 2], (byte)((i % 10) + '0'));
|
||||
|
||||
Assert.Equal(block.End, block.Start + 3);
|
||||
Assert.Equal(iter.Index, block.End);
|
||||
}
|
||||
for (var i = 1000u; i <= 9999u; i++)
|
||||
{
|
||||
block.Reset();
|
||||
var iter = new MemoryPoolIterator(block);
|
||||
iter.CopyFromNumeric(i);
|
||||
|
||||
Assert.Equal(block.Array[block.Start], (byte)((i / 1000) + '0'));
|
||||
Assert.Equal(block.Array[block.Start + 1], (byte)(((i % 1000) / 100) + '0'));
|
||||
Assert.Equal(block.Array[block.Start + 2], (byte)(((i % 100) / 10) + '0'));
|
||||
Assert.Equal(block.Array[block.Start + 3], (byte)((i % 10) + '0'));
|
||||
|
||||
Assert.Equal(block.End, block.Start + 4);
|
||||
Assert.Equal(iter.Index, block.End);
|
||||
}
|
||||
{
|
||||
block.Reset();
|
||||
var iter = new MemoryPoolIterator(block);
|
||||
iter.CopyFromNumeric(ulong.MaxValue);
|
||||
|
||||
var outputBytes = Encoding.ASCII.GetBytes(ulong.MaxValue.ToString("0"));
|
||||
|
||||
for (var i = 0; i < outputBytes.Length; i++)
|
||||
{
|
||||
Assert.Equal(block.Array[block.Start + i], outputBytes[i]);
|
||||
}
|
||||
|
||||
Assert.Equal(block.End, block.Start + outputBytes.Length);
|
||||
Assert.Equal(iter.Index, block.End);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
pool.Return(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private delegate bool GetKnownString(MemoryPoolIterator iter, out string result);
|
||||
|
||||
private void TestKnownStringsInterning(string input, string expected, GetKnownString action)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
|
|
@ -19,47 +20,41 @@ namespace Microsoft.AspNetCore.Server.Kestrel.GeneratedCode
|
|||
return condition ? formatter() : "";
|
||||
}
|
||||
|
||||
static string AppendSwitch(IEnumerable<IGrouping<int, KnownHeader>> values, string className, bool handleUnknown = false) =>
|
||||
$@"fixed (byte* ptr = &keyBytes[keyOffset])
|
||||
{{
|
||||
var pUB = ptr;
|
||||
var pUL = (ulong*)pUB;
|
||||
static string AppendSwitch(IEnumerable<IGrouping<int, KnownHeader>> values, string className) =>
|
||||
$@"var pUL = (ulong*)pUB;
|
||||
var pUI = (uint*)pUB;
|
||||
var pUS = (ushort*)pUB;
|
||||
var stringValue = new StringValues(value);
|
||||
switch (keyLength)
|
||||
{{{Each(values, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if ({header.EqualIgnoreCaseBytes()})
|
||||
{{
|
||||
{{{(header.Identifier == "ContentLength" ? $@"
|
||||
if (_contentLength.HasValue)
|
||||
{{
|
||||
ThrowMultipleContentLengthsException();
|
||||
}}
|
||||
else
|
||||
{{
|
||||
_contentLength = ParseContentLength(value);
|
||||
}}
|
||||
return;" : $@"
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
_headers._{header.Identifier} = AppendValue(_headers._{header.Identifier}, value);
|
||||
}}
|
||||
else
|
||||
{{{If(className == "FrameResponseHeaders" && header.Identifier == "ContentLength", () => @"
|
||||
_contentLength = ParseContentLength(value);")}
|
||||
{{
|
||||
{header.SetBit()};
|
||||
_headers._{header.Identifier} = new StringValues(value);{(header.EnhancedSetter == false ? "" : $@"
|
||||
_headers._{header.Identifier} = stringValue;{(header.EnhancedSetter == false ? "" : $@"
|
||||
_headers._raw{header.Identifier} = null;")}
|
||||
}}
|
||||
return;
|
||||
return;")}
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
|
||||
{(handleUnknown ? $@"
|
||||
key = new string('\0', keyLength);
|
||||
fixed(char *keyBuffer = key)
|
||||
{{
|
||||
if (!AsciiUtilities.TryGetAsciiString(ptr, keyBuffer, keyLength))
|
||||
{{
|
||||
throw BadHttpRequestException.GetException(RequestRejectionReason.InvalidCharactersInHeaderName);
|
||||
}}
|
||||
}}
|
||||
": "")}
|
||||
}}";
|
||||
")}}}";
|
||||
|
||||
class KnownHeader
|
||||
{
|
||||
|
|
@ -72,7 +67,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.GeneratedCode
|
|||
public int BytesCount { get; set; }
|
||||
public bool EnhancedSetter { get; set; }
|
||||
public bool PrimaryHeader { get; set; }
|
||||
public string TestBit() => $"((_bits & {1L << Index}L) != 0)";
|
||||
public string TestBit() => $"(_bits & {1L << Index}L) != 0";
|
||||
public string TestTempBit() => $"(tempBits & {1L << Index}L) != 0";
|
||||
public string TestNotTempBit() => $"(tempBits & ~{1L << Index}L) == 0";
|
||||
public string TestNotBit() => $"(_bits & {1L << Index}L) == 0";
|
||||
public string SetBit() => $"_bits |= {1L << Index}L";
|
||||
public string ClearBit() => $"_bits &= ~{1L << Index}L";
|
||||
|
||||
|
|
@ -135,7 +133,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.GeneratedCode
|
|||
{
|
||||
"Connection",
|
||||
"Date",
|
||||
"Content-Length",
|
||||
"Content-Type",
|
||||
"Server",
|
||||
};
|
||||
|
|
@ -152,7 +149,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.GeneratedCode
|
|||
"Via",
|
||||
"Warning",
|
||||
"Allow",
|
||||
"Content-Length",
|
||||
"Content-Type",
|
||||
"Content-Encoding",
|
||||
"Content-Language",
|
||||
|
|
@ -192,19 +188,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.GeneratedCode
|
|||
"TE",
|
||||
"Translate",
|
||||
"User-Agent",
|
||||
}).Concat(corsRequestHeaders).Select((header, index) => new KnownHeader
|
||||
})
|
||||
.Concat(corsRequestHeaders)
|
||||
.Select((header, index) => new KnownHeader
|
||||
{
|
||||
Name = header,
|
||||
Index = index,
|
||||
PrimaryHeader = requestPrimaryHeaders.Contains(header)
|
||||
}).ToArray();
|
||||
})
|
||||
.Concat(new[] { new KnownHeader
|
||||
{
|
||||
Name = "Content-Length",
|
||||
Index = -1,
|
||||
PrimaryHeader = requestPrimaryHeaders.Contains("Content-Length")
|
||||
}})
|
||||
.ToArray();
|
||||
Debug.Assert(requestHeaders.Length <= 64);
|
||||
Debug.Assert(requestHeaders.Max(x => x.Index) <= 62);
|
||||
|
||||
var enhancedHeaders = new[]
|
||||
{
|
||||
"Connection",
|
||||
"Server",
|
||||
"Date",
|
||||
"Transfer-Encoding",
|
||||
"Content-Length",
|
||||
"Transfer-Encoding"
|
||||
};
|
||||
// http://www.w3.org/TR/cors/#syntax
|
||||
var corsResponseHeaders = new[]
|
||||
|
|
@ -228,13 +235,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.GeneratedCode
|
|||
"Set-Cookie",
|
||||
"Vary",
|
||||
"WWW-Authenticate",
|
||||
}).Concat(corsResponseHeaders).Select((header, index) => new KnownHeader
|
||||
})
|
||||
.Concat(corsResponseHeaders)
|
||||
.Select((header, index) => new KnownHeader
|
||||
{
|
||||
Name = header,
|
||||
Index = index,
|
||||
EnhancedSetter = enhancedHeaders.Contains(header),
|
||||
PrimaryHeader = responsePrimaryHeaders.Contains(header)
|
||||
}).ToArray();
|
||||
})
|
||||
.Concat(new[] { new KnownHeader
|
||||
{
|
||||
Name = "Content-Length",
|
||||
Index = -1,
|
||||
EnhancedSetter = enhancedHeaders.Contains("Content-Length"),
|
||||
PrimaryHeader = responsePrimaryHeaders.Contains("Content-Length")
|
||||
}})
|
||||
.ToArray();
|
||||
// 63 for reponseHeaders as it steals one bit for Content-Length in CopyTo(ref MemoryPoolIterator output)
|
||||
Debug.Assert(responseHeaders.Length <= 63);
|
||||
Debug.Assert(responseHeaders.Max(x => x.Index) <= 62);
|
||||
|
||||
var loops = new[]
|
||||
{
|
||||
new
|
||||
|
|
@ -269,6 +290,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{{
|
||||
|
|
@ -286,61 +308,48 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
private HeaderReferences _headers;
|
||||
{Each(loop.Headers, header => $@"
|
||||
public StringValues Header{header.Identifier}
|
||||
{{
|
||||
{{{(header.Identifier == "ContentLength" ? $@"
|
||||
get
|
||||
{{
|
||||
if ({header.TestBit()})
|
||||
StringValues value;
|
||||
if (_contentLength.HasValue)
|
||||
{{
|
||||
return _headers._{header.Identifier};
|
||||
value = new StringValues(HeaderUtilities.FormatInt64(_contentLength.Value));
|
||||
}}
|
||||
return StringValues.Empty;
|
||||
return value;
|
||||
}}
|
||||
set
|
||||
{{{If(loop.ClassName == "FrameResponseHeaders" && header.Identifier == "ContentLength", () => @"
|
||||
_contentLength = ParseContentLength(value);")}
|
||||
{{
|
||||
_contentLength = ParseContentLength(value);
|
||||
}}" : $@"
|
||||
get
|
||||
{{
|
||||
StringValues value;
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
value = _headers._{header.Identifier};
|
||||
}}
|
||||
return value;
|
||||
}}
|
||||
set
|
||||
{{
|
||||
{header.SetBit()};
|
||||
_headers._{header.Identifier} = value; {(header.EnhancedSetter == false ? "" : $@"
|
||||
_headers._raw{header.Identifier} = null;")}
|
||||
}}
|
||||
}}")}
|
||||
}}")}
|
||||
{Each(loop.Headers.Where(header => header.EnhancedSetter), header => $@"
|
||||
{Each(loop.Headers.Where(header => header.EnhancedSetter), header => $@"
|
||||
public void SetRaw{header.Identifier}(StringValues value, byte[] raw)
|
||||
{{{If(loop.ClassName == "FrameResponseHeaders" && header.Identifier == "ContentLength", () => @"
|
||||
_contentLength = ParseContentLength(value);")}
|
||||
{{
|
||||
{header.SetBit()};
|
||||
_headers._{header.Identifier} = value;
|
||||
_headers._raw{header.Identifier} = raw;
|
||||
}}")}
|
||||
protected override int GetCountFast()
|
||||
{{
|
||||
return BitCount(_bits) + (MaybeUnknown?.Count ?? 0);
|
||||
}}
|
||||
protected override StringValues GetValueFast(string key)
|
||||
{{
|
||||
switch (key.Length)
|
||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
|
||||
{{
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
return _headers._{header.Identifier};
|
||||
}}
|
||||
else
|
||||
{{
|
||||
ThrowKeyNotFoundException();
|
||||
}}
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
if (MaybeUnknown == null)
|
||||
{{
|
||||
ThrowKeyNotFoundException();
|
||||
}}
|
||||
return MaybeUnknown[key];
|
||||
return (_contentLength.HasValue ? 1 : 0 ) + BitCount(_bits) + (MaybeUnknown?.Count ?? 0);
|
||||
}}
|
||||
|
||||
protected override bool TryGetValueFast(string key, out StringValues value)
|
||||
{{
|
||||
switch (key.Length)
|
||||
|
|
@ -348,71 +357,83 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
|
||||
{{
|
||||
{{{(header.Identifier == "ContentLength" ? @"
|
||||
if (_contentLength.HasValue)
|
||||
{
|
||||
value = HeaderUtilities.FormatInt64(_contentLength.Value);
|
||||
return true;
|
||||
}
|
||||
return false;" : $@"
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
value = _headers._{header.Identifier};
|
||||
return true;
|
||||
}}
|
||||
else
|
||||
{{
|
||||
value = StringValues.Empty;
|
||||
return false;
|
||||
}}
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
value = StringValues.Empty;
|
||||
return false;")}
|
||||
}}")}
|
||||
}}
|
||||
break;")}
|
||||
}}
|
||||
|
||||
return MaybeUnknown?.TryGetValue(key, out value) ?? false;
|
||||
}}
|
||||
|
||||
protected override void SetValueFast(string key, StringValues value)
|
||||
{{
|
||||
{(loop.ClassName == "FrameResponseHeaders" ? "ValidateHeaderCharacters(value);" : "")}
|
||||
{{{(loop.ClassName == "FrameResponseHeaders" ? @"
|
||||
ValidateHeaderCharacters(value);" : "")}
|
||||
switch (key.Length)
|
||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
|
||||
{{{If(loop.ClassName == "FrameResponseHeaders" && header.Identifier == "ContentLength", () => @"
|
||||
_contentLength = ParseContentLength(value);")}
|
||||
{{{(header.Identifier == "ContentLength" ? $@"
|
||||
_contentLength = ParseContentLength(value.ToString());" : $@"
|
||||
{header.SetBit()};
|
||||
_headers._{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
|
||||
_headers._raw{header.Identifier} = null;")}
|
||||
_headers._raw{header.Identifier} = null;")}")}
|
||||
return;
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
{(loop.ClassName == "FrameResponseHeaders" ? "ValidateHeaderCharacters(key);" : "")}
|
||||
Unknown[key] = value;
|
||||
}}")}
|
||||
}}
|
||||
break;")}
|
||||
}}
|
||||
|
||||
SetValueUnknown(key, value);
|
||||
}}
|
||||
protected override void AddValueFast(string key, StringValues value)
|
||||
{{
|
||||
{(loop.ClassName == "FrameResponseHeaders" ? "ValidateHeaderCharacters(value);" : "")}
|
||||
|
||||
protected override bool AddValueFast(string key, StringValues value)
|
||||
{{{(loop.ClassName == "FrameResponseHeaders" ? @"
|
||||
ValidateHeaderCharacters(value);" : "")}
|
||||
switch (key.Length)
|
||||
{{{Each(loop.HeadersByLength, byLength => $@"
|
||||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
|
||||
{{
|
||||
if ({header.TestBit()})
|
||||
{{{(header.Identifier == "ContentLength" ? $@"
|
||||
if (!_contentLength.HasValue)
|
||||
{{
|
||||
ThrowDuplicateKeyException();
|
||||
}}{
|
||||
If(loop.ClassName == "FrameResponseHeaders" && header.Identifier == "ContentLength", () => @"
|
||||
_contentLength = ParseContentLength(value);")}
|
||||
{header.SetBit()};
|
||||
_headers._{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
|
||||
_headers._raw{header.Identifier} = null;")}
|
||||
return;
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
{(loop.ClassName == "FrameResponseHeaders" ? "ValidateHeaderCharacters(key);" : "")}
|
||||
_contentLength = ParseContentLength(value);
|
||||
return true;
|
||||
}}
|
||||
return false;" : $@"
|
||||
if ({header.TestNotBit()})
|
||||
{{
|
||||
{header.SetBit()};
|
||||
_headers._{header.Identifier} = value;{(header.EnhancedSetter == false ? "" : $@"
|
||||
_headers._raw{header.Identifier} = null;")}
|
||||
return true;
|
||||
}}
|
||||
return false;")}
|
||||
}}")}
|
||||
}}
|
||||
break;")}
|
||||
}}
|
||||
{(loop.ClassName == "FrameResponseHeaders" ? @"
|
||||
ValidateHeaderCharacters(key);" : "")}
|
||||
Unknown.Add(key, value);
|
||||
// Return true, above will throw and exit for false
|
||||
return true;
|
||||
}}
|
||||
|
||||
protected override bool RemoveFast(string key)
|
||||
{{
|
||||
switch (key.Length)
|
||||
|
|
@ -420,74 +441,88 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
case {byLength.Key}:
|
||||
{{{Each(byLength, header => $@"
|
||||
if (""{header.Name}"".Equals(key, StringComparison.OrdinalIgnoreCase))
|
||||
{{
|
||||
{{{(header.Identifier == "ContentLength" ? @"
|
||||
if (_contentLength.HasValue)
|
||||
{
|
||||
_contentLength = null;
|
||||
return true;
|
||||
}
|
||||
return false;" : $@"
|
||||
if ({header.TestBit()})
|
||||
{{{If(loop.ClassName == "FrameResponseHeaders" && header.Identifier == "ContentLength", () => @"
|
||||
_contentLength = null;")}
|
||||
{{
|
||||
{header.ClearBit()};
|
||||
_headers._{header.Identifier} = StringValues.Empty;{(header.EnhancedSetter == false ? "" : $@"
|
||||
_headers._{header.Identifier} = default(StringValues);{(header.EnhancedSetter == false ? "" : $@"
|
||||
_headers._raw{header.Identifier} = null;")}
|
||||
return true;
|
||||
}}
|
||||
else
|
||||
{{
|
||||
return false;
|
||||
}}
|
||||
}}
|
||||
")}}}
|
||||
break;
|
||||
")}}}
|
||||
return false;")}
|
||||
}}")}
|
||||
}}
|
||||
break;")}
|
||||
}}
|
||||
|
||||
return MaybeUnknown?.Remove(key) ?? false;
|
||||
}}
|
||||
|
||||
protected override void ClearFast()
|
||||
{{
|
||||
{{
|
||||
MaybeUnknown?.Clear();
|
||||
{(loop.ClassName == "FrameResponseHeaders" ? "_contentLength = null;" : "")}
|
||||
if(FrameHeaders.BitCount(_bits) > 12)
|
||||
_contentLength = null;
|
||||
var tempBits = _bits;
|
||||
_bits = 0;
|
||||
if(FrameHeaders.BitCount(tempBits) > 12)
|
||||
{{
|
||||
_headers = default(HeaderReferences);
|
||||
_bits = 0;
|
||||
return;
|
||||
}}
|
||||
{Each(loop.Headers.OrderBy(h => !h.PrimaryHeader), header => $@"
|
||||
if ({header.TestBit()})
|
||||
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength").OrderBy(h => !h.PrimaryHeader), header => $@"
|
||||
if ({header.TestTempBit()})
|
||||
{{
|
||||
_headers._{header.Identifier} = default(StringValues);
|
||||
{header.ClearBit()};
|
||||
if(_bits == 0)
|
||||
if({header.TestNotTempBit()})
|
||||
{{
|
||||
return;
|
||||
}}
|
||||
tempBits &= ~{1L << header.Index}L;
|
||||
}}
|
||||
")}
|
||||
}}
|
||||
|
||||
protected override void CopyToFast(KeyValuePair<string, StringValues>[] array, int arrayIndex)
|
||||
protected override bool CopyToFast(KeyValuePair<string, StringValues>[] array, int arrayIndex)
|
||||
{{
|
||||
if (arrayIndex < 0)
|
||||
{{
|
||||
ThrowArgumentException();
|
||||
return false;
|
||||
}}
|
||||
{Each(loop.Headers, header => $@"
|
||||
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength"), header => $@"
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
if (arrayIndex == array.Length)
|
||||
{{
|
||||
ThrowArgumentException();
|
||||
return false;
|
||||
}}
|
||||
|
||||
array[arrayIndex] = new KeyValuePair<string, StringValues>(""{header.Name}"", _headers._{header.Identifier});
|
||||
++arrayIndex;
|
||||
}}")}
|
||||
if (_contentLength.HasValue)
|
||||
{{
|
||||
if (arrayIndex == array.Length)
|
||||
{{
|
||||
return false;
|
||||
}}
|
||||
array[arrayIndex] = new KeyValuePair<string, StringValues>(""Content-Length"", HeaderUtilities.FormatInt64(_contentLength.Value));
|
||||
++arrayIndex;
|
||||
}}
|
||||
")}
|
||||
((ICollection<KeyValuePair<string, StringValues>>)MaybeUnknown)?.CopyTo(array, arrayIndex);
|
||||
|
||||
return true;
|
||||
}}
|
||||
{(loop.ClassName == "FrameResponseHeaders" ? $@"
|
||||
protected void CopyToFast(ref MemoryPoolIterator output)
|
||||
{{
|
||||
var tempBits = _bits;
|
||||
{Each(loop.Headers.OrderBy(h => !h.PrimaryHeader), header => $@"
|
||||
if ({header.TestBit()})
|
||||
var tempBits = _bits | (_contentLength.HasValue ? {1L << 63}L : 0);
|
||||
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength").OrderBy(h => !h.PrimaryHeader), header => $@"
|
||||
if ({header.TestTempBit()})
|
||||
{{ {(header.EnhancedSetter == false ? "" : $@"
|
||||
if (_headers._raw{header.Identifier} != null)
|
||||
{{
|
||||
|
|
@ -507,35 +542,46 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
}}
|
||||
}}
|
||||
|
||||
tempBits &= ~{1L << header.Index}L;
|
||||
if(tempBits == 0)
|
||||
if({header.TestNotTempBit()})
|
||||
{{
|
||||
return;
|
||||
}}
|
||||
}}
|
||||
")}
|
||||
}}
|
||||
|
||||
" : "")}
|
||||
tempBits &= ~{1L << header.Index}L;
|
||||
}}{(header.Identifier == "Server" ? $@"
|
||||
if ((tempBits & {1L << 63}L) != 0)
|
||||
{{
|
||||
output.CopyFrom(_headerBytes, {loop.Headers.First(x => x.Identifier == "ContentLength").BytesOffset}, {loop.Headers.First(x => x.Identifier == "ContentLength").BytesCount});
|
||||
output.CopyFromNumeric((ulong)ContentLength.Value);
|
||||
|
||||
if((tempBits & ~{1L << 63}L) == 0)
|
||||
{{
|
||||
return;
|
||||
}}
|
||||
tempBits &= ~{1L << 63}L;
|
||||
}}" : "")}")}
|
||||
}}" : "")}
|
||||
{(loop.ClassName == "FrameRequestHeaders" ? $@"
|
||||
public unsafe void Append(byte[] keyBytes, int keyOffset, int keyLength, string value)
|
||||
{{
|
||||
{AppendSwitch(loop.Headers.Where(h => h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName)}
|
||||
|
||||
AppendNonPrimaryHeaders(keyBytes, keyOffset, keyLength, value);
|
||||
fixed (byte* ptr = &keyBytes[keyOffset])
|
||||
{{
|
||||
var pUB = ptr;
|
||||
{AppendSwitch(loop.Headers.Where(h => h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName)}
|
||||
|
||||
AppendNonPrimaryHeaders(ptr, keyOffset, keyLength, value);
|
||||
}}
|
||||
}}
|
||||
|
||||
private unsafe void AppendNonPrimaryHeaders(byte[] keyBytes, int keyOffset, int keyLength, string value)
|
||||
private unsafe void AppendNonPrimaryHeaders(byte* pKeyBytes, int keyOffset, int keyLength, string value)
|
||||
{{
|
||||
string key;
|
||||
{AppendSwitch(loop.Headers.Where(h => !h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName, true)}
|
||||
var pUB = pKeyBytes;
|
||||
{AppendSwitch(loop.Headers.Where(h => !h.PrimaryHeader).GroupBy(x => x.Name.Length), loop.ClassName)}
|
||||
|
||||
StringValues existing;
|
||||
Unknown.TryGetValue(key, out existing);
|
||||
Unknown[key] = AppendValue(existing, value);
|
||||
AppendUnknownHeaders(pKeyBytes, keyLength, value);
|
||||
}}" : "")}
|
||||
|
||||
private struct HeaderReferences
|
||||
{{{Each(loop.Headers, header => @"
|
||||
{{{Each(loop.Headers.Where(header => header.Identifier != "ContentLength"), header => @"
|
||||
public StringValues _" + header.Identifier + ";")}
|
||||
{Each(loop.Headers.Where(header => header.EnhancedSetter), header => @"
|
||||
public byte[] _raw" + header.Identifier + ";")}
|
||||
|
|
@ -547,14 +593,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{{
|
||||
switch (_state)
|
||||
{{
|
||||
{Each(loop.Headers, header => $@"
|
||||
case {header.Index}:
|
||||
goto state{header.Index};
|
||||
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength"), header => $@"
|
||||
case {header.Index}:
|
||||
goto state{header.Index};
|
||||
")}
|
||||
case {loop.Headers.Count()}:
|
||||
goto state{loop.Headers.Count()};
|
||||
default:
|
||||
goto state_default;
|
||||
}}
|
||||
{Each(loop.Headers, header => $@"
|
||||
{Each(loop.Headers.Where(header => header.Identifier != "ContentLength"), header => $@"
|
||||
state{header.Index}:
|
||||
if ({header.TestBit()})
|
||||
{{
|
||||
|
|
@ -563,6 +611,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
return true;
|
||||
}}
|
||||
")}
|
||||
state{loop.Headers.Count()}:
|
||||
if (_collection._contentLength.HasValue)
|
||||
{{
|
||||
_current = new KeyValuePair<string, StringValues>(""Content-Length"", HeaderUtilities.FormatInt64(_collection._contentLength.Value));
|
||||
_state = {loop.Headers.Count() + 1};
|
||||
return true;
|
||||
}}
|
||||
state_default:
|
||||
if (!_hasUnknown || !_unknownEnumerator.MoveNext())
|
||||
{{
|
||||
|
|
|
|||
Loading…
Reference in New Issue