Skip to content

Commit 1ffad5c

Browse files
author
Cesar Blum Silveira
committed
Handle multiple tokens in Connection header (#1170).
1 parent d64b4c7 commit 1ffad5c

File tree

8 files changed

+309
-21
lines changed

8 files changed

+309
-21
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
7+
{
8+
[Flags]
9+
public enum ConnectionOptions
10+
{
11+
None = 0,
12+
Close = 1,
13+
KeepAlive = 2,
14+
Upgrade = 4
15+
}
16+
}

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.FeatureCollection.cs

+1-12
Original file line numberDiff line numberDiff line change
@@ -259,18 +259,7 @@ bool IHttpResponseFeature.HasStarted
259259
get { return HasResponseStarted; }
260260
}
261261

262-
bool IHttpUpgradeFeature.IsUpgradableRequest
263-
{
264-
get
265-
{
266-
StringValues values;
267-
if (RequestHeaders.TryGetValue("Connection", out values))
268-
{
269-
return values.Any(value => value.IndexOf("upgrade", StringComparison.OrdinalIgnoreCase) != -1);
270-
}
271-
return false;
272-
}
273-
}
262+
bool IHttpUpgradeFeature.IsUpgradableRequest => _upgrade;
274263

275264
bool IFeatureCollection.IsReadOnly => false;
276265

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/Frame.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public abstract partial class Frame : IFrameControl
6060

6161
private RequestProcessingStatus _requestProcessingStatus;
6262
protected bool _keepAlive;
63+
protected bool _upgrade;
6364
private bool _canHaveBody;
6465
private bool _autoChunk;
6566
protected Exception _applicationException;
@@ -810,13 +811,13 @@ private void CreateResponseHeader(
810811
{
811812
var responseHeaders = FrameResponseHeaders;
812813
var hasConnection = responseHeaders.HasConnection;
814+
var connectionOptions = hasConnection ? FrameHeaders.ParseConnection(responseHeaders.HeaderConnection) : ConnectionOptions.None;
813815

814816
var end = SocketOutput.ProducingStart();
815817

816818
if (_keepAlive && hasConnection)
817819
{
818-
var connectionValue = responseHeaders.HeaderConnection.ToString();
819-
_keepAlive = connectionValue.Equals("keep-alive", StringComparison.OrdinalIgnoreCase);
820+
_keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
820821
}
821822

822823
// Set whether response can have body

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameHeaders.cs

+100-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Collections;
66
using System.Collections.Generic;
7-
using System.Globalization;
87
using System.Linq;
98
using Microsoft.AspNetCore.Http;
109
using Microsoft.Extensions.Primitives;
@@ -265,6 +264,106 @@ public static unsafe long ParseContentLength(StringValues value)
265264
return parsed;
266265
}
267266

267+
public static unsafe ConnectionOptions ParseConnection(StringValues connection)
268+
{
269+
var connectionOptions = ConnectionOptions.None;
270+
271+
foreach (var value in connection)
272+
{
273+
fixed (char* ptr = value)
274+
{
275+
var ch = ptr;
276+
var tokenEnd = ch;
277+
var end = ch + value.Length;
278+
279+
while (ch < end)
280+
{
281+
while (tokenEnd < end && *tokenEnd != ',')
282+
{
283+
tokenEnd++;
284+
}
285+
286+
while (ch < tokenEnd && *ch == ' ')
287+
{
288+
ch++;
289+
}
290+
291+
var tokenLength = tokenEnd - ch;
292+
293+
if (tokenLength >= 9 && (*ch | 0x20) == 'k')
294+
{
295+
if ((*++ch | 0x20) == 'e' &&
296+
(*++ch | 0x20) == 'e' &&
297+
(*++ch | 0x20) == 'p' &&
298+
*++ch == '-' &&
299+
(*++ch | 0x20) == 'a' &&
300+
(*++ch | 0x20) == 'l' &&
301+
(*++ch | 0x20) == 'i' &&
302+
(*++ch | 0x20) == 'v' &&
303+
(*++ch | 0x20) == 'e')
304+
{
305+
ch++;
306+
while (ch < tokenEnd && *ch == ' ')
307+
{
308+
ch++;
309+
}
310+
311+
if (ch == tokenEnd || *ch == ',')
312+
{
313+
connectionOptions |= ConnectionOptions.KeepAlive;
314+
}
315+
}
316+
}
317+
else if (tokenLength >= 7 && (*ch | 0x20) == 'u')
318+
{
319+
if ((*++ch | 0x20) == 'p' &&
320+
(*++ch | 0x20) == 'g' &&
321+
(*++ch | 0x20) == 'r' &&
322+
(*++ch | 0x20) == 'a' &&
323+
(*++ch | 0x20) == 'd' &&
324+
(*++ch | 0x20) == 'e')
325+
{
326+
ch++;
327+
while (ch < tokenEnd && *ch == ' ')
328+
{
329+
ch++;
330+
}
331+
332+
if (ch == tokenEnd || *ch == ',')
333+
{
334+
connectionOptions |= ConnectionOptions.Upgrade;
335+
}
336+
}
337+
}
338+
else if (tokenLength >= 5 && (*ch | 0x20) == 'c')
339+
{
340+
if ((*++ch | 0x20) == 'l' &&
341+
(*++ch | 0x20) == 'o' &&
342+
(*++ch | 0x20) == 's' &&
343+
(*++ch | 0x20) == 'e')
344+
{
345+
ch++;
346+
while (ch < tokenEnd && *ch == ' ')
347+
{
348+
ch++;
349+
}
350+
351+
if (ch == tokenEnd || *ch == ',')
352+
{
353+
connectionOptions |= ConnectionOptions.Close;
354+
}
355+
}
356+
}
357+
358+
tokenEnd++;
359+
ch = tokenEnd;
360+
}
361+
}
362+
}
363+
364+
return connectionOptions;
365+
}
366+
268367
private static void ThrowInvalidContentLengthException(string value)
269368
{
270369
throw new InvalidOperationException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number.");

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/FrameOfT.cs

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public override async Task RequestProcessingAsync()
8686
{
8787
var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this);
8888
_keepAlive = messageBody.RequestKeepAlive;
89+
_upgrade = messageBody.RequestUpgrade;
8990

9091
InitializeStreams(messageBody);
9192

src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/MessageBody.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Threading.Tasks;
99
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
1010
using Microsoft.Extensions.Internal;
11+
using Microsoft.Extensions.Primitives;
1112

1213
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
1314
{
@@ -23,6 +24,8 @@ protected MessageBody(Frame context)
2324

2425
public bool RequestKeepAlive { get; protected set; }
2526

27+
public bool RequestUpgrade { get; protected set; }
28+
2629
public Task<int> ReadAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
2730
{
2831
var task = PeekAsync(cancellationToken);
@@ -231,15 +234,17 @@ public static MessageBody For(
231234
// see also http://tools.ietf.org/html/rfc2616#section-4.4
232235
var keepAlive = httpVersion != HttpVersion.Http10;
233236

234-
var connection = headers.HeaderConnection.ToString();
235-
if (connection.Length > 0)
237+
var connection = headers.HeaderConnection;
238+
if (connection.Count > 0)
236239
{
237-
if (connection.Equals("upgrade", StringComparison.OrdinalIgnoreCase))
240+
var connectionOptions = FrameHeaders.ParseConnection(connection);
241+
242+
if ((connectionOptions & ConnectionOptions.Upgrade) == ConnectionOptions.Upgrade)
238243
{
239-
return new ForRemainingData(context);
244+
return new ForRemainingData(true, context);
240245
}
241246

242-
keepAlive = connection.Equals("keep-alive", StringComparison.OrdinalIgnoreCase);
247+
keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
243248
}
244249

245250
var transferEncoding = headers.HeaderTransferEncoding.ToString();
@@ -267,9 +272,10 @@ public static MessageBody For(
267272

268273
private class ForRemainingData : MessageBody
269274
{
270-
public ForRemainingData(Frame context)
275+
public ForRemainingData(bool upgrade, Frame context)
271276
: base(context)
272277
{
278+
RequestUpgrade = upgrade;
273279
}
274280

275281
protected override ValueTask<ArraySegment<byte>> PeekAsync(CancellationToken cancellationToken)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
5+
using Microsoft.Extensions.Primitives;
6+
using Xunit;
7+
8+
namespace Microsoft.AspNetCore.Server.KestrelTests
9+
{
10+
public class FrameHeadersTests
11+
{
12+
[Theory]
13+
[InlineData("keep-alive", ConnectionOptions.KeepAlive)]
14+
[InlineData("keep-alive, upgrade", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
15+
[InlineData("keep-alive,upgrade", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
16+
[InlineData("upgrade, keep-alive", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
17+
[InlineData("upgrade,keep-alive", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
18+
[InlineData("upgrade,,keep-alive", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
19+
[InlineData("keep-alive,", ConnectionOptions.KeepAlive)]
20+
[InlineData("keep-alive,,", ConnectionOptions.KeepAlive)]
21+
[InlineData(",keep-alive", ConnectionOptions.KeepAlive)]
22+
[InlineData(",,keep-alive", ConnectionOptions.KeepAlive)]
23+
[InlineData("keep-alive, ", ConnectionOptions.KeepAlive)]
24+
[InlineData("keep-alive, ,", ConnectionOptions.KeepAlive)]
25+
[InlineData("keep-alive, , ", ConnectionOptions.KeepAlive)]
26+
[InlineData("keep-alive ,", ConnectionOptions.KeepAlive)]
27+
[InlineData(",keep-alive", ConnectionOptions.KeepAlive)]
28+
[InlineData(", keep-alive", ConnectionOptions.KeepAlive)]
29+
[InlineData(",,keep-alive", ConnectionOptions.KeepAlive)]
30+
[InlineData(", ,keep-alive", ConnectionOptions.KeepAlive)]
31+
[InlineData(",, keep-alive", ConnectionOptions.KeepAlive)]
32+
[InlineData(", , keep-alive", ConnectionOptions.KeepAlive)]
33+
[InlineData("upgrade,", ConnectionOptions.Upgrade)]
34+
[InlineData("upgrade,,", ConnectionOptions.Upgrade)]
35+
[InlineData(",upgrade", ConnectionOptions.Upgrade)]
36+
[InlineData(",,upgrade", ConnectionOptions.Upgrade)]
37+
[InlineData("upgrade, ", ConnectionOptions.Upgrade)]
38+
[InlineData("upgrade, ,", ConnectionOptions.Upgrade)]
39+
[InlineData("upgrade, , ", ConnectionOptions.Upgrade)]
40+
[InlineData("upgrade ,", ConnectionOptions.Upgrade)]
41+
[InlineData(",upgrade", ConnectionOptions.Upgrade)]
42+
[InlineData(", upgrade", ConnectionOptions.Upgrade)]
43+
[InlineData(",,upgrade", ConnectionOptions.Upgrade)]
44+
[InlineData(", ,upgrade", ConnectionOptions.Upgrade)]
45+
[InlineData(",, upgrade", ConnectionOptions.Upgrade)]
46+
[InlineData(", , upgrade", ConnectionOptions.Upgrade)]
47+
[InlineData("close,", ConnectionOptions.Close)]
48+
[InlineData("close,,", ConnectionOptions.Close)]
49+
[InlineData(",close", ConnectionOptions.Close)]
50+
[InlineData(",,close", ConnectionOptions.Close)]
51+
[InlineData("close, ", ConnectionOptions.Close)]
52+
[InlineData("close, ,", ConnectionOptions.Close)]
53+
[InlineData("close, , ", ConnectionOptions.Close)]
54+
[InlineData("close ,", ConnectionOptions.Close)]
55+
[InlineData(",close", ConnectionOptions.Close)]
56+
[InlineData(", close", ConnectionOptions.Close)]
57+
[InlineData(",,close", ConnectionOptions.Close)]
58+
[InlineData(", ,close", ConnectionOptions.Close)]
59+
[InlineData(",, close", ConnectionOptions.Close)]
60+
[InlineData(", , close", ConnectionOptions.Close)]
61+
[InlineData("kupgrade", ConnectionOptions.None)]
62+
[InlineData("keupgrade", ConnectionOptions.None)]
63+
[InlineData("ukeep-alive", ConnectionOptions.None)]
64+
[InlineData("upkeep-alive", ConnectionOptions.None)]
65+
[InlineData("k,upgrade", ConnectionOptions.Upgrade)]
66+
[InlineData("u,keep-alive", ConnectionOptions.KeepAlive)]
67+
[InlineData("ke,upgrade", ConnectionOptions.Upgrade)]
68+
[InlineData("up,keep-alive", ConnectionOptions.KeepAlive)]
69+
[InlineData("close", ConnectionOptions.Close)]
70+
[InlineData("upgrade,close", ConnectionOptions.Close | ConnectionOptions.Upgrade)]
71+
[InlineData("close,upgrade", ConnectionOptions.Close | ConnectionOptions.Upgrade)]
72+
[InlineData("keep-alive2", ConnectionOptions.None)]
73+
[InlineData("keep-alive2,", ConnectionOptions.None)]
74+
[InlineData("keep-alive2 ", ConnectionOptions.None)]
75+
[InlineData("keep-alive2 ,", ConnectionOptions.None)]
76+
[InlineData("keep-alive2,", ConnectionOptions.None)]
77+
[InlineData("upgrade2", ConnectionOptions.None)]
78+
[InlineData("upgrade2,", ConnectionOptions.None)]
79+
[InlineData("upgrade2 ", ConnectionOptions.None)]
80+
[InlineData("upgrade2 ,", ConnectionOptions.None)]
81+
[InlineData("upgrade2,", ConnectionOptions.None)]
82+
[InlineData("close2", ConnectionOptions.None)]
83+
[InlineData("close2,", ConnectionOptions.None)]
84+
[InlineData("close2 ", ConnectionOptions.None)]
85+
[InlineData("close2 ,", ConnectionOptions.None)]
86+
[InlineData("close2,", ConnectionOptions.None)]
87+
[InlineData("keep-alivekeep-alive", ConnectionOptions.None)]
88+
[InlineData("keep-aliveupgrade", ConnectionOptions.None)]
89+
[InlineData("upgradeupgrade", ConnectionOptions.None)]
90+
[InlineData("upgradekeep-alive", ConnectionOptions.None)]
91+
[InlineData("closeclose", ConnectionOptions.None)]
92+
[InlineData("closeupgrade", ConnectionOptions.None)]
93+
[InlineData("upgradeclose", ConnectionOptions.None)]
94+
[InlineData("keep-alive 2", ConnectionOptions.None)]
95+
[InlineData("upgrade 2", ConnectionOptions.None)]
96+
[InlineData("keep-alive 2, close", ConnectionOptions.Close)]
97+
[InlineData("upgrade 2, close", ConnectionOptions.Close)]
98+
[InlineData("close, keep-alive 2", ConnectionOptions.Close)]
99+
[InlineData("close, upgrade 2", ConnectionOptions.Close)]
100+
[InlineData("close 2, upgrade", ConnectionOptions.Upgrade)]
101+
[InlineData("upgrade, close 2", ConnectionOptions.Upgrade)]
102+
[InlineData("k2ep-alive", ConnectionOptions.None)]
103+
[InlineData("ke2p-alive", ConnectionOptions.None)]
104+
[InlineData("u2grade", ConnectionOptions.None)]
105+
[InlineData("up2rade", ConnectionOptions.None)]
106+
[InlineData("c2ose", ConnectionOptions.None)]
107+
[InlineData("cl2se", ConnectionOptions.None)]
108+
[InlineData("k2ep-alive,", ConnectionOptions.None)]
109+
[InlineData("ke2p-alive,", ConnectionOptions.None)]
110+
[InlineData("u2grade,", ConnectionOptions.None)]
111+
[InlineData("up2rade,", ConnectionOptions.None)]
112+
[InlineData("c2ose,", ConnectionOptions.None)]
113+
[InlineData("cl2se,", ConnectionOptions.None)]
114+
[InlineData("k2ep-alive ", ConnectionOptions.None)]
115+
[InlineData("ke2p-alive ", ConnectionOptions.None)]
116+
[InlineData("u2grade ", ConnectionOptions.None)]
117+
[InlineData("up2rade ", ConnectionOptions.None)]
118+
[InlineData("c2ose ", ConnectionOptions.None)]
119+
[InlineData("cl2se ", ConnectionOptions.None)]
120+
[InlineData("k2ep-alive ,", ConnectionOptions.None)]
121+
[InlineData("ke2p-alive ,", ConnectionOptions.None)]
122+
[InlineData("u2grade ,", ConnectionOptions.None)]
123+
[InlineData("up2rade ,", ConnectionOptions.None)]
124+
[InlineData("c2ose ,", ConnectionOptions.None)]
125+
[InlineData("cl2se ,", ConnectionOptions.None)]
126+
public void TestParseConnection(string connection, ConnectionOptions expectedConnectionOptionss)
127+
{
128+
var connectionOptions = FrameHeaders.ParseConnection(connection);
129+
Assert.Equal(expectedConnectionOptionss, connectionOptions);
130+
}
131+
132+
[Theory]
133+
[InlineData("keep-alive", "upgrade", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
134+
[InlineData("upgrade", "keep-alive", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
135+
[InlineData("keep-alive", "", ConnectionOptions.KeepAlive)]
136+
[InlineData("", "keep-alive", ConnectionOptions.KeepAlive)]
137+
[InlineData("upgrade", "", ConnectionOptions.Upgrade)]
138+
[InlineData("", "upgrade", ConnectionOptions.Upgrade)]
139+
[InlineData("keep-alive, upgrade", "", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
140+
[InlineData("upgrade, keep-alive", "", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
141+
[InlineData("", "keep-alive, upgrade", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
142+
[InlineData("", "upgrade, keep-alive", ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade)]
143+
[InlineData("", "", ConnectionOptions.None)]
144+
[InlineData("close", "", ConnectionOptions.Close)]
145+
[InlineData("", "close", ConnectionOptions.Close)]
146+
[InlineData("close", "upgrade", ConnectionOptions.Close | ConnectionOptions.Upgrade)]
147+
[InlineData("upgrade", "close", ConnectionOptions.Close | ConnectionOptions.Upgrade)]
148+
public void TestParseConnectionMultipleValues(string value1, string value2, ConnectionOptions expectedConnectionOptionss)
149+
{
150+
var connection = new StringValues(new[] { value1, value2 });
151+
var connectionOptions = FrameHeaders.ParseConnection(connection);
152+
Assert.Equal(expectedConnectionOptionss, connectionOptions);
153+
}
154+
}
155+
}

0 commit comments

Comments
 (0)