Merge source code from aspnet/KestrelHttpServer into this repo

This commit is contained in:
Nate McMaster 2018-11-14 11:21:49 -08:00
commit 02536ff991
No known key found for this signature in database
GPG Key ID: A778D9601BD78810
484 changed files with 74876 additions and 0 deletions

View File

@ -0,0 +1,44 @@
// 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 System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Connections
{
public class ConnectionBuilder : IConnectionBuilder
{
private readonly IList<Func<ConnectionDelegate, ConnectionDelegate>> _components = new List<Func<ConnectionDelegate, ConnectionDelegate>>();
public IServiceProvider ApplicationServices { get; }
public ConnectionBuilder(IServiceProvider applicationServices)
{
ApplicationServices = applicationServices;
}
public IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware)
{
_components.Add(middleware);
return this;
}
public ConnectionDelegate Build()
{
ConnectionDelegate app = features =>
{
return Task.CompletedTask;
};
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
}
}
}

View File

@ -0,0 +1,43 @@
// 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.Threading.Tasks;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Connections
{
public static class ConnectionBuilderExtensions
{
public static IConnectionBuilder UseConnectionHandler<TConnectionHandler>(this IConnectionBuilder connectionBuilder) where TConnectionHandler : ConnectionHandler
{
var handler = ActivatorUtilities.GetServiceOrCreateInstance<TConnectionHandler>(connectionBuilder.ApplicationServices);
// This is a terminal middleware, so there's no need to use the 'next' parameter
return connectionBuilder.Run(connection => handler.OnConnectedAsync(connection));
}
public static IConnectionBuilder Use(this IConnectionBuilder connectionBuilder, Func<ConnectionContext, Func<Task>, Task> middleware)
{
return connectionBuilder.Use(next =>
{
return context =>
{
Func<Task> simpleNext = () => next(context);
return middleware(context, simpleNext);
};
});
}
public static IConnectionBuilder Run(this IConnectionBuilder connectionBuilder, Func<ConnectionContext, Task> middleware)
{
return connectionBuilder.Use(next =>
{
return context =>
{
return middleware(context);
};
});
}
}
}

View File

@ -0,0 +1,31 @@
// 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.Collections.Generic;
using System.IO.Pipelines;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Connections
{
public abstract class ConnectionContext
{
public abstract string ConnectionId { get; set; }
public abstract IFeatureCollection Features { get; }
public abstract IDictionary<object, object> Items { get; set; }
public abstract IDuplexPipe Transport { get; set; }
public virtual void Abort(ConnectionAbortedException abortReason)
{
// We expect this to be overridden, but this helps maintain back compat
// with implementations of ConnectionContext that predate the addition of
// ConnectioContext.Abort()
Features.Get<IConnectionLifetimeFeature>()?.Abort();
}
public virtual void Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application."));
}
}

View File

@ -0,0 +1,6 @@
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Connections
{
public delegate Task ConnectionDelegate(ConnectionContext connection);
}

View File

@ -0,0 +1,20 @@
// 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.Threading.Tasks;
namespace Microsoft.AspNetCore.Connections
{
/// <summary>
/// Represents an end point that multiple connections connect to. For HTTP, endpoints are URLs, for non HTTP it can be a TCP listener (or similar)
/// </summary>
public abstract class ConnectionHandler
{
/// <summary>
/// Called when a new connection is accepted to the endpoint
/// </summary>
/// <param name="connection">The new <see cref="ConnectionContext"/></param>
/// <returns>A <see cref="Task"/> that represents the connection lifetime. When the task completes, the connection is complete.</returns>
public abstract Task OnConnectedAsync(ConnectionContext connection);
}
}

View File

@ -0,0 +1,119 @@
// 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.Concurrent;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Connections
{
public class ConnectionItems : IDictionary<object, object>
{
public ConnectionItems()
: this(new Dictionary<object, object>())
{
}
public ConnectionItems(IDictionary<object, object> items)
{
Items = items;
}
public IDictionary<object, object> Items { get; }
// Replace the indexer with one that returns null for missing values
object IDictionary<object, object>.this[object key]
{
get
{
if (Items.TryGetValue(key, out var value))
{
return value;
}
return null;
}
set { Items[key] = value; }
}
void IDictionary<object, object>.Add(object key, object value)
{
Items.Add(key, value);
}
bool IDictionary<object, object>.ContainsKey(object key)
{
return Items.ContainsKey(key);
}
ICollection<object> IDictionary<object, object>.Keys
{
get { return Items.Keys; }
}
bool IDictionary<object, object>.Remove(object key)
{
return Items.Remove(key);
}
bool IDictionary<object, object>.TryGetValue(object key, out object value)
{
return Items.TryGetValue(key, out value);
}
ICollection<object> IDictionary<object, object>.Values
{
get { return Items.Values; }
}
void ICollection<KeyValuePair<object, object>>.Add(KeyValuePair<object, object> item)
{
Items.Add(item);
}
void ICollection<KeyValuePair<object, object>>.Clear()
{
Items.Clear();
}
bool ICollection<KeyValuePair<object, object>>.Contains(KeyValuePair<object, object> item)
{
return Items.Contains(item);
}
void ICollection<KeyValuePair<object, object>>.CopyTo(KeyValuePair<object, object>[] array, int arrayIndex)
{
Items.CopyTo(array, arrayIndex);
}
int ICollection<KeyValuePair<object, object>>.Count
{
get { return Items.Count; }
}
bool ICollection<KeyValuePair<object, object>>.IsReadOnly
{
get { return Items.IsReadOnly; }
}
bool ICollection<KeyValuePair<object, object>>.Remove(KeyValuePair<object, object> item)
{
object value;
if (Items.TryGetValue(item.Key, out value) && Equals(item.Value, value))
{
return Items.Remove(item.Key);
}
return false;
}
IEnumerator<KeyValuePair<object, object>> IEnumerable<KeyValuePair<object, object>>.GetEnumerator()
{
return Items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Items.GetEnumerator();
}
}
}

View File

@ -0,0 +1,78 @@
// 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 System.IO.Pipelines;
using System.Security.Claims;
using System.Threading;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Connections
{
public class DefaultConnectionContext : ConnectionContext,
IDisposable,
IConnectionIdFeature,
IConnectionItemsFeature,
IConnectionTransportFeature,
IConnectionUserFeature,
IConnectionLifetimeFeature
{
private CancellationTokenSource _connectionClosedTokenSource = new CancellationTokenSource();
public DefaultConnectionContext() :
this(Guid.NewGuid().ToString())
{
ConnectionClosed = _connectionClosedTokenSource.Token;
}
/// <summary>
/// Creates the DefaultConnectionContext without Pipes to avoid upfront allocations.
/// The caller is expected to set the <see cref="Transport"/> and <see cref="Application"/> pipes manually.
/// </summary>
/// <param name="id"></param>
public DefaultConnectionContext(string id)
{
ConnectionId = id;
Features = new FeatureCollection();
Features.Set<IConnectionUserFeature>(this);
Features.Set<IConnectionItemsFeature>(this);
Features.Set<IConnectionIdFeature>(this);
Features.Set<IConnectionTransportFeature>(this);
Features.Set<IConnectionLifetimeFeature>(this);
}
public DefaultConnectionContext(string id, IDuplexPipe transport, IDuplexPipe application)
: this(id)
{
Transport = transport;
Application = application;
}
public override string ConnectionId { get; set; }
public override IFeatureCollection Features { get; }
public ClaimsPrincipal User { get; set; }
public override IDictionary<object, object> Items { get; set; } = new ConnectionItems();
public IDuplexPipe Application { get; set; }
public override IDuplexPipe Transport { get; set; }
public CancellationToken ConnectionClosed { get; set; }
public override void Abort(ConnectionAbortedException abortReason)
{
ThreadPool.QueueUserWorkItem(cts => ((CancellationTokenSource)cts).Cancel(), _connectionClosedTokenSource);
}
public void Dispose()
{
_connectionClosedTokenSource.Dispose();
}
}
}

View File

@ -0,0 +1,18 @@
// 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.Connections
{
public class AddressInUseException : InvalidOperationException
{
public AddressInUseException(string message) : base(message)
{
}
public AddressInUseException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,21 @@
using System;
namespace Microsoft.AspNetCore.Connections
{
public class ConnectionAbortedException : OperationCanceledException
{
public ConnectionAbortedException() :
this("The connection was aborted")
{
}
public ConnectionAbortedException(string message) : base(message)
{
}
public ConnectionAbortedException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,19 @@
// 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.IO;
namespace Microsoft.AspNetCore.Connections
{
public class ConnectionResetException : IOException
{
public ConnectionResetException(string message) : base(message)
{
}
public ConnectionResetException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,12 @@
// 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.Connections.Features
{
public interface IConnectionHeartbeatFeature
{
void OnHeartbeat(Action<object> action, object state);
}
}

View File

@ -0,0 +1,10 @@
// 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.
namespace Microsoft.AspNetCore.Connections.Features
{
public interface IConnectionIdFeature
{
string ConnectionId { get; set; }
}
}

View File

@ -0,0 +1,24 @@
// 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 System.Text;
namespace Microsoft.AspNetCore.Connections.Features
{
/// <summary>
/// Indicates if the connection transport has an "inherent keep-alive", which means that the transport will automatically
/// inform the client that it is still present.
/// </summary>
/// <remarks>
/// The most common example of this feature is the Long Polling HTTP transport, which must (due to HTTP limitations) terminate
/// each poll within a particular interval and return a signal indicating "the server is still here, but there is no data yet".
/// This feature allows applications to add keep-alive functionality, but limit it only to transports that don't have some kind
/// of inherent keep-alive.
/// </remarks>
public interface IConnectionInherentKeepAliveFeature
{
bool HasInherentKeepAlive { get; }
}
}

View File

@ -0,0 +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.Generic;
namespace Microsoft.AspNetCore.Connections.Features
{
public interface IConnectionItemsFeature
{
IDictionary<object, object> Items { get; set; }
}
}

View File

@ -0,0 +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.Threading;
namespace Microsoft.AspNetCore.Connections.Features
{
public interface IConnectionLifetimeFeature
{
CancellationToken ConnectionClosed { get; set; }
void Abort();
}
}

View File

@ -0,0 +1,14 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Threading;
namespace Microsoft.AspNetCore.Connections.Features
{
public interface IConnectionTransportFeature
{
IDuplexPipe Transport { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using System.Security.Claims;
namespace Microsoft.AspNetCore.Connections.Features
{
public interface IConnectionUserFeature
{
ClaimsPrincipal User { get; set; }
}
}

View File

@ -0,0 +1,14 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Threading;
namespace Microsoft.AspNetCore.Connections.Features
{
public interface IMemoryPoolFeature
{
MemoryPool<byte> MemoryPool { get; }
}
}

View File

@ -0,0 +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.
namespace Microsoft.AspNetCore.Connections.Features
{
public interface ITransferFormatFeature
{
TransferFormat SupportedFormats { get; }
TransferFormat ActiveFormat { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace Microsoft.AspNetCore.Connections
{
public interface IConnectionBuilder
{
IServiceProvider ApplicationServices { get; }
IConnectionBuilder Use(Func<ConnectionDelegate, ConnectionDelegate> middleware);
ConnectionDelegate Build();
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Core components of ASP.NET Core networking protocol stack.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore</PackageTags>
<NoWarn>CS1591;$(NoWarn)</NoWarn>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Http.Features" />
<Reference Include="Microsoft.Extensions.ActivatorUtilities.Sources" PrivateAssets="All" />
<Reference Include="System.IO.Pipelines" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,14 @@
// 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.Connections
{
[Flags]
public enum TransferFormat
{
Binary = 0x01,
Text = 0x02
}
}

View File

@ -0,0 +1,2 @@
{
}

View File

@ -0,0 +1,9 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
<PropertyGroup>
<BaseIntermediateOutputPath>$(RepositoryRoot)obj\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
<BaseOutputPath>$(RepositoryRoot)bin\$(MSBuildProjectName)\</BaseOutputPath>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,170 @@
// 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.IO;
using System.IO.Pipelines;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
public class AdaptedPipeline : IDuplexPipe
{
private static readonly int MinAllocBufferSize = KestrelMemoryPool.MinimumSegmentSize / 2;
private readonly IDuplexPipe _transport;
public AdaptedPipeline(IDuplexPipe transport,
Pipe inputPipe,
Pipe outputPipe,
IKestrelTrace log)
{
_transport = transport;
Input = inputPipe;
Output = outputPipe;
Log = log;
}
public Pipe Input { get; }
public Pipe Output { get; }
public IKestrelTrace Log { get; }
PipeReader IDuplexPipe.Input => Input.Reader;
PipeWriter IDuplexPipe.Output => Output.Writer;
public async Task RunAsync(Stream stream)
{
var inputTask = ReadInputAsync(stream);
var outputTask = WriteOutputAsync(stream);
await inputTask;
await outputTask;
}
private async Task WriteOutputAsync(Stream stream)
{
try
{
if (stream == null)
{
return;
}
while (true)
{
var result = await Output.Reader.ReadAsync();
var buffer = result.Buffer;
try
{
if (buffer.IsEmpty)
{
if (result.IsCompleted)
{
break;
}
await stream.FlushAsync();
}
else if (buffer.IsSingleSegment)
{
#if NETCOREAPP2_1
await stream.WriteAsync(buffer.First);
#else
var array = buffer.First.GetArray();
await stream.WriteAsync(array.Array, array.Offset, array.Count);
#endif
}
else
{
foreach (var memory in buffer)
{
#if NETCOREAPP2_1
await stream.WriteAsync(memory);
#else
var array = memory.GetArray();
await stream.WriteAsync(array.Array, array.Offset, array.Count);
#endif
}
}
}
finally
{
Output.Reader.AdvanceTo(buffer.End);
}
}
}
catch (Exception ex)
{
Log.LogError(0, ex, $"{nameof(AdaptedPipeline)}.{nameof(WriteOutputAsync)}");
}
finally
{
Output.Reader.Complete();
_transport.Output.Complete();
}
}
private async Task ReadInputAsync(Stream stream)
{
Exception error = null;
try
{
if (stream == null)
{
// REVIEW: Do we need an exception here?
return;
}
while (true)
{
var outputBuffer = Input.Writer.GetMemory(MinAllocBufferSize);
#if NETCOREAPP2_1
var bytesRead = await stream.ReadAsync(outputBuffer);
#else
var array = outputBuffer.GetArray();
var bytesRead = await stream.ReadAsync(array.Array, array.Offset, array.Count);
#endif
Input.Writer.Advance(bytesRead);
if (bytesRead == 0)
{
// FIN
break;
}
var result = await Input.Writer.FlushAsync();
if (result.IsCompleted)
{
break;
}
}
}
catch (Exception ex)
{
// Don't rethrow the exception. It should be handled by the Pipeline consumer.
error = ex;
}
finally
{
Input.Writer.Complete(error);
// The application could have ended the input pipe so complete
// the transport pipe as well
_transport.Input.Complete();
}
}
public void Dispose()
{
}
}
}

View File

@ -0,0 +1,26 @@
// 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.IO;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
// Even though this only includes the non-adapted ConnectionStream currently, this is a context in case
// we want to add more connection metadata later.
public class ConnectionAdapterContext
{
internal ConnectionAdapterContext(ConnectionContext connectionContext, Stream connectionStream)
{
ConnectionContext = connectionContext;
ConnectionStream = connectionStream;
}
internal ConnectionContext ConnectionContext { get; }
public IFeatureCollection Features => ConnectionContext.Features;
public Stream ConnectionStream { get; }
}
}

View File

@ -0,0 +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.IO;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
public interface IAdaptedConnection : IDisposable
{
Stream ConnectionStream { get; }
}
}

View File

@ -0,0 +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.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
public interface IConnectionAdapter
{
bool IsHttps { get; }
Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context);
}
}

View File

@ -0,0 +1,47 @@
// 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.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
public class LoggingConnectionAdapter : IConnectionAdapter
{
private readonly ILogger _logger;
public LoggingConnectionAdapter(ILogger logger)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger;
}
public bool IsHttps => false;
public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
{
return Task.FromResult<IAdaptedConnection>(
new LoggingAdaptedConnection(context.ConnectionStream, _logger));
}
private class LoggingAdaptedConnection : IAdaptedConnection
{
public LoggingAdaptedConnection(Stream rawStream, ILogger logger)
{
ConnectionStream = new LoggingStream(rawStream, logger);
}
public Stream ConnectionStream { get; }
public void Dispose()
{
}
}
}
}

View File

@ -0,0 +1,246 @@
// 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.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
internal class LoggingStream : Stream
{
private readonly Stream _inner;
private readonly ILogger _logger;
public LoggingStream(Stream inner, ILogger logger)
{
_inner = inner;
_logger = logger;
}
public override bool CanRead
{
get
{
return _inner.CanRead;
}
}
public override bool CanSeek
{
get
{
return _inner.CanSeek;
}
}
public override bool CanWrite
{
get
{
return _inner.CanWrite;
}
}
public override long Length
{
get
{
return _inner.Length;
}
}
public override long Position
{
get
{
return _inner.Position;
}
set
{
_inner.Position = value;
}
}
public override void Flush()
{
_inner.Flush();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return _inner.FlushAsync(cancellationToken);
}
public override int Read(byte[] buffer, int offset, int count)
{
int read = _inner.Read(buffer, offset, count);
Log("Read", new ReadOnlySpan<byte>(buffer, offset, read));
return read;
}
#if NETCOREAPP2_1
public override int Read(Span<byte> destination)
{
int read = _inner.Read(destination);
Log("Read", destination.Slice(0, read));
return read;
}
#endif
public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
int read = await _inner.ReadAsync(buffer, offset, count, cancellationToken);
Log("ReadAsync", new ReadOnlySpan<byte>(buffer, offset, read));
return read;
}
#if NETCOREAPP2_1
public override async ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
int read = await _inner.ReadAsync(destination, cancellationToken);
Log("ReadAsync", destination.Span.Slice(0, read));
return read;
}
#endif
public override long Seek(long offset, SeekOrigin origin)
{
return _inner.Seek(offset, origin);
}
public override void SetLength(long value)
{
_inner.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
Log("Write", new ReadOnlySpan<byte>(buffer, offset, count));
_inner.Write(buffer, offset, count);
}
#if NETCOREAPP2_1
public override void Write(ReadOnlySpan<byte> source)
{
Log("Write", source);
_inner.Write(source);
}
#endif
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
Log("WriteAsync", new ReadOnlySpan<byte>(buffer, offset, count));
return _inner.WriteAsync(buffer, offset, count, cancellationToken);
}
#if NETCOREAPP2_1
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
Log("WriteAsync", source.Span);
return _inner.WriteAsync(source, cancellationToken);
}
#endif
private void Log(string method, ReadOnlySpan<byte> buffer)
{
var builder = new StringBuilder($"{method}[{buffer.Length}] ");
// Write the hex
for (int i = 0; i < buffer.Length; i++)
{
builder.Append(buffer[i].ToString("X2"));
builder.Append(" ");
}
builder.AppendLine();
// Write the bytes as if they were ASCII
for (int i = 0; i < buffer.Length; i++)
{
builder.Append((char)buffer[i]);
}
_logger.LogDebug(builder.ToString());
}
// The below APM methods call the underlying Read/WriteAsync methods which will still be logged.
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
var task = ReadAsync(buffer, offset, count, default(CancellationToken), state);
if (callback != null)
{
task.ContinueWith(t => callback.Invoke(t));
}
return task;
}
public override int EndRead(IAsyncResult asyncResult)
{
return ((Task<int>)asyncResult).GetAwaiter().GetResult();
}
private Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
{
var tcs = new TaskCompletionSource<int>(state);
var task = ReadAsync(buffer, offset, count, cancellationToken);
task.ContinueWith((task2, state2) =>
{
var tcs2 = (TaskCompletionSource<int>)state2;
if (task2.IsCanceled)
{
tcs2.SetCanceled();
}
else if (task2.IsFaulted)
{
tcs2.SetException(task2.Exception);
}
else
{
tcs2.SetResult(task2.Result);
}
}, tcs, cancellationToken);
return tcs.Task;
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
var task = WriteAsync(buffer, offset, count, default(CancellationToken), state);
if (callback != null)
{
task.ContinueWith(t => callback.Invoke(t));
}
return task;
}
public override void EndWrite(IAsyncResult asyncResult)
{
((Task<object>)asyncResult).GetAwaiter().GetResult();
}
private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
{
var tcs = new TaskCompletionSource<object>(state);
var task = WriteAsync(buffer, offset, count, cancellationToken);
task.ContinueWith((task2, state2) =>
{
var tcs2 = (TaskCompletionSource<object>)state2;
if (task2.IsCanceled)
{
tcs2.SetCanceled();
}
else if (task2.IsFaulted)
{
tcs2.SetException(task2.Exception);
}
else
{
tcs2.SetResult(null);
}
}, tcs, cancellationToken);
return tcs.Task;
}
}
}

View File

@ -0,0 +1,217 @@
// 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.IO.Pipelines;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Buffers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal
{
public class RawStream : Stream
{
private readonly PipeReader _input;
private readonly PipeWriter _output;
public RawStream(PipeReader input, PipeWriter output)
{
_input = input;
_output = output;
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length
{
get
{
throw new NotSupportedException();
}
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
// ValueTask uses .GetAwaiter().GetResult() if necessary
// https://github.com/dotnet/corefx/blob/f9da3b4af08214764a51b2331f3595ffaf162abe/src/System.Threading.Tasks.Extensions/src/System/Threading/Tasks/ValueTask.cs#L156
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count)).Result;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count)).AsTask();
}
#if NETCOREAPP2_1
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
return ReadAsyncInternal(destination);
}
#endif
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer != null)
{
_output.Write(new ReadOnlySpan<byte>(buffer, offset, count));
}
await _output.FlushAsync(cancellationToken);
}
#if NETCOREAPP2_1
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
_output.Write(source.Span);
await _output.FlushAsync(cancellationToken);
}
#endif
public override void Flush()
{
FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return WriteAsync(null, 0, 0, cancellationToken);
}
private async ValueTask<int> ReadAsyncInternal(Memory<byte> destination)
{
while (true)
{
var result = await _input.ReadAsync();
var readableBuffer = result.Buffer;
try
{
if (!readableBuffer.IsEmpty)
{
// buffer.Count is int
var count = (int) Math.Min(readableBuffer.Length, destination.Length);
readableBuffer = readableBuffer.Slice(0, count);
readableBuffer.CopyTo(destination.Span);
return count;
}
if (result.IsCompleted)
{
return 0;
}
}
finally
{
_input.AdvanceTo(readableBuffer.End, readableBuffer.End);
}
}
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
var task = ReadAsync(buffer, offset, count, default(CancellationToken), state);
if (callback != null)
{
task.ContinueWith(t => callback.Invoke(t));
}
return task;
}
public override int EndRead(IAsyncResult asyncResult)
{
return ((Task<int>)asyncResult).GetAwaiter().GetResult();
}
private Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
{
var tcs = new TaskCompletionSource<int>(state);
var task = ReadAsync(buffer, offset, count, cancellationToken);
task.ContinueWith((task2, state2) =>
{
var tcs2 = (TaskCompletionSource<int>)state2;
if (task2.IsCanceled)
{
tcs2.SetCanceled();
}
else if (task2.IsFaulted)
{
tcs2.SetException(task2.Exception);
}
else
{
tcs2.SetResult(task2.Result);
}
}, tcs, cancellationToken);
return tcs.Task;
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
var task = WriteAsync(buffer, offset, count, default(CancellationToken), state);
if (callback != null)
{
task.ContinueWith(t => callback.Invoke(t));
}
return task;
}
public override void EndWrite(IAsyncResult asyncResult)
{
((Task<object>)asyncResult).GetAwaiter().GetResult();
}
private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
{
var tcs = new TaskCompletionSource<object>(state);
var task = WriteAsync(buffer, offset, count, cancellationToken);
task.ContinueWith((task2, state2) =>
{
var tcs2 = (TaskCompletionSource<object>)state2;
if (task2.IsCanceled)
{
tcs2.SetCanceled();
}
else if (task2.IsFaulted)
{
tcs2.SetException(task2.Exception);
}
else
{
tcs2.SetResult(null);
}
}, tcs, cancellationToken);
return tcs.Task;
}
}
}

View File

@ -0,0 +1,38 @@
// 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.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Adapter.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Hosting
{
public static class ListenOptionsConnectionLoggingExtensions
{
/// <summary>
/// Emits verbose logs for bytes read from and written to the connection.
/// </summary>
/// <returns>
/// The <see cref="ListenOptions"/>.
/// </returns>
public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions)
{
return listenOptions.UseConnectionLogging(nameof(LoggingConnectionAdapter));
}
/// <summary>
/// Emits verbose logs for bytes read from and written to the connection.
/// </summary>
/// <returns>
/// The <see cref="ListenOptions"/>.
/// </returns>
public static ListenOptions UseConnectionLogging(this ListenOptions listenOptions, string loggerName)
{
var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger(loggerName ?? nameof(LoggingConnectionAdapter));
listenOptions.ConnectionAdapters.Add(new LoggingConnectionAdapter(logger));
return listenOptions;
}
}
}

View File

@ -0,0 +1,37 @@
// 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.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
internal class AnyIPListenOptions : ListenOptions
{
internal AnyIPListenOptions(int port)
: base(new IPEndPoint(IPAddress.IPv6Any, port))
{
}
internal override async Task BindAsync(AddressBindContext context)
{
// when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
try
{
await base.BindAsync(context).ConfigureAwait(false);
}
catch (Exception ex) when (!(ex is IOException))
{
context.Logger.LogDebug(CoreStrings.FormatFallbackToIPv4Any(IPEndPoint.Port));
// for machines that do not support IPv6
IPEndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.Port);
await base.BindAsync(context).ConfigureAwait(false);
}
}
}
}

View File

@ -0,0 +1,176 @@
// 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.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Server.Kestrel.Core
{
public sealed class BadHttpRequestException : IOException
{
private BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason)
: this(message, statusCode, reason, null)
{ }
private BadHttpRequestException(string message, int statusCode, RequestRejectionReason reason, HttpMethod? requiredMethod)
: base(message)
{
StatusCode = statusCode;
Reason = reason;
if (requiredMethod.HasValue)
{
AllowedHeader = HttpUtilities.MethodToString(requiredMethod.Value);
}
}
internal int StatusCode { get; }
internal StringValues AllowedHeader { get; }
internal RequestRejectionReason Reason { get; }
[StackTraceHidden]
internal static void Throw(RequestRejectionReason reason)
{
throw GetException(reason);
}
[StackTraceHidden]
public static void Throw(RequestRejectionReason reason, HttpMethod method)
=> throw GetException(reason, method.ToString().ToUpperInvariant());
[MethodImpl(MethodImplOptions.NoInlining)]
internal static BadHttpRequestException GetException(RequestRejectionReason reason)
{
BadHttpRequestException ex;
switch (reason)
{
case RequestRejectionReason.InvalidRequestHeadersNoCRLF:
ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidRequestHeadersNoCRLF, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.InvalidRequestLine:
ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidRequestLine, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.MalformedRequestInvalidHeaders:
ex = new BadHttpRequestException(CoreStrings.BadRequest_MalformedRequestInvalidHeaders, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.MultipleContentLengths:
ex = new BadHttpRequestException(CoreStrings.BadRequest_MultipleContentLengths, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.UnexpectedEndOfRequestContent:
ex = new BadHttpRequestException(CoreStrings.BadRequest_UnexpectedEndOfRequestContent, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.BadChunkSuffix:
ex = new BadHttpRequestException(CoreStrings.BadRequest_BadChunkSuffix, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.BadChunkSizeData:
ex = new BadHttpRequestException(CoreStrings.BadRequest_BadChunkSizeData, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.ChunkedRequestIncomplete:
ex = new BadHttpRequestException(CoreStrings.BadRequest_ChunkedRequestIncomplete, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.InvalidCharactersInHeaderName:
ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidCharactersInHeaderName, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.RequestLineTooLong:
ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestLineTooLong, StatusCodes.Status414UriTooLong, reason);
break;
case RequestRejectionReason.HeadersExceedMaxTotalSize:
ex = new BadHttpRequestException(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, StatusCodes.Status431RequestHeaderFieldsTooLarge, reason);
break;
case RequestRejectionReason.TooManyHeaders:
ex = new BadHttpRequestException(CoreStrings.BadRequest_TooManyHeaders, StatusCodes.Status431RequestHeaderFieldsTooLarge, reason);
break;
case RequestRejectionReason.RequestBodyTooLarge:
ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestBodyTooLarge, StatusCodes.Status413PayloadTooLarge, reason);
break;
case RequestRejectionReason.RequestHeadersTimeout:
ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestHeadersTimeout, StatusCodes.Status408RequestTimeout, reason);
break;
case RequestRejectionReason.RequestBodyTimeout:
ex = new BadHttpRequestException(CoreStrings.BadRequest_RequestBodyTimeout, StatusCodes.Status408RequestTimeout, reason);
break;
case RequestRejectionReason.OptionsMethodRequired:
ex = new BadHttpRequestException(CoreStrings.BadRequest_MethodNotAllowed, StatusCodes.Status405MethodNotAllowed, reason, HttpMethod.Options);
break;
case RequestRejectionReason.ConnectMethodRequired:
ex = new BadHttpRequestException(CoreStrings.BadRequest_MethodNotAllowed, StatusCodes.Status405MethodNotAllowed, reason, HttpMethod.Connect);
break;
case RequestRejectionReason.MissingHostHeader:
ex = new BadHttpRequestException(CoreStrings.BadRequest_MissingHostHeader, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.MultipleHostHeaders:
ex = new BadHttpRequestException(CoreStrings.BadRequest_MultipleHostHeaders, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.InvalidHostHeader:
ex = new BadHttpRequestException(CoreStrings.BadRequest_InvalidHostHeader, StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.UpgradeRequestCannotHavePayload:
ex = new BadHttpRequestException(CoreStrings.BadRequest_UpgradeRequestCannotHavePayload, StatusCodes.Status400BadRequest, reason);
break;
default:
ex = new BadHttpRequestException(CoreStrings.BadRequest, StatusCodes.Status400BadRequest, reason);
break;
}
return ex;
}
[StackTraceHidden]
internal static void Throw(RequestRejectionReason reason, string detail)
{
throw GetException(reason, detail);
}
[StackTraceHidden]
internal static void Throw(RequestRejectionReason reason, in StringValues detail)
{
throw GetException(reason, detail.ToString());
}
[MethodImpl(MethodImplOptions.NoInlining)]
internal static BadHttpRequestException GetException(RequestRejectionReason reason, string detail)
{
BadHttpRequestException ex;
switch (reason)
{
case RequestRejectionReason.InvalidRequestLine:
ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(detail), StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.InvalidRequestTarget:
ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(detail), StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.InvalidRequestHeader:
ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(detail), StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.InvalidContentLength:
ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidContentLength_Detail(detail), StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.UnrecognizedHTTPVersion:
ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_UnrecognizedHTTPVersion(detail), StatusCodes.Status505HttpVersionNotsupported, reason);
break;
case RequestRejectionReason.FinalTransferCodingNotChunked:
ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_FinalTransferCodingNotChunked(detail), StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.LengthRequired:
ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_LengthRequired(detail), StatusCodes.Status411LengthRequired, reason);
break;
case RequestRejectionReason.LengthRequiredHttp10:
ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_LengthRequiredHttp10(detail), StatusCodes.Status400BadRequest, reason);
break;
case RequestRejectionReason.InvalidHostHeader:
ex = new BadHttpRequestException(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail(detail), StatusCodes.Status400BadRequest, reason);
break;
default:
ex = new BadHttpRequestException(CoreStrings.BadRequest, StatusCodes.Status400BadRequest, reason);
break;
}
return ex;
}
}
}

View File

@ -0,0 +1,26 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Https
{
/// <summary>
/// Describes the client certificate requirements for a HTTPS connection.
/// </summary>
public enum ClientCertificateMode
{
/// <summary>
/// A client certificate is not required and will not be requested from clients.
/// </summary>
NoCertificate,
/// <summary>
/// A client certificate will be requested; however, authentication will not fail if a certificate is not provided by the client.
/// </summary>
AllowCertificate,
/// <summary>
/// A client certificate will be requested, and the client must provide a valid certificate for authentication to succeed.
/// </summary>
RequireCertificate
}
}

View File

@ -0,0 +1,521 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="BadRequest" xml:space="preserve">
<value>Bad request.</value>
</data>
<data name="BadRequest_BadChunkSizeData" xml:space="preserve">
<value>Bad chunk size data.</value>
</data>
<data name="BadRequest_BadChunkSuffix" xml:space="preserve">
<value>Bad chunk suffix.</value>
</data>
<data name="BadRequest_ChunkedRequestIncomplete" xml:space="preserve">
<value>Chunked request incomplete.</value>
</data>
<data name="BadRequest_FinalTransferCodingNotChunked" xml:space="preserve">
<value>The message body length cannot be determined because the final transfer coding was set to '{detail}' instead of 'chunked'.</value>
</data>
<data name="BadRequest_HeadersExceedMaxTotalSize" xml:space="preserve">
<value>Request headers too long.</value>
</data>
<data name="BadRequest_InvalidCharactersInHeaderName" xml:space="preserve">
<value>Invalid characters in header name.</value>
</data>
<data name="BadRequest_InvalidContentLength_Detail" xml:space="preserve">
<value>Invalid content length: {detail}</value>
</data>
<data name="BadRequest_InvalidHostHeader" xml:space="preserve">
<value>Invalid Host header.</value>
</data>
<data name="BadRequest_InvalidHostHeader_Detail" xml:space="preserve">
<value>Invalid Host header: '{detail}'</value>
</data>
<data name="BadRequest_InvalidRequestHeadersNoCRLF" xml:space="preserve">
<value>Invalid request headers: missing final CRLF in header fields.</value>
</data>
<data name="BadRequest_InvalidRequestHeader_Detail" xml:space="preserve">
<value>Invalid request header: '{detail}'</value>
</data>
<data name="BadRequest_InvalidRequestLine" xml:space="preserve">
<value>Invalid request line.</value>
</data>
<data name="BadRequest_InvalidRequestLine_Detail" xml:space="preserve">
<value>Invalid request line: '{detail}'</value>
</data>
<data name="BadRequest_InvalidRequestTarget_Detail" xml:space="preserve">
<value>Invalid request target: '{detail}'</value>
</data>
<data name="BadRequest_LengthRequired" xml:space="preserve">
<value>{detail} request contains no Content-Length or Transfer-Encoding header.</value>
</data>
<data name="BadRequest_LengthRequiredHttp10" xml:space="preserve">
<value>{detail} request contains no Content-Length header.</value>
</data>
<data name="BadRequest_MalformedRequestInvalidHeaders" xml:space="preserve">
<value>Malformed request: invalid headers.</value>
</data>
<data name="BadRequest_MethodNotAllowed" xml:space="preserve">
<value>Method not allowed.</value>
</data>
<data name="BadRequest_MissingHostHeader" xml:space="preserve">
<value>Request is missing Host header.</value>
</data>
<data name="BadRequest_MultipleContentLengths" xml:space="preserve">
<value>Multiple Content-Length headers.</value>
</data>
<data name="BadRequest_MultipleHostHeaders" xml:space="preserve">
<value>Multiple Host headers.</value>
</data>
<data name="BadRequest_RequestLineTooLong" xml:space="preserve">
<value>Request line too long.</value>
</data>
<data name="BadRequest_RequestHeadersTimeout" xml:space="preserve">
<value>Reading the request headers timed out.</value>
</data>
<data name="BadRequest_TooManyHeaders" xml:space="preserve">
<value>Request contains too many headers.</value>
</data>
<data name="BadRequest_UnexpectedEndOfRequestContent" xml:space="preserve">
<value>Unexpected end of request content.</value>
</data>
<data name="BadRequest_UnrecognizedHTTPVersion" xml:space="preserve">
<value>Unrecognized HTTP version: '{detail}'</value>
</data>
<data name="BadRequest_UpgradeRequestCannotHavePayload" xml:space="preserve">
<value>Requests with 'Connection: Upgrade' cannot have content in the request body.</value>
</data>
<data name="FallbackToIPv4Any" xml:space="preserve">
<value>Failed to bind to http://[::]:{port} (IPv6Any). Attempting to bind to http://0.0.0.0:{port} instead.</value>
</data>
<data name="ResponseStreamWasUpgraded" xml:space="preserve">
<value>Cannot write to response body after connection has been upgraded.</value>
</data>
<data name="BigEndianNotSupported" xml:space="preserve">
<value>Kestrel does not support big-endian architectures.</value>
</data>
<data name="MaxRequestBufferSmallerThanRequestHeaderBuffer" xml:space="preserve">
<value>Maximum request buffer size ({requestBufferSize}) must be greater than or equal to maximum request header size ({requestHeaderSize}).</value>
</data>
<data name="MaxRequestBufferSmallerThanRequestLineBuffer" xml:space="preserve">
<value>Maximum request buffer size ({requestBufferSize}) must be greater than or equal to maximum request line size ({requestLineSize}).</value>
</data>
<data name="ServerAlreadyStarted" xml:space="preserve">
<value>Server has already started.</value>
</data>
<data name="UnknownTransportMode" xml:space="preserve">
<value>Unknown transport mode: '{mode}'.</value>
</data>
<data name="InvalidAsciiOrControlChar" xml:space="preserve">
<value>Invalid non-ASCII or control character in header: {character}</value>
</data>
<data name="InvalidContentLength_InvalidNumber" xml:space="preserve">
<value>Invalid Content-Length: "{value}". Value must be a positive integral number.</value>
</data>
<data name="NonNegativeNumberOrNullRequired" xml:space="preserve">
<value>Value must be null or a non-negative number.</value>
</data>
<data name="NonNegativeNumberRequired" xml:space="preserve">
<value>Value must be a non-negative number.</value>
</data>
<data name="PositiveNumberRequired" xml:space="preserve">
<value>Value must be a positive number.</value>
</data>
<data name="PositiveNumberOrNullRequired" xml:space="preserve">
<value>Value must be null or a positive number.</value>
</data>
<data name="UnixSocketPathMustBeAbsolute" xml:space="preserve">
<value>Unix socket path must be absolute.</value>
</data>
<data name="AddressBindingFailed" xml:space="preserve">
<value>Failed to bind to address {address}.</value>
</data>
<data name="BindingToDefaultAddress" xml:space="preserve">
<value>No listening endpoints were configured. Binding to {address} by default.</value>
</data>
<data name="ConfigureHttpsFromMethodCall" xml:space="preserve">
<value>HTTPS endpoints can only be configured using {methodName}.</value>
</data>
<data name="ConfigurePathBaseFromMethodCall" xml:space="preserve">
<value>A path base can only be configured using {methodName}.</value>
</data>
<data name="DynamicPortOnLocalhostNotSupported" xml:space="preserve">
<value>Dynamic port binding is not supported when binding to localhost. You must either bind to 127.0.0.1:0 or [::1]:0, or both.</value>
</data>
<data name="EndpointAlreadyInUse" xml:space="preserve">
<value>Failed to bind to address {endpoint}: address already in use.</value>
</data>
<data name="InvalidUrl" xml:space="preserve">
<value>Invalid URL: '{url}'.</value>
</data>
<data name="NetworkInterfaceBindingFailed" xml:space="preserve">
<value>Unable to bind to {address} on the {interfaceName} interface: '{error}'.</value>
</data>
<data name="OverridingWithKestrelOptions" xml:space="preserve">
<value>Overriding address(es) '{addresses}'. Binding to endpoints defined in {methodName} instead.</value>
</data>
<data name="OverridingWithPreferHostingUrls" xml:space="preserve">
<value>Overriding endpoints defined in UseKestrel() because {settingName} is set to true. Binding to address(es) '{addresses}' instead.</value>
</data>
<data name="UnsupportedAddressScheme" xml:space="preserve">
<value>Unrecognized scheme in server address '{address}'. Only 'http://' is supported.</value>
</data>
<data name="HeadersAreReadOnly" xml:space="preserve">
<value>Headers are read-only, response has already started.</value>
</data>
<data name="KeyAlreadyExists" xml:space="preserve">
<value>An item with the same key has already been added.</value>
</data>
<data name="HeaderNotAllowedOnResponse" xml:space="preserve">
<value>Setting the header {name} is not allowed on responses with status code {statusCode}.</value>
</data>
<data name="ParameterReadOnlyAfterResponseStarted" xml:space="preserve">
<value>{name} cannot be set because the response has already started.</value>
</data>
<data name="RequestProcessingAborted" xml:space="preserve">
<value>Request processing didn't complete within the shutdown timeout.</value>
</data>
<data name="TooFewBytesWritten" xml:space="preserve">
<value>Response Content-Length mismatch: too few bytes written ({written} of {expected}).</value>
</data>
<data name="TooManyBytesWritten" xml:space="preserve">
<value>Response Content-Length mismatch: too many bytes written ({written} of {expected}).</value>
</data>
<data name="UnhandledApplicationException" xml:space="preserve">
<value>The response has been aborted due to an unhandled application exception.</value>
</data>
<data name="WritingToResponseBodyNotSupported" xml:space="preserve">
<value>Writing to the response body is invalid for responses with status code {statusCode}.</value>
</data>
<data name="ConnectionShutdownError" xml:space="preserve">
<value>Connection shutdown abnormally.</value>
</data>
<data name="RequestProcessingEndError" xml:space="preserve">
<value>Connection processing ended abnormally.</value>
</data>
<data name="CannotUpgradeNonUpgradableRequest" xml:space="preserve">
<value>Cannot upgrade a non-upgradable request. Check IHttpUpgradeFeature.IsUpgradableRequest to determine if a request can be upgraded.</value>
</data>
<data name="UpgradedConnectionLimitReached" xml:space="preserve">
<value>Request cannot be upgraded because the server has already opened the maximum number of upgraded connections.</value>
</data>
<data name="UpgradeCannotBeCalledMultipleTimes" xml:space="preserve">
<value>IHttpUpgradeFeature.UpgradeAsync was already called and can only be called once per connection.</value>
</data>
<data name="BadRequest_RequestBodyTooLarge" xml:space="preserve">
<value>Request body too large.</value>
</data>
<data name="MaxRequestBodySizeCannotBeModifiedAfterRead" xml:space="preserve">
<value>The maximum request body size cannot be modified after the app has already started reading from the request body.</value>
</data>
<data name="MaxRequestBodySizeCannotBeModifiedForUpgradedRequests" xml:space="preserve">
<value>The maximum request body size cannot be modified after the request has been upgraded.</value>
</data>
<data name="PositiveTimeSpanRequired" xml:space="preserve">
<value>Value must be a positive TimeSpan.</value>
</data>
<data name="NonNegativeTimeSpanRequired" xml:space="preserve">
<value>Value must be a non-negative TimeSpan.</value>
</data>
<data name="MinimumGracePeriodRequired" xml:space="preserve">
<value>The request body rate enforcement grace period must be greater than {heartbeatInterval} second.</value>
</data>
<data name="SynchronousReadsDisallowed" xml:space="preserve">
<value>Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.</value>
</data>
<data name="SynchronousWritesDisallowed" xml:space="preserve">
<value>Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.</value>
</data>
<data name="PositiveNumberOrNullMinDataRateRequired" xml:space="preserve">
<value>Value must be a positive number. To disable a minimum data rate, use null where a MinDataRate instance is expected.</value>
</data>
<data name="ConcurrentTimeoutsNotSupported" xml:space="preserve">
<value>Concurrent timeouts are not supported.</value>
</data>
<data name="PositiveFiniteTimeSpanRequired" xml:space="preserve">
<value>Timespan must be positive and finite.</value>
</data>
<data name="EndPointRequiresAtLeastOneProtocol" xml:space="preserve">
<value>An endpoint must be configured to serve at least one protocol.</value>
</data>
<data name="EndPointRequiresTlsForHttp1AndHttp2" xml:space="preserve">
<value>Using both HTTP/1.x and HTTP/2 on the same endpoint requires the use of TLS.</value>
</data>
<data name="EndPointHttp2NotNegotiated" xml:space="preserve">
<value>HTTP/2 over TLS was not negotiated on an HTTP/2-only endpoint.</value>
</data>
<data name="HPackErrorDynamicTableSizeUpdateTooLarge" xml:space="preserve">
<value>A dynamic table size of {size} octets is greater than the configured maximum size of {maxSize} octets.</value>
</data>
<data name="HPackErrorIndexOutOfRange" xml:space="preserve">
<value>Index {index} is outside the bounds of the header field table.</value>
</data>
<data name="HPackHuffmanErrorIncomplete" xml:space="preserve">
<value>Input data could not be fully decoded.</value>
</data>
<data name="HPackHuffmanErrorEOS" xml:space="preserve">
<value>Input data contains the EOS symbol.</value>
</data>
<data name="HPackHuffmanErrorDestinationTooSmall" xml:space="preserve">
<value>The destination buffer is not large enough to store the decoded data.</value>
</data>
<data name="HPackHuffmanError" xml:space="preserve">
<value>Huffman decoding error.</value>
</data>
<data name="HPackStringLengthTooLarge" xml:space="preserve">
<value>Decoded string length of {length} octets is greater than the configured maximum length of {maxStringLength} octets.</value>
</data>
<data name="HPackErrorIncompleteHeaderBlock" xml:space="preserve">
<value>The header block was incomplete and could not be fully decoded.</value>
</data>
<data name="Http2ErrorStreamIdEven" xml:space="preserve">
<value>The client sent a {frameType} frame with even stream ID {streamId}.</value>
</data>
<data name="Http2ErrorPushPromiseReceived" xml:space="preserve">
<value>The client sent a A PUSH_PROMISE frame.</value>
</data>
<data name="Http2ErrorHeadersInterleaved" xml:space="preserve">
<value>The client sent a {frameType} frame to stream ID {streamId} before signaling of the header block for stream ID {headersStreamId}.</value>
</data>
<data name="Http2ErrorStreamIdZero" xml:space="preserve">
<value>The client sent a {frameType} frame with stream ID 0.</value>
</data>
<data name="Http2ErrorStreamIdNotZero" xml:space="preserve">
<value>The client sent a {frameType} frame with stream ID different than 0.</value>
</data>
<data name="Http2ErrorPaddingTooLong" xml:space="preserve">
<value>The client sent a {frameType} frame with padding longer than or with the same length as the sent data.</value>
</data>
<data name="Http2ErrorStreamClosed" xml:space="preserve">
<value>The client sent a {frameType} frame to closed stream ID {streamId}.</value>
</data>
<data name="Http2ErrorStreamHalfClosedRemote" xml:space="preserve">
<value>The client sent a {frameType} frame to stream ID {streamId} which is in the "half-closed (remote) state".</value>
</data>
<data name="Http2ErrorStreamSelfDependency" xml:space="preserve">
<value>The client sent a {frameType} frame with dependency information that would cause stream ID {streamId} to depend on itself.</value>
</data>
<data name="Http2ErrorUnexpectedFrameLength" xml:space="preserve">
<value>The client sent a {frameType} frame with length different than {expectedLength}.</value>
</data>
<data name="Http2ErrorSettingsLengthNotMultipleOfSix" xml:space="preserve">
<value>The client sent a SETTINGS frame with a length that is not a multiple of 6.</value>
</data>
<data name="Http2ErrorSettingsAckLengthNotZero" xml:space="preserve">
<value>The client sent a SETTINGS frame with ACK set and length different than 0.</value>
</data>
<data name="Http2ErrorSettingsParameterOutOfRange" xml:space="preserve">
<value>The client sent a SETTINGS frame with a value for parameter {parameter} that is out of range.</value>
</data>
<data name="Http2ErrorWindowUpdateIncrementZero" xml:space="preserve">
<value>The client sent a WINDOW_UPDATE frame with a window size increment of 0.</value>
</data>
<data name="Http2ErrorContinuationWithNoHeaders" xml:space="preserve">
<value>The client sent a CONTINUATION frame not preceded by a HEADERS frame.</value>
</data>
<data name="Http2ErrorStreamIdle" xml:space="preserve">
<value>The client sent a {frameType} frame to idle stream ID {streamId}.</value>
</data>
<data name="Http2ErrorTrailersContainPseudoHeaderField" xml:space="preserve">
<value>The client sent trailers containing one or more pseudo-header fields.</value>
</data>
<data name="Http2ErrorHeaderNameUppercase" xml:space="preserve">
<value>The client sent a header with uppercase characters in its name.</value>
</data>
<data name="Http2ErrorTrailerNameUppercase" xml:space="preserve">
<value>The client sent a trailer with uppercase characters in its name.</value>
</data>
<data name="Http2ErrorHeadersWithTrailersNoEndStream" xml:space="preserve">
<value>The client sent a HEADERS frame containing trailers without setting the END_STREAM flag.</value>
</data>
<data name="Http2ErrorMissingMandatoryPseudoHeaderFields" xml:space="preserve">
<value>Request headers missing one or more mandatory pseudo-header fields.</value>
</data>
<data name="Http2ErrorPseudoHeaderFieldAfterRegularHeaders" xml:space="preserve">
<value>Pseudo-header field found in request headers after regular header fields.</value>
</data>
<data name="Http2ErrorUnknownPseudoHeaderField" xml:space="preserve">
<value>Request headers contain unknown pseudo-header field.</value>
</data>
<data name="Http2ErrorResponsePseudoHeaderField" xml:space="preserve">
<value>Request headers contain response-specific pseudo-header field.</value>
</data>
<data name="Http2ErrorDuplicatePseudoHeaderField" xml:space="preserve">
<value>Request headers contain duplicate pseudo-header field.</value>
</data>
<data name="Http2ErrorConnectionSpecificHeaderField" xml:space="preserve">
<value>Request headers contain connection-specific header field.</value>
</data>
<data name="UnableToConfigureHttpsBindings" xml:space="preserve">
<value>Unable to configure default https bindings because no IDefaultHttpsProvider service was provided.</value>
</data>
<data name="AuthenticationFailed" xml:space="preserve">
<value>Failed to authenticate HTTPS connection.</value>
</data>
<data name="AuthenticationTimedOut" xml:space="preserve">
<value>Authentication of the HTTPS connection timed out.</value>
</data>
<data name="InvalidServerCertificateEku" xml:space="preserve">
<value>Certificate {thumbprint} cannot be used as an SSL server certificate. It has an Extended Key Usage extension but the usages do not include Server Authentication (OID 1.3.6.1.5.5.7.3.1).</value>
</data>
<data name="PositiveTimeSpanRequired1" xml:space="preserve">
<value>Value must be a positive TimeSpan.</value>
</data>
<data name="ServerCertificateRequired" xml:space="preserve">
<value>The server certificate parameter is required.</value>
</data>
<data name="BindingToDefaultAddresses" xml:space="preserve">
<value>No listening endpoints were configured. Binding to {address0} and {address1} by default.</value>
</data>
<data name="CertNotFoundInStore" xml:space="preserve">
<value>The requested certificate {subject} could not be found in {storeLocation}/{storeName} with AllowInvalid setting: {allowInvalid}.</value>
</data>
<data name="EndpointMissingUrl" xml:space="preserve">
<value>The endpoint {endpointName} is missing the required 'Url' parameter.</value>
</data>
<data name="NoCertSpecifiedNoDevelopmentCertificateFound" xml:space="preserve">
<value>Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found.
To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'.
For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.</value>
</data>
<data name="MultipleCertificateSources" xml:space="preserve">
<value>The endpoint {endpointName} specified multiple certificate sources.</value>
</data>
<data name="Http2NotSupported" xml:space="preserve">
<value>HTTP/2 support is experimental, see https://go.microsoft.com/fwlink/?linkid=866785 to enable it.</value>
</data>
<data name="WritingToResponseBodyAfterResponseCompleted" xml:space="preserve">
<value>Cannot write to the response body, the response has completed.</value>
</data>
<data name="BadRequest_RequestBodyTimeout" xml:space="preserve">
<value>Reading the request body timed out due to data arriving too slowly. See MinRequestBodyDataRate.</value>
</data>
<data name="ConnectionAbortedByApplication" xml:space="preserve">
<value>The connection was aborted by the application.</value>
</data>
<data name="ConnectionAbortedDuringServerShutdown" xml:space="preserve">
<value>The connection was aborted because the server is shutting down and request processing didn't complete within the time specified by HostOptions.ShutdownTimeout.</value>
</data>
<data name="ConnectionTimedBecauseResponseMininumDataRateNotSatisfied" xml:space="preserve">
<value>The connection was timed out by the server because the response was not read by the client at the specified minimum data rate.</value>
</data>
<data name="ConnectionTimedOutByServer" xml:space="preserve">
<value>The connection was timed out by the server.</value>
</data>
</root>

View File

@ -0,0 +1,26 @@
// 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 Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration;
namespace Microsoft.AspNetCore.Server.Kestrel
{
public class EndpointConfiguration
{
internal EndpointConfiguration(bool isHttps, ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions, IConfigurationSection configSection)
{
IsHttps = isHttps;
ListenOptions = listenOptions ?? throw new ArgumentNullException(nameof(listenOptions));
HttpsOptions = httpsOptions ?? throw new ArgumentNullException(nameof(httpsOptions));
ConfigSection = configSection ?? throw new ArgumentNullException(nameof(configSection));
}
public bool IsHttps { get; }
public ListenOptions ListenOptions { get; }
public HttpsConnectionAdapterOptions HttpsOptions { get; }
public IConfigurationSection ConfigSection { get; }
}
}

View File

@ -0,0 +1,31 @@
// 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.Core.Features
{
/// <summary>
/// Feature for efficiently handling connection timeouts.
/// </summary>
public interface IConnectionTimeoutFeature
{
/// <summary>
/// Close the connection after the specified positive finite <see cref="TimeSpan"/>
/// unless the timeout is canceled or reset. This will fail if there is an ongoing timeout.
/// </summary>
void SetTimeout(TimeSpan timeSpan);
/// <summary>
/// Close the connection after the specified positive finite <see cref="TimeSpan"/>
/// unless the timeout is canceled or reset. This will cancel any ongoing timeouts.
/// </summary>
void ResetTimeout(TimeSpan timeSpan);
/// <summary>
/// Prevent the connection from closing after a timeout specified by <see cref="SetTimeout(TimeSpan)"/>
/// or <see cref="ResetTimeout(TimeSpan)"/>.
/// </summary>
void CancelTimeout();
}
}

View File

@ -0,0 +1,17 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features
{
/// <summary>
/// A connection feature allowing middleware to stop counting connections towards <see cref="KestrelServerLimits.MaxConcurrentConnections"/>.
/// This is used by Kestrel internally to stop counting upgraded connections towards this limit.
/// </summary>
public interface IDecrementConcurrentConnectionCountFeature
{
/// <summary>
/// Idempotent method to stop counting a connection towards <see cref="KestrelServerLimits.MaxConcurrentConnections"/>.
/// </summary>
void ReleaseConnection();
}
}

View File

@ -0,0 +1,10 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features
{
public interface IHttp2StreamIdFeature
{
int StreamId { get; }
}
}

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features
{
/// <summary>
/// Feature to set the minimum data rate at which the the request body must be sent by the client.
/// </summary>
public interface IHttpMinRequestBodyDataRateFeature
{
/// <summary>
/// The minimum data rate in bytes/second at which the request body must be sent by the client.
/// Setting this property to null indicates no minimum data rate should be enforced.
/// This limit has no effect on upgraded connections which are always unlimited.
/// </summary>
MinDataRate MinDataRate { get; set; }
}
}

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features
{
/// <summary>
/// Feature to set the minimum data rate at which the response must be received by the client.
/// </summary>
public interface IHttpMinResponseDataRateFeature
{
/// <summary>
/// The minimum data rate in bytes/second at which the response must be received by the client.
/// Setting this property to null indicates no minimum data rate should be enforced.
/// This limit has no effect on upgraded connections which are always unlimited.
/// </summary>
MinDataRate MinDataRate { get; set; }
}
}

View File

@ -0,0 +1,12 @@
// 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.Core.Features
{
public interface ITlsApplicationProtocolFeature
{
ReadOnlyMemory<byte> ApplicationProtocol { get; }
}
}

View File

@ -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.Core
{
[Flags]
public enum HttpProtocols
{
None = 0x0,
Http1 = 0x1,
Http2 = 0x2,
Http1AndHttp2 = Http1 | Http2,
}
}

View File

@ -0,0 +1,94 @@
// 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.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core;
namespace Microsoft.AspNetCore.Server.Kestrel.Https
{
/// <summary>
/// Settings for how Kestrel should handle HTTPS connections.
/// </summary>
public class HttpsConnectionAdapterOptions
{
private TimeSpan _handshakeTimeout;
/// <summary>
/// Initializes a new instance of <see cref="HttpsConnectionAdapterOptions"/>.
/// </summary>
public HttpsConnectionAdapterOptions()
{
ClientCertificateMode = ClientCertificateMode.NoCertificate;
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11;
HandshakeTimeout = TimeSpan.FromSeconds(10);
}
/// <summary>
/// <para>
/// Specifies the server certificate used to authenticate HTTPS connections. This is ignored if ServerCertificateSelector is set.
/// </para>
/// <para>
/// If the server certificate has an Extended Key Usage extension, the usages must include Server Authentication (OID 1.3.6.1.5.5.7.3.1).
/// </para>
/// </summary>
public X509Certificate2 ServerCertificate { get; set; }
/// <summary>
/// <para>
/// A callback that will be invoked to dynamically select a server certificate. This is higher priority than ServerCertificate.
/// If SNI is not avialable then the name parameter will be null.
/// </para>
/// <para>
/// If the server certificate has an Extended Key Usage extension, the usages must include Server Authentication (OID 1.3.6.1.5.5.7.3.1).
/// </para>
/// </summary>
public Func<ConnectionContext, string, X509Certificate2> ServerCertificateSelector { get; set; }
/// <summary>
/// Specifies the client certificate requirements for a HTTPS connection. Defaults to <see cref="ClientCertificateMode.NoCertificate"/>.
/// </summary>
public ClientCertificateMode ClientCertificateMode { get; set; }
/// <summary>
/// Specifies a callback for additional client certificate validation that will be invoked during authentication.
/// </summary>
public Func<X509Certificate2, X509Chain, SslPolicyErrors, bool> ClientCertificateValidation { get; set; }
/// <summary>
/// Specifies allowable SSL protocols. Defaults to <see cref="SslProtocols.Tls12" /> and <see cref="SslProtocols.Tls11"/>.
/// </summary>
public SslProtocols SslProtocols { get; set; }
/// <summary>
/// The protocols enabled on this endpoint.
/// </summary>
/// <remarks>Defaults to HTTP/1.x only.</remarks>
internal HttpProtocols HttpProtocols { get; set; }
/// <summary>
/// Specifies whether the certificate revocation list is checked during authentication.
/// </summary>
public bool CheckCertificateRevocation { get; set; }
/// <summary>
/// Specifies the maximum amount of time allowed for the TLS/SSL handshake. This must be positive and finite.
/// </summary>
public TimeSpan HandshakeTimeout
{
get => _handshakeTimeout;
set
{
if (value <= TimeSpan.Zero && value != Timeout.InfiniteTimeSpan)
{
throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.PositiveTimeSpanRequired);
}
_handshakeTimeout = value != Timeout.InfiniteTimeSpan ? value : TimeSpan.MaxValue;
}
}
}
}

View File

@ -0,0 +1,20 @@
// 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 System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
internal class AddressBindContext
{
public ICollection<string> Addresses { get; set; }
public List<ListenOptions> ListenOptions { get; set; }
public KestrelServerOptions ServerOptions { get; set; }
public ILogger Logger { get; set; }
public Func<ListenOptions, Task> CreateBinding { get; set; }
}
}

View File

@ -0,0 +1,267 @@
// 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 System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
internal class AddressBinder
{
public static async Task BindAsync(IServerAddressesFeature addresses,
KestrelServerOptions serverOptions,
ILogger logger,
Func<ListenOptions, Task> createBinding)
{
var listenOptions = serverOptions.ListenOptions;
var strategy = CreateStrategy(
listenOptions.ToArray(),
addresses.Addresses.ToArray(),
addresses.PreferHostingUrls);
var context = new AddressBindContext
{
Addresses = addresses.Addresses,
ListenOptions = listenOptions,
ServerOptions = serverOptions,
Logger = logger,
CreateBinding = createBinding
};
// reset options. The actual used options and addresses will be populated
// by the address binding feature
listenOptions.Clear();
addresses.Addresses.Clear();
await strategy.BindAsync(context).ConfigureAwait(false);
}
private static IStrategy CreateStrategy(ListenOptions[] listenOptions, string[] addresses, bool preferAddresses)
{
var hasListenOptions = listenOptions.Length > 0;
var hasAddresses = addresses.Length > 0;
if (preferAddresses && hasAddresses)
{
if (hasListenOptions)
{
return new OverrideWithAddressesStrategy(addresses);
}
return new AddressesStrategy(addresses);
}
else if (hasListenOptions)
{
if (hasAddresses)
{
return new OverrideWithEndpointsStrategy(listenOptions, addresses);
}
return new EndpointsStrategy(listenOptions);
}
else if (hasAddresses)
{
// If no endpoints are configured directly using KestrelServerOptions, use those configured via the IServerAddressesFeature.
return new AddressesStrategy(addresses);
}
else
{
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
return new DefaultAddressStrategy();
}
}
/// <summary>
/// Returns an <see cref="IPEndPoint"/> for the given host an port.
/// If the host parameter isn't "localhost" or an IP address, use IPAddress.Any.
/// </summary>
protected internal static bool TryCreateIPEndPoint(ServerAddress address, out IPEndPoint endpoint)
{
if (!IPAddress.TryParse(address.Host, out var ip))
{
endpoint = null;
return false;
}
endpoint = new IPEndPoint(ip, address.Port);
return true;
}
internal static async Task BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
{
try
{
await context.CreateBinding(endpoint).ConfigureAwait(false);
}
catch (AddressInUseException ex)
{
throw new IOException(CoreStrings.FormatEndpointAlreadyInUse(endpoint), ex);
}
context.ListenOptions.Add(endpoint);
}
internal static ListenOptions ParseAddress(string address, out bool https)
{
var parsedAddress = ServerAddress.FromUrl(address);
https = false;
if (parsedAddress.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
{
https = true;
}
else if (!parsedAddress.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(CoreStrings.FormatUnsupportedAddressScheme(address));
}
if (!string.IsNullOrEmpty(parsedAddress.PathBase))
{
throw new InvalidOperationException(CoreStrings.FormatConfigurePathBaseFromMethodCall($"{nameof(IApplicationBuilder)}.UsePathBase()"));
}
ListenOptions options = null;
if (parsedAddress.IsUnixPipe)
{
options = new ListenOptions(parsedAddress.UnixPipePath);
}
else if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase))
{
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
options = new LocalhostListenOptions(parsedAddress.Port);
}
else if (TryCreateIPEndPoint(parsedAddress, out var endpoint))
{
options = new ListenOptions(endpoint);
}
else
{
// when address is 'http://hostname:port', 'http://*:port', or 'http://+:port'
options = new AnyIPListenOptions(parsedAddress.Port);
}
return options;
}
private interface IStrategy
{
Task BindAsync(AddressBindContext context);
}
private class DefaultAddressStrategy : IStrategy
{
public async Task BindAsync(AddressBindContext context)
{
var httpDefault = ParseAddress(Constants.DefaultServerAddress, out var https);
context.ServerOptions.ApplyEndpointDefaults(httpDefault);
await httpDefault.BindAsync(context).ConfigureAwait(false);
// Conditional https default, only if a cert is available
var httpsDefault = ParseAddress(Constants.DefaultServerHttpsAddress, out https);
context.ServerOptions.ApplyEndpointDefaults(httpsDefault);
if (httpsDefault.ConnectionAdapters.Any(f => f.IsHttps)
|| httpsDefault.TryUseHttps())
{
await httpsDefault.BindAsync(context).ConfigureAwait(false);
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddresses,
Constants.DefaultServerAddress, Constants.DefaultServerHttpsAddress);
}
else
{
// No default cert is available, do not bind to the https endpoint.
context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress);
}
}
}
private class OverrideWithAddressesStrategy : AddressesStrategy
{
public OverrideWithAddressesStrategy(IReadOnlyCollection<string> addresses)
: base(addresses)
{
}
public override Task BindAsync(AddressBindContext context)
{
var joined = string.Join(", ", _addresses);
context.Logger.LogInformation(CoreStrings.OverridingWithPreferHostingUrls, nameof(IServerAddressesFeature.PreferHostingUrls), joined);
return base.BindAsync(context);
}
}
private class OverrideWithEndpointsStrategy : EndpointsStrategy
{
private readonly string[] _originalAddresses;
public OverrideWithEndpointsStrategy(IReadOnlyCollection<ListenOptions> endpoints, string[] originalAddresses)
: base(endpoints)
{
_originalAddresses = originalAddresses;
}
public override Task BindAsync(AddressBindContext context)
{
var joined = string.Join(", ", _originalAddresses);
context.Logger.LogWarning(CoreStrings.OverridingWithKestrelOptions, joined, "UseKestrel()");
return base.BindAsync(context);
}
}
private class EndpointsStrategy : IStrategy
{
private readonly IReadOnlyCollection<ListenOptions> _endpoints;
public EndpointsStrategy(IReadOnlyCollection<ListenOptions> endpoints)
{
_endpoints = endpoints;
}
public virtual async Task BindAsync(AddressBindContext context)
{
foreach (var endpoint in _endpoints)
{
await endpoint.BindAsync(context).ConfigureAwait(false);
}
}
}
private class AddressesStrategy : IStrategy
{
protected readonly IReadOnlyCollection<string> _addresses;
public AddressesStrategy(IReadOnlyCollection<string> addresses)
{
_addresses = addresses;
}
public virtual async Task BindAsync(AddressBindContext context)
{
foreach (var address in _addresses)
{
var options = ParseAddress(address, out var https);
context.ServerOptions.ApplyEndpointDefaults(options);
if (https && !options.ConnectionAdapters.Any(f => f.IsHttps))
{
options.UseHttps();
}
await options.BindAsync(context).ConfigureAwait(false);
}
}
}
}
}

View File

@ -0,0 +1,125 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace System.Buffers
{
internal ref struct BufferReader
{
private ReadOnlySpan<byte> _currentSpan;
private int _index;
private ReadOnlySequence<byte> _sequence;
private SequencePosition _currentSequencePosition;
private SequencePosition _nextSequencePosition;
private int _consumedBytes;
private bool _end;
public BufferReader(ReadOnlySequence<byte> buffer)
{
_end = false;
_index = 0;
_consumedBytes = 0;
_sequence = buffer;
_currentSequencePosition = _sequence.Start;
_nextSequencePosition = _currentSequencePosition;
_currentSpan = ReadOnlySpan<byte>.Empty;
MoveNext();
}
public bool End => _end;
public int CurrentSegmentIndex => _index;
public SequencePosition Position => _sequence.GetPosition(_index, _currentSequencePosition);
public ReadOnlySpan<byte> CurrentSegment => _currentSpan;
public ReadOnlySpan<byte> UnreadSegment => _currentSpan.Slice(_index);
public int ConsumedBytes => _consumedBytes;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Peek()
{
if (_end)
{
return -1;
}
return _currentSpan[_index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Read()
{
if (_end)
{
return -1;
}
var value = _currentSpan[_index];
_index++;
_consumedBytes++;
if (_index >= _currentSpan.Length)
{
MoveNext();
}
return value;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void MoveNext()
{
var previous = _nextSequencePosition;
while (_sequence.TryGet(ref _nextSequencePosition, out var memory, true))
{
_currentSequencePosition = previous;
_currentSpan = memory.Span;
_index = 0;
if (_currentSpan.Length > 0)
{
return;
}
}
_end = true;
}
public void Advance(int byteCount)
{
if (byteCount < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
}
_consumedBytes += byteCount;
while (!_end && byteCount > 0)
{
if ((_index + byteCount) < _currentSpan.Length)
{
_index += byteCount;
byteCount = 0;
break;
}
var remaining = (_currentSpan.Length - _index);
_index += remaining;
byteCount -= remaining;
MoveNext();
}
if (byteCount > 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
}
}
}
}

View File

@ -0,0 +1,96 @@
// 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.Runtime.CompilerServices;
namespace System.Buffers
{
internal ref struct BufferWriter<T> where T : IBufferWriter<byte>
{
private T _output;
private Span<byte> _span;
private int _buffered;
private long _bytesCommitted;
public BufferWriter(T output)
{
_buffered = 0;
_bytesCommitted = 0;
_output = output;
_span = output.GetSpan();
}
public Span<byte> Span => _span;
public long BytesCommitted => _bytesCommitted;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Commit()
{
var buffered = _buffered;
if (buffered > 0)
{
_bytesCommitted += buffered;
_buffered = 0;
_output.Advance(buffered);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Advance(int count)
{
_buffered += count;
_span = _span.Slice(count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> source)
{
if (_span.Length >= source.Length)
{
source.CopyTo(_span);
Advance(source.Length);
}
else
{
WriteMultiBuffer(source);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Ensure(int count = 1)
{
if (_span.Length < count)
{
EnsureMore(count);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void EnsureMore(int count = 0)
{
if (_buffered > 0)
{
Commit();
}
_output.GetMemory(count);
_span = _output.GetSpan();
}
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
{
while (source.Length > 0)
{
if (_span.Length == 0)
{
EnsureMore();
}
var writable = Math.Min(source.Length, _span.Length);
source.Slice(0, writable).CopyTo(_span);
source = source.Slice(writable);
Advance(writable);
}
}
}
}

View File

@ -0,0 +1,97 @@
// 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.Linq;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Server.Kestrel.Core;
namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
{
public static class CertificateLoader
{
// See http://oid-info.com/get/1.3.6.1.5.5.7.3.1
// Indicates that a certificate can be used as a SSL server certificate
private const string ServerAuthenticationOid = "1.3.6.1.5.5.7.3.1";
public static X509Certificate2 LoadFromStoreCert(string subject, string storeName, StoreLocation storeLocation, bool allowInvalid)
{
using (var store = new X509Store(storeName, storeLocation))
{
X509Certificate2Collection storeCertificates = null;
X509Certificate2 foundCertificate = null;
try
{
store.Open(OpenFlags.ReadOnly);
storeCertificates = store.Certificates;
var foundCertificates = storeCertificates.Find(X509FindType.FindBySubjectName, subject, !allowInvalid);
foundCertificate = foundCertificates
.OfType<X509Certificate2>()
.Where(IsCertificateAllowedForServerAuth)
.OrderByDescending(certificate => certificate.NotAfter)
.FirstOrDefault();
if (foundCertificate == null)
{
throw new InvalidOperationException(CoreStrings.FormatCertNotFoundInStore(subject, storeLocation, storeName, allowInvalid));
}
return foundCertificate;
}
finally
{
DisposeCertificates(storeCertificates, except: foundCertificate);
}
}
}
internal static bool IsCertificateAllowedForServerAuth(X509Certificate2 certificate)
{
/* If the Extended Key Usage extension is included, then we check that the serverAuth usage is included. (http://oid-info.com/get/1.3.6.1.5.5.7.3.1)
* If the Extended Key Usage extension is not included, then we assume the certificate is allowed for all usages.
*
* See also https://blogs.msdn.microsoft.com/kaushal/2012/02/17/client-certificates-vs-server-certificates/
*
* From https://tools.ietf.org/html/rfc3280#section-4.2.1.13 "Certificate Extensions: Extended Key Usage"
*
* If the (Extended Key Usage) extension is present, then the certificate MUST only be used
* for one of the purposes indicated. If multiple purposes are
* indicated the application need not recognize all purposes indicated,
* as long as the intended purpose is present. Certificate using
* applications MAY require that a particular purpose be indicated in
* order for the certificate to be acceptable to that application.
*/
var hasEkuExtension = false;
foreach (var extension in certificate.Extensions.OfType<X509EnhancedKeyUsageExtension>())
{
hasEkuExtension = true;
foreach (var oid in extension.EnhancedKeyUsages)
{
if (oid.Value.Equals(ServerAuthenticationOid, StringComparison.Ordinal))
{
return true;
}
}
}
return !hasEkuExtension;
}
private static void DisposeCertificates(X509Certificate2Collection certificates, X509Certificate2 except)
{
if (certificates != null)
{
foreach (var certificate in certificates)
{
if (!certificate.Equals(except))
{
certificate.Dispose();
}
}
}
}
}
}

View File

@ -0,0 +1,68 @@
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
{
internal class ClosedStream : Stream
{
private static readonly Task<int> ZeroResultTask = Task.FromResult(result: 0);
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length
{
get
{
throw new NotSupportedException();
}
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override void Flush()
{
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
return 0;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return ZeroResultTask;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
}

View File

@ -0,0 +1,139 @@
// 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.Extensions.Configuration;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
internal class ConfigurationReader
{
private IConfiguration _configuration;
private IDictionary<string, CertificateConfig> _certificates;
private IList<EndpointConfig> _endpoints;
public ConfigurationReader(IConfiguration configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
public IDictionary<string, CertificateConfig> Certificates
{
get
{
if (_certificates == null)
{
ReadCertificates();
}
return _certificates;
}
}
public IEnumerable<EndpointConfig> Endpoints
{
get
{
if (_endpoints == null)
{
ReadEndpoints();
}
return _endpoints;
}
}
private void ReadCertificates()
{
_certificates = new Dictionary<string, CertificateConfig>(0);
var certificatesConfig = _configuration.GetSection("Certificates").GetChildren();
foreach (var certificateConfig in certificatesConfig)
{
_certificates.Add(certificateConfig.Key, new CertificateConfig(certificateConfig));
}
}
private void ReadEndpoints()
{
_endpoints = new List<EndpointConfig>();
var endpointsConfig = _configuration.GetSection("Endpoints").GetChildren();
foreach (var endpointConfig in endpointsConfig)
{
// "EndpointName": {
        // "Url": "https://*:5463",
        // "Certificate": {
          // "Path": "testCert.pfx",
          // "Password": "testPassword"
       // }
// }
var url = endpointConfig["Url"];
if (string.IsNullOrEmpty(url))
{
throw new InvalidOperationException(CoreStrings.FormatEndpointMissingUrl(endpointConfig.Key));
}
var endpoint = new EndpointConfig()
{
Name = endpointConfig.Key,
Url = url,
ConfigSection = endpointConfig,
Certificate = new CertificateConfig(endpointConfig.GetSection("Certificate")),
};
_endpoints.Add(endpoint);
}
}
}
// "EndpointName": {
// "Url": "https://*:5463",
// "Certificate": {
// "Path": "testCert.pfx",
// "Password": "testPassword"
// }
// }
internal class EndpointConfig
{
public string Name { get; set; }
public string Url { get; set; }
public IConfigurationSection ConfigSection { get; set; }
public CertificateConfig Certificate { get; set; }
}
// "CertificateName": {
// "Path": "testCert.pfx",
// "Password": "testPassword"
// }
internal class CertificateConfig
{
public CertificateConfig(IConfigurationSection configSection)
{
ConfigSection = configSection;
ConfigSection.Bind(this);
}
public IConfigurationSection ConfigSection { get; }
// File
public bool IsFileCert => !string.IsNullOrEmpty(Path);
public string Path { get; set; }
public string Password { get; set; }
// Cert store
public bool IsStoreCert => !string.IsNullOrEmpty(Subject);
public string Subject { get; set; }
public string Store { get; set; }
public string Location { get; set; }
public bool? AllowInvalid { get; set; }
}
}

View File

@ -0,0 +1,113 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public class ConnectionDispatcher : IConnectionDispatcher
{
private readonly ServiceContext _serviceContext;
private readonly ConnectionDelegate _connectionDelegate;
public ConnectionDispatcher(ServiceContext serviceContext, ConnectionDelegate connectionDelegate)
{
_serviceContext = serviceContext;
_connectionDelegate = connectionDelegate;
}
private IKestrelTrace Log => _serviceContext.Log;
public void OnConnection(TransportConnection connection)
{
// REVIEW: Unfortunately, we still need to use the service context to create the pipes since the settings
// for the scheduler and limits are specified here
var inputOptions = GetInputPipeOptions(_serviceContext, connection.MemoryPool, connection.InputWriterScheduler);
var outputOptions = GetOutputPipeOptions(_serviceContext, connection.MemoryPool, connection.OutputReaderScheduler);
var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions);
// Set the transport and connection id
connection.ConnectionId = CorrelationIdGenerator.GetNextId();
connection.Transport = pair.Transport;
// This *must* be set before returning from OnConnection
connection.Application = pair.Application;
// REVIEW: This task should be tracked by the server for graceful shutdown
// Today it's handled specifically for http but not for aribitrary middleware
_ = Execute(connection);
}
private async Task Execute(ConnectionContext connectionContext)
{
using (BeginConnectionScope(connectionContext))
{
try
{
await _connectionDelegate(connectionContext);
}
catch (Exception ex)
{
Log.LogCritical(0, ex, $"{nameof(ConnectionDispatcher)}.{nameof(Execute)}() {connectionContext.ConnectionId}");
}
}
}
private IDisposable BeginConnectionScope(ConnectionContext connectionContext)
{
if (Log.IsEnabled(LogLevel.Critical))
{
return Log.BeginScope(new ConnectionLogScope(connectionContext.ConnectionId));
}
return null;
}
// Internal for testing
internal static PipeOptions GetInputPipeOptions(ServiceContext serviceContext, MemoryPool<byte> memoryPool, PipeScheduler writerScheduler) => new PipeOptions
(
pool: memoryPool,
readerScheduler: serviceContext.Scheduler,
writerScheduler: writerScheduler,
pauseWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0,
resumeWriterThreshold: serviceContext.ServerOptions.Limits.MaxRequestBufferSize ?? 0,
useSynchronizationContext: false,
minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize
);
internal static PipeOptions GetOutputPipeOptions(ServiceContext serviceContext, MemoryPool<byte> memoryPool, PipeScheduler readerScheduler) => new PipeOptions
(
pool: memoryPool,
readerScheduler: readerScheduler,
writerScheduler: serviceContext.Scheduler,
pauseWriterThreshold: GetOutputResponseBufferSize(serviceContext),
resumeWriterThreshold: GetOutputResponseBufferSize(serviceContext),
useSynchronizationContext: false,
minimumSegmentSize: KestrelMemoryPool.MinimumSegmentSize
);
private static long GetOutputResponseBufferSize(ServiceContext serviceContext)
{
var bufferSize = serviceContext.ServerOptions.Limits.MaxResponseBufferSize;
if (bufferSize == 0)
{
// 0 = no buffering so we need to configure the pipe so the the writer waits on the reader directly
return 1;
}
// null means that we have no back pressure
return bufferSize ?? 0;
}
}
}

View File

@ -0,0 +1,74 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public class ConnectionLimitMiddleware
{
private readonly ConnectionDelegate _next;
private readonly ResourceCounter _concurrentConnectionCounter;
private readonly IKestrelTrace _trace;
public ConnectionLimitMiddleware(ConnectionDelegate next, long connectionLimit, IKestrelTrace trace)
: this(next, ResourceCounter.Quota(connectionLimit), trace)
{
}
// For Testing
internal ConnectionLimitMiddleware(ConnectionDelegate next, ResourceCounter concurrentConnectionCounter, IKestrelTrace trace)
{
_next = next;
_concurrentConnectionCounter = concurrentConnectionCounter;
_trace = trace;
}
public async Task OnConnectionAsync(ConnectionContext connection)
{
if (!_concurrentConnectionCounter.TryLockOne())
{
KestrelEventSource.Log.ConnectionRejected(connection.ConnectionId);
_trace.ConnectionRejected(connection.ConnectionId);
connection.Transport.Input.Complete();
connection.Transport.Output.Complete();
return;
}
var releasor = new ConnectionReleasor(_concurrentConnectionCounter);
try
{
connection.Features.Set<IDecrementConcurrentConnectionCountFeature>(releasor);
await _next(connection);
}
finally
{
releasor.ReleaseConnection();
}
}
private class ConnectionReleasor : IDecrementConcurrentConnectionCountFeature
{
private readonly ResourceCounter _concurrentConnectionCounter;
private bool _connectionReleased;
public ConnectionReleasor(ResourceCounter normalConnectionCounter)
{
_concurrentConnectionCounter = normalConnectionCounter;
}
public void ReleaseConnection()
{
if (!_connectionReleased)
{
_connectionReleased = true;
_concurrentConnectionCounter.ReleaseOne();
}
}
}
}
}

View File

@ -0,0 +1,63 @@
// 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.Globalization;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
{
public class ConnectionLogScope : IReadOnlyList<KeyValuePair<string, object>>
{
private readonly string _connectionId;
private string _cachedToString;
public ConnectionLogScope(string connectionId)
{
_connectionId = connectionId;
}
public KeyValuePair<string, object> this[int index]
{
get
{
if (index == 0)
{
return new KeyValuePair<string, object>("ConnectionId", _connectionId);
}
throw new ArgumentOutOfRangeException(nameof(index));
}
}
public int Count => 1;
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
for (int i = 0; i < Count; ++i)
{
yield return this[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public override string ToString()
{
if (_cachedToString == null)
{
_cachedToString = string.Format(
CultureInfo.InvariantCulture,
"ConnectionId:{0}",
_connectionId);
}
return _cachedToString;
}
}
}

View File

@ -0,0 +1,41 @@
using System.Buffers;
namespace System.IO.Pipelines
{
internal class DuplexPipe : IDuplexPipe
{
public DuplexPipe(PipeReader reader, PipeWriter writer)
{
Input = reader;
Output = writer;
}
public PipeReader Input { get; }
public PipeWriter Output { get; }
public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions)
{
var input = new Pipe(inputOptions);
var output = new Pipe(outputOptions);
var transportToApplication = new DuplexPipe(output.Reader, input.Writer);
var applicationToTransport = new DuplexPipe(input.Reader, output.Writer);
return new DuplexPipePair(applicationToTransport, transportToApplication);
}
// This class exists to work around issues with value tuple on .NET Framework
public readonly struct DuplexPipePair
{
public IDuplexPipe Transport { get; }
public IDuplexPipe Application { get; }
public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application)
{
Transport = transport;
Application = application;
}
}
}
}

View File

@ -0,0 +1,63 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Text;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
internal static class ChunkWriter
{
private static readonly ArraySegment<byte> _endChunkBytes = CreateAsciiByteArraySegment("\r\n");
private static readonly byte[] _hex = Encoding.ASCII.GetBytes("0123456789abcdef");
private static ArraySegment<byte> CreateAsciiByteArraySegment(string text)
{
var bytes = Encoding.ASCII.GetBytes(text);
return new ArraySegment<byte>(bytes);
}
public static ArraySegment<byte> BeginChunkBytes(int dataCount)
{
var bytes = new byte[10]
{
_hex[((dataCount >> 0x1c) & 0x0f)],
_hex[((dataCount >> 0x18) & 0x0f)],
_hex[((dataCount >> 0x14) & 0x0f)],
_hex[((dataCount >> 0x10) & 0x0f)],
_hex[((dataCount >> 0x0c) & 0x0f)],
_hex[((dataCount >> 0x08) & 0x0f)],
_hex[((dataCount >> 0x04) & 0x0f)],
_hex[((dataCount >> 0x00) & 0x0f)],
(byte)'\r',
(byte)'\n',
};
// Determine the most-significant non-zero nibble
int total, shift;
total = (dataCount > 0xffff) ? 0x10 : 0x00;
dataCount >>= total;
shift = (dataCount > 0x00ff) ? 0x08 : 0x00;
dataCount >>= shift;
total |= shift;
total |= (dataCount > 0x000f) ? 0x04 : 0x00;
var offset = 7 - (total >> 2);
return new ArraySegment<byte>(bytes, offset, 10 - offset);
}
internal static int WriteBeginChunkBytes(ref BufferWriter<PipeWriter> start, int dataCount)
{
var chunkSegment = BeginChunkBytes(dataCount);
start.Write(new ReadOnlySpan<byte>(chunkSegment.Array, chunkSegment.Offset, chunkSegment.Count));
return chunkSegment.Count;
}
internal static void WriteEndChunkBytes(ref BufferWriter<PipeWriter> start)
{
start.Write(new ReadOnlySpan<byte>(_endChunkBytes.Array, _endChunkBytes.Offset, _endChunkBytes.Count));
}
}
}

View File

@ -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.Core.Internal.Http
{
[Flags]
public enum ConnectionOptions
{
None = 0,
Close = 1,
KeepAlive = 2,
Upgrade = 4
}
}

View File

@ -0,0 +1,73 @@
// 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.Text;
using System.Threading;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
/// <summary>
/// Manages the generation of the date header value.
/// </summary>
public class DateHeaderValueManager : IHeartbeatHandler
{
private static readonly byte[] _datePreambleBytes = Encoding.ASCII.GetBytes("\r\nDate: ");
private DateHeaderValues _dateValues;
/// <summary>
/// Initializes a new instance of the <see cref="DateHeaderValueManager"/> class.
/// </summary>
public DateHeaderValueManager()
: this(systemClock: new SystemClock())
{
}
// Internal for testing
internal DateHeaderValueManager(ISystemClock systemClock)
{
SetDateValues(systemClock.UtcNow);
}
/// <summary>
/// Returns a value representing the current server date/time for use in the HTTP "Date" response header
/// in accordance with http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18
/// </summary>
/// <returns>The value in string and byte[] format.</returns>
public DateHeaderValues GetDateHeaderValues() => _dateValues;
// Called by the Timer (background) thread
public void OnHeartbeat(DateTimeOffset now)
{
SetDateValues(now);
}
/// <summary>
/// Sets date values from a provided ticks value
/// </summary>
/// <param name="value">A DateTimeOffset value</param>
private void SetDateValues(DateTimeOffset value)
{
var dateValue = HeaderUtilities.FormatDate(value);
var dateBytes = new byte[_datePreambleBytes.Length + dateValue.Length];
Buffer.BlockCopy(_datePreambleBytes, 0, dateBytes, 0, _datePreambleBytes.Length);
Encoding.ASCII.GetBytes(dateValue, 0, dateValue.Length, dateBytes, _datePreambleBytes.Length);
var dateValues = new DateHeaderValues()
{
Bytes = dateBytes,
String = dateValue
};
Volatile.Write(ref _dateValues, dateValues);
}
public class DateHeaderValues
{
public byte[] Bytes;
public string String;
}
}
}

View File

@ -0,0 +1,48 @@
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public partial class Http1Connection : IHttpUpgradeFeature
{
bool IHttpUpgradeFeature.IsUpgradableRequest => IsUpgradableRequest;
async Task<Stream> IHttpUpgradeFeature.UpgradeAsync()
{
if (!((IHttpUpgradeFeature)this).IsUpgradableRequest)
{
throw new InvalidOperationException(CoreStrings.CannotUpgradeNonUpgradableRequest);
}
if (IsUpgraded)
{
throw new InvalidOperationException(CoreStrings.UpgradeCannotBeCalledMultipleTimes);
}
if (!ServiceContext.ConnectionManager.UpgradedConnectionCount.TryLockOne())
{
throw new InvalidOperationException(CoreStrings.UpgradedConnectionLimitReached);
}
IsUpgraded = true;
ConnectionFeatures.Get<IDecrementConcurrentConnectionCountFeature>()?.ReleaseConnection();
StatusCode = StatusCodes.Status101SwitchingProtocols;
ReasonPhrase = "Switching Protocols";
ResponseHeaders["Connection"] = "Upgrade";
await FlushAsync(default(CancellationToken));
return _streams.Upgrade();
}
}
}

View File

@ -0,0 +1,527 @@
// 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.Buffers;
using System.Diagnostics;
using System.Globalization;
using System.IO.Pipelines;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Connections.Abstractions;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public partial class Http1Connection : HttpProtocol, IRequestProcessor
{
private const byte ByteAsterisk = (byte)'*';
private const byte ByteForwardSlash = (byte)'/';
private const string Asterisk = "*";
private readonly Http1ConnectionContext _context;
private readonly IHttpParser<Http1ParsingHandler> _parser;
protected readonly long _keepAliveTicks;
private readonly long _requestHeadersTimeoutTicks;
private volatile bool _requestTimedOut;
private uint _requestCount;
private HttpRequestTarget _requestTargetForm = HttpRequestTarget.Unknown;
private Uri _absoluteRequestTarget;
private int _remainingRequestHeadersBytesAllowed;
public Http1Connection(Http1ConnectionContext context)
: base(context)
{
_context = context;
_parser = ServiceContext.HttpParser;
_keepAliveTicks = ServerOptions.Limits.KeepAliveTimeout.Ticks;
_requestHeadersTimeoutTicks = ServerOptions.Limits.RequestHeadersTimeout.Ticks;
Output = new Http1OutputProducer(
_context.Transport.Output,
_context.ConnectionId,
_context.ConnectionContext,
_context.ServiceContext.Log,
_context.TimeoutControl,
_context.ConnectionFeatures.Get<IBytesWrittenFeature>());
}
public PipeReader Input => _context.Transport.Input;
public ITimeoutControl TimeoutControl => _context.TimeoutControl;
public bool RequestTimedOut => _requestTimedOut;
public override bool IsUpgradableRequest => _upgradeAvailable;
/// <summary>
/// Stops the request processing loop between requests.
/// Called on all active connections when the server wants to initiate a shutdown
/// and after a keep-alive timeout.
/// </summary>
public void StopProcessingNextRequest()
{
_keepAlive = false;
Input.CancelPendingRead();
}
public void SendTimeoutResponse()
{
_requestTimedOut = true;
Input.CancelPendingRead();
}
public void ParseRequest(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
{
consumed = buffer.Start;
examined = buffer.End;
switch (_requestProcessingStatus)
{
case RequestProcessingStatus.RequestPending:
if (buffer.IsEmpty)
{
break;
}
TimeoutControl.ResetTimeout(_requestHeadersTimeoutTicks, TimeoutAction.SendTimeoutResponse);
_requestProcessingStatus = RequestProcessingStatus.ParsingRequestLine;
goto case RequestProcessingStatus.ParsingRequestLine;
case RequestProcessingStatus.ParsingRequestLine:
if (TakeStartLine(buffer, out consumed, out examined))
{
buffer = buffer.Slice(consumed, buffer.End);
_requestProcessingStatus = RequestProcessingStatus.ParsingHeaders;
goto case RequestProcessingStatus.ParsingHeaders;
}
else
{
break;
}
case RequestProcessingStatus.ParsingHeaders:
if (TakeMessageHeaders(buffer, out consumed, out examined))
{
_requestProcessingStatus = RequestProcessingStatus.AppStarted;
}
break;
}
}
public bool TakeStartLine(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
{
var overLength = false;
if (buffer.Length >= ServerOptions.Limits.MaxRequestLineSize)
{
buffer = buffer.Slice(buffer.Start, ServerOptions.Limits.MaxRequestLineSize);
overLength = true;
}
var result = _parser.ParseRequestLine(new Http1ParsingHandler(this), buffer, out consumed, out examined);
if (!result && overLength)
{
BadHttpRequestException.Throw(RequestRejectionReason.RequestLineTooLong);
}
return result;
}
public bool TakeMessageHeaders(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
{
// Make sure the buffer is limited
bool overLength = false;
if (buffer.Length >= _remainingRequestHeadersBytesAllowed)
{
buffer = buffer.Slice(buffer.Start, _remainingRequestHeadersBytesAllowed);
// If we sliced it means the current buffer bigger than what we're
// allowed to look at
overLength = true;
}
var result = _parser.ParseHeaders(new Http1ParsingHandler(this), buffer, out consumed, out examined, out var consumedBytes);
_remainingRequestHeadersBytesAllowed -= consumedBytes;
if (!result && overLength)
{
BadHttpRequestException.Throw(RequestRejectionReason.HeadersExceedMaxTotalSize);
}
if (result)
{
TimeoutControl.CancelTimeout();
}
return result;
}
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
{
Debug.Assert(target.Length != 0, "Request target must be non-zero length");
var ch = target[0];
if (ch == ByteForwardSlash)
{
// origin-form.
// The most common form of request-target.
// https://tools.ietf.org/html/rfc7230#section-5.3.1
OnOriginFormTarget(method, version, target, path, query, customMethod, pathEncoded);
}
else if (ch == ByteAsterisk && target.Length == 1)
{
OnAsteriskFormTarget(method);
}
else if (target.GetKnownHttpScheme(out var scheme))
{
OnAbsoluteFormTarget(target, query);
}
else
{
// Assume anything else is considered authority form.
// FYI: this should be an edge case. This should only happen when
// a client mistakenly thinks this server is a proxy server.
OnAuthorityFormTarget(method, target);
}
Method = method;
if (method == HttpMethod.Custom)
{
_methodText = customMethod.GetAsciiStringNonNullCharacters();
}
_httpVersion = version;
Debug.Assert(RawTarget != null, "RawTarget was not set");
Debug.Assert(((IHttpRequestFeature)this).Method != null, "Method was not set");
Debug.Assert(Path != null, "Path was not set");
Debug.Assert(QueryString != null, "QueryString was not set");
Debug.Assert(HttpVersion != null, "HttpVersion was not set");
}
private void OnOriginFormTarget(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
{
Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /");
_requestTargetForm = HttpRequestTarget.OriginForm;
// URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
// Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
// then encoded/escaped to ASCII https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
string requestUrlPath = null;
string rawTarget = null;
try
{
// Read raw target before mutating memory.
rawTarget = target.GetAsciiStringNonNullCharacters();
if (pathEncoded)
{
// URI was encoded, unescape and then parse as UTF-8
// Disabling warning temporary
var pathLength = UrlDecoder.Decode(path, path);
// Removing dot segments must be done after unescaping. From RFC 3986:
//
// URI producing applications should percent-encode data octets that
// correspond to characters in the reserved set unless these characters
// are specifically allowed by the URI scheme to represent data in that
// component. If a reserved character is found in a URI component and
// no delimiting role is known for that character, then it must be
// interpreted as representing the data octet corresponding to that
// character's encoding in US-ASCII.
//
// https://tools.ietf.org/html/rfc3986#section-2.2
pathLength = PathNormalizer.RemoveDotSegments(path.Slice(0, pathLength));
requestUrlPath = GetUtf8String(path.Slice(0, pathLength));
}
else
{
var pathLength = PathNormalizer.RemoveDotSegments(path);
if (path.Length == pathLength && query.Length == 0)
{
// If no decoding was required, no dot segments were removed and
// there is no query, the request path is the same as the raw target
requestUrlPath = rawTarget;
}
else
{
requestUrlPath = path.Slice(0, pathLength).GetAsciiStringNonNullCharacters();
}
}
}
catch (InvalidOperationException)
{
ThrowRequestTargetRejected(target);
}
QueryString = query.GetAsciiStringNonNullCharacters();
RawTarget = rawTarget;
Path = requestUrlPath;
}
private void OnAuthorityFormTarget(HttpMethod method, Span<byte> target)
{
_requestTargetForm = HttpRequestTarget.AuthorityForm;
// This is not complete validation. It is just a quick scan for invalid characters
// but doesn't check that the target fully matches the URI spec.
for (var i = 0; i < target.Length; i++)
{
var ch = target[i];
if (!UriUtilities.IsValidAuthorityCharacter(ch))
{
ThrowRequestTargetRejected(target);
}
}
// The authority-form of request-target is only used for CONNECT
// requests (https://tools.ietf.org/html/rfc7231#section-4.3.6).
if (method != HttpMethod.Connect)
{
BadHttpRequestException.Throw(RequestRejectionReason.ConnectMethodRequired);
}
// When making a CONNECT request to establish a tunnel through one or
// more proxies, a client MUST send only the target URI's authority
// component (excluding any userinfo and its "@" delimiter) as the
// request-target.For example,
//
// CONNECT www.example.com:80 HTTP/1.1
//
// Allowed characters in the 'host + port' section of authority.
// See https://tools.ietf.org/html/rfc3986#section-3.2
RawTarget = target.GetAsciiStringNonNullCharacters();
Path = string.Empty;
QueryString = string.Empty;
}
private void OnAsteriskFormTarget(HttpMethod method)
{
_requestTargetForm = HttpRequestTarget.AsteriskForm;
// The asterisk-form of request-target is only used for a server-wide
// OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7).
if (method != HttpMethod.Options)
{
BadHttpRequestException.Throw(RequestRejectionReason.OptionsMethodRequired);
}
RawTarget = Asterisk;
Path = string.Empty;
QueryString = string.Empty;
}
private void OnAbsoluteFormTarget(Span<byte> target, Span<byte> query)
{
_requestTargetForm = HttpRequestTarget.AbsoluteForm;
// absolute-form
// https://tools.ietf.org/html/rfc7230#section-5.3.2
// This code should be the edge-case.
// From the spec:
// a server MUST accept the absolute-form in requests, even though
// HTTP/1.1 clients will only send them in requests to proxies.
RawTarget = target.GetAsciiStringNonNullCharacters();
// Validation of absolute URIs is slow, but clients
// should not be sending this form anyways, so perf optimization
// not high priority
if (!Uri.TryCreate(RawTarget, UriKind.Absolute, out var uri))
{
ThrowRequestTargetRejected(target);
}
_absoluteRequestTarget = uri;
Path = uri.LocalPath;
// don't use uri.Query because we need the unescaped version
QueryString = query.GetAsciiStringNonNullCharacters();
}
private static unsafe string GetUtf8String(Span<byte> path)
{
// .NET 451 doesn't have pointer overloads for Encoding.GetString so we
// copy to an array
fixed (byte* pointer = &MemoryMarshal.GetReference(path))
{
return Encoding.UTF8.GetString(pointer, path.Length);
}
}
internal void EnsureHostHeaderExists()
{
// https://tools.ietf.org/html/rfc7230#section-5.4
// A server MUST respond with a 400 (Bad Request) status code to any
// HTTP/1.1 request message that lacks a Host header field and to any
// request message that contains more than one Host header field or a
// Host header field with an invalid field-value.
var hostCount = HttpRequestHeaders.HostCount;
var hostText = HttpRequestHeaders.HeaderHost.ToString();
if (hostCount <= 0)
{
if (_httpVersion == Http.HttpVersion.Http10)
{
return;
}
BadHttpRequestException.Throw(RequestRejectionReason.MissingHostHeader);
}
else if (hostCount > 1)
{
BadHttpRequestException.Throw(RequestRejectionReason.MultipleHostHeaders);
}
else if (_requestTargetForm != HttpRequestTarget.OriginForm)
{
// Tail call
ValidateNonOrginHostHeader(hostText);
}
else
{
// Tail call
HttpUtilities.ValidateHostHeader(hostText);
}
}
private void ValidateNonOrginHostHeader(string hostText)
{
if (_requestTargetForm == HttpRequestTarget.AuthorityForm)
{
if (hostText != RawTarget)
{
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}
else if (_requestTargetForm == HttpRequestTarget.AbsoluteForm)
{
// If the target URI includes an authority component, then a
// client MUST send a field - value for Host that is identical to that
// authority component, excluding any userinfo subcomponent and its "@"
// delimiter.
// System.Uri doesn't not tell us if the port was in the original string or not.
// When IsDefaultPort = true, we will allow Host: with or without the default port
if (hostText != _absoluteRequestTarget.Authority)
{
if (!_absoluteRequestTarget.IsDefaultPort
|| hostText != _absoluteRequestTarget.Authority + ":" + _absoluteRequestTarget.Port.ToString(CultureInfo.InvariantCulture))
{
BadHttpRequestException.Throw(RequestRejectionReason.InvalidHostHeader, hostText);
}
}
}
// Tail call
HttpUtilities.ValidateHostHeader(hostText);
}
protected override void OnReset()
{
ResetIHttpUpgradeFeature();
_requestTimedOut = false;
_requestTargetForm = HttpRequestTarget.Unknown;
_absoluteRequestTarget = null;
_remainingRequestHeadersBytesAllowed = ServerOptions.Limits.MaxRequestHeadersTotalSize + 2;
_requestCount++;
}
protected override void OnRequestProcessingEnding()
{
Input.Complete();
}
protected override string CreateRequestId()
=> StringUtilities.ConcatAsHexSuffix(ConnectionId, ':', _requestCount);
protected override MessageBody CreateMessageBody()
=> Http1MessageBody.For(_httpVersion, HttpRequestHeaders, this);
protected override void BeginRequestProcessing()
{
// Reset the features and timeout.
Reset();
TimeoutControl.SetTimeout(_keepAliveTicks, TimeoutAction.StopProcessingNextRequest);
}
protected override bool BeginRead(out ValueTask<ReadResult> awaitable)
{
awaitable = Input.ReadAsync();
return true;
}
protected override bool TryParseRequest(ReadResult result, out bool endConnection)
{
var examined = result.Buffer.End;
var consumed = result.Buffer.End;
try
{
ParseRequest(result.Buffer, out consumed, out examined);
}
catch (InvalidOperationException)
{
if (_requestProcessingStatus == RequestProcessingStatus.ParsingHeaders)
{
BadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders);
}
throw;
}
finally
{
Input.AdvanceTo(consumed, examined);
}
if (result.IsCompleted)
{
switch (_requestProcessingStatus)
{
case RequestProcessingStatus.RequestPending:
endConnection = true;
return true;
case RequestProcessingStatus.ParsingRequestLine:
BadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestLine);
break;
case RequestProcessingStatus.ParsingHeaders:
BadHttpRequestException.Throw(RequestRejectionReason.MalformedRequestInvalidHeaders);
break;
}
}
else if (!_keepAlive && _requestProcessingStatus == RequestProcessingStatus.RequestPending)
{
// Stop the request processing loop if the server is shutting down or there was a keep-alive timeout
// and there is no ongoing request.
endConnection = true;
return true;
}
else if (RequestTimedOut)
{
// In this case, there is an ongoing request but the start line/header parsing has timed out, so send
// a 408 response.
BadHttpRequestException.Throw(RequestRejectionReason.RequestHeadersTimeout);
}
endConnection = false;
if (_requestProcessingStatus == RequestProcessingStatus.AppStarted)
{
EnsureHostHeaderExists();
return true;
}
else
{
return false;
}
}
}
}

View File

@ -0,0 +1,26 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Net;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public class Http1ConnectionContext : IHttpProtocolContext
{
public string ConnectionId { get; set; }
public ServiceContext ServiceContext { get; set; }
public ConnectionContext ConnectionContext { get; set; }
public IFeatureCollection ConnectionFeatures { get; set; }
public MemoryPool<byte> MemoryPool { get; set; }
public IPEndPoint RemoteEndPoint { get; set; }
public IPEndPoint LocalEndPoint { get; set; }
public ITimeoutControl TimeoutControl { get; set; }
public IDuplexPipe Transport { get; set; }
public IDuplexPipe Application { get; set; }
}
}

View File

@ -0,0 +1,716 @@
// 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.Buffers;
using System.IO;
using System.IO.Pipelines;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public abstract class Http1MessageBody : MessageBody
{
private readonly Http1Connection _context;
private volatile bool _canceled;
private Task _pumpTask;
protected Http1MessageBody(Http1Connection context)
: base(context)
{
_context = context;
}
private async Task PumpAsync()
{
Exception error = null;
try
{
var awaitable = _context.Input.ReadAsync();
if (!awaitable.IsCompleted)
{
TryProduceContinue();
}
TryStartTimingReads();
while (true)
{
var result = await awaitable;
if (_context.RequestTimedOut)
{
BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTimeout);
}
var readableBuffer = result.Buffer;
var consumed = readableBuffer.Start;
var examined = readableBuffer.End;
try
{
if (_canceled)
{
break;
}
if (!readableBuffer.IsEmpty)
{
bool done;
done = Read(readableBuffer, _context.RequestBodyPipe.Writer, out consumed, out examined);
var writeAwaitable = _context.RequestBodyPipe.Writer.FlushAsync();
var backpressure = false;
if (!writeAwaitable.IsCompleted)
{
// Backpressure, stop controlling incoming data rate until data is read.
backpressure = true;
TryPauseTimingReads();
}
await writeAwaitable;
if (backpressure)
{
TryResumeTimingReads();
}
if (done)
{
break;
}
}
// Read() will have already have greedily consumed the entire request body if able.
if (result.IsCompleted)
{
// Treat any FIN from an upgraded request as expected.
// It's up to higher-level consumer (i.e. WebSocket middleware) to determine
// if the end is actually expected based on higher-level framing.
if (RequestUpgrade)
{
break;
}
BadHttpRequestException.Throw(RequestRejectionReason.UnexpectedEndOfRequestContent);
}
}
finally
{
_context.Input.AdvanceTo(consumed, examined);
}
awaitable = _context.Input.ReadAsync();
}
}
catch (Exception ex)
{
error = ex;
}
finally
{
_context.RequestBodyPipe.Writer.Complete(error);
TryStopTimingReads();
}
}
public override Task StopAsync()
{
if (!_context.HasStartedConsumingRequestBody)
{
return Task.CompletedTask;
}
_canceled = true;
_context.Input.CancelPendingRead();
return _pumpTask;
}
protected override Task OnConsumeAsync()
{
try
{
if (_context.RequestBodyPipe.Reader.TryRead(out var readResult))
{
_context.RequestBodyPipe.Reader.AdvanceTo(readResult.Buffer.End);
if (readResult.IsCompleted)
{
return Task.CompletedTask;
}
}
}
catch (BadHttpRequestException ex)
{
// At this point, the response has already been written, so this won't result in a 4XX response;
// however, we still need to stop the request processing loop and log.
_context.SetBadRequestState(ex);
return Task.CompletedTask;
}
return OnConsumeAsyncAwaited();
}
private async Task OnConsumeAsyncAwaited()
{
Log.RequestBodyNotEntirelyRead(_context.ConnectionIdFeature, _context.TraceIdentifier);
_context.TimeoutControl.SetTimeout(Constants.RequestBodyDrainTimeout.Ticks, TimeoutAction.AbortConnection);
try
{
ReadResult result;
do
{
result = await _context.RequestBodyPipe.Reader.ReadAsync();
_context.RequestBodyPipe.Reader.AdvanceTo(result.Buffer.End);
} while (!result.IsCompleted);
}
catch (BadHttpRequestException ex)
{
_context.SetBadRequestState(ex);
}
catch (ConnectionAbortedException)
{
Log.RequestBodyDrainTimedOut(_context.ConnectionIdFeature, _context.TraceIdentifier);
}
finally
{
_context.TimeoutControl.CancelTimeout();
}
}
protected void Copy(ReadOnlySequence<byte> readableBuffer, PipeWriter writableBuffer)
{
_context.TimeoutControl.BytesRead(readableBuffer.Length);
if (readableBuffer.IsSingleSegment)
{
writableBuffer.Write(readableBuffer.First.Span);
}
else
{
foreach (var memory in readableBuffer)
{
writableBuffer.Write(memory.Span);
}
}
}
protected override void OnReadStarted()
{
_pumpTask = PumpAsync();
}
protected virtual bool Read(ReadOnlySequence<byte> readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
throw new NotImplementedException();
}
private void TryStartTimingReads()
{
if (!RequestUpgrade)
{
Log.RequestBodyStart(_context.ConnectionIdFeature, _context.TraceIdentifier);
_context.TimeoutControl.StartTimingReads();
}
}
private void TryPauseTimingReads()
{
if (!RequestUpgrade)
{
_context.TimeoutControl.PauseTimingReads();
}
}
private void TryResumeTimingReads()
{
if (!RequestUpgrade)
{
_context.TimeoutControl.ResumeTimingReads();
}
}
private void TryStopTimingReads()
{
if (!RequestUpgrade)
{
Log.RequestBodyDone(_context.ConnectionIdFeature, _context.TraceIdentifier);
_context.TimeoutControl.StopTimingReads();
}
}
public static MessageBody For(
HttpVersion httpVersion,
HttpRequestHeaders headers,
Http1Connection context)
{
// see also http://tools.ietf.org/html/rfc2616#section-4.4
var keepAlive = httpVersion != HttpVersion.Http10;
var upgrade = false;
if (headers.HasConnection)
{
var connectionOptions = HttpHeaders.ParseConnection(headers.HeaderConnection);
upgrade = (connectionOptions & ConnectionOptions.Upgrade) == ConnectionOptions.Upgrade;
keepAlive = (connectionOptions & ConnectionOptions.KeepAlive) == ConnectionOptions.KeepAlive;
}
if (upgrade)
{
if (headers.HeaderTransferEncoding.Count > 0 || (headers.ContentLength.HasValue && headers.ContentLength.Value != 0))
{
BadHttpRequestException.Throw(RequestRejectionReason.UpgradeRequestCannotHavePayload);
}
return new ForUpgrade(context);
}
if (headers.HasTransferEncoding)
{
var transferEncoding = headers.HeaderTransferEncoding;
var transferCoding = HttpHeaders.GetFinalTransferCoding(transferEncoding);
// https://tools.ietf.org/html/rfc7230#section-3.3.3
// If a Transfer-Encoding header field
// is present in a request and the chunked transfer coding is not
// the final encoding, the message body length cannot be determined
// reliably; the server MUST respond with the 400 (Bad Request)
// status code and then close the connection.
if (transferCoding != TransferCoding.Chunked)
{
BadHttpRequestException.Throw(RequestRejectionReason.FinalTransferCodingNotChunked, in transferEncoding);
}
return new ForChunkedEncoding(keepAlive, context);
}
if (headers.ContentLength.HasValue)
{
var contentLength = headers.ContentLength.Value;
if (contentLength == 0)
{
return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose;
}
return new ForContentLength(keepAlive, contentLength, context);
}
// Avoid slowing down most common case
if (!object.ReferenceEquals(context.Method, HttpMethods.Get))
{
// If we got here, request contains no Content-Length or Transfer-Encoding header.
// Reject with 411 Length Required.
if (context.Method == HttpMethod.Post || context.Method == HttpMethod.Put)
{
var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10;
BadHttpRequestException.Throw(requestRejectionReason, context.Method);
}
}
return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose;
}
private class ForUpgrade : Http1MessageBody
{
public ForUpgrade(Http1Connection context)
: base(context)
{
RequestUpgrade = true;
}
public override bool IsEmpty => true;
protected override bool Read(ReadOnlySequence<byte> readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
Copy(readableBuffer, writableBuffer);
consumed = readableBuffer.End;
examined = readableBuffer.End;
return false;
}
}
private class ForContentLength : Http1MessageBody
{
private readonly long _contentLength;
private long _inputLength;
public ForContentLength(bool keepAlive, long contentLength, Http1Connection context)
: base(context)
{
RequestKeepAlive = keepAlive;
_contentLength = contentLength;
_inputLength = _contentLength;
}
protected override bool Read(ReadOnlySequence<byte> readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
if (_inputLength == 0)
{
throw new InvalidOperationException("Attempted to read from completed Content-Length request body.");
}
var actual = (int)Math.Min(readableBuffer.Length, _inputLength);
_inputLength -= actual;
consumed = readableBuffer.GetPosition(actual);
examined = consumed;
Copy(readableBuffer.Slice(0, actual), writableBuffer);
return _inputLength == 0;
}
protected override void OnReadStarting()
{
if (_contentLength > _context.MaxRequestBodySize)
{
BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
}
}
}
/// <summary>
/// http://tools.ietf.org/html/rfc2616#section-3.6.1
/// </summary>
private class ForChunkedEncoding : Http1MessageBody
{
// byte consts don't have a data type annotation so we pre-cast it
private const byte ByteCR = (byte)'\r';
// "7FFFFFFF\r\n" is the largest chunk size that could be returned as an int.
private const int MaxChunkPrefixBytes = 10;
private long _inputLength;
private long _consumedBytes;
private Mode _mode = Mode.Prefix;
public ForChunkedEncoding(bool keepAlive, Http1Connection context)
: base(context)
{
RequestKeepAlive = keepAlive;
}
protected override bool Read(ReadOnlySequence<byte> readableBuffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
consumed = default(SequencePosition);
examined = default(SequencePosition);
while (_mode < Mode.Trailer)
{
if (_mode == Mode.Prefix)
{
ParseChunkedPrefix(readableBuffer, out consumed, out examined);
if (_mode == Mode.Prefix)
{
return false;
}
readableBuffer = readableBuffer.Slice(consumed);
}
if (_mode == Mode.Extension)
{
ParseExtension(readableBuffer, out consumed, out examined);
if (_mode == Mode.Extension)
{
return false;
}
readableBuffer = readableBuffer.Slice(consumed);
}
if (_mode == Mode.Data)
{
ReadChunkedData(readableBuffer, writableBuffer, out consumed, out examined);
if (_mode == Mode.Data)
{
return false;
}
readableBuffer = readableBuffer.Slice(consumed);
}
if (_mode == Mode.Suffix)
{
ParseChunkedSuffix(readableBuffer, out consumed, out examined);
if (_mode == Mode.Suffix)
{
return false;
}
readableBuffer = readableBuffer.Slice(consumed);
}
}
// Chunks finished, parse trailers
if (_mode == Mode.Trailer)
{
ParseChunkedTrailer(readableBuffer, out consumed, out examined);
if (_mode == Mode.Trailer)
{
return false;
}
readableBuffer = readableBuffer.Slice(consumed);
}
// _consumedBytes aren't tracked for trailer headers, since headers have separate limits.
if (_mode == Mode.TrailerHeaders)
{
if (_context.TakeMessageHeaders(readableBuffer, out consumed, out examined))
{
_mode = Mode.Complete;
}
}
return _mode == Mode.Complete;
}
private void AddAndCheckConsumedBytes(long consumedBytes)
{
_consumedBytes += consumedBytes;
if (_consumedBytes > _context.MaxRequestBodySize)
{
BadHttpRequestException.Throw(RequestRejectionReason.RequestBodyTooLarge);
}
}
private void ParseChunkedPrefix(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
{
consumed = buffer.Start;
examined = buffer.Start;
var reader = new BufferReader(buffer);
var ch1 = reader.Read();
var ch2 = reader.Read();
if (ch1 == -1 || ch2 == -1)
{
examined = reader.Position;
return;
}
var chunkSize = CalculateChunkSize(ch1, 0);
ch1 = ch2;
while (reader.ConsumedBytes < MaxChunkPrefixBytes)
{
if (ch1 == ';')
{
consumed = reader.Position;
examined = reader.Position;
AddAndCheckConsumedBytes(reader.ConsumedBytes);
_inputLength = chunkSize;
_mode = Mode.Extension;
return;
}
ch2 = reader.Read();
if (ch2 == -1)
{
examined = reader.Position;
return;
}
if (ch1 == '\r' && ch2 == '\n')
{
consumed = reader.Position;
examined = reader.Position;
AddAndCheckConsumedBytes(reader.ConsumedBytes);
_inputLength = chunkSize;
_mode = chunkSize > 0 ? Mode.Data : Mode.Trailer;
return;
}
chunkSize = CalculateChunkSize(ch1, chunkSize);
ch1 = ch2;
}
// At this point, 10 bytes have been consumed which is enough to parse the max value "7FFFFFFF\r\n".
BadHttpRequestException.Throw(RequestRejectionReason.BadChunkSizeData);
}
private void ParseExtension(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
{
// Chunk-extensions not currently parsed
// Just drain the data
consumed = buffer.Start;
examined = buffer.Start;
do
{
SequencePosition? extensionCursorPosition = buffer.PositionOf(ByteCR);
if (extensionCursorPosition == null)
{
// End marker not found yet
consumed = buffer.End;
examined = buffer.End;
AddAndCheckConsumedBytes(buffer.Length);
return;
};
var extensionCursor = extensionCursorPosition.Value;
var charsToByteCRExclusive = buffer.Slice(0, extensionCursor).Length;
var sufixBuffer = buffer.Slice(extensionCursor);
if (sufixBuffer.Length < 2)
{
consumed = extensionCursor;
examined = buffer.End;
AddAndCheckConsumedBytes(charsToByteCRExclusive);
return;
}
sufixBuffer = sufixBuffer.Slice(0, 2);
var sufixSpan = sufixBuffer.ToSpan();
if (sufixSpan[1] == '\n')
{
// We consumed the \r\n at the end of the extension, so switch modes.
_mode = _inputLength > 0 ? Mode.Data : Mode.Trailer;
consumed = sufixBuffer.End;
examined = sufixBuffer.End;
AddAndCheckConsumedBytes(charsToByteCRExclusive + 2);
}
else
{
// Don't consume suffixSpan[1] in case it is also a \r.
buffer = buffer.Slice(charsToByteCRExclusive + 1);
consumed = extensionCursor;
AddAndCheckConsumedBytes(charsToByteCRExclusive + 1);
}
} while (_mode == Mode.Extension);
}
private void ReadChunkedData(ReadOnlySequence<byte> buffer, PipeWriter writableBuffer, out SequencePosition consumed, out SequencePosition examined)
{
var actual = Math.Min(buffer.Length, _inputLength);
consumed = buffer.GetPosition(actual);
examined = consumed;
Copy(buffer.Slice(0, actual), writableBuffer);
_inputLength -= actual;
AddAndCheckConsumedBytes(actual);
if (_inputLength == 0)
{
_mode = Mode.Suffix;
}
}
private void ParseChunkedSuffix(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
{
consumed = buffer.Start;
examined = buffer.Start;
if (buffer.Length < 2)
{
examined = buffer.End;
return;
}
var suffixBuffer = buffer.Slice(0, 2);
var suffixSpan = suffixBuffer.ToSpan();
if (suffixSpan[0] == '\r' && suffixSpan[1] == '\n')
{
consumed = suffixBuffer.End;
examined = suffixBuffer.End;
AddAndCheckConsumedBytes(2);
_mode = Mode.Prefix;
}
else
{
BadHttpRequestException.Throw(RequestRejectionReason.BadChunkSuffix);
}
}
private void ParseChunkedTrailer(ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
{
consumed = buffer.Start;
examined = buffer.Start;
if (buffer.Length < 2)
{
examined = buffer.End;
return;
}
var trailerBuffer = buffer.Slice(0, 2);
var trailerSpan = trailerBuffer.ToSpan();
if (trailerSpan[0] == '\r' && trailerSpan[1] == '\n')
{
consumed = trailerBuffer.End;
examined = trailerBuffer.End;
AddAndCheckConsumedBytes(2);
_mode = Mode.Complete;
}
else
{
_mode = Mode.TrailerHeaders;
}
}
private int CalculateChunkSize(int extraHexDigit, int currentParsedSize)
{
try
{
checked
{
if (extraHexDigit >= '0' && extraHexDigit <= '9')
{
return currentParsedSize * 0x10 + (extraHexDigit - '0');
}
else if (extraHexDigit >= 'A' && extraHexDigit <= 'F')
{
return currentParsedSize * 0x10 + (extraHexDigit - ('A' - 10));
}
else if (extraHexDigit >= 'a' && extraHexDigit <= 'f')
{
return currentParsedSize * 0x10 + (extraHexDigit - ('a' - 10));
}
}
}
catch (OverflowException ex)
{
throw new IOException(CoreStrings.BadRequest_BadChunkSizeData, ex);
}
BadHttpRequestException.Throw(RequestRejectionReason.BadChunkSizeData);
return -1; // can't happen, but compiler complains
}
private enum Mode
{
Prefix,
Extension,
Data,
Suffix,
Trailer,
TrailerHeaders,
Complete
};
}
}
}

View File

@ -0,0 +1,294 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public class Http1OutputProducer : IHttpOutputProducer
{
private static readonly ReadOnlyMemory<byte> _continueBytes = new ReadOnlyMemory<byte>(Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"));
private static readonly byte[] _bytesHttpVersion11 = Encoding.ASCII.GetBytes("HTTP/1.1 ");
private static readonly byte[] _bytesEndHeaders = Encoding.ASCII.GetBytes("\r\n\r\n");
private static readonly ReadOnlyMemory<byte> _endChunkedResponseBytes = new ReadOnlyMemory<byte>(Encoding.ASCII.GetBytes("0\r\n\r\n"));
private readonly string _connectionId;
private readonly ConnectionContext _connectionContext;
private readonly ITimeoutControl _timeoutControl;
private readonly IKestrelTrace _log;
private readonly IBytesWrittenFeature _transportBytesWrittenFeature;
// This locks access to to all of the below fields
private readonly object _contextLock = new object();
private bool _completed = false;
private bool _aborted;
private long _unflushedBytes;
private long _totalBytesCommitted;
private readonly PipeWriter _pipeWriter;
// https://github.com/dotnet/corefxlab/issues/1334
// Pipelines don't support multiple awaiters on flush
// this is temporary until it does
private TaskCompletionSource<object> _flushTcs;
private readonly object _flushLock = new object();
private Action _flushCompleted;
private ValueTask<FlushResult> _flushTask;
public Http1OutputProducer(
PipeWriter pipeWriter,
string connectionId,
ConnectionContext connectionContext,
IKestrelTrace log,
ITimeoutControl timeoutControl,
IBytesWrittenFeature transportBytesWrittenFeature)
{
_pipeWriter = pipeWriter;
_connectionId = connectionId;
_connectionContext = connectionContext;
_timeoutControl = timeoutControl;
_log = log;
_flushCompleted = OnFlushCompleted;
_transportBytesWrittenFeature = transportBytesWrittenFeature;
}
public Task WriteDataAsync(ReadOnlySpan<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
return WriteAsync(buffer, cancellationToken);
}
public Task WriteStreamSuffixAsync(CancellationToken cancellationToken)
{
return WriteAsync(_endChunkedResponseBytes.Span, cancellationToken);
}
public Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return WriteAsync(Constants.EmptyData, cancellationToken);
}
public void Write<T>(Func<PipeWriter, T, long> callback, T state)
{
lock (_contextLock)
{
if (_completed)
{
return;
}
var buffer = _pipeWriter;
var bytesCommitted = callback(buffer, state);
_unflushedBytes += bytesCommitted;
_totalBytesCommitted += bytesCommitted;
}
}
public Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state)
{
lock (_contextLock)
{
if (_completed)
{
return Task.CompletedTask;
}
var buffer = _pipeWriter;
var bytesCommitted = callback(buffer, state);
_unflushedBytes += bytesCommitted;
_totalBytesCommitted += bytesCommitted;
}
return FlushAsync();
}
public void WriteResponseHeaders(int statusCode, string reasonPhrase, HttpResponseHeaders responseHeaders)
{
lock (_contextLock)
{
if (_completed)
{
return;
}
var buffer = _pipeWriter;
var writer = new BufferWriter<PipeWriter>(buffer);
writer.Write(_bytesHttpVersion11);
var statusBytes = ReasonPhrases.ToStatusBytes(statusCode, reasonPhrase);
writer.Write(statusBytes);
responseHeaders.CopyTo(ref writer);
writer.Write(_bytesEndHeaders);
writer.Commit();
_unflushedBytes += writer.BytesCommitted;
_totalBytesCommitted += writer.BytesCommitted;
}
}
public void Dispose()
{
lock (_contextLock)
{
if (_completed)
{
return;
}
_log.ConnectionDisconnect(_connectionId);
_completed = true;
_pipeWriter.Complete();
var unsentBytes = _totalBytesCommitted - _transportBytesWrittenFeature.TotalBytesWritten;
if (unsentBytes > 0)
{
// unsentBytes should never be over 64KB in the default configuration.
_timeoutControl.StartTimingWrite((int)Math.Min(unsentBytes, int.MaxValue));
_pipeWriter.OnReaderCompleted((ex, state) => ((ITimeoutControl)state).StopTimingWrite(), _timeoutControl);
}
}
}
public void Abort(ConnectionAbortedException error)
{
// Abort can be called after Dispose if there's a flush timeout.
// It's important to still call _lifetimeFeature.Abort() in this case.
lock (_contextLock)
{
if (_aborted)
{
return;
}
_aborted = true;
_connectionContext.Abort(error);
if (!_completed)
{
_log.ConnectionDisconnect(_connectionId);
_completed = true;
_pipeWriter.Complete();
}
}
}
public Task Write100ContinueAsync(CancellationToken cancellationToken)
{
return WriteAsync(_continueBytes.Span, default(CancellationToken));
}
private Task WriteAsync(
ReadOnlySpan<byte> buffer,
CancellationToken cancellationToken)
{
var writableBuffer = default(PipeWriter);
long bytesWritten = 0;
lock (_contextLock)
{
if (_completed)
{
return Task.CompletedTask;
}
writableBuffer = _pipeWriter;
var writer = new BufferWriter<PipeWriter>(writableBuffer);
if (buffer.Length > 0)
{
writer.Write(buffer);
_unflushedBytes += buffer.Length;
_totalBytesCommitted += buffer.Length;
}
writer.Commit();
bytesWritten = _unflushedBytes;
_unflushedBytes = 0;
}
return FlushAsync(writableBuffer, bytesWritten, cancellationToken);
}
// Single caller, at end of method - so inline
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Task FlushAsync(PipeWriter writableBuffer, long bytesWritten, CancellationToken cancellationToken)
{
var awaitable = writableBuffer.FlushAsync(cancellationToken);
if (awaitable.IsCompleted)
{
// The flush task can't fail today
return Task.CompletedTask;
}
return FlushAsyncAwaited(awaitable, bytesWritten, cancellationToken);
}
private async Task FlushAsyncAwaited(ValueTask<FlushResult> awaitable, long count, CancellationToken cancellationToken)
{
// https://github.com/dotnet/corefxlab/issues/1334
// Since the flush awaitable doesn't currently support multiple awaiters
// we need to use a task to track the callbacks.
// All awaiters get the same task
lock (_flushLock)
{
_flushTask = awaitable;
if (_flushTcs == null || _flushTcs.Task.IsCompleted)
{
_flushTcs = new TaskCompletionSource<object>();
_flushTask.GetAwaiter().OnCompleted(_flushCompleted);
}
}
_timeoutControl.StartTimingWrite(count);
try
{
await _flushTcs.Task;
cancellationToken.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
_completed = true;
throw;
}
finally
{
_timeoutControl.StopTimingWrite();
}
}
private void OnFlushCompleted()
{
try
{
_flushTask.GetAwaiter().GetResult();
_flushTcs.TrySetResult(null);
}
catch (Exception exception)
{
_flushTcs.TrySetResult(exception);
}
finally
{
_flushTask = default;
}
}
}
}

View File

@ -0,0 +1,23 @@
// 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.Core.Internal.Http
{
public struct Http1ParsingHandler : IHttpRequestLineHandler, IHttpHeadersHandler
{
public Http1Connection Connection;
public Http1ParsingHandler(Http1Connection connection)
{
Connection = connection;
}
public void OnHeader(Span<byte> name, Span<byte> value)
=> Connection.OnHeader(name, value);
public void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded)
=> Connection.OnStartLine(method, version, target, path, query, customMethod, pathEncoded);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,444 @@
// 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.Linq;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public abstract class HttpHeaders : 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
{
StringValues value;
TryGetValueFast(key, out value);
return value;
}
set
{
if (_isReadOnly)
{
ThrowHeadersReadOnlyException();
}
if (value.Count == 0)
{
RemoveFast(key);
}
else
{
SetValueFast(key, value);
}
}
}
StringValues IDictionary<string, StringValues>.this[string key]
{
get
{
// Unlike the IHeaderDictionary version, this getter will throw a KeyNotFoundException.
StringValues value;
if (!TryGetValueFast(key, out value))
{
ThrowKeyNotFoundException();
}
return value;
}
set
{
((IHeaderDictionary)this)[key] = value;
}
}
protected void ThrowHeadersReadOnlyException()
{
throw new InvalidOperationException(CoreStrings.HeadersAreReadOnly);
}
protected void ThrowArgumentException()
{
throw new ArgumentException();
}
protected void ThrowKeyNotFoundException()
{
throw new KeyNotFoundException();
}
protected void ThrowDuplicateKeyException()
{
throw new ArgumentException(CoreStrings.KeyAlreadyExists);
}
int ICollection<KeyValuePair<string, StringValues>>.Count => GetCountFast();
bool ICollection<KeyValuePair<string, StringValues>>.IsReadOnly => _isReadOnly;
ICollection<string> IDictionary<string, StringValues>.Keys => ((IDictionary<string, StringValues>)this).Select(pair => pair.Key).ToList();
ICollection<StringValues> IDictionary<string, StringValues>.Values => ((IDictionary<string, StringValues>)this).Select(pair => pair.Value).ToList();
public void SetReadOnly()
{
_isReadOnly = true;
}
public void Reset()
{
_isReadOnly = false;
ClearFast();
}
[MethodImpl(MethodImplOptions.NoInlining)]
protected static StringValues AppendValue(in StringValues existing, string append)
{
return StringValues.Concat(existing, append);
}
protected static int BitCount(long value)
{
// see https://github.com/dotnet/corefx/blob/5965fd3756bc9dd9c89a27621eb10c6931126de2/src/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BitArithmetic.cs
const ulong Mask01010101 = 0x5555555555555555UL;
const ulong Mask00110011 = 0x3333333333333333UL;
const ulong Mask00001111 = 0x0F0F0F0F0F0F0F0FUL;
const ulong Mask00000001 = 0x0101010101010101UL;
var v = (ulong)value;
v = v - ((v >> 1) & Mask01010101);
v = (v & Mask00110011) + ((v >> 2) & Mask00110011);
return (int)(unchecked(((v + (v >> 4)) & Mask00001111) * Mask00000001) >> 56);
}
protected virtual int GetCountFast()
{ throw new NotImplementedException(); }
protected virtual bool TryGetValueFast(string key, out StringValues value)
{ throw new NotImplementedException(); }
protected virtual void SetValueFast(string key, in StringValues value)
{ throw new NotImplementedException(); }
protected virtual bool AddValueFast(string key, in StringValues value)
{ throw new NotImplementedException(); }
protected virtual bool RemoveFast(string key)
{ throw new NotImplementedException(); }
protected virtual void ClearFast()
{ throw new NotImplementedException(); }
protected virtual bool CopyToFast(KeyValuePair<string, StringValues>[] array, int arrayIndex)
{ throw new NotImplementedException(); }
protected virtual IEnumerator<KeyValuePair<string, StringValues>> GetEnumeratorFast()
{ throw new NotImplementedException(); }
void ICollection<KeyValuePair<string, StringValues>>.Add(KeyValuePair<string, StringValues> item)
{
((IDictionary<string, StringValues>)this).Add(item.Key, item.Value);
}
void IDictionary<string, StringValues>.Add(string key, StringValues value)
{
if (_isReadOnly)
{
ThrowHeadersReadOnlyException();
}
if (value.Count > 0 && !AddValueFast(key, value))
{
ThrowDuplicateKeyException();
}
}
void ICollection<KeyValuePair<string, StringValues>>.Clear()
{
if (_isReadOnly)
{
ThrowHeadersReadOnlyException();
}
ClearFast();
}
bool ICollection<KeyValuePair<string, StringValues>>.Contains(KeyValuePair<string, StringValues> item)
{
StringValues value;
return
TryGetValueFast(item.Key, out value) &&
value.Equals(item.Value);
}
bool IDictionary<string, StringValues>.ContainsKey(string key)
{
StringValues value;
return TryGetValueFast(key, out value);
}
void ICollection<KeyValuePair<string, StringValues>>.CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
{
if (!CopyToFast(array, arrayIndex))
{
ThrowArgumentException();
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumeratorFast();
}
IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
{
return GetEnumeratorFast();
}
bool ICollection<KeyValuePair<string, StringValues>>.Remove(KeyValuePair<string, StringValues> item)
{
StringValues value;
return
TryGetValueFast(item.Key, out value) &&
value.Equals(item.Value) &&
RemoveFast(item.Key);
}
bool IDictionary<string, StringValues>.Remove(string key)
{
if (_isReadOnly)
{
ThrowHeadersReadOnlyException();
}
return RemoveFast(key);
}
bool IDictionary<string, StringValues>.TryGetValue(string key, out StringValues value)
{
return TryGetValueFast(key, out value);
}
public static void ValidateHeaderCharacters(in StringValues headerValues)
{
var count = headerValues.Count;
for (var i = 0; i < count; i++)
{
ValidateHeaderCharacters(headerValues[i]);
}
}
public static void ValidateHeaderCharacters(string headerCharacters)
{
if (headerCharacters != null)
{
foreach (var ch in headerCharacters)
{
if (ch < 0x20 || ch > 0x7E)
{
ThrowInvalidHeaderCharacter(ch);
}
}
}
}
public static unsafe ConnectionOptions ParseConnection(in StringValues connection)
{
var connectionOptions = ConnectionOptions.None;
var connectionCount = connection.Count;
for (var i = 0; i < connectionCount; i++)
{
var value = connection[i];
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)
{
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)
{
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)
{
connectionOptions |= ConnectionOptions.Close;
}
}
}
tokenEnd++;
ch = tokenEnd;
}
}
}
return connectionOptions;
}
public static unsafe TransferCoding GetFinalTransferCoding(in StringValues transferEncoding)
{
var transferEncodingOptions = TransferCoding.None;
var transferEncodingCount = transferEncoding.Count;
for (var i = 0; i < transferEncodingCount; i++)
{
var value = transferEncoding[i];
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 >= 7 && (*ch | 0x20) == 'c')
{
if ((*++ch | 0x20) == 'h' &&
(*++ch | 0x20) == 'u' &&
(*++ch | 0x20) == 'n' &&
(*++ch | 0x20) == 'k' &&
(*++ch | 0x20) == 'e' &&
(*++ch | 0x20) == 'd')
{
ch++;
while (ch < tokenEnd && *ch == ' ')
{
ch++;
}
if (ch == tokenEnd)
{
transferEncodingOptions = TransferCoding.Chunked;
}
}
}
if (tokenLength > 0 && ch != tokenEnd)
{
transferEncodingOptions = TransferCoding.Other;
}
tokenEnd++;
ch = tokenEnd;
}
}
}
return transferEncodingOptions;
}
private static void ThrowInvalidContentLengthException(long value)
{
throw new ArgumentOutOfRangeException(CoreStrings.FormatInvalidContentLength_InvalidNumber(value));
}
private static void ThrowInvalidHeaderCharacter(char ch)
{
throw new InvalidOperationException(CoreStrings.FormatInvalidAsciiOrControlChar(string.Format("0x{0:X4}", (ushort)ch)));
}
}
}

View File

@ -0,0 +1,22 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public enum HttpMethod: byte
{
Get,
Put,
Delete,
Post,
Head,
Trace,
Patch,
Connect,
Options,
Custom,
None = byte.MaxValue,
}
}

View File

@ -0,0 +1,509 @@
// 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.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public class HttpParser<TRequestHandler> : IHttpParser<TRequestHandler> where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler
{
private bool _showErrorDetails;
public HttpParser() : this(showErrorDetails: true)
{
}
public HttpParser(bool showErrorDetails)
{
_showErrorDetails = showErrorDetails;
}
// byte types don't have a data type annotation so we pre-cast them; to avoid in-place casts
private const byte ByteCR = (byte)'\r';
private const byte ByteLF = (byte)'\n';
private const byte ByteColon = (byte)':';
private const byte ByteSpace = (byte)' ';
private const byte ByteTab = (byte)'\t';
private const byte ByteQuestionMark = (byte)'?';
private const byte BytePercentage = (byte)'%';
public unsafe bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined)
{
consumed = buffer.Start;
examined = buffer.End;
// Prepare the first span
var span = buffer.First.Span;
var lineIndex = span.IndexOf(ByteLF);
if (lineIndex >= 0)
{
consumed = buffer.GetPosition(lineIndex + 1, consumed);
span = span.Slice(0, lineIndex + 1);
}
else if (buffer.IsSingleSegment)
{
// No request line end
return false;
}
else if (TryGetNewLine(buffer, out var found))
{
span = buffer.Slice(consumed, found).ToSpan();
consumed = found;
}
else
{
// No request line end
return false;
}
// Fix and parse the span
fixed (byte* data = &MemoryMarshal.GetReference(span))
{
ParseRequestLine(handler, data, span.Length);
}
examined = consumed;
return true;
}
private unsafe void ParseRequestLine(TRequestHandler handler, byte* data, int length)
{
int offset;
// Get Method and set the offset
var method = HttpUtilities.GetKnownMethod(data, length, out offset);
Span<byte> customMethod = method == HttpMethod.Custom ?
GetUnknownMethod(data, length, out offset) :
default;
// Skip space
offset++;
byte ch = 0;
// Target = Path and Query
var pathEncoded = false;
var pathStart = -1;
for (; offset < length; offset++)
{
ch = data[offset];
if (ch == ByteSpace)
{
if (pathStart == -1)
{
// Empty path is illegal
RejectRequestLine(data, length);
}
break;
}
else if (ch == ByteQuestionMark)
{
if (pathStart == -1)
{
// Empty path is illegal
RejectRequestLine(data, length);
}
break;
}
else if (ch == BytePercentage)
{
if (pathStart == -1)
{
// Path starting with % is illegal
RejectRequestLine(data, length);
}
pathEncoded = true;
}
else if (pathStart == -1)
{
pathStart = offset;
}
}
if (pathStart == -1)
{
// Start of path not found
RejectRequestLine(data, length);
}
var pathBuffer = new Span<byte>(data + pathStart, offset - pathStart);
// Query string
var queryStart = offset;
if (ch == ByteQuestionMark)
{
// We have a query string
for (; offset < length; offset++)
{
ch = data[offset];
if (ch == ByteSpace)
{
break;
}
}
}
// End of query string not found
if (offset == length)
{
RejectRequestLine(data, length);
}
var targetBuffer = new Span<byte>(data + pathStart, offset - pathStart);
var query = new Span<byte>(data + queryStart, offset - queryStart);
// Consume space
offset++;
// Version
var httpVersion = HttpUtilities.GetKnownVersion(data + offset, length - offset);
if (httpVersion == HttpVersion.Unknown)
{
if (data[offset] == ByteCR || data[length - 2] != ByteCR)
{
// If missing delimiter or CR before LF, reject and log entire line
RejectRequestLine(data, length);
}
else
{
// else inform HTTP version is unsupported.
RejectUnknownVersion(data + offset, length - offset - 2);
}
}
// After version's 8 bytes and CR, expect LF
if (data[offset + 8 + 1] != ByteLF)
{
RejectRequestLine(data, length);
}
handler.OnStartLine(method, httpVersion, targetBuffer, pathBuffer, query, customMethod, pathEncoded);
}
public unsafe bool ParseHeaders(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined, out int consumedBytes)
{
consumed = buffer.Start;
examined = buffer.End;
consumedBytes = 0;
var bufferEnd = buffer.End;
var reader = new BufferReader(buffer);
var start = default(BufferReader);
var done = false;
try
{
while (!reader.End)
{
var span = reader.CurrentSegment;
var remaining = span.Length - reader.CurrentSegmentIndex;
fixed (byte* pBuffer = &MemoryMarshal.GetReference(span))
{
while (remaining > 0)
{
var index = reader.CurrentSegmentIndex;
int ch1;
int ch2;
var readAhead = false;
// Fast path, we're still looking at the same span
if (remaining >= 2)
{
ch1 = pBuffer[index];
ch2 = pBuffer[index + 1];
}
else
{
// Store the reader before we look ahead 2 bytes (probably straddling
// spans)
start = reader;
// Possibly split across spans
ch1 = reader.Read();
ch2 = reader.Read();
readAhead = true;
}
if (ch1 == ByteCR)
{
// Check for final CRLF.
if (ch2 == -1)
{
// Reset the reader so we don't consume anything
reader = start;
return false;
}
else if (ch2 == ByteLF)
{
// If we got 2 bytes from the span directly so skip ahead 2 so that
// the reader's state matches what we expect
if (!readAhead)
{
reader.Advance(2);
}
done = true;
return true;
}
// Headers don't end in CRLF line.
BadHttpRequestException.Throw(RequestRejectionReason.InvalidRequestHeadersNoCRLF);
}
// We moved the reader so look ahead 2 bytes so reset both the reader
// and the index
if (readAhead)
{
reader = start;
index = reader.CurrentSegmentIndex;
}
var endIndex = new Span<byte>(pBuffer + index, remaining).IndexOf(ByteLF);
var length = 0;
if (endIndex != -1)
{
length = endIndex + 1;
var pHeader = pBuffer + index;
TakeSingleHeader(pHeader, length, handler);
}
else
{
var current = reader.Position;
var currentSlice = buffer.Slice(current, bufferEnd);
var lineEndPosition = currentSlice.PositionOf(ByteLF);
// Split buffers
if (lineEndPosition == null)
{
// Not there
return false;
}
var lineEnd = lineEndPosition.Value;
// Make sure LF is included in lineEnd
lineEnd = buffer.GetPosition(1, lineEnd);
var headerSpan = buffer.Slice(current, lineEnd).ToSpan();
length = headerSpan.Length;
fixed (byte* pHeader = &MemoryMarshal.GetReference(headerSpan))
{
TakeSingleHeader(pHeader, length, handler);
}
// We're going to the next span after this since we know we crossed spans here
// so mark the remaining as equal to the headerSpan so that we end up at 0
// on the next iteration
remaining = length;
}
// Skip the reader forward past the header line
reader.Advance(length);
remaining -= length;
}
}
}
return false;
}
finally
{
consumed = reader.Position;
consumedBytes = reader.ConsumedBytes;
if (done)
{
examined = consumed;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe int FindEndOfName(byte* headerLine, int length)
{
var index = 0;
var sawWhitespace = false;
for (; index < length; index++)
{
var ch = headerLine[index];
if (ch == ByteColon)
{
break;
}
if (ch == ByteTab || ch == ByteSpace || ch == ByteCR)
{
sawWhitespace = true;
}
}
if (index == length || sawWhitespace)
{
RejectRequestHeader(headerLine, length);
}
return index;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void TakeSingleHeader(byte* headerLine, int length, TRequestHandler handler)
{
// Skip CR, LF from end position
var valueEnd = length - 3;
var nameEnd = FindEndOfName(headerLine, length);
if (headerLine[valueEnd + 2] != ByteLF)
{
RejectRequestHeader(headerLine, length);
}
if (headerLine[valueEnd + 1] != ByteCR)
{
RejectRequestHeader(headerLine, length);
}
// Skip colon from value start
var valueStart = nameEnd + 1;
// Ignore start whitespace
for (; valueStart < valueEnd; valueStart++)
{
var ch = headerLine[valueStart];
if (ch != ByteTab && ch != ByteSpace && ch != ByteCR)
{
break;
}
else if (ch == ByteCR)
{
RejectRequestHeader(headerLine, length);
}
}
// Check for CR in value
var valueBuffer = new Span<byte>(headerLine + valueStart, valueEnd - valueStart + 1);
if (valueBuffer.IndexOf(ByteCR) >= 0)
{
RejectRequestHeader(headerLine, length);
}
// Ignore end whitespace
var lengthChanged = false;
for (; valueEnd >= valueStart; valueEnd--)
{
var ch = headerLine[valueEnd];
if (ch != ByteTab && ch != ByteSpace)
{
break;
}
lengthChanged = true;
}
if (lengthChanged)
{
// Length changed
valueBuffer = new Span<byte>(headerLine + valueStart, valueEnd - valueStart + 1);
}
var nameBuffer = new Span<byte>(headerLine, nameEnd);
handler.OnHeader(nameBuffer, valueBuffer);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryGetNewLine(in ReadOnlySequence<byte> buffer, out SequencePosition found)
{
var byteLfPosition = buffer.PositionOf(ByteLF);
if (byteLfPosition != null)
{
// Move 1 byte past the \n
found = buffer.GetPosition(1, byteLfPosition.Value);
return true;
}
found = default;
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe Span<byte> GetUnknownMethod(byte* data, int length, out int methodLength)
{
methodLength = 0;
for (var i = 0; i < length; i++)
{
var ch = data[i];
if (ch == ByteSpace)
{
if (i == 0)
{
RejectRequestLine(data, length);
}
methodLength = i;
break;
}
else if (!IsValidTokenChar((char)ch))
{
RejectRequestLine(data, length);
}
}
return new Span<byte>(data, methodLength);
}
private static bool IsValidTokenChar(char c)
{
// Determines if a character is valid as a 'token' as defined in the
// HTTP spec: https://tools.ietf.org/html/rfc7230#section-3.2.6
return
(c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
c == '!' ||
c == '#' ||
c == '$' ||
c == '%' ||
c == '&' ||
c == '\'' ||
c == '*' ||
c == '+' ||
c == '-' ||
c == '.' ||
c == '^' ||
c == '_' ||
c == '`' ||
c == '|' ||
c == '~';
}
[StackTraceHidden]
private unsafe void RejectRequestLine(byte* requestLine, int length)
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestLine, requestLine, length);
[StackTraceHidden]
private unsafe void RejectRequestHeader(byte* headerLine, int length)
=> throw GetInvalidRequestException(RequestRejectionReason.InvalidRequestHeader, headerLine, length);
[StackTraceHidden]
private unsafe void RejectUnknownVersion(byte* version, int length)
=> throw GetInvalidRequestException(RequestRejectionReason.UnrecognizedHTTPVersion, version, length);
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe BadHttpRequestException GetInvalidRequestException(RequestRejectionReason reason, byte* detail, int length)
=> BadHttpRequestException.GetException(
reason,
_showErrorDetails
? new Span<byte>(detail, length).GetAsciiStringEscaped(Constants.MaxExceptionDetailSize)
: string.Empty);
}
}

View File

@ -0,0 +1,290 @@
// 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.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public partial class HttpProtocol : IFeatureCollection,
IHttpRequestFeature,
IHttpResponseFeature,
IHttpConnectionFeature,
IHttpRequestLifetimeFeature,
IHttpRequestIdentifierFeature,
IHttpBodyControlFeature,
IHttpMaxRequestBodySizeFeature,
IHttpMinRequestBodyDataRateFeature,
IHttpMinResponseDataRateFeature
{
// NOTE: When feature interfaces are added to or removed from this HttpProtocol class implementation,
// then the list of `implementedFeatures` in the generated code project MUST also be updated.
// See also: tools/Microsoft.AspNetCore.Server.Kestrel.GeneratedCode/HttpProtocolFeatureCollection.cs
private int _featureRevision;
private List<KeyValuePair<Type, object>> MaybeExtra;
public void ResetFeatureCollection()
{
FastReset();
MaybeExtra?.Clear();
_featureRevision++;
}
private object ExtraFeatureGet(Type key)
{
if (MaybeExtra == null)
{
return null;
}
for (var i = 0; i < MaybeExtra.Count; i++)
{
var kv = MaybeExtra[i];
if (kv.Key == key)
{
return kv.Value;
}
}
return null;
}
private void ExtraFeatureSet(Type key, object value)
{
if (MaybeExtra == null)
{
MaybeExtra = new List<KeyValuePair<Type, object>>(2);
}
for (var i = 0; i < MaybeExtra.Count; i++)
{
if (MaybeExtra[i].Key == key)
{
MaybeExtra[i] = new KeyValuePair<Type, object>(key, value);
return;
}
}
MaybeExtra.Add(new KeyValuePair<Type, object>(key, value));
}
string IHttpRequestFeature.Protocol
{
get => HttpVersion;
set => HttpVersion = value;
}
string IHttpRequestFeature.Scheme
{
get => Scheme ?? "http";
set => Scheme = value;
}
string IHttpRequestFeature.Method
{
get
{
if (_methodText != null)
{
return _methodText;
}
_methodText = HttpUtilities.MethodToString(Method) ?? string.Empty;
return _methodText;
}
set
{
_methodText = value;
}
}
string IHttpRequestFeature.PathBase
{
get => PathBase ?? "";
set => PathBase = value;
}
string IHttpRequestFeature.Path
{
get => Path;
set => Path = value;
}
string IHttpRequestFeature.QueryString
{
get => QueryString;
set => QueryString = value;
}
string IHttpRequestFeature.RawTarget
{
get => RawTarget;
set => RawTarget = value;
}
IHeaderDictionary IHttpRequestFeature.Headers
{
get => RequestHeaders;
set => RequestHeaders = value;
}
Stream IHttpRequestFeature.Body
{
get => RequestBody;
set => RequestBody = value;
}
int IHttpResponseFeature.StatusCode
{
get => StatusCode;
set => StatusCode = value;
}
string IHttpResponseFeature.ReasonPhrase
{
get => ReasonPhrase;
set => ReasonPhrase = value;
}
IHeaderDictionary IHttpResponseFeature.Headers
{
get => ResponseHeaders;
set => ResponseHeaders = value;
}
Stream IHttpResponseFeature.Body
{
get => ResponseBody;
set => ResponseBody = value;
}
CancellationToken IHttpRequestLifetimeFeature.RequestAborted
{
get => RequestAborted;
set => RequestAborted = value;
}
bool IHttpResponseFeature.HasStarted => HasResponseStarted;
bool IFeatureCollection.IsReadOnly => false;
int IFeatureCollection.Revision => _featureRevision;
IPAddress IHttpConnectionFeature.RemoteIpAddress
{
get => RemoteIpAddress;
set => RemoteIpAddress = value;
}
IPAddress IHttpConnectionFeature.LocalIpAddress
{
get => LocalIpAddress;
set => LocalIpAddress = value;
}
int IHttpConnectionFeature.RemotePort
{
get => RemotePort;
set => RemotePort = value;
}
int IHttpConnectionFeature.LocalPort
{
get => LocalPort;
set => LocalPort = value;
}
string IHttpConnectionFeature.ConnectionId
{
get => ConnectionIdFeature;
set => ConnectionIdFeature = value;
}
string IHttpRequestIdentifierFeature.TraceIdentifier
{
get => TraceIdentifier;
set => TraceIdentifier = value;
}
bool IHttpBodyControlFeature.AllowSynchronousIO
{
get => AllowSynchronousIO;
set => AllowSynchronousIO = value;
}
bool IHttpMaxRequestBodySizeFeature.IsReadOnly => HasStartedConsumingRequestBody || IsUpgraded;
long? IHttpMaxRequestBodySizeFeature.MaxRequestBodySize
{
get => MaxRequestBodySize;
set
{
if (HasStartedConsumingRequestBody)
{
throw new InvalidOperationException(CoreStrings.MaxRequestBodySizeCannotBeModifiedAfterRead);
}
if (IsUpgraded)
{
throw new InvalidOperationException(CoreStrings.MaxRequestBodySizeCannotBeModifiedForUpgradedRequests);
}
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), CoreStrings.NonNegativeNumberOrNullRequired);
}
MaxRequestBodySize = value;
}
}
MinDataRate IHttpMinRequestBodyDataRateFeature.MinDataRate
{
get => MinRequestBodyDataRate;
set => MinRequestBodyDataRate = value;
}
MinDataRate IHttpMinResponseDataRateFeature.MinDataRate
{
get => MinResponseDataRate;
set => MinResponseDataRate = value;
}
protected void ResetIHttpUpgradeFeature()
{
_currentIHttpUpgradeFeature = this;
}
protected void ResetIHttp2StreamIdFeature()
{
_currentIHttp2StreamIdFeature = this;
}
void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)
{
OnStarting(callback, state);
}
void IHttpResponseFeature.OnCompleted(Func<object, Task> callback, object state)
{
OnCompleted(callback, state);
}
IEnumerator<KeyValuePair<Type, object>> IEnumerable<KeyValuePair<Type, object>>.GetEnumerator() => FastEnumerable().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator();
void IHttpRequestLifetimeFeature.Abort()
{
Log.ApplicationAbortedConnection(ConnectionId, TraceIdentifier);
Abort(new ConnectionAbortedException(CoreStrings.ConnectionAbortedByApplication));
}
}
}

View File

@ -0,0 +1,566 @@
// 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.Http.Features;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public partial class HttpProtocol
{
private static readonly Type IHttpRequestFeatureType = typeof(IHttpRequestFeature);
private static readonly Type IHttpResponseFeatureType = typeof(IHttpResponseFeature);
private static readonly Type IHttpRequestIdentifierFeatureType = typeof(IHttpRequestIdentifierFeature);
private static readonly Type IServiceProvidersFeatureType = typeof(IServiceProvidersFeature);
private static readonly Type IHttpRequestLifetimeFeatureType = typeof(IHttpRequestLifetimeFeature);
private static readonly Type IHttpConnectionFeatureType = typeof(IHttpConnectionFeature);
private static readonly Type IHttpAuthenticationFeatureType = typeof(IHttpAuthenticationFeature);
private static readonly Type IQueryFeatureType = typeof(IQueryFeature);
private static readonly Type IFormFeatureType = typeof(IFormFeature);
private static readonly Type IHttpUpgradeFeatureType = typeof(IHttpUpgradeFeature);
private static readonly Type IHttp2StreamIdFeatureType = typeof(IHttp2StreamIdFeature);
private static readonly Type IResponseCookiesFeatureType = typeof(IResponseCookiesFeature);
private static readonly Type IItemsFeatureType = typeof(IItemsFeature);
private static readonly Type ITlsConnectionFeatureType = typeof(ITlsConnectionFeature);
private static readonly Type IHttpWebSocketFeatureType = typeof(IHttpWebSocketFeature);
private static readonly Type ISessionFeatureType = typeof(ISessionFeature);
private static readonly Type IHttpMaxRequestBodySizeFeatureType = typeof(IHttpMaxRequestBodySizeFeature);
private static readonly Type IHttpMinRequestBodyDataRateFeatureType = typeof(IHttpMinRequestBodyDataRateFeature);
private static readonly Type IHttpMinResponseDataRateFeatureType = typeof(IHttpMinResponseDataRateFeature);
private static readonly Type IHttpBodyControlFeatureType = typeof(IHttpBodyControlFeature);
private static readonly Type IHttpSendFileFeatureType = typeof(IHttpSendFileFeature);
private object _currentIHttpRequestFeature;
private object _currentIHttpResponseFeature;
private object _currentIHttpRequestIdentifierFeature;
private object _currentIServiceProvidersFeature;
private object _currentIHttpRequestLifetimeFeature;
private object _currentIHttpConnectionFeature;
private object _currentIHttpAuthenticationFeature;
private object _currentIQueryFeature;
private object _currentIFormFeature;
private object _currentIHttpUpgradeFeature;
private object _currentIHttp2StreamIdFeature;
private object _currentIResponseCookiesFeature;
private object _currentIItemsFeature;
private object _currentITlsConnectionFeature;
private object _currentIHttpWebSocketFeature;
private object _currentISessionFeature;
private object _currentIHttpMaxRequestBodySizeFeature;
private object _currentIHttpMinRequestBodyDataRateFeature;
private object _currentIHttpMinResponseDataRateFeature;
private object _currentIHttpBodyControlFeature;
private object _currentIHttpSendFileFeature;
private void FastReset()
{
_currentIHttpRequestFeature = this;
_currentIHttpResponseFeature = this;
_currentIHttpRequestIdentifierFeature = this;
_currentIHttpRequestLifetimeFeature = this;
_currentIHttpConnectionFeature = this;
_currentIHttpMaxRequestBodySizeFeature = this;
_currentIHttpMinRequestBodyDataRateFeature = this;
_currentIHttpMinResponseDataRateFeature = this;
_currentIHttpBodyControlFeature = this;
_currentIServiceProvidersFeature = null;
_currentIHttpAuthenticationFeature = null;
_currentIQueryFeature = null;
_currentIFormFeature = null;
_currentIHttpUpgradeFeature = null;
_currentIHttp2StreamIdFeature = null;
_currentIResponseCookiesFeature = null;
_currentIItemsFeature = null;
_currentITlsConnectionFeature = null;
_currentIHttpWebSocketFeature = null;
_currentISessionFeature = null;
_currentIHttpSendFileFeature = null;
}
object IFeatureCollection.this[Type key]
{
get
{
object feature = null;
if (key == IHttpRequestFeatureType)
{
feature = _currentIHttpRequestFeature;
}
else if (key == IHttpResponseFeatureType)
{
feature = _currentIHttpResponseFeature;
}
else if (key == IHttpRequestIdentifierFeatureType)
{
feature = _currentIHttpRequestIdentifierFeature;
}
else if (key == IServiceProvidersFeatureType)
{
feature = _currentIServiceProvidersFeature;
}
else if (key == IHttpRequestLifetimeFeatureType)
{
feature = _currentIHttpRequestLifetimeFeature;
}
else if (key == IHttpConnectionFeatureType)
{
feature = _currentIHttpConnectionFeature;
}
else if (key == IHttpAuthenticationFeatureType)
{
feature = _currentIHttpAuthenticationFeature;
}
else if (key == IQueryFeatureType)
{
feature = _currentIQueryFeature;
}
else if (key == IFormFeatureType)
{
feature = _currentIFormFeature;
}
else if (key == IHttpUpgradeFeatureType)
{
feature = _currentIHttpUpgradeFeature;
}
else if (key == IHttp2StreamIdFeatureType)
{
feature = _currentIHttp2StreamIdFeature;
}
else if (key == IResponseCookiesFeatureType)
{
feature = _currentIResponseCookiesFeature;
}
else if (key == IItemsFeatureType)
{
feature = _currentIItemsFeature;
}
else if (key == ITlsConnectionFeatureType)
{
feature = _currentITlsConnectionFeature;
}
else if (key == IHttpWebSocketFeatureType)
{
feature = _currentIHttpWebSocketFeature;
}
else if (key == ISessionFeatureType)
{
feature = _currentISessionFeature;
}
else if (key == IHttpMaxRequestBodySizeFeatureType)
{
feature = _currentIHttpMaxRequestBodySizeFeature;
}
else if (key == IHttpMinRequestBodyDataRateFeatureType)
{
feature = _currentIHttpMinRequestBodyDataRateFeature;
}
else if (key == IHttpMinResponseDataRateFeatureType)
{
feature = _currentIHttpMinResponseDataRateFeature;
}
else if (key == IHttpBodyControlFeatureType)
{
feature = _currentIHttpBodyControlFeature;
}
else if (key == IHttpSendFileFeatureType)
{
feature = _currentIHttpSendFileFeature;
}
else if (MaybeExtra != null)
{
feature = ExtraFeatureGet(key);
}
return feature ?? ConnectionFeatures[key];
}
set
{
_featureRevision++;
if (key == IHttpRequestFeatureType)
{
_currentIHttpRequestFeature = value;
}
else if (key == IHttpResponseFeatureType)
{
_currentIHttpResponseFeature = value;
}
else if (key == IHttpRequestIdentifierFeatureType)
{
_currentIHttpRequestIdentifierFeature = value;
}
else if (key == IServiceProvidersFeatureType)
{
_currentIServiceProvidersFeature = value;
}
else if (key == IHttpRequestLifetimeFeatureType)
{
_currentIHttpRequestLifetimeFeature = value;
}
else if (key == IHttpConnectionFeatureType)
{
_currentIHttpConnectionFeature = value;
}
else if (key == IHttpAuthenticationFeatureType)
{
_currentIHttpAuthenticationFeature = value;
}
else if (key == IQueryFeatureType)
{
_currentIQueryFeature = value;
}
else if (key == IFormFeatureType)
{
_currentIFormFeature = value;
}
else if (key == IHttpUpgradeFeatureType)
{
_currentIHttpUpgradeFeature = value;
}
else if (key == IHttp2StreamIdFeatureType)
{
_currentIHttp2StreamIdFeature = value;
}
else if (key == IResponseCookiesFeatureType)
{
_currentIResponseCookiesFeature = value;
}
else if (key == IItemsFeatureType)
{
_currentIItemsFeature = value;
}
else if (key == ITlsConnectionFeatureType)
{
_currentITlsConnectionFeature = value;
}
else if (key == IHttpWebSocketFeatureType)
{
_currentIHttpWebSocketFeature = value;
}
else if (key == ISessionFeatureType)
{
_currentISessionFeature = value;
}
else if (key == IHttpMaxRequestBodySizeFeatureType)
{
_currentIHttpMaxRequestBodySizeFeature = value;
}
else if (key == IHttpMinRequestBodyDataRateFeatureType)
{
_currentIHttpMinRequestBodyDataRateFeature = value;
}
else if (key == IHttpMinResponseDataRateFeatureType)
{
_currentIHttpMinResponseDataRateFeature = value;
}
else if (key == IHttpBodyControlFeatureType)
{
_currentIHttpBodyControlFeature = value;
}
else if (key == IHttpSendFileFeatureType)
{
_currentIHttpSendFileFeature = value;
}
else
{
ExtraFeatureSet(key, value);
}
}
}
void IFeatureCollection.Set<TFeature>(TFeature feature)
{
_featureRevision++;
if (typeof(TFeature) == typeof(IHttpRequestFeature))
{
_currentIHttpRequestFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpResponseFeature))
{
_currentIHttpResponseFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpRequestIdentifierFeature))
{
_currentIHttpRequestIdentifierFeature = feature;
}
else if (typeof(TFeature) == typeof(IServiceProvidersFeature))
{
_currentIServiceProvidersFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpRequestLifetimeFeature))
{
_currentIHttpRequestLifetimeFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpConnectionFeature))
{
_currentIHttpConnectionFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature))
{
_currentIHttpAuthenticationFeature = feature;
}
else if (typeof(TFeature) == typeof(IQueryFeature))
{
_currentIQueryFeature = feature;
}
else if (typeof(TFeature) == typeof(IFormFeature))
{
_currentIFormFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpUpgradeFeature))
{
_currentIHttpUpgradeFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttp2StreamIdFeature))
{
_currentIHttp2StreamIdFeature = feature;
}
else if (typeof(TFeature) == typeof(IResponseCookiesFeature))
{
_currentIResponseCookiesFeature = feature;
}
else if (typeof(TFeature) == typeof(IItemsFeature))
{
_currentIItemsFeature = feature;
}
else if (typeof(TFeature) == typeof(ITlsConnectionFeature))
{
_currentITlsConnectionFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpWebSocketFeature))
{
_currentIHttpWebSocketFeature = feature;
}
else if (typeof(TFeature) == typeof(ISessionFeature))
{
_currentISessionFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpMaxRequestBodySizeFeature))
{
_currentIHttpMaxRequestBodySizeFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpMinRequestBodyDataRateFeature))
{
_currentIHttpMinRequestBodyDataRateFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpMinResponseDataRateFeature))
{
_currentIHttpMinResponseDataRateFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpBodyControlFeature))
{
_currentIHttpBodyControlFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpSendFileFeature))
{
_currentIHttpSendFileFeature = feature;
}
else
{
ExtraFeatureSet(typeof(TFeature), feature);
}
}
TFeature IFeatureCollection.Get<TFeature>()
{
TFeature feature = default;
if (typeof(TFeature) == typeof(IHttpRequestFeature))
{
feature = (TFeature)_currentIHttpRequestFeature;
}
else if (typeof(TFeature) == typeof(IHttpResponseFeature))
{
feature = (TFeature)_currentIHttpResponseFeature;
}
else if (typeof(TFeature) == typeof(IHttpRequestIdentifierFeature))
{
feature = (TFeature)_currentIHttpRequestIdentifierFeature;
}
else if (typeof(TFeature) == typeof(IServiceProvidersFeature))
{
feature = (TFeature)_currentIServiceProvidersFeature;
}
else if (typeof(TFeature) == typeof(IHttpRequestLifetimeFeature))
{
feature = (TFeature)_currentIHttpRequestLifetimeFeature;
}
else if (typeof(TFeature) == typeof(IHttpConnectionFeature))
{
feature = (TFeature)_currentIHttpConnectionFeature;
}
else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature))
{
feature = (TFeature)_currentIHttpAuthenticationFeature;
}
else if (typeof(TFeature) == typeof(IQueryFeature))
{
feature = (TFeature)_currentIQueryFeature;
}
else if (typeof(TFeature) == typeof(IFormFeature))
{
feature = (TFeature)_currentIFormFeature;
}
else if (typeof(TFeature) == typeof(IHttpUpgradeFeature))
{
feature = (TFeature)_currentIHttpUpgradeFeature;
}
else if (typeof(TFeature) == typeof(IHttp2StreamIdFeature))
{
feature = (TFeature)_currentIHttp2StreamIdFeature;
}
else if (typeof(TFeature) == typeof(IResponseCookiesFeature))
{
feature = (TFeature)_currentIResponseCookiesFeature;
}
else if (typeof(TFeature) == typeof(IItemsFeature))
{
feature = (TFeature)_currentIItemsFeature;
}
else if (typeof(TFeature) == typeof(ITlsConnectionFeature))
{
feature = (TFeature)_currentITlsConnectionFeature;
}
else if (typeof(TFeature) == typeof(IHttpWebSocketFeature))
{
feature = (TFeature)_currentIHttpWebSocketFeature;
}
else if (typeof(TFeature) == typeof(ISessionFeature))
{
feature = (TFeature)_currentISessionFeature;
}
else if (typeof(TFeature) == typeof(IHttpMaxRequestBodySizeFeature))
{
feature = (TFeature)_currentIHttpMaxRequestBodySizeFeature;
}
else if (typeof(TFeature) == typeof(IHttpMinRequestBodyDataRateFeature))
{
feature = (TFeature)_currentIHttpMinRequestBodyDataRateFeature;
}
else if (typeof(TFeature) == typeof(IHttpMinResponseDataRateFeature))
{
feature = (TFeature)_currentIHttpMinResponseDataRateFeature;
}
else if (typeof(TFeature) == typeof(IHttpBodyControlFeature))
{
feature = (TFeature)_currentIHttpBodyControlFeature;
}
else if (typeof(TFeature) == typeof(IHttpSendFileFeature))
{
feature = (TFeature)_currentIHttpSendFileFeature;
}
else if (MaybeExtra != null)
{
feature = (TFeature)(ExtraFeatureGet(typeof(TFeature)));
}
if (feature == null)
{
feature = ConnectionFeatures.Get<TFeature>();
}
return feature;
}
private IEnumerable<KeyValuePair<Type, object>> FastEnumerable()
{
if (_currentIHttpRequestFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpRequestFeatureType, _currentIHttpRequestFeature as IHttpRequestFeature);
}
if (_currentIHttpResponseFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpResponseFeatureType, _currentIHttpResponseFeature as IHttpResponseFeature);
}
if (_currentIHttpRequestIdentifierFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpRequestIdentifierFeatureType, _currentIHttpRequestIdentifierFeature as IHttpRequestIdentifierFeature);
}
if (_currentIServiceProvidersFeature != null)
{
yield return new KeyValuePair<Type, object>(IServiceProvidersFeatureType, _currentIServiceProvidersFeature as IServiceProvidersFeature);
}
if (_currentIHttpRequestLifetimeFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpRequestLifetimeFeatureType, _currentIHttpRequestLifetimeFeature as IHttpRequestLifetimeFeature);
}
if (_currentIHttpConnectionFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpConnectionFeatureType, _currentIHttpConnectionFeature as IHttpConnectionFeature);
}
if (_currentIHttpAuthenticationFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature as IHttpAuthenticationFeature);
}
if (_currentIQueryFeature != null)
{
yield return new KeyValuePair<Type, object>(IQueryFeatureType, _currentIQueryFeature as IQueryFeature);
}
if (_currentIFormFeature != null)
{
yield return new KeyValuePair<Type, object>(IFormFeatureType, _currentIFormFeature as IFormFeature);
}
if (_currentIHttpUpgradeFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpUpgradeFeatureType, _currentIHttpUpgradeFeature as IHttpUpgradeFeature);
}
if (_currentIHttp2StreamIdFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttp2StreamIdFeatureType, _currentIHttp2StreamIdFeature as IHttp2StreamIdFeature);
}
if (_currentIResponseCookiesFeature != null)
{
yield return new KeyValuePair<Type, object>(IResponseCookiesFeatureType, _currentIResponseCookiesFeature as IResponseCookiesFeature);
}
if (_currentIItemsFeature != null)
{
yield return new KeyValuePair<Type, object>(IItemsFeatureType, _currentIItemsFeature as IItemsFeature);
}
if (_currentITlsConnectionFeature != null)
{
yield return new KeyValuePair<Type, object>(ITlsConnectionFeatureType, _currentITlsConnectionFeature as ITlsConnectionFeature);
}
if (_currentIHttpWebSocketFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpWebSocketFeatureType, _currentIHttpWebSocketFeature as IHttpWebSocketFeature);
}
if (_currentISessionFeature != null)
{
yield return new KeyValuePair<Type, object>(ISessionFeatureType, _currentISessionFeature as ISessionFeature);
}
if (_currentIHttpMaxRequestBodySizeFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpMaxRequestBodySizeFeatureType, _currentIHttpMaxRequestBodySizeFeature as IHttpMaxRequestBodySizeFeature);
}
if (_currentIHttpMinRequestBodyDataRateFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpMinRequestBodyDataRateFeatureType, _currentIHttpMinRequestBodyDataRateFeature as IHttpMinRequestBodyDataRateFeature);
}
if (_currentIHttpMinResponseDataRateFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpMinResponseDataRateFeatureType, _currentIHttpMinResponseDataRateFeature as IHttpMinResponseDataRateFeature);
}
if (_currentIHttpBodyControlFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpBodyControlFeatureType, _currentIHttpBodyControlFeature as IHttpBodyControlFeature);
}
if (_currentIHttpSendFileFeature != null)
{
yield return new KeyValuePair<Type, object>(IHttpSendFileFeatureType, _currentIHttpSendFileFeature as IHttpSendFileFeature);
}
if (MaybeExtra != null)
{
foreach(var item in MaybeExtra)
{
yield return item;
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
// 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 System.Runtime.InteropServices;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public partial class HttpRequestHeaders : HttpHeaders
{
private static long ParseContentLength(string value)
{
long parsed;
if (!HeaderUtilities.TryParseNonNegativeInt64(value, out parsed))
{
BadHttpRequestException.Throw(RequestRejectionReason.InvalidContentLength, value);
}
return parsed;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void SetValueUnknown(string key, in StringValues value)
{
Unknown[key] = value;
}
public unsafe void Append(Span<byte> name, string value)
{
fixed (byte* namePtr = &MemoryMarshal.GetReference(name))
{
Append(namePtr, name.Length, 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 (!StringUtilities.TryGetAsciiString(pKeyBytes, keyBuffer, keyLength))
{
BadHttpRequestException.Throw(RequestRejectionReason.InvalidCharactersInHeaderName);
}
}
StringValues existing;
Unknown.TryGetValue(key, out existing);
Unknown[key] = AppendValue(existing, value);
}
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
protected override IEnumerator<KeyValuePair<string, StringValues>> GetEnumeratorFast()
{
return GetEnumerator();
}
public partial struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
{
private readonly HttpRequestHeaders _collection;
private readonly long _bits;
private int _state;
private KeyValuePair<string, StringValues> _current;
private readonly bool _hasUnknown;
private Dictionary<string, StringValues>.Enumerator _unknownEnumerator;
internal Enumerator(HttpRequestHeaders collection)
{
_collection = collection;
_bits = collection._bits;
_state = 0;
_current = default(KeyValuePair<string, StringValues>);
_hasUnknown = collection.MaybeUnknown != null;
_unknownEnumerator = _hasUnknown
? collection.MaybeUnknown.GetEnumerator()
: default(Dictionary<string, StringValues>.Enumerator);
}
public KeyValuePair<string, StringValues> Current => _current;
object IEnumerator.Current => _current;
public void Dispose()
{
}
public void Reset()
{
_state = 0;
}
}
}
}

View File

@ -0,0 +1,217 @@
// 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.IO;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
internal class HttpRequestStream : ReadOnlyStream
{
private readonly IHttpBodyControlFeature _bodyControl;
private MessageBody _body;
private HttpStreamState _state;
private Exception _error;
public HttpRequestStream(IHttpBodyControlFeature bodyControl)
{
_bodyControl = bodyControl;
_state = HttpStreamState.Closed;
}
public override bool CanSeek => false;
public override long Length
=> throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush()
{
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (!_bodyControl.AllowSynchronousIO)
{
throw new InvalidOperationException(CoreStrings.SynchronousReadsDisallowed);
}
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
var task = ReadAsync(buffer, offset, count, default(CancellationToken), state);
if (callback != null)
{
task.ContinueWith(t => callback.Invoke(t));
}
return task;
}
public override int EndRead(IAsyncResult asyncResult)
{
return ((Task<int>)asyncResult).GetAwaiter().GetResult();
}
private Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
{
var tcs = new TaskCompletionSource<int>(state);
var task = ReadAsync(buffer, offset, count, cancellationToken);
task.ContinueWith((task2, state2) =>
{
var tcs2 = (TaskCompletionSource<int>)state2;
if (task2.IsCanceled)
{
tcs2.SetCanceled();
}
else if (task2.IsFaulted)
{
tcs2.SetException(task2.Exception);
}
else
{
tcs2.SetResult(task2.Result);
}
}, tcs, cancellationToken);
return tcs.Task;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateState(cancellationToken);
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}
#if NETCOREAPP2_1
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
ValidateState(cancellationToken);
return ReadAsyncInternal(destination, cancellationToken);
}
#endif
private async ValueTask<int> ReadAsyncInternal(Memory<byte> buffer, CancellationToken cancellationToken)
{
try
{
return await _body.ReadAsync(buffer, cancellationToken);
}
catch (ConnectionAbortedException ex)
{
throw new TaskCanceledException("The request was aborted", ex);
}
}
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
if (destination == null)
{
throw new ArgumentNullException(nameof(destination));
}
if (bufferSize <= 0)
{
throw new ArgumentException(CoreStrings.PositiveNumberRequired, nameof(bufferSize));
}
ValidateState(cancellationToken);
return CopyToAsyncInternal(destination, cancellationToken);
}
private async Task CopyToAsyncInternal(Stream destination, CancellationToken cancellationToken)
{
try
{
await _body.CopyToAsync(destination, cancellationToken);
}
catch (ConnectionAbortedException ex)
{
throw new TaskCanceledException("The request was aborted", ex);
}
}
public void StartAcceptingReads(MessageBody body)
{
// Only start if not aborted
if (_state == HttpStreamState.Closed)
{
_state = HttpStreamState.Open;
_body = body;
}
}
public void StopAcceptingReads()
{
// Can't use dispose (or close) as can be disposed too early by user code
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
_state = HttpStreamState.Closed;
_body = null;
}
public void Abort(Exception error = null)
{
// We don't want to throw an ODE until the app func actually completes.
// If the request is aborted, we throw a TaskCanceledException instead,
// unless error is not null, in which case we throw it.
if (_state != HttpStreamState.Closed)
{
_state = HttpStreamState.Aborted;
_error = error;
}
}
private void ValidateState(CancellationToken cancellationToken)
{
switch (_state)
{
case HttpStreamState.Open:
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
break;
case HttpStreamState.Closed:
throw new ObjectDisposedException(nameof(HttpRequestStream));
case HttpStreamState.Aborted:
if (_error != null)
{
ExceptionDispatchInfo.Capture(_error).Throw();
}
else
{
throw new TaskCanceledException();
}
break;
}
}
}
}

View File

@ -0,0 +1,15 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public enum HttpRequestTarget
{
Unknown = -1,
// origin-form is the most common
OriginForm,
AbsoluteForm,
AuthorityForm,
AsteriskForm
}
}

View File

@ -0,0 +1,110 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public partial class HttpResponseHeaders : HttpHeaders
{
private static readonly byte[] _CrLf = new[] { (byte)'\r', (byte)'\n' };
private static readonly byte[] _colonSpace = new[] { (byte)':', (byte)' ' };
public Enumerator GetEnumerator()
{
return new Enumerator(this);
}
protected override IEnumerator<KeyValuePair<string, StringValues>> GetEnumeratorFast()
{
return GetEnumerator();
}
internal void CopyTo(ref BufferWriter<PipeWriter> buffer)
{
CopyToFast(ref buffer);
if (MaybeUnknown != null)
{
foreach (var kv in MaybeUnknown)
{
foreach (var value in kv.Value)
{
if (value != null)
{
buffer.Write(_CrLf);
PipelineExtensions.WriteAsciiNoValidation(ref buffer, kv.Key);
buffer.Write(_colonSpace);
PipelineExtensions.WriteAsciiNoValidation(ref buffer, value);
}
}
}
}
}
private static long ParseContentLength(string value)
{
long parsed;
if (!HeaderUtilities.TryParseNonNegativeInt64(value, out parsed))
{
ThrowInvalidContentLengthException(value);
}
return parsed;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void SetValueUnknown(string key, in StringValues value)
{
ValidateHeaderCharacters(key);
Unknown[key] = value;
}
private static void ThrowInvalidContentLengthException(string value)
{
throw new InvalidOperationException(CoreStrings.FormatInvalidContentLength_InvalidNumber(value));
}
public partial struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
{
private readonly HttpResponseHeaders _collection;
private readonly long _bits;
private int _state;
private KeyValuePair<string, StringValues> _current;
private readonly bool _hasUnknown;
private Dictionary<string, StringValues>.Enumerator _unknownEnumerator;
internal Enumerator(HttpResponseHeaders collection)
{
_collection = collection;
_bits = collection._bits;
_state = 0;
_current = default;
_hasUnknown = collection.MaybeUnknown != null;
_unknownEnumerator = _hasUnknown
? collection.MaybeUnknown.GetEnumerator()
: default;
}
public KeyValuePair<string, StringValues> Current => _current;
object IEnumerator.Current => _current;
public void Dispose()
{
}
public void Reset()
{
_state = 0;
}
}
}
}

View File

@ -0,0 +1,170 @@
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
internal class HttpResponseStream : WriteOnlyStream
{
private readonly IHttpBodyControlFeature _bodyControl;
private readonly IHttpResponseControl _httpResponseControl;
private HttpStreamState _state;
public HttpResponseStream(IHttpBodyControlFeature bodyControl, IHttpResponseControl httpResponseControl)
{
_bodyControl = bodyControl;
_httpResponseControl = httpResponseControl;
_state = HttpStreamState.Closed;
}
public override bool CanSeek => false;
public override long Length
=> throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush()
{
FlushAsync(default(CancellationToken)).GetAwaiter().GetResult();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
ValidateState(cancellationToken);
return _httpResponseControl.FlushAsync(cancellationToken);
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
if (!_bodyControl.AllowSynchronousIO)
{
throw new InvalidOperationException(CoreStrings.SynchronousWritesDisallowed);
}
WriteAsync(buffer, offset, count, default(CancellationToken)).GetAwaiter().GetResult();
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
var task = WriteAsync(buffer, offset, count, default(CancellationToken), state);
if (callback != null)
{
task.ContinueWith(t => callback.Invoke(t));
}
return task;
}
public override void EndWrite(IAsyncResult asyncResult)
{
((Task<object>)asyncResult).GetAwaiter().GetResult();
}
private Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken, object state)
{
var tcs = new TaskCompletionSource<object>(state);
var task = WriteAsync(buffer, offset, count, cancellationToken);
task.ContinueWith((task2, state2) =>
{
var tcs2 = (TaskCompletionSource<object>)state2;
if (task2.IsCanceled)
{
tcs2.SetCanceled();
}
else if (task2.IsFaulted)
{
tcs2.SetException(task2.Exception);
}
else
{
tcs2.SetResult(null);
}
}, tcs, cancellationToken);
return tcs.Task;
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateState(cancellationToken);
return _httpResponseControl.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
}
#if NETCOREAPP2_1
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
ValidateState(cancellationToken);
return new ValueTask(_httpResponseControl.WriteAsync(source, cancellationToken));
}
#endif
public void StartAcceptingWrites()
{
// Only start if not aborted
if (_state == HttpStreamState.Closed)
{
_state = HttpStreamState.Open;
}
}
public void StopAcceptingWrites()
{
// Can't use dispose (or close) as can be disposed too early by user code
// As exampled in EngineTests.ZeroContentLengthNotSetAutomaticallyForCertainStatusCodes
_state = HttpStreamState.Closed;
}
public void Abort()
{
// We don't want to throw an ODE until the app func actually completes.
if (_state != HttpStreamState.Closed)
{
_state = HttpStreamState.Aborted;
}
}
private void ValidateState(CancellationToken cancellationToken)
{
switch (_state)
{
case HttpStreamState.Open:
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
break;
case HttpStreamState.Closed:
throw new ObjectDisposedException(nameof(HttpResponseStream), CoreStrings.WritingToResponseBodyAfterResponseCompleted);
case HttpStreamState.Aborted:
if (cancellationToken.IsCancellationRequested)
{
// Aborted state only throws on write if cancellationToken requests it
cancellationToken.ThrowIfCancellationRequested();
}
break;
}
}
}
}

View File

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public enum HttpScheme
{
Unknown = -1,
Http = 0,
Https = 1
}
}

View File

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
enum HttpStreamState
{
Open,
Closed,
Aborted
}
}

View File

@ -0,0 +1,202 @@
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
internal class HttpUpgradeStream : Stream
{
private readonly Stream _requestStream;
private readonly Stream _responseStream;
public HttpUpgradeStream(Stream requestStream, Stream responseStream)
{
_requestStream = requestStream;
_responseStream = responseStream;
}
public override bool CanRead
{
get
{
return _requestStream.CanRead;
}
}
public override bool CanSeek
{
get
{
return _requestStream.CanSeek;
}
}
public override bool CanTimeout
{
get
{
return _responseStream.CanTimeout || _requestStream.CanTimeout;
}
}
public override bool CanWrite
{
get
{
return _responseStream.CanWrite;
}
}
public override long Length
{
get
{
return _requestStream.Length;
}
}
public override long Position
{
get
{
return _requestStream.Position;
}
set
{
_requestStream.Position = value;
}
}
public override int ReadTimeout
{
get
{
return _requestStream.ReadTimeout;
}
set
{
_requestStream.ReadTimeout = value;
}
}
public override int WriteTimeout
{
get
{
return _responseStream.WriteTimeout;
}
set
{
_responseStream.WriteTimeout = value;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_requestStream.Dispose();
_responseStream.Dispose();
}
}
public override void Flush()
{
_responseStream.Flush();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return _responseStream.FlushAsync(cancellationToken);
}
public override void Close()
{
_requestStream.Close();
_responseStream.Close();
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _requestStream.BeginRead(buffer, offset, count, callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
return _requestStream.EndRead(asyncResult);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return _responseStream.BeginWrite(buffer, offset, count, callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
_responseStream.EndWrite(asyncResult);
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _requestStream.ReadAsync(buffer, offset, count, cancellationToken);
}
#if NETCOREAPP2_1
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
return _requestStream.ReadAsync(destination, cancellationToken);
}
#endif
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
return _requestStream.CopyToAsync(destination, bufferSize, cancellationToken);
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _responseStream.WriteAsync(buffer, offset, count, cancellationToken);
}
#if NETCOREAPP2_1
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
return _responseStream.WriteAsync(source, cancellationToken);
}
#endif
public override long Seek(long offset, SeekOrigin origin)
{
return _requestStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
_requestStream.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count)
{
return _requestStream.Read(buffer, offset, count);
}
public override int ReadByte()
{
return _requestStream.ReadByte();
}
public override void Write(byte[] buffer, int offset, int count)
{
_responseStream.Write(buffer, offset, count);
}
public override void WriteByte(byte value)
{
_responseStream.WriteByte(value);
}
}
}

View File

@ -0,0 +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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public enum HttpVersion
{
Unknown = -1,
Http10 = 0,
Http11 = 1,
Http2
}
}

View File

@ -0,0 +1,12 @@
// 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.Core.Internal.Http
{
public interface IHttpHeadersHandler
{
void OnHeader(Span<byte> name, Span<byte> value);
}
}

View File

@ -0,0 +1,25 @@
// 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.IO.Pipelines;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public interface IHttpOutputProducer : IDisposable
{
void Abort(ConnectionAbortedException abortReason);
Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state);
Task FlushAsync(CancellationToken cancellationToken);
Task Write100ContinueAsync(CancellationToken cancellationToken);
void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders);
// The reason this is ReadOnlySpan and not ReadOnlyMemory is because writes are always
// synchronous. Flushing to get back pressure is the only time we truly go async but
// that's after the buffer is copied
Task WriteDataAsync(ReadOnlySpan<byte> data, CancellationToken cancellationToken);
Task WriteStreamSuffixAsync(CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,15 @@
// 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.Buffers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public interface IHttpParser<TRequestHandler> where TRequestHandler : IHttpHeadersHandler, IHttpRequestLineHandler
{
bool ParseRequestLine(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined);
bool ParseHeaders(TRequestHandler handler, in ReadOnlySequence<byte> buffer, out SequencePosition consumed, out SequencePosition examined, out int consumedBytes);
}
}

View File

@ -0,0 +1,19 @@
// 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.Buffers;
using System.Net;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public interface IHttpProtocolContext
{
string ConnectionId { get; set; }
ServiceContext ServiceContext { get; set; }
IFeatureCollection ConnectionFeatures { get; set; }
MemoryPool<byte> MemoryPool { get; set; }
IPEndPoint RemoteEndPoint { get; set; }
IPEndPoint LocalEndPoint { get; set; }
}
}

View File

@ -0,0 +1,12 @@
// 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.Core.Internal.Http
{
public interface IHttpRequestLineHandler
{
void OnStartLine(HttpMethod method, HttpVersion version, Span<byte> target, Span<byte> path, Span<byte> query, Span<byte> customMethod, bool pathEncoded);
}
}

View File

@ -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;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public interface IHttpResponseControl
{
void ProduceContinue();
Task WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
Task FlushAsync(CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,172 @@
// 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.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.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 HttpProtocol _context;
private bool _send100Continue = true;
protected MessageBody(HttpProtocol context)
{
_context = context;
}
public static MessageBody ZeroContentLengthClose => _zeroContentLengthClose;
public static MessageBody ZeroContentLengthKeepAlive => _zeroContentLengthKeepAlive;
public bool RequestKeepAlive { get; protected set; }
public bool RequestUpgrade { get; protected set; }
public virtual bool IsEmpty => false;
protected IKestrelTrace Log => _context.ServiceContext.Log;
public virtual async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
{
TryInit();
while (true)
{
var result = await _context.RequestBodyPipe.Reader.ReadAsync();
var readableBuffer = result.Buffer;
var consumed = readableBuffer.End;
try
{
if (!readableBuffer.IsEmpty)
{
// buffer.Count is int
var actual = (int) Math.Min(readableBuffer.Length, buffer.Length);
var slice = readableBuffer.Slice(0, actual);
consumed = readableBuffer.GetPosition(actual);
slice.CopyTo(buffer.Span);
return actual;
}
if (result.IsCompleted)
{
return 0;
}
}
finally
{
_context.RequestBodyPipe.Reader.AdvanceTo(consumed);
}
}
}
public virtual async Task CopyToAsync(Stream destination, CancellationToken cancellationToken = default(CancellationToken))
{
TryInit();
while (true)
{
var result = await _context.RequestBodyPipe.Reader.ReadAsync();
var readableBuffer = result.Buffer;
var consumed = readableBuffer.End;
try
{
if (!readableBuffer.IsEmpty)
{
foreach (var memory in readableBuffer)
{
// REVIEW: This *could* be slower if 2 things are true
// - The WriteAsync(ReadOnlyMemory<byte>) isn't overridden on the destination
// - We change the Kestrel Memory Pool to not use pinned arrays but instead use native memory
#if NETCOREAPP2_1
await destination.WriteAsync(memory);
#else
var array = memory.GetArray();
await destination.WriteAsync(array.Array, array.Offset, array.Count, cancellationToken);
#endif
}
}
if (result.IsCompleted)
{
return;
}
}
finally
{
_context.RequestBodyPipe.Reader.AdvanceTo(consumed);
}
}
}
public virtual Task ConsumeAsync()
{
TryInit();
return OnConsumeAsync();
}
protected abstract Task OnConsumeAsync();
public abstract Task StopAsync();
protected void TryProduceContinue()
{
if (_send100Continue)
{
_context.HttpResponseControl.ProduceContinue();
_send100Continue = false;
}
}
private void TryInit()
{
if (!_context.HasStartedConsumingRequestBody)
{
OnReadStarting();
_context.HasStartedConsumingRequestBody = true;
OnReadStarted();
}
}
protected virtual void OnReadStarting()
{
}
protected virtual void OnReadStarted()
{
}
private class ForZeroContentLength : MessageBody
{
public ForZeroContentLength(bool keepAlive)
: base(null)
{
RequestKeepAlive = keepAlive;
}
public override bool IsEmpty => true;
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken)) => new ValueTask<int>(0);
public override Task CopyToAsync(Stream destination, CancellationToken cancellationToken = default(CancellationToken)) => Task.CompletedTask;
public override Task ConsumeAsync() => Task.CompletedTask;
public override Task StopAsync() => Task.CompletedTask;
protected override Task OnConsumeAsync() => Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,208 @@
// 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.Diagnostics;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public static class PathNormalizer
{
private const byte ByteSlash = (byte)'/';
private const byte ByteDot = (byte)'.';
// In-place implementation of the algorithm from https://tools.ietf.org/html/rfc3986#section-5.2.4
public static unsafe int RemoveDotSegments(Span<byte> input)
{
fixed (byte* start = &MemoryMarshal.GetReference(input))
{
var end = start + input.Length;
return RemoveDotSegments(start, end);
}
}
public static unsafe int RemoveDotSegments(byte* start, byte* end)
{
if (!ContainsDotSegments(start, end))
{
return (int)(end - start);
}
var src = start;
var dst = start;
while (src < end)
{
var ch1 = *src;
Debug.Assert(ch1 == '/', "Path segment must always start with a '/'");
byte ch2, ch3, ch4;
switch (end - src)
{
case 1:
break;
case 2:
ch2 = *(src + 1);
if (ch2 == ByteDot)
{
// B. if the input buffer begins with a prefix of "/./" or "/.",
// where "." is a complete path segment, then replace that
// prefix with "/" in the input buffer; otherwise,
src += 1;
*src = ByteSlash;
continue;
}
break;
case 3:
ch2 = *(src + 1);
ch3 = *(src + 2);
if (ch2 == ByteDot && ch3 == ByteDot)
{
// C. if the input buffer begins with a prefix of "/../" or "/..",
// where ".." is a complete path segment, then replace that
// prefix with "/" in the input buffer and remove the last
// segment and its preceding "/" (if any) from the output
// buffer; otherwise,
src += 2;
*src = ByteSlash;
if (dst > start)
{
do
{
dst--;
} while (dst > start && *dst != ByteSlash);
}
continue;
}
else if (ch2 == ByteDot && ch3 == ByteSlash)
{
// B. if the input buffer begins with a prefix of "/./" or "/.",
// where "." is a complete path segment, then replace that
// prefix with "/" in the input buffer; otherwise,
src += 2;
continue;
}
break;
default:
ch2 = *(src + 1);
ch3 = *(src + 2);
ch4 = *(src + 3);
if (ch2 == ByteDot && ch3 == ByteDot && ch4 == ByteSlash)
{
// C. if the input buffer begins with a prefix of "/../" or "/..",
// where ".." is a complete path segment, then replace that
// prefix with "/" in the input buffer and remove the last
// segment and its preceding "/" (if any) from the output
// buffer; otherwise,
src += 3;
if (dst > start)
{
do
{
dst--;
} while (dst > start && *dst != ByteSlash);
}
continue;
}
else if (ch2 == ByteDot && ch3 == ByteSlash)
{
// B. if the input buffer begins with a prefix of "/./" or "/.",
// where "." is a complete path segment, then replace that
// prefix with "/" in the input buffer; otherwise,
src += 2;
continue;
}
break;
}
// E. move the first path segment in the input buffer to the end of
// the output buffer, including the initial "/" character (if
// any) and any subsequent characters up to, but not including,
// the next "/" character or the end of the input buffer.
do
{
*dst++ = ch1;
ch1 = *++src;
} while (src < end && ch1 != ByteSlash);
}
if (dst == start)
{
*dst++ = ByteSlash;
}
return (int)(dst - start);
}
public static unsafe bool ContainsDotSegments(byte* start, byte* end)
{
var src = start;
var dst = start;
while (src < end)
{
var ch1 = *src;
Debug.Assert(ch1 == '/', "Path segment must always start with a '/'");
byte ch2, ch3, ch4;
switch (end - src)
{
case 1:
break;
case 2:
ch2 = *(src + 1);
if (ch2 == ByteDot)
{
return true;
}
break;
case 3:
ch2 = *(src + 1);
ch3 = *(src + 2);
if ((ch2 == ByteDot && ch3 == ByteDot) ||
(ch2 == ByteDot && ch3 == ByteSlash))
{
return true;
}
break;
default:
ch2 = *(src + 1);
ch3 = *(src + 2);
ch4 = *(src + 3);
if ((ch2 == ByteDot && ch3 == ByteDot && ch4 == ByteSlash) ||
(ch2 == ByteDot && ch3 == ByteSlash))
{
return true;
}
break;
}
do
{
ch1 = *++src;
} while (src < end && ch1 != ByteSlash);
}
return false;
}
}
}

View File

@ -0,0 +1,273 @@
// 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.Buffers;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public static class PipelineExtensions
{
private const int _maxULongByteLength = 20;
[ThreadStatic]
private static byte[] _numericBytesScratch;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<byte> ToSpan(this ReadOnlySequence<byte> buffer)
{
if (buffer.IsSingleSegment)
{
return buffer.First.Span;
}
return buffer.ToArray();
}
public static ArraySegment<byte> GetArray(this Memory<byte> buffer)
{
return ((ReadOnlyMemory<byte>)buffer).GetArray();
}
public static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> memory)
{
if (!MemoryMarshal.TryGetArray(memory, out var result))
{
throw new InvalidOperationException("Buffer backed by array was expected");
}
return result;
}
internal static unsafe void WriteAsciiNoValidation(ref this BufferWriter<PipeWriter> buffer, string data)
{
if (string.IsNullOrEmpty(data))
{
return;
}
var dest = buffer.Span;
var destLength = dest.Length;
var sourceLength = data.Length;
// Fast path, try copying to the available memory directly
if (sourceLength <= destLength)
{
fixed (char* input = data)
fixed (byte* output = &MemoryMarshal.GetReference(dest))
{
EncodeAsciiCharsToBytes(input, output, sourceLength);
}
buffer.Advance(sourceLength);
}
else
{
WriteAsciiMultiWrite(ref buffer, data);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe void WriteNumeric(ref this BufferWriter<PipeWriter> buffer, ulong number)
{
const byte AsciiDigitStart = (byte)'0';
var span = buffer.Span;
var bytesLeftInBlock = span.Length;
// Fast path, try copying to the available memory directly
var simpleWrite = true;
fixed (byte* output = &MemoryMarshal.GetReference(span))
{
var start = output;
if (number < 10 && bytesLeftInBlock >= 1)
{
*(start) = (byte)(((uint)number) + AsciiDigitStart);
buffer.Advance(1);
}
else if (number < 100 && bytesLeftInBlock >= 2)
{
var val = (uint)number;
var tens = (byte)((val * 205u) >> 11); // div10, valid to 1028
*(start) = (byte)(tens + AsciiDigitStart);
*(start + 1) = (byte)(val - (tens * 10) + AsciiDigitStart);
buffer.Advance(2);
}
else if (number < 1000 && bytesLeftInBlock >= 3)
{
var val = (uint)number;
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);
buffer.Advance(3);
}
else
{
simpleWrite = false;
}
}
if (!simpleWrite)
{
WriteNumericMultiWrite(ref buffer, number);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void WriteNumericMultiWrite(ref this BufferWriter<PipeWriter> buffer, ulong number)
{
const byte AsciiDigitStart = (byte)'0';
var value = number;
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);
var length = _maxULongByteLength - position;
buffer.Write(new ReadOnlySpan<byte>(byteBuffer, position, length));
}
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe static void WriteAsciiMultiWrite(ref this BufferWriter<PipeWriter> buffer, string data)
{
var remaining = data.Length;
fixed (char* input = data)
{
var inputSlice = input;
while (remaining > 0)
{
var writable = Math.Min(remaining, buffer.Span.Length);
if (writable == 0)
{
buffer.Ensure();
continue;
}
fixed (byte* output = &MemoryMarshal.GetReference(buffer.Span))
{
EncodeAsciiCharsToBytes(inputSlice, output, writable);
}
inputSlice += writable;
remaining -= writable;
buffer.Advance(writable);
}
}
}
private static unsafe void EncodeAsciiCharsToBytes(char* input, byte* output, int length)
{
// Note: Not BIGENDIAN or check for non-ascii
const int Shift16Shift24 = (1 << 16) | (1 << 24);
const int Shift8Identity = (1 << 8) | (1);
// Encode as bytes upto the first non-ASCII byte and return count encoded
int i = 0;
// Use Intrinsic switch
if (IntPtr.Size == 8) // 64 bit
{
if (length < 4) goto trailing;
int unaligned = (int)(((ulong)input) & 0x7) >> 1;
// Unaligned chars
for (; i < unaligned; i++)
{
char ch = *(input + i);
*(output + i) = (byte)ch; // Cast convert
}
// Aligned
int ulongDoubleCount = (length - i) & ~0x7;
for (; i < ulongDoubleCount; i += 8)
{
ulong inputUlong0 = *(ulong*)(input + i);
ulong inputUlong1 = *(ulong*)(input + i + 4);
// Pack 16 ASCII chars into 16 bytes
*(uint*)(output + i) =
((uint)((inputUlong0 * Shift16Shift24) >> 24) & 0xffff) |
((uint)((inputUlong0 * Shift8Identity) >> 24) & 0xffff0000);
*(uint*)(output + i + 4) =
((uint)((inputUlong1 * Shift16Shift24) >> 24) & 0xffff) |
((uint)((inputUlong1 * Shift8Identity) >> 24) & 0xffff0000);
}
if (length - 4 > i)
{
ulong inputUlong = *(ulong*)(input + i);
// Pack 8 ASCII chars into 8 bytes
*(uint*)(output + i) =
((uint)((inputUlong * Shift16Shift24) >> 24) & 0xffff) |
((uint)((inputUlong * Shift8Identity) >> 24) & 0xffff0000);
i += 4;
}
trailing:
for (; i < length; i++)
{
char ch = *(input + i);
*(output + i) = (byte)ch; // Cast convert
}
}
else // 32 bit
{
// Unaligned chars
if ((unchecked((int)input) & 0x2) != 0)
{
char ch = *input;
i = 1;
*(output) = (byte)ch; // Cast convert
}
// Aligned
int uintCount = (length - i) & ~0x3;
for (; i < uintCount; i += 4)
{
uint inputUint0 = *(uint*)(input + i);
uint inputUint1 = *(uint*)(input + i + 2);
// Pack 4 ASCII chars into 4 bytes
*(ushort*)(output + i) = (ushort)(inputUint0 | (inputUint0 >> 8));
*(ushort*)(output + i + 2) = (ushort)(inputUint1 | (inputUint1 >> 8));
}
if (length - 1 > i)
{
uint inputUint = *(uint*)(input + i);
// Pack 2 ASCII chars into 2 bytes
*(ushort*)(output + i) = (ushort)(inputUint | (inputUint >> 8));
i += 2;
}
if (i < length)
{
char ch = *(input + i);
*(output + i) = (byte)ch; // Cast convert
i = length;
}
}
}
private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch();
[MethodImpl(MethodImplOptions.NoInlining)]
private static byte[] CreateNumericBytesScratch()
{
var bytes = new byte[_maxULongByteLength];
_numericBytesScratch = bytes;
return bytes;
}
}
}

View File

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public enum ProduceEndType
{
SocketShutdown,
SocketDisconnect,
ConnectionKeepAlive,
}
}

View File

@ -0,0 +1,236 @@
// 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.Diagnostics;
using System.Globalization;
using System.Text;
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public static class ReasonPhrases
{
private static readonly byte[] _bytesStatus100 = CreateStatusBytes(StatusCodes.Status100Continue);
private static readonly byte[] _bytesStatus101 = CreateStatusBytes(StatusCodes.Status101SwitchingProtocols);
private static readonly byte[] _bytesStatus102 = CreateStatusBytes(StatusCodes.Status102Processing);
private static readonly byte[] _bytesStatus200 = CreateStatusBytes(StatusCodes.Status200OK);
private static readonly byte[] _bytesStatus201 = CreateStatusBytes(StatusCodes.Status201Created);
private static readonly byte[] _bytesStatus202 = CreateStatusBytes(StatusCodes.Status202Accepted);
private static readonly byte[] _bytesStatus203 = CreateStatusBytes(StatusCodes.Status203NonAuthoritative);
private static readonly byte[] _bytesStatus204 = CreateStatusBytes(StatusCodes.Status204NoContent);
private static readonly byte[] _bytesStatus205 = CreateStatusBytes(StatusCodes.Status205ResetContent);
private static readonly byte[] _bytesStatus206 = CreateStatusBytes(StatusCodes.Status206PartialContent);
private static readonly byte[] _bytesStatus207 = CreateStatusBytes(StatusCodes.Status207MultiStatus);
private static readonly byte[] _bytesStatus208 = CreateStatusBytes(StatusCodes.Status208AlreadyReported);
private static readonly byte[] _bytesStatus226 = CreateStatusBytes(StatusCodes.Status226IMUsed);
private static readonly byte[] _bytesStatus300 = CreateStatusBytes(StatusCodes.Status300MultipleChoices);
private static readonly byte[] _bytesStatus301 = CreateStatusBytes(StatusCodes.Status301MovedPermanently);
private static readonly byte[] _bytesStatus302 = CreateStatusBytes(StatusCodes.Status302Found);
private static readonly byte[] _bytesStatus303 = CreateStatusBytes(StatusCodes.Status303SeeOther);
private static readonly byte[] _bytesStatus304 = CreateStatusBytes(StatusCodes.Status304NotModified);
private static readonly byte[] _bytesStatus305 = CreateStatusBytes(StatusCodes.Status305UseProxy);
private static readonly byte[] _bytesStatus306 = CreateStatusBytes(StatusCodes.Status306SwitchProxy);
private static readonly byte[] _bytesStatus307 = CreateStatusBytes(StatusCodes.Status307TemporaryRedirect);
private static readonly byte[] _bytesStatus308 = CreateStatusBytes(StatusCodes.Status308PermanentRedirect);
private static readonly byte[] _bytesStatus400 = CreateStatusBytes(StatusCodes.Status400BadRequest);
private static readonly byte[] _bytesStatus401 = CreateStatusBytes(StatusCodes.Status401Unauthorized);
private static readonly byte[] _bytesStatus402 = CreateStatusBytes(StatusCodes.Status402PaymentRequired);
private static readonly byte[] _bytesStatus403 = CreateStatusBytes(StatusCodes.Status403Forbidden);
private static readonly byte[] _bytesStatus404 = CreateStatusBytes(StatusCodes.Status404NotFound);
private static readonly byte[] _bytesStatus405 = CreateStatusBytes(StatusCodes.Status405MethodNotAllowed);
private static readonly byte[] _bytesStatus406 = CreateStatusBytes(StatusCodes.Status406NotAcceptable);
private static readonly byte[] _bytesStatus407 = CreateStatusBytes(StatusCodes.Status407ProxyAuthenticationRequired);
private static readonly byte[] _bytesStatus408 = CreateStatusBytes(StatusCodes.Status408RequestTimeout);
private static readonly byte[] _bytesStatus409 = CreateStatusBytes(StatusCodes.Status409Conflict);
private static readonly byte[] _bytesStatus410 = CreateStatusBytes(StatusCodes.Status410Gone);
private static readonly byte[] _bytesStatus411 = CreateStatusBytes(StatusCodes.Status411LengthRequired);
private static readonly byte[] _bytesStatus412 = CreateStatusBytes(StatusCodes.Status412PreconditionFailed);
private static readonly byte[] _bytesStatus413 = CreateStatusBytes(StatusCodes.Status413PayloadTooLarge);
private static readonly byte[] _bytesStatus414 = CreateStatusBytes(StatusCodes.Status414UriTooLong);
private static readonly byte[] _bytesStatus415 = CreateStatusBytes(StatusCodes.Status415UnsupportedMediaType);
private static readonly byte[] _bytesStatus416 = CreateStatusBytes(StatusCodes.Status416RangeNotSatisfiable);
private static readonly byte[] _bytesStatus417 = CreateStatusBytes(StatusCodes.Status417ExpectationFailed);
private static readonly byte[] _bytesStatus418 = CreateStatusBytes(StatusCodes.Status418ImATeapot);
private static readonly byte[] _bytesStatus419 = CreateStatusBytes(StatusCodes.Status419AuthenticationTimeout);
private static readonly byte[] _bytesStatus421 = CreateStatusBytes(StatusCodes.Status421MisdirectedRequest);
private static readonly byte[] _bytesStatus422 = CreateStatusBytes(StatusCodes.Status422UnprocessableEntity);
private static readonly byte[] _bytesStatus423 = CreateStatusBytes(StatusCodes.Status423Locked);
private static readonly byte[] _bytesStatus424 = CreateStatusBytes(StatusCodes.Status424FailedDependency);
private static readonly byte[] _bytesStatus426 = CreateStatusBytes(StatusCodes.Status426UpgradeRequired);
private static readonly byte[] _bytesStatus428 = CreateStatusBytes(StatusCodes.Status428PreconditionRequired);
private static readonly byte[] _bytesStatus429 = CreateStatusBytes(StatusCodes.Status429TooManyRequests);
private static readonly byte[] _bytesStatus431 = CreateStatusBytes(StatusCodes.Status431RequestHeaderFieldsTooLarge);
private static readonly byte[] _bytesStatus451 = CreateStatusBytes(StatusCodes.Status451UnavailableForLegalReasons);
private static readonly byte[] _bytesStatus500 = CreateStatusBytes(StatusCodes.Status500InternalServerError);
private static readonly byte[] _bytesStatus501 = CreateStatusBytes(StatusCodes.Status501NotImplemented);
private static readonly byte[] _bytesStatus502 = CreateStatusBytes(StatusCodes.Status502BadGateway);
private static readonly byte[] _bytesStatus503 = CreateStatusBytes(StatusCodes.Status503ServiceUnavailable);
private static readonly byte[] _bytesStatus504 = CreateStatusBytes(StatusCodes.Status504GatewayTimeout);
private static readonly byte[] _bytesStatus505 = CreateStatusBytes(StatusCodes.Status505HttpVersionNotsupported);
private static readonly byte[] _bytesStatus506 = CreateStatusBytes(StatusCodes.Status506VariantAlsoNegotiates);
private static readonly byte[] _bytesStatus507 = CreateStatusBytes(StatusCodes.Status507InsufficientStorage);
private static readonly byte[] _bytesStatus508 = CreateStatusBytes(StatusCodes.Status508LoopDetected);
private static readonly byte[] _bytesStatus510 = CreateStatusBytes(StatusCodes.Status510NotExtended);
private static readonly byte[] _bytesStatus511 = CreateStatusBytes(StatusCodes.Status511NetworkAuthenticationRequired);
private static byte[] CreateStatusBytes(int statusCode)
{
var reasonPhrase = WebUtilities.ReasonPhrases.GetReasonPhrase(statusCode);
Debug.Assert(!string.IsNullOrEmpty(reasonPhrase));
return Encoding.ASCII.GetBytes(statusCode.ToString(CultureInfo.InvariantCulture) + " " + reasonPhrase);
}
public static byte[] ToStatusBytes(int statusCode, string reasonPhrase = null)
{
if (string.IsNullOrEmpty(reasonPhrase))
{
switch (statusCode)
{
case StatusCodes.Status100Continue:
return _bytesStatus100;
case StatusCodes.Status101SwitchingProtocols:
return _bytesStatus101;
case StatusCodes.Status102Processing:
return _bytesStatus102;
case StatusCodes.Status200OK:
return _bytesStatus200;
case StatusCodes.Status201Created:
return _bytesStatus201;
case StatusCodes.Status202Accepted:
return _bytesStatus202;
case StatusCodes.Status203NonAuthoritative:
return _bytesStatus203;
case StatusCodes.Status204NoContent:
return _bytesStatus204;
case StatusCodes.Status205ResetContent:
return _bytesStatus205;
case StatusCodes.Status206PartialContent:
return _bytesStatus206;
case StatusCodes.Status207MultiStatus:
return _bytesStatus207;
case StatusCodes.Status208AlreadyReported:
return _bytesStatus208;
case StatusCodes.Status226IMUsed:
return _bytesStatus226;
case StatusCodes.Status300MultipleChoices:
return _bytesStatus300;
case StatusCodes.Status301MovedPermanently:
return _bytesStatus301;
case StatusCodes.Status302Found:
return _bytesStatus302;
case StatusCodes.Status303SeeOther:
return _bytesStatus303;
case StatusCodes.Status304NotModified:
return _bytesStatus304;
case StatusCodes.Status305UseProxy:
return _bytesStatus305;
case StatusCodes.Status306SwitchProxy:
return _bytesStatus306;
case StatusCodes.Status307TemporaryRedirect:
return _bytesStatus307;
case StatusCodes.Status308PermanentRedirect:
return _bytesStatus308;
case StatusCodes.Status400BadRequest:
return _bytesStatus400;
case StatusCodes.Status401Unauthorized:
return _bytesStatus401;
case StatusCodes.Status402PaymentRequired:
return _bytesStatus402;
case StatusCodes.Status403Forbidden:
return _bytesStatus403;
case StatusCodes.Status404NotFound:
return _bytesStatus404;
case StatusCodes.Status405MethodNotAllowed:
return _bytesStatus405;
case StatusCodes.Status406NotAcceptable:
return _bytesStatus406;
case StatusCodes.Status407ProxyAuthenticationRequired:
return _bytesStatus407;
case StatusCodes.Status408RequestTimeout:
return _bytesStatus408;
case StatusCodes.Status409Conflict:
return _bytesStatus409;
case StatusCodes.Status410Gone:
return _bytesStatus410;
case StatusCodes.Status411LengthRequired:
return _bytesStatus411;
case StatusCodes.Status412PreconditionFailed:
return _bytesStatus412;
case StatusCodes.Status413PayloadTooLarge:
return _bytesStatus413;
case StatusCodes.Status414UriTooLong:
return _bytesStatus414;
case StatusCodes.Status415UnsupportedMediaType:
return _bytesStatus415;
case StatusCodes.Status416RangeNotSatisfiable:
return _bytesStatus416;
case StatusCodes.Status417ExpectationFailed:
return _bytesStatus417;
case StatusCodes.Status418ImATeapot:
return _bytesStatus418;
case StatusCodes.Status419AuthenticationTimeout:
return _bytesStatus419;
case StatusCodes.Status421MisdirectedRequest:
return _bytesStatus421;
case StatusCodes.Status422UnprocessableEntity:
return _bytesStatus422;
case StatusCodes.Status423Locked:
return _bytesStatus423;
case StatusCodes.Status424FailedDependency:
return _bytesStatus424;
case StatusCodes.Status426UpgradeRequired:
return _bytesStatus426;
case StatusCodes.Status428PreconditionRequired:
return _bytesStatus428;
case StatusCodes.Status429TooManyRequests:
return _bytesStatus429;
case StatusCodes.Status431RequestHeaderFieldsTooLarge:
return _bytesStatus431;
case StatusCodes.Status451UnavailableForLegalReasons:
return _bytesStatus451;
case StatusCodes.Status500InternalServerError:
return _bytesStatus500;
case StatusCodes.Status501NotImplemented:
return _bytesStatus501;
case StatusCodes.Status502BadGateway:
return _bytesStatus502;
case StatusCodes.Status503ServiceUnavailable:
return _bytesStatus503;
case StatusCodes.Status504GatewayTimeout:
return _bytesStatus504;
case StatusCodes.Status505HttpVersionNotsupported:
return _bytesStatus505;
case StatusCodes.Status506VariantAlsoNegotiates:
return _bytesStatus506;
case StatusCodes.Status507InsufficientStorage:
return _bytesStatus507;
case StatusCodes.Status508LoopDetected:
return _bytesStatus508;
case StatusCodes.Status510NotExtended:
return _bytesStatus510;
case StatusCodes.Status511NetworkAuthenticationRequired:
return _bytesStatus511;
default:
var predefinedReasonPhrase = WebUtilities.ReasonPhrases.GetReasonPhrase(statusCode);
// https://tools.ietf.org/html/rfc7230#section-3.1.2 requires trailing whitespace regardless of reason phrase
var formattedStatusCode = statusCode.ToString(CultureInfo.InvariantCulture) + " ";
return string.IsNullOrEmpty(predefinedReasonPhrase)
? Encoding.ASCII.GetBytes(formattedStatusCode)
: Encoding.ASCII.GetBytes(formattedStatusCode + predefinedReasonPhrase);
}
}
return Encoding.ASCII.GetBytes(statusCode.ToString(CultureInfo.InvariantCulture) + " " + reasonPhrase);
}
}
}

View File

@ -0,0 +1,14 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public enum RequestProcessingStatus
{
RequestPending,
ParsingRequestLine,
ParsingHeaders,
AppStarted,
ResponseStarted
}
}

View File

@ -0,0 +1,38 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
public enum RequestRejectionReason
{
UnrecognizedHTTPVersion,
InvalidRequestLine,
InvalidRequestHeader,
InvalidRequestHeadersNoCRLF,
MalformedRequestInvalidHeaders,
InvalidContentLength,
MultipleContentLengths,
UnexpectedEndOfRequestContent,
BadChunkSuffix,
BadChunkSizeData,
ChunkedRequestIncomplete,
InvalidRequestTarget,
InvalidCharactersInHeaderName,
RequestLineTooLong,
HeadersExceedMaxTotalSize,
TooManyHeaders,
RequestBodyTooLarge,
RequestHeadersTimeout,
RequestBodyTimeout,
FinalTransferCodingNotChunked,
LengthRequired,
LengthRequiredHttp10,
OptionsMethodRequired,
ConnectMethodRequired,
MissingHostHeader,
MultipleHostHeaders,
InvalidHostHeader,
UpgradeRequestCannotHavePayload,
RequestBodyExceedsContentLength
}
}

View File

@ -0,0 +1,15 @@
// 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.Core.Internal.Http
{
[Flags]
public enum TransferCoding
{
None,
Chunked,
Other
}
}

View File

@ -0,0 +1,411 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Microsoft.AspNetCore.Connections.Abstractions
{
internal class UrlDecoder
{
static bool[] IsAllowed = new bool[0x7F + 1];
/// <summary>
/// Unescape a URL path
/// </summary>
/// <param name="source">The byte span represents a UTF8 encoding url path.</param>
/// <param name="destination">The byte span where unescaped url path is copied to.</param>
/// <returns>The length of the byte sequence of the unescaped url path.</returns>
public static int Decode(ReadOnlySpan<byte> source, Span<byte> destination)
{
if (destination.Length < source.Length)
{
throw new ArgumentException(
"Lenghth of the destination byte span is less then the source.",
nameof(destination));
}
// This requires the destination span to be larger or equal to source span
source.CopyTo(destination);
return DecodeInPlace(destination);
}
/// <summary>
/// Unescape a URL path in place.
/// </summary>
/// <param name="buffer">The byte span represents a UTF8 encoding url path.</param>
/// <returns>The number of the bytes representing the result.</returns>
/// <remarks>
/// The unescape is done in place, which means after decoding the result is the subset of
/// the input span.
/// </remarks>
public static int DecodeInPlace(Span<byte> buffer)
{
// the slot to read the input
var sourceIndex = 0;
// the slot to write the unescaped byte
var destinationIndex = 0;
while (true)
{
if (sourceIndex == buffer.Length)
{
break;
}
if (buffer[sourceIndex] == '%')
{
var decodeIndex = sourceIndex;
// If decoding process succeeds, the writer iterator will be moved
// to the next write-ready location. On the other hand if the scanned
// percent-encodings cannot be interpreted as sequence of UTF-8 octets,
// these bytes should be copied to output as is.
// The decodeReader iterator is always moved to the first byte not yet
// be scanned after the process. A failed decoding means the chars
// between the reader and decodeReader can be copied to output untouched.
if (!DecodeCore(ref decodeIndex, ref destinationIndex, buffer))
{
Copy(sourceIndex, decodeIndex, ref destinationIndex, buffer);
}
sourceIndex = decodeIndex;
}
else
{
buffer[destinationIndex++] = buffer[sourceIndex++];
}
}
return destinationIndex;
}
/// <summary>
/// Unescape the percent-encodings
/// </summary>
/// <param name="sourceIndex">The iterator point to the first % char</param>
/// <param name="destinationIndex">The place to write to</param>
/// <param name="buffer">The byte array</param>
private static bool DecodeCore(ref int sourceIndex, ref int destinationIndex, Span<byte> buffer)
{
// preserves the original head. if the percent-encodings cannot be interpreted as sequence of UTF-8 octets,
// bytes from this till the last scanned one will be copied to the memory pointed by writer.
var byte1 = UnescapePercentEncoding(ref sourceIndex, buffer);
if (byte1 == -1)
{
return false;
}
if (byte1 == 0)
{
throw new InvalidOperationException("The path contains null characters.");
}
if (byte1 <= 0x7F)
{
// first byte < U+007f, it is a single byte ASCII
buffer[destinationIndex++] = (byte)byte1;
return true;
}
int byte2 = 0, byte3 = 0, byte4 = 0;
// anticipate more bytes
var currentDecodeBits = 0;
var byteCount = 1;
var expectValueMin = 0;
if ((byte1 & 0xE0) == 0xC0)
{
// 110x xxxx, expect one more byte
currentDecodeBits = byte1 & 0x1F;
byteCount = 2;
expectValueMin = 0x80;
}
else if ((byte1 & 0xF0) == 0xE0)
{
// 1110 xxxx, expect two more bytes
currentDecodeBits = byte1 & 0x0F;
byteCount = 3;
expectValueMin = 0x800;
}
else if ((byte1 & 0xF8) == 0xF0)
{
// 1111 0xxx, expect three more bytes
currentDecodeBits = byte1 & 0x07;
byteCount = 4;
expectValueMin = 0x10000;
}
else
{
// invalid first byte
return false;
}
var remainingBytes = byteCount - 1;
while (remainingBytes > 0)
{
// read following three chars
if (sourceIndex == buffer.Length)
{
return false;
}
var nextSourceIndex = sourceIndex;
var nextByte = UnescapePercentEncoding(ref nextSourceIndex, buffer);
if (nextByte == -1)
{
return false;
}
if ((nextByte & 0xC0) != 0x80)
{
// the follow up byte is not in form of 10xx xxxx
return false;
}
currentDecodeBits = (currentDecodeBits << 6) | (nextByte & 0x3F);
remainingBytes--;
if (remainingBytes == 1 && currentDecodeBits >= 0x360 && currentDecodeBits <= 0x37F)
{
// this is going to end up in the range of 0xD800-0xDFFF UTF-16 surrogates that
// are not allowed in UTF-8;
return false;
}
if (remainingBytes == 2 && currentDecodeBits >= 0x110)
{
// this is going to be out of the upper Unicode bound 0x10FFFF.
return false;
}
sourceIndex = nextSourceIndex;
if (byteCount - remainingBytes == 2)
{
byte2 = nextByte;
}
else if (byteCount - remainingBytes == 3)
{
byte3 = nextByte;
}
else if (byteCount - remainingBytes == 4)
{
byte4 = nextByte;
}
}
if (currentDecodeBits < expectValueMin)
{
// overlong encoding (e.g. using 2 bytes to encode something that only needed 1).
return false;
}
// all bytes are verified, write to the output
// TODO: measure later to determine if the performance of following logic can be improved
// the idea is to combine the bytes into short/int and write to span directly to avoid
// range check cost
if (byteCount > 0)
{
buffer[destinationIndex++] = (byte)byte1;
}
if (byteCount > 1)
{
buffer[destinationIndex++] = (byte)byte2;
}
if (byteCount > 2)
{
buffer[destinationIndex++] = (byte)byte3;
}
if (byteCount > 3)
{
buffer[destinationIndex++] = (byte)byte4;
}
return true;
}
private static void Copy(int begin, int end, ref int writer, Span<byte> buffer)
{
while (begin != end)
{
buffer[writer++] = buffer[begin++];
}
}
/// <summary>
/// Read the percent-encoding and try unescape it.
///
/// The operation first peek at the character the <paramref name="scan"/>
/// iterator points at. If it is % the <paramref name="scan"/> is then
/// moved on to scan the following to characters. If the two following
/// characters are hexadecimal literals they will be unescaped and the
/// value will be returned.
///
/// If the first character is not % the <paramref name="scan"/> iterator
/// will be removed beyond the location of % and -1 will be returned.
///
/// If the following two characters can't be successfully unescaped the
/// <paramref name="scan"/> iterator will be move behind the % and -1
/// will be returned.
/// </summary>
/// <param name="scan">The value to read</param>
/// <param name="buffer">The byte array</param>
/// <returns>The unescaped byte if success. Otherwise return -1.</returns>
private static int UnescapePercentEncoding(ref int scan, Span<byte> buffer)
{
if (buffer[scan++] != '%')
{
return -1;
}
var probe = scan;
var value1 = ReadHex(ref probe, buffer);
if (value1 == -1)
{
return -1;
}
var value2 = ReadHex(ref probe, buffer);
if (value2 == -1)
{
return -1;
}
if (SkipUnescape(value1, value2))
{
return -1;
}
scan = probe;
return (value1 << 4) + value2;
}
/// <summary>
/// Read the next char and convert it into hexadecimal value.
///
/// The <paramref name="scan"/> index will be moved to the next
/// byte no matter no matter whether the operation successes.
/// </summary>
/// <param name="scan">The index of the byte in the buffer to read</param>
/// <param name="buffer">The byte span from which the hex to be read</param>
/// <returns>The hexadecimal value if successes, otherwise -1.</returns>
private static int ReadHex(ref int scan, Span<byte> buffer)
{
if (scan == buffer.Length)
{
return -1;
}
var value = buffer[scan++];
var isHead = ((value >= '0') && (value <= '9')) ||
((value >= 'A') && (value <= 'F')) ||
((value >= 'a') && (value <= 'f'));
if (!isHead)
{
return -1;
}
if (value <= '9')
{
return value - '0';
}
else if (value <= 'F')
{
return (value - 'A') + 10;
}
else // a - f
{
return (value - 'a') + 10;
}
}
private static bool SkipUnescape(int value1, int value2)
{
// skip %2F - '/'
if (value1 == 2 && value2 == 15)
{
return true;
}
return false;
}
static UrlDecoder()
{
// Unreserved
IsAllowed['A'] = true;
IsAllowed['B'] = true;
IsAllowed['C'] = true;
IsAllowed['D'] = true;
IsAllowed['E'] = true;
IsAllowed['F'] = true;
IsAllowed['G'] = true;
IsAllowed['H'] = true;
IsAllowed['I'] = true;
IsAllowed['J'] = true;
IsAllowed['K'] = true;
IsAllowed['L'] = true;
IsAllowed['M'] = true;
IsAllowed['N'] = true;
IsAllowed['O'] = true;
IsAllowed['P'] = true;
IsAllowed['Q'] = true;
IsAllowed['R'] = true;
IsAllowed['S'] = true;
IsAllowed['T'] = true;
IsAllowed['U'] = true;
IsAllowed['V'] = true;
IsAllowed['W'] = true;
IsAllowed['X'] = true;
IsAllowed['Y'] = true;
IsAllowed['Z'] = true;
IsAllowed['a'] = true;
IsAllowed['b'] = true;
IsAllowed['c'] = true;
IsAllowed['d'] = true;
IsAllowed['e'] = true;
IsAllowed['f'] = true;
IsAllowed['g'] = true;
IsAllowed['h'] = true;
IsAllowed['i'] = true;
IsAllowed['j'] = true;
IsAllowed['k'] = true;
IsAllowed['l'] = true;
IsAllowed['m'] = true;
IsAllowed['n'] = true;
IsAllowed['o'] = true;
IsAllowed['p'] = true;
IsAllowed['q'] = true;
IsAllowed['r'] = true;
IsAllowed['s'] = true;
IsAllowed['t'] = true;
IsAllowed['u'] = true;
IsAllowed['v'] = true;
IsAllowed['w'] = true;
IsAllowed['x'] = true;
IsAllowed['y'] = true;
IsAllowed['z'] = true;
IsAllowed['0'] = true;
IsAllowed['1'] = true;
IsAllowed['2'] = true;
IsAllowed['3'] = true;
IsAllowed['4'] = true;
IsAllowed['5'] = true;
IsAllowed['6'] = true;
IsAllowed['7'] = true;
IsAllowed['8'] = true;
IsAllowed['9'] = true;
IsAllowed['-'] = true;
IsAllowed['_'] = true;
IsAllowed['.'] = true;
IsAllowed['~'] = true;
}
}
}

View File

@ -0,0 +1,94 @@
// 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.Core.Internal.Http2.HPack
{
public class DynamicTable
{
private HeaderField[] _buffer;
private int _maxSize;
private int _size;
private int _count;
private int _insertIndex;
private int _removeIndex;
public DynamicTable(int maxSize)
{
_buffer = new HeaderField[maxSize / HeaderField.RfcOverhead];
_maxSize = maxSize;
}
public int Count => _count;
public int Size => _size;
public int MaxSize => _maxSize;
public HeaderField this[int index]
{
get
{
if (index >= _count)
{
throw new IndexOutOfRangeException();
}
return _buffer[_insertIndex == 0 ? _buffer.Length - 1 : _insertIndex - index - 1];
}
}
public void Insert(Span<byte> name, Span<byte> value)
{
var entryLength = HeaderField.GetLength(name.Length, value.Length);
EnsureAvailable(entryLength);
if (entryLength > _maxSize)
{
// http://httpwg.org/specs/rfc7541.html#rfc.section.4.4
// It is not an error to attempt to add an entry that is larger than the maximum size;
// an attempt to add an entry larger than the maximum size causes the table to be emptied
// of all existing entries and results in an empty table.
return;
}
var entry = new HeaderField(name, value);
_buffer[_insertIndex] = entry;
_insertIndex = (_insertIndex + 1) % _buffer.Length;
_size += entry.Length;
_count++;
}
public void Resize(int maxSize)
{
if (maxSize > _maxSize)
{
var newBuffer = new HeaderField[maxSize / HeaderField.RfcOverhead];
for (var i = 0; i < Count; i++)
{
newBuffer[i] = _buffer[i];
}
_buffer = newBuffer;
_maxSize = maxSize;
}
else
{
_maxSize = maxSize;
EnsureAvailable(0);
}
}
private void EnsureAvailable(int available)
{
while (_count > 0 && _maxSize - _size < available)
{
_size -= _buffer[_removeIndex].Length;
_count--;
_removeIndex = (_removeIndex + 1) % _buffer.Length;
}
}
}
}

View File

@ -0,0 +1,406 @@
// 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 Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
{
public class HPackDecoder
{
private enum State
{
Ready,
HeaderFieldIndex,
HeaderNameIndex,
HeaderNameLength,
HeaderNameLengthContinue,
HeaderName,
HeaderValueLength,
HeaderValueLengthContinue,
HeaderValue,
DynamicTableSizeUpdate
}
// TODO: add new configurable limit
public const int MaxStringOctets = 4096;
// http://httpwg.org/specs/rfc7541.html#rfc.section.6.1
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+
// | 1 | Index (7+) |
// +---+---------------------------+
private const byte IndexedHeaderFieldMask = 0x80;
private const byte IndexedHeaderFieldRepresentation = 0x80;
// http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.1
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+
// | 0 | 1 | Index (6+) |
// +---+---+-----------------------+
private const byte LiteralHeaderFieldWithIncrementalIndexingMask = 0xc0;
private const byte LiteralHeaderFieldWithIncrementalIndexingRepresentation = 0x40;
// http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.2
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+
// | 0 | 0 | 0 | 0 | Index (4+) |
// +---+---+-----------------------+
private const byte LiteralHeaderFieldWithoutIndexingMask = 0xf0;
private const byte LiteralHeaderFieldWithoutIndexingRepresentation = 0x00;
// http://httpwg.org/specs/rfc7541.html#rfc.section.6.2.3
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+
// | 0 | 0 | 0 | 1 | Index (4+) |
// +---+---+-----------------------+
private const byte LiteralHeaderFieldNeverIndexedMask = 0xf0;
private const byte LiteralHeaderFieldNeverIndexedRepresentation = 0x10;
// http://httpwg.org/specs/rfc7541.html#rfc.section.6.3
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+
// | 0 | 0 | 1 | Max size (5+) |
// +---+---------------------------+
private const byte DynamicTableSizeUpdateMask = 0xe0;
private const byte DynamicTableSizeUpdateRepresentation = 0x20;
// http://httpwg.org/specs/rfc7541.html#rfc.section.5.2
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+
// | H | String Length (7+) |
// +---+---------------------------+
private const byte HuffmanMask = 0x80;
private const int IndexedHeaderFieldPrefix = 7;
private const int LiteralHeaderFieldWithIncrementalIndexingPrefix = 6;
private const int LiteralHeaderFieldWithoutIndexingPrefix = 4;
private const int LiteralHeaderFieldNeverIndexedPrefix = 4;
private const int DynamicTableSizeUpdatePrefix = 5;
private const int StringLengthPrefix = 7;
private readonly int _maxDynamicTableSize;
private readonly DynamicTable _dynamicTable;
private readonly IntegerDecoder _integerDecoder = new IntegerDecoder();
private readonly byte[] _stringOctets = new byte[MaxStringOctets];
private readonly byte[] _headerNameOctets = new byte[MaxStringOctets];
private readonly byte[] _headerValueOctets = new byte[MaxStringOctets];
private State _state = State.Ready;
private byte[] _headerName;
private int _stringIndex;
private int _stringLength;
private int _headerNameLength;
private int _headerValueLength;
private bool _index;
private bool _huffman;
public HPackDecoder(int maxDynamicTableSize)
: this(maxDynamicTableSize, new DynamicTable(maxDynamicTableSize))
{
_maxDynamicTableSize = maxDynamicTableSize;
}
// For testing.
internal HPackDecoder(int maxDynamicTableSize, DynamicTable dynamicTable)
{
_maxDynamicTableSize = maxDynamicTableSize;
_dynamicTable = dynamicTable;
}
public void Decode(Span<byte> data, bool endHeaders, IHttpHeadersHandler handler)
{
for (var i = 0; i < data.Length; i++)
{
OnByte(data[i], handler);
}
if (endHeaders && _state != State.Ready)
{
throw new HPackDecodingException(CoreStrings.HPackErrorIncompleteHeaderBlock);
}
}
public void OnByte(byte b, IHttpHeadersHandler handler)
{
switch (_state)
{
case State.Ready:
if ((b & IndexedHeaderFieldMask) == IndexedHeaderFieldRepresentation)
{
var val = b & ~IndexedHeaderFieldMask;
if (_integerDecoder.BeginDecode((byte)val, IndexedHeaderFieldPrefix))
{
OnIndexedHeaderField(_integerDecoder.Value, handler);
}
else
{
_state = State.HeaderFieldIndex;
}
}
else if ((b & LiteralHeaderFieldWithIncrementalIndexingMask) == LiteralHeaderFieldWithIncrementalIndexingRepresentation)
{
_index = true;
var val = b & ~LiteralHeaderFieldWithIncrementalIndexingMask;
if (val == 0)
{
_state = State.HeaderNameLength;
}
else if (_integerDecoder.BeginDecode((byte)val, LiteralHeaderFieldWithIncrementalIndexingPrefix))
{
OnIndexedHeaderName(_integerDecoder.Value);
}
else
{
_state = State.HeaderNameIndex;
}
}
else if ((b & LiteralHeaderFieldWithoutIndexingMask) == LiteralHeaderFieldWithoutIndexingRepresentation)
{
_index = false;
var val = b & ~LiteralHeaderFieldWithoutIndexingMask;
if (val == 0)
{
_state = State.HeaderNameLength;
}
else if (_integerDecoder.BeginDecode((byte)val, LiteralHeaderFieldWithoutIndexingPrefix))
{
OnIndexedHeaderName(_integerDecoder.Value);
}
else
{
_state = State.HeaderNameIndex;
}
}
else if ((b & LiteralHeaderFieldNeverIndexedMask) == LiteralHeaderFieldNeverIndexedRepresentation)
{
_index = false;
var val = b & ~LiteralHeaderFieldNeverIndexedMask;
if (val == 0)
{
_state = State.HeaderNameLength;
}
else if (_integerDecoder.BeginDecode((byte)val, LiteralHeaderFieldNeverIndexedPrefix))
{
OnIndexedHeaderName(_integerDecoder.Value);
}
else
{
_state = State.HeaderNameIndex;
}
}
else if ((b & DynamicTableSizeUpdateMask) == DynamicTableSizeUpdateRepresentation)
{
if (_integerDecoder.BeginDecode((byte)(b & ~DynamicTableSizeUpdateMask), DynamicTableSizeUpdatePrefix))
{
// TODO: validate that it's less than what's defined via SETTINGS
_dynamicTable.Resize(_integerDecoder.Value);
}
else
{
_state = State.DynamicTableSizeUpdate;
}
}
else
{
// Can't happen
throw new HPackDecodingException($"Byte value {b} does not encode a valid header field representation.");
}
break;
case State.HeaderFieldIndex:
if (_integerDecoder.Decode(b))
{
OnIndexedHeaderField(_integerDecoder.Value, handler);
}
break;
case State.HeaderNameIndex:
if (_integerDecoder.Decode(b))
{
OnIndexedHeaderName(_integerDecoder.Value);
}
break;
case State.HeaderNameLength:
_huffman = (b & HuffmanMask) != 0;
if (_integerDecoder.BeginDecode((byte)(b & ~HuffmanMask), StringLengthPrefix))
{
OnStringLength(_integerDecoder.Value, nextState: State.HeaderName);
}
else
{
_state = State.HeaderNameLengthContinue;
}
break;
case State.HeaderNameLengthContinue:
if (_integerDecoder.Decode(b))
{
OnStringLength(_integerDecoder.Value, nextState: State.HeaderName);
}
break;
case State.HeaderName:
_stringOctets[_stringIndex++] = b;
if (_stringIndex == _stringLength)
{
OnString(nextState: State.HeaderValueLength);
}
break;
case State.HeaderValueLength:
_huffman = (b & HuffmanMask) != 0;
if (_integerDecoder.BeginDecode((byte)(b & ~HuffmanMask), StringLengthPrefix))
{
OnStringLength(_integerDecoder.Value, nextState: State.HeaderValue);
if (_integerDecoder.Value == 0)
{
ProcessHeaderValue(handler);
}
}
else
{
_state = State.HeaderValueLengthContinue;
}
break;
case State.HeaderValueLengthContinue:
if (_integerDecoder.Decode(b))
{
OnStringLength(_integerDecoder.Value, nextState: State.HeaderValue);
if (_integerDecoder.Value == 0)
{
ProcessHeaderValue(handler);
}
}
break;
case State.HeaderValue:
_stringOctets[_stringIndex++] = b;
if (_stringIndex == _stringLength)
{
ProcessHeaderValue(handler);
}
break;
case State.DynamicTableSizeUpdate:
if (_integerDecoder.Decode(b))
{
if (_integerDecoder.Value > _maxDynamicTableSize)
{
throw new HPackDecodingException(
CoreStrings.FormatHPackErrorDynamicTableSizeUpdateTooLarge(_integerDecoder.Value, _maxDynamicTableSize));
}
_dynamicTable.Resize(_integerDecoder.Value);
_state = State.Ready;
}
break;
default:
// Can't happen
throw new HPackDecodingException("The HPACK decoder reached an invalid state.");
}
}
private void ProcessHeaderValue(IHttpHeadersHandler handler)
{
OnString(nextState: State.Ready);
var headerNameSpan = new Span<byte>(_headerName, 0, _headerNameLength);
var headerValueSpan = new Span<byte>(_headerValueOctets, 0, _headerValueLength);
handler.OnHeader(headerNameSpan, headerValueSpan);
if (_index)
{
_dynamicTable.Insert(headerNameSpan, headerValueSpan);
}
}
private void OnIndexedHeaderField(int index, IHttpHeadersHandler handler)
{
var header = GetHeader(index);
handler.OnHeader(new Span<byte>(header.Name), new Span<byte>(header.Value));
_state = State.Ready;
}
private void OnIndexedHeaderName(int index)
{
var header = GetHeader(index);
_headerName = header.Name;
_headerNameLength = header.Name.Length;
_state = State.HeaderValueLength;
}
private void OnStringLength(int length, State nextState)
{
if (length > _stringOctets.Length)
{
throw new HPackDecodingException(CoreStrings.FormatHPackStringLengthTooLarge(length, _stringOctets.Length));
}
_stringLength = length;
_stringIndex = 0;
_state = nextState;
}
private void OnString(State nextState)
{
int Decode(byte[] dst)
{
if (_huffman)
{
return Huffman.Decode(_stringOctets, 0, _stringLength, dst);
}
else
{
Buffer.BlockCopy(_stringOctets, 0, dst, 0, _stringLength);
return _stringLength;
}
}
try
{
if (_state == State.HeaderName)
{
_headerName = _headerNameOctets;
_headerNameLength = Decode(_headerNameOctets);
}
else
{
_headerValueLength = Decode(_headerValueOctets);
}
}
catch (HuffmanDecodingException ex)
{
throw new HPackDecodingException(CoreStrings.HPackHuffmanError, ex);
}
_state = nextState;
}
private HeaderField GetHeader(int index)
{
try
{
return index <= StaticTable.Instance.Count
? StaticTable.Instance[index - 1]
: _dynamicTable[index - StaticTable.Instance.Count - 1];
}
catch (IndexOutOfRangeException ex)
{
throw new HPackDecodingException(CoreStrings.FormatHPackErrorIndexOutOfRange(index), ex);
}
}
}
}

View File

@ -0,0 +1,19 @@
// 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.Core.Internal.Http2.HPack
{
public class HPackDecodingException : Exception
{
public HPackDecodingException(string message)
: base(message)
{
}
public HPackDecodingException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@ -0,0 +1,151 @@
// 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;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
{
public class HPackEncoder
{
private IEnumerator<KeyValuePair<string, string>> _enumerator;
public bool BeginEncode(IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
{
_enumerator = headers.GetEnumerator();
_enumerator.MoveNext();
return Encode(buffer, out length);
}
public bool BeginEncode(int statusCode, IEnumerable<KeyValuePair<string, string>> headers, Span<byte> buffer, out int length)
{
_enumerator = headers.GetEnumerator();
_enumerator.MoveNext();
var statusCodeLength = EncodeStatusCode(statusCode, buffer);
var done = Encode(buffer.Slice(statusCodeLength), out var headersLength);
length = statusCodeLength + headersLength;
return done;
}
public bool Encode(Span<byte> buffer, out int length)
{
length = 0;
do
{
if (!EncodeHeader(_enumerator.Current.Key, _enumerator.Current.Value, buffer.Slice(length), out var headerLength))
{
return false;
}
length += headerLength;
} while (_enumerator.MoveNext());
return true;
}
private int EncodeStatusCode(int statusCode, Span<byte> buffer)
{
switch (statusCode)
{
case 200:
case 204:
case 206:
case 304:
case 400:
case 404:
case 500:
buffer[0] = (byte)(0x80 | StaticTable.Instance.StatusIndex[statusCode]);
return 1;
default:
// Send as Literal Header Field Without Indexing - Indexed Name
buffer[0] = 0x08;
var statusBytes = StatusCodes.ToStatusBytes(statusCode);
buffer[1] = (byte)statusBytes.Length;
((Span<byte>)statusBytes).CopyTo(buffer.Slice(2));
return 2 + statusBytes.Length;
}
}
private bool EncodeHeader(string name, string value, Span<byte> buffer, out int length)
{
var i = 0;
length = 0;
if (buffer.Length == 0)
{
return false;
}
buffer[i++] = 0;
if (i == buffer.Length)
{
return false;
}
if (!EncodeString(name, buffer.Slice(i), out var nameLength, lowercase: true))
{
return false;
}
i += nameLength;
if (i >= buffer.Length)
{
return false;
}
if (!EncodeString(value, buffer.Slice(i), out var valueLength, lowercase: false))
{
return false;
}
i += valueLength;
length = i;
return true;
}
private bool EncodeString(string s, Span<byte> buffer, out int length, bool lowercase)
{
const int toLowerMask = 0x20;
var i = 0;
length = 0;
if (buffer.Length == 0)
{
return false;
}
buffer[0] = 0;
if (!IntegerEncoder.Encode(s.Length, 7, buffer, out var nameLength))
{
return false;
}
i += nameLength;
// TODO: use huffman encoding
for (var j = 0; j < s.Length; j++)
{
if (i >= buffer.Length)
{
return false;
}
buffer[i++] = (byte)(s[j] | (lowercase ? toLowerMask : 0));
}
length = i;
return true;
}
}
}

Some files were not shown because too many files have changed in this diff Show More