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

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Xunit;

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

public class HttpHeadersTests
{
    [Theory]
    [InlineData("", (int)(ConnectionOptions.None))]
    [InlineData(",", (int)(ConnectionOptions.None))]
    [InlineData(" ,", (int)(ConnectionOptions.None))]
    [InlineData(" , ", (int)(ConnectionOptions.None))]
    [InlineData(",,", (int)(ConnectionOptions.None))]
    [InlineData(" ,,", (int)(ConnectionOptions.None))]
    [InlineData(",, ", (int)(ConnectionOptions.None))]
    [InlineData(" , ,", (int)(ConnectionOptions.None))]
    [InlineData(" , , ", (int)(ConnectionOptions.None))]
    [InlineData("KEEP-ALIVE", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("keep-alive", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("keep-alive, upgrade", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("keep-alive,upgrade", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("upgrade, keep-alive", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("upgrade,keep-alive", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("upgrade,,keep-alive", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("keep-alive,", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("keep-alive,,", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("keep-alive, ", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("keep-alive, ,", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("keep-alive, , ", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("keep-alive ,", (int)(ConnectionOptions.KeepAlive))]
    [InlineData(",keep-alive", (int)(ConnectionOptions.KeepAlive))]
    [InlineData(", keep-alive", (int)(ConnectionOptions.KeepAlive))]
    [InlineData(",,keep-alive", (int)(ConnectionOptions.KeepAlive))]
    [InlineData(", ,keep-alive", (int)(ConnectionOptions.KeepAlive))]
    [InlineData(",, keep-alive", (int)(ConnectionOptions.KeepAlive))]
    [InlineData(", , keep-alive", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("UPGRADE", (int)(ConnectionOptions.Upgrade))]
    [InlineData("upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData("upgrade,", (int)(ConnectionOptions.Upgrade))]
    [InlineData("upgrade,,", (int)(ConnectionOptions.Upgrade))]
    [InlineData("upgrade, ", (int)(ConnectionOptions.Upgrade))]
    [InlineData("upgrade, ,", (int)(ConnectionOptions.Upgrade))]
    [InlineData("upgrade, , ", (int)(ConnectionOptions.Upgrade))]
    [InlineData("upgrade ,", (int)(ConnectionOptions.Upgrade))]
    [InlineData(",upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData(", upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData(",,upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData(", ,upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData(",, upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData(", , upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData("close,", (int)(ConnectionOptions.Close))]
    [InlineData("close,,", (int)(ConnectionOptions.Close))]
    [InlineData("close, ", (int)(ConnectionOptions.Close))]
    [InlineData("close, ,", (int)(ConnectionOptions.Close))]
    [InlineData("close, , ", (int)(ConnectionOptions.Close))]
    [InlineData("close ,", (int)(ConnectionOptions.Close))]
    [InlineData(",close", (int)(ConnectionOptions.Close))]
    [InlineData(", close", (int)(ConnectionOptions.Close))]
    [InlineData(",,close", (int)(ConnectionOptions.Close))]
    [InlineData(", ,close", (int)(ConnectionOptions.Close))]
    [InlineData(",, close", (int)(ConnectionOptions.Close))]
    [InlineData(", , close", (int)(ConnectionOptions.Close))]
    [InlineData("kupgrade", (int)(ConnectionOptions.None))]
    [InlineData("keupgrade", (int)(ConnectionOptions.None))]
    [InlineData("ukeep-alive", (int)(ConnectionOptions.None))]
    [InlineData("upkeep-alive", (int)(ConnectionOptions.None))]
    [InlineData("k,upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData("u,keep-alive", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("ke,upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData("up,keep-alive", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("CLOSE", (int)(ConnectionOptions.Close))]
    [InlineData("close", (int)(ConnectionOptions.Close))]
    [InlineData("upgrade,close", (int)(ConnectionOptions.Close | ConnectionOptions.Upgrade))]
    [InlineData("close,upgrade", (int)(ConnectionOptions.Close | ConnectionOptions.Upgrade))]
    [InlineData("keep-alive2", (int)(ConnectionOptions.None))]
    [InlineData("keep-alive2 ", (int)(ConnectionOptions.None))]
    [InlineData("keep-alive2 ,", (int)(ConnectionOptions.None))]
    [InlineData("keep-alive2,", (int)(ConnectionOptions.None))]
    [InlineData("upgrade2", (int)(ConnectionOptions.None))]
    [InlineData("upgrade2 ", (int)(ConnectionOptions.None))]
    [InlineData("upgrade2 ,", (int)(ConnectionOptions.None))]
    [InlineData("upgrade2,", (int)(ConnectionOptions.None))]
    [InlineData("close2", (int)(ConnectionOptions.None))]
    [InlineData("close2 ", (int)(ConnectionOptions.None))]
    [InlineData("close2 ,", (int)(ConnectionOptions.None))]
    [InlineData("close2,", (int)(ConnectionOptions.None))]
    [InlineData("close close", (int)(ConnectionOptions.None))]
    [InlineData("close dclose", (int)(ConnectionOptions.None))]
    [InlineData("keep-alivekeep-alive", (int)(ConnectionOptions.None))]
    [InlineData("keep-aliveupgrade", (int)(ConnectionOptions.None))]
    [InlineData("upgradeupgrade", (int)(ConnectionOptions.None))]
    [InlineData("upgradekeep-alive", (int)(ConnectionOptions.None))]
    [InlineData("closeclose", (int)(ConnectionOptions.None))]
    [InlineData("closeupgrade", (int)(ConnectionOptions.None))]
    [InlineData("upgradeclose", (int)(ConnectionOptions.None))]
    [InlineData("keep-alive 2", (int)(ConnectionOptions.None))]
    [InlineData("upgrade 2", (int)(ConnectionOptions.None))]
    [InlineData("keep-alive 2, close", (int)(ConnectionOptions.Close))]
    [InlineData("upgrade 2, close", (int)(ConnectionOptions.Close))]
    [InlineData("close, keep-alive 2", (int)(ConnectionOptions.Close))]
    [InlineData("close, upgrade 2", (int)(ConnectionOptions.Close))]
    [InlineData("close 2, upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData("upgrade, close 2", (int)(ConnectionOptions.Upgrade))]
    [InlineData("k2ep-alive", (int)(ConnectionOptions.None))]
    [InlineData("ke2p-alive", (int)(ConnectionOptions.None))]
    [InlineData("u2grade", (int)(ConnectionOptions.None))]
    [InlineData("up2rade", (int)(ConnectionOptions.None))]
    [InlineData("c2ose", (int)(ConnectionOptions.None))]
    [InlineData("cl2se", (int)(ConnectionOptions.None))]
    [InlineData("k2ep-alive,", (int)(ConnectionOptions.None))]
    [InlineData("ke2p-alive,", (int)(ConnectionOptions.None))]
    [InlineData("u2grade,", (int)(ConnectionOptions.None))]
    [InlineData("up2rade,", (int)(ConnectionOptions.None))]
    [InlineData("c2ose,", (int)(ConnectionOptions.None))]
    [InlineData("cl2se,", (int)(ConnectionOptions.None))]
    [InlineData("k2ep-alive ", (int)(ConnectionOptions.None))]
    [InlineData("ke2p-alive ", (int)(ConnectionOptions.None))]
    [InlineData("u2grade ", (int)(ConnectionOptions.None))]
    [InlineData("up2rade ", (int)(ConnectionOptions.None))]
    [InlineData("c2ose ", (int)(ConnectionOptions.None))]
    [InlineData("cl2se ", (int)(ConnectionOptions.None))]
    [InlineData("k2ep-alive ,", (int)(ConnectionOptions.None))]
    [InlineData("ke2p-alive ,", (int)(ConnectionOptions.None))]
    [InlineData("u2grade ,", (int)(ConnectionOptions.None))]
    [InlineData("up2rade ,", (int)(ConnectionOptions.None))]
    [InlineData("c2ose ,", (int)(ConnectionOptions.None))]
    [InlineData("cl2se ,", (int)(ConnectionOptions.None))]
    public void TestParseConnection(string connection, int intExpectedConnectionOptions)
    {
        var expectedConnectionOptions = (ConnectionOptions)intExpectedConnectionOptions;
        var requestHeaders = new HttpRequestHeaders();
        requestHeaders.HeaderConnection = connection;
        var connectionOptions = HttpHeaders.ParseConnection(requestHeaders);
        Assert.Equal(expectedConnectionOptions, connectionOptions);
    }

    [Theory]
    [InlineData("keep-alive", "upgrade", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("upgrade", "keep-alive", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("keep-alive", "", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("", "keep-alive", (int)(ConnectionOptions.KeepAlive))]
    [InlineData("upgrade", "", (int)(ConnectionOptions.Upgrade))]
    [InlineData("", "upgrade", (int)(ConnectionOptions.Upgrade))]
    [InlineData("keep-alive, upgrade", "", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("upgrade, keep-alive", "", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("", "keep-alive, upgrade", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("", "upgrade, keep-alive", (int)(ConnectionOptions.KeepAlive | ConnectionOptions.Upgrade))]
    [InlineData("", "", (int)(ConnectionOptions.None))]
    [InlineData("close", "", (int)(ConnectionOptions.Close))]
    [InlineData("", "close", (int)(ConnectionOptions.Close))]
    [InlineData("close", "upgrade", (int)(ConnectionOptions.Close | ConnectionOptions.Upgrade))]
    [InlineData("upgrade", "close", (int)(ConnectionOptions.Close | ConnectionOptions.Upgrade))]
    public void TestParseConnectionMultipleValues(string value1, string value2, int intExpectedConnectionOptions)
    {
        var expectedConnectionOptions = (ConnectionOptions)intExpectedConnectionOptions;
        var connection = new StringValues(new[] { value1, value2 });
        var requestHeaders = new HttpRequestHeaders();
        requestHeaders.HeaderConnection = connection;
        var connectionOptions = HttpHeaders.ParseConnection(requestHeaders);
        Assert.Equal(expectedConnectionOptions, connectionOptions);
    }

    [Theory]
    [InlineData("", (int)(TransferCoding.None))]
    [InlineData(",,", (int)(TransferCoding.None))]
    [InlineData(" ,,", (int)(TransferCoding.None))]
    [InlineData(",, ", (int)(TransferCoding.None))]
    [InlineData(" , ,", (int)(TransferCoding.None))]
    [InlineData(" , , ", (int)(TransferCoding.None))]
    [InlineData("c", (int)(TransferCoding.Other))]
    [InlineData("z", (int)(TransferCoding.Other))]
    [InlineData("chunk", (int)(TransferCoding.Other))]
    [InlineData("chunked,", (int)(TransferCoding.Chunked))]
    [InlineData("chunked,,", (int)(TransferCoding.Chunked))]
    [InlineData("chunked, ", (int)(TransferCoding.Chunked))]
    [InlineData("chunked, ,", (int)(TransferCoding.Chunked))]
    [InlineData("chunked, , ", (int)(TransferCoding.Chunked))]
    [InlineData("chunked ,", (int)(TransferCoding.Chunked))]
    [InlineData(",chunked", (int)(TransferCoding.Chunked))]
    [InlineData(", chunked", (int)(TransferCoding.Chunked))]
    [InlineData(",,chunked", (int)(TransferCoding.Chunked))]
    [InlineData(", ,chunked", (int)(TransferCoding.Chunked))]
    [InlineData(",, chunked", (int)(TransferCoding.Chunked))]
    [InlineData(", , chunked", (int)(TransferCoding.Chunked))]
    [InlineData("chunked, gzip", (int)(TransferCoding.Other))]
    [InlineData("chunked,compress", (int)(TransferCoding.Other))]
    [InlineData("deflate, chunked", (int)(TransferCoding.Chunked))]
    [InlineData("gzip,chunked", (int)(TransferCoding.Chunked))]
    [InlineData("compress,,chunked", (int)(TransferCoding.Chunked))]
    [InlineData("chunked,c", (int)(TransferCoding.Other))]
    [InlineData("chunked,z", (int)(TransferCoding.Other))]
    [InlineData("chunked,zz", (int)(TransferCoding.Other))]
    [InlineData("chunked, z", (int)(TransferCoding.Other))]
    [InlineData("chunked, zz", (int)(TransferCoding.Other))]
    [InlineData("chunked,chunk", (int)(TransferCoding.Other))]
    [InlineData("z,chunked", (int)(TransferCoding.Chunked))]
    [InlineData("z, chunked", (int)(TransferCoding.Chunked))]
    [InlineData("chunkedchunked", (int)(TransferCoding.Other))]
    [InlineData("chunked2", (int)(TransferCoding.Other))]
    [InlineData("chunked 2", (int)(TransferCoding.Other))]
    [InlineData("2chunked", (int)(TransferCoding.Other))]
    [InlineData("c2unked", (int)(TransferCoding.Other))]
    [InlineData("ch2nked", (int)(TransferCoding.Other))]
    [InlineData("chunked 2, gzip", (int)(TransferCoding.Other))]
    [InlineData("chunked2, gzip", (int)(TransferCoding.Other))]
    [InlineData("gzip, chunked 2", (int)(TransferCoding.Other))]
    [InlineData("gzip, chunked2", (int)(TransferCoding.Other))]
    public void TestParseTransferEncoding(string transferEncoding, int intExpectedTransferEncodingOptions)
    {
        var expectedTransferEncodingOptions = (TransferCoding)intExpectedTransferEncodingOptions;

        var transferEncodingOptions = HttpHeaders.GetFinalTransferCoding(transferEncoding);
        Assert.Equal(expectedTransferEncodingOptions, transferEncodingOptions);
    }

    [Theory]
    [InlineData("chunked", "gzip", (int)(TransferCoding.Other))]
    [InlineData("compress", "chunked", (int)(TransferCoding.Chunked))]
    [InlineData("chunked", "", (int)(TransferCoding.Chunked))]
    [InlineData("", "chunked", (int)(TransferCoding.Chunked))]
    [InlineData("chunked, deflate", "", (int)(TransferCoding.Other))]
    [InlineData("gzip, chunked", "", (int)(TransferCoding.Chunked))]
    [InlineData("", "chunked, compress", (int)(TransferCoding.Other))]
    [InlineData("", "compress, chunked", (int)(TransferCoding.Chunked))]
    [InlineData("", "", (int)(TransferCoding.None))]
    [InlineData("deflate", "", (int)(TransferCoding.Other))]
    [InlineData("", "gzip", (int)(TransferCoding.Other))]
    public void TestParseTransferEncodingMultipleValues(string value1, string value2, int intExpectedTransferEncodingOptions)
    {
        var expectedTransferEncodingOptions = (TransferCoding)intExpectedTransferEncodingOptions;

        var transferEncoding = new StringValues(new[] { value1, value2 });
        var transferEncodingOptions = HttpHeaders.GetFinalTransferCoding(transferEncoding);
        Assert.Equal(expectedTransferEncodingOptions, transferEncodingOptions);
    }

    [Fact]
    public void ValidContentLengthsAccepted()
    {
        ValidContentLengthsAcceptedImpl(new HttpRequestHeaders());
        ValidContentLengthsAcceptedImpl(new HttpResponseHeaders());
    }

    private static void ValidContentLengthsAcceptedImpl(HttpHeaders httpHeaders)
    {
        IDictionary<string, StringValues> headers = httpHeaders;

        Assert.False(headers.TryGetValue("Content-Length", out var value));
        Assert.Null(httpHeaders.ContentLength);
        Assert.False(httpHeaders.ContentLength.HasValue);

        httpHeaders.ContentLength = 1;
        Assert.True(headers.TryGetValue("Content-Length", out value));
        Assert.Equal("1", value[0]);
        Assert.Equal(1, httpHeaders.ContentLength);
        Assert.True(httpHeaders.ContentLength.HasValue);

        httpHeaders.ContentLength = long.MaxValue;
        Assert.True(headers.TryGetValue("Content-Length", out value));
        Assert.Equal(HeaderUtilities.FormatNonNegativeInt64(long.MaxValue), value[0]);
        Assert.Equal(long.MaxValue, httpHeaders.ContentLength);
        Assert.True(httpHeaders.ContentLength.HasValue);

        httpHeaders.ContentLength = null;
        Assert.False(headers.TryGetValue("Content-Length", out value));
        Assert.Null(httpHeaders.ContentLength);
        Assert.False(httpHeaders.ContentLength.HasValue);
    }

    [Fact]
    public void InvalidContentLengthsRejected()
    {
        InvalidContentLengthsRejectedImpl(new HttpRequestHeaders());
        InvalidContentLengthsRejectedImpl(new HttpResponseHeaders());
    }

    private static void InvalidContentLengthsRejectedImpl(HttpHeaders httpHeaders)
    {
        IDictionary<string, StringValues> headers = httpHeaders;

        StringValues value;

        Assert.False(headers.TryGetValue("Content-Length", out value));
        Assert.Null(httpHeaders.ContentLength);
        Assert.False(httpHeaders.ContentLength.HasValue);

        Assert.Throws<ArgumentOutOfRangeException>(() => httpHeaders.ContentLength = -1);
        Assert.Throws<ArgumentOutOfRangeException>(() => httpHeaders.ContentLength = long.MinValue);

        Assert.False(headers.TryGetValue("Content-Length", out value));
        Assert.Null(httpHeaders.ContentLength);
        Assert.False(httpHeaders.ContentLength.HasValue);
    }
}
