// 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 Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Xunit;

namespace Microsoft.AspNetCore.Testing
{
    public class HttpParsingData
    {
        public static IEnumerable<string[]> RequestLineValidData
        {
            get
            {
                var methods = new[]
                {
                    "GET",
                    "CUSTOM",
                };
                var paths = new[]
                {
                    Tuple.Create("/", "/"),
                    Tuple.Create("/abc", "/abc"),
                    Tuple.Create("/abc/de/f", "/abc/de/f"),
                    Tuple.Create("/%20", "/ "),
                    Tuple.Create("/a%20", "/a "),
                    Tuple.Create("/%20a", "/ a"),
                    Tuple.Create("/a/b%20c", "/a/b c"),
                    Tuple.Create("/%C3%A5", "/\u00E5"),
                    Tuple.Create("/a%C3%A5a", "/a\u00E5a"),
                    Tuple.Create("/%C3%A5/bc", "/\u00E5/bc"),
                    Tuple.Create("/%25", "/%"),
                    Tuple.Create("/%25%30%30", "/%00"),
                    Tuple.Create("/%%2000", "/% 00"),
                    Tuple.Create("/%2F", "/%2F"),
                    Tuple.Create("http://host/abs/path", "/abs/path"),
                    Tuple.Create("http://host/abs/path/", "/abs/path/"),
                    Tuple.Create("http://host/a%20b%20c/", "/a b c/"),
                    Tuple.Create("https://host/abs/path", "/abs/path"),
                    Tuple.Create("https://host/abs/path/", "/abs/path/"),
                    Tuple.Create("https://host:22/abs/path", "/abs/path"),
                    Tuple.Create("https://user@host:9080/abs/path", "/abs/path"),
                    Tuple.Create("http://host/", "/"),
                    Tuple.Create("http://host", "/"),
                    Tuple.Create("https://host/", "/"),
                    Tuple.Create("https://host", "/"),
                    Tuple.Create("http://user@host/", "/"),
                    Tuple.Create("http://127.0.0.1/", "/"),
                    Tuple.Create("http://user@127.0.0.1/", "/"),
                    Tuple.Create("http://user@127.0.0.1:8080/", "/"),
                    Tuple.Create("http://127.0.0.1:8080/", "/"),
                    Tuple.Create("http://[::1]", "/"),
                    Tuple.Create("http://[::1]/path", "/path"),
                    Tuple.Create("http://[::1]:8080/", "/"),
                    Tuple.Create("http://user@[::1]:8080/", "/"),
                };
                var queryStrings = new[]
                {
                    "",
                    "?",
                    "?arg1=val1",
                    "?arg1=a%20b",
                    "?%A",
                    "?%20=space",
                    "?%C3%A5=val",
                    "?path=/home",
                    "?path=/%C3%A5/",
                    "?question=what?",
                    "?%00",
                    "?arg=%00"
                };
                var httpVersions = new[]
                {
                    "HTTP/1.0",
                    "HTTP/1.1"
                };

                return from method in methods
                       from path in paths
                       from queryString in queryStrings
                       from httpVersion in httpVersions
                       select new[]
                       {
                           $"{method} {path.Item1}{queryString} {httpVersion}\r\n",
                           method,
                           $"{path.Item1}{queryString}",
                           $"{path.Item1}",
                           $"{path.Item2}",
                           queryString,
                           httpVersion
                       };
            }
        }

        public static IEnumerable<string[]> RequestLineDotSegmentData => new[]
        {
            new[] { "GET /a/../b HTTP/1.1\r\n", "/a/../b", "/b", "" },
            new[] { "GET /%61/../%62 HTTP/1.1\r\n", "/%61/../%62", "/b", "" },
            new[] { "GET /a/%2E%2E/b HTTP/1.1\r\n", "/a/%2E%2E/b", "/b", "" },
            new[] { "GET /%61/%2E%2E/%62 HTTP/1.1\r\n", "/%61/%2E%2E/%62", "/b", "" },
            new[] { "GET /a?p=/a/../b HTTP/1.1\r\n", "/a?p=/a/../b", "/a", "?p=/a/../b" },
            new[] { "GET /a?p=/a/%2E%2E/b HTTP/1.1\r\n", "/a?p=/a/%2E%2E/b", "/a", "?p=/a/%2E%2E/b" },
            new[] { "GET http://example.com/a/../b HTTP/1.1\r\n", "http://example.com/a/../b", "/b", "" },
            new[] { "GET http://example.com/%61/../%62 HTTP/1.1\r\n", "http://example.com/%61/../%62", "/b", "" },
            new[] { "GET http://example.com/a/%2E%2E/b HTTP/1.1\r\n", "http://example.com/a/%2E%2E/b", "/b", "" },
            new[] { "GET http://example.com/%61/%2E%2E/%62 HTTP/1.1\r\n", "http://example.com/%61/%2E%2E/%62", "/b", "" },
            new[] { "GET http://example.com/a?p=/a/../b HTTP/1.1\r\n", "http://example.com/a?p=/a/../b", "/a", "?p=/a/../b" },
            new[] { "GET http://example.com/a?p=/a/%2E%2E/b HTTP/1.1\r\n", "http://example.com/a?p=/a/%2E%2E/b", "/a", "?p=/a/%2E%2E/b" },
            new[] { "GET http://example.com?p=/a/../b HTTP/1.1\r\n", "http://example.com?p=/a/../b", "/", "?p=/a/../b" },
            new[] { "GET http://example.com?p=/a/%2E%2E/b HTTP/1.1\r\n", "http://example.com?p=/a/%2E%2E/b", "/", "?p=/a/%2E%2E/b" },

            // Asterisk-form and authority-form should be unaffected and cause no issues
            new[] { "OPTIONS * HTTP/1.1\r\n", "*", "", "" },
            new[] { "CONNECT www.example.com HTTP/1.1\r\n", "www.example.com", "", "" },
        };

        public static IEnumerable<string> RequestLineIncompleteData => new[]
        {
            "G",
            "GE",
            "GET",
            "GET ",
            "GET /",
            "GET / ",
            "GET / H",
            "GET / HT",
            "GET / HTT",
            "GET / HTTP",
            "GET / HTTP/",
            "GET / HTTP/1",
            "GET / HTTP/1.",
            "GET / HTTP/1.1",
            "GET / HTTP/1.1\r",
        };

        public static IEnumerable<string> RequestLineInvalidData
        {
            get
            {
                return new[]
                {
                    "G\r\n",
                    "GE\r\n",
                    "GET\r\n",
                    "GET \r\n",
                    "GET /\r\n",
                    "GET / \r\n",
                    "GET/HTTP/1.1\r\n",
                    "GET /HTTP/1.1\r\n",
                    " \r\n",
                    "  \r\n",
                    "/ HTTP/1.1\r\n",
                    " / HTTP/1.1\r\n",
                    "/ \r\n",
                    "GET  \r\n",
                    "GET  HTTP/1.0\r\n",
                    "GET  HTTP/1.1\r\n",
                    "GET / \n",
                    "GET / HTTP/1.0\n",
                    "GET / HTTP/1.1\n",
                    "GET / HTTP/1.0\rA\n",
                    "GET / HTTP/1.1\ra\n",
                    "GET? / HTTP/1.1\r\n",
                    "GET ? HTTP/1.1\r\n",
                    "GET /a?b=cHTTP/1.1\r\n",
                    "GET /a%20bHTTP/1.1\r\n",
                    "GET /a%20b?c=dHTTP/1.1\r\n",
                    "GET %2F HTTP/1.1\r\n",
                    "GET %00 HTTP/1.1\r\n",
                    "CUSTOM \r\n",
                    "CUSTOM /\r\n",
                    "CUSTOM / \r\n",
                    "CUSTOM /HTTP/1.1\r\n",
                    "CUSTOM  \r\n",
                    "CUSTOM  HTTP/1.0\r\n",
                    "CUSTOM  HTTP/1.1\r\n",
                    "CUSTOM / \n",
                    "CUSTOM / HTTP/1.0\n",
                    "CUSTOM / HTTP/1.1\n",
                    "CUSTOM / HTTP/1.0\rA\n",
                    "CUSTOM / HTTP/1.1\ra\n",
                    "CUSTOM ? HTTP/1.1\r\n",
                    "CUSTOM /a?b=cHTTP/1.1\r\n",
                    "CUSTOM /a%20bHTTP/1.1\r\n",
                    "CUSTOM /a%20b?c=dHTTP/1.1\r\n",
                    "CUSTOM %2F HTTP/1.1\r\n",
                    "CUSTOM %00 HTTP/1.1\r\n",
                }.Concat(MethodWithNonTokenCharData.Select(method => $"{method} / HTTP/1.0\r\n"));
            }
        }

        // Bad HTTP Methods (invalid according to RFC)
        public static IEnumerable<string> MethodWithNonTokenCharData
        {
            get
            {
                return new[]
                {
                    "(",
                    ")",
                    "<",
                    ">",
                    "@",
                    ",",
                    ";",
                    ":",
                    "\\",
                    "\"",
                    "/",
                    "[",
                    "]",
                    "?",
                    "=",
                    "{",
                    "}",
                    "get@",
                    "post=",
                    "[0x00]"
                }.Concat(MethodWithNullCharData);
            }
        }

        public static IEnumerable<string> MethodWithNullCharData => new[]
        {
            // Bad HTTP Methods (invalid according to RFC)
            "\0",
            "\0GET",
            "G\0T",
            "GET\0",
        };

        public static IEnumerable<string> TargetWithEncodedNullCharData => new[]
        {
            "/%00",
            "/%00%00",
            "/%E8%00%84",
            "/%E8%85%00",
            "/%F3%00%82%86",
            "/%F3%85%00%82",
            "/%F3%85%82%00",
        };

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

                // Invalid absolute-form
                data.Add("GET", "http://");
                data.Add("GET", "http:/");
                data.Add("GET", "https:/");
                data.Add("GET", "http:///");
                data.Add("GET", "https://");
                data.Add("GET", "http:////");
                data.Add("GET", "http://:80");
                data.Add("GET", "http://:80/abc");
                data.Add("GET", "http://user@");
                data.Add("GET", "http://user@/abc");
                data.Add("GET", "http://abc%20xyz/abc");
                data.Add("GET", "http://%20/abc?query=%0A");
                // Valid absolute-form but with unsupported schemes
                data.Add("GET", "otherscheme://host/");
                data.Add("GET", "ws://host/");
                data.Add("GET", "wss://host/");
                // Must only have one asterisk
                data.Add("OPTIONS", "**");
                // Relative form
                data.Add("GET", "../../");
                data.Add("GET", "..\\.");

                return data;
            }
        }

        public static TheoryData<string, int> MethodNotAllowedRequestLine
        {
            get
            {
                var methods = new[]
                {
                    "GET",
                    "PUT",
                    "DELETE",
                    "POST",
                    "HEAD",
                    "TRACE",
                    "PATCH",
                    "CONNECT",
                    "OPTIONS",
                    "CUSTOM",
                };

                var data = new TheoryData<string, int>();

                foreach (var method in methods.Except(new[] { "OPTIONS" }))
                {
                    data.Add($"{method} * HTTP/1.1\r\n", (int)HttpMethod.Options);
                }

                foreach (var method in methods.Except(new[] { "CONNECT" }))
                {
                    data.Add($"{method} www.example.com:80 HTTP/1.1\r\n", (int)HttpMethod.Connect);
                }

                return data;
            }
        }

        public static IEnumerable<string> TargetWithNullCharData
        {
            get
            {
                return new[]
                {
                    "\0",
                    "/\0",
                    "/\0\0",
                    "/%C8\0",
                }.Concat(QueryStringWithNullCharData);
            }
        }

        public static IEnumerable<string> QueryStringWithNullCharData => new[]
        {
            "/?\0=a",
            "/?a=\0",
        };

        public static TheoryData<string> UnrecognizedHttpVersionData => new TheoryData<string>
        {
            " ",
            "/",
            "H",
            "HT",
            "HTT",
            "HTTP",
            "HTTP/",
            "HTTP/1",
            "HTTP/1.",
            "http/1.0",
            "http/1.1",
            "HTTP/1.1 ",
            "HTTP/1.0a",
            "HTTP/1.0ab",
            "HTTP/1.1a",
            "HTTP/1.1ab",
            "HTTP/1.2",
            "HTTP/3.0",
            "hello",
            "8charact",
        };

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

            // Line folding
            new[] { "Header: line1\r\n line2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" line2\x0D\x0A") },
            new[] { "Header: line1\r\n\tline2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"\x09line2\x0D\x0A") },
            new[] { "Header: line1\r\n  line2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"  line2\x0D\x0A") },
            new[] { "Header: line1\r\n \tline2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" \x09line2\x0D\x0A") },
            new[] { "Header: line1\r\n\t line2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"\x09 line2\x0D\x0A") },
            new[] { "Header: line1\r\n\t\tline2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"\x09\x09line2\x0D\x0A") },
            new[] { "Header: line1\r\n \t\t line2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" \x09\x09 line2\x0D\x0A") },
            new[] { "Header: line1\r\n \t \t line2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" \x09 \x09 line2\x0D\x0A") },
            new[] { "Header-1: multi\r\n line\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" line\x0D\x0A") },
            new[] { "Header-1: value1\r\nHeader-2: multi\r\n line\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" line\x0D\x0A") },
            new[] { "Header-1: value1\r\n Header-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" Header-2: value2\x0D\x0A") },
            new[] { "Header-1: value1\r\n\tHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"\x09Header-2: value2\x0D\x0A") },

            // CR in value
            new[] { "Header-1: value1\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: value1\x0D\x0D\x0A") },
            new[] { "Header-1: val\rue1\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: val\x0Due1\x0D\x0A") },
            new[] { "Header-1: value1\rHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: value1\x0DHeader-2: value2\x0D\x0A") },
            new[] { "Header-1: value1\r\nHeader-2: value2\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2: value2\x0D\x0D\x0A") },
            new[] { "Header-1: value1\r\nHeader-2: v\ralue2\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2: v\x0Dalue2\x0D\x0A") },
            new[] { "Header-1: Value__\rVector16________Vector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value__\x0DVector16________Vector32\x0D\x0A") },
            new[] { "Header-1: Value___Vector16\r________Vector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16\x0D________Vector32\x0D\x0A") },
            new[] { "Header-1: Value___Vector16_______\rVector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16_______\x0DVector32\x0D\x0A") },
            new[] { "Header-1: Value___Vector16________Vector32\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32\x0D\x0D\x0A") },
            new[] { "Header-1: Value___Vector16________Vector32_\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32_\x0D\x0D\x0A") },
            new[] { "Header-1: Value___Vector16________Vector32Value___Vector16_______\rVector32\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32Value___Vector16_______\x0DVector32\x0D\x0A") },
            new[] { "Header-1: Value___Vector16________Vector32Value___Vector16________Vector32\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32Value___Vector16________Vector32\x0D\x0D\x0A") },
            new[] { "Header-1: Value___Vector16________Vector32Value___Vector16________Vector32_\r\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1: Value___Vector16________Vector32Value___Vector16________Vector32_\x0D\x0D\x0A") },

            // Missing colon
            new[] { "Header-1 value1\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1 value1\x0D\x0A") },
            new[] { "Header-1 value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-1 value1\x0D\x0A") },
            new[] { "Header-1: value1\r\nHeader-2 value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2 value2\x0D\x0A") },
            new[] { "\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"\x0A") },

            // Starting with whitespace
            new[] { " Header: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" Header: value\x0D\x0A") },
            new[] { "\tHeader: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"\x09Header: value\x0D\x0A") },
            new[] { " Header-1: value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@" Header-1: value1\x0D\x0A") },
            new[] { "\tHeader-1: value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"\x09Header-1: value1\x0D\x0A") },

            // Whitespace in header name
            new[] { "Header : value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header : value\x0D\x0A") },
            new[] { "Header\t: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header\x09: value\x0D\x0A") },
            new[] { "Header\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header\x0D: value\x0D\x0A") },
            new[] { "Header_\rVector16: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header_\x0DVector16: value\x0D\x0A") },
            new[] { "Header__Vector16\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16\x0D: value\x0D\x0A") },
            new[] { "Header__Vector16_\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16_\x0D: value\x0D\x0A") },
            new[] { "Header_\rVector16________Vector32: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header_\x0DVector16________Vector32: value\x0D\x0A") },
            new[] { "Header__Vector16________Vector32\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32\x0D: value\x0D\x0A") },
            new[] { "Header__Vector16________Vector32_\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32_\x0D: value\x0D\x0A") },
            new[] { "Header__Vector16________Vector32Header_\rVector16________Vector32: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32Header_\x0DVector16________Vector32: value\x0D\x0A") },
            new[] { "Header__Vector16________Vector32Header__Vector16________Vector32\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32Header__Vector16________Vector32\x0D: value\x0D\x0A") },
            new[] { "Header__Vector16________Vector32Header__Vector16________Vector32_\r: value\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header__Vector16________Vector32Header__Vector16________Vector32_\x0D: value\x0D\x0A") },
            new[] { "Header 1: value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 1: value1\x0D\x0A") },
            new[] { "Header 1 : value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 1 : value1\x0D\x0A") },
            new[] { "Header 1\t: value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 1\x09: value1\x0D\x0A") },
            new[] { "Header 1\r: value1\r\nHeader-2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 1\x0D: value1\x0D\x0A") },
            new[] { "Header-1: value1\r\nHeader 2: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header 2: value2\x0D\x0A") },
            new[] { "Header-1: value1\r\nHeader-2 : value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2 : value2\x0D\x0A") },
            new[] { "Header-1: value1\r\nHeader-2\t: value2\r\n\r\n", CoreStrings.FormatBadRequest_InvalidRequestHeader_Detail(@"Header-2\x09: value2\x0D\x0A") },

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

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

        public static TheoryData<string, string> HostHeaderData
            => new TheoryData<string, string>
                {
                    { "OPTIONS *", "" },
                    { "GET /pub/WWW/", "" },
                    { "GET /pub/WWW/", "   " },
                    { "GET /pub/WWW/", "." },
                    { "GET /pub/WWW/", "www.example.org" },
                    { "GET http://localhost/", "localhost" },
                    { "GET http://localhost:80/", "localhost:80" },
                    { "GET https://localhost/", "localhost" },
                    { "GET https://localhost:443/", "localhost:443" },
                    { "CONNECT asp.net:80", "asp.net:80" },
                    { "CONNECT asp.net:443", "asp.net:443" },
                };

        public static TheoryData<string, string> HostHeaderInvalidData
        {
            get
            {
                // see https://tools.ietf.org/html/rfc7230#section-5.4
                var invalidHostValues = new[] {
                    "",
                    "   ",
                    "contoso.com:4000",
                    "contoso.com/",
                    "not-contoso.com",
                    "user@password:contoso.com",
                    "user@contoso.com",
                    "http://contoso.com/",
                    "http://contoso.com"
                };

                var data = new TheoryData<string, string>();

                foreach (var host in invalidHostValues)
                {
                    // absolute form
                    // expected: GET http://contoso.com/ => Host: contoso.com
                    data.Add("GET http://contoso.com/", host);

                    // authority-form
                    // expected: CONNECT contoso.com => Host: contoso.com
                    data.Add("CONNECT contoso.com", host);
                }

                // port mismatch when target contains port
                data.Add("GET https://contoso.com:443/", "contoso.com:5000");
                data.Add("CONNECT contoso.com:443", "contoso.com:5000");

                return data;
            }
        }
    }
}
