// 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.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;

namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
    public class Http1ConnectionTests : IDisposable
    {
        private readonly IDuplexPipe _transport;
        private readonly IDuplexPipe _application;
        private readonly TestHttp1Connection _http1Connection;
        private readonly ServiceContext _serviceContext;
        private readonly HttpConnectionContext _http1ConnectionContext;
        private readonly MemoryPool<byte> _pipelineFactory;
        private SequencePosition _consumed;
        private SequencePosition _examined;
        private Mock<ITimeoutControl> _timeoutControl;

        public Http1ConnectionTests()
        {
            _pipelineFactory = SlabMemoryPoolFactory.Create();
            var options = new PipeOptions(_pipelineFactory, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
            var pair = DuplexPipe.CreateConnectionPair(options, options);

            _transport = pair.Transport;
            _application = pair.Application;

            var connectionFeatures = new FeatureCollection();
            connectionFeatures.Set(Mock.Of<IConnectionLifetimeFeature>());

            _serviceContext = new TestServiceContext()
            {
                Scheduler = PipeScheduler.Inline
            };

            _timeoutControl = new Mock<ITimeoutControl>();
            _http1ConnectionContext = new HttpConnectionContext
            {
                ServiceContext = _serviceContext,
                ConnectionContext = Mock.Of<ConnectionContext>(),
                ConnectionFeatures = connectionFeatures,
                MemoryPool = _pipelineFactory,
                TimeoutControl = _timeoutControl.Object,
                Transport = pair.Transport
            };

            _http1Connection = new TestHttp1Connection(_http1ConnectionContext);
            _http1Connection.Reset();
        }

        public void Dispose()
        {
            _transport.Input.Complete();
            _transport.Output.Complete();

            _application.Input.Complete();
            _application.Output.Complete();

            _pipelineFactory.Dispose();
        }

        [Fact]
        public async Task TakeMessageHeadersSucceedsWhenHeaderValueContainsUTF8()
        {
            var headerName = "Header";
            var headerValueBytes = new byte[] { 0x46, 0x72, 0x61, 0x6e, 0xc3, 0xa7, 0x6f, 0x69, 0x73 };
            var headerValue = Encoding.UTF8.GetString(headerValueBytes);
            _http1Connection.Reset();

            await _application.Output.WriteAsync(Encoding.UTF8.GetBytes($"{headerName}: "));
            await _application.Output.WriteAsync(headerValueBytes);
            await _application.Output.WriteAsync(Encoding.UTF8.GetBytes("\r\n\r\n"));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.Equal(headerValue, _http1Connection.RequestHeaders[headerName]);
        }

        [Fact]
        public async Task TakeMessageHeadersThrowsWhenHeaderValueContainsExtendedASCII()
        {
            var extendedAsciiEncoding = Encoding.GetEncoding("ISO-8859-1");
            var headerName = "Header";
            var headerValueBytes = new byte[] { 0x46, 0x72, 0x61, 0x6e, 0xe7, 0x6f, 0x69, 0x73 };
            _http1Connection.Reset();

            await _application.Output.WriteAsync(extendedAsciiEncoding.GetBytes($"{headerName}: "));
            await _application.Output.WriteAsync(headerValueBytes);
            await _application.Output.WriteAsync(extendedAsciiEncoding.GetBytes("\r\n\r\n"));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var exception = Assert.Throws<InvalidOperationException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
        }

        [Fact]
        public async Task TakeMessageHeadersThrowsWhenHeadersExceedTotalSizeLimit()
        {
            const string headerLine = "Header: value\r\n";
            _serviceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize = headerLine.Length - 1;
            _http1Connection.Reset();

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine}\r\n"));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.Equal(CoreStrings.BadRequest_HeadersExceedMaxTotalSize, exception.Message);
            Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
        }

        [Fact]
        public async Task TakeMessageHeadersThrowsWhenHeadersExceedCountLimit()
        {
            const string headerLines = "Header-1: value1\r\nHeader-2: value2\r\n";
            _serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = 1;

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLines}\r\n"));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined));
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.Equal(CoreStrings.BadRequest_TooManyHeaders, exception.Message);
            Assert.Equal(StatusCodes.Status431RequestHeaderFieldsTooLarge, exception.StatusCode);
        }

        [Fact]
        public void ResetResetsScheme()
        {
            _http1Connection.Scheme = "https";

            // Act
            _http1Connection.Reset();

            // Assert
            Assert.Equal("http", ((IFeatureCollection)_http1Connection).Get<IHttpRequestFeature>().Scheme);
        }

        [Fact]
        public void ResetResetsTraceIdentifier()
        {
            _http1Connection.TraceIdentifier = "xyz";

            _http1Connection.Reset();

            var nextId = ((IFeatureCollection)_http1Connection).Get<IHttpRequestIdentifierFeature>().TraceIdentifier;
            Assert.NotEqual("xyz", nextId);

            _http1Connection.Reset();
            var secondId = ((IFeatureCollection)_http1Connection).Get<IHttpRequestIdentifierFeature>().TraceIdentifier;
            Assert.NotEqual(nextId, secondId);
        }

        [Fact]
        public void ResetResetsMinRequestBodyDataRate()
        {
            _http1Connection.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: TimeSpan.MaxValue);

            _http1Connection.Reset();

            Assert.Same(_serviceContext.ServerOptions.Limits.MinRequestBodyDataRate, _http1Connection.MinRequestBodyDataRate);
        }

        [Fact]
        public void ResetResetsMinResponseDataRate()
        {
            _http1Connection.MinResponseDataRate = new MinDataRate(bytesPerSecond: 1, gracePeriod: TimeSpan.MaxValue);

            _http1Connection.Reset();

            Assert.Same(_serviceContext.ServerOptions.Limits.MinResponseDataRate, _http1Connection.MinResponseDataRate);
        }

        [Fact]
        public void TraceIdentifierCountsRequestsPerHttp1Connection()
        {
            var connectionId = _http1ConnectionContext.ConnectionId;
            var feature = ((IFeatureCollection)_http1Connection).Get<IHttpRequestIdentifierFeature>();
            // Reset() is called once in the test ctor
            var count = 1;
            void Reset()
            {
                _http1Connection.Reset();
                count++;
            }

            var nextId = feature.TraceIdentifier;
            Assert.Equal($"{connectionId}:00000001", nextId);

            Reset();
            var secondId = feature.TraceIdentifier;
            Assert.Equal($"{connectionId}:00000002", secondId);

            var big = 1_000_000;
            while (big-- > 0) Reset();
            Assert.Equal($"{connectionId}:{count:X8}", feature.TraceIdentifier);
        }

        [Fact]
        public void TraceIdentifierGeneratesWhenNull()
        {
            _http1Connection.TraceIdentifier = null;
            var id = _http1Connection.TraceIdentifier;
            Assert.NotNull(id);
            Assert.Equal(id, _http1Connection.TraceIdentifier);

            _http1Connection.Reset();
            Assert.NotEqual(id, _http1Connection.TraceIdentifier);
        }

        [Fact]
        public async Task ResetResetsHeaderLimits()
        {
            const string headerLine1 = "Header-1: value1\r\n";
            const string headerLine2 = "Header-2: value2\r\n";

            var options = new KestrelServerOptions();
            options.Limits.MaxRequestHeadersTotalSize = headerLine1.Length;
            options.Limits.MaxRequestHeaderCount = 1;
            _serviceContext.ServerOptions = options;

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine1}\r\n"));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.True(takeMessageHeaders);
            Assert.Equal(1, _http1Connection.RequestHeaders.Count);
            Assert.Equal("value1", _http1Connection.RequestHeaders["Header-1"]);

            _http1Connection.Reset();

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"{headerLine2}\r\n"));
            readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            takeMessageHeaders = _http1Connection.TakeMessageHeaders(readableBuffer, trailers: false, out _consumed, out _examined);
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.True(takeMessageHeaders);
            Assert.Equal(1, _http1Connection.RequestHeaders.Count);
            Assert.Equal("value2", _http1Connection.RequestHeaders["Header-2"]);
        }

        [Fact]
        public async Task ThrowsWhenStatusCodeIsSetAfterResponseStarted()
        {
            // Act
            await _http1Connection.WriteAsync(new ArraySegment<byte>(new byte[1]));

            // Assert
            Assert.True(_http1Connection.HasResponseStarted);
            Assert.Throws<InvalidOperationException>(() => ((IHttpResponseFeature)_http1Connection).StatusCode = StatusCodes.Status404NotFound);
        }

        [Fact]
        public async Task ThrowsWhenReasonPhraseIsSetAfterResponseStarted()
        {
            // Act
            await _http1Connection.WriteAsync(new ArraySegment<byte>(new byte[1]));

            // Assert
            Assert.True(_http1Connection.HasResponseStarted);
            Assert.Throws<InvalidOperationException>(() => ((IHttpResponseFeature)_http1Connection).ReasonPhrase = "Reason phrase");
        }

        [Fact]
        public async Task ThrowsWhenOnStartingIsSetAfterResponseStarted()
        {
            await _http1Connection.WriteAsync(new ArraySegment<byte>(new byte[1]));

            // Act/Assert
            Assert.True(_http1Connection.HasResponseStarted);
            Assert.Throws<InvalidOperationException>(() => ((IHttpResponseFeature)_http1Connection).OnStarting(_ => Task.CompletedTask, null));
        }

        [Theory]
        [MemberData(nameof(MinDataRateData))]
        public void ConfiguringIHttpMinRequestBodyDataRateFeatureSetsMinRequestBodyDataRate(MinDataRate minDataRate)
        {
            ((IFeatureCollection)_http1Connection).Get<IHttpMinRequestBodyDataRateFeature>().MinDataRate = minDataRate;

            Assert.Same(minDataRate, _http1Connection.MinRequestBodyDataRate);
        }

        [Theory]
        [MemberData(nameof(MinDataRateData))]
        public void ConfiguringIHttpMinResponseDataRateFeatureSetsMinResponseDataRate(MinDataRate minDataRate)
        {
            ((IFeatureCollection)_http1Connection).Get<IHttpMinResponseDataRateFeature>().MinDataRate = minDataRate;

            Assert.Same(minDataRate, _http1Connection.MinResponseDataRate);
        }

        [Fact]
        public void ResetResetsRequestHeaders()
        {
            // Arrange
            var originalRequestHeaders = _http1Connection.RequestHeaders;
            _http1Connection.RequestHeaders = new HttpRequestHeaders();

            // Act
            _http1Connection.Reset();

            // Assert
            Assert.Same(originalRequestHeaders, _http1Connection.RequestHeaders);
        }

        [Fact]
        public void ResetResetsResponseHeaders()
        {
            // Arrange
            var originalResponseHeaders = _http1Connection.ResponseHeaders;
            _http1Connection.ResponseHeaders = new HttpResponseHeaders();

            // Act
            _http1Connection.Reset();

            // Assert
            Assert.Same(originalResponseHeaders, _http1Connection.ResponseHeaders);
        }

        [Fact]
        public void InitializeStreamsResetsStreams()
        {
            // Arrange
            var messageBody = Http1MessageBody.For(Kestrel.Core.Internal.Http.HttpVersion.Http11, (HttpRequestHeaders)_http1Connection.RequestHeaders, _http1Connection);
            _http1Connection.InitializeBodyControl(messageBody);

            var originalRequestBody = _http1Connection.RequestBody;
            var originalResponseBody = _http1Connection.ResponseBody;
            _http1Connection.RequestBody = new MemoryStream();
            _http1Connection.ResponseBody = new MemoryStream();

            // Act
            _http1Connection.InitializeBodyControl(messageBody);

            // Assert
            Assert.Same(originalRequestBody, _http1Connection.RequestBody);
            Assert.Same(originalResponseBody, _http1Connection.ResponseBody);
        }

        [Theory]
        [MemberData(nameof(RequestLineValidData))]
        public async Task TakeStartLineSetsHttpProtocolProperties(
            string requestLine,
            string expectedMethod,
            string expectedRawTarget,
            // This warns that theory methods should use all of their parameters,
            // but this method is using a shared data collection with HttpParserTests.ParsesRequestLine and others.
#pragma warning disable xUnit1026
            string expectedRawPath,
#pragma warning restore xUnit1026
            string expectedDecodedPath,
            string expectedQueryString,
            string expectedHttpVersion)
        {
            var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
            await _application.Output.WriteAsync(requestLineBytes);
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var returnValue = _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined);
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.True(returnValue);
            Assert.Equal(expectedMethod, ((IHttpRequestFeature)_http1Connection).Method);
            Assert.Equal(expectedRawTarget, _http1Connection.RawTarget);
            Assert.Equal(expectedDecodedPath, _http1Connection.Path);
            Assert.Equal(expectedQueryString, _http1Connection.QueryString);
            Assert.Equal(expectedHttpVersion, _http1Connection.HttpVersion);
        }

        [Theory]
        [MemberData(nameof(RequestLineDotSegmentData))]
        public async Task TakeStartLineRemovesDotSegmentsFromTarget(
            string requestLine,
            string expectedRawTarget,
            string expectedDecodedPath,
            string expectedQueryString)
        {
            var requestLineBytes = Encoding.ASCII.GetBytes(requestLine);
            await _application.Output.WriteAsync(requestLineBytes);
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var returnValue = _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined);
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.True(returnValue);
            Assert.Equal(expectedRawTarget, _http1Connection.RawTarget);
            Assert.Equal(expectedDecodedPath, _http1Connection.Path);
            Assert.Equal(expectedQueryString, _http1Connection.QueryString);
        }

        [Fact]
        public async Task ParseRequestStartsRequestHeadersTimeoutOnFirstByteAvailable()
        {
            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("G"));

            _http1Connection.ParseRequest((await _transport.Input.ReadAsync()).Buffer, out _consumed, out _examined);
            _transport.Input.AdvanceTo(_consumed, _examined);

            var expectedRequestHeadersTimeout = _serviceContext.ServerOptions.Limits.RequestHeadersTimeout.Ticks;
            _timeoutControl.Verify(cc => cc.ResetTimeout(expectedRequestHeadersTimeout, TimeoutReason.RequestHeaders));
        }

        [Fact]
        public async Task TakeStartLineThrowsWhenTooLong()
        {
            _serviceContext.ServerOptions.Limits.MaxRequestLineSize = "GET / HTTP/1.1\r\n".Length;

            var requestLineBytes = Encoding.ASCII.GetBytes("GET /a HTTP/1.1\r\n");
            await _application.Output.WriteAsync(requestLineBytes);

            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;
            var exception = Assert.Throws<BadHttpRequestException>(() => _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.Equal(CoreStrings.BadRequest_RequestLineTooLong, exception.Message);
            Assert.Equal(StatusCodes.Status414UriTooLong, exception.StatusCode);
        }

        [Theory]
        [MemberData(nameof(TargetWithEncodedNullCharData))]
        public async Task TakeStartLineThrowsOnEncodedNullCharInTarget(string target)
        {
            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"GET {target} HTTP/1.1\r\n"));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var exception = Assert.Throws<BadHttpRequestException>(() =>
                _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target), exception.Message);
        }

        [Theory]
        [MemberData(nameof(TargetWithNullCharData))]
        public async Task TakeStartLineThrowsOnNullCharInTarget(string target)
        {
            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"GET {target} HTTP/1.1\r\n"));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var exception = Assert.Throws<BadHttpRequestException>(() =>
                _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message);
        }

        [Theory]
        [MemberData(nameof(MethodWithNullCharData))]
        public async Task TakeStartLineThrowsOnNullCharInMethod(string method)
        {
            var requestLine = $"{method} / HTTP/1.1\r\n";

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var exception = Assert.Throws<BadHttpRequestException>(() =>
                _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestLine_Detail(requestLine.EscapeNonPrintable()), exception.Message);
        }

        [Theory]
        [MemberData(nameof(QueryStringWithNullCharData))]
        public async Task TakeStartLineThrowsOnNullCharInQueryString(string queryString)
        {
            var target = $"/{queryString}";

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"GET {target} HTTP/1.1\r\n"));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var exception = Assert.Throws<BadHttpRequestException>(() =>
                _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message);
        }

        [Theory]
        [MemberData(nameof(TargetInvalidData))]
        public async Task TakeStartLineThrowsWhenRequestTargetIsInvalid(string method, string target)
        {
            var requestLine = $"{method} {target} HTTP/1.1\r\n";

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var exception = Assert.Throws<BadHttpRequestException>(() =>
                _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(target.EscapeNonPrintable()), exception.Message);
        }

        [Theory]
        [MemberData(nameof(MethodNotAllowedTargetData))]
        public async Task TakeStartLineThrowsWhenMethodNotAllowed(string requestLine, int intAllowedMethod)
        {
            var allowedMethod = (HttpMethod)intAllowedMethod;
            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(requestLine));
            var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

            var exception = Assert.Throws<BadHttpRequestException>(() =>
                _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
            _transport.Input.AdvanceTo(_consumed, _examined);

            Assert.Equal(405, exception.StatusCode);
            Assert.Equal(CoreStrings.BadRequest_MethodNotAllowed, exception.Message);
            Assert.Equal(HttpUtilities.MethodToString(allowedMethod), exception.AllowedHeader);
        }

        [Fact]
        public async Task ProcessRequestsAsyncEnablesKeepAliveTimeout()
        {
            var requestProcessingTask = _http1Connection.ProcessRequestsAsync<object>(null);

            var expectedKeepAliveTimeout = _serviceContext.ServerOptions.Limits.KeepAliveTimeout.Ticks;
            _timeoutControl.Verify(cc => cc.SetTimeout(expectedKeepAliveTimeout, TimeoutReason.KeepAlive));

            _http1Connection.StopProcessingNextRequest();
            _application.Output.Complete();

            await requestProcessingTask.DefaultTimeout();
        }

        [Fact]
        public async Task WriteThrowsForNonBodyResponse()
        {
            // Arrange
            ((IHttpResponseFeature)_http1Connection).StatusCode = StatusCodes.Status304NotModified;

            // Act/Assert
            await Assert.ThrowsAsync<InvalidOperationException>(() => _http1Connection.WriteAsync(new ArraySegment<byte>(new byte[1])));
        }

        [Fact]
        public async Task WriteAsyncThrowsForNonBodyResponse()
        {
            // Arrange
            _http1Connection.HttpVersion = "HTTP/1.1";
            ((IHttpResponseFeature)_http1Connection).StatusCode = StatusCodes.Status304NotModified;

            // Act/Assert
            await Assert.ThrowsAsync<InvalidOperationException>(() => _http1Connection.WriteAsync(new ArraySegment<byte>(new byte[1]), default(CancellationToken)));
        }

        [Fact]
        public async Task WriteDoesNotThrowForHeadResponse()
        {
            // Arrange
            _http1Connection.HttpVersion = "HTTP/1.1";
            _http1Connection.Method = HttpMethod.Head;

            // Act/Assert
            await _http1Connection.WriteAsync(new ArraySegment<byte>(new byte[1]));
        }

        [Fact]
        public async Task WriteAsyncDoesNotThrowForHeadResponse()
        {
            // Arrange
            _http1Connection.HttpVersion = "HTTP/1.1";
            _http1Connection.Method = HttpMethod.Head;

            // Act/Assert
            await _http1Connection.WriteAsync(new ArraySegment<byte>(new byte[1]), default(CancellationToken));
        }

        [Fact]
        public async Task ManuallySettingTransferEncodingThrowsForHeadResponse()
        {
            // Arrange
            _http1Connection.HttpVersion = "HTTP/1.1";
            _http1Connection.Method = HttpMethod.Head;

            // Act
            _http1Connection.ResponseHeaders.Add("Transfer-Encoding", "chunked");

            // Assert
            await Assert.ThrowsAsync<InvalidOperationException>(() => _http1Connection.FlushAsync());
        }

        [Fact]
        public async Task ManuallySettingTransferEncodingThrowsForNoBodyResponse()
        {
            // Arrange
            _http1Connection.HttpVersion = "HTTP/1.1";
            ((IHttpResponseFeature)_http1Connection).StatusCode = StatusCodes.Status304NotModified;

            // Act
            _http1Connection.ResponseHeaders.Add("Transfer-Encoding", "chunked");

            // Assert
            await Assert.ThrowsAsync<InvalidOperationException>(() => _http1Connection.FlushAsync());
        }

        [Fact]
        public async Task RequestProcessingTaskIsUnwrapped()
        {
            var requestProcessingTask = _http1Connection.ProcessRequestsAsync<object>(null);

            var data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\r\nHost:\r\n\r\n");
            await _application.Output.WriteAsync(data);

            _http1Connection.StopProcessingNextRequest();
            Assert.IsNotType<Task<Task>>(requestProcessingTask);

            await requestProcessingTask.DefaultTimeout();
            _application.Output.Complete();
        }

        [Fact]
        public async Task RequestAbortedTokenIsResetBeforeLastWriteWithContentLength()
        {
            _http1Connection.ResponseHeaders["Content-Length"] = "12";

            var original = _http1Connection.RequestAborted;

            foreach (var ch in "hello, worl")
            {
                await _http1Connection.WriteAsync(new ArraySegment<byte>(new[] { (byte)ch }));
                Assert.Equal(original, _http1Connection.RequestAborted);
            }

            await _http1Connection.WriteAsync(new ArraySegment<byte>(new[] { (byte)'d' }));
            Assert.NotEqual(original, _http1Connection.RequestAborted);

            _http1Connection.Abort(new ConnectionAbortedException());

            Assert.False(original.IsCancellationRequested);
            Assert.False(_http1Connection.RequestAborted.IsCancellationRequested);
        }

        [Fact]
        public async Task RequestAbortedTokenIsResetBeforeLastWriteAsyncWithContentLength()
        {
            _http1Connection.ResponseHeaders["Content-Length"] = "12";

            var original = _http1Connection.RequestAborted;

            foreach (var ch in "hello, worl")
            {
                await _http1Connection.WriteAsync(new ArraySegment<byte>(new[] { (byte)ch }), default(CancellationToken));
                Assert.Equal(original, _http1Connection.RequestAborted);
            }

            await _http1Connection.WriteAsync(new ArraySegment<byte>(new[] { (byte)'d' }), default(CancellationToken));
            Assert.NotEqual(original, _http1Connection.RequestAborted);

            _http1Connection.Abort(new ConnectionAbortedException());

            Assert.False(original.IsCancellationRequested);
            Assert.False(_http1Connection.RequestAborted.IsCancellationRequested);
        }

        [Fact]
        public async Task RequestAbortedTokenIsResetBeforeLastWriteAsyncAwaitedWithContentLength()
        {
            _http1Connection.ResponseHeaders["Content-Length"] = "12";

            var original = _http1Connection.RequestAborted;

            // Only first write can be WriteAsyncAwaited
            var startingTask = _http1Connection.InitializeResponseAwaited(Task.CompletedTask, 1);
            await _http1Connection.WriteAsyncAwaited(startingTask, new ArraySegment<byte>(new[] { (byte)'h' }), default(CancellationToken));
            Assert.Equal(original, _http1Connection.RequestAborted);

            foreach (var ch in "ello, worl")
            {
                await _http1Connection.WriteAsync(new ArraySegment<byte>(new[] { (byte)ch }), default(CancellationToken));
                Assert.Equal(original, _http1Connection.RequestAborted);
            }

            await _http1Connection.WriteAsync(new ArraySegment<byte>(new[] { (byte)'d' }), default(CancellationToken));
            Assert.NotEqual(original, _http1Connection.RequestAborted);

            _http1Connection.Abort(new ConnectionAbortedException());

            Assert.False(original.IsCancellationRequested);
            Assert.False(_http1Connection.RequestAborted.IsCancellationRequested);
        }

        [Fact]
        public async Task RequestAbortedTokenIsResetBeforeLastWriteWithChunkedEncoding()
        {
            var original = _http1Connection.RequestAborted;

            _http1Connection.HttpVersion = "HTTP/1.1";
            await _http1Connection.WriteAsync(new ArraySegment<byte>(Encoding.ASCII.GetBytes("hello, world")), default(CancellationToken));
            Assert.Equal(original, _http1Connection.RequestAborted);

            await _http1Connection.ProduceEndAsync();
            Assert.NotEqual(original, _http1Connection.RequestAborted);

            _http1Connection.Abort(new ConnectionAbortedException());

            Assert.False(original.IsCancellationRequested);
            Assert.False(_http1Connection.RequestAborted.IsCancellationRequested);
        }

        [Fact]
        public void RequestAbortedTokenIsFullyUsableAfterCancellation()
        {
            var originalToken = _http1Connection.RequestAborted;
            var originalRegistration = originalToken.Register(() => { });

            _http1Connection.Abort(new ConnectionAbortedException());

            Assert.True(originalToken.WaitHandle.WaitOne(TestConstants.DefaultTimeout));
            Assert.True(_http1Connection.RequestAborted.WaitHandle.WaitOne(TestConstants.DefaultTimeout));

            Assert.Equal(originalToken, originalRegistration.Token);
        }

        [Fact]
        public void RequestAbortedTokenIsUsableAfterCancellation()
        {
            var originalToken = _http1Connection.RequestAborted;
            var originalRegistration = originalToken.Register(() => { });

            _http1Connection.Abort(new ConnectionAbortedException());

            // The following line will throw an ODE because the original CTS backing the token has been diposed.
            // See https://github.com/aspnet/AspNetCore/pull/4447 for the history behind this test.
            //Assert.True(originalToken.WaitHandle.WaitOne(TestConstants.DefaultTimeout));
            Assert.True(_http1Connection.RequestAborted.WaitHandle.WaitOne(TestConstants.DefaultTimeout));

            Assert.Equal(originalToken, originalRegistration.Token);
        }

        [Fact]
        public async Task ExceptionDetailNotIncludedWhenLogLevelInformationNotEnabled()
        {
            var previousLog = _serviceContext.Log;

            try
            {
                var mockTrace = new Mock<IKestrelTrace>();
                mockTrace
                    .Setup(trace => trace.IsEnabled(LogLevel.Information))
                    .Returns(false);

                _serviceContext.Log = mockTrace.Object;

                await _application.Output.WriteAsync(Encoding.ASCII.GetBytes($"GET /%00 HTTP/1.1\r\n"));
                var readableBuffer = (await _transport.Input.ReadAsync()).Buffer;

                var exception = Assert.Throws<BadHttpRequestException>(() =>
                    _http1Connection.TakeStartLine(readableBuffer, out _consumed, out _examined));
                _transport.Input.AdvanceTo(_consumed, _examined);

                Assert.Equal(CoreStrings.FormatBadRequest_InvalidRequestTarget_Detail(string.Empty), exception.Message);
                Assert.Equal(StatusCodes.Status400BadRequest, exception.StatusCode);
            }
            finally
            {
                _serviceContext.Log = previousLog;
            }
        }

        [Theory]
        [InlineData(1, 1)]
        [InlineData(5, 5)]
        [InlineData(100, 100)]
        [InlineData(600, 100)]
        [InlineData(700, 1)]
        [InlineData(1, 700)]
        public async Task AcceptsHeadersAcrossSends(int header0Count, int header1Count)
        {
            _serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = header0Count + header1Count;

            var headers0 = MakeHeaders(header0Count);
            var headers1 = MakeHeaders(header1Count, header0Count);

            var requestProcessingTask = _http1Connection.ProcessRequestsAsync<object>(null);

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.0\r\n"));
            await WaitForCondition(TestConstants.DefaultTimeout, () => _http1Connection.RequestHeaders != null);
            Assert.Equal(0, _http1Connection.RequestHeaders.Count);

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(headers0));
            await WaitForCondition(TestConstants.DefaultTimeout, () => _http1Connection.RequestHeaders.Count >= header0Count);
            Assert.Equal(header0Count, _http1Connection.RequestHeaders.Count);

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(headers1));
            await WaitForCondition(TestConstants.DefaultTimeout, () => _http1Connection.RequestHeaders.Count >= header0Count + header1Count);
            Assert.Equal(header0Count + header1Count, _http1Connection.RequestHeaders.Count);

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("\r\n"));
            await requestProcessingTask.DefaultTimeout();
        }

        [Theory]
        [InlineData(1, 1)]
        [InlineData(5, 5)]
        [InlineData(100, 100)]
        [InlineData(600, 100)]
        [InlineData(700, 1)]
        [InlineData(1, 700)]
        public async Task KeepsSameHeaderCollectionAcrossSends(int header0Count, int header1Count)
        {
            _serviceContext.ServerOptions.Limits.MaxRequestHeaderCount = header0Count + header1Count;

            var headers0 = MakeHeaders(header0Count);
            var headers1 = MakeHeaders(header1Count, header0Count);

            var requestProcessingTask = _http1Connection.ProcessRequestsAsync<object>(null);

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("GET / HTTP/1.0\r\n"));
            await WaitForCondition(TestConstants.DefaultTimeout, () => _http1Connection.RequestHeaders != null);
            Assert.Equal(0, _http1Connection.RequestHeaders.Count);

            var newRequestHeaders = new RequestHeadersWrapper(_http1Connection.RequestHeaders);
            _http1Connection.RequestHeaders = newRequestHeaders;
            Assert.Same(newRequestHeaders, _http1Connection.RequestHeaders);

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(headers0));
            await WaitForCondition(TestConstants.DefaultTimeout, () => _http1Connection.RequestHeaders.Count >= header0Count);
            Assert.Same(newRequestHeaders, _http1Connection.RequestHeaders);
            Assert.Equal(header0Count, _http1Connection.RequestHeaders.Count);

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes(headers1));
            await WaitForCondition(TestConstants.DefaultTimeout, () => _http1Connection.RequestHeaders.Count >= header0Count + header1Count);
            Assert.Same(newRequestHeaders, _http1Connection.RequestHeaders);
            Assert.Equal(header0Count + header1Count, _http1Connection.RequestHeaders.Count);

            await _application.Output.WriteAsync(Encoding.ASCII.GetBytes("\r\n"));
            await requestProcessingTask.TimeoutAfter(TimeSpan.FromSeconds(10));
        }

        [Fact]
        public void ThrowsWhenMaxRequestBodySizeIsSetAfterReadingFromRequestBody()
        {
            // Act
            // This would normally be set by the MessageBody during the first read.
            _http1Connection.HasStartedConsumingRequestBody = true;

            // Assert
            Assert.True(((IHttpMaxRequestBodySizeFeature)_http1Connection).IsReadOnly);
            var ex = Assert.Throws<InvalidOperationException>(() => ((IHttpMaxRequestBodySizeFeature)_http1Connection).MaxRequestBodySize = 1);
            Assert.Equal(CoreStrings.MaxRequestBodySizeCannotBeModifiedAfterRead, ex.Message);
        }

        [Fact]
        public void ThrowsWhenMaxRequestBodySizeIsSetToANegativeValue()
        {
            // Assert
            var ex = Assert.Throws<ArgumentOutOfRangeException>(() => ((IHttpMaxRequestBodySizeFeature)_http1Connection).MaxRequestBodySize = -1);
            Assert.StartsWith(CoreStrings.NonNegativeNumberOrNullRequired, ex.Message);
        }

        [Fact]
        public async Task ConsumesRequestWhenApplicationDoesNotConsumeIt()
        {
            var httpApplication = new DummyApplication(async context =>
            {
                var buffer = new byte[10];
                await context.Response.Body.WriteAsync(buffer, 0, 10);
            });
            var mockMessageBody = new Mock<MessageBody>(null);
            _http1Connection.NextMessageBody = mockMessageBody.Object;

            var requestProcessingTask = _http1Connection.ProcessRequestsAsync(httpApplication);

            var data = Encoding.ASCII.GetBytes("POST / HTTP/1.1\r\nHost:\r\nConnection: close\r\ncontent-length: 1\r\n\r\n");
            await _application.Output.WriteAsync(data);
            await requestProcessingTask.DefaultTimeout();

            mockMessageBody.Verify(body => body.ConsumeAsync(), Times.Once);
        }

        [Fact]
        public void Http10HostHeaderNotRequired()
        {
            _http1Connection.HttpVersion = "HTTP/1.0";
            _http1Connection.EnsureHostHeaderExists();
        }

        [Fact]
        public void Http10HostHeaderAllowed()
        {
            _http1Connection.HttpVersion = "HTTP/1.0";
            _http1Connection.RequestHeaders[HeaderNames.Host] = "localhost:5000";
            _http1Connection.EnsureHostHeaderExists();
        }

        [Fact]
        public void Http11EmptyHostHeaderAccepted()
        {
            _http1Connection.HttpVersion = "HTTP/1.1";
            _http1Connection.RequestHeaders[HeaderNames.Host] = "";
            _http1Connection.EnsureHostHeaderExists();
        }

        [Fact]
        public void Http11ValidHostHeadersAccepted()
        {
            _http1Connection.HttpVersion = "HTTP/1.1";
            _http1Connection.RequestHeaders[HeaderNames.Host] = "localhost:5000";
            _http1Connection.EnsureHostHeaderExists();
        }

        [Fact]
        public void BadRequestFor10BadHostHeaderFormat()
        {
            _http1Connection.HttpVersion = "HTTP/1.0";
            _http1Connection.RequestHeaders[HeaderNames.Host] = "a=b";
            var ex = Assert.Throws<BadHttpRequestException>(() => _http1Connection.EnsureHostHeaderExists());
            Assert.Equal(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"), ex.Message);
        }

        [Fact]
        public void BadRequestFor11BadHostHeaderFormat()
        {
            _http1Connection.HttpVersion = "HTTP/1.1";
            _http1Connection.RequestHeaders[HeaderNames.Host] = "a=b";
            var ex = Assert.Throws<BadHttpRequestException>(() => _http1Connection.EnsureHostHeaderExists());
            Assert.Equal(CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("a=b"), ex.Message);
        }

        private static async Task WaitForCondition(TimeSpan timeout, Func<bool> condition)
        {
            const int MaxWaitLoop = 150;

            var delay = (int)Math.Ceiling(timeout.TotalMilliseconds / MaxWaitLoop);

            var waitLoop = 0;
            while (waitLoop < MaxWaitLoop && !condition())
            {
                // Wait for parsing condition to trigger
                await Task.Delay(delay);
                waitLoop++;
            }
        }

        private static string MakeHeaders(int count, int startAt = 0)
        {
            return string.Join("", Enumerable
                .Range(0, count)
                .Select(i => $"Header-{startAt + i}: value{startAt + i}\r\n"));
        }

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

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

        public static TheoryData<string> TargetWithEncodedNullCharData
        {
            get
            {
                var data = new TheoryData<string>();

                foreach (var target in HttpParsingData.TargetWithEncodedNullCharData)
                {
                    data.Add(target);
                }

                return data;
            }
        }

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

        public static TheoryData<string, int> MethodNotAllowedTargetData
            => HttpParsingData.MethodNotAllowedRequestLine;

        public static TheoryData<string> TargetWithNullCharData
        {
            get
            {
                var data = new TheoryData<string>();

                foreach (var target in HttpParsingData.TargetWithNullCharData)
                {
                    data.Add(target);
                }

                return data;
            }
        }

        public static TheoryData<string> MethodWithNullCharData
        {
            get
            {
                var data = new TheoryData<string>();

                foreach (var target in HttpParsingData.MethodWithNullCharData)
                {
                    data.Add(target);
                }

                return data;
            }
        }

        public static TheoryData<string> QueryStringWithNullCharData
        {
            get
            {
                var data = new TheoryData<string>();

                foreach (var target in HttpParsingData.QueryStringWithNullCharData)
                {
                    data.Add(target);
                }

                return data;
            }
        }

        public static TheoryData<MinDataRate> MinDataRateData => new TheoryData<MinDataRate>
        {
            null,
            new MinDataRate(bytesPerSecond: 1, gracePeriod: TimeSpan.MaxValue)
        };

        private class RequestHeadersWrapper : IHeaderDictionary
        {
            IHeaderDictionary _innerHeaders;

            public RequestHeadersWrapper(IHeaderDictionary headers)
            {
                _innerHeaders = headers;
            }

            public StringValues this[string key] { get => _innerHeaders[key]; set => _innerHeaders[key] = value; }
            public long? ContentLength { get => _innerHeaders.ContentLength; set => _innerHeaders.ContentLength = value; }
            public ICollection<string> Keys => _innerHeaders.Keys;
            public ICollection<StringValues> Values => _innerHeaders.Values;
            public int Count => _innerHeaders.Count;
            public bool IsReadOnly => _innerHeaders.IsReadOnly;
            public void Add(string key, StringValues value) => _innerHeaders.Add(key, value);
            public void Add(KeyValuePair<string, StringValues> item) => _innerHeaders.Add(item);
            public void Clear() => _innerHeaders.Clear();
            public bool Contains(KeyValuePair<string, StringValues> item) => _innerHeaders.Contains(item);
            public bool ContainsKey(string key) => _innerHeaders.ContainsKey(key);
            public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex) => _innerHeaders.CopyTo(array, arrayIndex);
            public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator() => _innerHeaders.GetEnumerator();
            public bool Remove(string key) => _innerHeaders.Remove(key);
            public bool Remove(KeyValuePair<string, StringValues> item) => _innerHeaders.Remove(item);
            public bool TryGetValue(string key, out StringValues value) => _innerHeaders.TryGetValue(key, out value);
            IEnumerator IEnumerable.GetEnumerator() => _innerHeaders.GetEnumerator();
        }
    }
}
