| // 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/balsa/balsa_frame.h" |
| |
| #include <stdlib.h> |
| |
| #include <cstdint> |
| #include <limits> |
| #include <map> |
| #include <memory> |
| #include <random> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #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/balsa/balsa_enums.h" |
| #include "quiche/balsa/balsa_headers.h" |
| #include "quiche/balsa/balsa_visitor_interface.h" |
| #include "quiche/balsa/http_validation_policy.h" |
| #include "quiche/balsa/noop_balsa_visitor.h" |
| #include "quiche/balsa/simple_buffer.h" |
| #include "quiche/common/platform/api/quiche_command_line_flags.h" |
| #include "quiche/common/platform/api/quiche_expect_bug.h" |
| #include "quiche/common/platform/api/quiche_flags.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::Pointee; |
| using ::testing::Property; |
| using ::testing::Range; |
| using ::testing::StrEq; |
| using ::testing::StrictMock; |
| |
| DEFINE_QUICHE_COMMAND_LINE_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 OnTrailers(std::unique_ptr<BalsaHeaders> trailers) override { |
| FakeHeaders fake_trailers; |
| GenerateFakeHeaders(*trailers, &fake_trailers); |
| OnTrailers(fake_trailers); |
| } |
| |
| 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, OnTrailers, (const FakeHeaders& trailers)); |
| 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, OnInterimHeaders, (std::unique_ptr<BalsaHeaders> headers), |
| (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_balsa_headers(&headers_); |
| balsa_frame_.set_balsa_visitor(&visitor_mock_); |
| balsa_frame_.set_is_request(true); |
| balsa_frame_.EnableTrailers(); |
| } |
| |
| 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_; |
| 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()}); |
| BalsaHeaders trailers; |
| BalsaHeadersTestPeer::WriteFromFramer(&trailers, trailer.data(), |
| trailer.length()); |
| BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue( |
| &balsa_frame_, lines, true /*is_trailer*/, &trailers); |
| // 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()}); |
| BalsaHeaders trailers; |
| BalsaHeadersTestPeer::WriteFromFramer(&trailers, trailer.data(), |
| trailer.length()); |
| BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue( |
| &balsa_frame_, lines, true /*is_trailer*/, &trailers); |
| EXPECT_FALSE(balsa_frame_.Error()); |
| absl::string_view fraction = trailers.GetHeader("Fraction"); |
| EXPECT_EQ("0.23", fraction); |
| absl::string_view some = trailers.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()}); |
| BalsaHeaders trailers; |
| BalsaHeadersTestPeer::WriteFromFramer(&trailers, trailer.data(), |
| trailer.length()); |
| BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue( |
| &balsa_frame_, lines, true /*is_trailer*/, &trailers); |
| 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, ": ", "\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, ": ", "\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::BALSA_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::BALSA_NO_ERROR); |
| WhitespaceHeaderTestHelper( |
| "GET / HTTP/1.1\r\n" |
| "test: test\r\n" |
| " \r\n" |
| "\r\n", |
| true, BalsaFrameEnums::BALSA_NO_ERROR); |
| SCOPED_TRACE( |
| "Test a confusing and ambiguous case: is it a line continuation or a new " |
| "header field?"); |
| WhitespaceHeaderTestHelper( |
| "GET / HTTP/1.1\r\n" |
| "test: test\r\n" |
| " confusing:continued\r\n" |
| "\r\n", |
| true, BalsaFrameEnums::BALSA_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::BALSA_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::BALSA_NO_ERROR); |
| SCOPED_TRACE( |
| "Test a confusing and ambiguous case: is it a line continuation or a new " |
| "header field?"); |
| WhitespaceHeaderTestHelper( |
| "HTTP/1.0 200 Reason\r\n" |
| "test: test\r\n" |
| " confusing:continued\r\n" |
| "Content-Length: 0\r\n" |
| "\r\n", |
| false, BalsaFrameEnums::BALSA_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, |
| VisitorInvokedProperlyForRequestWithSplitBlankLines) { |
| 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, ContentLengthNotRequired) { |
| HttpValidationPolicy http_validation_policy; |
| http_validation_policy.require_content_length_if_body_required = false; |
| balsa_frame_.set_http_validation_policy(http_validation_policy); |
| |
| std::string message = |
| "PUT /search?q=fo HTTP/1.1\n" |
| "\n"; |
| |
| balsa_frame_.ProcessInput(message.data(), message.size()); |
| EXPECT_TRUE(balsa_frame_.MessageFullyRead()); |
| EXPECT_FALSE(balsa_frame_.Error()); |
| } |
| |
| 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::BALSA_NO_ERROR, balsa_frame_.ErrorCode()); |
| } |
| |
| TEST_F(HTTPBalsaFrameTest, RequestWithTrailers) { |
| 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"; |
| |
| InSequence s; |
| |
| FakeHeaders fake_headers; |
| fake_headers.AddKeyValue("Connection", "close"); |
| fake_headers.AddKeyValue("transfer-encoding", "chunked"); |
| EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers)); |
| ASSERT_EQ(headers.size(), |
| balsa_frame_.ProcessInput(headers.data(), headers.size())); |
| testing::Mock::VerifyAndClearExpectations(&visitor_mock_); |
| |
| ASSERT_EQ(chunks.size(), |
| balsa_frame_.ProcessInput(chunks.data(), chunks.size())); |
| |
| FakeHeaders fake_trailers; |
| fake_trailers.AddKeyValue("crass", "monkeys"); |
| fake_trailers.AddKeyValue("funky", "monkeys"); |
| EXPECT_CALL(visitor_mock_, OnTrailers(fake_trailers)); |
| |
| EXPECT_CALL(visitor_mock_, OnTrailerInput(_)).Times(AtLeast(1)); |
| |
| EXPECT_EQ(trailer.size(), |
| balsa_frame_.ProcessInput(trailer.data(), trailer.size())); |
| |
| EXPECT_TRUE(balsa_frame_.MessageFullyRead()); |
| EXPECT_EQ(BalsaFrameEnums::BALSA_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_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::BALSA_NO_ERROR, balsa_frame_.ErrorCode()); |
| } |
| |
| 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::BALSA_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::BALSA_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); |
| |
| 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::BALSA_NO_ERROR, balsa_frame_.ErrorCode()); |
| } |
| |
| 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::BALSA_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::BALSA_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::BALSA_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"); |
| |
| FakeHeaders fake_trailers; |
| fake_trailers.AddKeyValue("a_trailer_key", "and a trailer value"); |
| |
| { |
| 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_, OnTrailers(fake_trailers)); |
| 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::BALSA_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::BALSA_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::BALSA_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::BALSA_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::BALSA_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::BALSA_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); |
| |
| { |
| 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_, OnTrailers(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::BALSA_NO_ERROR, balsa_frame_.ErrorCode()); |
| |
| EXPECT_EQ(message_body, body_input); |
| EXPECT_EQ(message_body_data, body_data); |
| } |
| |
| 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); |
| |
| { |
| 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_, OnTrailers(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::BALSA_NO_ERROR, balsa_frame_.ErrorCode()); |
| |
| EXPECT_EQ(message_body, body_input); |
| EXPECT_EQ(message_body_data, body_data); |
| EXPECT_EQ(trailer_data, trailer_input); |
| } |
| |
| TEST(HTTPBalsaFrame, |
| VisitorInvokedProperlyForResponseWithTransferEncodingAndTrailersRandom) { |
| TestSeed seed; |
| seed.Initialize(GetQuicheCommandLineFlag(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; |
| BalsaFrame balsa_frame; |
| balsa_frame.set_is_request(false); |
| balsa_frame.set_balsa_headers(&headers); |
| balsa_frame.EnableTrailers(); |
| 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, OnTrailers(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 j = 0; j < message.size();) { |
| auto dist = std::uniform_int_distribution<>(0, message.size() - j + 1); |
| count = dist(rng); |
| size_t processed = balsa_frame.ProcessInput(message.data() + j, count); |
| ASSERT_GE(count, processed); |
| total_processed += processed; |
| j += processed; |
| } |
| EXPECT_EQ(message.size(), total_processed); |
| EXPECT_TRUE(balsa_frame.MessageFullyRead()); |
| EXPECT_FALSE(balsa_frame.Error()); |
| EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame.ErrorCode()); |
| |
| EXPECT_EQ(message_body, body_input); |
| EXPECT_EQ(message_body_data, body_data); |
| EXPECT_EQ(trailer_data, trailer_input); |
| } |
| } |
| |
| TEST_F(HTTPBalsaFrameTest, |
| AppropriateActionTakenWhenHeadersTooLongWithTooMuchInput) { |
| const absl::string_view message = |
| "GET /asflkasfdhjsafdkljhasfdlkjhasdflkjhsafdlkjhh HTTP/1.1"; |
| 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 QuicheTest { |
| protected: |
| void SetUp() override { |
| 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_.EnableTrailers(); |
| |
| 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_, OnTrailers(_)).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_; |
| BalsaFrame balsa_frame_; |
| StrictMock<BalsaVisitorMock> visitor_mock_; |
| }; |
| |
| TEST_F(BalsaFrameParsingTest, AppropriateActionTakenWhenHeaderColonsAreFunny) { |
| 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, /*invalid_name_char=*/false); |
| |
| balsa_frame_.Reset(); |
| |
| std::string trailer2 = |
| " \r\n" |
| "test: test\r\n" |
| "\r\n"; |
| TestInvalidTrailerFormat(trailer2, /*invalid_name_char=*/true); |
| |
| balsa_frame_.Reset(); |
| |
| std::string trailer3 = |
| "a: b\r\n" |
| ": test\r\n" |
| "\r\n"; |
| TestInvalidTrailerFormat(trailer3, /*invalid_name_char=*/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) |