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