Skip to content

[automated] Merge branch 'release/7.0' => 'main' #43437

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
11 commits merged into from
Aug 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
<Compile Include="$(RepoRoot)src\SignalR\common\Shared\BinaryMessageParser.cs" LinkBase="BlazorPack" />
<Compile Include="$(RepoRoot)src\SignalR\common\Shared\MemoryBufferWriter.cs" LinkBase="BlazorPack" />
<Compile Include="$(RepoRoot)src\SignalR\common\Protocols.MessagePack\src\Protocol\MessagePackHubProtocolWorker.cs" LinkBase="BlazorPack" />
<Compile Include="$(RepoRoot)src\SignalR\common\Shared\TryGetReturnType.cs" LinkBase="BlazorPack" />

<!-- MessagePack -->
<Compile Include="$(MessagePackRoot)BufferWriter.cs" LinkBase="BlazorPack\MessagePack" />
Expand Down
302 changes: 180 additions & 122 deletions src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private static ServiceContext CreateServiceContext(IOptions<KestrelServerOptions
{
Log = trace,
Scheduler = PipeScheduler.ThreadPool,
HttpParser = new HttpParser<Http1ParsingHandler>(trace.IsEnabled(LogLevel.Information)),
HttpParser = new HttpParser<Http1ParsingHandler>(trace.IsEnabled(LogLevel.Information), serverOptions.DisableHttp1LineFeedTerminators),
SystemClock = heartbeatManager,
DateHeaderValueManager = dateHeaderValueManager,
ConnectionManager = connectionManager,
Expand Down
20 changes: 20 additions & 0 deletions src/Servers/Kestrel/Core/src/KestrelServerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core;
/// </summary>
public class KestrelServerOptions
{
internal const string DisableHttp1LineFeedTerminatorsSwitchKey = "Microsoft.AspNetCore.Server.Kestrel.DisableHttp1LineFeedTerminators";

// internal to fast-path header decoding when RequestHeaderEncodingSelector is unchanged.
internal static readonly Func<string, Encoding?> DefaultHeaderEncodingSelector = _ => null;

Expand Down Expand Up @@ -175,6 +177,24 @@ internal bool EnableWebTransportAndH3Datagrams
set => _enableWebTransportAndH3Datagrams = value;
}

/// <summary>
/// Internal AppContext switch to toggle whether a request line can end with LF only instead of CR/LF.
/// </summary>
private bool? _disableHttp1LineFeedTerminators;
internal bool DisableHttp1LineFeedTerminators
{
get
{
if (!_disableHttp1LineFeedTerminators.HasValue)
{
_disableHttp1LineFeedTerminators = AppContext.TryGetSwitch(DisableHttp1LineFeedTerminatorsSwitchKey, out var disabled) && disabled;
}

return _disableHttp1LineFeedTerminators.Value;
}
set => _disableHttp1LineFeedTerminators = value;
}

/// <summary>
/// Specifies a configuration Action to run for each newly created endpoint. Calling this again will replace
/// the prior action.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Core components of ASP.NET Core Kestrel cross-platform web server.</Description>
Expand Down Expand Up @@ -29,6 +29,7 @@
<Compile Include="$(SharedSourceRoot)runtime\Http3\**\*.cs" Link="Shared\runtime\Http3\%(Filename)%(Extension)" />
<Compile Include="$(SharedSourceRoot)Hpack\**\*.cs" Link="Shared\Hpack\%(Filename)%(Extension)" />
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\**\*.cs" />
<Compile Include="$(SharedSourceRoot)CancellationTokenSourcePool.cs" />
<Compile Include="$(RepoRoot)src\Shared\TaskToApm.cs" Link="Internal\TaskToApm.cs" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
Expand Down
291 changes: 261 additions & 30 deletions src/Servers/Kestrel/Core/test/HttpParserTests.cs

Large diffs are not rendered by default.

37 changes: 34 additions & 3 deletions src/Servers/Kestrel/shared/test/HttpParsingData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,15 @@ public static IEnumerable<string> RequestLineInvalidData
"CUSTOM / HTTP/1.1a\n",
"CUSTOM / HTTP/1.1a\r\n",
"CUSTOM / HTTP/1.1ab\r\n",
"CUSTOM / H\n",
"CUSTOM / HT\n",
"CUSTOM / HTT\n",
"CUSTOM / HTTP\n",
"CUSTOM / HTTP/\n",
"CUSTOM / HTTP/1\n",
"CUSTOM / HTTP/1.\n",
"CUSTOM / hello\r\n",
"CUSTOM / hello\n",
"CUSTOM ? HTTP/1.1\r\n",
"CUSTOM /a?b=cHTTP/1.1\r\n",
"CUSTOM /a%20bHTTP/1.1\r\n",
Expand All @@ -217,6 +225,21 @@ public static IEnumerable<string> RequestLineInvalidData
}
}

// This list is valid in quirk mode
public static IEnumerable<string> RequestLineInvalidDataLineFeedTerminator
{
get
{
return new[]
{
"GET / HTTP/1.0\n",
"GET / HTTP/1.1\n",
"CUSTOM / HTTP/1.0\n",
"CUSTOM / HTTP/1.1\n",
};
}
}

// Bad HTTP Methods (invalid according to RFC)
public static IEnumerable<string> MethodWithNonTokenCharData
{
Expand Down Expand Up @@ -364,13 +387,19 @@ public static IEnumerable<string> TargetWithNullCharData
"8charact",
};

public static IEnumerable<object[]> RequestHeaderInvalidData => new[]
public static IEnumerable<object[]> RequestHeaderInvalidDataLineFeedTerminator => new[]
{
// Missing CR
new[] { "Header: value\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header: value\x0A") },
new[] { "Header-1: value1\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: value1\x0A") },
new[] { "Header-1: value1\r\nHeader-2: value2\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2: value2\x0A") },

// Empty header name
new[] { ":a\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@":a\x0A") },
};

public static IEnumerable<object[]> RequestHeaderInvalidData => new[]
{
// Line folding
new[] { "Header: line1\r\n line2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" line2\x0D\x0A") },
new[] { "Header: line1\r\n\tline2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"\x09line2\x0D\x0A") },
Expand Down Expand Up @@ -404,7 +433,7 @@ public static IEnumerable<string> TargetWithNullCharData
new[] { "Header-1 value1\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1 value1\x0D\x0A") },
new[] { "Header-1 value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1 value1\x0D\x0A") },
new[] { "Header-1: value1\r\nHeader-2 value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2 value2\x0D\x0A") },
new[] { "\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"\x0A") },
new[] { "HeaderValue1\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"HeaderValue1\x0D\x0A") },

// Starting with whitespace
new[] { " Header: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" Header: value\x0D\x0A") },
Expand Down Expand Up @@ -435,11 +464,13 @@ public static IEnumerable<string> TargetWithNullCharData

// Headers not ending in CRLF line
new[] { "Header-1: value1\r\nHeader-2: value2\r\n\r\r", CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF },
new[] { "Header-1: value1\r\nHeader-2: value2\r\n\r ", CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF },
new[] { "Header-1: value1\r\nHeader-2: value2\r\n\r ", CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF },
new[] { "Header-1: value1\r\nHeader-2: value2\r\n\r \n", CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF },
new[] { "Header-1: value1\r\nHeader-2\t: value2 \n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2\x09: value2 \x0A") },

// Empty header name
new[] { ": value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@": value\x0D\x0A") },
new[] { ":a\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@":a\x0D\x0A") },
};

public static TheoryData<string, string> HostHeaderData
Expand Down
14 changes: 7 additions & 7 deletions src/Servers/Kestrel/shared/test/TestServiceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ internal class TestServiceContext : ServiceContext
{
public TestServiceContext()
{
Initialize(NullLoggerFactory.Instance, CreateLoggingTrace(NullLoggerFactory.Instance));
Initialize(NullLoggerFactory.Instance, CreateLoggingTrace(NullLoggerFactory.Instance), false);
}

public TestServiceContext(ILoggerFactory loggerFactory)
public TestServiceContext(ILoggerFactory loggerFactory, bool disableHttp1LineFeedTerminators = true)
{
Initialize(loggerFactory, CreateLoggingTrace(loggerFactory));
Initialize(loggerFactory, CreateLoggingTrace(loggerFactory), disableHttp1LineFeedTerminators);
}

public TestServiceContext(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace)
public TestServiceContext(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace, bool disableHttp1LineFeedTerminators = true)
{
Initialize(loggerFactory, kestrelTrace);
Initialize(loggerFactory, kestrelTrace, disableHttp1LineFeedTerminators);
}

private static KestrelTrace CreateLoggingTrace(ILoggerFactory loggerFactory)
Expand All @@ -49,7 +49,7 @@ public void InitializeHeartbeat()
SystemClock = heartbeatManager;
}

private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace)
private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace, bool disableHttp1LineFeedTerminators)
{
LoggerFactory = loggerFactory;
Log = kestrelTrace;
Expand All @@ -58,7 +58,7 @@ private void Initialize(ILoggerFactory loggerFactory, KestrelTrace kestrelTrace)
SystemClock = MockSystemClock;
DateHeaderValueManager = new DateHeaderValueManager();
ConnectionManager = new ConnectionManager(Log, ResourceCounter.Unlimited);
HttpParser = new HttpParser<Http1ParsingHandler>(Log.IsEnabled(LogLevel.Information));
HttpParser = new HttpParser<Http1ParsingHandler>(Log.IsEnabled(LogLevel.Information), disableHttp1LineFeedTerminators);
ServerOptions = new KestrelServerOptions
{
AddServerHeader = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,5 +524,7 @@ public static TheoryData<string, string> InvalidRequestLineData

public static IEnumerable<object[]> InvalidRequestHeaderData => HttpParsingData.RequestHeaderInvalidData;

public static IEnumerable<object[]> InvalidRequestHeaderDataLineFeedTerminator => HttpParsingData.RequestHeaderInvalidDataLineFeedTerminator;

public static TheoryData<string, string> InvalidHostHeaderData => HttpParsingData.HostHeaderInvalidData;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2284,6 +2284,49 @@ await connection.Receive(
}
}

[Fact]
public async Task SingleLineFeedIsSupportedAnywhere()
{
// Exercises all combinations of LF and CRLF as line separators.
// Uses a bit mask for all the possible combinations.

var lines = new[]
{
$"GET / HTTP/1.1",
"Content-Length: 0",
$"Host: localhost",
"",
};

await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory, disableHttp1LineFeedTerminators: false)))
{
var mask = Math.Pow(2, lines.Length) - 1;

for (var m = 0; m <= mask; m++)
{
using (var client = server.CreateConnection())
{
var sb = new StringBuilder();

for (var pos = 0; pos < lines.Length; pos++)
{
sb.Append(lines[pos]);
var separator = (m & (1 << pos)) != 0 ? "\n" : "\r\n";
sb.Append(separator);
}

var text = sb.ToString();
var writer = new StreamWriter(client.Stream, Encoding.GetEncoding("iso-8859-1"));
await writer.WriteAsync(text).ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false);
await client.Stream.FlushAsync().ConfigureAwait(false);

await client.Receive("HTTP/1.1 200");
}
}
}
}

public static TheoryData<string, string> HostHeaderData => HttpParsingData.HostHeaderData;

private class IntAsClass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System.Collections.Concurrent;

namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
namespace Microsoft.AspNetCore.Internal;

internal sealed class CancellationTokenSourcePool
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ public InvocationHandler put(String target, Object action, Type... types) {
}
}
}
methodHandlers = new ArrayList<>(methodHandlers);
methodHandlers.add(handler);

// replace List in handlers map
handlers.remove(target);
handlers.put(target, methodHandlers);
return handler;
} finally {
lock.unlock();
Expand All @@ -41,7 +46,7 @@ public InvocationHandler put(String target, Object action, Type... types) {
public List<InvocationHandler> get(String key) {
try {
lock.lock();
return handlers.get(key);
return this.handlers.get(key);
} finally {
lock.unlock();
}
Expand All @@ -55,4 +60,21 @@ public void remove(String key) {
lock.unlock();
}
}

public void remove(String key, InvocationHandler handler) {
try {
lock.lock();
List<InvocationHandler> handlers = this.handlers.get(key);
if (handlers != null) {
handlers = new ArrayList<>(handlers);
handlers.remove(handler);

// replace List in handlers map
this.handlers.remove(key);
this.handlers.put(key, handlers);
}
} finally {
lock.unlock();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ public class Subscription {
* Removes the client method handler represented by this subscription.
*/
public void unsubscribe() {
List<InvocationHandler> handler = this.handlers.get(target);
if (handler != null) {
handler.remove(this.handler);
}
this.handlers.remove(this.target, this.handler);
}
}
2 changes: 1 addition & 1 deletion src/SignalR/clients/ts/FunctionalTests/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<
{
try
{
var result = await hubContext.Clients.Client(id).InvokeAsync<int>("Result");
var result = await hubContext.Clients.Client(id).InvokeAsync<int>("Result", cancellationToken: default);
return result.ToString(CultureInfo.InvariantCulture);
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextReader.cs" Link="Utf8BufferTextReader.cs" />
<Compile Include="$(SignalRSharedSourceRoot)Utf8BufferTextWriter.cs" Link="Utf8BufferTextWriter.cs" />
<Compile Include="$(SignalRSharedSourceRoot)ReusableUtf8JsonWriter.cs" Link="ReusableUtf8JsonWriter.cs" />
<Compile Include="$(SignalRSharedSourceRoot)TryGetReturnType.cs" Link="TryGetReturnType.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,16 @@ public ReadOnlyMemory<byte> GetMessageBytes(HubMessage message)
else
{
// If we have an invocation id already we can parse the end result
var returnType = binder.GetReturnType(invocationId);
result = BindType(ref reader, input, returnType);
var returnType = ProtocolHelper.TryGetReturnType(binder, invocationId);
if (returnType is null)
{
reader.Skip();
result = null;
}
else
{
result = BindType(ref reader, input, returnType);
}
}
}
else if (reader.ValueTextEquals(ItemPropertyNameBytes.EncodedUtf8Bytes))
Expand Down Expand Up @@ -408,8 +416,15 @@ public ReadOnlyMemory<byte> GetMessageBytes(HubMessage message)

if (hasResultToken)
{
var returnType = binder.GetReturnType(invocationId);
result = BindType(ref resultToken, input, returnType);
var returnType = ProtocolHelper.TryGetReturnType(binder, invocationId);
if (returnType is null)
{
result = null;
}
else
{
result = BindType(ref resultToken, input, returnType);
}
}

message = BindCompletionMessage(invocationId, error, result, hasResult);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Compile Include="$(SignalRSharedSourceRoot)BinaryMessageFormatter.cs" Link="BinaryMessageFormatter.cs" />
<Compile Include="$(SignalRSharedSourceRoot)BinaryMessageParser.cs" Link="BinaryMessageParser.cs" />
<Compile Include="$(SignalRSharedSourceRoot)MemoryBufferWriter.cs" Link="Internal\MemoryBufferWriter.cs" />
<Compile Include="$(SignalRSharedSourceRoot)TryGetReturnType.cs" Link="TryGetReturnType.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading