Export balsa_frame_test.cc and balsa_headers_test.cc publicly. PiperOrigin-RevId: 445194714
diff --git a/quiche/common/balsa/balsa_frame_test.cc b/quiche/common/balsa/balsa_frame_test.cc new file mode 100644 index 0000000..e99d6f6 --- /dev/null +++ b/quiche/common/balsa/balsa_frame_test.cc
@@ -0,0 +1,3703 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/common/balsa/balsa_frame.h" + +#include <stdlib.h> + +#include <cstdint> +#include <limits> +#include <map> +#include <random> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include "absl/flags/flag.h" +#include "absl/strings/escaping.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "quiche/common/balsa/balsa_enums.h" +#include "quiche/common/balsa/balsa_headers.h" +#include "quiche/common/balsa/balsa_visitor_interface.h" +#include "quiche/common/balsa/http_validation_policy.h" +#include "quiche/common/balsa/noop_balsa_visitor.h" +#include "quiche/common/balsa/simple_buffer.h" +#include "quiche/common/platform/api/quiche_expect_bug.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/platform/api/quiche_test.h" + +using testing::_; +using testing::AnyNumber; +using testing::AtLeast; +using testing::InSequence; +using testing::IsEmpty; +using testing::Mock; +using testing::NiceMock; +using testing::Range; +using testing::StrEq; +using testing::StrictMock; + +ABSL_FLAG(std::string, randseed, "", + "This is the seed for Pseudo-random number" + " generator used when generating random messages for unittests"); + +namespace quiche { + +namespace test { + +// This random engine from the standard library supports initialization with a +// seed, which is helpful for reproducing any unit test failures that are due to +// random sequence variation. +using RandomEngine = std::mt19937; + +class BalsaFrameTestPeer { + public: + static int32_t HeaderFramingFound(BalsaFrame* balsa_frame, char c) { + return balsa_frame->HeaderFramingFound(c); + } + + static void FindColonsAndParseIntoKeyValue(BalsaFrame* balsa_frame, + const BalsaFrame::Lines& lines, + bool is_trailer, + BalsaHeaders* headers) { + balsa_frame->FindColonsAndParseIntoKeyValue(lines, is_trailer, headers); + } +}; + +class BalsaHeadersTestPeer { + public: + static void WriteFromFramer(BalsaHeaders* headers, const char* ptr, + size_t size) { + headers->WriteFromFramer(ptr, size); + } +}; + +namespace { + +// This class encapsulates the policy of seed selection. If user supplies a +// valid use via the --randseed flag, GetSeed will only return the user +// supplied seed value. This is useful in reproducing bugs reported by the +// test. If an invalid seed value is supplied (likely due to bad numeric +// format), the test will abort (since this mode tend to be used for debugging, +// it is better to die early so the user knows a bad value is supplied). If no +// seed is supplied, the value supplied by ACMRandom::HostnamePidTimeSeed() is +// used. This class is supposed to be a singleton, but there is no ill-effect if +// multiple instances are created (although that tends not to be what the user +// wants). +class TestSeed { + public: + TestSeed() : test_seed_(0), user_supplied_seed_(false) {} + + void Initialize(const std::string& seed_flag) { + if (!seed_flag.empty()) { + ASSERT_TRUE(absl::SimpleAtoi(seed_flag, &test_seed_)); + user_supplied_seed_ = true; + } + } + + int GetSeed() const { + int seed = + (user_supplied_seed_ ? test_seed_ + : testing::UnitTest::GetInstance()->random_seed()); + QUICHE_LOG(INFO) << "**** The current seed is " << seed << " ****"; + return seed; + } + + private: + int test_seed_; + bool user_supplied_seed_; +}; + +static bool RandomBool(RandomEngine& rng) { return rng() % 2 != 0; } + +std::string EscapeString(absl::string_view message) { + return absl::StrReplaceAll( + message, {{"\n", "\\\\n\n"}, {"\\r", "\\\\r"}, {"\\t", "\\\\t"}}); +} + +char random_lws(RandomEngine& rng) { + if (RandomBool(rng)) { + return '\t'; + } + return ' '; +} + +const char* random_line_term(RandomEngine& rng) { + if (RandomBool(rng)) { + return "\r\n"; + } + return "\n"; +} + +void AppendRandomWhitespace(RandomEngine& rng, std::stringstream* s) { + // Appending a random amount of whitespace to the unparsed value. There is a + // max of 1000 pieces of whitespace that will be attached, however, it is + // extremely unlikely (1 in 2^1000) that we'll hit this limit, as we have a + // 50% probability of exiting the loop at any point in time. + for (int i = 0; i < 1000 && RandomBool(rng); ++i) { + *s << random_lws(rng); + } +} + +// Creates an HTTP message firstline from the given inputs. +// +// tokens - The list of nonwhitespace tokens (which should later be parsed out +// from the firstline). +// whitespace - the whitespace that occurs before, between, and +// after the tokens. Note that the last whitespace +// character should -not- include any '\n'. +// line_ending - one of "\n" or "\r\n" +// +// whitespace[0] occurs before the first token. +// whitespace[1] occurs between the first and second token +// whitespace[2] occurs between the second and third token +// whitespace[3] occurs between the third token and the line_ending. +// +// This code: +// const char tokens[3] = {"GET", "/", "HTTP/1.0"}; +// const char whitespace[4] = { "\n\n", " ", "\t", "\t"}; +// const char line_ending = "\r\n"; +// CreateFirstLine(tokens, whitespace, line_ending) -> +// Would yield the following string: +// string( +// "\n" +// "\n" +// "GET /\tHTTP/1.0\t\r\n" +// ); +// +std::string CreateFirstLine(const char* tokens[3], const char* whitespace[4], + const char* line_ending) { + QUICHE_CHECK(tokens != nullptr); + QUICHE_CHECK(whitespace != nullptr); + QUICHE_CHECK(line_ending != nullptr); + QUICHE_CHECK(std::string(line_ending) == "\n" || + std::string(line_ending) == "\r\n") + << "line_ending: " << EscapeString(line_ending); + SimpleBuffer firstline_buffer; + firstline_buffer.WriteString(whitespace[0]); + for (int i = 0; i < 3; ++i) { + firstline_buffer.WriteString(tokens[i]); + firstline_buffer.WriteString(whitespace[i + 1]); + } + firstline_buffer.WriteString(line_ending); + return std::string(firstline_buffer.GetReadableRegion()); +} + +// Creates a string (ostensibly an entire HTTP message) from the given input +// arguments. +// +// firstline - the first line of the request or response. +// The firstline should already have a line-ending on it. If you use the +// CreateFirstLine function, you'll get a valid firstline string for this +// function. This may include 'extraneous' whitespace before the first +// nonwhitespace character, including '\n's +// headers - a list of the -interpreted- key, value pairs. +// In other words, the value should be what you expect to get out of the +// headers after framing has occurred (and should include no whitespace +// before or after the first and list nonwhitespace characters, +// respectively). While this function will succeed if you don't follow +// these guidelines, the VerifyHeaderLines function will likely not agree +// with that input. +// headers_len - the number of key value pairs +// colon - the string that exists between the key and value pairs. +// It MUST include EXACTLY one colon, and may include any amount of either +// ' ' or '\t'. Note that for certain key strings, this value will be +// modified to exclude any leading whitespace. See the body of the function +// for more details. +// line_ending - one of "\r\n", or "\n\n" +// body - the appropriate body. +// The CreateMessage function does not do any checking that the headers +// agree with the present of any body, so the input must be correct given +// the set of headers. +std::string CreateMessage(const char* firstline, + const std::pair<std::string, std::string>* headers, + size_t headers_len, const char* colon, + const char* line_ending, const char* body) { + SimpleBuffer request_buffer; + request_buffer.WriteString(firstline); + if (headers_len > 0) { + QUICHE_CHECK(headers != nullptr); + QUICHE_CHECK(colon != nullptr); + } + QUICHE_CHECK(line_ending != nullptr); + QUICHE_CHECK(std::string(line_ending) == "\n" || + std::string(line_ending) == "\r\n") + << "line_ending: " << EscapeString(line_ending); + QUICHE_CHECK(body != nullptr); + for (size_t i = 0; i < headers_len; ++i) { + bool only_whitespace_in_key = true; + { + // If the 'key' part includes no non-whitespace characters, then we need + // to be sure that the 'colon' part includes no whitespace before the + // ':'. If it did, then the line would be (correctly!) interpreted as a + // continuation, and the test would not work properly. + const char* tmp_key = headers[i].first.c_str(); + while (*tmp_key != '\0') { + if (*tmp_key > ' ') { + only_whitespace_in_key = false; + break; + } + ++tmp_key; + } + } + const char* tmp_colon = colon; + if (only_whitespace_in_key) { + while (*tmp_colon != ':') { + ++tmp_colon; + } + } + request_buffer.WriteString(headers[i].first); + request_buffer.WriteString(tmp_colon); + request_buffer.WriteString(headers[i].second); + request_buffer.WriteString(line_ending); + } + request_buffer.WriteString(line_ending); + request_buffer.WriteString(body); + return std::string(request_buffer.GetReadableRegion()); +} + +void VerifyRequestFirstLine(const char* tokens[3], + const BalsaHeaders& headers) { + EXPECT_EQ(tokens[0], headers.request_method()); + EXPECT_EQ(tokens[1], headers.request_uri()); + EXPECT_EQ(0u, headers.parsed_response_code()); + EXPECT_EQ(tokens[2], headers.request_version()); +} + +void VerifyResponseFirstLine(const char* tokens[3], + size_t expected_response_code, + const BalsaHeaders& headers) { + EXPECT_EQ(tokens[0], headers.response_version()); + EXPECT_EQ(tokens[1], headers.response_code()); + EXPECT_EQ(expected_response_code, headers.parsed_response_code()); + EXPECT_EQ(tokens[2], headers.response_reason_phrase()); +} + +// This function verifies that the expected_headers key and values +// are exactly equal to that returned by an iterator to a BalsaHeader +// object. +// +// expected_headers - key, value pairs, in the order in which they're +// expected to be returned from the iterator. +// headers_len - as expected, the number of expected key-value pairs. +// headers - the BalsaHeaders from which we'll examine the actual +// headers. +void VerifyHeaderLines( + const std::pair<std::string, std::string>* expected_headers, + size_t headers_len, const BalsaHeaders& headers) { + BalsaHeaders::const_header_lines_iterator it = headers.lines().begin(); + for (size_t i = 0; it != headers.lines().end(); ++it, ++i) { + ASSERT_GT(headers_len, i); + std::string actual_key; + std::string actual_value; + if (!it->first.empty()) { + actual_key = std::string(it->first); + } + if (!it->second.empty()) { + actual_value = std::string(it->second); + } + EXPECT_THAT(actual_key, StrEq(expected_headers[i].first)); + EXPECT_THAT(actual_value, StrEq(expected_headers[i].second)); + } + EXPECT_TRUE(headers.lines().end() == it); +} + +void FirstLineParsedCorrectlyHelper(const char* tokens[3], + size_t expected_response_code, + bool is_request, const char* whitespace) { + BalsaHeaders headers; + BalsaFrame framer; + framer.set_is_request(is_request); + framer.set_balsa_headers(&headers); + const char* tmp_tokens[3] = {tokens[0], tokens[1], tokens[2]}; + const char* tmp_whitespace[4] = {"", whitespace, whitespace, ""}; + for (int j = 2; j >= 0; --j) { + framer.Reset(); + std::string firstline = CreateFirstLine(tmp_tokens, tmp_whitespace, "\n"); + std::string message = + CreateMessage(firstline.c_str(), nullptr, 0, nullptr, "\n", ""); + SCOPED_TRACE(absl::StrFormat("input: \n%s", EscapeString(message))); + EXPECT_GE(message.size(), + framer.ProcessInput(message.data(), message.size())); + // If this is a request then we don't expect a framer error (as we'll be + // getting back warnings that fields are missing). If, however, this is + // a response, and it is missing anything other than the reason phrase, + // the framer will signal an error instead. + if (is_request || j >= 1) { + EXPECT_FALSE(framer.Error()); + if (is_request) { + EXPECT_TRUE(framer.MessageFullyRead()); + } + if (j == 0) { + expected_response_code = 0; + } + if (is_request) { + VerifyRequestFirstLine(tmp_tokens, *framer.headers()); + } else { + VerifyResponseFirstLine(tmp_tokens, expected_response_code, + *framer.headers()); + } + } else { + EXPECT_TRUE(framer.Error()); + } + tmp_tokens[j] = ""; + tmp_whitespace[j] = ""; + } +} + +TEST(HTTPBalsaFrame, ParseStateToString) { + EXPECT_STREQ("ERROR", + BalsaFrameEnums::ParseStateToString(BalsaFrameEnums::ERROR)); + EXPECT_STREQ("READING_HEADER_AND_FIRSTLINE", + BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE)); + EXPECT_STREQ("READING_CHUNK_LENGTH", + BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::READING_CHUNK_LENGTH)); + EXPECT_STREQ("READING_CHUNK_EXTENSION", + BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::READING_CHUNK_EXTENSION)); + EXPECT_STREQ("READING_CHUNK_DATA", BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::READING_CHUNK_DATA)); + EXPECT_STREQ("READING_CHUNK_TERM", BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::READING_CHUNK_TERM)); + EXPECT_STREQ("READING_LAST_CHUNK_TERM", + BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::READING_LAST_CHUNK_TERM)); + EXPECT_STREQ("READING_TRAILER", BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::READING_TRAILER)); + EXPECT_STREQ("READING_UNTIL_CLOSE", + BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::READING_UNTIL_CLOSE)); + EXPECT_STREQ("READING_CONTENT", BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::READING_CONTENT)); + EXPECT_STREQ("MESSAGE_FULLY_READ", BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::MESSAGE_FULLY_READ)); + + EXPECT_STREQ("UNKNOWN_STATE", BalsaFrameEnums::ParseStateToString( + BalsaFrameEnums::NUM_STATES)); + EXPECT_STREQ("UNKNOWN_STATE", + BalsaFrameEnums::ParseStateToString( + static_cast<BalsaFrameEnums::ParseState>(-1))); + + for (int i = 0; i < BalsaFrameEnums::NUM_STATES; ++i) { + EXPECT_STRNE("UNKNOWN_STATE", + BalsaFrameEnums::ParseStateToString( + static_cast<BalsaFrameEnums::ParseState>(i))); + } +} + +TEST(HTTPBalsaFrame, ErrorCodeToString) { + EXPECT_STREQ("NO_STATUS_LINE_IN_RESPONSE", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::NO_STATUS_LINE_IN_RESPONSE)); + EXPECT_STREQ("NO_REQUEST_LINE_IN_REQUEST", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::NO_REQUEST_LINE_IN_REQUEST)); + EXPECT_STREQ("FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION)); + EXPECT_STREQ("FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD)); + EXPECT_STREQ( + "FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE)); + EXPECT_STREQ( + "FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI)); + EXPECT_STREQ( + "FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE)); + EXPECT_STREQ( + "FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION)); + EXPECT_STREQ("FAILED_CONVERTING_STATUS_CODE_TO_INT", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT)); + EXPECT_STREQ("HEADERS_TOO_LONG", BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::HEADERS_TOO_LONG)); + EXPECT_STREQ("UNPARSABLE_CONTENT_LENGTH", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH)); + EXPECT_STREQ("MAYBE_BODY_BUT_NO_CONTENT_LENGTH", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::MAYBE_BODY_BUT_NO_CONTENT_LENGTH)); + EXPECT_STREQ("HEADER_MISSING_COLON", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::HEADER_MISSING_COLON)); + EXPECT_STREQ("INVALID_CHUNK_LENGTH", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::INVALID_CHUNK_LENGTH)); + EXPECT_STREQ("CHUNK_LENGTH_OVERFLOW", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW)); + EXPECT_STREQ("CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO)); + EXPECT_STREQ("CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums:: + CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT)); + EXPECT_STREQ("MULTIPLE_CONTENT_LENGTH_KEYS", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::MULTIPLE_CONTENT_LENGTH_KEYS)); + EXPECT_STREQ("MULTIPLE_TRANSFER_ENCODING_KEYS", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::MULTIPLE_TRANSFER_ENCODING_KEYS)); + EXPECT_STREQ("INVALID_HEADER_FORMAT", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::INVALID_HEADER_FORMAT)); + EXPECT_STREQ("INVALID_TRAILER_FORMAT", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::INVALID_TRAILER_FORMAT)); + EXPECT_STREQ("TRAILER_TOO_LONG", BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::TRAILER_TOO_LONG)); + EXPECT_STREQ("TRAILER_MISSING_COLON", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::TRAILER_MISSING_COLON)); + EXPECT_STREQ("INTERNAL_LOGIC_ERROR", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::INTERNAL_LOGIC_ERROR)); + EXPECT_STREQ("INVALID_HEADER_CHARACTER", + BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::INVALID_HEADER_CHARACTER)); + + EXPECT_STREQ("UNKNOWN_ERROR", BalsaFrameEnums::ErrorCodeToString( + BalsaFrameEnums::NUM_ERROR_CODES)); + EXPECT_STREQ("UNKNOWN_ERROR", + BalsaFrameEnums::ErrorCodeToString( + static_cast<BalsaFrameEnums::ErrorCode>(-1))); + + for (int i = 0; i < BalsaFrameEnums::NUM_ERROR_CODES; ++i) { + EXPECT_STRNE("UNKNOWN_ERROR", + BalsaFrameEnums::ErrorCodeToString( + static_cast<BalsaFrameEnums::ErrorCode>(i))); + } +} + +class FakeHeaders { + public: + struct KeyValuePair { + KeyValuePair(const std::string& key, const std::string& value) + : key(key), value(value) {} + KeyValuePair() {} + + std::string key; + std::string value; + }; + typedef std::vector<KeyValuePair> KeyValuePairs; + KeyValuePairs key_value_pairs_; + + bool operator==(const FakeHeaders& other) const { + if (key_value_pairs_.size() != other.key_value_pairs_.size()) { + return false; + } + for (KeyValuePairs::size_type i = 0; i < key_value_pairs_.size(); ++i) { + if (key_value_pairs_[i].key != other.key_value_pairs_[i].key) { + return false; + } + if (key_value_pairs_[i].value != other.key_value_pairs_[i].value) { + return false; + } + } + return true; + } + + void AddKeyValue(const std::string& key, const std::string& value) { + key_value_pairs_.push_back(KeyValuePair(key, value)); + } +}; + +class BalsaVisitorMock : public BalsaVisitorInterface { + public: + ~BalsaVisitorMock() override = default; + + void ProcessHeaders(const BalsaHeaders& headers) override { + FakeHeaders fake_headers; + GenerateFakeHeaders(headers, &fake_headers); + ProcessHeaders(fake_headers); + } + void ProcessTrailers(const BalsaHeaders& trailer) override { + FakeHeaders fake_headers; + GenerateFakeHeaders(trailer, &fake_headers); + ProcessTrailers(fake_headers); + } + + MOCK_METHOD(void, OnRawBodyInput, (absl::string_view input), (override)); + MOCK_METHOD(void, OnBodyChunkInput, (absl::string_view input), (override)); + MOCK_METHOD(void, OnHeaderInput, (absl::string_view input), (override)); + MOCK_METHOD(void, OnTrailerInput, (absl::string_view input), (override)); + MOCK_METHOD(void, ProcessHeaders, (const FakeHeaders& headers)); + MOCK_METHOD(void, ProcessTrailers, (const FakeHeaders& headers)); + MOCK_METHOD(void, OnRequestFirstLineInput, + (absl::string_view line_input, absl::string_view method_input, + absl::string_view request_uri, absl::string_view version_input), + (override)); + MOCK_METHOD(void, OnResponseFirstLineInput, + (absl::string_view line_input, absl::string_view version_input, + absl::string_view status_input, absl::string_view reason_input), + (override)); + MOCK_METHOD(void, OnChunkLength, (size_t length), (override)); + MOCK_METHOD(void, OnChunkExtensionInput, (absl::string_view input), + (override)); + MOCK_METHOD(void, ContinueHeaderDone, (), (override)); + MOCK_METHOD(void, HeaderDone, (), (override)); + MOCK_METHOD(void, MessageDone, (), (override)); + MOCK_METHOD(void, HandleError, (BalsaFrameEnums::ErrorCode error_code), + (override)); + MOCK_METHOD(void, HandleWarning, (BalsaFrameEnums::ErrorCode error_code), + (override)); + + private: + static void GenerateFakeHeaders(const BalsaHeaders& headers, + FakeHeaders* fake_headers) { + for (const auto& line : headers.lines()) { + fake_headers->AddKeyValue(std::string(line.first), + std::string(line.second)); + } + } +}; + +class HTTPBalsaFrameTest : public QuicheTest { + protected: + void SetUp() override { + balsa_frame_.set_http_validation_policy( + HttpValidationPolicy::CreateDefault()); + balsa_frame_.set_balsa_headers(&headers_); + balsa_frame_.set_balsa_visitor(&visitor_mock_); + balsa_frame_.set_is_request(true); + } + + void VerifyFirstLineParsing(const std::string& firstline, + BalsaFrameEnums::ErrorCode error_code) { + balsa_frame_.ProcessInput(firstline.data(), firstline.size()); + EXPECT_EQ(error_code, balsa_frame_.ErrorCode()); + } + + BalsaHeaders headers_; + BalsaHeaders trailer_; + BalsaFrame balsa_frame_; + NiceMock<BalsaVisitorMock> visitor_mock_; +}; + +// Test correct return value for HeaderFramingFound. +TEST_F(HTTPBalsaFrameTest, TestHeaderFramingFound) { + // Pattern \r\n\r\n should match kValidTerm1. + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, ' ')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r')); + EXPECT_EQ(BalsaFrame::kValidTerm1, + BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n')); + + // Pattern \n\r\n should match kValidTerm1. + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\t')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r')); + EXPECT_EQ(BalsaFrame::kValidTerm1, + BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n')); + + // Pattern \r\n\n should match kValidTerm2. + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, 'a')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n')); + EXPECT_EQ(BalsaFrame::kValidTerm2, + BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n')); + + // Pattern \n\n should match kValidTerm2. + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '1')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n')); + EXPECT_EQ(BalsaFrame::kValidTerm2, + BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n')); + + // Other patterns should not match. + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, ':')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r')); + EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n')); +} + +TEST_F(HTTPBalsaFrameTest, MissingColonInTrailer) { + const absl::string_view trailer = "kv\r\n\r\n"; + + BalsaFrame::Lines lines; + lines.push_back({0, 4}); + lines.push_back({4, trailer.length()}); + BalsaHeadersTestPeer::WriteFromFramer(&trailer_, trailer.data(), + trailer.length()); + BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue( + &balsa_frame_, lines, true /*is_trailer*/, &trailer_); + // Note missing colon is not an error, just a warning. + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::TRAILER_MISSING_COLON, balsa_frame_.ErrorCode()); +} + +// Correctness of FindColonsAndParseIntoKeyValue is already verified for +// headers, so trailer related test is light. +TEST_F(HTTPBalsaFrameTest, FindColonsAndParseIntoKeyValueInTrailer) { + const absl::string_view trailer_line1 = "Fraction: 0.23\r\n"; + const absl::string_view trailer_line2 = "Some:junk \r\n"; + const absl::string_view trailer_line3 = "\r\n"; + const std::string trailer = + absl::StrCat(trailer_line1, trailer_line2, trailer_line3); + + BalsaFrame::Lines lines; + lines.push_back({0, trailer_line1.length()}); + lines.push_back({trailer_line1.length(), + trailer_line1.length() + trailer_line2.length()}); + lines.push_back( + {trailer_line1.length() + trailer_line2.length(), trailer.length()}); + BalsaHeadersTestPeer::WriteFromFramer(&trailer_, trailer.data(), + trailer.length()); + BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue( + &balsa_frame_, lines, true /*is_trailer*/, &trailer_); + EXPECT_FALSE(balsa_frame_.Error()); + absl::string_view fraction = trailer_.GetHeader("Fraction"); + EXPECT_EQ("0.23", fraction); + absl::string_view some = trailer_.GetHeader("Some"); + EXPECT_EQ("junk", some); +} + +TEST_F(HTTPBalsaFrameTest, InvalidTrailer) { + const absl::string_view trailer_line1 = "Fraction : 0.23\r\n"; + const absl::string_view trailer_line2 = "Some\t :junk \r\n"; + const absl::string_view trailer_line3 = "\r\n"; + const std::string trailer = + absl::StrCat(trailer_line1, trailer_line2, trailer_line3); + + BalsaFrame::Lines lines; + lines.push_back({0, trailer_line1.length()}); + lines.push_back({trailer_line1.length(), + trailer_line1.length() + trailer_line2.length()}); + lines.push_back( + {trailer_line1.length() + trailer_line2.length(), trailer.length()}); + BalsaHeadersTestPeer::WriteFromFramer(&trailer_, trailer.data(), + trailer.length()); + BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue( + &balsa_frame_, lines, true /*is_trailer*/, &trailer_); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_TRAILER_NAME_CHARACTER, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, OneCharacterFirstLineParsedAsExpected) { + VerifyFirstLineParsing( + "a\r\n\r\n", BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD); +} + +TEST_F(HTTPBalsaFrameTest, + OneCharacterFirstLineWithWhitespaceParsedAsExpected) { + VerifyFirstLineParsing( + "a \r\n\r\n", BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD); +} + +TEST_F(HTTPBalsaFrameTest, WhitespaceOnlyFirstLineIsNotACompleteHeader) { + VerifyFirstLineParsing(" \n\n", BalsaFrameEnums::NO_REQUEST_LINE_IN_REQUEST); +} + +TEST(HTTPBalsaFrame, RequestFirstLineParsedCorrectly) { + const char* request_tokens[3] = {"GET", "/jjsdjrqk", "HTTP/1.0"}; + FirstLineParsedCorrectlyHelper(request_tokens, 0, true, " "); + FirstLineParsedCorrectlyHelper(request_tokens, 0, true, "\t"); + FirstLineParsedCorrectlyHelper(request_tokens, 0, true, "\t "); + FirstLineParsedCorrectlyHelper(request_tokens, 0, true, " \t"); + FirstLineParsedCorrectlyHelper(request_tokens, 0, true, " \t \t "); +} + +TEST_F(HTTPBalsaFrameTest, NonnumericResponseCode) { + balsa_frame_.set_is_request(false); + + VerifyFirstLineParsing("HTTP/1.1 0x3 Digits only\r\n\r\n", + BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT); + + EXPECT_EQ("HTTP/1.1 0x3 Digits only", headers_.first_line()); +} + +TEST_F(HTTPBalsaFrameTest, NegativeResponseCode) { + balsa_frame_.set_is_request(false); + + VerifyFirstLineParsing("HTTP/1.1 -11 No sign allowed\r\n\r\n", + BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT); + + EXPECT_EQ("HTTP/1.1 -11 No sign allowed", headers_.first_line()); +} + +TEST_F(HTTPBalsaFrameTest, WithoutTrailingWhitespace) { + balsa_frame_.set_is_request(false); + + VerifyFirstLineParsing( + "HTTP/1.1 101\r\n\r\n", + BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE); + + EXPECT_EQ("HTTP/1.1 101", headers_.first_line()); +} + +TEST_F(HTTPBalsaFrameTest, TrailingWhitespace) { + balsa_frame_.set_is_request(false); + + // b/69446061 + std::string firstline = "HTTP/1.1 101 \r\n\r\n"; + balsa_frame_.ProcessInput(firstline.data(), firstline.size()); + + EXPECT_EQ("HTTP/1.1 101 ", headers_.first_line()); +} + +TEST(HTTPBalsaFrame, ResponseFirstLineParsedCorrectly) { + const char* response_tokens[3] = {"HTTP/1.1", "200", "A reason\tphrase"}; + FirstLineParsedCorrectlyHelper(response_tokens, 200, false, " "); + FirstLineParsedCorrectlyHelper(response_tokens, 200, false, "\t"); + FirstLineParsedCorrectlyHelper(response_tokens, 200, false, "\t "); + FirstLineParsedCorrectlyHelper(response_tokens, 200, false, " \t"); + FirstLineParsedCorrectlyHelper(response_tokens, 200, false, " \t \t "); + + response_tokens[1] = "312"; + FirstLineParsedCorrectlyHelper(response_tokens, 312, false, " "); + FirstLineParsedCorrectlyHelper(response_tokens, 312, false, "\t"); + FirstLineParsedCorrectlyHelper(response_tokens, 312, false, "\t "); + FirstLineParsedCorrectlyHelper(response_tokens, 312, false, " \t"); + FirstLineParsedCorrectlyHelper(response_tokens, 312, false, " \t \t "); + + // Who knows what the future may hold w.r.t. response codes?! + response_tokens[1] = "4242"; + FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, " "); + FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, "\t"); + FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, "\t "); + FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, " \t"); + FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, " \t \t "); +} + +void HeaderLineTestHelper(const char* firstline, bool is_request, + const std::pair<std::string, std::string>* headers, + size_t headers_len, const char* colon, + const char* line_ending) { + BalsaHeaders balsa_headers; + BalsaFrame framer; + framer.set_is_request(is_request); + framer.set_balsa_headers(&balsa_headers); + std::string message = + CreateMessage(firstline, headers, headers_len, colon, line_ending, ""); + SCOPED_TRACE(EscapeString(message)); + size_t bytes_consumed = framer.ProcessInput(message.data(), message.size()); + EXPECT_EQ(message.size(), bytes_consumed); + VerifyHeaderLines(headers, headers_len, *framer.headers()); +} + +TEST(HTTPBalsaFrame, RequestLinesParsedProperly) { + SCOPED_TRACE("Testing that lines are properly parsed."); + const char firstline[] = "GET / HTTP/1.1\r\n"; + const std::pair<std::string, std::string> headers[] = { + std::pair<std::string, std::string>("foo", "bar"), + std::pair<std::string, std::string>("duck", "water"), + std::pair<std::string, std::string>("goose", "neck"), + std::pair<std::string, std::string>("key_is_fine", + "value:includes:colons"), + std::pair<std::string, std::string>("trucks", + "along\rvalue\rincluding\rslash\rrs"), + std::pair<std::string, std::string>("monster", "truck"), + std::pair<std::string, std::string>("another_key", ":colons in value"), + std::pair<std::string, std::string>("another_key", "colons in value:"), + std::pair<std::string, std::string>("another_key", + "value includes\r\n continuation"), + std::pair<std::string, std::string>("key_without_continuations", + "multiple\n in\r\n the\n value"), + std::pair<std::string, std::string>("key_without_value", + ""), // empty value + std::pair<std::string, std::string>("", + "value without key"), // empty key + std::pair<std::string, std::string>("", ""), // both key and value empty + std::pair<std::string, std::string>("normal_key", "normal_value"), + }; + const size_t headers_len = ABSL_ARRAYSIZE(headers); + HeaderLineTestHelper(firstline, true, headers, headers_len, ": ", "\n"); + HeaderLineTestHelper(firstline, true, headers, headers_len, ": ", "\r\n"); + HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t", "\n"); + HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t", "\r\n"); + HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t ", "\n"); + HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t ", "\r\n"); + HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t\t", "\n"); + HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t\t", "\r\n"); + HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t \t", "\n"); + HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t \t", "\r\n"); +} + +TEST(HTTPBalsaFrame, ResponseLinesParsedProperly) { + SCOPED_TRACE("ResponseLineParsedProperly"); + const char firstline[] = "HTTP/1.0 200 A reason\tphrase\r\n"; + const std::pair<std::string, std::string> headers[] = { + std::pair<std::string, std::string>("foo", "bar"), + std::pair<std::string, std::string>("duck", "water"), + std::pair<std::string, std::string>("goose", "neck"), + std::pair<std::string, std::string>("key_is_fine", + "value:includes:colons"), + std::pair<std::string, std::string>("trucks", + "along\rvalue\rincluding\rslash\rrs"), + std::pair<std::string, std::string>("monster", "truck"), + std::pair<std::string, std::string>("another_key", ":colons in value"), + std::pair<std::string, std::string>("another_key", "colons in value:"), + std::pair<std::string, std::string>("another_key", + "value includes\r\n continuation"), + std::pair<std::string, std::string>("key_includes_no_continuations", + "multiple\n in\r\n the\n value"), + std::pair<std::string, std::string>("key_without_value", + ""), // empty value + std::pair<std::string, std::string>("", + "value without key"), // empty key + std::pair<std::string, std::string>("", ""), // both key and value empty + std::pair<std::string, std::string>("normal_key", "normal_value"), + }; + const size_t headers_len = ABSL_ARRAYSIZE(headers); + HeaderLineTestHelper(firstline, false, headers, headers_len, ": ", "\n"); + HeaderLineTestHelper(firstline, false, headers, headers_len, ": ", "\r\n"); + HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t", "\n"); + HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t", "\r\n"); + HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t ", "\n"); + HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t ", "\r\n"); + HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t\t", "\n"); + HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t\t", "\r\n"); + HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t \t", "\n"); + HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t \t", + "\r\n"); +} + +void WhitespaceHeaderTestHelper( + const std::string& message, bool is_request, + BalsaFrameEnums::ErrorCode expected_error_code) { + BalsaHeaders balsa_headers; + BalsaFrame framer; + framer.set_is_request(is_request); + framer.set_balsa_headers(&balsa_headers); + SCOPED_TRACE(EscapeString(message)); + size_t bytes_consumed = framer.ProcessInput(message.data(), message.size()); + EXPECT_EQ(message.size(), bytes_consumed); + if (expected_error_code == BalsaFrameEnums::NO_ERROR) { + EXPECT_EQ(false, framer.Error()); + } else { + EXPECT_EQ(true, framer.Error()); + } + EXPECT_EQ(expected_error_code, framer.ErrorCode()); +} + +TEST(HTTPBalsaFrame, WhitespaceInRequestsProcessedProperly) { + SCOPED_TRACE( + "Test that a request header with a line with spaces and no " + "data generates an error."); + WhitespaceHeaderTestHelper( + "GET / HTTP/1.1\r\n" + " \r\n" + "\r\n", + true, BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER); + WhitespaceHeaderTestHelper( + "GET / HTTP/1.1\r\n" + " \r\n" + "test: test\r\n" + "\r\n", + true, BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER); + + SCOPED_TRACE("Test proper handling for line continuation in requests."); + WhitespaceHeaderTestHelper( + "GET / HTTP/1.1\r\n" + "test: test\r\n" + " continued\r\n" + "\r\n", + true, BalsaFrameEnums::NO_ERROR); + WhitespaceHeaderTestHelper( + "GET / HTTP/1.1\r\n" + "test: test\r\n" + " \r\n" + "\r\n", + true, BalsaFrameEnums::NO_ERROR); +} + +TEST(HTTPBalsaFrame, WhitespaceInResponsesProcessedProperly) { + SCOPED_TRACE( + "Test that a response header with a line with spaces and no " + "data generates an error."); + WhitespaceHeaderTestHelper( + "HTTP/1.0 200 Reason\r\n" + " \r\nContent-Length: 0\r\n" + "\r\n", + false, BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER); + + SCOPED_TRACE("Test proper handling for line continuation in responses."); + WhitespaceHeaderTestHelper( + "HTTP/1.0 200 Reason\r\n" + "test: test\r\n" + " continued\r\n" + "Content-Length: 0\r\n" + "\r\n", + false, BalsaFrameEnums::NO_ERROR); + WhitespaceHeaderTestHelper( + "HTTP/1.0 200 Reason\r\n" + "test: test\r\n" + " \r\n" + "Content-Length: 0\r\n" + "\r\n", + false, BalsaFrameEnums::NO_ERROR); +} + +TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForTrivialRequest) { + std::string message = "GET /foobar HTTP/1.0\r\n\n"; + + FakeHeaders fake_headers; + + { + InSequence s; + + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("GET /foobar HTTP/1.0", "GET", + "/foobar", "HTTP/1.0")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message)); + + ASSERT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); +} + +TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForRequestWithBlankLines) { + std::string message = "\n\n\r\n\nGET /foobar HTTP/1.0\r\n\n"; + + FakeHeaders fake_headers; + + { + InSequence s1; + // Yes, that is correct-- the framer 'eats' the blank-lines at the beginning + // and never notifies the visitor. + + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("GET /foobar HTTP/1.0", "GET", + "/foobar", "HTTP/1.0")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput("GET /foobar HTTP/1.0\r\n\n")); + + ASSERT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForRequestWitSplithBlankLines) { + std::string blanks = + "\n" + "\n" + "\r\n" + "\n"; + std::string header_input = "GET /foobar HTTP/1.0\r\n\n"; + + FakeHeaders fake_headers; + + { + InSequence s1; + // Yes, that is correct-- the framer 'eats' the blank-lines at the beginning + // and never notifies the visitor. + + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("GET /foobar HTTP/1.0", "GET", + "/foobar", "HTTP/1.0")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput("GET /foobar HTTP/1.0\r\n\n")); + + ASSERT_EQ(blanks.size(), + balsa_frame_.ProcessInput(blanks.data(), blanks.size())); + ASSERT_EQ(header_input.size(), balsa_frame_.ProcessInput( + header_input.data(), header_input.size())); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForRequestWithZeroContentLength) { + std::string message = + "PUT /search?q=fo HTTP/1.1\n" + "content-length: 0 \n" + "\n"; + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("content-length", "0"); + + { + InSequence s1; + + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("PUT /search?q=fo HTTP/1.1", "PUT", + "/search?q=fo", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message)); + + ASSERT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForRequestWithMissingContentLength) { + std::string message = + "PUT /search?q=fo HTTP/1.1\n" + "\n"; + + auto error_code = + BalsaFrameEnums::BalsaFrameEnums::REQUIRED_BODY_BUT_NO_CONTENT_LENGTH; + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + + balsa_frame_.ProcessInput(message.data(), message.size()); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(error_code, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForPermittedMissingContentLength) { + std::string message = + "PUT /search?q=fo HTTP/1.1\n" + "\n"; + + FakeHeaders fake_headers; + + { + InSequence s1; + + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("PUT /search?q=fo HTTP/1.1", "PUT", + "/search?q=fo", "HTTP/1.1")); + } + ASSERT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); +} + +TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenNothingInConnectionLine) { + // This is similar to the test above, but we use different whitespace + // throughout. + std::string message = + "PUT \t /search?q=fo \t HTTP/1.1 \t \r\n" + "Connection:\r\n" + "content-length: 0\r\n" + "\r\n"; + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("Connection", ""); + fake_headers.AddKeyValue("content-length", "0"); + + { + InSequence s1; + + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1", + "PUT", "/search?q=fo", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message)); + + ASSERT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); +} + +TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenOnlyCommentsInConnectionLine) { + // This is similar to the test above, but we use different whitespace + // throughout. + std::string message = + "PUT \t /search?q=fo \t HTTP/1.1 \t \r\n" + "Connection: ,,,,,,,,\r\n" + "content-length: 0\r\n" + "\r\n"; + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("Connection", ",,,,,,,,"); + fake_headers.AddKeyValue("content-length", "0"); + + { + InSequence s1; + + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1", + "PUT", "/search?q=fo", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message)); + + ASSERT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForRequestWithZeroContentLengthMk2) { + // This is similar to the test above, but we use different whitespace + // throughout. + std::string message = + "PUT \t /search?q=fo \t HTTP/1.1 \t \r\n" + "Connection: \t close \t\r\n" + "content-length: \t\t 0 \t\t \r\n" + "\r\n"; + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("Connection", "close"); + fake_headers.AddKeyValue("content-length", "0"); + + { + InSequence s1; + + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1", + "PUT", "/search?q=fo", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message)); + + ASSERT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); +} + +TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenNoVisitorIsAssigned) { + std::string headers = + "GET / HTTP/1.1\r\n" + "Connection: close\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + std::string trailer = + "crass: monkeys\r\n" + "funky: monkeys\r\n" + "\r\n"; + + balsa_frame_.set_balsa_visitor(nullptr); + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenNoVisitorIsAssignedInResponse) { + std::string headers = + "HTTP/1.1 502 Bad Gateway\r\n" + "Connection: close\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + std::string trailer = + "crass: monkeys\r\n" + "funky: monkeys\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_trailer(&trailer_); + balsa_frame_.set_balsa_visitor(nullptr); + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + const absl::string_view crass = trailer_.GetHeader("crass"); + EXPECT_EQ("monkeys", crass); + const absl::string_view funky = trailer_.GetHeader("funky"); + EXPECT_EQ("monkeys", funky); +} + +TEST_F(HTTPBalsaFrameTest, TransferEncodingIdentityIsIgnored) { + std::string headers = + "GET / HTTP/1.1\r\n" + "Connection: close\r\n" + "transfer-encoding: identity\r\n" + "content-length: 10\r\n" + "\r\n"; + + std::string body = "1234567890"; + std::string message = (headers + body); + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + NothingBadHappensWhenAVisitorIsChangedToNULLInMidParsing) { + std::string headers = + "GET / HTTP/1.1\r\n" + "Connection: close\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + std::string trailer = + "crass: monkeys\r\n" + "funky: monkeys\r\n" + "\n"; + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + balsa_frame_.set_balsa_visitor(nullptr); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + ASSERT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + NothingBadHappensWhenAVisitorIsChangedToNULLInMidParsingInTrailer) { + std::string headers = + "HTTP/1.1 503 Server Not Available\r\n" + "Connection: close\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + std::string trailer = + "crass: monkeys\r\n" + "funky: monkeys\r\n" + "\n"; + + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_trailer(&trailer_); + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + balsa_frame_.set_balsa_visitor(nullptr); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + ASSERT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + const absl::string_view crass = trailer_.GetHeader("crass"); + EXPECT_EQ("monkeys", crass); + const absl::string_view funky = trailer_.GetHeader("funky"); + EXPECT_EQ("monkeys", funky); +} + +TEST_F(HTTPBalsaFrameTest, + NothingBadHappensWhenNoVisitorAssignedAndChunkingErrorOccurs) { + std::string headers = + "GET / HTTP/1.1\r\n" + "Connection: close\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\r\n" // should overflow + "0\r\n"; + std::string trailer = + "crass: monkeys\r\n" + "funky: monkeys\r\n" + "\n"; + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + balsa_frame_.set_balsa_visitor(nullptr); + EXPECT_GE(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, FramerRecognizesSemicolonAsChunkSizeDelimiter) { + std::string headers = + "GET / HTTP/1.1\r\n" + "Connection: close\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "8; foo=bar\r\n" + "deadbeef\r\n" + "0\r\n" + "\r\n"; + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + + balsa_frame_.set_balsa_visitor(&visitor_mock_); + EXPECT_CALL(visitor_mock_, OnChunkLength(8)); + EXPECT_CALL(visitor_mock_, OnChunkLength(0)); + EXPECT_CALL(visitor_mock_, OnChunkExtensionInput("; foo=bar")); + EXPECT_CALL(visitor_mock_, OnChunkExtensionInput("")); + + EXPECT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); +} + +TEST_F(HTTPBalsaFrameTest, NonAsciiCharacterInChunkLength) { + std::string headers = + "GET / HTTP/1.1\r\n" + "Connection: close\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "555\xAB\r\n" // Character overflowing 7 bits, see b/20238315 + "0\r\n"; + std::string trailer = + "crass: monkeys\r\n" + "funky: monkeys\r\n" + "\n"; + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("Connection", "close"); + fake_headers.AddKeyValue("transfer-encoding", "chunked"); + + auto error_code = BalsaFrameEnums::INVALID_CHUNK_LENGTH; + { + InSequence s1; + EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET", + "/", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnRawBodyInput("555\xAB")); + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + } + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + EXPECT_EQ(strlen("555\xAB"), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_CHUNK_LENGTH, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, VisitorCalledAsExpectedWhenChunkingOverflowOccurs) { + std::string headers = + "GET / HTTP/1.1\r\n" + "Connection: close\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\r\n" // should overflow + "0\r\n"; + std::string trailer = + "crass: monkeys\r\n" + "funky: monkeys\r\n" + "\n"; + + const char* chunk_read_before_overflow = "FFFFFFFFFFFFFFFFF"; + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("Connection", "close"); + fake_headers.AddKeyValue("transfer-encoding", "chunked"); + + auto error_code = BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW; + { + InSequence s1; + EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET", + "/", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnRawBodyInput(chunk_read_before_overflow)); + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + } + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + EXPECT_EQ(strlen(chunk_read_before_overflow), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorCalledAsExpectedWhenInvalidChunkLengthOccurs) { + std::string headers = + "GET / HTTP/1.1\r\n" + "Connection: close\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "12z123 \r\n" // invalid chunk length + "0\r\n"; + std::string trailer = + "crass: monkeys\r\n" + "funky: monkeys\r\n" + "\n"; + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("Connection", "close"); + fake_headers.AddKeyValue("transfer-encoding", "chunked"); + + auto error_code = BalsaFrameEnums::INVALID_CHUNK_LENGTH; + { + InSequence s1; + EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET", + "/", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnRawBodyInput("12z")); + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + } + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + EXPECT_EQ(3u, balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_CHUNK_LENGTH, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForRequestWithContentLength) { + std::string message_headers = + "PUT \t /search?q=fo \t HTTP/1.1 \t \r\n" + "content-length: \t\t 20 \t\t \r\n" + "\r\n"; + std::string message_body = "12345678901234567890"; + std::string message = + std::string(message_headers) + std::string(message_body); + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("content-length", "20"); + + { + InSequence s1; + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1", + "PUT", "/search?q=fo", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnRawBodyInput(message_body)); + EXPECT_CALL(visitor_mock_, OnBodyChunkInput(message_body)); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers)); + + ASSERT_EQ(message_headers.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + ASSERT_EQ(message_body.size(), + balsa_frame_.ProcessInput(message.data() + message_headers.size(), + message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForRequestWithOneCharContentLength) { + std::string message_headers = + "PUT \t /search?q=fo \t HTTP/1.1 \t \r\n" + "content-length: \t\t 2 \t\t \r\n" + "\r\n"; + std::string message_body = "12"; + std::string message = + std::string(message_headers) + std::string(message_body); + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("content-length", "2"); + + { + InSequence s1; + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1", + "PUT", "/search?q=fo", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnRawBodyInput(message_body)); + EXPECT_CALL(visitor_mock_, OnBodyChunkInput(message_body)); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers)); + + ASSERT_EQ(message_headers.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + ASSERT_EQ(message_body.size(), + balsa_frame_.ProcessInput(message.data() + message_headers.size(), + message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForRequestWithTransferEncoding) { + std::string message_headers = + "DELETE /search?q=fo \t HTTP/1.1 \t \r\n" + "trAnsfer-eNcoding: chunked\r\n" + "\r\n"; + std::string message_body = + "A chunkjed extension \r\n" + "01234567890 more crud including numbers 123123\r\n" + "3f\n" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + "0 last one\r\n" + "\r\n"; + std::string message_body_data = + "0123456789" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + + std::string message = + std::string(message_headers) + std::string(message_body); + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked"); + + { + InSequence s1; + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("DELETE /search?q=fo \t HTTP/1.1", + "DELETE", "/search?q=fo", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnChunkLength(10)); + EXPECT_CALL(visitor_mock_, + OnChunkExtensionInput(" chunkjed extension ")); + EXPECT_CALL(visitor_mock_, OnChunkLength(63)); + EXPECT_CALL(visitor_mock_, OnChunkExtensionInput("")); + EXPECT_CALL(visitor_mock_, OnChunkLength(0)); + EXPECT_CALL(visitor_mock_, OnChunkExtensionInput(" last one")); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers)); + std::string body_input; + EXPECT_CALL(visitor_mock_, OnRawBodyInput(_)) + .WillRepeatedly([&body_input](absl::string_view input) { + absl::StrAppend(&body_input, input); + }); + std::string body_data; + EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_)) + .WillRepeatedly([&body_data](absl::string_view input) { + absl::StrAppend(&body_data, input); + }); + EXPECT_CALL(visitor_mock_, OnTrailerInput(_)).Times(0); + + ASSERT_EQ(message_headers.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_EQ(message_body.size(), + balsa_frame_.ProcessInput(message.data() + message_headers.size(), + message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + + EXPECT_EQ(message_body, body_input); + EXPECT_EQ(message_body_data, body_data); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForRequestWithTransferEncodingAndTrailers) { + std::string message_headers = + "DELETE /search?q=fo \t HTTP/1.1 \t \r\n" + "trAnsfer-eNcoding: chunked\r\n" + "another_random_header: \r\n" + " \t \n" + " \t includes a continuation\n" + "\r\n"; + std::string message_body = + "A chunkjed extension \r\n" + "01234567890 more crud including numbers 123123\r\n" + "3f\n" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + "1 \r\n" + "x \r\n" + "0 last one\r\n"; + std::string trailer_data = + "a_trailer_key: and a trailer value\r\n" + "\r\n"; + std::string message_body_data = + "0123456789" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + + std::string message = (std::string(message_headers) + + std::string(message_body) + std::string(trailer_data)); + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked"); + fake_headers.AddKeyValue("another_random_header", "includes a continuation"); + + { + InSequence s1; + + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("DELETE /search?q=fo \t HTTP/1.1", + "DELETE", "/search?q=fo", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnChunkLength(10)); + EXPECT_CALL(visitor_mock_, OnChunkLength(63)); + EXPECT_CALL(visitor_mock_, OnChunkLength(1)); + EXPECT_CALL(visitor_mock_, OnChunkLength(0)); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers)); + std::string body_input; + EXPECT_CALL(visitor_mock_, OnRawBodyInput(_)) + .WillRepeatedly([&body_input](absl::string_view input) { + absl::StrAppend(&body_input, input); + }); + std::string body_data; + EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_)) + .WillRepeatedly([&body_data](absl::string_view input) { + absl::StrAppend(&body_data, input); + }); + EXPECT_CALL(visitor_mock_, OnTrailerInput(trailer_data)); + EXPECT_CALL(visitor_mock_, OnChunkExtensionInput(_)).Times(AnyNumber()); + + ASSERT_EQ(message_headers.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_EQ(message_body.size() + trailer_data.size(), + balsa_frame_.ProcessInput(message.data() + message_headers.size(), + message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + + EXPECT_EQ(message_body, body_input); + EXPECT_EQ(message_body_data, body_data); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyWithRequestFirstLineWarningWithOnlyMethod) { + std::string message = "GET\n"; + + FakeHeaders fake_headers; + + auto error_code = BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD; + { + InSequence s; + EXPECT_CALL(visitor_mock_, HandleWarning(error_code)); + EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET", "GET", "", "")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message)); + + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyWithRequestFirstLineWarningWithOnlyMethodAndWS) { + std::string message = "GET \n"; + + FakeHeaders fake_headers; + + auto error_code = BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD; + { + InSequence s; + EXPECT_CALL(visitor_mock_, HandleWarning(error_code)); + // The flag setting here intentionally alters the framer's behavior with + // trailing whitespace. + EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET ", "GET", "", "")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message)); + + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyWithRequestFirstLineWarningWithMethodAndURI) { + std::string message = "GET /uri\n"; + + FakeHeaders fake_headers; + + auto error_code = + BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI; + { + InSequence s; + EXPECT_CALL(visitor_mock_, HandleWarning(error_code)); + EXPECT_CALL(visitor_mock_, + OnRequestFirstLineInput("GET /uri", "GET", "/uri", "")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message)); + + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyWithResponseFirstLineError) { + std::string message = "HTTP/1.1\n\n"; + + FakeHeaders fake_headers; + + balsa_frame_.set_is_request(false); + auto error_code = BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION; + { + InSequence s; + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + // The function returns before any of the following is called. + EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput).Times(0); + EXPECT_CALL(visitor_mock_, ProcessHeaders(_)).Times(0); + EXPECT_CALL(visitor_mock_, HeaderDone()).Times(0); + EXPECT_CALL(visitor_mock_, MessageDone()).Times(0); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(_)).Times(0); + + EXPECT_GE(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, FlagsErrorWithContentLengthOverflow) { + std::string message = + "HTTP/1.0 200 OK\r\n" + "content-length: 9999999999999999999999999999999999999999\n" + "\n"; + + balsa_frame_.set_is_request(false); + auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH; + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, FlagsErrorWithInvalidResponseCode) { + std::string message = + "HTTP/1.0 x OK\r\n" + "\n"; + + balsa_frame_.set_is_request(false); + auto error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT; + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + + EXPECT_GE(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, FlagsErrorWithOverflowingResponseCode) { + std::string message = + "HTTP/1.0 999999999999999999999999999999999999999 OK\r\n" + "\n"; + + balsa_frame_.set_is_request(false); + auto error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT; + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + + EXPECT_GE(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, FlagsErrorWithInvalidContentLength) { + std::string message = + "HTTP/1.0 200 OK\r\n" + "content-length: xxx\n" + "\n"; + + balsa_frame_.set_is_request(false); + auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH; + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, FlagsErrorWithNegativeContentLengthValue) { + std::string message = + "HTTP/1.0 200 OK\r\n" + "content-length: -20\n" + "\n"; + + balsa_frame_.set_is_request(false); + auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH; + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, FlagsErrorWithEmptyContentLengthValue) { + std::string message = + "HTTP/1.0 200 OK\r\n" + "content-length: \n" + "\n"; + + balsa_frame_.set_is_request(false); + auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH; + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForTrivialResponse) { + std::string message = + "HTTP/1.0 200 OK\r\n" + "content-length: 0\n" + "\n"; + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("content-length", "0"); + + balsa_frame_.set_is_request(false); + { + InSequence s; + EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput( + "HTTP/1.0 200 OK", "HTTP/1.0", "200", "OK")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message)); + + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForResponseWithSplitBlankLines) { + std::string blanks = + "\n" + "\r\n" + "\r\n"; + std::string header_input = + "HTTP/1.0 200 OK\r\n" + "content-length: 0\n" + "\n"; + FakeHeaders fake_headers; + fake_headers.AddKeyValue("content-length", "0"); + + balsa_frame_.set_is_request(false); + { + InSequence s; + EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput( + "HTTP/1.0 200 OK", "HTTP/1.0", "200", "OK")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(header_input)); + + EXPECT_EQ(blanks.size(), + balsa_frame_.ProcessInput(blanks.data(), blanks.size())); + EXPECT_EQ(header_input.size(), balsa_frame_.ProcessInput( + header_input.data(), header_input.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForResponseWithBlankLines) { + std::string blanks = + "\n" + "\r\n" + "\n" + "\n" + "\r\n" + "\r\n"; + std::string header_input = + "HTTP/1.0 200 OK\r\n" + "content-length: 0\n" + "\n"; + std::string message = blanks + header_input; + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("content-length", "0"); + + balsa_frame_.set_is_request(false); + { + InSequence s; + EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput( + "HTTP/1.0 200 OK", "HTTP/1.0", "200", "OK")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(header_input)); + + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForResponseWithContentLength) { + std::string message_headers = + "HTTP/1.1 \t 200 Ok all is well\r\n" + "content-length: \t\t 20 \t\t \r\n" + "\r\n"; + std::string message_body = "12345678901234567890"; + std::string message = + std::string(message_headers) + std::string(message_body); + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("content-length", "20"); + + balsa_frame_.set_is_request(false); + { + InSequence s1; + EXPECT_CALL(visitor_mock_, + OnResponseFirstLineInput("HTTP/1.1 \t 200 Ok all is well", + "HTTP/1.1", "200", "Ok all is well")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnRawBodyInput(message_body)); + EXPECT_CALL(visitor_mock_, OnBodyChunkInput(message_body)); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers)); + + ASSERT_EQ(message_headers.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_EQ(message_body.size(), + balsa_frame_.ProcessInput(message.data() + message_headers.size(), + message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForResponseWithTransferEncoding) { + std::string message_headers = + "HTTP/1.1 \t 200 Ok all is well\r\n" + "trAnsfer-eNcoding: chunked\r\n" + "\r\n"; + std::string message_body = + "A chunkjed extension \r\n" + "01234567890 more crud including numbers 123123\r\n" + "3f\n" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + "0 last one\r\n" + "\r\n"; + std::string message_body_data = + "0123456789" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + + std::string message = + std::string(message_headers) + std::string(message_body); + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked"); + + balsa_frame_.set_is_request(false); + { + InSequence s1; + EXPECT_CALL(visitor_mock_, + OnResponseFirstLineInput("HTTP/1.1 \t 200 Ok all is well", + "HTTP/1.1", "200", "Ok all is well")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnChunkLength(10)); + EXPECT_CALL(visitor_mock_, OnChunkLength(63)); + EXPECT_CALL(visitor_mock_, OnChunkLength(0)); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers)); + std::string body_input; + EXPECT_CALL(visitor_mock_, OnRawBodyInput(_)) + .WillRepeatedly([&body_input](absl::string_view input) { + absl::StrAppend(&body_input, input); + }); + std::string body_data; + EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_)) + .WillRepeatedly([&body_data](absl::string_view input) { + absl::StrAppend(&body_data, input); + }); + EXPECT_CALL(visitor_mock_, OnTrailerInput(_)).Times(0); + + ASSERT_EQ(message_headers.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_EQ(message_body.size(), + balsa_frame_.ProcessInput(message.data() + message_headers.size(), + message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + + EXPECT_EQ(message_body, body_input); + EXPECT_EQ(message_body_data, body_data); +} + +TEST_F(HTTPBalsaFrameTest, + VisitorInvokedProperlyForResponseWithTransferEncodingAndTrailers) { + std::string message_headers = + "HTTP/1.1 \t 200 Ok all is well\r\n" + "trAnsfer-eNcoding: chunked\r\n" + "\r\n"; + std::string message_body = + "A chunkjed extension \r\n" + "01234567890 more crud including numbers 123123\r\n" + "3f\n" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + "0 last one\r\n"; + std::string trailer_data = + "a_trailer_key: and a trailer value\r\n" + "\r\n"; + std::string message_body_data = + "0123456789" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + + std::string message = (std::string(message_headers) + + std::string(message_body) + std::string(trailer_data)); + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked"); + + FakeHeaders fake_headers_in_trailer; + fake_headers_in_trailer.AddKeyValue("a_trailer_key", "and a trailer value"); + + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_trailer(&trailer_); + + { + InSequence s1; + EXPECT_CALL(visitor_mock_, + OnResponseFirstLineInput("HTTP/1.1 \t 200 Ok all is well", + "HTTP/1.1", "200", "Ok all is well")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnChunkLength(10)); + EXPECT_CALL(visitor_mock_, OnChunkLength(63)); + EXPECT_CALL(visitor_mock_, OnChunkLength(0)); + EXPECT_CALL(visitor_mock_, ProcessTrailers(fake_headers_in_trailer)); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers)); + std::string body_input; + EXPECT_CALL(visitor_mock_, OnRawBodyInput(_)) + .WillRepeatedly([&body_input](absl::string_view input) { + absl::StrAppend(&body_input, input); + }); + std::string body_data; + EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_)) + .WillRepeatedly([&body_data](absl::string_view input) { + absl::StrAppend(&body_data, input); + }); + EXPECT_CALL(visitor_mock_, OnTrailerInput(trailer_data)); + + ASSERT_EQ(message_headers.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_EQ(message_body.size() + trailer_data.size(), + balsa_frame_.ProcessInput(message.data() + message_headers.size(), + message.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + + EXPECT_EQ(message_body, body_input); + EXPECT_EQ(message_body_data, body_data); + + const absl::string_view a_trailer_key = trailer_.GetHeader("a_trailer_key"); + EXPECT_EQ("and a trailer value", a_trailer_key); +} + +TEST_F( + HTTPBalsaFrameTest, + VisitorInvokedProperlyForResponseWithTransferEncodingAndTrailersBytePer) { + std::string message_headers = + "HTTP/1.1 \t 200 Ok all is well\r\n" + "trAnsfer-eNcoding: chunked\r\n" + "\r\n"; + std::string message_body = + "A chunkjed extension \r\n" + "01234567890 more crud including numbers 123123\r\n" + "3f\n" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + "0 last one\r\n"; + std::string trailer_data = + "a_trailer_key: and a trailer value\r\n" + "\r\n"; + std::string message_body_data = + "0123456789" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + + std::string message = (std::string(message_headers) + + std::string(message_body) + std::string(trailer_data)); + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked"); + FakeHeaders fake_headers_in_trailer; + fake_headers_in_trailer.AddKeyValue("a_trailer_key", "and a trailer value"); + + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_trailer(&trailer_); + + { + InSequence s1; + EXPECT_CALL(visitor_mock_, + OnResponseFirstLineInput("HTTP/1.1 \t 200 Ok all is well", + "HTTP/1.1", "200", "Ok all is well")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnChunkLength(10)); + EXPECT_CALL(visitor_mock_, OnChunkLength(63)); + EXPECT_CALL(visitor_mock_, OnChunkLength(0)); + EXPECT_CALL(visitor_mock_, ProcessTrailers(fake_headers_in_trailer)); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers)); + std::string body_input; + EXPECT_CALL(visitor_mock_, OnRawBodyInput(_)) + .WillRepeatedly([&body_input](absl::string_view input) { + absl::StrAppend(&body_input, input); + }); + std::string body_data; + EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_)) + .WillRepeatedly([&body_data](absl::string_view input) { + absl::StrAppend(&body_data, input); + }); + std::string trailer_input; + EXPECT_CALL(visitor_mock_, OnTrailerInput(_)) + .WillRepeatedly([&trailer_input](absl::string_view input) { + absl::StrAppend(&trailer_input, input); + }); + + for (size_t i = 0; i < message.size(); ++i) { + ASSERT_EQ(1u, balsa_frame_.ProcessInput(message.data() + i, 1)); + } + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + + EXPECT_EQ(message_body, body_input); + EXPECT_EQ(message_body_data, body_data); + EXPECT_EQ(trailer_data, trailer_input); + + const absl::string_view a_trailer_key = trailer_.GetHeader("a_trailer_key"); + EXPECT_EQ("and a trailer value", a_trailer_key); +} + +TEST(HTTPBalsaFrame, + VisitorInvokedProperlyForResponseWithTransferEncodingAndTrailersRandom) { + TestSeed seed; + seed.Initialize(absl::GetFlag(FLAGS_randseed)); + RandomEngine rng; + rng.seed(seed.GetSeed()); + for (int i = 0; i < 1000; ++i) { + std::string message_headers = + "HTTP/1.1 \t 200 Ok all is well\r\n" + "trAnsfer-eNcoding: chunked\r\n" + "\r\n"; + std::string message_body = + "A chunkjed extension \r\n" + "01234567890 more crud including numbers 123123\r\n" + "3f\n" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + "0 last one\r\n"; + std::string trailer_data = + "a_trailer_key: and a trailer value\r\n" + "\r\n"; + std::string message_body_data = + "0123456789" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + + std::string message = + (std::string(message_headers) + std::string(message_body) + + std::string(trailer_data)); + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked"); + FakeHeaders fake_headers_in_trailer; + fake_headers_in_trailer.AddKeyValue("a_trailer_key", "and a trailer value"); + + StrictMock<BalsaVisitorMock> visitor_mock; + + BalsaHeaders headers; + BalsaHeaders trailer; + BalsaFrame balsa_frame; + balsa_frame.set_is_request(false); + balsa_frame.set_balsa_headers(&headers); + balsa_frame.set_balsa_trailer(&trailer); + balsa_frame.set_balsa_visitor(&visitor_mock); + + { + InSequence s1; + EXPECT_CALL(visitor_mock, OnResponseFirstLineInput( + "HTTP/1.1 \t 200 Ok all is well", + "HTTP/1.1", "200", "Ok all is well")); + EXPECT_CALL(visitor_mock, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock, HeaderDone()); + EXPECT_CALL(visitor_mock, ProcessTrailers(fake_headers_in_trailer)); + EXPECT_CALL(visitor_mock, MessageDone()); + } + EXPECT_CALL(visitor_mock, OnHeaderInput(message_headers)); + std::string body_input; + EXPECT_CALL(visitor_mock, OnRawBodyInput(_)) + .WillRepeatedly([&body_input](absl::string_view input) { + absl::StrAppend(&body_input, input); + }); + std::string body_data; + EXPECT_CALL(visitor_mock, OnBodyChunkInput(_)) + .WillRepeatedly([&body_data](absl::string_view input) { + absl::StrAppend(&body_data, input); + }); + std::string trailer_input; + EXPECT_CALL(visitor_mock, OnTrailerInput(_)) + .WillRepeatedly([&trailer_input](absl::string_view input) { + absl::StrAppend(&trailer_input, input); + }); + EXPECT_CALL(visitor_mock, OnChunkLength(_)).Times(AtLeast(1)); + EXPECT_CALL(visitor_mock, OnChunkExtensionInput(_)).Times(AtLeast(1)); + + size_t count = 0; + size_t total_processed = 0; + for (size_t i = 0; i < message.size();) { + auto dist = std::uniform_int_distribution<>(0, message.size() - i + 1); + count = dist(rng); + size_t processed = balsa_frame.ProcessInput(message.data() + i, count); + ASSERT_GE(count, processed); + total_processed += processed; + i += processed; + } + EXPECT_EQ(message.size(), total_processed); + EXPECT_TRUE(balsa_frame.MessageFullyRead()); + EXPECT_FALSE(balsa_frame.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame.ErrorCode()); + + EXPECT_EQ(message_body, body_input); + EXPECT_EQ(message_body_data, body_data); + EXPECT_EQ(trailer_data, trailer_input); + + const absl::string_view a_trailer_key = trailer.GetHeader("a_trailer_key"); + EXPECT_EQ("and a trailer value", a_trailer_key); + } +} + +TEST_F(HTTPBalsaFrameTest, + AppropriateActionTakenWhenHeadersTooLongWithTooMuchInput) { + std::string message = + "GET /asflkasfdhjsafdkljhasfdlkjhasdflkjhsafdlkjhh HTTP/1.1\r\n" + "\r\n"; + const size_t kAmountLessThanHeaderLen = 10; + ASSERT_LE(kAmountLessThanHeaderLen, message.size()); + + auto error_code = BalsaFrameEnums::HEADERS_TOO_LONG; + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + + balsa_frame_.set_max_header_length(message.size() - kAmountLessThanHeaderLen); + + ASSERT_EQ(balsa_frame_.max_header_length(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, AppropriateActionTakenWhenHeadersTooLongWithBody) { + std::string message = + "PUT /foo HTTP/1.1\r\n" + "Content-Length: 4\r\n" + "header: xxxxxxxxx\r\n\r\n" + "B"; // body begin + + auto error_code = BalsaFrameEnums::HEADERS_TOO_LONG; + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + + // -2 because we have 1 byte of body, and we want to refuse + // this. + balsa_frame_.set_max_header_length(message.size() - 2); + + ASSERT_EQ(balsa_frame_.max_header_length(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, AppropriateActionTakenWhenHeadersTooLongWhenReset) { + std::string message = + "GET /asflkasfdhjsafdkljhasfdlkjhasdflkjhsafdlkjhh HTTP/1.1\r\n" + "\r\n"; + const size_t kAmountLessThanHeaderLen = 10; + ASSERT_LE(kAmountLessThanHeaderLen, message.size()); + + auto error_code = BalsaFrameEnums::HEADERS_TOO_LONG; + + ASSERT_EQ(message.size() - 2, + balsa_frame_.ProcessInput(message.data(), message.size() - 2)); + + // Now set max header length to something smaller. + balsa_frame_.set_max_header_length(message.size() - kAmountLessThanHeaderLen); + EXPECT_CALL(visitor_mock_, HandleError(error_code)); + + ASSERT_EQ(0u, + balsa_frame_.ProcessInput(message.data() + message.size() - 2, 2)); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, balsa_frame_.ErrorCode()); +} + +class BalsaFrameParsingTest : public testing::Test { + protected: + void SetUp() override { + balsa_frame_.set_http_validation_policy( + HttpValidationPolicy::CreateDefault()); + balsa_frame_.set_is_request(true); + balsa_frame_.set_balsa_headers(&headers_); + balsa_frame_.set_balsa_visitor(&visitor_mock_); + } + + void TestEmptyHeaderKeyHelper(const std::string& message) { + InSequence s; + EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET", + "/", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, OnHeaderInput(_)); + EXPECT_CALL(visitor_mock_, + HandleError(BalsaFrameEnums::INVALID_HEADER_FORMAT)); + + ASSERT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + Mock::VerifyAndClearExpectations(&visitor_mock_); + } + + void TestInvalidTrailerFormat(const std::string& trailer, + bool invalid_name_char) { + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_trailer(&trailer_); + + std::string headers = + "HTTP/1.0 200 ok\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + + InSequence s; + + EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput); + EXPECT_CALL(visitor_mock_, OnHeaderInput); + EXPECT_CALL(visitor_mock_, ProcessHeaders); + EXPECT_CALL(visitor_mock_, HeaderDone); + EXPECT_CALL(visitor_mock_, OnChunkLength(3)); + EXPECT_CALL(visitor_mock_, OnChunkExtensionInput); + EXPECT_CALL(visitor_mock_, OnRawBodyInput); + EXPECT_CALL(visitor_mock_, OnBodyChunkInput); + EXPECT_CALL(visitor_mock_, OnChunkLength(0)); + EXPECT_CALL(visitor_mock_, OnChunkExtensionInput); + EXPECT_CALL(visitor_mock_, OnRawBodyInput); + EXPECT_CALL(visitor_mock_, OnRawBodyInput); + const auto expected_error = + invalid_name_char ? BalsaFrameEnums::INVALID_TRAILER_NAME_CHARACTER + : BalsaFrameEnums::INVALID_TRAILER_FORMAT; + EXPECT_CALL(visitor_mock_, HandleError(expected_error)).Times(1); + + EXPECT_CALL(visitor_mock_, ProcessTrailers(_)).Times(0); + EXPECT_CALL(visitor_mock_, MessageDone()).Times(0); + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(expected_error, balsa_frame_.ErrorCode()); + + Mock::VerifyAndClearExpectations(&visitor_mock_); + } + + BalsaHeaders headers_; + BalsaHeaders trailer_; + BalsaFrame balsa_frame_; + StrictMock<BalsaVisitorMock> visitor_mock_; +}; + +TEST_F(BalsaFrameParsingTest, AppropriateActionTakenWhenHeaderColonsAreFunny) { + // Believe it or not, the following message is not structured willy-nilly. + // It is structured so that both codepaths in both SSE2 and non SSE2 paths + // for finding colons are exersized. + std::string message = + "GET / HTTP/1.1\r\n" + "a\r\n" + "b\r\n" + "c\r\n" + "d\r\n" + "e\r\n" + "f\r\n" + "g\r\n" + "h\r\n" + "i:\r\n" + "j\r\n" + "k\r\n" + "l\r\n" + "m\r\n" + "n\r\n" + "o\r\n" + "p\r\n" + "q\r\n" + "r\r\n" + "s\r\n" + "t\r\n" + "u\r\n" + "v\r\n" + "w\r\n" + "x\r\n" + "y\r\n" + "z\r\n" + "A\r\n" + "B\r\n" + ": val\r\n" + "\r\n"; + + EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET", + "/", "HTTP/1.1")); + EXPECT_CALL(visitor_mock_, OnHeaderInput(_)); + EXPECT_CALL(visitor_mock_, + HandleWarning(BalsaFrameEnums::HEADER_MISSING_COLON)) + .Times(27); + EXPECT_CALL(visitor_mock_, + HandleError(BalsaFrameEnums::INVALID_HEADER_FORMAT)); + + ASSERT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + + EXPECT_TRUE(balsa_frame_.Error()); +} + +TEST_F(BalsaFrameParsingTest, ErrorWhenHeaderKeyIsEmpty) { + std::string firstKeyIsEmpty = + "GET / HTTP/1.1\r\n" + ": \r\n" + "a:b\r\n" + "c:d\r\n" + "\r\n"; + TestEmptyHeaderKeyHelper(firstKeyIsEmpty); + + balsa_frame_.Reset(); + + std::string laterKeyIsEmpty = + "GET / HTTP/1.1\r\n" + "a:b\r\n" + ": \r\n" + "c:d\r\n" + "\r\n"; + TestEmptyHeaderKeyHelper(laterKeyIsEmpty); +} + +TEST_F(BalsaFrameParsingTest, InvalidTrailerFormat) { + std::string trailer = + ":monkeys\n" + "\r\n"; + TestInvalidTrailerFormat(trailer, false); + + balsa_frame_.Reset(); + + std::string trailer2 = + " \r\n" + "test: test\r\n" + "\r\n"; + TestInvalidTrailerFormat(trailer2, true); + + balsa_frame_.Reset(); + + std::string trailer3 = + "a: b\r\n" + ": test\r\n" + "\r\n"; + TestInvalidTrailerFormat(trailer3, false); +} + +TEST_F(HTTPBalsaFrameTest, + EnsureHeaderFramingFoundWithVariousCombinationsOfRN_RN) { + const std::string message = + "GET / HTTP/1.1\r\n" + "content-length: 0\r\n" + "a\r\n" + "b\r\n" + "c\r\n" + "d\r\n" + "e\r\n" + "f\r\n" + "g\r\n" + "h\r\n" + "i\r\n" + "\r\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.Error()) + << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + EnsureHeaderFramingFoundWithVariousCombinationsOfRN_N) { + const std::string message = + "GET / HTTP/1.1\n" + "content-length: 0\n" + "a\n" + "b\n" + "c\n" + "d\n" + "e\n" + "f\n" + "g\n" + "h\n" + "i\n" + "\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.Error()) + << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + EnsureHeaderFramingFoundWithVariousCombinationsOfRN_RN_N) { + const std::string message = + "GET / HTTP/1.1\n" + "content-length: 0\r\n" + "a\r\n" + "b\n" + "c\r\n" + "d\n" + "e\r\n" + "f\n" + "g\r\n" + "h\n" + "i\r\n" + "\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.Error()) + << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, + EnsureHeaderFramingFoundWithVariousCombinationsOfRN_N_RN) { + const std::string message = + "GET / HTTP/1.1\n" + "content-length: 0\r\n" + "a\n" + "b\r\n" + "c\n" + "d\r\n" + "e\n" + "f\r\n" + "g\n" + "h\r\n" + "i\n" + "\r\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.Error()) + << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, ReadUntilCloseStateEnteredAsExpectedAndNotExited) { + std::string message = + "HTTP/1.1 200 OK\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_FALSE(balsa_frame_.Error()) + << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode()); + EXPECT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE, balsa_frame_.ParseState()); + + std::string gobldygook = "-198324-9182-43981-23498-98342-jasldfn-1294hj"; + for (int i = 0; i < 1000; ++i) { + EXPECT_EQ(gobldygook.size(), + balsa_frame_.ProcessInput(gobldygook.data(), gobldygook.size())); + EXPECT_FALSE(balsa_frame_.Error()) + << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode()); + EXPECT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE, balsa_frame_.ParseState()); + } +} + +TEST_F(HTTPBalsaFrameTest, + BytesSafeToSpliceAndBytesSplicedWorksWithContentLength) { + std::string header = + "HTTP/1.1 200 OK\r\n" + "content-length: 1000\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + size_t bytes_safe_to_splice = 1000; + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_EQ(header.size(), + balsa_frame_.ProcessInput(header.data(), header.size())); + EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice()); + while (bytes_safe_to_splice > 0) { + balsa_frame_.BytesSpliced(1); + bytes_safe_to_splice -= 1; + ASSERT_FALSE(balsa_frame_.Error()) + << BalsaFrameEnums::ParseStateToString(balsa_frame_.ParseState()) << " " + << BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode()) + << " with bytes_safe_to_splice: " << bytes_safe_to_splice + << " and BytesSafeToSplice(): " << balsa_frame_.BytesSafeToSplice(); + } + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); +} + +TEST_F(HTTPBalsaFrameTest, BytesSplicedFlagsErrorsWhenNotInProperState) { + balsa_frame_.set_is_request(false); + balsa_frame_.BytesSpliced(1); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO, + balsa_frame_.ErrorCode()); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); +} + +TEST_F(HTTPBalsaFrameTest, + BytesSplicedFlagsErrorsWhenTooMuchSplicedForContentLen) { + std::string header = + "HTTP/1.1 200 OK\r\n" + "content-length: 1000\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_EQ(header.size(), + balsa_frame_.ProcessInput(header.data(), header.size())); + EXPECT_EQ(1000u, balsa_frame_.BytesSafeToSplice()); + balsa_frame_.BytesSpliced(1001); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ( + BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT, + balsa_frame_.ErrorCode()); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); +} + +TEST_F(HTTPBalsaFrameTest, BytesSplicedWorksAsExpectedForReadUntilClose) { + std::string header = + "HTTP/1.1 200 OK\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_EQ(header.size(), + balsa_frame_.ProcessInput(header.data(), header.size())); + EXPECT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE, balsa_frame_.ParseState()); + EXPECT_EQ(std::numeric_limits<size_t>::max(), + balsa_frame_.BytesSafeToSplice()); + for (int i = 0; i < 1000; ++i) { + EXPECT_EQ(std::numeric_limits<size_t>::max(), + balsa_frame_.BytesSafeToSplice()); + balsa_frame_.BytesSpliced(12312312); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + } + EXPECT_EQ(std::numeric_limits<size_t>::max(), + balsa_frame_.BytesSafeToSplice()); +} + +TEST_F(HTTPBalsaFrameTest, + BytesSplicedFlagsErrorsWhenTooMuchSplicedForChunked) { + std::string header = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + std::string body_fragment = "a\r\n"; + balsa_frame_.set_is_request(false); + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_EQ(header.size(), + balsa_frame_.ProcessInput(header.data(), header.size())); + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_EQ( + body_fragment.size(), + balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size())); + EXPECT_EQ(10u, balsa_frame_.BytesSafeToSplice()); + balsa_frame_.BytesSpliced(11); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ( + BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT, + balsa_frame_.ErrorCode()); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); +} + +TEST_F(HTTPBalsaFrameTest, BytesSafeToSpliceAndBytesSplicedWorksWithChunks) { + std::string header = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_EQ(header.size(), + balsa_frame_.ProcessInput(header.data(), header.size())); + + { + std::string body_fragment = "3e8\r\n"; + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + size_t bytes_safe_to_splice = 1000; + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_EQ( + body_fragment.size(), + balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size())); + EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice()); + while (bytes_safe_to_splice > 0) { + balsa_frame_.BytesSpliced(1); + bytes_safe_to_splice -= 1; + ASSERT_FALSE(balsa_frame_.Error()); + } + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_FALSE(balsa_frame_.Error()); + } + { + std::string body_fragment = "\r\n7d0\r\n"; + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + size_t bytes_safe_to_splice = 2000; + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_EQ( + body_fragment.size(), + balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size())); + EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice()); + while (bytes_safe_to_splice > 0) { + balsa_frame_.BytesSpliced(1); + bytes_safe_to_splice -= 1; + ASSERT_FALSE(balsa_frame_.Error()); + } + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_FALSE(balsa_frame_.Error()); + } + { + std::string body_fragment = "\r\n1\r\n"; + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + size_t bytes_safe_to_splice = 1; + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_EQ( + body_fragment.size(), + balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size())); + EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice()); + while (bytes_safe_to_splice > 0) { + balsa_frame_.BytesSpliced(1); + bytes_safe_to_splice -= 1; + ASSERT_FALSE(balsa_frame_.Error()); + } + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_FALSE(balsa_frame_.Error()); + } + { + std::string body_fragment = "\r\n0\r\n\r\n"; + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_EQ( + body_fragment.size(), + balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size())); + EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice()); + EXPECT_FALSE(balsa_frame_.Error()); + } + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); +} + +TEST_F(HTTPBalsaFrameTest, TwoDifferentContentHeadersIsAnError) { + std::string header = + "HTTP/1.1 200 OK\r\n" + "content-length: 12\r\n" + "content-length: 14\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + balsa_frame_.ProcessInput(header.data(), header.size()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::MULTIPLE_CONTENT_LENGTH_KEYS, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, TwoSameContentHeadersIsNotAnError) { + std::string header = + "POST / HTTP/1.1\r\n" + "content-length: 1\r\n" + "content-length: 1\r\n" + "\r\n" + "1"; + balsa_frame_.ProcessInput(header.data(), header.size()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + EXPECT_FALSE(balsa_frame_.Error()); + balsa_frame_.ProcessInput(header.data(), header.size()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); +} + +TEST_F(HTTPBalsaFrameTest, TwoTransferEncodingHeadersIsAnError) { + std::string header = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked\r\n" + "transfer-encoding: identity\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + balsa_frame_.ProcessInput(header.data(), header.size()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::MULTIPLE_TRANSFER_ENCODING_KEYS, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, TwoTransferEncodingTokensIsAnError) { + std::string header = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked, identity\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + balsa_frame_.ProcessInput(header.data(), header.size()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, UnknownTransferEncodingTokenIsAnError) { + std::string header = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked-identity\r\n" + "\r\n"; + balsa_frame_.set_is_request(false); + balsa_frame_.ProcessInput(header.data(), header.size()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING, + balsa_frame_.ErrorCode()); +} + +class DetachOnDoneFramer : public NoOpBalsaVisitor { + public: + DetachOnDoneFramer() { + framer_.set_balsa_headers(&headers_); + framer_.set_balsa_visitor(this); + } + + void MessageDone() override { framer_.set_balsa_headers(nullptr); } + + BalsaFrame* framer() { return &framer_; } + + protected: + BalsaFrame framer_; + BalsaHeaders headers_; +}; + +TEST(HTTPBalsaFrame, TestDetachOnDone) { + DetachOnDoneFramer framer; + const char* message = "GET HTTP/1.1\r\n\r\n"; + // Frame the whole message. The framer will call MessageDone which will set + // the headers to nullptr. + framer.framer()->ProcessInput(message, strlen(message)); + EXPECT_TRUE(framer.framer()->MessageFullyRead()); + EXPECT_FALSE(framer.framer()->Error()); +} + +// We simply extend DetachOnDoneFramer so that we do not have +// to provide trivial implementation for various functions. +class ModifyMaxHeaderLengthFramerInFirstLine : public DetachOnDoneFramer { + public: + void MessageDone() override {} + // This sets to max_header_length to a low number and + // this would cause us to reject the query. Even though + // our original headers length was acceptable. + void OnRequestFirstLineInput(absl::string_view /*line_input*/, + absl::string_view /*method_input*/, + absl::string_view /*request_uri*/, + absl::string_view /*version_input*/ + ) override { + framer_.set_max_header_length(1); + } +}; + +// In this case we have already processed the headers and called on +// the visitor HeadersDone and hence its too late to reduce the +// max_header_length here. +class ModifyMaxHeaderLengthFramerInHeaderDone : public DetachOnDoneFramer { + public: + void MessageDone() override {} + void HeaderDone() override { framer_.set_max_header_length(1); } +}; + +TEST(HTTPBalsaFrame, ChangeMaxHeadersLengthOnFirstLine) { + std::string message = + "PUT /foo HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "header: xxxxxxxxx\r\n\r\n" + "B"; // body begin + + ModifyMaxHeaderLengthFramerInFirstLine balsa_frame; + balsa_frame.framer()->set_is_request(true); + balsa_frame.framer()->set_max_header_length(message.size() - 1); + + balsa_frame.framer()->ProcessInput(message.data(), message.size()); + EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, + balsa_frame.framer()->ErrorCode()); +} + +TEST(HTTPBalsaFrame, ChangeMaxHeadersLengthOnHeaderDone) { + std::string message = + "PUT /foo HTTP/1.1\r\n" + "Content-Length: 2\r\n" + "header: xxxxxxxxx\r\n\r\n" + "B"; // body begin + + ModifyMaxHeaderLengthFramerInHeaderDone balsa_frame; + balsa_frame.framer()->set_is_request(true); + balsa_frame.framer()->set_max_header_length(message.size() - 1); + + balsa_frame.framer()->ProcessInput(message.data(), message.size()); + EXPECT_EQ(0, balsa_frame.framer()->ErrorCode()); +} + +// This is a simple test to ensure the simple case that we accept +// a query which has headers size same as the max_header_length. +// (i.e., there is no off by one error). +TEST(HTTPBalsaFrame, HeadersSizeSameAsMaxLengthIsAccepted) { + std::string message = + "GET /foo HTTP/1.1\r\n" + "header: xxxxxxxxx\r\n\r\n"; + + ModifyMaxHeaderLengthFramerInHeaderDone balsa_frame; + balsa_frame.framer()->set_is_request(true); + balsa_frame.framer()->set_max_header_length(message.size()); + balsa_frame.framer()->ProcessInput(message.data(), message.size()); + EXPECT_EQ(0, balsa_frame.framer()->ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, KeyHasSpaces) { + const std::string message = + "GET / HTTP/1.1\r\n" + "key has spaces: lock\r\n" + "\r\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, SpaceBeforeColon) { + const std::string message = + "GET / HTTP/1.1\r\n" + "key : lock\r\n" + "\r\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, SpaceBeforeColonNotAfter) { + const std::string message = + "GET / HTTP/1.1\r\n" + "key :lock\r\n" + "\r\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, KeyHasTabs) { + const std::string message = + "GET / HTTP/1.1\r\n" + "key\thas\ttabs: lock\r\n" + "\r\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, TabBeforeColon) { + const std::string message = + "GET / HTTP/1.1\r\n" + "key\t: lock\r\n" + "\r\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, KeyHasContinuation) { + const std::string message = + "GET / HTTP/1.1\r\n" + "key\n includes continuation: but not value\r\n" + "\r\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER, + balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, KeyHasMultipleContinuations) { + const std::string message = + "GET / HTTP/1.1\r\n" + "key\n includes\r\n multiple\n continuations: but not value\r\n" + "\r\n"; + EXPECT_EQ(message.size(), + balsa_frame_.ProcessInput(message.data(), message.size())); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER, + balsa_frame_.ErrorCode()); +} + +// Missing colon is a warning, not an error. +TEST_F(HTTPBalsaFrameTest, TrailerMissingColon) { + std::string headers = + "HTTP/1.0 302 Redirect\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + std::string trailer = + "crass_monkeys\n" + "\r\n"; + + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_trailer(&trailer_); + EXPECT_CALL(visitor_mock_, + HandleWarning(BalsaFrameEnums::TRAILER_MISSING_COLON)); + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::TRAILER_MISSING_COLON, balsa_frame_.ErrorCode()); + EXPECT_FALSE(trailer_.HasHeader("crass")); + EXPECT_TRUE(trailer_.HasHeader("crass_monkeys")); + const absl::string_view crass_monkeys = trailer_.GetHeader("crass_monkeys"); + EXPECT_TRUE(crass_monkeys.empty()); +} + +// This tests multiple headers in trailer. We currently do not and have no plan +// to support Trailer field in headers to limit valid field-name in trailer. +// Test that we aren't confused by the non-alphanumeric characters in the +// trailer, especially ':'. +TEST_F(HTTPBalsaFrameTest, MultipleHeadersInTrailer) { + std::string headers = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\n" + "0\n"; + std::map<std::string, std::string> trailer; + trailer["X-Trace"] = + "http://trace.example.com/trace?host=" + "foobar.example.com&start=2012-06-03_15:59:06&rpc_duration=0.243349"; + trailer["Date"] = "Sun, 03 Jun 2012 22:59:06 GMT"; + trailer["Content-Type"] = "text/html"; + trailer["X-Backends"] = "127.0.0.1_0,foo.example.com:39359"; + trailer["X-Request-Trace"] = + "foo.example.com:39359,127.0.0.1_1," + "foo.example.com:39359,127.0.0.1_0," + "foo.example.com:39359"; + trailer["X-Service-Trace"] = "default"; + trailer["X-Service"] = "default"; + + std::map<std::string, std::string>::const_iterator iter; + std::string trailer_data; + TestSeed seed; + seed.Initialize(absl::GetFlag(FLAGS_randseed)); + RandomEngine rng; + rng.seed(seed.GetSeed()); + FakeHeaders fake_headers_in_trailer; + for (iter = trailer.begin(); iter != trailer.end(); ++iter) { + trailer_data += iter->first; + trailer_data += ":"; + std::stringstream leading_whitespace_for_value; + AppendRandomWhitespace(rng, &leading_whitespace_for_value); + trailer_data += leading_whitespace_for_value.str(); + trailer_data += iter->second; + std::stringstream trailing_whitespace_for_value; + AppendRandomWhitespace(rng, &trailing_whitespace_for_value); + trailer_data += trailing_whitespace_for_value.str(); + trailer_data += random_line_term(rng); + fake_headers_in_trailer.AddKeyValue(iter->first, iter->second); + } + trailer_data += random_line_term(rng); + + FakeHeaders fake_headers; + fake_headers.AddKeyValue("transfer-encoding", "chunked"); + + { + InSequence s1; + EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput( + "HTTP/1.1 200 OK", "HTTP/1.1", "200", "OK")); + EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, OnChunkLength(3)); + EXPECT_CALL(visitor_mock_, OnChunkLength(0)); + EXPECT_CALL(visitor_mock_, ProcessTrailers(fake_headers_in_trailer)); + EXPECT_CALL(visitor_mock_, OnTrailerInput(trailer_data)); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + EXPECT_CALL(visitor_mock_, OnHeaderInput(headers)); + std::string body_input; + EXPECT_CALL(visitor_mock_, OnRawBodyInput(_)) + .WillRepeatedly([&body_input](absl::string_view input) { + absl::StrAppend(&body_input, input); + }); + EXPECT_CALL(visitor_mock_, OnBodyChunkInput("123")); + + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_trailer(&trailer_); + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_EQ(trailer_data.size(), balsa_frame_.ProcessInput( + trailer_data.data(), trailer_data.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + + EXPECT_EQ(chunks, body_input); + + for (iter = trailer.begin(); iter != trailer.end(); ++iter) { + const absl::string_view value = trailer_.GetHeader(iter->first); + EXPECT_EQ(iter->second, value); + } +} + +// Test if trailer is not set (the common case), everything will be fine. +TEST_F(HTTPBalsaFrameTest, NothingBadHappensWithNULLTrailer) { + std::string headers = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + std::string trailer = + "crass: monkeys\r\n" + "funky: monkeys\r\n" + "\n"; + + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_visitor(nullptr); + balsa_frame_.set_balsa_trailer(nullptr); + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + ASSERT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +// Test Reset() correctly resets trailer related states. +TEST_F(HTTPBalsaFrameTest, FrameAndResetAndFrameAgain) { + std::string headers = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + std::string trailer = + "k: v\n" + "\n"; + + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_trailer(&trailer_); + balsa_frame_.set_balsa_visitor(nullptr); + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + ASSERT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + + absl::string_view value = trailer_.GetHeader("k"); + EXPECT_EQ("v", value); + + balsa_frame_.Reset(); + + headers = + "HTTP/1.1 404 Error\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + chunks = + "4\r\n" + "1234\r\n" + "0\r\n"; + trailer = + "nk: nv\n" + "\n"; + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + ASSERT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + + value = trailer_.GetHeader("k"); + EXPECT_TRUE(value.empty()); + value = trailer_.GetHeader("nk"); + EXPECT_EQ("nv", value); +} + +TEST_F(HTTPBalsaFrameTest, TrackInvalidChars) { + EXPECT_FALSE(balsa_frame_.track_invalid_chars()); +} + +// valid chars are 9 (tab), 10 (LF), 13(CR), and 32-255 +TEST_F(HTTPBalsaFrameTest, InvalidCharsInHeaderValueWarning) { + balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning); + // nulls are double escaped since otherwise this initialized wrong + const std::string kEscapedInvalid1 = + "GET /foo HTTP/1.1\r\n" + "Bogus-Head: val\\x00\r\n" + "More-Invalid: \\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\r\n" + "And-More: \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D" + "\x1E\x1F\r\n\r\n"; + std::string message; + // now we convert to real embedded nulls + absl::CUnescape(kEscapedInvalid1, &message); + + EXPECT_CALL(visitor_mock_, + HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER)); + + balsa_frame_.ProcessInput(message.data(), message.size()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); +} + +// Header names reject invalid chars even at the warning level. +TEST_F(HTTPBalsaFrameTest, InvalidCharsInHeaderKeyError) { + balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning); + // nulls are double escaped since otherwise this initialized wrong + const std::string kEscapedInvalid1 = + "GET /foo HTTP/1.1\r\n" + "Bogus\\x00-Head: val\r\n\r\n"; + std::string message; + // now we convert to real embedded nulls + absl::CUnescape(kEscapedInvalid1, &message); + + EXPECT_CALL(visitor_mock_, + HandleError(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER)); + + balsa_frame_.ProcessInput(message.data(), message.size()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); +} + +TEST_F(HTTPBalsaFrameTest, InvalidCharsInHeaderError) { + balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kError); + const std::string kEscapedInvalid = + "GET /foo HTTP/1.1\r\n" + "Smuggle-Me: \\x00GET /bar HTTP/1.1\r\n" + "Another-Header: value\r\n\r\n"; + std::string message; + absl::CUnescape(kEscapedInvalid, &message); + + EXPECT_CALL(visitor_mock_, + HandleError(BalsaFrameEnums::INVALID_HEADER_CHARACTER)); + + balsa_frame_.ProcessInput(message.data(), message.size()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); +} + +class HTTPBalsaFrameTestOneChar : public HTTPBalsaFrameTest, + public testing::WithParamInterface<char> { + public: + char GetCharUnderTest() { return GetParam(); } +}; + +TEST_P(HTTPBalsaFrameTestOneChar, InvalidCharsWarningSet) { + balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning); + const std::string kRequest = + "GET /foo HTTP/1.1\r\n" + "Bogus-Char-Goes-Here: "; + const std::string kEnding = "\r\n\r\n"; + std::string message = kRequest; + const char c = GetCharUnderTest(); + message.append(1, c); + message.append(kEnding); + if (c == 9 || c == 10 || c == 13) { + // valid char + EXPECT_CALL(visitor_mock_, + HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER)) + .Times(0); + balsa_frame_.ProcessInput(message.data(), message.size()); + EXPECT_THAT(balsa_frame_.get_invalid_chars(), IsEmpty()); + } else { + // invalid char + absl::flat_hash_map<char, int> expected_count = {{c, 1}}; + EXPECT_CALL(visitor_mock_, + HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER)); + balsa_frame_.ProcessInput(message.data(), message.size()); + EXPECT_EQ(balsa_frame_.get_invalid_chars(), expected_count); + } + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); +} + +INSTANTIATE_TEST_SUITE_P(TestInvalidCharSet, HTTPBalsaFrameTestOneChar, + Range((char)0, (char)32)); + +TEST_F(HTTPBalsaFrameTest, InvalidCharEndOfLine) { + balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning); + const std::string kInvalid1 = + "GET /foo HTTP/1.1\r\n" + "Header-Key: headervalue\\x00\r\n" + "Legit-Header: legitvalue\r\n\r\n"; + std::string message; + absl::CUnescape(kInvalid1, &message); + + EXPECT_CALL(visitor_mock_, + HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER)); + balsa_frame_.ProcessInput(message.data(), message.size()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); +} + +TEST_F(HTTPBalsaFrameTest, InvalidCharInFirstLine) { + balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning); + const std::string kInvalid1 = + "GET /foo \\x00HTTP/1.1\r\n" + "Legit-Header: legitvalue\r\n\r\n"; + std::string message; + absl::CUnescape(kInvalid1, &message); + + EXPECT_CALL(visitor_mock_, + HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER)); + balsa_frame_.ProcessInput(message.data(), message.size()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); +} + +TEST_F(HTTPBalsaFrameTest, InvalidCharsAreCounted) { + balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning); + const std::string kInvalid1 = + "GET /foo \\x00\\x00\\x00HTTP/1.1\r\n" + "Bogus-Header: \\x00\\x04\\x04value\r\n\r\n"; + std::string message; + absl::CUnescape(kInvalid1, &message); + + EXPECT_CALL(visitor_mock_, + HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER)); + balsa_frame_.ProcessInput(message.data(), message.size()); + absl::flat_hash_map<char, int> expected_count = {{'\0', 4}, {'\4', 2}}; + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_EQ(balsa_frame_.get_invalid_chars(), expected_count); + + absl::flat_hash_map<char, int> empty_count; + balsa_frame_.Reset(); + EXPECT_EQ(balsa_frame_.get_invalid_chars(), empty_count); +} + +// Test gibberish in headers and trailer. GFE does not crash but garbage in +// garbage out. +TEST_F(HTTPBalsaFrameTest, GibberishInHeadersAndTrailer) { + const char kGibberish1[] = {138, 175, 233, 0}; + const char kGibberish2[] = {'?', '?', 128, 255, 129, 254, 0}; + const char kGibberish3[] = "foo: bar : eeep : baz"; + + std::string gibberish_headers; + gibberish_headers += kGibberish1; + gibberish_headers += ":"; + gibberish_headers += kGibberish2; + gibberish_headers += "\r\n"; + gibberish_headers += kGibberish3; + gibberish_headers += "\r\n"; + + std::string headers = + "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked\r\n"; + headers += gibberish_headers; + headers += "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + std::string trailer = "k: v\n"; + trailer += gibberish_headers; + trailer += "\n"; + + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_trailer(&trailer_); + balsa_frame_.set_balsa_visitor(nullptr); + + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + ASSERT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); + + // Transfer-encoding can be multi-valued, so GetHeader does not work. + EXPECT_TRUE(headers_.transfer_encoding_is_chunked()); + absl::string_view field_value = headers_.GetHeader(kGibberish1); + EXPECT_EQ(kGibberish2, field_value); + field_value = headers_.GetHeader("foo"); + EXPECT_EQ("bar : eeep : baz", field_value); + + field_value = trailer_.GetHeader("k"); + EXPECT_EQ("v", field_value); + field_value = trailer_.GetHeader(kGibberish1); + EXPECT_EQ(kGibberish2, field_value); + field_value = trailer_.GetHeader("foo"); + EXPECT_EQ("bar : eeep : baz", field_value); +} + +// Test that trailer is not allowed for request. +TEST_F(HTTPBalsaFrameTest, TrailerIsNotAllowedInRequest) { + EXPECT_QUICHE_BUG(balsa_frame_.set_balsa_trailer(&trailer_), + "Trailer in request is not allowed"); +} + +// Note we reuse the header length limit because trailer is just multiple +// headers. +TEST_F(HTTPBalsaFrameTest, TrailerTooLong) { + std::string headers = + "HTTP/1.0 200 ok\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + std::string trailer = + "very : long trailer\n" + "should:cause\r\n" + "trailer :too long error\n" + "\r\n"; + + balsa_frame_.set_is_request(false); + balsa_frame_.set_balsa_trailer(&trailer_); + ASSERT_LT(headers.size(), trailer.size()); + balsa_frame_.set_max_header_length(headers.size()); + + EXPECT_CALL(visitor_mock_, HandleError(BalsaFrameEnums::TRAILER_TOO_LONG)); + EXPECT_CALL(visitor_mock_, ProcessTrailers(_)).Times(0); + EXPECT_CALL(visitor_mock_, MessageDone()).Times(0); + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_EQ(balsa_frame_.max_header_length(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_FALSE(balsa_frame_.MessageFullyRead()); + EXPECT_TRUE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::TRAILER_TOO_LONG, balsa_frame_.ErrorCode()); +} + +// If the trailer_ object in the framer is not set, ProcessTrailers won't be +// called. +TEST_F(HTTPBalsaFrameTest, + NoProcessTrailersCallWhenFramerHasNullTrailerObject) { + std::string headers = + "HTTP/1.0 200 ok\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"; + + std::string chunks = + "3\r\n" + "123\r\n" + "0\r\n"; + std::string trailer = + "trailer_key : trailer_value\n" + "\r\n"; + + balsa_frame_.set_is_request(false); + + EXPECT_CALL(visitor_mock_, ProcessTrailers(_)).Times(0); + ASSERT_EQ(headers.size(), + balsa_frame_.ProcessInput(headers.data(), headers.size())); + ASSERT_EQ(chunks.size(), + balsa_frame_.ProcessInput(chunks.data(), chunks.size())); + EXPECT_EQ(trailer.size(), + balsa_frame_.ProcessInput(trailer.data(), trailer.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +// Handle two sets of headers when set up properly and the first is 100 +// Continue. +TEST_F(HTTPBalsaFrameTest, Support100Continue) { + std::string initial_headers = + "HTTP/1.1 100 Continue\r\n" + "\r\n"; + std::string real_headers = + "HTTP/1.1 200 OK\r\n" + "content-length: 3\r\n" + "\r\n"; + std::string body = "foo"; + + balsa_frame_.set_is_request(false); + BalsaHeaders continue_headers; + balsa_frame_.set_continue_headers(&continue_headers); + + ASSERT_EQ(initial_headers.size(), + balsa_frame_.ProcessInput(initial_headers.data(), + initial_headers.size())); + ASSERT_EQ(real_headers.size(), + balsa_frame_.ProcessInput(real_headers.data(), real_headers.size())) + << balsa_frame_.ErrorCode(); + ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +// Handle two sets of headers when set up properly and the first is 100 +// Continue and it meets the conditions for b/62408297 +TEST_F(HTTPBalsaFrameTest, Support100Continue401Unauthorized) { + std::string initial_headers = + "HTTP/1.1 100 Continue\r\n" + "\r\n"; + std::string real_headers = + "HTTP/1.1 401 Unauthorized\r\n" + "content-length: 3\r\n" + "\r\n"; + std::string body = "foo"; + + balsa_frame_.set_is_request(false); + BalsaHeaders continue_headers; + balsa_frame_.set_continue_headers(&continue_headers); + + ASSERT_EQ(initial_headers.size(), + balsa_frame_.ProcessInput(initial_headers.data(), + initial_headers.size())); + ASSERT_EQ(real_headers.size(), + balsa_frame_.ProcessInput(real_headers.data(), real_headers.size())) + << balsa_frame_.ErrorCode(); + ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +TEST_F(HTTPBalsaFrameTest, Support100ContinueRunTogether) { + std::string both_headers = + "HTTP/1.1 100 Continue\r\n" + "\r\n" + "HTTP/1.1 200 OK\r\n" + "content-length: 3\r\n" + "\r\n"; + std::string body = "foo"; + + { + InSequence s; + EXPECT_CALL(visitor_mock_, ContinueHeaderDone()); + EXPECT_CALL(visitor_mock_, HeaderDone()); + EXPECT_CALL(visitor_mock_, MessageDone()); + } + + balsa_frame_.set_is_request(false); + BalsaHeaders continue_headers; + balsa_frame_.set_continue_headers(&continue_headers); + + ASSERT_EQ(both_headers.size(), + balsa_frame_.ProcessInput(both_headers.data(), both_headers.size())) + << balsa_frame_.ErrorCode(); + ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size())); + EXPECT_TRUE(balsa_frame_.MessageFullyRead()); + EXPECT_FALSE(balsa_frame_.Error()); + EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode()); +} + +} // namespace + +} // namespace test + +} // namespace quiche
diff --git a/quiche/common/balsa/balsa_headers_test.cc b/quiche/common/balsa/balsa_headers_test.cc new file mode 100644 index 0000000..216ab07 --- /dev/null +++ b/quiche/common/balsa/balsa_headers_test.cc
@@ -0,0 +1,3743 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Note that several of the BalsaHeaders functions are +// tested in the balsa_frame_test as the BalsaFrame and +// BalsaHeaders classes are fairly related. + +#include "quiche/common/balsa/balsa_headers.h" + +#include <cstring> +#include <limits> +#include <memory> +#include <sstream> +#include <string> +#include <tuple> +#include <utility> +#include <vector> + +#include "absl/base/macros.h" +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "quiche/common/balsa/balsa_enums.h" +#include "quiche/common/balsa/balsa_frame.h" +#include "quiche/common/balsa/simple_buffer.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/platform/api/quiche_test.h" +#include "quiche/common/platform/api/quiche_test_helpers.h" + +using absl::make_unique; +using testing::AnyOf; +using testing::Combine; +using testing::ElementsAre; +using testing::Eq; +using testing::StrEq; +using testing::ValuesIn; + +namespace quiche { + +namespace test { + +class BalsaHeadersTestPeer { + public: + static void WriteFromFramer(BalsaHeaders* headers, const char* ptr, + size_t size) { + headers->WriteFromFramer(ptr, size); + } +}; + +namespace { + +class BalsaBufferTest : public QuicheTest { + public: + void CreateBuffer(size_t blocksize) { + buffer_ = std::make_unique<BalsaBuffer>(blocksize); + } + void CreateBuffer() { buffer_ = std::make_unique<BalsaBuffer>(); } + static std::unique_ptr<BalsaBuffer> CreateUnmanagedBuffer(size_t blocksize) { + return std::make_unique<BalsaBuffer>(blocksize); + } + absl::string_view Write(absl::string_view sp, size_t* block_buffer_idx) { + if (sp.empty()) { + return sp; + } + char* storage = buffer_->Reserve(sp.size(), block_buffer_idx); + memcpy(storage, sp.data(), sp.size()); + return absl::string_view(storage, sp.size()); + } + + protected: + std::unique_ptr<BalsaBuffer> buffer_; +}; + +using BufferBlock = BalsaBuffer::BufferBlock; + +BufferBlock MakeBufferBlock(const std::string& s) { + // Make the buffer twice the size needed to verify that CopyFrom copies our + // buffer_size (as opposed to shrinking to fit or reusing an old buffer). + BufferBlock block{make_unique<char[]>(s.size()), s.size() * 2, s.size()}; + std::memcpy(block.buffer.get(), s.data(), s.size()); + return block; +} + +BalsaHeaders CreateHTTPHeaders(bool request, absl::string_view s) { + BalsaHeaders headers; + BalsaFrame framer; + framer.set_is_request(request); + framer.set_balsa_headers(&headers); + QUICHE_CHECK_EQ(s.size(), framer.ProcessInput(s.data(), s.size())); + QUICHE_CHECK(framer.MessageFullyRead()); + return headers; +} + +class BufferBlockTest + : public QuicheTestWithParam<std::tuple<const char*, const char*>> {}; + +TEST_P(BufferBlockTest, CopyFrom) { + const std::string s1 = std::get<0>(GetParam()); + const std::string s2 = std::get<1>(GetParam()); + BufferBlock block; + block.CopyFrom(MakeBufferBlock(s1)); + EXPECT_EQ(s1.size(), block.bytes_free); + ASSERT_EQ(2 * s1.size(), block.buffer_size); + EXPECT_EQ(0, memcmp(s1.data(), block.buffer.get(), s1.size())); + block.CopyFrom(MakeBufferBlock(s2)); + EXPECT_EQ(s2.size(), block.bytes_free); + ASSERT_EQ(2 * s2.size(), block.buffer_size); + EXPECT_EQ(0, memcmp(s2.data(), block.buffer.get(), s2.size())); +} + +const char* block_strings[] = {"short string", "longer than the other string"}; +INSTANTIATE_TEST_SUITE_P(VariousSizes, BufferBlockTest, + Combine(ValuesIn(block_strings), + ValuesIn(block_strings))); + +TEST_F(BalsaBufferTest, BlocksizeSet) { + CreateBuffer(); + EXPECT_EQ(BalsaBuffer::kDefaultBlocksize, buffer_->blocksize()); + CreateBuffer(1024); + EXPECT_EQ(1024u, buffer_->blocksize()); +} + +TEST_F(BalsaBufferTest, GetMemorySize) { + CreateBuffer(10); + EXPECT_EQ(0u, buffer_->GetTotalBytesUsed()); + EXPECT_EQ(0u, buffer_->GetTotalBufferBlockSize()); + BalsaBuffer::Blocks::size_type index; + buffer_->Reserve(1024, &index); + EXPECT_EQ(10u + 1024u, buffer_->GetTotalBufferBlockSize()); + EXPECT_EQ(1024u, buffer_->GetTotalBytesUsed()); +} + +TEST_F(BalsaBufferTest, ManyWritesToContiguousBuffer) { + CreateBuffer(0); + // The test is that the process completes. If it needs to do a resize on + // every write, it will timeout or run out of memory. + // ( 10 + 20 + 30 + ... + 1.2e6 bytes => ~1e11 bytes ) + std::string data = "0123456789"; + for (int i = 0; i < 120 * 1000; ++i) { + buffer_->WriteToContiguousBuffer(data); + } +} + +TEST_F(BalsaBufferTest, CopyFrom) { + CreateBuffer(10); + std::unique_ptr<BalsaBuffer> ptr = CreateUnmanagedBuffer(1024); + ASSERT_EQ(1024u, ptr->blocksize()); + EXPECT_EQ(0u, ptr->num_blocks()); + + std::string data1 = "foobarbaz01"; + buffer_->WriteToContiguousBuffer(data1); + buffer_->NoMoreWriteToContiguousBuffer(); + std::string data2 = "12345"; + Write(data2, nullptr); + std::string data3 = "6789"; + Write(data3, nullptr); + std::string data4 = "123456789012345"; + Write(data3, nullptr); + + ptr->CopyFrom(*buffer_); + + EXPECT_EQ(ptr->can_write_to_contiguous_buffer(), + buffer_->can_write_to_contiguous_buffer()); + ASSERT_EQ(ptr->num_blocks(), buffer_->num_blocks()); + for (size_t i = 0; i < buffer_->num_blocks(); ++i) { + ASSERT_EQ(ptr->bytes_used(i), buffer_->bytes_used(i)); + ASSERT_EQ(ptr->buffer_size(i), buffer_->buffer_size(i)); + EXPECT_EQ(0, + memcmp(ptr->GetPtr(i), buffer_->GetPtr(i), ptr->bytes_used(i))); + } +} + +TEST_F(BalsaBufferTest, ClearWorks) { + CreateBuffer(10); + + std::string data1 = "foobarbaz01"; + buffer_->WriteToContiguousBuffer(data1); + buffer_->NoMoreWriteToContiguousBuffer(); + std::string data2 = "12345"; + Write(data2, nullptr); + std::string data3 = "6789"; + Write(data3, nullptr); + std::string data4 = "123456789012345"; + Write(data3, nullptr); + + buffer_->Clear(); + + EXPECT_TRUE(buffer_->can_write_to_contiguous_buffer()); + EXPECT_EQ(10u, buffer_->blocksize()); + EXPECT_EQ(0u, buffer_->num_blocks()); +} + +TEST_F(BalsaBufferTest, ClearWorksWhenLargerThanBlocksize) { + CreateBuffer(10); + + std::string data1 = "foobarbaz01lkjasdlkjasdlkjasd"; + buffer_->WriteToContiguousBuffer(data1); + buffer_->NoMoreWriteToContiguousBuffer(); + std::string data2 = "12345"; + Write(data2, nullptr); + std::string data3 = "6789"; + Write(data3, nullptr); + std::string data4 = "123456789012345"; + Write(data3, nullptr); + + buffer_->Clear(); + + EXPECT_TRUE(buffer_->can_write_to_contiguous_buffer()); + EXPECT_EQ(10u, buffer_->blocksize()); + EXPECT_EQ(0u, buffer_->num_blocks()); +} + +TEST_F(BalsaBufferTest, ContiguousWriteSmallerThanBlocksize) { + CreateBuffer(1024); + + std::string data1 = "foo"; + buffer_->WriteToContiguousBuffer(data1); + std::string composite = data1; + const char* buf_ptr = buffer_->GetPtr(0); + ASSERT_LE(composite.size(), buffer_->buffer_size(0)); + EXPECT_EQ(0, memcmp(composite.data(), buf_ptr, composite.size())); + + std::string data2 = "barbaz"; + buffer_->WriteToContiguousBuffer(data2); + composite += data2; + buf_ptr = buffer_->GetPtr(0); + ASSERT_LE(composite.size(), buffer_->buffer_size(0)); + EXPECT_EQ(0, memcmp(composite.data(), buf_ptr, composite.size())); +} + +TEST_F(BalsaBufferTest, SingleContiguousWriteLargerThanBlocksize) { + CreateBuffer(10); + + std::string data1 = "abracadabrawords"; + buffer_->WriteToContiguousBuffer(data1); + std::string composite = data1; + const char* buf_ptr = buffer_->GetPtr(0); + ASSERT_LE(data1.size(), buffer_->buffer_size(0)); + EXPECT_EQ(0, memcmp(composite.data(), buf_ptr, composite.size())) + << composite << "\n" + << absl::string_view(buf_ptr, buffer_->bytes_used(0)); +} + +TEST_F(BalsaBufferTest, ContiguousWriteLargerThanBlocksize) { + CreateBuffer(10); + + std::string data1 = "123456789"; + buffer_->WriteToContiguousBuffer(data1); + std::string composite = data1; + ASSERT_LE(10u, buffer_->buffer_size(0)); + + std::string data2 = "0123456789"; + buffer_->WriteToContiguousBuffer(data2); + composite += data2; + + const char* buf_ptr = buffer_->GetPtr(0); + ASSERT_LE(composite.size(), buffer_->buffer_size(0)); + EXPECT_EQ(0, memcmp(composite.data(), buf_ptr, composite.size())) + << "composite: " << composite << "\n" + << " actual: " << absl::string_view(buf_ptr, buffer_->bytes_used(0)); +} + +TEST_F(BalsaBufferTest, TwoContiguousWritesLargerThanBlocksize) { + CreateBuffer(5); + + std::string data1 = "123456"; + buffer_->WriteToContiguousBuffer(data1); + std::string composite = data1; + ASSERT_LE(composite.size(), buffer_->buffer_size(0)); + + std::string data2 = "7890123"; + buffer_->WriteToContiguousBuffer(data2); + composite += data2; + + const char* buf_ptr = buffer_->GetPtr(0); + ASSERT_LE(composite.size(), buffer_->buffer_size(0)); + EXPECT_EQ(0, memcmp(composite.data(), buf_ptr, composite.size())) + << "composite: " << composite << "\n" + << " actual: " << absl::string_view(buf_ptr, buffer_->bytes_used(0)); +} + +TEST_F(BalsaBufferTest, WriteSmallerThanBlocksize) { + CreateBuffer(5); + std::string data1 = "1234"; + size_t block_idx = 0; + absl::string_view write_result = Write(data1, &block_idx); + ASSERT_EQ(1u, block_idx); + EXPECT_THAT(write_result, StrEq(data1)); + + CreateBuffer(5); + data1 = "1234"; + block_idx = 0; + write_result = Write(data1, &block_idx); + ASSERT_EQ(1u, block_idx); + EXPECT_THAT(write_result, StrEq(data1)); +} + +TEST_F(BalsaBufferTest, TwoWritesSmallerThanBlocksizeThenAnotherWrite) { + CreateBuffer(10); + std::string data1 = "12345"; + size_t block_idx = 0; + absl::string_view write_result = Write(data1, &block_idx); + ASSERT_EQ(1u, block_idx); + EXPECT_THAT(write_result, StrEq(data1)); + + std::string data2 = "data2"; + block_idx = 0; + write_result = Write(data2, &block_idx); + ASSERT_EQ(1u, block_idx); + EXPECT_THAT(write_result, StrEq(data2)); + + std::string data3 = "data3"; + block_idx = 0; + write_result = Write(data3, &block_idx); + ASSERT_EQ(2u, block_idx); + EXPECT_THAT(write_result, StrEq(data3)); + + CreateBuffer(10); + buffer_->NoMoreWriteToContiguousBuffer(); + data1 = "12345"; + block_idx = 0; + write_result = Write(data1, &block_idx); + ASSERT_EQ(0u, block_idx); + EXPECT_THAT(write_result, StrEq(data1)); + + data2 = "data2"; + block_idx = 0; + write_result = Write(data2, &block_idx); + ASSERT_EQ(0u, block_idx); + EXPECT_THAT(write_result, StrEq(data2)); + + data3 = "data3"; + block_idx = 0; + write_result = Write(data3, &block_idx); + ASSERT_EQ(1u, block_idx); + EXPECT_THAT(write_result, StrEq(data3)); +} + +TEST_F(BalsaBufferTest, WriteLargerThanBlocksize) { + CreateBuffer(5); + std::string data1 = "123456789"; + size_t block_idx = 0; + absl::string_view write_result = Write(data1, &block_idx); + ASSERT_EQ(1u, block_idx); + EXPECT_THAT(write_result, StrEq(data1)); + + CreateBuffer(5); + buffer_->NoMoreWriteToContiguousBuffer(); + data1 = "123456789"; + block_idx = 0; + write_result = Write(data1, &block_idx); + ASSERT_EQ(1u, block_idx); + EXPECT_THAT(write_result, StrEq(data1)); +} + +TEST_F(BalsaBufferTest, ContiguousThenTwoSmallerThanBlocksize) { + CreateBuffer(5); + std::string data1 = "1234567890"; + buffer_->WriteToContiguousBuffer(data1); + size_t block_idx = 0; + std::string data2 = "1234"; + absl::string_view write_result = Write(data2, &block_idx); + ASSERT_EQ(1u, block_idx); + std::string data3 = "1234"; + write_result = Write(data3, &block_idx); + ASSERT_EQ(2u, block_idx); +} + +TEST_F(BalsaBufferTest, AccessFirstBlockUninitialized) { + CreateBuffer(5); + EXPECT_EQ(0u, buffer_->GetReadableBytesOfFirstBlock()); + EXPECT_DFATAL(buffer_->StartOfFirstBlock(), "First block not allocated yet!"); + EXPECT_DFATAL(buffer_->EndOfFirstBlock(), "First block not allocated yet!"); +} + +TEST_F(BalsaBufferTest, AccessFirstBlockInitialized) { + CreateBuffer(5); + std::string data1 = "1234567890"; + buffer_->WriteToContiguousBuffer(data1); + const char* start = buffer_->StartOfFirstBlock(); + EXPECT_TRUE(start != nullptr); + const char* end = buffer_->EndOfFirstBlock(); + EXPECT_TRUE(end != nullptr); + EXPECT_EQ(data1.length(), static_cast<size_t>(end - start)); + EXPECT_EQ(data1.length(), buffer_->GetReadableBytesOfFirstBlock()); +} + +TEST(BalsaHeaders, CanAssignBeginToIterator) { + { + BalsaHeaders header; + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + static_cast<void>(chli); + } + { + const BalsaHeaders header; + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + static_cast<void>(chli); + } +} + +TEST(BalsaHeaders, CanAssignEndToIterator) { + { + BalsaHeaders header; + BalsaHeaders::const_header_lines_iterator chli = header.lines().end(); + static_cast<void>(chli); + } + { + const BalsaHeaders header; + BalsaHeaders::const_header_lines_iterator chli = header.lines().end(); + static_cast<void>(chli); + } +} + +TEST(BalsaHeaders, ReplaceOrAppendHeaderTestAppending) { + BalsaHeaders header; + std::string key_1 = "key_1"; + std::string value_1 = "value_1"; + header.ReplaceOrAppendHeader(key_1, value_1); + + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + ASSERT_EQ(absl::string_view("key_1"), chli->first); + ASSERT_EQ(absl::string_view("value_1"), chli->second); + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_EQ(header.lines().end(), chli); +} + +TEST(BalsaHeaders, ReplaceOrAppendHeaderTestReplacing) { + BalsaHeaders header; + std::string key_1 = "key_1"; + std::string value_1 = "value_1"; + std::string key_2 = "key_2"; + header.ReplaceOrAppendHeader(key_1, value_1); + header.ReplaceOrAppendHeader(key_2, value_1); + std::string value_2 = "value_2_string"; + header.ReplaceOrAppendHeader(key_1, value_2); + + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + ASSERT_EQ(key_1, chli->first); + ASSERT_EQ(value_2, chli->second); + ++chli; + ASSERT_EQ(key_2, chli->first); + ASSERT_EQ(value_1, chli->second); + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_EQ(header.lines().end(), chli); +} + +TEST(BalsaHeaders, ReplaceOrAppendHeaderTestReplacingMultiple) { + BalsaHeaders header; + std::string key_1 = "key_1"; + std::string key_2 = "key_2"; + std::string value_1 = "val_1"; + std::string value_2 = "val_2"; + std::string value_3 = + "value_3_is_longer_than_value_1_and_value_2_and_their_keys"; + // Set up header keys 1, 1, 2. We will replace the value of key 1 with a long + // enough string that it should be moved to the end. This regression tests + // that replacement works if we move the header to the end. + header.AppendHeader(key_1, value_1); + header.AppendHeader(key_1, value_2); + header.AppendHeader(key_2, value_1); + header.ReplaceOrAppendHeader(key_1, value_3); + + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + ASSERT_EQ(key_1, chli->first); + ASSERT_EQ(value_3, chli->second); + ++chli; + ASSERT_EQ(key_2, chli->first); + ASSERT_EQ(value_1, chli->second); + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_EQ(header.lines().end(), chli); + + // Now test that replacement works with a shorter value, so that if we ever do + // in-place replacement it's tested. + header.ReplaceOrAppendHeader(key_1, value_1); + chli = header.lines().begin(); + ASSERT_EQ(key_1, chli->first); + ASSERT_EQ(value_1, chli->second); + ++chli; + ASSERT_EQ(key_2, chli->first); + ASSERT_EQ(value_1, chli->second); + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_EQ(header.lines().end(), chli); +} + +TEST(BalsaHeaders, AppendHeaderAndIteratorTest1) { + BalsaHeaders header; + ASSERT_EQ(header.lines().begin(), header.lines().end()); + { + std::string key_1 = "key_1"; + std::string value_1 = "value_1"; + header.AppendHeader(key_1, value_1); + key_1 = "garbage"; + value_1 = "garbage"; + } + + ASSERT_NE(header.lines().begin(), header.lines().end()); + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + ASSERT_EQ(header.lines().begin(), chli); + ASSERT_NE(header.lines().end(), chli); + ASSERT_EQ(absl::string_view("key_1"), chli->first); + ASSERT_EQ(absl::string_view("value_1"), chli->second); + + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_EQ(header.lines().end(), chli); +} + +TEST(BalsaHeaders, AppendHeaderAndIteratorTest2) { + BalsaHeaders header; + ASSERT_EQ(header.lines().begin(), header.lines().end()); + { + std::string key_1 = "key_1"; + std::string value_1 = "value_1"; + header.AppendHeader(key_1, value_1); + key_1 = "garbage"; + value_1 = "garbage"; + } + { + std::string key_2 = "key_2"; + std::string value_2 = "value_2"; + header.AppendHeader(key_2, value_2); + key_2 = "garbage"; + value_2 = "garbage"; + } + + ASSERT_NE(header.lines().begin(), header.lines().end()); + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + ASSERT_EQ(header.lines().begin(), chli); + ASSERT_NE(header.lines().end(), chli); + ASSERT_EQ(absl::string_view("key_1"), chli->first); + ASSERT_EQ(absl::string_view("value_1"), chli->second); + + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_NE(header.lines().end(), chli); + ASSERT_EQ(absl::string_view("key_2"), chli->first); + ASSERT_EQ(absl::string_view("value_2"), chli->second); + + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_EQ(header.lines().end(), chli); +} + +TEST(BalsaHeaders, AppendHeaderAndIteratorTest3) { + BalsaHeaders header; + ASSERT_EQ(header.lines().begin(), header.lines().end()); + { + std::string key_1 = "key_1"; + std::string value_1 = "value_1"; + header.AppendHeader(key_1, value_1); + key_1 = "garbage"; + value_1 = "garbage"; + } + { + std::string key_2 = "key_2"; + std::string value_2 = "value_2"; + header.AppendHeader(key_2, value_2); + key_2 = "garbage"; + value_2 = "garbage"; + } + { + std::string key_3 = "key_3"; + std::string value_3 = "value_3"; + header.AppendHeader(key_3, value_3); + key_3 = "garbage"; + value_3 = "garbage"; + } + + ASSERT_NE(header.lines().begin(), header.lines().end()); + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + ASSERT_EQ(header.lines().begin(), chli); + ASSERT_NE(header.lines().end(), chli); + ASSERT_EQ(absl::string_view("key_1"), chli->first); + ASSERT_EQ(absl::string_view("value_1"), chli->second); + + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_NE(header.lines().end(), chli); + ASSERT_EQ(absl::string_view("key_2"), chli->first); + ASSERT_EQ(absl::string_view("value_2"), chli->second); + + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_NE(header.lines().end(), chli); + ASSERT_EQ(absl::string_view("key_3"), chli->first); + ASSERT_EQ(absl::string_view("value_3"), chli->second); + + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_EQ(header.lines().end(), chli); +} + +TEST(BalsaHeaders, AppendHeaderAndTestEraseWithIterator) { + BalsaHeaders header; + ASSERT_EQ(header.lines().begin(), header.lines().end()); + { + std::string key_1 = "key_1"; + std::string value_1 = "value_1"; + header.AppendHeader(key_1, value_1); + key_1 = "garbage"; + value_1 = "garbage"; + } + { + std::string key_2 = "key_2"; + std::string value_2 = "value_2"; + header.AppendHeader(key_2, value_2); + key_2 = "garbage"; + value_2 = "garbage"; + } + { + std::string key_3 = "key_3"; + std::string value_3 = "value_3"; + header.AppendHeader(key_3, value_3); + key_3 = "garbage"; + value_3 = "garbage"; + } + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + ++chli; // should now point to key_2. + ASSERT_EQ(absl::string_view("key_2"), chli->first); + header.erase(chli); + chli = header.lines().begin(); + + ASSERT_NE(header.lines().begin(), header.lines().end()); + ASSERT_EQ(header.lines().begin(), chli); + ASSERT_NE(header.lines().end(), chli); + ASSERT_EQ(absl::string_view("key_1"), chli->first); + ASSERT_EQ(absl::string_view("value_1"), chli->second); + + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_NE(header.lines().end(), chli); + ASSERT_EQ(absl::string_view("key_3"), chli->first); + ASSERT_EQ(absl::string_view("value_3"), chli->second); + + ++chli; + ASSERT_NE(header.lines().begin(), chli); + ASSERT_EQ(header.lines().end(), chli); +} + +TEST(BalsaHeaders, TestSetFirstlineInAdditionalBuffer) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + ASSERT_THAT(headers.first_line(), StrEq("GET / HTTP/1.0")); +} + +TEST(BalsaHeaders, TestSetFirstlineInOriginalBufferAndIsShorterThanOriginal) { + BalsaHeaders headers = CreateHTTPHeaders(true, + "GET /foobar HTTP/1.0\r\n" + "\r\n"); + ASSERT_THAT(headers.first_line(), StrEq("GET /foobar HTTP/1.0")); + // Note that this SetRequestFirstlineFromStringPieces should replace the + // original one in the -non- 'additional' buffer. + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + ASSERT_THAT(headers.first_line(), StrEq("GET / HTTP/1.0")); +} + +TEST(BalsaHeaders, TestSetFirstlineInOriginalBufferAndIsLongerThanOriginal) { + // Similar to above, but this time the new firstline is larger than + // the original, yet it should still fit into the original -non- + // 'additional' buffer as the first header-line has been erased. + BalsaHeaders headers = CreateHTTPHeaders(true, + "GET / HTTP/1.0\r\n" + "some_key: some_value\r\n" + "another_key: another_value\r\n" + "\r\n"); + ASSERT_THAT(headers.first_line(), StrEq("GET / HTTP/1.0")); + headers.erase(headers.lines().begin()); + // Note that this SetRequestFirstlineFromStringPieces should replace the + // original one in the -non- 'additional' buffer. + headers.SetRequestFirstlineFromStringPieces("GET", "/foobar", "HTTP/1.0"); + ASSERT_THAT(headers.first_line(), StrEq("GET /foobar HTTP/1.0")); +} + +TEST(BalsaHeaders, TestSetFirstlineInAdditionalDataAndIsShorterThanOriginal) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/foobar", "HTTP/1.0"); + ASSERT_THAT(headers.first_line(), StrEq("GET /foobar HTTP/1.0")); + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + ASSERT_THAT(headers.first_line(), StrEq("GET / HTTP/1.0")); +} + +TEST(BalsaHeaders, TestSetFirstlineInAdditionalDataAndIsLongerThanOriginal) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + ASSERT_THAT(headers.first_line(), StrEq("GET / HTTP/1.0")); + headers.SetRequestFirstlineFromStringPieces("GET", "/foobar", "HTTP/1.0"); + ASSERT_THAT(headers.first_line(), StrEq("GET /foobar HTTP/1.0")); +} + +TEST(BalsaHeaders, TestDeletingSubstring) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("key1", "value1"); + headers.AppendHeader("key2", "value2"); + headers.AppendHeader("key", "value"); + headers.AppendHeader("unrelated", "value"); + + // RemoveAllOfHeader should not delete key1 or key2 given a substring. + headers.RemoveAllOfHeader("key"); + EXPECT_TRUE(headers.HasHeader("key1")); + EXPECT_TRUE(headers.HasHeader("key2")); + EXPECT_TRUE(headers.HasHeader("unrelated")); + EXPECT_FALSE(headers.HasHeader("key")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("key")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("KeY")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("UNREL")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("key3")); + + EXPECT_FALSE(headers.GetHeader("key1").empty()); + EXPECT_FALSE(headers.GetHeader("KEY1").empty()); + EXPECT_FALSE(headers.GetHeader("key2").empty()); + EXPECT_FALSE(headers.GetHeader("unrelated").empty()); + EXPECT_TRUE(headers.GetHeader("key").empty()); + + // Add key back in. + headers.AppendHeader("key", ""); + EXPECT_TRUE(headers.HasHeader("key")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("key")); + EXPECT_TRUE(headers.GetHeader("key").empty()); + + // RemoveAllHeadersWithPrefix should delete everything starting with key. + headers.RemoveAllHeadersWithPrefix("key"); + EXPECT_FALSE(headers.HasHeader("key1")); + EXPECT_FALSE(headers.HasHeader("key2")); + EXPECT_TRUE(headers.HasHeader("unrelated")); + EXPECT_FALSE(headers.HasHeader("key")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("key")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("key1")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("key2")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("kEy")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("unrelated")); + + EXPECT_TRUE(headers.GetHeader("key1").empty()); + EXPECT_TRUE(headers.GetHeader("key2").empty()); + EXPECT_FALSE(headers.GetHeader("unrelated").empty()); + EXPECT_TRUE(headers.GetHeader("key").empty()); +} + +TEST(BalsaHeaders, TestRemovingValues) { + // Remove entire line from headers, twice. Ensures working line-skipping. + // Skip consideration of a line whose key is larger than our search key. + // Skip consideration of a line whose key is smaller than our search key. + // Skip consideration of a line that is already marked for skipping. + // Skip consideration of a line whose value is too small. + // Skip consideration of a line whose key is correct in length but doesn't + // match. + { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("hi", "hello"); + headers.AppendHeader("key1", "val1"); + headers.AppendHeader("key1", "value2"); + headers.AppendHeader("key1", "value3"); + headers.AppendHeader("key2", "value4"); + headers.AppendHeader("unrelated", "value"); + + EXPECT_EQ(0u, headers.RemoveValue("key1", "")); + EXPECT_EQ(1u, headers.RemoveValue("key1", "value2")); + + std::string key1_vals = headers.GetAllOfHeaderAsString("key1"); + EXPECT_THAT(key1_vals, StrEq("val1,value3")); + + EXPECT_TRUE(headers.HeaderHasValue("key1", "val1")); + EXPECT_TRUE(headers.HeaderHasValue("key1", "value3")); + EXPECT_EQ("value4", headers.GetHeader("key2")); + EXPECT_EQ("hello", headers.GetHeader("hi")); + EXPECT_EQ("value", headers.GetHeader("unrelated")); + EXPECT_FALSE(headers.HeaderHasValue("key1", "value2")); + + EXPECT_EQ(1u, headers.RemoveValue("key1", "value3")); + + key1_vals = headers.GetAllOfHeaderAsString("key1"); + EXPECT_THAT(key1_vals, StrEq("val1")); + + EXPECT_TRUE(headers.HeaderHasValue("key1", "val1")); + EXPECT_EQ("value4", headers.GetHeader("key2")); + EXPECT_EQ("hello", headers.GetHeader("hi")); + EXPECT_EQ("value", headers.GetHeader("unrelated")); + EXPECT_FALSE(headers.HeaderHasValue("key1", "value3")); + EXPECT_FALSE(headers.HeaderHasValue("key1", "value2")); + } + + // Remove/keep values with surrounding spaces. + // Remove values from in between others in multi-value line. + // Remove entire multi-value line. + // Keep value in between removed values in multi-value line. + // Keep trailing value that is too small to be matched after removing a match. + // Keep value containing matched value (partial but not complete match). + // Keep an empty header. + { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("key1", "value1"); + headers.AppendHeader("key1", "value2, value3,value2"); + headers.AppendHeader("key1", "value4 ,value2,value5,val6"); + headers.AppendHeader("key1", "value2, value2 , value2"); + headers.AppendHeader("key1", " value2 , value2 "); + headers.AppendHeader("key1", " value2 a"); + headers.AppendHeader("key1", ""); + headers.AppendHeader("key1", ", ,,"); + headers.AppendHeader("unrelated", "value"); + + EXPECT_EQ(8u, headers.RemoveValue("key1", "value2")); + + std::string key1_vals = headers.GetAllOfHeaderAsString("key1"); + EXPECT_THAT(key1_vals, + StrEq("value1,value3,value4 ,value5,val6,value2 a,,, ,,")); + + EXPECT_EQ("value", headers.GetHeader("unrelated")); + EXPECT_TRUE(headers.HeaderHasValue("key1", "value1")); + EXPECT_TRUE(headers.HeaderHasValue("key1", "value3")); + EXPECT_TRUE(headers.HeaderHasValue("key1", "value4")); + EXPECT_TRUE(headers.HeaderHasValue("key1", "value5")); + EXPECT_TRUE(headers.HeaderHasValue("key1", "val6")); + EXPECT_FALSE(headers.HeaderHasValue("key1", "value2")); + } + + { + const absl::string_view key("key"); + const absl::string_view value1("foo\0bar", 7); + const absl::string_view value2("value2"); + const std::string value = absl::StrCat(value1, ",", value2); + + { + BalsaHeaders headers; + headers.AppendHeader(key, value); + + EXPECT_TRUE(headers.HeaderHasValue(key, value1)); + EXPECT_TRUE(headers.HeaderHasValue(key, value2)); + EXPECT_EQ(value, headers.GetAllOfHeaderAsString(key)); + + EXPECT_EQ(1u, headers.RemoveValue(key, value2)); + + EXPECT_TRUE(headers.HeaderHasValue(key, value1)); + EXPECT_FALSE(headers.HeaderHasValue(key, value2)); + EXPECT_EQ(value1, headers.GetAllOfHeaderAsString(key)); + } + + { + BalsaHeaders headers; + headers.AppendHeader(key, value1); + headers.AppendHeader(key, value2); + + EXPECT_TRUE(headers.HeaderHasValue(key, value1)); + EXPECT_TRUE(headers.HeaderHasValue(key, value2)); + EXPECT_EQ(value, headers.GetAllOfHeaderAsString(key)); + + EXPECT_EQ(1u, headers.RemoveValue(key, value2)); + + EXPECT_TRUE(headers.HeaderHasValue(key, value1)); + EXPECT_FALSE(headers.HeaderHasValue(key, value2)); + EXPECT_EQ(value1, headers.GetAllOfHeaderAsString(key)); + } + } +} + +TEST(BalsaHeaders, ZeroAppendToHeaderWithCommaAndSpace) { + // Create an initial header with zero 'X-Forwarded-For' headers. + BalsaHeaders headers = CreateHTTPHeaders(true, + "GET / HTTP/1.0\r\n" + "\r\n"); + + // Use AppendToHeaderWithCommaAndSpace to add 4 new 'X-Forwarded-For' headers. + // Appending these headers should preserve the order in which they are added. + // i.e. 1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4 + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "1.1.1.1"); + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "2.2.2.2"); + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "3.3.3.3"); + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "4.4.4.4"); + + // Fetch the 'X-Forwarded-For' headers and compare them to the expected order. + EXPECT_THAT(headers.GetAllOfHeader("X-Forwarded-For"), + ElementsAre("1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4")); +} + +TEST(BalsaHeaders, SingleAppendToHeaderWithCommaAndSpace) { + // Create an initial header with one 'X-Forwarded-For' header. + BalsaHeaders headers = CreateHTTPHeaders(true, + "GET / HTTP/1.0\r\n" + "X-Forwarded-For: 1.1.1.1\r\n" + "\r\n"); + + // Use AppendToHeaderWithCommaAndSpace to add 4 new 'X-Forwarded-For' headers. + // Appending these headers should preserve the order in which they are added. + // i.e. 1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4, 5.5.5.5 + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "2.2.2.2"); + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "3.3.3.3"); + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "4.4.4.4"); + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "5.5.5.5"); + + // Fetch the 'X-Forwarded-For' headers and compare them to the expected order. + EXPECT_THAT(headers.GetAllOfHeader("X-Forwarded-For"), + ElementsAre("1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4, 5.5.5.5")); +} + +TEST(BalsaHeaders, MultipleAppendToHeaderWithCommaAndSpace) { + // Create an initial header with two 'X-Forwarded-For' headers. + BalsaHeaders headers = CreateHTTPHeaders(true, + "GET / HTTP/1.0\r\n" + "X-Forwarded-For: 1.1.1.1\r\n" + "X-Forwarded-For: 2.2.2.2\r\n" + "\r\n"); + + // Use AppendToHeaderWithCommaAndSpace to add 4 new 'X-Forwarded-For' headers. + // Appending these headers should preserve the order in which they are added. + // i.e. 1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4, 5.5.5.5, 6.6.6.6 + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "3.3.3.3"); + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "4.4.4.4"); + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "5.5.5.5"); + headers.AppendToHeaderWithCommaAndSpace("X-Forwarded-For", "6.6.6.6"); + + // Fetch the 'X-Forwarded-For' headers and compare them to the expected order. + EXPECT_THAT( + headers.GetAllOfHeader("X-Forwarded-For"), + ElementsAre("1.1.1.1", "2.2.2.2, 3.3.3.3, 4.4.4.4, 5.5.5.5, 6.6.6.6")); +} + +TEST(BalsaHeaders, HeaderHasValues) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + // Make sure we find values at the beginning, middle, and end, and we handle + // multiple .find() calls correctly. + headers.AppendHeader("key", "val1,val2val2,val2,val3"); + // Make sure we don't mess up comma/boundary checks for beginning, middle and + // end. + headers.AppendHeader("key", "val4val5val6"); + headers.AppendHeader("key", "val11 val12"); + headers.AppendHeader("key", "v val13"); + // Make sure we catch the line header + headers.AppendHeader("key", "val7"); + // Make sure there's no out-of-bounds indexing on an empty line. + headers.AppendHeader("key", ""); + // Make sure it works when there's spaces before or after a comma. + headers.AppendHeader("key", "val8 , val9 , val10"); + // Make sure it works when val is surrounded by spaces. + headers.AppendHeader("key", " val14 "); + // Make sure other keys aren't used. + headers.AppendHeader("key2", "val15"); + // Mixed case. + headers.AppendHeader("key", "Val16"); + headers.AppendHeader("key", "foo, Val17, bar"); + + // All case-sensitive. + EXPECT_TRUE(headers.HeaderHasValue("key", "val1")); + EXPECT_TRUE(headers.HeaderHasValue("key", "val2")); + EXPECT_TRUE(headers.HeaderHasValue("key", "val3")); + EXPECT_TRUE(headers.HeaderHasValue("key", "val7")); + EXPECT_TRUE(headers.HeaderHasValue("key", "val8")); + EXPECT_TRUE(headers.HeaderHasValue("key", "val9")); + EXPECT_TRUE(headers.HeaderHasValue("key", "val10")); + EXPECT_TRUE(headers.HeaderHasValue("key", "val14")); + EXPECT_FALSE(headers.HeaderHasValue("key", "val4")); + EXPECT_FALSE(headers.HeaderHasValue("key", "val5")); + EXPECT_FALSE(headers.HeaderHasValue("key", "val6")); + EXPECT_FALSE(headers.HeaderHasValue("key", "val11")); + EXPECT_FALSE(headers.HeaderHasValue("key", "val12")); + EXPECT_FALSE(headers.HeaderHasValue("key", "val13")); + EXPECT_FALSE(headers.HeaderHasValue("key", "val15")); + EXPECT_FALSE(headers.HeaderHasValue("key", "val16")); + EXPECT_FALSE(headers.HeaderHasValue("key", "val17")); + + // All case-insensitive, only change is for val16 and val17. + EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val1")); + EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val2")); + EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val3")); + EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val7")); + EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val8")); + EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val9")); + EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val10")); + EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val14")); + EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val4")); + EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val5")); + EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val6")); + EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val11")); + EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val12")); + EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val13")); + EXPECT_FALSE(headers.HeaderHasValueIgnoreCase("key", "val15")); + EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val16")); + EXPECT_TRUE(headers.HeaderHasValueIgnoreCase("key", "val17")); +} + +// Because we're dealing with one giant buffer, make sure we don't go beyond +// the bounds of the key when doing compares! +TEST(BalsaHeaders, TestNotDeletingBeyondString) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("key1", "value1"); + + headers.RemoveAllHeadersWithPrefix("key1: value1"); + EXPECT_NE(headers.lines().begin(), headers.lines().end()); +} + +TEST(BalsaHeaders, TestIteratingOverErasedHeaders) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("key1", "value1"); + headers.AppendHeader("key2", "value2"); + headers.AppendHeader("key3", "value3"); + headers.AppendHeader("key4", "value4"); + headers.AppendHeader("key5", "value5"); + headers.AppendHeader("key6", "value6"); + + headers.RemoveAllOfHeader("key6"); + headers.RemoveAllOfHeader("key5"); + headers.RemoveAllOfHeader("key4"); + + BalsaHeaders::const_header_lines_iterator chli = headers.lines().begin(); + EXPECT_NE(headers.lines().end(), chli); + EXPECT_EQ(headers.lines().begin(), chli); + EXPECT_THAT(chli->first, StrEq("key1")); + EXPECT_THAT(chli->second, StrEq("value1")); + + ++chli; + EXPECT_NE(headers.lines().end(), chli); + EXPECT_NE(headers.lines().begin(), chli); + EXPECT_THAT(chli->first, StrEq("key2")); + EXPECT_THAT(chli->second, StrEq("value2")); + + ++chli; + EXPECT_NE(headers.lines().end(), chli); + EXPECT_NE(headers.lines().begin(), chli); + EXPECT_THAT(chli->first, StrEq("key3")); + EXPECT_THAT(chli->second, StrEq("value3")); + + ++chli; + EXPECT_EQ(headers.lines().end(), chli); + EXPECT_NE(headers.lines().begin(), chli); + + headers.RemoveAllOfHeader("key1"); + headers.RemoveAllOfHeader("key2"); + chli = headers.lines().begin(); + EXPECT_THAT(chli->first, StrEq("key3")); + EXPECT_THAT(chli->second, StrEq("value3")); + EXPECT_NE(headers.lines().end(), chli); + EXPECT_EQ(headers.lines().begin(), chli); + + ++chli; + EXPECT_EQ(headers.lines().end(), chli); + EXPECT_NE(headers.lines().begin(), chli); +} + +TEST(BalsaHeaders, CanCompareIterators) { + BalsaHeaders header; + ASSERT_EQ(header.lines().begin(), header.lines().end()); + { + std::string key_1 = "key_1"; + std::string value_1 = "value_1"; + header.AppendHeader(key_1, value_1); + key_1 = "garbage"; + value_1 = "garbage"; + } + { + std::string key_2 = "key_2"; + std::string value_2 = "value_2"; + header.AppendHeader(key_2, value_2); + key_2 = "garbage"; + value_2 = "garbage"; + } + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + BalsaHeaders::const_header_lines_iterator chlj = header.lines().begin(); + EXPECT_EQ(chli, chlj); + ++chlj; + EXPECT_NE(chli, chlj); + EXPECT_LT(chli, chlj); + EXPECT_LE(chli, chlj); + EXPECT_LE(chli, chli); + EXPECT_GT(chlj, chli); + EXPECT_GE(chlj, chli); + EXPECT_GE(chlj, chlj); +} + +TEST(BalsaHeaders, AppendHeaderAndTestThatYouCanEraseEverything) { + BalsaHeaders header; + ASSERT_EQ(header.lines().begin(), header.lines().end()); + { + std::string key_1 = "key_1"; + std::string value_1 = "value_1"; + header.AppendHeader(key_1, value_1); + key_1 = "garbage"; + value_1 = "garbage"; + } + { + std::string key_2 = "key_2"; + std::string value_2 = "value_2"; + header.AppendHeader(key_2, value_2); + key_2 = "garbage"; + value_2 = "garbage"; + } + { + std::string key_3 = "key_3"; + std::string value_3 = "value_3"; + header.AppendHeader(key_3, value_3); + key_3 = "garbage"; + value_3 = "garbage"; + } + EXPECT_NE(header.lines().begin(), header.lines().end()); + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + while (chli != header.lines().end()) { + header.erase(chli); + chli = header.lines().begin(); + } + ASSERT_EQ(header.lines().begin(), header.lines().end()); +} + +TEST(BalsaHeaders, GetHeaderPositionWorksAsExpectedWithNoHeaderLines) { + BalsaHeaders header; + BalsaHeaders::const_header_lines_iterator i = header.GetHeaderPosition("foo"); + EXPECT_EQ(i, header.lines().end()); +} + +TEST(BalsaHeaders, GetHeaderPositionWorksAsExpectedWithBalsaFrameProcessInput) { + BalsaHeaders headers = CreateHTTPHeaders( + true, + "GET / HTTP/1.0\r\n" + "key1: value_1\r\n" + "key1: value_foo\r\n" // this one cannot be fetched via GetHeader + "key2: value_2\r\n" + "key3: value_3\r\n" + "a: value_a\r\n" + "b: value_b\r\n" + "\r\n"); + + BalsaHeaders::const_header_lines_iterator header_position_b = + headers.GetHeaderPosition("b"); + ASSERT_NE(header_position_b, headers.lines().end()); + absl::string_view header_key_b_value = header_position_b->second; + ASSERT_FALSE(header_key_b_value.empty()); + EXPECT_EQ(std::string("value_b"), header_key_b_value); + + BalsaHeaders::const_header_lines_iterator header_position_1 = + headers.GetHeaderPosition("key1"); + ASSERT_NE(header_position_1, headers.lines().end()); + absl::string_view header_key_1_value = header_position_1->second; + ASSERT_FALSE(header_key_1_value.empty()); + EXPECT_EQ(std::string("value_1"), header_key_1_value); + + BalsaHeaders::const_header_lines_iterator header_position_3 = + headers.GetHeaderPosition("key3"); + ASSERT_NE(header_position_3, headers.lines().end()); + absl::string_view header_key_3_value = header_position_3->second; + ASSERT_FALSE(header_key_3_value.empty()); + EXPECT_EQ(std::string("value_3"), header_key_3_value); + + BalsaHeaders::const_header_lines_iterator header_position_2 = + headers.GetHeaderPosition("key2"); + ASSERT_NE(header_position_2, headers.lines().end()); + absl::string_view header_key_2_value = header_position_2->second; + ASSERT_FALSE(header_key_2_value.empty()); + EXPECT_EQ(std::string("value_2"), header_key_2_value); + + BalsaHeaders::const_header_lines_iterator header_position_a = + headers.GetHeaderPosition("a"); + ASSERT_NE(header_position_a, headers.lines().end()); + absl::string_view header_key_a_value = header_position_a->second; + ASSERT_FALSE(header_key_a_value.empty()); + EXPECT_EQ(std::string("value_a"), header_key_a_value); +} + +TEST(BalsaHeaders, GetHeaderWorksAsExpectedWithNoHeaderLines) { + BalsaHeaders header; + absl::string_view value = header.GetHeader("foo"); + EXPECT_TRUE(value.empty()); + value = header.GetHeader(""); + EXPECT_TRUE(value.empty()); +} + +TEST(BalsaHeaders, HasHeaderWorksAsExpectedWithNoHeaderLines) { + BalsaHeaders header; + EXPECT_FALSE(header.HasHeader("foo")); + EXPECT_FALSE(header.HasHeader("")); + EXPECT_FALSE(header.HasHeadersWithPrefix("foo")); + EXPECT_FALSE(header.HasHeadersWithPrefix("")); +} + +TEST(BalsaHeaders, HasHeaderWorksAsExpectedWithBalsaFrameProcessInput) { + BalsaHeaders headers = CreateHTTPHeaders(true, + "GET / HTTP/1.0\r\n" + "key1: value_1\r\n" + "key1: value_foo\r\n" + "key2:\r\n" + "\r\n"); + + EXPECT_FALSE(headers.HasHeader("foo")); + EXPECT_TRUE(headers.HasHeader("key1")); + EXPECT_TRUE(headers.HasHeader("key2")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("foo")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("key")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("KEY")); +} + +TEST(BalsaHeaders, GetHeaderWorksAsExpectedWithBalsaFrameProcessInput) { + BalsaHeaders headers = CreateHTTPHeaders( + true, + "GET / HTTP/1.0\r\n" + "key1: value_1\r\n" + "key1: value_foo\r\n" // this one cannot be fetched via GetHeader + "key2: value_2\r\n" + "key3: value_3\r\n" + "key4:\r\n" + "a: value_a\r\n" + "b: value_b\r\n" + "\r\n"); + + absl::string_view header_key_b_value = headers.GetHeader("b"); + ASSERT_FALSE(header_key_b_value.empty()); + EXPECT_EQ(std::string("value_b"), header_key_b_value); + + absl::string_view header_key_1_value = headers.GetHeader("key1"); + ASSERT_FALSE(header_key_1_value.empty()); + EXPECT_EQ(std::string("value_1"), header_key_1_value); + + absl::string_view header_key_3_value = headers.GetHeader("key3"); + ASSERT_FALSE(header_key_3_value.empty()); + EXPECT_EQ(std::string("value_3"), header_key_3_value); + + absl::string_view header_key_2_value = headers.GetHeader("key2"); + ASSERT_FALSE(header_key_2_value.empty()); + EXPECT_EQ(std::string("value_2"), header_key_2_value); + + absl::string_view header_key_a_value = headers.GetHeader("a"); + ASSERT_FALSE(header_key_a_value.empty()); + EXPECT_EQ(std::string("value_a"), header_key_a_value); + + EXPECT_TRUE(headers.GetHeader("key4").empty()); +} + +TEST(BalsaHeaders, GetHeaderWorksAsExpectedWithAppendHeader) { + BalsaHeaders header; + + header.AppendHeader("key1", "value_1"); + // note that this (following) one cannot be found using GetHeader. + header.AppendHeader("key1", "value_2"); + header.AppendHeader("key2", "value_2"); + header.AppendHeader("key3", "value_3"); + header.AppendHeader("a", "value_a"); + header.AppendHeader("b", "value_b"); + + absl::string_view header_key_b_value = header.GetHeader("b"); + absl::string_view header_key_1_value = header.GetHeader("key1"); + absl::string_view header_key_3_value = header.GetHeader("key3"); + absl::string_view header_key_2_value = header.GetHeader("key2"); + absl::string_view header_key_a_value = header.GetHeader("a"); + + ASSERT_FALSE(header_key_1_value.empty()); + ASSERT_FALSE(header_key_2_value.empty()); + ASSERT_FALSE(header_key_3_value.empty()); + ASSERT_FALSE(header_key_a_value.empty()); + ASSERT_FALSE(header_key_b_value.empty()); + + EXPECT_TRUE(header.HasHeader("key1")); + EXPECT_TRUE(header.HasHeader("key2")); + EXPECT_TRUE(header.HasHeader("key3")); + EXPECT_TRUE(header.HasHeader("a")); + EXPECT_TRUE(header.HasHeader("b")); + + EXPECT_TRUE(header.HasHeadersWithPrefix("key1")); + EXPECT_TRUE(header.HasHeadersWithPrefix("key2")); + EXPECT_TRUE(header.HasHeadersWithPrefix("key3")); + EXPECT_TRUE(header.HasHeadersWithPrefix("a")); + EXPECT_TRUE(header.HasHeadersWithPrefix("b")); + + EXPECT_EQ(std::string("value_1"), header_key_1_value); + EXPECT_EQ(std::string("value_2"), header_key_2_value); + EXPECT_EQ(std::string("value_3"), header_key_3_value); + EXPECT_EQ(std::string("value_a"), header_key_a_value); + EXPECT_EQ(std::string("value_b"), header_key_b_value); +} + +TEST(BalsaHeaders, HasHeaderWorksAsExpectedWithAppendHeader) { + BalsaHeaders header; + + ASSERT_FALSE(header.HasHeader("key1")); + EXPECT_FALSE(header.HasHeadersWithPrefix("K")); + EXPECT_FALSE(header.HasHeadersWithPrefix("ke")); + EXPECT_FALSE(header.HasHeadersWithPrefix("key")); + EXPECT_FALSE(header.HasHeadersWithPrefix("key1")); + EXPECT_FALSE(header.HasHeadersWithPrefix("key2")); + header.AppendHeader("key1", "value_1"); + EXPECT_TRUE(header.HasHeader("key1")); + EXPECT_TRUE(header.HasHeadersWithPrefix("K")); + EXPECT_TRUE(header.HasHeadersWithPrefix("ke")); + EXPECT_TRUE(header.HasHeadersWithPrefix("key")); + EXPECT_TRUE(header.HasHeadersWithPrefix("key1")); + EXPECT_FALSE(header.HasHeadersWithPrefix("key2")); + + header.AppendHeader("key1", "value_2"); + EXPECT_TRUE(header.HasHeader("key1")); + EXPECT_FALSE(header.HasHeader("key2")); + EXPECT_TRUE(header.HasHeadersWithPrefix("k")); + EXPECT_TRUE(header.HasHeadersWithPrefix("ke")); + EXPECT_TRUE(header.HasHeadersWithPrefix("key")); + EXPECT_TRUE(header.HasHeadersWithPrefix("key1")); + EXPECT_FALSE(header.HasHeadersWithPrefix("key2")); +} + +TEST(BalsaHeaders, GetHeaderWorksAsExpectedWithHeadersErased) { + BalsaHeaders header; + header.AppendHeader("key1", "value_1"); + header.AppendHeader("key1", "value_2"); + header.AppendHeader("key2", "value_2"); + header.AppendHeader("key3", "value_3"); + header.AppendHeader("a", "value_a"); + header.AppendHeader("b", "value_b"); + + header.erase(header.GetHeaderPosition("key2")); + + absl::string_view header_key_b_value = header.GetHeader("b"); + absl::string_view header_key_1_value = header.GetHeader("key1"); + absl::string_view header_key_3_value = header.GetHeader("key3"); + absl::string_view header_key_2_value = header.GetHeader("key2"); + absl::string_view header_key_a_value = header.GetHeader("a"); + + ASSERT_FALSE(header_key_1_value.empty()); + ASSERT_TRUE(header_key_2_value.empty()); + ASSERT_FALSE(header_key_3_value.empty()); + ASSERT_FALSE(header_key_a_value.empty()); + ASSERT_FALSE(header_key_b_value.empty()); + + EXPECT_EQ(std::string("value_1"), header_key_1_value); + EXPECT_EQ(std::string("value_3"), header_key_3_value); + EXPECT_EQ(std::string("value_a"), header_key_a_value); + EXPECT_EQ(std::string("value_b"), header_key_b_value); + + // Erasing one makes the next one visible: + header.erase(header.GetHeaderPosition("key1")); + header_key_1_value = header.GetHeader("key1"); + ASSERT_FALSE(header_key_1_value.empty()); + EXPECT_EQ(std::string("value_2"), header_key_1_value); + + // Erase both: + header.erase(header.GetHeaderPosition("key1")); + ASSERT_TRUE(header.GetHeader("key1").empty()); +} + +TEST(BalsaHeaders, HasHeaderWorksAsExpectedWithHeadersErased) { + BalsaHeaders header; + header.AppendHeader("key1", "value_1"); + header.AppendHeader("key2", "value_2a"); + header.AppendHeader("key2", "value_2b"); + + ASSERT_TRUE(header.HasHeader("key1")); + ASSERT_TRUE(header.HasHeadersWithPrefix("key1")); + ASSERT_TRUE(header.HasHeadersWithPrefix("key2")); + ASSERT_TRUE(header.HasHeadersWithPrefix("kEY")); + header.erase(header.GetHeaderPosition("key1")); + EXPECT_FALSE(header.HasHeader("key1")); + EXPECT_FALSE(header.HasHeadersWithPrefix("key1")); + EXPECT_TRUE(header.HasHeadersWithPrefix("key2")); + EXPECT_TRUE(header.HasHeadersWithPrefix("kEY")); + + ASSERT_TRUE(header.HasHeader("key2")); + header.erase(header.GetHeaderPosition("key2")); + ASSERT_TRUE(header.HasHeader("key2")); + EXPECT_FALSE(header.HasHeadersWithPrefix("key1")); + EXPECT_TRUE(header.HasHeadersWithPrefix("key2")); + EXPECT_TRUE(header.HasHeadersWithPrefix("kEY")); + header.erase(header.GetHeaderPosition("key2")); + EXPECT_FALSE(header.HasHeader("key2")); + EXPECT_FALSE(header.HasHeadersWithPrefix("key1")); + EXPECT_FALSE(header.HasHeadersWithPrefix("key2")); + EXPECT_FALSE(header.HasHeadersWithPrefix("kEY")); +} + +TEST(BalsaHeaders, HasNonEmptyHeaderWorksAsExpectedWithNoHeaderLines) { + BalsaHeaders header; + EXPECT_FALSE(header.HasNonEmptyHeader("foo")); + EXPECT_FALSE(header.HasNonEmptyHeader("")); +} + +TEST(BalsaHeaders, HasNonEmptyHeaderWorksAsExpectedWithAppendHeader) { + BalsaHeaders header; + + EXPECT_FALSE(header.HasNonEmptyHeader("key1")); + header.AppendHeader("key1", ""); + EXPECT_FALSE(header.HasNonEmptyHeader("key1")); + + header.AppendHeader("key1", "value_2"); + EXPECT_TRUE(header.HasNonEmptyHeader("key1")); + EXPECT_FALSE(header.HasNonEmptyHeader("key2")); +} + +TEST(BalsaHeaders, HasNonEmptyHeaderWorksAsExpectedWithHeadersErased) { + BalsaHeaders header; + header.AppendHeader("key1", "value_1"); + header.AppendHeader("key2", "value_2a"); + header.AppendHeader("key2", ""); + + EXPECT_TRUE(header.HasNonEmptyHeader("key1")); + header.erase(header.GetHeaderPosition("key1")); + EXPECT_FALSE(header.HasNonEmptyHeader("key1")); + + EXPECT_TRUE(header.HasNonEmptyHeader("key2")); + header.erase(header.GetHeaderPosition("key2")); + EXPECT_FALSE(header.HasNonEmptyHeader("key2")); + header.erase(header.GetHeaderPosition("key2")); + EXPECT_FALSE(header.HasNonEmptyHeader("key2")); +} + +TEST(BalsaHeaders, HasNonEmptyHeaderWorksAsExpectedWithBalsaFrameProcessInput) { + BalsaHeaders headers = CreateHTTPHeaders(true, + "GET / HTTP/1.0\r\n" + "key1: value_1\r\n" + "key2:\r\n" + "key3:\r\n" + "key3: value_3\r\n" + "key4:\r\n" + "key4:\r\n" + "key5: value_5\r\n" + "key5:\r\n" + "\r\n"); + + EXPECT_FALSE(headers.HasNonEmptyHeader("foo")); + EXPECT_TRUE(headers.HasNonEmptyHeader("key1")); + EXPECT_FALSE(headers.HasNonEmptyHeader("key2")); + EXPECT_TRUE(headers.HasNonEmptyHeader("key3")); + EXPECT_FALSE(headers.HasNonEmptyHeader("key4")); + EXPECT_TRUE(headers.HasNonEmptyHeader("key5")); + + headers.erase(headers.GetHeaderPosition("key5")); + EXPECT_FALSE(headers.HasNonEmptyHeader("key5")); +} + +TEST(BalsaHeaders, GetAllOfHeader) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("Key", "value_2,value_3"); + header.AppendHeader("key", ""); + header.AppendHeader("KEY", "value_4"); + + std::vector<absl::string_view> result; + header.GetAllOfHeader("key", &result); + ASSERT_EQ(4u, result.size()); + EXPECT_EQ("value_1", result[0]); + EXPECT_EQ("value_2,value_3", result[1]); + EXPECT_EQ("", result[2]); + EXPECT_EQ("value_4", result[3]); + + EXPECT_EQ(header.GetAllOfHeader("key"), result); +} + +TEST(BalsaHeaders, GetAllOfHeaderDoesWhatItSays) { + BalsaHeaders header; + // Multiple values for a given header. + // Some values appear multiple times + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + header.AppendHeader("key", ""); + header.AppendHeader("key", "value_1"); + + ASSERT_NE(header.lines().begin(), header.lines().end()); + std::vector<absl::string_view> out; + + header.GetAllOfHeader("key", &out); + ASSERT_EQ(4u, out.size()); + EXPECT_EQ("value_1", out[0]); + EXPECT_EQ("value_2", out[1]); + EXPECT_EQ("", out[2]); + EXPECT_EQ("value_1", out[3]); + + EXPECT_EQ(header.GetAllOfHeader("key"), out); +} + +TEST(BalsaHeaders, GetAllOfHeaderWithPrefix) { + BalsaHeaders header; + header.AppendHeader("foo-Foo", "value_1"); + header.AppendHeader("Foo-bar", "value_2,value_3"); + header.AppendHeader("foo-Foo", ""); + header.AppendHeader("bar", "value_not"); + header.AppendHeader("fOO-fOO", "value_4"); + + std::vector<std::pair<absl::string_view, absl::string_view>> result; + header.GetAllOfHeaderWithPrefix("abc", &result); + ASSERT_EQ(0u, result.size()); + + header.GetAllOfHeaderWithPrefix("foo", &result); + ASSERT_EQ(4u, result.size()); + EXPECT_EQ("foo-Foo", result[0].first); + EXPECT_EQ("value_1", result[0].second); + EXPECT_EQ("Foo-bar", result[1].first); + EXPECT_EQ("value_2,value_3", result[1].second); + EXPECT_EQ("", result[2].second); + EXPECT_EQ("value_4", result[3].second); + + std::vector<std::pair<absl::string_view, absl::string_view>> result2; + header.GetAllOfHeaderWithPrefix("FoO", &result2); + ASSERT_EQ(4u, result2.size()); +} + +TEST(BalsaHeaders, GetAllHeadersWithLimit) { + BalsaHeaders header; + header.AppendHeader("foo-Foo", "value_1"); + header.AppendHeader("Foo-bar", "value_2,value_3"); + header.AppendHeader("foo-Foo", ""); + header.AppendHeader("bar", "value_4"); + header.AppendHeader("fOO-fOO", "value_5"); + + std::vector<std::pair<absl::string_view, absl::string_view>> result; + header.GetAllHeadersWithLimit(&result, 4); + ASSERT_EQ(4u, result.size()); + EXPECT_EQ("foo-Foo", result[0].first); + EXPECT_EQ("value_1", result[0].second); + EXPECT_EQ("Foo-bar", result[1].first); + EXPECT_EQ("value_2,value_3", result[1].second); + EXPECT_EQ("", result[2].second); + EXPECT_EQ("value_4", result[3].second); + + std::vector<std::pair<absl::string_view, absl::string_view>> result2; + header.GetAllHeadersWithLimit(&result2, -1); + ASSERT_EQ(5u, result2.size()); +} + +TEST(BalsaHeaders, RangeFor) { + BalsaHeaders header; + // Multiple values for a given header. + // Some values appear multiple times + header.AppendHeader("key1", "value_1a"); + header.AppendHeader("key1", "value_1b"); + header.AppendHeader("key2", ""); + header.AppendHeader("key3", "value_3"); + + std::vector<std::pair<absl::string_view, absl::string_view>> out; + for (const auto& line : header.lines()) { + out.push_back(line); + } + const std::vector<std::pair<absl::string_view, absl::string_view>> expected = + {{"key1", "value_1a"}, + {"key1", "value_1b"}, + {"key2", ""}, + {"key3", "value_3"}}; + EXPECT_EQ(expected, out); +} + +TEST(BalsaHeaders, GetAllOfHeaderWithNonExistentKey) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + std::vector<absl::string_view> out; + + header.GetAllOfHeader("key_non_existent", &out); + ASSERT_EQ(0u, out.size()); + + EXPECT_EQ(header.GetAllOfHeader("key_non_existent"), out); +} + +TEST(BalsaHeaders, GetAllOfHeaderEmptyValVariation1) { + BalsaHeaders header; + header.AppendHeader("key", ""); + header.AppendHeader("key", ""); + header.AppendHeader("key", "v1"); + std::vector<absl::string_view> out; + header.GetAllOfHeader("key", &out); + ASSERT_EQ(3u, out.size()); + EXPECT_EQ("", out[0]); + EXPECT_EQ("", out[1]); + EXPECT_EQ("v1", out[2]); + + EXPECT_EQ(header.GetAllOfHeader("key"), out); +} + +TEST(BalsaHeaders, GetAllOfHeaderEmptyValVariation2) { + BalsaHeaders header; + header.AppendHeader("key", ""); + header.AppendHeader("key", "v1"); + header.AppendHeader("key", ""); + std::vector<absl::string_view> out; + header.GetAllOfHeader("key", &out); + ASSERT_EQ(3u, out.size()); + EXPECT_EQ("", out[0]); + EXPECT_EQ("v1", out[1]); + EXPECT_EQ("", out[2]); + + EXPECT_EQ(header.GetAllOfHeader("key"), out); +} + +TEST(BalsaHeaders, GetAllOfHeaderEmptyValVariation3) { + BalsaHeaders header; + header.AppendHeader("key", ""); + header.AppendHeader("key", "v1"); + std::vector<absl::string_view> out; + header.GetAllOfHeader("key", &out); + ASSERT_EQ(2u, out.size()); + EXPECT_EQ("", out[0]); + EXPECT_EQ("v1", out[1]); + + EXPECT_EQ(header.GetAllOfHeader("key"), out); +} + +TEST(BalsaHeaders, GetAllOfHeaderEmptyValVariation4) { + BalsaHeaders header; + header.AppendHeader("key", "v1"); + header.AppendHeader("key", ""); + std::vector<absl::string_view> out; + header.GetAllOfHeader("key", &out); + ASSERT_EQ(2u, out.size()); + EXPECT_EQ("v1", out[0]); + EXPECT_EQ("", out[1]); + + EXPECT_EQ(header.GetAllOfHeader("key"), out); +} + +TEST(BalsaHeaders, GetAllOfHeaderWithAppendHeaders) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + std::vector<absl::string_view> out; + + header.GetAllOfHeader("key_new", &out); + ASSERT_EQ(0u, out.size()); + EXPECT_EQ(header.GetAllOfHeader("key_new"), out); + + // Add key_new to the header + header.AppendHeader("key_new", "value_3"); + header.GetAllOfHeader("key_new", &out); + ASSERT_EQ(1u, out.size()); + EXPECT_EQ("value_3", out[0]); + EXPECT_EQ(header.GetAllOfHeader("key_new"), out); + + // Get the keys that are not modified + header.GetAllOfHeader("key", &out); + ASSERT_EQ(3u, out.size()); + EXPECT_EQ("value_1", out[1]); + EXPECT_EQ("value_2", out[2]); + EXPECT_THAT(header.GetAllOfHeader("key"), ElementsAre("value_1", "value_2")); +} + +TEST(BalsaHeaders, GetAllOfHeaderWithRemoveHeaders) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + header.AppendHeader("a", "va"); + + header.RemoveAllOfHeader("key"); + std::vector<absl::string_view> out; + header.GetAllOfHeader("key", &out); + ASSERT_EQ(0u, out.size()); + EXPECT_EQ(header.GetAllOfHeader("key"), out); + + header.GetAllOfHeader("a", &out); + ASSERT_EQ(1u, out.size()); + EXPECT_EQ(header.GetAllOfHeader("a"), out); + + out.clear(); + header.RemoveAllOfHeader("a"); + header.GetAllOfHeader("a", &out); + ASSERT_EQ(0u, out.size()); + EXPECT_EQ(header.GetAllOfHeader("a"), out); +} + +TEST(BalsaHeaders, GetAllOfHeaderWithRemoveNonExistentHeaders) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("Accept-Encoding", "deflate,compress"); + EXPECT_EQ(0u, headers.RemoveValue("Accept-Encoding", "gzip(gfe)")); + std::string accept_encoding_vals = + headers.GetAllOfHeaderAsString("Accept-Encoding"); + EXPECT_EQ("deflate,compress", accept_encoding_vals); +} + +TEST(BalsaHeaders, GetAllOfHeaderWithEraseHeaders) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + header.AppendHeader("a", "va"); + + std::vector<absl::string_view> out; + + header.erase(header.GetHeaderPosition("key")); + header.GetAllOfHeader("key", &out); + ASSERT_EQ(1u, out.size()); + EXPECT_EQ("value_2", out[0]); + EXPECT_EQ(header.GetAllOfHeader("key"), out); + + out.clear(); + header.erase(header.GetHeaderPosition("key")); + header.GetAllOfHeader("key", &out); + ASSERT_EQ(0u, out.size()); + EXPECT_EQ(header.GetAllOfHeader("key"), out); + + out.clear(); + header.GetAllOfHeader("a", &out); + ASSERT_EQ(1u, out.size()); + EXPECT_EQ(header.GetAllOfHeader("a"), out); + + out.clear(); + header.erase(header.GetHeaderPosition("a")); + header.GetAllOfHeader("a", &out); + ASSERT_EQ(0u, out.size()); + EXPECT_EQ(header.GetAllOfHeader("key"), out); +} + +TEST(BalsaHeaders, GetAllOfHeaderWithNoHeaderLines) { + BalsaHeaders header; + std::vector<absl::string_view> out; + header.GetAllOfHeader("key", &out); + EXPECT_EQ(0u, out.size()); + EXPECT_EQ(header.GetAllOfHeader("key"), out); +} + +TEST(BalsaHeaders, GetAllOfHeaderDoesWhatItSaysForVariousKeys) { + BalsaHeaders header; + header.AppendHeader("key1", "value_11"); + header.AppendHeader("key2", "value_21"); + header.AppendHeader("key1", "value_12"); + header.AppendHeader("key2", "value_22"); + + std::vector<absl::string_view> out; + + header.GetAllOfHeader("key1", &out); + EXPECT_EQ("value_11", out[0]); + EXPECT_EQ("value_12", out[1]); + EXPECT_EQ(header.GetAllOfHeader("key1"), out); + + header.GetAllOfHeader("key2", &out); + EXPECT_EQ("value_21", out[2]); + EXPECT_EQ("value_22", out[3]); + EXPECT_THAT(header.GetAllOfHeader("key2"), + ElementsAre("value_21", "value_22")); +} + +TEST(BalsaHeaders, GetAllOfHeaderWithBalsaFrameProcessInput) { + BalsaHeaders header = CreateHTTPHeaders(true, + "GET / HTTP/1.0\r\n" + "key1: value_1\r\n" + "key1: value_foo\r\n" + "key2: value_2\r\n" + "a: value_a\r\n" + "key2: \r\n" + "b: value_b\r\n" + "\r\n"); + + std::vector<absl::string_view> out; + int index = 0; + header.GetAllOfHeader("key1", &out); + EXPECT_EQ("value_1", out[index++]); + EXPECT_EQ("value_foo", out[index++]); + EXPECT_EQ(header.GetAllOfHeader("key1"), out); + + header.GetAllOfHeader("key2", &out); + EXPECT_EQ("value_2", out[index++]); + EXPECT_EQ("", out[index++]); + EXPECT_THAT(header.GetAllOfHeader("key2"), ElementsAre("value_2", "")); + + header.GetAllOfHeader("a", &out); + EXPECT_EQ("value_a", out[index++]); + EXPECT_THAT(header.GetAllOfHeader("a"), ElementsAre("value_a")); + + header.GetAllOfHeader("b", &out); + EXPECT_EQ("value_b", out[index++]); + EXPECT_THAT(header.GetAllOfHeader("b"), ElementsAre("value_b")); +} + +TEST(BalsaHeaders, GetAllOfHeaderIncludeRemovedDoesWhatItSays) { + BalsaHeaders header; + header.AppendHeader("key1", "value_11"); + header.AppendHeader("key2", "value_21"); + header.AppendHeader("key1", "value_12"); + header.AppendHeader("key2", "value_22"); + header.AppendHeader("key1", ""); + + std::vector<absl::string_view> out; + header.GetAllOfHeaderIncludeRemoved("key1", &out); + ASSERT_EQ(3u, out.size()); + EXPECT_EQ("value_11", out[0]); + EXPECT_EQ("value_12", out[1]); + EXPECT_EQ("", out[2]); + header.GetAllOfHeaderIncludeRemoved("key2", &out); + ASSERT_EQ(5u, out.size()); + EXPECT_EQ("value_21", out[3]); + EXPECT_EQ("value_22", out[4]); + + header.erase(header.GetHeaderPosition("key1")); + out.clear(); + header.GetAllOfHeaderIncludeRemoved("key1", &out); + ASSERT_EQ(3u, out.size()); + EXPECT_EQ("value_12", out[0]); + EXPECT_EQ("", out[1]); + EXPECT_EQ("value_11", out[2]); + header.GetAllOfHeaderIncludeRemoved("key2", &out); + ASSERT_EQ(5u, out.size()); + EXPECT_EQ("value_21", out[3]); + EXPECT_EQ("value_22", out[4]); + + header.RemoveAllOfHeader("key1"); + out.clear(); + header.GetAllOfHeaderIncludeRemoved("key1", &out); + ASSERT_EQ(3u, out.size()); + EXPECT_EQ("value_11", out[0]); + EXPECT_EQ("value_12", out[1]); + EXPECT_EQ("", out[2]); + header.GetAllOfHeaderIncludeRemoved("key2", &out); + ASSERT_EQ(5u, out.size()); + EXPECT_EQ("value_21", out[3]); + EXPECT_EQ("value_22", out[4]); + + header.Clear(); + out.clear(); + header.GetAllOfHeaderIncludeRemoved("key1", &out); + ASSERT_EQ(0u, out.size()); + header.GetAllOfHeaderIncludeRemoved("key2", &out); + ASSERT_EQ(0u, out.size()); +} + +TEST(BalsaHeaders, GetAllOfHeaderIncludeRemovedWithNonExistentKey) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + std::vector<absl::string_view> out; + header.GetAllOfHeaderIncludeRemoved("key_non_existent", &out); + ASSERT_EQ(0u, out.size()); +} + +TEST(BalsaHeaders, GetIteratorForKeyDoesWhatItSays) { + BalsaHeaders header; + // Multiple values for a given header. + // Some values appear multiple times + header.AppendHeader("key", "value_1"); + header.AppendHeader("Key", "value_2"); + header.AppendHeader("key", ""); + header.AppendHeader("KEY", "value_1"); + + BalsaHeaders::const_header_lines_key_iterator key_it = + header.GetIteratorForKey("key"); + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("key", key_it->first); + EXPECT_EQ("value_1", key_it->second); + ++key_it; + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("Key", key_it->first); + EXPECT_EQ("value_2", key_it->second); + ++key_it; + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("key", key_it->first); + EXPECT_EQ("", key_it->second); + ++key_it; + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("KEY", key_it->first); + EXPECT_EQ("value_1", key_it->second); + ++key_it; + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); +} + +TEST(BalsaHeaders, GetIteratorForKeyWithNonExistentKey) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + + BalsaHeaders::const_header_lines_key_iterator key_it = + header.GetIteratorForKey("key_non_existent"); + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); + const auto lines = header.lines("key_non_existent"); + EXPECT_EQ(lines.begin(), header.lines().end()); + EXPECT_EQ(lines.end(), header.header_lines_key_end()); +} + +TEST(BalsaHeaders, GetIteratorForKeyWithAppendHeaders) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + + BalsaHeaders::const_header_lines_key_iterator key_it = + header.GetIteratorForKey("key_new"); + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); + + // Add key_new to the header + header.AppendHeader("key_new", "value_3"); + key_it = header.GetIteratorForKey("key_new"); + const auto lines1 = header.lines("key_new"); + EXPECT_EQ(lines1.begin(), key_it); + EXPECT_EQ(lines1.end(), header.header_lines_key_end()); + + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("key_new", key_it->first); + EXPECT_EQ("value_3", key_it->second); + ++key_it; + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); + + // Get the keys that are not modified + key_it = header.GetIteratorForKey("key"); + const auto lines2 = header.lines("key"); + EXPECT_EQ(lines2.begin(), key_it); + EXPECT_EQ(lines2.end(), header.header_lines_key_end()); + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("key", key_it->first); + EXPECT_EQ("value_1", key_it->second); + ++key_it; + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("key", key_it->first); + EXPECT_EQ("value_2", key_it->second); + ++key_it; + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); +} + +TEST(BalsaHeaders, GetIteratorForKeyWithRemoveHeaders) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + header.AppendHeader("a", "va"); + + header.RemoveAllOfHeader("a"); + BalsaHeaders::const_header_lines_key_iterator key_it = + header.GetIteratorForKey("key"); + EXPECT_NE(header.lines().end(), key_it); + const auto lines1 = header.lines("key"); + EXPECT_EQ(lines1.begin(), key_it); + EXPECT_EQ(lines1.end(), header.header_lines_key_end()); + EXPECT_EQ("value_1", key_it->second); + ++key_it; + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("key", key_it->first); + EXPECT_EQ("value_2", key_it->second); + ++key_it; + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); + + // Check that a typical loop works properly. + for (BalsaHeaders::const_header_lines_key_iterator it = + header.GetIteratorForKey("key"); + it != header.lines().end(); ++it) { + EXPECT_EQ("key", it->first); + } +} + +TEST(BalsaHeaders, GetIteratorForKeyWithEraseHeaders) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + header.AppendHeader("a", "va"); + header.erase(header.GetHeaderPosition("key")); + + BalsaHeaders::const_header_lines_key_iterator key_it = + header.GetIteratorForKey("key"); + EXPECT_NE(header.lines().end(), key_it); + const auto lines1 = header.lines("key"); + EXPECT_EQ(lines1.begin(), key_it); + EXPECT_EQ(lines1.end(), header.header_lines_key_end()); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("key", key_it->first); + EXPECT_EQ("value_2", key_it->second); + ++key_it; + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); + + header.erase(header.GetHeaderPosition("key")); + key_it = header.GetIteratorForKey("key"); + const auto lines2 = header.lines("key"); + EXPECT_EQ(lines2.begin(), key_it); + EXPECT_EQ(lines2.end(), header.header_lines_key_end()); + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); + + key_it = header.GetIteratorForKey("a"); + const auto lines3 = header.lines("a"); + EXPECT_EQ(lines3.begin(), key_it); + EXPECT_EQ(lines3.end(), header.header_lines_key_end()); + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("a", key_it->first); + EXPECT_EQ("va", key_it->second); + ++key_it; + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); + + header.erase(header.GetHeaderPosition("a")); + key_it = header.GetIteratorForKey("a"); + const auto lines4 = header.lines("a"); + EXPECT_EQ(lines4.begin(), key_it); + EXPECT_EQ(lines4.end(), header.header_lines_key_end()); + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); +} + +TEST(BalsaHeaders, GetIteratorForKeyWithNoHeaderLines) { + BalsaHeaders header; + BalsaHeaders::const_header_lines_key_iterator key_it = + header.GetIteratorForKey("key"); + const auto lines = header.lines("key"); + EXPECT_EQ(lines.begin(), key_it); + EXPECT_EQ(lines.end(), header.header_lines_key_end()); + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); +} + +TEST(BalsaHeaders, GetIteratorForKeyWithBalsaFrameProcessInput) { + BalsaHeaders header = CreateHTTPHeaders(true, + "GET / HTTP/1.0\r\n" + "key1: value_1\r\n" + "Key1: value_foo\r\n" + "key2: value_2\r\n" + "a: value_a\r\n" + "key2: \r\n" + "b: value_b\r\n" + "\r\n"); + + BalsaHeaders::const_header_lines_key_iterator key_it = + header.GetIteratorForKey("Key1"); + const auto lines1 = header.lines("Key1"); + EXPECT_EQ(lines1.begin(), key_it); + EXPECT_EQ(lines1.end(), header.header_lines_key_end()); + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("key1", key_it->first); + EXPECT_EQ("value_1", key_it->second); + ++key_it; + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("Key1", key_it->first); + EXPECT_EQ("value_foo", key_it->second); + ++key_it; + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); + + key_it = header.GetIteratorForKey("key2"); + EXPECT_NE(header.lines().end(), key_it); + const auto lines2 = header.lines("key2"); + EXPECT_EQ(lines2.begin(), key_it); + EXPECT_EQ(lines2.end(), header.header_lines_key_end()); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("key2", key_it->first); + EXPECT_EQ("value_2", key_it->second); + ++key_it; + EXPECT_NE(header.lines().end(), key_it); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("key2", key_it->first); + EXPECT_EQ("", key_it->second); + ++key_it; + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); + + key_it = header.GetIteratorForKey("a"); + EXPECT_NE(header.lines().end(), key_it); + const auto lines3 = header.lines("a"); + EXPECT_EQ(lines3.begin(), key_it); + EXPECT_EQ(lines3.end(), header.header_lines_key_end()); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("a", key_it->first); + EXPECT_EQ("value_a", key_it->second); + ++key_it; + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); + + key_it = header.GetIteratorForKey("b"); + EXPECT_NE(header.lines().end(), key_it); + const auto lines4 = header.lines("b"); + EXPECT_EQ(lines4.begin(), key_it); + EXPECT_EQ(lines4.end(), header.header_lines_key_end()); + EXPECT_NE(header.header_lines_key_end(), key_it); + EXPECT_EQ("b", key_it->first); + EXPECT_EQ("value_b", key_it->second); + ++key_it; + EXPECT_EQ(header.lines().end(), key_it); + EXPECT_EQ(header.header_lines_key_end(), key_it); +} + +TEST(BalsaHeaders, GetAllOfHeaderAsStringDoesWhatItSays) { + BalsaHeaders header; + // Multiple values for a given header. + // Some values appear multiple times + header.AppendHeader("key", "value_1"); + header.AppendHeader("Key", "value_2"); + header.AppendHeader("key", ""); + header.AppendHeader("KEY", "value_1"); + + std::string result = header.GetAllOfHeaderAsString("key"); + EXPECT_EQ("value_1,value_2,,value_1", result); +} + +TEST(BalsaHeaders, RemoveAllOfHeaderDoesWhatItSays) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + ASSERT_NE(header.lines().begin(), header.lines().end()); + header.RemoveAllOfHeader("key"); + ASSERT_EQ(header.lines().begin(), header.lines().end()); +} + +TEST(BalsaHeaders, + RemoveAllOfHeaderDoesWhatItSaysEvenWhenThingsHaveBeenErased) { + BalsaHeaders header; + header.AppendHeader("key1", "value_1"); + header.AppendHeader("key1", "value_2"); + header.AppendHeader("key2", "value_3"); + header.AppendHeader("key1", "value_4"); + header.AppendHeader("key2", "value_5"); + header.AppendHeader("key1", "value_6"); + ASSERT_NE(header.lines().begin(), header.lines().end()); + + BalsaHeaders::const_header_lines_iterator chli = header.lines().begin(); + ++chli; + ++chli; + ++chli; + header.erase(chli); + + chli = header.lines().begin(); + ++chli; + header.erase(chli); + + header.RemoveAllOfHeader("key1"); + for (const auto& line : header.lines()) { + EXPECT_NE(std::string("key1"), line.first); + } +} + +TEST(BalsaHeaders, RemoveAllOfHeaderDoesNothingWhenNoKeyOfThatNameExists) { + BalsaHeaders header; + header.AppendHeader("key", "value_1"); + header.AppendHeader("key", "value_2"); + ASSERT_NE(header.lines().begin(), header.lines().end()); + header.RemoveAllOfHeader("foo"); + int num_found = 0; + for (const auto& line : header.lines()) { + ++num_found; + EXPECT_EQ(absl::string_view("key"), line.first); + } + EXPECT_EQ(2, num_found); + EXPECT_NE(header.lines().begin(), header.lines().end()); +} + +TEST(BalsaHeaders, WriteHeaderEndingToBuffer) { + BalsaHeaders header; + SimpleBuffer simple_buffer; + header.WriteHeaderEndingToBuffer(&simple_buffer); + EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq("\r\n")); +} + +TEST(BalsaHeaders, WriteToBufferDoesntCrashWithUninitializedHeader) { + BalsaHeaders header; + SimpleBuffer simple_buffer; + header.WriteHeaderAndEndingToBuffer(&simple_buffer); +} + +TEST(BalsaHeaders, WriteToBufferWorksWithBalsaHeadersParsedByFramer) { + std::string input = + "GET / HTTP/1.0\r\n" + "key_with_value: value\r\n" + "key_with_continuation_value: \r\n" + " with continuation\r\n" + "key_with_two_continuation_value: \r\n" + " continuation 1\r\n" + " continuation 2\r\n" + "a: foo \r\n" + "b-s:\n" + " bar\t\n" + "foo: \r\n" + "bazzzzzzzleriffic!: snaps\n" + "\n"; + std::string expected = + "GET / HTTP/1.0\r\n" + "key_with_value: value\r\n" + "key_with_continuation_value: with continuation\r\n" + "key_with_two_continuation_value: continuation 1\r\n" + " continuation 2\r\n" + "a: foo\r\n" + "b-s: bar\r\n" + "foo: \r\n" + "bazzzzzzzleriffic!: snaps\r\n" + "\r\n"; + + BalsaHeaders headers = CreateHTTPHeaders(true, input); + SimpleBuffer simple_buffer; + size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer(); + headers.WriteHeaderAndEndingToBuffer(&simple_buffer); + EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected)); + EXPECT_EQ(expected_write_buffer_size, + static_cast<size_t>(simple_buffer.ReadableBytes())); +} + +TEST(BalsaHeaders, + WriteToBufferWorksWithBalsaHeadersParsedByFramerTabContinuations) { + std::string input = + "GET / HTTP/1.0\r\n" + "key_with_value: value\r\n" + "key_with_continuation_value: \r\n" + "\twith continuation\r\n" + "key_with_two_continuation_value: \r\n" + "\tcontinuation 1\r\n" + "\tcontinuation 2\r\n" + "a: foo \r\n" + "b-s:\n" + "\tbar\t\n" + "foo: \r\n" + "bazzzzzzzleriffic!: snaps\n" + "\n"; + std::string expected = + "GET / HTTP/1.0\r\n" + "key_with_value: value\r\n" + "key_with_continuation_value: with continuation\r\n" + "key_with_two_continuation_value: continuation 1\r\n" + "\tcontinuation 2\r\n" + "a: foo\r\n" + "b-s: bar\r\n" + "foo: \r\n" + "bazzzzzzzleriffic!: snaps\r\n" + "\r\n"; + + BalsaHeaders headers = CreateHTTPHeaders(true, input); + SimpleBuffer simple_buffer; + size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer(); + headers.WriteHeaderAndEndingToBuffer(&simple_buffer); + EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected)); + EXPECT_EQ(expected_write_buffer_size, + static_cast<size_t>(simple_buffer.ReadableBytes())); +} + +TEST(BalsaHeaders, WriteToBufferWorksWhenFirstlineSetThroughHeaders) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + std::string expected = + "GET / HTTP/1.0\r\n" + "\r\n"; + SimpleBuffer simple_buffer; + size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer(); + headers.WriteHeaderAndEndingToBuffer(&simple_buffer); + EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected)); + EXPECT_EQ(expected_write_buffer_size, + static_cast<size_t>(simple_buffer.ReadableBytes())); +} + +TEST(BalsaHeaders, WriteToBufferWorksWhenSetThroughHeaders) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("key1", "value1"); + headers.AppendHeader("key 2", "value\n 2"); + headers.AppendHeader("key\n 3", "value3"); + std::string expected = + "GET / HTTP/1.0\r\n" + "key1: value1\r\n" + "key 2: value\n" + " 2\r\n" + "key\n" + " 3: value3\r\n" + "\r\n"; + SimpleBuffer simple_buffer; + size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer(); + headers.WriteHeaderAndEndingToBuffer(&simple_buffer); + EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected)); + EXPECT_EQ(expected_write_buffer_size, + static_cast<size_t>(simple_buffer.ReadableBytes())); +} + +TEST(BalsaHeaders, WriteToBufferWorkWhensOnlyLinesSetThroughHeaders) { + BalsaHeaders headers; + headers.AppendHeader("key1", "value1"); + headers.AppendHeader("key 2", "value\n 2"); + headers.AppendHeader("key\n 3", "value3"); + std::string expected = + "\r\n" + "key1: value1\r\n" + "key 2: value\n" + " 2\r\n" + "key\n" + " 3: value3\r\n" + "\r\n"; + SimpleBuffer simple_buffer; + size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer(); + headers.WriteHeaderAndEndingToBuffer(&simple_buffer); + EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected)); + EXPECT_EQ(expected_write_buffer_size, + static_cast<size_t>(simple_buffer.ReadableBytes())); +} + +TEST(BalsaHeaders, WriteToBufferWorksWhenSetThroughHeadersWithElementsErased) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("key1", "value1"); + headers.AppendHeader("key 2", "value\n 2"); + headers.AppendHeader("key\n 3", "value3"); + headers.RemoveAllOfHeader("key1"); + headers.RemoveAllOfHeader("key\n 3"); + std::string expected = + "GET / HTTP/1.0\r\n" + "key 2: value\n" + " 2\r\n" + "\r\n"; + SimpleBuffer simple_buffer; + size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer(); + headers.WriteHeaderAndEndingToBuffer(&simple_buffer); + EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected)); + EXPECT_EQ(expected_write_buffer_size, + static_cast<size_t>(simple_buffer.ReadableBytes())); +} + +TEST(BalsaHeaders, WriteToBufferWithManuallyAppendedHeaderLine) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("key1", "value1"); + headers.AppendHeader("key 2", "value\n 2"); + std::string expected = + "GET / HTTP/1.0\r\n" + "key1: value1\r\n" + "key 2: value\n" + " 2\r\n" + "key 3: value 3\r\n" + "\r\n"; + + SimpleBuffer simple_buffer; + size_t expected_write_buffer_size = headers.GetSizeForWriteBuffer(); + headers.WriteToBuffer(&simple_buffer); + headers.WriteHeaderLineToBuffer(&simple_buffer, "key 3", "value 3", + BalsaHeaders::CaseOption::kNoModification); + headers.WriteHeaderEndingToBuffer(&simple_buffer); + EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected)); + EXPECT_EQ(expected_write_buffer_size + 16, + static_cast<size_t>(simple_buffer.ReadableBytes())); +} + +TEST(BalsaHeaders, DumpToStringEmptyHeaders) { + BalsaHeaders headers; + std::string headers_str; + headers.DumpToString(&headers_str); + EXPECT_EQ("\n <empty header>\n", headers_str); +} + +TEST(BalsaHeaders, DumpToStringParsedHeaders) { + std::string input = + "GET / HTTP/1.0\r\n" + "Header1: value\r\n" + "Header2: value\r\n" + "\r\n"; + std::string output = + "\n" + " GET / HTTP/1.0\n" + " Header1: value\n" + " Header2: value\n"; + + BalsaHeaders headers = CreateHTTPHeaders(true, input); + std::string headers_str; + headers.DumpToString(&headers_str); + EXPECT_EQ(output, headers_str); + EXPECT_TRUE(headers.FramerIsDoneWriting()); +} + +TEST(BalsaHeaders, DumpToStringPartialHeaders) { + BalsaHeaders headers; + BalsaFrame balsa_frame; + balsa_frame.set_is_request(true); + balsa_frame.set_balsa_headers(&headers); + std::string input = + "GET / HTTP/1.0\r\n" + "Header1: value\r\n" + "Header2: value\r\n"; + std::string output = absl::StrFormat("\n <incomplete header len: %d>\n ", + static_cast<int>(input.size())); + output += input; + output += '\n'; + + ASSERT_EQ(input.size(), balsa_frame.ProcessInput(input.data(), input.size())); + ASSERT_FALSE(balsa_frame.MessageFullyRead()); + std::string headers_str; + headers.DumpToString(&headers_str); + EXPECT_EQ(output, headers_str); + EXPECT_FALSE(headers.FramerIsDoneWriting()); +} + +TEST(BalsaHeaders, DumpToStringParsingNonHeadersData) { + BalsaHeaders headers; + BalsaFrame balsa_frame; + balsa_frame.set_is_request(true); + balsa_frame.set_balsa_headers(&headers); + std::string input = + "This is not a header. " + "Just some random data to simulate mismatch."; + std::string output = absl::StrFormat("\n <incomplete header len: %d>\n ", + static_cast<int>(input.size())); + output += input; + output += '\n'; + + ASSERT_EQ(input.size(), balsa_frame.ProcessInput(input.data(), input.size())); + ASSERT_FALSE(balsa_frame.MessageFullyRead()); + std::string headers_str; + headers.DumpToString(&headers_str); + EXPECT_EQ(output, headers_str); +} + +TEST(BalsaHeaders, Clear) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("key1", "value1"); + headers.AppendHeader("key 2", "value\n 2"); + headers.AppendHeader("key\n 3", "value3"); + headers.RemoveAllOfHeader("key1"); + headers.RemoveAllOfHeader("key\n 3"); + headers.Clear(); + EXPECT_TRUE(headers.first_line().empty()); + EXPECT_EQ(headers.lines().begin(), headers.lines().end()); + EXPECT_TRUE(headers.IsEmpty()); +} + +TEST(BalsaHeaders, + TestSetFromStringPiecesWithInitialFirstlineInHeaderStreamAndNewToo) { + BalsaHeaders headers = CreateHTTPHeaders(false, + "HTTP/1.1 200 reason phrase\r\n" + "content-length: 0\r\n" + "\r\n"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1")); + EXPECT_THAT(headers.response_code(), StrEq("200")); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase")); + + headers.SetResponseFirstline("HTTP/1.0", 404, "a reason"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0")); + EXPECT_THAT(headers.response_code(), StrEq("404")); + EXPECT_THAT(headers.parsed_response_code(), Eq(404)); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("a reason")); + EXPECT_THAT(headers.first_line(), StrEq("HTTP/1.0 404 a reason")); +} + +TEST(BalsaHeaders, + TestSetFromStringPiecesWithInitialFirstlineInHeaderStreamButNotNew) { + BalsaHeaders headers = CreateHTTPHeaders(false, + "HTTP/1.1 200 reason phrase\r\n" + "content-length: 0\r\n" + "\r\n"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1")); + EXPECT_THAT(headers.response_code(), StrEq("200")); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase")); + + headers.SetResponseFirstline("HTTP/1.000", 404000, + "supercalifragilisticexpealidocious"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.000")); + EXPECT_THAT(headers.response_code(), StrEq("404000")); + EXPECT_THAT(headers.parsed_response_code(), Eq(404000)); + EXPECT_THAT(headers.response_reason_phrase(), + StrEq("supercalifragilisticexpealidocious")); + EXPECT_THAT(headers.first_line(), + StrEq("HTTP/1.000 404000 supercalifragilisticexpealidocious")); +} + +TEST(BalsaHeaders, + TestSetFromStringPiecesWithFirstFirstlineInHeaderStreamButNotNew2) { + SCOPED_TRACE( + "This test tests the codepath where the new firstline is" + " too large to fit within the space used by the original" + " firstline, but large enuogh to space in the free space" + " available in both firstline plus the space made available" + " with deleted header lines (specifically, the first one"); + BalsaHeaders headers = CreateHTTPHeaders( + false, + "HTTP/1.1 200 reason phrase\r\n" + "a: 0987123409871234078130948710938471093827401983740198327401982374\r\n" + "content-length: 0\r\n" + "\r\n"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1")); + EXPECT_THAT(headers.response_code(), StrEq("200")); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase")); + + headers.erase(headers.lines().begin()); + headers.SetResponseFirstline("HTTP/1.000", 404000, + "supercalifragilisticexpealidocious"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.000")); + EXPECT_THAT(headers.response_code(), StrEq("404000")); + EXPECT_THAT(headers.parsed_response_code(), Eq(404000)); + EXPECT_THAT(headers.response_reason_phrase(), + StrEq("supercalifragilisticexpealidocious")); + EXPECT_THAT(headers.first_line(), + StrEq("HTTP/1.000 404000 supercalifragilisticexpealidocious")); +} + +TEST(BalsaHeaders, TestSetFirstlineFromStringPiecesWithNoInitialFirstline) { + BalsaHeaders headers; + headers.SetResponseFirstline("HTTP/1.1", 200, "don't need a reason"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1")); + EXPECT_THAT(headers.response_code(), StrEq("200")); + EXPECT_THAT(headers.parsed_response_code(), Eq(200)); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("don't need a reason")); + EXPECT_THAT(headers.first_line(), StrEq("HTTP/1.1 200 don't need a reason")); +} + +TEST(BalsaHeaders, TestSettingFirstlineElementsWithOtherElementsMissing) { + { + BalsaHeaders headers; + headers.SetRequestMethod("GET"); + headers.SetRequestUri("/"); + EXPECT_THAT(headers.first_line(), StrEq("GET / ")); + } + { + BalsaHeaders headers; + headers.SetRequestMethod("GET"); + headers.SetRequestVersion("HTTP/1.1"); + EXPECT_THAT(headers.first_line(), StrEq("GET HTTP/1.1")); + } + { + BalsaHeaders headers; + headers.SetRequestUri("/"); + headers.SetRequestVersion("HTTP/1.1"); + EXPECT_THAT(headers.first_line(), StrEq(" / HTTP/1.1")); + } +} + +TEST(BalsaHeaders, TestSettingMissingFirstlineElementsAfterBalsaHeadersParsed) { + { + BalsaHeaders headers = CreateHTTPHeaders(true, "GET /foo\r\n"); + ASSERT_THAT(headers.first_line(), StrEq("GET /foo")); + + headers.SetRequestVersion("HTTP/1.1"); + EXPECT_THAT(headers.first_line(), StrEq("GET /foo HTTP/1.1")); + } + { + BalsaHeaders headers = CreateHTTPHeaders(true, "GET\r\n"); + ASSERT_THAT(headers.first_line(), StrEq("GET")); + + headers.SetRequestUri("/foo"); + EXPECT_THAT(headers.first_line(), StrEq("GET /foo ")); + } +} + +// Here we exersize the codepaths involved in setting a new firstine when the +// previously set firstline is stored in the 'additional_data_stream_' +// variable, and the new firstline is larger than the previously set firstline. +TEST(BalsaHeaders, + SetFirstlineFromStringPiecesFirstInAdditionalDataAndNewLarger) { + BalsaHeaders headers; + // This one will end up being put into the additional data stream + headers.SetResponseFirstline("HTTP/1.1", 200, "don't need a reason"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1")); + EXPECT_THAT(headers.response_code(), StrEq("200")); + EXPECT_THAT(headers.parsed_response_code(), Eq(200)); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("don't need a reason")); + EXPECT_THAT(headers.first_line(), StrEq("HTTP/1.1 200 don't need a reason")); + + // Now, we set it again, this time we're extending what exists + // here. + headers.SetResponseFirstline("HTTP/1.10", 2000, "REALLY don't need a reason"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.10")); + EXPECT_THAT(headers.response_code(), StrEq("2000")); + EXPECT_THAT(headers.parsed_response_code(), Eq(2000)); + EXPECT_THAT(headers.response_reason_phrase(), + StrEq("REALLY don't need a reason")); + EXPECT_THAT(headers.first_line(), + StrEq("HTTP/1.10 2000 REALLY don't need a reason")); +} + +// Here we exersize the codepaths involved in setting a new firstine when the +// previously set firstline is stored in the 'additional_data_stream_' +// variable, and the new firstline is smaller than the previously set firstline. +TEST(BalsaHeaders, + TestSetFirstlineFromStringPiecesWithPreviousInAdditionalDataNewSmaller) { + BalsaHeaders headers; + // This one will end up being put into the additional data stream + // + headers.SetResponseFirstline("HTTP/1.10", 2000, "REALLY don't need a reason"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.10")); + EXPECT_THAT(headers.response_code(), StrEq("2000")); + EXPECT_THAT(headers.parsed_response_code(), Eq(2000)); + EXPECT_THAT(headers.response_reason_phrase(), + StrEq("REALLY don't need a reason")); + EXPECT_THAT(headers.first_line(), + StrEq("HTTP/1.10 2000 REALLY don't need a reason")); + + // Now, we set it again, this time we're extending what exists + // here. + headers.SetResponseFirstline("HTTP/1.0", 200, "a reason"); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0")); + EXPECT_THAT(headers.response_code(), StrEq("200")); + EXPECT_THAT(headers.parsed_response_code(), Eq(200)); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("a reason")); + EXPECT_THAT(headers.first_line(), StrEq("HTTP/1.0 200 a reason")); +} + +TEST(BalsaHeaders, CopyFrom) { + // TODO(fenix): test -all- member variables. + // (remaining: transfer_encoding_is_chunked_ + // content_length_ + // content_length_status_ + // parsed_response_code_ + // connection_close_token_found_ + // connection_keep_alive_token_found_ + // content_encoding_gzip_token_found_) + BalsaHeaders headers1, headers2; + absl::string_view method("GET"); + absl::string_view uri("/foo"); + absl::string_view version("HTTP/1.0"); + headers1.SetRequestFirstlineFromStringPieces(method, uri, version); + headers1.AppendHeader("key1", "value1"); + headers1.AppendHeader("key 2", "value\n 2"); + headers1.AppendHeader("key\n 3", "value3"); + + // "GET /foo HTTP/1.0" // 17 + // "key1: value1\r\n" // 14 + // "key 2: value\n 2\r\n" // 17 + // "key\n 3: value3\r\n" // 16 + + headers2.CopyFrom(headers1); + + EXPECT_THAT(headers1.first_line(), StrEq("GET /foo HTTP/1.0")); + BalsaHeaders::const_header_lines_iterator chli = headers1.lines().begin(); + EXPECT_THAT(chli->first, StrEq("key1")); + EXPECT_THAT(chli->second, StrEq("value1")); + ++chli; + EXPECT_THAT(chli->first, StrEq("key 2")); + EXPECT_THAT(chli->second, StrEq("value\n 2")); + ++chli; + EXPECT_THAT(chli->first, StrEq("key\n 3")); + EXPECT_THAT(chli->second, StrEq("value3")); + ++chli; + EXPECT_EQ(headers1.lines().end(), chli); + + EXPECT_THAT(headers1.request_method(), + StrEq((std::string(headers2.request_method())))); + EXPECT_THAT(headers1.request_uri(), + StrEq((std::string(headers2.request_uri())))); + EXPECT_THAT(headers1.request_version(), + StrEq((std::string(headers2.request_version())))); + + EXPECT_THAT(headers2.first_line(), StrEq("GET /foo HTTP/1.0")); + chli = headers2.lines().begin(); + EXPECT_THAT(chli->first, StrEq("key1")); + EXPECT_THAT(chli->second, StrEq("value1")); + ++chli; + EXPECT_THAT(chli->first, StrEq("key 2")); + EXPECT_THAT(chli->second, StrEq("value\n 2")); + ++chli; + EXPECT_THAT(chli->first, StrEq("key\n 3")); + EXPECT_THAT(chli->second, StrEq("value3")); + ++chli; + EXPECT_EQ(headers2.lines().end(), chli); + + version = absl::string_view("HTTP/1.1"); + int code = 200; + absl::string_view reason_phrase("reason phrase asdf"); + + headers1.RemoveAllOfHeader("key1"); + headers1.AppendHeader("key4", "value4"); + + headers1.SetResponseFirstline(version, code, reason_phrase); + + headers2.CopyFrom(headers1); + + // "GET /foo HTTP/1.0" // 17 + // "XXXXXXXXXXXXXX" // 14 + // "key 2: value\n 2\r\n" // 17 + // "key\n 3: value3\r\n" // 16 + // "key4: value4\r\n" // 14 + // + // -> + // + // "HTTP/1.1 200 reason phrase asdf" // 31 = (17 + 14) + // "key 2: value\n 2\r\n" // 17 + // "key\n 3: value3\r\n" // 16 + // "key4: value4\r\n" // 14 + + EXPECT_THAT(headers1.request_method(), + StrEq((std::string(headers2.request_method())))); + EXPECT_THAT(headers1.request_uri(), + StrEq((std::string(headers2.request_uri())))); + EXPECT_THAT(headers1.request_version(), + StrEq((std::string(headers2.request_version())))); + + EXPECT_THAT(headers2.first_line(), StrEq("HTTP/1.1 200 reason phrase asdf")); + chli = headers2.lines().begin(); + EXPECT_THAT(chli->first, StrEq("key 2")); + EXPECT_THAT(chli->second, StrEq("value\n 2")); + ++chli; + EXPECT_THAT(chli->first, StrEq("key\n 3")); + EXPECT_THAT(chli->second, StrEq("value3")); + ++chli; + EXPECT_THAT(chli->first, StrEq("key4")); + EXPECT_THAT(chli->second, StrEq("value4")); + ++chli; + EXPECT_EQ(headers2.lines().end(), chli); +} + +// Test BalsaHeaders move constructor and move assignment operator. +TEST(BalsaHeaders, Move) { + BalsaHeaders headers1, headers3; + absl::string_view method("GET"); + absl::string_view uri("/foo"); + absl::string_view version("HTTP/1.0"); + headers1.SetRequestFirstlineFromStringPieces(method, uri, version); + headers1.AppendHeader("key1", "value1"); + headers1.AppendHeader("key 2", "value\n 2"); + headers1.AppendHeader("key\n 3", "value3"); + + // "GET /foo HTTP/1.0" // 17 + // "key1: value1\r\n" // 14 + // "key 2: value\n 2\r\n" // 17 + // "key\n 3: value3\r\n" // 16 + + BalsaHeaders headers2 = std::move(headers1); + + EXPECT_EQ("GET /foo HTTP/1.0", headers2.first_line()); + BalsaHeaders::const_header_lines_iterator chli = headers2.lines().begin(); + EXPECT_EQ("key1", chli->first); + EXPECT_EQ("value1", chli->second); + ++chli; + EXPECT_EQ("key 2", chli->first); + EXPECT_EQ("value\n 2", chli->second); + ++chli; + EXPECT_EQ("key\n 3", chli->first); + EXPECT_EQ("value3", chli->second); + ++chli; + EXPECT_EQ(headers2.lines().end(), chli); + + EXPECT_EQ("GET", headers2.request_method()); + EXPECT_EQ("/foo", headers2.request_uri()); + EXPECT_EQ("HTTP/1.0", headers2.request_version()); + + headers3 = std::move(headers2); + version = absl::string_view("HTTP/1.1"); + int code = 200; + absl::string_view reason_phrase("reason phrase asdf"); + + headers3.RemoveAllOfHeader("key1"); + headers3.AppendHeader("key4", "value4"); + + headers3.SetResponseFirstline(version, code, reason_phrase); + + BalsaHeaders headers4 = std::move(headers3); + + // "GET /foo HTTP/1.0" // 17 + // "XXXXXXXXXXXXXX" // 14 + // "key 2: value\n 2\r\n" // 17 + // "key\n 3: value3\r\n" // 16 + // "key4: value4\r\n" // 14 + // + // -> + // + // "HTTP/1.1 200 reason phrase asdf" // 31 = (17 + 14) + // "key 2: value\n 2\r\n" // 17 + // "key\n 3: value3\r\n" // 16 + // "key4: value4\r\n" // 14 + + EXPECT_EQ("200", headers4.response_code()); + EXPECT_EQ("reason phrase asdf", headers4.response_reason_phrase()); + EXPECT_EQ("HTTP/1.1", headers4.response_version()); + + EXPECT_EQ("HTTP/1.1 200 reason phrase asdf", headers4.first_line()); + chli = headers4.lines().begin(); + EXPECT_EQ("key 2", chli->first); + EXPECT_EQ("value\n 2", chli->second); + ++chli; + EXPECT_EQ("key\n 3", chli->first); + EXPECT_EQ("value3", chli->second); + ++chli; + EXPECT_EQ("key4", chli->first); + EXPECT_EQ("value4", chli->second); + ++chli; + EXPECT_EQ(headers4.lines().end(), chli); +} + +TEST(BalsaHeaders, IteratorWorksWithOStreamAsExpected) { + { + std::stringstream actual; + BalsaHeaders::const_header_lines_iterator chli; + actual << chli; + // Note that the output depends on the flavor of standard library in use. + EXPECT_THAT(actual.str(), AnyOf(StrEq("[0, 0]"), // libstdc++ + StrEq("[(nil), 0]"))); // libc++ + } + { + BalsaHeaders headers; + std::stringstream actual; + BalsaHeaders::const_header_lines_iterator chli = headers.lines().begin(); + actual << chli; + std::stringstream expected; + expected << "[" << &headers << ", 0]"; + EXPECT_THAT(expected.str(), StrEq(actual.str())); + } +} + +TEST(BalsaHeaders, TestSetResponseReasonPhraseWithNoInitialFirstline) { + BalsaHeaders balsa_headers; + balsa_headers.SetResponseReasonPhrase("don't need a reason"); + EXPECT_THAT(balsa_headers.first_line(), StrEq(" don't need a reason")); + EXPECT_TRUE(balsa_headers.response_version().empty()); + EXPECT_TRUE(balsa_headers.response_code().empty()); + EXPECT_THAT(balsa_headers.response_reason_phrase(), + StrEq("don't need a reason")); +} + +// Testing each of 9 combinations separately was taking up way too much of this +// file (not to mention the inordinate amount of stupid code duplication), thus +// this test tests all 9 combinations of smaller, equal, and larger in one +// place. +TEST(BalsaHeaders, TestSetResponseReasonPhrase) { + const char* response_reason_phrases[] = { + "qwerty asdfgh", + "qwerty", + "qwerty asdfghjkl", + }; + size_t arraysize_squared = (ABSL_ARRAYSIZE(response_reason_phrases) * + ABSL_ARRAYSIZE(response_reason_phrases)); + // We go through the 9 different permutations of (response_reason_phrases + // choose 2) in the loop below. For each permutation, we mutate the firstline + // twice-- once from the original, and once from the previous. + for (size_t iteration = 0; iteration < arraysize_squared; ++iteration) { + SCOPED_TRACE("Original firstline: \"HTTP/1.0 200 reason phrase\""); + BalsaHeaders headers = CreateHTTPHeaders(true, + "HTTP/1.0 200 reason phrase\r\n" + "content-length: 0\r\n" + "\r\n"); + ASSERT_THAT(headers.first_line(), StrEq("HTTP/1.0 200 reason phrase")); + + { + int first = iteration / ABSL_ARRAYSIZE(response_reason_phrases); + const char* response_reason_phrase_first = response_reason_phrases[first]; + std::string expected_new_firstline = + absl::StrFormat("HTTP/1.0 200 %s", response_reason_phrase_first); + SCOPED_TRACE(absl::StrFormat("Then set response_reason_phrase(\"%s\")", + response_reason_phrase_first)); + + headers.SetResponseReasonPhrase(response_reason_phrase_first); + EXPECT_THAT(headers.first_line(), + StrEq(absl::StrFormat("HTTP/1.0 200 %s", + response_reason_phrase_first))); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0")); + EXPECT_THAT(headers.response_code(), StrEq("200")); + EXPECT_THAT(headers.response_reason_phrase(), + StrEq(response_reason_phrase_first)); + } + + // Note that each iteration of the outer loop causes the headers to be left + // in a different state. Nothing wrong with that, but we should use each of + // these states, and try each of our scenarios again. This inner loop does + // that. + { + int second = iteration % ABSL_ARRAYSIZE(response_reason_phrases); + const char* response_reason_phrase_second = + response_reason_phrases[second]; + std::string expected_new_firstline = + absl::StrFormat("HTTP/1.0 200 %s", response_reason_phrase_second); + SCOPED_TRACE(absl::StrFormat("Then set response_reason_phrase(\"%s\")", + response_reason_phrase_second)); + + headers.SetResponseReasonPhrase(response_reason_phrase_second); + EXPECT_THAT(headers.first_line(), + StrEq(absl::StrFormat("HTTP/1.0 200 %s", + response_reason_phrase_second))); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0")); + EXPECT_THAT(headers.response_code(), StrEq("200")); + EXPECT_THAT(headers.response_reason_phrase(), + StrEq(response_reason_phrase_second)); + } + } +} + +TEST(BalsaHeaders, TestSetResponseVersionWithNoInitialFirstline) { + BalsaHeaders balsa_headers; + balsa_headers.SetResponseVersion("HTTP/1.1"); + EXPECT_THAT(balsa_headers.first_line(), StrEq("HTTP/1.1 ")); + EXPECT_THAT(balsa_headers.response_version(), StrEq("HTTP/1.1")); + EXPECT_TRUE(balsa_headers.response_code().empty()); + EXPECT_TRUE(balsa_headers.response_reason_phrase().empty()); +} + +// Testing each of 9 combinations separately was taking up way too much of this +// file (not to mention the inordinate amount of stupid code duplication), thus +// this test tests all 9 combinations of smaller, equal, and larger in one +// place. +TEST(BalsaHeaders, TestSetResponseVersion) { + const char* response_versions[] = { + "ABCD/123", + "ABCD", + "ABCD/123456", + }; + size_t arraysize_squared = + (ABSL_ARRAYSIZE(response_versions) * ABSL_ARRAYSIZE(response_versions)); + // We go through the 9 different permutations of (response_versions choose 2) + // in the loop below. For each permutation, we mutate the firstline twice-- + // once from the original, and once from the previous. + for (size_t iteration = 0; iteration < arraysize_squared; ++iteration) { + SCOPED_TRACE("Original firstline: \"HTTP/1.0 200 reason phrase\""); + BalsaHeaders headers = CreateHTTPHeaders(false, + "HTTP/1.0 200 reason phrase\r\n" + "content-length: 0\r\n" + "\r\n"); + ASSERT_THAT(headers.first_line(), StrEq("HTTP/1.0 200 reason phrase")); + + // This structure guarantees that we'll visit all of the possible + // variations of setting. + + { + int first = iteration / ABSL_ARRAYSIZE(response_versions); + const char* response_version_first = response_versions[first]; + std::string expected_new_firstline = + absl::StrFormat("%s 200 reason phrase", response_version_first); + SCOPED_TRACE(absl::StrFormat("Then set response_version(\"%s\")", + response_version_first)); + + headers.SetResponseVersion(response_version_first); + EXPECT_THAT(headers.first_line(), StrEq(expected_new_firstline)); + EXPECT_THAT(headers.response_version(), StrEq(response_version_first)); + EXPECT_THAT(headers.response_code(), StrEq("200")); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase")); + } + { + int second = iteration % ABSL_ARRAYSIZE(response_versions); + const char* response_version_second = response_versions[second]; + std::string expected_new_firstline = + absl::StrFormat("%s 200 reason phrase", response_version_second); + SCOPED_TRACE(absl::StrFormat("Then set response_version(\"%s\")", + response_version_second)); + + headers.SetResponseVersion(response_version_second); + EXPECT_THAT(headers.first_line(), StrEq(expected_new_firstline)); + EXPECT_THAT(headers.response_version(), StrEq(response_version_second)); + EXPECT_THAT(headers.response_code(), StrEq("200")); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase")); + } + } +} + +TEST(BalsaHeaders, TestSetResponseReasonAndVersionWithNoInitialFirstline) { + BalsaHeaders headers; + headers.SetResponseVersion("HTTP/1.1"); + headers.SetResponseReasonPhrase("don't need a reason"); + EXPECT_THAT(headers.first_line(), StrEq("HTTP/1.1 don't need a reason")); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.1")); + EXPECT_TRUE(headers.response_code().empty()); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("don't need a reason")); +} + +TEST(BalsaHeaders, TestSetResponseCodeWithNoInitialFirstline) { + BalsaHeaders balsa_headers; + balsa_headers.SetParsedResponseCodeAndUpdateFirstline(2002); + EXPECT_THAT(balsa_headers.first_line(), StrEq(" 2002 ")); + EXPECT_TRUE(balsa_headers.response_version().empty()); + EXPECT_THAT(balsa_headers.response_code(), StrEq("2002")); + EXPECT_TRUE(balsa_headers.response_reason_phrase().empty()); + EXPECT_THAT(balsa_headers.parsed_response_code(), Eq(2002)); +} + +TEST(BalsaHeaders, TestSetParsedResponseCode) { + BalsaHeaders balsa_headers; + balsa_headers.set_parsed_response_code(std::numeric_limits<int>::max()); + EXPECT_THAT(balsa_headers.parsed_response_code(), + Eq(std::numeric_limits<int>::max())); +} + +TEST(BalsaHeaders, TestSetResponseCode) { + const char* response_codes[] = { + "200" + "23", + "200200", + }; + size_t arraysize_squared = + (ABSL_ARRAYSIZE(response_codes) * ABSL_ARRAYSIZE(response_codes)); + // We go through the 9 different permutations of (response_codes choose 2) + // in the loop below. For each permutation, we mutate the firstline twice-- + // once from the original, and once from the previous. + for (size_t iteration = 0; iteration < arraysize_squared; ++iteration) { + SCOPED_TRACE("Original firstline: \"HTTP/1.0 200 reason phrase\""); + BalsaHeaders headers = CreateHTTPHeaders(false, + "HTTP/1.0 200 reason phrase\r\n" + "content-length: 0\r\n" + "\r\n"); + ASSERT_THAT(headers.first_line(), StrEq("HTTP/1.0 200 reason phrase")); + + // This structure guarantees that we'll visit all of the possible + // variations of setting. + + { + int first = iteration / ABSL_ARRAYSIZE(response_codes); + const char* response_code_first = response_codes[first]; + std::string expected_new_firstline = + absl::StrFormat("HTTP/1.0 %s reason phrase", response_code_first); + SCOPED_TRACE(absl::StrFormat("Then set response_code(\"%s\")", + response_code_first)); + + headers.SetResponseCode(response_code_first); + + EXPECT_THAT(headers.first_line(), StrEq(expected_new_firstline)); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0")); + EXPECT_THAT(headers.response_code(), StrEq(response_code_first)); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase")); + } + { + int second = iteration % ABSL_ARRAYSIZE(response_codes); + const char* response_code_second = response_codes[second]; + std::string expected_new_secondline = + absl::StrFormat("HTTP/1.0 %s reason phrase", response_code_second); + SCOPED_TRACE(absl::StrFormat("Then set response_code(\"%s\")", + response_code_second)); + + headers.SetResponseCode(response_code_second); + + EXPECT_THAT(headers.first_line(), StrEq(expected_new_secondline)); + EXPECT_THAT(headers.response_version(), StrEq("HTTP/1.0")); + EXPECT_THAT(headers.response_code(), StrEq(response_code_second)); + EXPECT_THAT(headers.response_reason_phrase(), StrEq("reason phrase")); + } + } +} + +TEST(BalsaHeaders, TestAppendToHeader) { + // Test the basic case of appending to a header. + BalsaHeaders headers; + headers.AppendHeader("foo", "foo_value"); + headers.AppendHeader("bar", "bar_value"); + headers.AppendToHeader("foo", "foo_value2"); + + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value,foo_value2")); + EXPECT_THAT(headers.GetHeader("bar"), StrEq("bar_value")); +} + +TEST(BalsaHeaders, TestInitialAppend) { + // Test that AppendToHeader works properly when the header did not already + // exist. + BalsaHeaders headers; + headers.AppendToHeader("foo", "foo_value"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value")); + headers.AppendToHeader("foo", "foo_value2"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value,foo_value2")); +} + +TEST(BalsaHeaders, TestAppendAndRemove) { + // Test that AppendToHeader works properly with removing. + BalsaHeaders headers; + headers.AppendToHeader("foo", "foo_value"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value")); + headers.AppendToHeader("foo", "foo_value2"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value,foo_value2")); + headers.RemoveAllOfHeader("foo"); + headers.AppendToHeader("foo", "foo_value3"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value3")); + headers.AppendToHeader("foo", "foo_value4"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value3,foo_value4")); +} + +TEST(BalsaHeaders, TestAppendToHeaderWithCommaAndSpace) { + // Test the basic case of appending to a header with comma and space. + BalsaHeaders headers; + headers.AppendHeader("foo", "foo_value"); + headers.AppendHeader("bar", "bar_value"); + headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value2"); + + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value, foo_value2")); + EXPECT_THAT(headers.GetHeader("bar"), StrEq("bar_value")); +} + +TEST(BalsaHeaders, TestInitialAppendWithCommaAndSpace) { + // Test that AppendToHeadeWithCommaAndSpace works properly when the + // header did not already exist. + BalsaHeaders headers; + headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value")); + headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value2"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value, foo_value2")); +} + +TEST(BalsaHeaders, TestAppendWithCommaAndSpaceAndRemove) { + // Test that AppendToHeadeWithCommaAndSpace works properly with removing. + BalsaHeaders headers; + headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value")); + headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value2"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value, foo_value2")); + headers.RemoveAllOfHeader("foo"); + headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value3"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value3")); + headers.AppendToHeaderWithCommaAndSpace("foo", "foo_value4"); + EXPECT_THAT(headers.GetHeader("foo"), StrEq("foo_value3, foo_value4")); +} + +TEST(BalsaHeaders, SetContentLength) { + // Test that SetContentLength correctly sets the content-length header and + // sets the content length status. + BalsaHeaders headers; + headers.SetContentLength(10); + EXPECT_THAT(headers.GetHeader("Content-length"), StrEq("10")); + EXPECT_EQ(BalsaHeadersEnums::VALID_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_TRUE(headers.content_length_valid()); + + // Test overwriting the content-length. + headers.SetContentLength(0); + EXPECT_THAT(headers.GetHeader("Content-length"), StrEq("0")); + EXPECT_EQ(BalsaHeadersEnums::VALID_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_TRUE(headers.content_length_valid()); + + // Make sure there is only one header line after the overwrite. + BalsaHeaders::const_header_lines_iterator iter = + headers.GetHeaderPosition("Content-length"); + EXPECT_EQ(headers.lines().begin(), iter); + EXPECT_EQ(headers.lines().end(), ++iter); + + // Test setting the same content-length again, this should be no-op. + headers.SetContentLength(0); + EXPECT_THAT(headers.GetHeader("Content-length"), StrEq("0")); + EXPECT_EQ(BalsaHeadersEnums::VALID_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_TRUE(headers.content_length_valid()); + + // Make sure the number of header lines didn't change. + iter = headers.GetHeaderPosition("Content-length"); + EXPECT_EQ(headers.lines().begin(), iter); + EXPECT_EQ(headers.lines().end(), ++iter); +} + +TEST(BalsaHeaders, ToggleChunkedEncoding) { + // Test that SetTransferEncodingToChunkedAndClearContentLength correctly adds + // chunk-encoding header and sets the transfer_encoding_is_chunked_ + // flag. + BalsaHeaders headers; + headers.SetTransferEncodingToChunkedAndClearContentLength(); + EXPECT_EQ("chunked", headers.GetAllOfHeaderAsString("Transfer-Encoding")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("Transfer-Encoding")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("transfer-encoding")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("transfer")); + EXPECT_TRUE(headers.transfer_encoding_is_chunked()); + + // Set it to the same value, nothing should change. + headers.SetTransferEncodingToChunkedAndClearContentLength(); + EXPECT_EQ("chunked", headers.GetAllOfHeaderAsString("Transfer-Encoding")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("Transfer-Encoding")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("transfer-encoding")); + EXPECT_TRUE(headers.HasHeadersWithPrefix("transfer")); + EXPECT_TRUE(headers.transfer_encoding_is_chunked()); + BalsaHeaders::const_header_lines_iterator iter = + headers.GetHeaderPosition("Transfer-Encoding"); + EXPECT_EQ(headers.lines().begin(), iter); + EXPECT_EQ(headers.lines().end(), ++iter); + + // Removes the chunked encoding, and there should be no transfer-encoding + // headers left. + headers.SetNoTransferEncoding(); + EXPECT_FALSE(headers.HasHeader("Transfer-Encoding")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("Transfer-Encoding")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("transfer-encoding")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("transfer")); + EXPECT_FALSE(headers.transfer_encoding_is_chunked()); + EXPECT_EQ(headers.lines().end(), headers.lines().begin()); + + // Clear chunked again, this should be a no-op and the header should not + // change. + headers.SetNoTransferEncoding(); + EXPECT_FALSE(headers.HasHeader("Transfer-Encoding")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("Transfer-Encoding")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("transfer-encoding")); + EXPECT_FALSE(headers.HasHeadersWithPrefix("transfer")); + EXPECT_FALSE(headers.transfer_encoding_is_chunked()); + EXPECT_EQ(headers.lines().end(), headers.lines().begin()); +} + +TEST(BalsaHeaders, SetNoTransferEncodingByRemoveHeader) { + // Tests that calling Remove() methods to clear the Transfer-Encoding + // header correctly resets transfer_encoding_is_chunked_ internal state. + BalsaHeaders headers; + headers.SetTransferEncodingToChunkedAndClearContentLength(); + headers.RemoveAllOfHeader("Transfer-Encoding"); + EXPECT_FALSE(headers.transfer_encoding_is_chunked()); + + headers.SetTransferEncodingToChunkedAndClearContentLength(); + std::vector<absl::string_view> headers_to_remove; + headers_to_remove.emplace_back("Transfer-Encoding"); + headers.RemoveAllOfHeaderInList(headers_to_remove); + EXPECT_FALSE(headers.transfer_encoding_is_chunked()); + + headers.SetTransferEncodingToChunkedAndClearContentLength(); + headers.RemoveAllHeadersWithPrefix("Transfer"); + EXPECT_FALSE(headers.transfer_encoding_is_chunked()); +} + +TEST(BalsaHeaders, ClearContentLength) { + // Test that ClearContentLength() removes the content-length header and + // resets content_length_status(). + BalsaHeaders headers; + headers.SetContentLength(10); + headers.ClearContentLength(); + EXPECT_FALSE(headers.HasHeader("Content-length")); + EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_FALSE(headers.content_length_valid()); + + // Clear it again; nothing should change. + headers.ClearContentLength(); + EXPECT_FALSE(headers.HasHeader("Content-length")); + EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_FALSE(headers.content_length_valid()); + + // Set chunked encoding and test that ClearContentLength() has no effect. + headers.SetTransferEncodingToChunkedAndClearContentLength(); + headers.ClearContentLength(); + EXPECT_EQ("chunked", headers.GetAllOfHeaderAsString("Transfer-Encoding")); + EXPECT_TRUE(headers.transfer_encoding_is_chunked()); + BalsaHeaders::const_header_lines_iterator iter = + headers.GetHeaderPosition("Transfer-Encoding"); + EXPECT_EQ(headers.lines().begin(), iter); + EXPECT_EQ(headers.lines().end(), ++iter); + + // Remove chunked encoding, and verify that the state is the same as after + // ClearContentLength(). + headers.SetNoTransferEncoding(); + EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_FALSE(headers.content_length_valid()); +} + +TEST(BalsaHeaders, ClearContentLengthByRemoveHeader) { + // Test that calling Remove() methods to clear the content-length header + // correctly resets internal content length fields. + BalsaHeaders headers; + headers.SetContentLength(10); + headers.RemoveAllOfHeader("Content-Length"); + EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_EQ(0u, headers.content_length()); + EXPECT_FALSE(headers.content_length_valid()); + + headers.SetContentLength(11); + std::vector<absl::string_view> headers_to_remove; + headers_to_remove.emplace_back("Content-Length"); + headers.RemoveAllOfHeaderInList(headers_to_remove); + EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_EQ(0u, headers.content_length()); + EXPECT_FALSE(headers.content_length_valid()); + + headers.SetContentLength(12); + headers.RemoveAllHeadersWithPrefix("Content"); + EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_EQ(0u, headers.content_length()); + EXPECT_FALSE(headers.content_length_valid()); +} + +// Chunk-encoding an identity-coded BalsaHeaders removes the identity-coding. +TEST(BalsaHeaders, IdentityCodingToChunked) { + std::string message = + "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: identity\r\n\r\n"; + BalsaHeaders headers; + BalsaFrame balsa_frame; + balsa_frame.set_is_request(false); + balsa_frame.set_balsa_headers(&headers); + EXPECT_EQ(message.size(), + balsa_frame.ProcessInput(message.data(), message.size())); + + EXPECT_TRUE(headers.is_framed_by_connection_close()); + EXPECT_FALSE(headers.transfer_encoding_is_chunked()); + EXPECT_THAT(headers.GetAllOfHeader("Transfer-Encoding"), + ElementsAre("identity")); + + headers.SetTransferEncodingToChunkedAndClearContentLength(); + + EXPECT_FALSE(headers.is_framed_by_connection_close()); + EXPECT_TRUE(headers.transfer_encoding_is_chunked()); + EXPECT_THAT(headers.GetAllOfHeader("Transfer-Encoding"), + ElementsAre("chunked")); +} + +TEST(BalsaHeaders, SwitchContentLengthToChunk) { + // Test that a header originally with content length header is correctly + // switched to using chunk encoding. + BalsaHeaders headers; + headers.SetContentLength(10); + EXPECT_THAT(headers.GetHeader("Content-length"), StrEq("10")); + EXPECT_EQ(BalsaHeadersEnums::VALID_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_TRUE(headers.content_length_valid()); + + headers.SetTransferEncodingToChunkedAndClearContentLength(); + EXPECT_EQ("chunked", headers.GetAllOfHeaderAsString("Transfer-Encoding")); + EXPECT_TRUE(headers.transfer_encoding_is_chunked()); + EXPECT_FALSE(headers.HasHeader("Content-length")); + EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_FALSE(headers.content_length_valid()); +} + +TEST(BalsaHeaders, SwitchChunkedToContentLength) { + // Test that a header originally with chunk encoding is correctly + // switched to using content length. + BalsaHeaders headers; + headers.SetTransferEncodingToChunkedAndClearContentLength(); + EXPECT_EQ("chunked", headers.GetAllOfHeaderAsString("Transfer-Encoding")); + EXPECT_TRUE(headers.transfer_encoding_is_chunked()); + EXPECT_FALSE(headers.HasHeader("Content-length")); + EXPECT_EQ(BalsaHeadersEnums::NO_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_FALSE(headers.content_length_valid()); + + headers.SetContentLength(10); + EXPECT_THAT(headers.GetHeader("Content-length"), StrEq("10")); + EXPECT_EQ(BalsaHeadersEnums::VALID_CONTENT_LENGTH, + headers.content_length_status()); + EXPECT_TRUE(headers.content_length_valid()); + EXPECT_FALSE(headers.HasHeader("Transfer-Encoding")); + EXPECT_FALSE(headers.transfer_encoding_is_chunked()); +} + +TEST(BalsaHeaders, OneHundredResponseMessagesNoFramedByClose) { + BalsaHeaders headers; + headers.SetResponseFirstline("HTTP/1.1", 100, "Continue"); + EXPECT_FALSE(headers.is_framed_by_connection_close()); +} + +TEST(BalsaHeaders, TwoOhFourResponseMessagesNoFramedByClose) { + BalsaHeaders headers; + headers.SetResponseFirstline("HTTP/1.1", 204, "Continue"); + EXPECT_FALSE(headers.is_framed_by_connection_close()); +} + +TEST(BalsaHeaders, ThreeOhFourResponseMessagesNoFramedByClose) { + BalsaHeaders headers; + headers.SetResponseFirstline("HTTP/1.1", 304, "Continue"); + EXPECT_FALSE(headers.is_framed_by_connection_close()); +} + +TEST(BalsaHeaders, InvalidCharInHeaderValue) { + std::string message = + "GET http://www.256.com/foo HTTP/1.1\r\n" + "Host: \x01\x01www.265.com\r\n" + "\r\n"; + BalsaHeaders headers = CreateHTTPHeaders(true, message); + EXPECT_EQ("www.265.com", headers.GetHeader("Host")); + SimpleBuffer buffer; + headers.WriteHeaderAndEndingToBuffer(&buffer); + message.replace(message.find_first_of(0x1), 2, ""); + EXPECT_EQ(message, buffer.GetReadableRegion()); +} + +TEST(BalsaHeaders, CarriageReturnAtStartOfLine) { + std::string message = + "GET /foo HTTP/1.1\r\n" + "Host: www.265.com\r\n" + "Foo: bar\r\n" + "\rX-User-Ip: 1.2.3.4\r\n" + "\r\n"; + BalsaHeaders headers; + BalsaFrame balsa_frame; + balsa_frame.set_is_request(true); + balsa_frame.set_balsa_headers(&headers); + EXPECT_EQ(message.size(), + balsa_frame.ProcessInput(message.data(), message.size())); + EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_FORMAT, balsa_frame.ErrorCode()); + EXPECT_TRUE(balsa_frame.Error()); +} + +TEST(BalsaHeaders, CheckEmpty) { + BalsaHeaders headers; + EXPECT_TRUE(headers.IsEmpty()); +} + +TEST(BalsaHeaders, CheckNonEmpty) { + BalsaHeaders headers; + BalsaHeadersTestPeer::WriteFromFramer(&headers, "a b c", 5); + EXPECT_FALSE(headers.IsEmpty()); +} + +TEST(BalsaHeaders, ForEachHeader) { + BalsaHeaders headers; + headers.AppendHeader(":host", "SomeHost"); + headers.AppendHeader("key", "val1,val2val2,val2,val3"); + headers.AppendHeader("key", "val4val5val6"); + headers.AppendHeader("key", "val11 val12"); + headers.AppendHeader("key", "v val13"); + headers.AppendHeader("key", "val7"); + headers.AppendHeader("key", ""); + headers.AppendHeader("key", "val8 , val9 ,, val10"); + headers.AppendHeader("key", " val14 "); + headers.AppendHeader("key2", "val15"); + headers.AppendHeader("key", "Val16"); + headers.AppendHeader("key", "foo, Val17, bar"); + headers.AppendHeader("date", "2 Jan 1970"); + headers.AppendHeader("AcceptEncoding", "MyFavoriteEncoding"); + + { + std::string result; + EXPECT_TRUE(headers.ForEachHeader( + [&result](const absl::string_view key, absl::string_view value) { + result.append("<") + .append(key.data(), key.size()) + .append("> = <") + .append(value.data(), value.size()) + .append(">\n"); + return true; + })); + + EXPECT_EQ(result, + "<:host> = <SomeHost>\n" + "<key> = <val1,val2val2,val2,val3>\n" + "<key> = <val4val5val6>\n" + "<key> = <val11 val12>\n" + "<key> = <v val13>\n" + "<key> = <val7>\n" + "<key> = <>\n" + "<key> = <val8 , val9 ,, val10>\n" + "<key> = < val14 >\n" + "<key2> = <val15>\n" + "<key> = <Val16>\n" + "<key> = <foo, Val17, bar>\n" + "<date> = <2 Jan 1970>\n" + "<AcceptEncoding> = <MyFavoriteEncoding>\n"); + } + + { + std::string result; + EXPECT_FALSE(headers.ForEachHeader( + [&result](const absl::string_view key, absl::string_view value) { + result.append("<") + .append(key.data(), key.size()) + .append("> = <") + .append(value.data(), value.size()) + .append(">\n"); + return !value.empty(); + })); + + EXPECT_EQ(result, + "<:host> = <SomeHost>\n" + "<key> = <val1,val2val2,val2,val3>\n" + "<key> = <val4val5val6>\n" + "<key> = <val11 val12>\n" + "<key> = <v val13>\n" + "<key> = <val7>\n" + "<key> = <>\n"); + } +} + +TEST(BalsaHeaders, WriteToBufferWithLowerCasedHeaderKey) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("Key1", "value1"); + headers.AppendHeader("Key2", "value2"); + std::string expected_lower_case = + "GET / HTTP/1.0\r\n" + "key1: value1\r\n" + "key2: value2\r\n"; + std::string expected_lower_case_with_end = + "GET / HTTP/1.0\r\n" + "key1: value1\r\n" + "key2: value2\r\n\r\n"; + std::string expected_upper_case = + "GET / HTTP/1.0\r\n" + "Key1: value1\r\n" + "Key2: value2\r\n"; + std::string expected_upper_case_with_end = + "GET / HTTP/1.0\r\n" + "Key1: value1\r\n" + "Key2: value2\r\n\r\n"; + + SimpleBuffer simple_buffer; + headers.WriteToBuffer(&simple_buffer, BalsaHeaders::CaseOption::kLowercase, + BalsaHeaders::CoalesceOption::kNoCoalesce); + EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected_lower_case)); + + simple_buffer.Clear(); + headers.WriteToBuffer(&simple_buffer); + EXPECT_THAT(simple_buffer.GetReadableRegion(), StrEq(expected_upper_case)); + + simple_buffer.Clear(); + headers.WriteHeaderAndEndingToBuffer(&simple_buffer); + EXPECT_THAT(simple_buffer.GetReadableRegion(), + StrEq(expected_upper_case_with_end)); + + simple_buffer.Clear(); + headers.WriteHeaderAndEndingToBuffer( + &simple_buffer, BalsaHeaders::CaseOption::kLowercase, + BalsaHeaders::CoalesceOption::kNoCoalesce); + EXPECT_THAT(simple_buffer.GetReadableRegion(), + StrEq(expected_lower_case_with_end)); +} + +TEST(BalsaHeaders, WriteToBufferWithProperCasedHeaderKey) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("Te", "value1"); + headers.AppendHeader("my-Test-header", "value2"); + std::string expected_proper_case = + "GET / HTTP/1.0\r\n" + "TE: value1\r\n" + "My-Test-Header: value2\r\n"; + std::string expected_proper_case_with_end = + "GET / HTTP/1.0\r\n" + "TE: value1\r\n" + "My-Test-Header: value2\r\n\r\n"; + std::string expected_unmodified = + "GET / HTTP/1.0\r\n" + "Te: value1\r\n" + "my-Test-header: value2\r\n"; + std::string expected_unmodified_with_end = + "GET / HTTP/1.0\r\n" + "Te: value1\r\n" + "my-Test-header: value2\r\n\r\n"; + + SimpleBuffer simple_buffer; + headers.WriteToBuffer(&simple_buffer, BalsaHeaders::CaseOption::kPropercase, + BalsaHeaders::CoalesceOption::kNoCoalesce); + EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_proper_case); + + simple_buffer.Clear(); + headers.WriteToBuffer(&simple_buffer, + BalsaHeaders::CaseOption::kNoModification, + BalsaHeaders::CoalesceOption::kNoCoalesce); + EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_unmodified); + + simple_buffer.Clear(); + headers.WriteHeaderAndEndingToBuffer( + &simple_buffer, BalsaHeaders::CaseOption::kNoModification, + BalsaHeaders::CoalesceOption::kNoCoalesce); + EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_unmodified_with_end); + + simple_buffer.Clear(); + headers.WriteHeaderAndEndingToBuffer( + &simple_buffer, BalsaHeaders::CaseOption::kPropercase, + BalsaHeaders::CoalesceOption::kNoCoalesce); + EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_proper_case_with_end); +} + +TEST(BalsaHeadersTest, ToPropercaseTest) { + EXPECT_EQ(BalsaHeaders::ToPropercase(""), ""); + EXPECT_EQ(BalsaHeaders::ToPropercase("Foo"), "Foo"); + EXPECT_EQ(BalsaHeaders::ToPropercase("foO"), "Foo"); + EXPECT_EQ(BalsaHeaders::ToPropercase("my-test-header"), "My-Test-Header"); + EXPECT_EQ(BalsaHeaders::ToPropercase("my--test-header"), "My--Test-Header"); +} + +TEST(BalsaHeaders, WriteToBufferCoalescingMultivaluedHeaders) { + BalsaHeaders::MultivaluedHeadersSet multivalued_headers; + multivalued_headers.insert("KeY1"); + multivalued_headers.insert("another_KEY"); + + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("Key1", "value1"); + headers.AppendHeader("Key2", "value2"); + headers.AppendHeader("Key1", "value11"); + headers.AppendHeader("Key2", "value21"); + headers.AppendHeader("Key1", "multiples, values, already"); + std::string expected_non_coalesced = + "GET / HTTP/1.0\r\n" + "Key1: value1\r\n" + "Key2: value2\r\n" + "Key1: value11\r\n" + "Key2: value21\r\n" + "Key1: multiples, values, already\r\n"; + std::string expected_coalesced = + "Key1: value1,value11,multiples, values, already\r\n" + "Key2: value2\r\n" + "Key2: value21\r\n"; + + SimpleBuffer simple_buffer; + headers.WriteToBuffer(&simple_buffer); + EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_non_coalesced); + + simple_buffer.Clear(); + headers.WriteToBufferCoalescingMultivaluedHeaders( + &simple_buffer, multivalued_headers, + BalsaHeaders::CaseOption::kNoModification); + EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_coalesced); +} + +TEST(BalsaHeaders, WriteToBufferCoalescingMultivaluedHeadersMultiLine) { + BalsaHeaders::MultivaluedHeadersSet multivalued_headers; + multivalued_headers.insert("Key 2"); + multivalued_headers.insert("key\n 3"); + + BalsaHeaders headers; + headers.AppendHeader("key1", "value1"); + headers.AppendHeader("key 2", "value\n 2"); + headers.AppendHeader("key\n 3", "value3"); + headers.AppendHeader("key 2", "value 21"); + headers.AppendHeader("key 3", "value 33"); + std::string expected_non_coalesced = + "\r\n" + "key1: value1\r\n" + "key 2: value\n" + " 2\r\n" + "key\n" + " 3: value3\r\n" + "key 2: value 21\r\n" + "key 3: value 33\r\n"; + + SimpleBuffer simple_buffer; + headers.WriteToBuffer(&simple_buffer); + EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_non_coalesced); + + std::string expected_coalesced = + "key1: value1\r\n" + "key 2: value\n" + " 2,value 21\r\n" + "key\n" + " 3: value3\r\n" + "key 3: value 33\r\n"; + + simple_buffer.Clear(); + headers.WriteToBufferCoalescingMultivaluedHeaders( + &simple_buffer, multivalued_headers, + BalsaHeaders::CaseOption::kNoModification); + EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_coalesced); +} + +TEST(BalsaHeaders, WriteToBufferCoalescingEnvoyHeaders) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("User-Agent", "UserAgent1"); + headers.AppendHeader("Key2", "value2"); + headers.AppendHeader("USER-AGENT", "UA2"); + headers.AppendHeader("Set-Cookie", "Cookie1=aaa"); + headers.AppendHeader("user-agent", "agent3"); + headers.AppendHeader("Set-Cookie", "Cookie2=bbb"); + std::string expected_non_coalesced = + "GET / HTTP/1.0\r\n" + "User-Agent: UserAgent1\r\n" + "Key2: value2\r\n" + "USER-AGENT: UA2\r\n" + "Set-Cookie: Cookie1=aaa\r\n" + "user-agent: agent3\r\n" + "Set-Cookie: Cookie2=bbb\r\n" + "\r\n"; + std::string expected_coalesced = + "GET / HTTP/1.0\r\n" + "User-Agent: UserAgent1,UA2,agent3\r\n" + "Key2: value2\r\n" + "Set-Cookie: Cookie1=aaa\r\n" + "Set-Cookie: Cookie2=bbb\r\n" + "\r\n"; + + SimpleBuffer simple_buffer; + headers.WriteHeaderAndEndingToBuffer(&simple_buffer); + EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_non_coalesced); + + simple_buffer.Clear(); + headers.WriteHeaderAndEndingToBuffer( + &simple_buffer, BalsaHeaders::CaseOption::kNoModification, + BalsaHeaders::CoalesceOption::kCoalesce); + EXPECT_EQ(simple_buffer.GetReadableRegion(), expected_coalesced); +} + +TEST(BalsaHeadersTest, GfeHeaderPolicyGfeBug) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + headers.AppendHeader("foo", "bar"); + EXPECT_QUICHE_BUG(headers.AppendHeader("X-Goog-Wombat", "tree"), + "Header X-Goog-Wombat violates go/gfe-header-policy"); +} + +TEST(BalsaHeadersTest, GfeHeaderPolicyExemptionNoGfeBug) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0"); + // Exempt header should not trigger a QUICHE_BUG. + headers.AppendHeader("X-Goog-Authenticated-User-ID", "id"); + headers.AppendHeader("x-goog-authenticated-user-id", "id"); +} + +TEST(BalsaHeadersTest, RemoveLastTokenFromOneLineHeader) { + BalsaHeaders headers = + CreateHTTPHeaders(true, + "GET /foo HTTP/1.1\r\n" + "Content-Length: 0\r\n" + "Content-Encoding: gzip, 3des, tar, prc\r\n\r\n"); + + BalsaHeaders::const_header_lines_key_iterator it = + headers.GetIteratorForKey("Content-Encoding"); + ASSERT_EQ("gzip, 3des, tar, prc", it->second); + EXPECT_EQ(headers.header_lines_key_end(), ++it); + + headers.RemoveLastTokenFromHeaderValue("Content-Encoding"); + it = headers.GetIteratorForKey("Content-Encoding"); + ASSERT_EQ("gzip, 3des, tar", it->second); + EXPECT_EQ(headers.header_lines_key_end(), ++it); + + headers.RemoveLastTokenFromHeaderValue("Content-Encoding"); + it = headers.GetIteratorForKey("Content-Encoding"); + ASSERT_EQ("gzip, 3des", it->second); + EXPECT_EQ(headers.header_lines_key_end(), ++it); + + headers.RemoveLastTokenFromHeaderValue("Content-Encoding"); + it = headers.GetIteratorForKey("Content-Encoding"); + ASSERT_EQ("gzip", it->second); + EXPECT_EQ(headers.header_lines_key_end(), ++it); + + headers.RemoveLastTokenFromHeaderValue("Content-Encoding"); + + EXPECT_FALSE(headers.HasHeader("Content-Encoding")); +} + +TEST(BalsaHeadersTest, RemoveLastTokenFromMultiLineHeader) { + BalsaHeaders headers = + CreateHTTPHeaders(true, + "GET /foo HTTP/1.1\r\n" + "Content-Length: 0\r\n" + "Content-Encoding: gzip, 3des\r\n" + "Content-Encoding: tar, prc\r\n\r\n"); + + BalsaHeaders::const_header_lines_key_iterator it = + headers.GetIteratorForKey("Content-Encoding"); + ASSERT_EQ("gzip, 3des", it->second); + ASSERT_EQ("tar, prc", (++it)->second); + ASSERT_EQ(headers.header_lines_key_end(), ++it); + + // First, we should start removing tokens from the second line. + headers.RemoveLastTokenFromHeaderValue("Content-Encoding"); + it = headers.GetIteratorForKey("Content-Encoding"); + ASSERT_EQ("gzip, 3des", it->second); + ASSERT_EQ("tar", (++it)->second); + ASSERT_EQ(headers.header_lines_key_end(), ++it); + + // Second line should be entirely removed after all its tokens are gone. + headers.RemoveLastTokenFromHeaderValue("Content-Encoding"); + it = headers.GetIteratorForKey("Content-Encoding"); + ASSERT_EQ("gzip, 3des", it->second); + ASSERT_EQ(headers.header_lines_key_end(), ++it); + + // Now we should be removing the tokens from the first line. + headers.RemoveLastTokenFromHeaderValue("Content-Encoding"); + it = headers.GetIteratorForKey("Content-Encoding"); + ASSERT_EQ("gzip", it->second); + ASSERT_EQ(headers.header_lines_key_end(), ++it); + + headers.RemoveLastTokenFromHeaderValue("Content-Encoding"); + EXPECT_FALSE(headers.HasHeader("Content-Encoding")); +} + +TEST(BalsaHeadersTest, ResponseCanHaveBody) { + // 1xx, 204 no content and 304 not modified responses can't have bodies. + EXPECT_FALSE(BalsaHeaders::ResponseCanHaveBody(100)); + EXPECT_FALSE(BalsaHeaders::ResponseCanHaveBody(101)); + EXPECT_FALSE(BalsaHeaders::ResponseCanHaveBody(102)); + EXPECT_FALSE(BalsaHeaders::ResponseCanHaveBody(204)); + EXPECT_FALSE(BalsaHeaders::ResponseCanHaveBody(304)); + + // Other responses can have body. + EXPECT_TRUE(BalsaHeaders::ResponseCanHaveBody(200)); + EXPECT_TRUE(BalsaHeaders::ResponseCanHaveBody(302)); + EXPECT_TRUE(BalsaHeaders::ResponseCanHaveBody(404)); + EXPECT_TRUE(BalsaHeaders::ResponseCanHaveBody(502)); +} + +} // namespace + +} // namespace test + +} // namespace quiche