// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
using Microsoft.AspNetCore.Testing;
using Xunit;

namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests;

public class RequestTargetProcessingTests : LoggedTest
{
    [Fact]
    public async Task RequestPathIsNotNormalized()
    {
        var testContext = new TestServiceContext(LoggerFactory);

        await using (var server = new TestServer(async context =>
        {
            Assert.Equal("/\u0041\u030A/B/\u0041\u030A", context.Request.Path.Value);

            context.Response.Headers.ContentLength = 11;
            await context.Response.WriteAsync("Hello World");
        }, testContext))
        {
            using (var connection = server.CreateConnection())
            {
                await connection.Send(
                    "GET /%41%CC%8A/A/../B/%41%CC%8A HTTP/1.1",
                    "Host:",
                    "",
                    "");
                await connection.Receive(
                    "HTTP/1.1 200 OK",
                    "Content-Length: 11",
                    $"Date: {testContext.DateHeaderValue}",
                    "",
                    "Hello World");
            }
        }
    }

    [Theory]
    [InlineData("/")]
    [InlineData("/.")]
    [InlineData("/..")]
    [InlineData("/./.")]
    [InlineData("/./..")]
    [InlineData("/../.")]
    [InlineData("/../..")]
    [InlineData("/path")]
    [InlineData("/path?foo=1&bar=2")]
    [InlineData("/hello%20world")]
    [InlineData("/hello%20world?foo=1&bar=2")]
    [InlineData("/base/path")]
    [InlineData("/base/path?foo=1&bar=2")]
    [InlineData("/base/hello%20world")]
    [InlineData("/base/hello%20world?foo=1&bar=2")]
    public async Task RequestFeatureContainsRawTarget(string requestTarget)
    {
        var testContext = new TestServiceContext(LoggerFactory);

        await using (var server = new TestServer(async context =>
        {
            Assert.Equal(requestTarget, context.Features.Get<IHttpRequestFeature>().RawTarget);

            context.Response.Headers["Content-Length"] = new[] { "11" };
            await context.Response.WriteAsync("Hello World");
        }, testContext))
        {
            using (var connection = server.CreateConnection())
            {
                await connection.Send(
                    $"GET {requestTarget} HTTP/1.1",
                    "Host:",
                    "",
                    "");
                await connection.Receive(
                    "HTTP/1.1 200 OK",
                    "Content-Length: 11",
                    $"Date: {testContext.DateHeaderValue}",
                    "",
                    "Hello World");
            }
        }
    }

    [Theory]
    [InlineData(HttpMethod.Options, "*")]
    [InlineData(HttpMethod.Connect, "host")]
    public async Task NonPathRequestTargetSetInRawTarget(HttpMethod method, string requestTarget)
    {
        var testContext = new TestServiceContext(LoggerFactory);

        await using (var server = new TestServer(async context =>
        {
            Assert.Equal(requestTarget, context.Features.Get<IHttpRequestFeature>().RawTarget);
            Assert.Empty(context.Request.Path.Value);
            Assert.Empty(context.Request.PathBase.Value);
            Assert.Empty(context.Request.QueryString.Value);

            await context.Response.CompleteAsync();
        }, testContext))
        {
            using (var connection = server.CreateConnection())
            {
                var host = method == HttpMethod.Connect
                    ? requestTarget
                    : string.Empty;

                await connection.Send(
                    $"{HttpUtilities.MethodToString(method)} {requestTarget} HTTP/1.1",
                    $"Host: {host}",
                    "",
                    "");
            }
        }
    }
}
