Handle multiple tokens in Connection header (#1170).
This commit is contained in:
parent
d64b4c7acb
commit
1ffad5ca38
|
|
@ -0,0 +1,16 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
[Flags]
|
||||
public enum ConnectionOptions
|
||||
{
|
||||
None = 0,
|
||||
Close = 1,
|
||||
KeepAlive = 2,
|
||||
Upgrade = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -259,18 +259,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
get { return HasResponseStarted; }
|
||||
}
|
||||
|
||||
bool IHttpUpgradeFeature.IsUpgradableRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
StringValues values;
|
||||
if (RequestHeaders.TryGetValue("Connection", out values))
|
||||
{
|
||||
return values.Any(value => value.IndexOf("upgrade", StringComparison.OrdinalIgnoreCase) != -1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool IHttpUpgradeFeature.IsUpgradableRequest => _upgrade;
|
||||
|
||||
bool IFeatureCollection.IsReadOnly => false;
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
private RequestProcessingStatus _requestProcessingStatus;
|
||||
protected bool _keepAlive;
|
||||
protected bool _upgrade;
|
||||
private bool _canHaveBody;
|
||||
private bool _autoChunk;
|
||||
protected Exception _applicationException;
|
||||
|
|
@ -810,13 +811,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
var responseHeaders = FrameResponseHeaders;
|
||||
var hasConnection = responseHeaders.HasConnection;
|
||||
var connectionOptions = hasConnection ? FrameHeaders.ParseConnection(responseHeaders.HeaderConnection) : ConnectionOptions.None;
|
||||
|
||||
var end = SocketOutput.ProducingStart();
|
||||
|
||||
if (_keepAlive && hasConnection)
|
||||
{
|
||||
var connectionValue = responseHeaders.HeaderConnection.ToString();
|
||||
_keepAlive = connectionValue.Equals("keep-alive", StringComparison.OrdinalIgnoreCase);
|
||||
_keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
|
||||
}
|
||||
|
||||
// Set whether response can have body
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
|
@ -265,6 +264,106 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
return parsed;
|
||||
}
|
||||
|
||||
public static unsafe ConnectionOptions ParseConnection(StringValues connection)
|
||||
{
|
||||
var connectionOptions = ConnectionOptions.None;
|
||||
|
||||
foreach (var value in connection)
|
||||
{
|
||||
fixed (char* ptr = value)
|
||||
{
|
||||
var ch = ptr;
|
||||
var tokenEnd = ch;
|
||||
var end = ch + value.Length;
|
||||
|
||||
while (ch < end)
|
||||
{
|
||||
while (tokenEnd < end && *tokenEnd != ',')
|
||||
{
|
||||
tokenEnd++;
|
||||
}
|
||||
|
||||
while (ch < tokenEnd && *ch == ' ')
|
||||
{
|
||||
ch++;
|
||||
}
|
||||
|
||||
var tokenLength = tokenEnd - ch;
|
||||
|
||||
if (tokenLength >= 9 && (*ch | 0x20) == 'k')
|
||||
{
|
||||
if ((*++ch | 0x20) == 'e' &&
|
||||
(*++ch | 0x20) == 'e' &&
|
||||
(*++ch | 0x20) == 'p' &&
|
||||
*++ch == '-' &&
|
||||
(*++ch | 0x20) == 'a' &&
|
||||
(*++ch | 0x20) == 'l' &&
|
||||
(*++ch | 0x20) == 'i' &&
|
||||
(*++ch | 0x20) == 'v' &&
|
||||
(*++ch | 0x20) == 'e')
|
||||
{
|
||||
ch++;
|
||||
while (ch < tokenEnd && *ch == ' ')
|
||||
{
|
||||
ch++;
|
||||
}
|
||||
|
||||
if (ch == tokenEnd || *ch == ',')
|
||||
{
|
||||
connectionOptions |= ConnectionOptions.KeepAlive;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tokenLength >= 7 && (*ch | 0x20) == 'u')
|
||||
{
|
||||
if ((*++ch | 0x20) == 'p' &&
|
||||
(*++ch | 0x20) == 'g' &&
|
||||
(*++ch | 0x20) == 'r' &&
|
||||
(*++ch | 0x20) == 'a' &&
|
||||
(*++ch | 0x20) == 'd' &&
|
||||
(*++ch | 0x20) == 'e')
|
||||
{
|
||||
ch++;
|
||||
while (ch < tokenEnd && *ch == ' ')
|
||||
{
|
||||
ch++;
|
||||
}
|
||||
|
||||
if (ch == tokenEnd || *ch == ',')
|
||||
{
|
||||
connectionOptions |= ConnectionOptions.Upgrade;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tokenLength >= 5 && (*ch | 0x20) == 'c')
|
||||
{
|
||||
if ((*++ch | 0x20) == 'l' &&
|
||||
(*++ch | 0x20) == 'o' &&
|
||||
(*++ch | 0x20) == 's' &&
|
||||
(*++ch | 0x20) == 'e')
|
||||
{
|
||||
ch++;
|
||||
while (ch < tokenEnd && *ch == ' ')
|
||||
{
|
||||
ch++;
|
||||
}
|
||||
|
||||
if (ch == tokenEnd || *ch == ',')
|
||||
{
|
||||
connectionOptions |= ConnectionOptions.Close;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokenEnd++;
|
||||
ch = tokenEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connectionOptions;
|
||||
}
|
||||
|
||||
private static void ThrowInvalidContentLengthException(string value)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number.");
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
{
|
||||
var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this);
|
||||
_keepAlive = messageBody.RequestKeepAlive;
|
||||
_upgrade = messageBody.RequestUpgrade;
|
||||
|
||||
InitializeStreams(messageBody);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||
{
|
||||
|
|
@ -23,6 +24,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
public bool RequestKeepAlive { get; protected set; }
|
||||
|
||||
public bool RequestUpgrade { get; protected set; }
|
||||
|
||||
public Task<int> ReadAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var task = PeekAsync(cancellationToken);
|
||||
|
|
@ -231,15 +234,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
// see also http://tools.ietf.org/html/rfc2616#section-4.4
|
||||
var keepAlive = httpVersion != HttpVersion.Http10;
|
||||
|
||||
var connection = headers.HeaderConnection.ToString();
|
||||
if (connection.Length > 0)
|
||||
var connection = headers.HeaderConnection;
|
||||
if (connection.Count > 0)
|
||||
{
|
||||
if (connection.Equals("upgrade", StringComparison.OrdinalIgnoreCase))
|
||||
var connectionOptions = FrameHeaders.ParseConnection(connection);
|
||||
|
||||
if ((connectionOptions & ConnectionOptions.Upgrade) == ConnectionOptions.Upgrade)
|
||||
{
|
||||
return new ForRemainingData(context);
|
||||
return new ForRemainingData(true, context);
|
||||
}
|
||||
|
||||
keepAlive = connection.Equals("keep-alive", StringComparison.OrdinalIgnoreCase);
|
||||
keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
|
||||
}
|
||||
|
||||
var transferEncoding = headers.HeaderTransferEncoding.ToString();
|
||||
|
|
@ -267,9 +272,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
|||
|
||||
private class ForRemainingData : MessageBody
|
||||
{
|
||||
public ForRemainingData(Frame context)
|
||||
public ForRemainingData(bool upgrade, Frame context)
|
||||
: base(context)
|
||||
{
|
||||
RequestUpgrade = upgrade;
|
||||
}
|
||||
|
||||
protected override ValueTask<ArraySegment<byte>> PeekAsync(CancellationToken cancellationToken)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,155 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.KestrelTests
|
||||
{
|
||||
public class FrameHeadersTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("keep-alive, upgrade", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("keep-alive,upgrade", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade, keep-alive", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade,keep-alive", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade,,keep-alive", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("keep-alive,", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("keep-alive,,", ConnectionOptions.KeepAlive)]
|
||||
[InlineData(",keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData(",,keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("keep-alive, ", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("keep-alive, ,", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("keep-alive, , ", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("keep-alive ,", ConnectionOptions.KeepAlive)]
|
||||
[InlineData(",keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData(", keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData(",,keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData(", ,keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData(",, keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData(", , keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("upgrade,", ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade,,", ConnectionOptions.Upgrade)]
|
||||
[InlineData(",upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData(",,upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade, ", ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade, ,", ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade, , ", ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade ,", ConnectionOptions.Upgrade)]
|
||||
[InlineData(",upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData(", upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData(",,upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData(", ,upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData(",, upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData(", , upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData("close,", ConnectionOptions.Close)]
|
||||
[InlineData("close,,", ConnectionOptions.Close)]
|
||||
[InlineData(",close", ConnectionOptions.Close)]
|
||||
[InlineData(",,close", ConnectionOptions.Close)]
|
||||
[InlineData("close, ", ConnectionOptions.Close)]
|
||||
[InlineData("close, ,", ConnectionOptions.Close)]
|
||||
[InlineData("close, , ", ConnectionOptions.Close)]
|
||||
[InlineData("close ,", ConnectionOptions.Close)]
|
||||
[InlineData(",close", ConnectionOptions.Close)]
|
||||
[InlineData(", close", ConnectionOptions.Close)]
|
||||
[InlineData(",,close", ConnectionOptions.Close)]
|
||||
[InlineData(", ,close", ConnectionOptions.Close)]
|
||||
[InlineData(",, close", ConnectionOptions.Close)]
|
||||
[InlineData(", , close", ConnectionOptions.Close)]
|
||||
[InlineData("kupgrade", ConnectionOptions.None)]
|
||||
[InlineData("keupgrade", ConnectionOptions.None)]
|
||||
[InlineData("ukeep-alive", ConnectionOptions.None)]
|
||||
[InlineData("upkeep-alive", ConnectionOptions.None)]
|
||||
[InlineData("k,upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData("u,keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("ke,upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData("up,keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("close", ConnectionOptions.Close)]
|
||||
[InlineData("upgrade,close", ConnectionOptions.Close | ConnectionOptions.Upgrade)]
|
||||
[InlineData("close,upgrade", ConnectionOptions.Close | ConnectionOptions.Upgrade)]
|
||||
[InlineData("keep-alive2", ConnectionOptions.None)]
|
||||
[InlineData("keep-alive2,", ConnectionOptions.None)]
|
||||
[InlineData("keep-alive2 ", ConnectionOptions.None)]
|
||||
[InlineData("keep-alive2 ,", ConnectionOptions.None)]
|
||||
[InlineData("keep-alive2,", ConnectionOptions.None)]
|
||||
[InlineData("upgrade2", ConnectionOptions.None)]
|
||||
[InlineData("upgrade2,", ConnectionOptions.None)]
|
||||
[InlineData("upgrade2 ", ConnectionOptions.None)]
|
||||
[InlineData("upgrade2 ,", ConnectionOptions.None)]
|
||||
[InlineData("upgrade2,", ConnectionOptions.None)]
|
||||
[InlineData("close2", ConnectionOptions.None)]
|
||||
[InlineData("close2,", ConnectionOptions.None)]
|
||||
[InlineData("close2 ", ConnectionOptions.None)]
|
||||
[InlineData("close2 ,", ConnectionOptions.None)]
|
||||
[InlineData("close2,", ConnectionOptions.None)]
|
||||
[InlineData("keep-alivekeep-alive", ConnectionOptions.None)]
|
||||
[InlineData("keep-aliveupgrade", ConnectionOptions.None)]
|
||||
[InlineData("upgradeupgrade", ConnectionOptions.None)]
|
||||
[InlineData("upgradekeep-alive", ConnectionOptions.None)]
|
||||
[InlineData("closeclose", ConnectionOptions.None)]
|
||||
[InlineData("closeupgrade", ConnectionOptions.None)]
|
||||
[InlineData("upgradeclose", ConnectionOptions.None)]
|
||||
[InlineData("keep-alive 2", ConnectionOptions.None)]
|
||||
[InlineData("upgrade 2", ConnectionOptions.None)]
|
||||
[InlineData("keep-alive 2, close", ConnectionOptions.Close)]
|
||||
[InlineData("upgrade 2, close", ConnectionOptions.Close)]
|
||||
[InlineData("close, keep-alive 2", ConnectionOptions.Close)]
|
||||
[InlineData("close, upgrade 2", ConnectionOptions.Close)]
|
||||
[InlineData("close 2, upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade, close 2", ConnectionOptions.Upgrade)]
|
||||
[InlineData("k2ep-alive", ConnectionOptions.None)]
|
||||
[InlineData("ke2p-alive", ConnectionOptions.None)]
|
||||
[InlineData("u2grade", ConnectionOptions.None)]
|
||||
[InlineData("up2rade", ConnectionOptions.None)]
|
||||
[InlineData("c2ose", ConnectionOptions.None)]
|
||||
[InlineData("cl2se", ConnectionOptions.None)]
|
||||
[InlineData("k2ep-alive,", ConnectionOptions.None)]
|
||||
[InlineData("ke2p-alive,", ConnectionOptions.None)]
|
||||
[InlineData("u2grade,", ConnectionOptions.None)]
|
||||
[InlineData("up2rade,", ConnectionOptions.None)]
|
||||
[InlineData("c2ose,", ConnectionOptions.None)]
|
||||
[InlineData("cl2se,", ConnectionOptions.None)]
|
||||
[InlineData("k2ep-alive ", ConnectionOptions.None)]
|
||||
[InlineData("ke2p-alive ", ConnectionOptions.None)]
|
||||
[InlineData("u2grade ", ConnectionOptions.None)]
|
||||
[InlineData("up2rade ", ConnectionOptions.None)]
|
||||
[InlineData("c2ose ", ConnectionOptions.None)]
|
||||
[InlineData("cl2se ", ConnectionOptions.None)]
|
||||
[InlineData("k2ep-alive ,", ConnectionOptions.None)]
|
||||
[InlineData("ke2p-alive ,", ConnectionOptions.None)]
|
||||
[InlineData("u2grade ,", ConnectionOptions.None)]
|
||||
[InlineData("up2rade ,", ConnectionOptions.None)]
|
||||
[InlineData("c2ose ,", ConnectionOptions.None)]
|
||||
[InlineData("cl2se ,", ConnectionOptions.None)]
|
||||
public void TestParseConnection(string connection, ConnectionOptions expectedConnectionOptionss)
|
||||
{
|
||||
var connectionOptions = FrameHeaders.ParseConnection(connection);
|
||||
Assert.Equal(expectedConnectionOptionss, connectionOptions);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("keep-alive", "upgrade", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade", "keep-alive", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("keep-alive", "", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("", "keep-alive", ConnectionOptions.KeepAlive)]
|
||||
[InlineData("upgrade", "", ConnectionOptions.Upgrade)]
|
||||
[InlineData("", "upgrade", ConnectionOptions.Upgrade)]
|
||||
[InlineData("keep-alive, upgrade", "", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade, keep-alive", "", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("", "keep-alive, upgrade", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("", "upgrade, keep-alive", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
|
||||
[InlineData("", "", ConnectionOptions.None)]
|
||||
[InlineData("close", "", ConnectionOptions.Close)]
|
||||
[InlineData("", "close", ConnectionOptions.Close)]
|
||||
[InlineData("close", "upgrade", ConnectionOptions.Close | ConnectionOptions.Upgrade)]
|
||||
[InlineData("upgrade", "close", ConnectionOptions.Close | ConnectionOptions.Upgrade)]
|
||||
public void TestParseConnectionMultipleValues(string value1, string value2, ConnectionOptions expectedConnectionOptionss)
|
||||
{
|
||||
var connection = new StringValues(new[] { value1, value2 });
|
||||
var connectionOptions = FrameHeaders.ParseConnection(connection);
|
||||
Assert.Equal(expectedConnectionOptionss, connectionOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -286,6 +286,27 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("keep-alive, upgrade")]
|
||||
[InlineData("Keep-Alive, Upgrade")]
|
||||
[InlineData("upgrade, keep-alive")]
|
||||
[InlineData("Upgrade, Keep-Alive")]
|
||||
public void ConnectionUpgradeKeepAlive(string headerConnection)
|
||||
{
|
||||
using (var input = new TestInput())
|
||||
{
|
||||
var body = MessageBody.For(HttpVersion.Http11, new FrameRequestHeaders { HeaderConnection = headerConnection }, input.FrameContext);
|
||||
var stream = new FrameRequestStream();
|
||||
stream.StartAcceptingReads(body);
|
||||
|
||||
input.Add("Hello", true);
|
||||
|
||||
var buffer = new byte[1024];
|
||||
Assert.Equal(5, stream.Read(buffer, 0, 1024));
|
||||
AssertASCII("Hello", new ArraySegment<byte>(buffer, 0, 5));
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertASCII(string expected, ArraySegment<byte> actual)
|
||||
{
|
||||
var encoding = Encoding.ASCII;
|
||||
|
|
|
|||
Loading…
Reference in New Issue