/*
 * Copyright (C) 1996-2026 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

#include "squid.h"

#include <cppunit/TestAssert.h>

#define private public
#define protected public

#include "compat/cppunit.h"
#include "debug/Stream.h"
#include "http/one/RequestParser.h"
#include "http/RequestMethod.h"
#include "MemBuf.h"
#include "SquidConfig.h"
#include "unitTestMain.h"

class TestHttp1Parser : public CPPUNIT_NS::TestFixture
{
    CPPUNIT_TEST_SUITE(TestHttp1Parser);
    // object basics are working, just in case.
    CPPUNIT_TEST(testParserConstruct);
    CPPUNIT_TEST(testDripFeed);
    CPPUNIT_TEST(testParseRequestLineMethods);
    CPPUNIT_TEST(testParseRequestLineProtocols);
    CPPUNIT_TEST(testParseRequestLineTerminators);
    CPPUNIT_TEST(testParseRequestLineStrange);
    CPPUNIT_TEST(testParseRequestLineInvalid);
    CPPUNIT_TEST_SUITE_END();

protected:
    void globalSetup();  // MemPools init etc.

    void testParserConstruct();  // whether the constructor works

    // request-line unit tests
    void testParseRequestLineTerminators();  // terminator detection correct
    void testParseRequestLineMethods();      // methoid detection correct
    void testParseRequestLineProtocols();    // protocol tokens handled correctly
    void testParseRequestLineStrange();      // strange but valid lines accepted
    void testParseRequestLineInvalid();      // rejection of invalid lines happens

    void testDripFeed();  // test incremental parse works
};

CPPUNIT_TEST_SUITE_REGISTRATION( TestHttp1Parser );

void
TestHttp1Parser::globalSetup()
{
    static bool setup_done = false;
    if (setup_done)
        return;

    Mem::Init();
    setup_done = true;

    // default to strict parser. set for loose parsing specifically where behaviour differs.
    Config.onoff.relaxed_header_parser = 0;

    Config.maxRequestHeaderSize = 1024; // XXX: unit test the RequestParser handling of this limit
}

struct resultSet {
    bool parsed;
    bool needsMore;
    Http1::ParseState parserState;
    Http::StatusCode status;
    SBuf::size_type suffixSz;
    HttpRequestMethod method;
    const char *uri;
    AnyP::ProtocolVersion version;
};

// define SQUID_DEBUG_TESTS to see exactly which test sub-cases fail and where
#ifdef SQUID_DEBUG_TESTS
// not optimized for runtime use
static void
Replace(SBuf &where, const SBuf &what, const SBuf &with)
{
    // prevent infinite loops
    if (!what.length() || with.find(what) != SBuf::npos)
        return;

    SBuf::size_type pos = 0;
    while ((pos = where.find(what, pos)) != SBuf::npos) {
        SBuf buf = where.substr(0, pos);
        buf.append(with);
        buf.append(where.substr(pos+what.length()));
        where = buf;
        pos += with.length();
    }
}

static SBuf Pretty(SBuf raw)
{
    Replace(raw, SBuf("\r"), SBuf("\\r"));
    Replace(raw, SBuf("\n"), SBuf("\\n"));
    return raw;
}
#endif

static void
testResults(int line, const SBuf &input, Http1::RequestParser &output, struct resultSet &expect)
{
#ifdef SQUID_DEBUG_TESTS
    std::cerr << "TEST @" << line << ", in=" << Pretty(input) << "\n";
#else
    (void)line;
#endif

    const bool parsed = output.parse(input);

#ifdef SQUID_DEBUG_TESTS
    if (expect.parsed != parsed)
        std::cerr << "\tparse-FAILED: " << expect.parsed << "!=" << parsed << "\n";
    else if (parsed && expect.method != output.method_)
        std::cerr << "\tmethod-FAILED: " << expect.method << "!=" << output.method_ << "\n";
    if (expect.status != output.parseStatusCode)
        std::cerr << "\tscode-FAILED: " << expect.status << "!=" << output.parseStatusCode << "\n";
    if (expect.suffixSz != output.buf_.length())
        std::cerr << "\tsuffixSz-FAILED: " << expect.suffixSz << "!=" << output.buf_.length() << "\n";
#endif

    // runs the parse
    CPPUNIT_ASSERT_EQUAL(expect.parsed, parsed);

    // if parsing was successful, check easily visible field outputs
    if (parsed) {
        CPPUNIT_ASSERT_EQUAL(expect.method, output.method_);
        if (expect.uri != nullptr)
            CPPUNIT_ASSERT_EQUAL(0, output.uri_.cmp(expect.uri));
        CPPUNIT_ASSERT_EQUAL(expect.version, output.msgProtocol_);
    }

    CPPUNIT_ASSERT_EQUAL(expect.status, output.parseStatusCode);

    // check more obscure states
    CPPUNIT_ASSERT_EQUAL(expect.needsMore, output.needsMoreData());
    if (output.needsMoreData())
        CPPUNIT_ASSERT_EQUAL(expect.parserState, output.parsingStage_);
    CPPUNIT_ASSERT_EQUAL(expect.suffixSz, output.buf_.length());
}

void
TestHttp1Parser::testParserConstruct()
{
    // whether the constructor works
    {
        Http1::RequestParser output;
        CPPUNIT_ASSERT_EQUAL(true, output.needsMoreData());
        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NONE, output.parsingStage_);
        CPPUNIT_ASSERT_EQUAL(Http::scNone, output.parseStatusCode); // XXX: clear() not being called.
        CPPUNIT_ASSERT(output.buf_.isEmpty());
        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), output.method_);
        CPPUNIT_ASSERT(output.uri_.isEmpty());
        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output.msgProtocol_);
    }

    // whether new() works
    {
        Http1::RequestParser *output = new Http1::RequestParser;
        CPPUNIT_ASSERT_EQUAL(true, output->needsMoreData());
        CPPUNIT_ASSERT_EQUAL(Http1::HTTP_PARSE_NONE, output->parsingStage_);
        CPPUNIT_ASSERT_EQUAL(Http::scNone, output->parseStatusCode);
        CPPUNIT_ASSERT(output->buf_.isEmpty());
        CPPUNIT_ASSERT_EQUAL(HttpRequestMethod(Http::METHOD_NONE), output->method_);
        CPPUNIT_ASSERT(output->uri_.isEmpty());
        CPPUNIT_ASSERT_EQUAL(AnyP::ProtocolVersion(), output->msgProtocol_);
        delete output;
    }
}

void
TestHttp1Parser::testParseRequestLineProtocols()
{
    // ensure MemPools etc exist
    globalSetup();

    SBuf input;
    Http1::RequestParser output;

    // TEST: Do we comply with RFC 1945 section 5.1 ?
    // TEST: Do we comply with RFC 7230 sections 2.6, 3.1.1 and 3.5 ?

    // RFC 1945 : HTTP/0.9 simple-request
    {
        input.append("GET /\r\n", 7);
        struct resultSet expect = {
            .parsed = true,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,0,9)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // RFC 1945 : invalid HTTP/0.9 simple-request (only GET is valid)
    {
        input.append("POST /\r\n", 8);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(Http::METHOD_POST),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // RFC 1945 and 7230 : HTTP/1.0 request
    {
        input.append("GET / HTTP/1.0\r\n", 16);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,0)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // RFC 7230 : HTTP/1.1 request
    {
        input.append("GET / HTTP/1.1\r\n", 16);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // RFC 7230 : future 1.x version full-request
    {
        input.append("GET / HTTP/1.2\r\n", 16);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,2)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // RFC 7230 : future versions do not use 1.x message syntax.
    // However, it is still valid syntax for the single-digit forms
    // to appear. The parser we are testing should accept them.
    {
        input.append("GET / HTTP/2.0\r\n", 16);
        struct resultSet expectA = {
            .parsed = true,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,2,0)
        };
        output.clear();
        testResults(__LINE__, input, output, expectA);
        input.clear();

        input.append("GET / HTTP/9.9\r\n", 16);
        struct resultSet expectB = {
            .parsed = true,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,9,9)
        };
        output.clear();
        testResults(__LINE__, input, output, expectB);
        input.clear();
    }

    // RFC 7230 : future versions >= 10.0 are invalid syntax
    {
        input.append("GET / HTTP/10.12\r\n", 18);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // unknown non-HTTP protocol names
    {
        input.append("GET / FOO/1.0\r\n", 15);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // no version digits
    {
        input.append("GET / HTTP/\r\n", 13);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // no major version
    {
        input.append("GET / HTTP/.1\r\n", 15);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // no version dot
    {
        input.append("GET / HTTP/11\r\n", 15);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // negative major version (bug 3062)
    {
        input.append("GET / HTTP/-999999.1\r\n", 22);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // no minor version
    {
        input.append("GET / HTTP/1.\r\n", 15);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // negative major version (bug 3062 corollary)
    {
        input.append("GET / HTTP/1.-999999\r\n", 22);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }
}

void
TestHttp1Parser::testParseRequestLineStrange()
{
    // ensure MemPools etc exist
    globalSetup();

    SBuf input;
    Http1::RequestParser output;

    // space padded URL
    {
        input.append("GET  /     HTTP/1.1\r\n", 21);
        // when being tolerant extra (sequential) SP delimiters are acceptable
        Config.onoff.relaxed_header_parser = 1;
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);

        Config.onoff.relaxed_header_parser = 0;
        struct resultSet expectStrict = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expectStrict);
        input.clear();
    }

    // whitespace inside URI. (nasty but happens)
    {
        input.append("GET /fo o/ HTTP/1.1\r\n", 21);
        Config.onoff.relaxed_header_parser = 1;
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/fo o/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);

        Config.onoff.relaxed_header_parser = 0;
        struct resultSet expectStrict = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expectStrict);
        input.clear();
    }

    // additional data in buffer
    {
        input.append("GET / HTTP/1.1\r\nboo!", 20);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 4, // strlen("boo!")
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
        Config.onoff.relaxed_header_parser = 0;
    }
}

void
TestHttp1Parser::testParseRequestLineTerminators()
{
    // ensure MemPools etc exist
    globalSetup();

    SBuf input;
    Http1::RequestParser output;

    // alternative EOL sequence: NL-only
    // RFC 7230 tolerance permits omitted CR
    {
        input.append("GET / HTTP/1.1\n", 15);
        Config.onoff.relaxed_header_parser = 1;
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);

        Config.onoff.relaxed_header_parser = 0;
        struct resultSet expectStrict = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expectStrict);
        input.clear();
    }

    // alternative EOL sequence: double-NL-only
    // RFC 7230 tolerance permits omitted CR
    // NP: represents a request with no mime headers
    {
        input.append("GET / HTTP/1.1\n\n", 16);
        Config.onoff.relaxed_header_parser = 1;
        struct resultSet expect = {
            .parsed = true,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);

        Config.onoff.relaxed_header_parser = 0;
        struct resultSet expectStrict = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expectStrict);
        input.clear();
    }

    // space padded version
    {
        // RFC 7230 specifies version is followed by CRLF. No intermediary bytes.
        input.append("GET / HTTP/1.1 \r\n", 17);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }
}

void
TestHttp1Parser::testParseRequestLineMethods()
{
    // ensure MemPools etc exist
    globalSetup();

    SBuf input;
    Http1::RequestParser output;

    // RFC 7230 : dot method
    {
        input.append(". / HTTP/1.1\r\n", 14);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(SBuf(".")),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // RFC 7230 : special TCHAR method chars
    {
        input.append("!#$%&'*+-.^_`|~ / HTTP/1.1\r\n", 28);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(SBuf("!#$%&'*+-.^_`|~")),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // OPTIONS with * URL
    {
        input.append("OPTIONS * HTTP/1.1\r\n", 20);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_OPTIONS),
            .uri = "*",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // unknown method
    {
        input.append("HELLOWORLD / HTTP/1.1\r\n", 23);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(SBuf("HELLOWORLD")),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // method-only
    {
        input.append("A\n", 2);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    {
        input.append("GET\n", 4);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // space padded method (SP is reserved so invalid as a method byte)
    {
        input.append(" GET / HTTP/1.1\r\n", 17);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // RFC 7230 defined tolerance: ignore empty line(s) prefix on messages
    {
        input.append("\r\n\r\n\nGET / HTTP/1.1\r\n", 21);
        Config.onoff.relaxed_header_parser = 1;
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);

        Config.onoff.relaxed_header_parser = 0;
        struct resultSet expectStrict = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expectStrict);
        input.clear();
    }

    // forbidden character in method
    {
        input.append("\tGET / HTTP/1.1\r\n", 17);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // CR in method delimiters
    {
        // RFC 7230 section 3.5 permits CR in whitespace but only for tolerant parsers
        input.append("GET\r / HTTP/1.1\r\n", 17);
        Config.onoff.relaxed_header_parser = 1;
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);

        Config.onoff.relaxed_header_parser = 0;
        struct resultSet expectStrict = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expectStrict);
        input.clear();
    }

    // tolerant parser delimiters
    {
        // RFC 7230 section 3.5 permits certain binary characters as whitespace delimiters
        input.append("GET\r\t\x0B\x0C / HTTP/1.1\r\n", 20);
        Config.onoff.relaxed_header_parser = 1;
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_MIME,
            .status = Http::scOkay,
            .suffixSz = 0,
            .method = HttpRequestMethod(Http::METHOD_GET),
            .uri = "/",
            .version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1)
        };
        output.clear();
        testResults(__LINE__, input, output, expect);

        Config.onoff.relaxed_header_parser = 0;
        struct resultSet expectStrict = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expectStrict);
        input.clear();
    }
}

void
TestHttp1Parser::testParseRequestLineInvalid()
{
    // ensure MemPools etc exist
    globalSetup();

    SBuf input;
    Http1::RequestParser output;

    // no method (or method delimiter)
    {
        // HTTP/0.9 requires method to be "GET"
        input.append("/ HTTP/1.0\n", 11);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // no method (with method delimiter)
    {
        input.append(" / HTTP/1.0\n", 12);
        struct resultSet expectStrict = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expectStrict);
        input.clear();
    }

    // binary code after method (invalid)
    {
        input.append("GET\x16 / HTTP/1.1\r\n", 17);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // binary code NUL! after method (always invalid)
    {
        input.append("GET\0 / HTTP/1.1\r\n", 17);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // Either an RFC 1945 HTTP/0.9 simple-request for an "HTTP/1.1" URI or
    // an invalid (no URI) HTTP/1.1 request. We treat this as latter, naturally.
    {
        input.append("GET  HTTP/1.1\r\n", 15);
        Config.onoff.relaxed_header_parser = 1;
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);

        Config.onoff.relaxed_header_parser = 0;
        struct resultSet expectStrict = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expectStrict);
        input.clear();
    }

    // Either an RFC 1945 HTTP/0.9 simple-request for an "HTTP/1.1" URI or
    // an invalid (no URI) HTTP/1.1 request. We treat this as latter, naturally.
    {
        input.append("GET HTTP/1.1\r\n", 14);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // binary line
    {
        input.append("\xB\xC\xE\xF\n", 5);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // mixed whitespace line
    {
        input.append("\t \t \t\n", 6);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }

    // mixed whitespace line with CR
    {
        input.append("\r  \t \n", 6);
        struct resultSet expect = {
            .parsed = false,
            .needsMore = false,
            .parserState = Http1::HTTP_PARSE_DONE,
            .status = Http::scBadRequest,
            .suffixSz = input.length(),
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };
        output.clear();
        testResults(__LINE__, input, output, expect);
        input.clear();
    }
}

void
TestHttp1Parser::testDripFeed()
{
    // Simulate a client drip-feeding Squid a few bytes at a time.
    // extend the size of the buffer from 0 bytes to full request length
    // calling the parser repeatedly as visible data grows.

    SBuf data;
    data.append("\n\n\n\n\n\n\n\n\n\n\n\n", 12);
    SBuf::size_type garbageEnd = data.length();
    data.append("GET ", 4);
    data.append("http://example.com/ ", 20);
    data.append("HTTP/1.1\r\n", 10);
    SBuf::size_type reqLineEnd = data.length() - 1;
    data.append("Host: example.com\r\n\r\n", 21);
    SBuf::size_type mimeEnd = data.length() - 1;
    data.append("...", 3); // trailer to catch mime EOS errors.

    SBuf ioBuf;
    Http1::RequestParser hp;

    // start with strict and move on to relaxed
    Config.onoff.relaxed_header_parser = 2;

    Config.maxRequestHeaderSize = 1024; // large enough to hold the test data.

    do {

        // state of things we expect right now
        struct resultSet expect = {
            .parsed = false,
            .needsMore = true,
            .parserState = Http1::HTTP_PARSE_NONE,
            .status = Http::scNone,
            .suffixSz = 0,
            .method = HttpRequestMethod(),
            .uri = nullptr,
            .version = AnyP::ProtocolVersion()
        };

        ioBuf.clear(); // begins empty for each parser type
        hp.clear();

        --Config.onoff.relaxed_header_parser;

        for (SBuf::size_type pos = 0; pos <= data.length(); ++pos) {

            // simulate reading one more byte
            ioBuf.append(data.substr(pos,1));

            // strict does not permit the garbage prefix
            if (pos < garbageEnd && !Config.onoff.relaxed_header_parser) {
                ioBuf.clear();
                continue;
            }

            // when the garbage is passed we expect to start seeing first-line bytes
            if (pos == garbageEnd)
                expect.parserState = Http1::HTTP_PARSE_FIRST;

            // all points after garbage start to see accumulated bytes looking for end of current section
            if (pos >= garbageEnd)
                expect.suffixSz = ioBuf.length();

            // at end of request line expect to see method, URI, version details
            // and switch to seeking Mime header section
            if (pos == reqLineEnd) {
                expect.parserState = Http1::HTTP_PARSE_MIME;
                expect.suffixSz = 0; // and a checkpoint buffer reset
                expect.status = Http::scOkay;
                expect.method = HttpRequestMethod(Http::METHOD_GET);
                expect.uri = "http://example.com/";
                expect.version = AnyP::ProtocolVersion(AnyP::PROTO_HTTP,1,1);
            }

            // one mime header is done we are expecting a new request
            // parse results say true and initial data is all gone from the buffer
            if (pos == mimeEnd) {
                expect.parsed = true;
                expect.needsMore = false;
                expect.suffixSz = 0; // and a checkpoint buffer reset
            }

            testResults(__LINE__, ioBuf, hp, expect);

            // sync the buffers like Squid does
            ioBuf = hp.remaining();

            // Squid stops using the parser once it has parsed the first message.
            if (!hp.needsMoreData())
                break;
        }

    } while (Config.onoff.relaxed_header_parser);

}

int
main(int argc, char *argv[])
{
    return TestProgram().run(argc, argv);
}

