blob: bf3d2a36595781a7a9da7fe0a1afaeb99c5e054f [file] [log] [blame]
// 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 <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::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 ProcessTrailers(const BalsaHeaders& trailer) override {
FakeHeaders fake_headers;
GenerateFakeHeaders(trailer, &fake_headers);
ProcessTrailers(fake_headers);
}
MOCK_METHOD(void, OnRawBodyInput, (absl::string_view input), (override));
MOCK_METHOD(void, OnBodyChunkInput, (absl::string_view input), (override));
MOCK_METHOD(void, OnHeaderInput, (absl::string_view input), (override));
MOCK_METHOD(void, OnTrailerInput, (absl::string_view input), (override));
MOCK_METHOD(void, ProcessHeaders, (const FakeHeaders& headers));
MOCK_METHOD(void, ProcessTrailers, (const FakeHeaders& headers));
MOCK_METHOD(void, OnRequestFirstLineInput,
(absl::string_view line_input, absl::string_view method_input,
absl::string_view request_uri, absl::string_view version_input),
(override));
MOCK_METHOD(void, OnResponseFirstLineInput,
(absl::string_view line_input, absl::string_view version_input,
absl::string_view status_input, absl::string_view reason_input),
(override));
MOCK_METHOD(void, OnChunkLength, (size_t length), (override));
MOCK_METHOD(void, OnChunkExtensionInput, (absl::string_view input),
(override));
MOCK_METHOD(void, ContinueHeaderDone, (), (override));
MOCK_METHOD(void, HeaderDone, (), (override));
MOCK_METHOD(void, MessageDone, (), (override));
MOCK_METHOD(void, HandleError, (BalsaFrameEnums::ErrorCode error_code),
(override));
MOCK_METHOD(void, HandleWarning, (BalsaFrameEnums::ErrorCode error_code),
(override));
private:
static void GenerateFakeHeaders(const BalsaHeaders& headers,
FakeHeaders* fake_headers) {
for (const auto& line : headers.lines()) {
fake_headers->AddKeyValue(std::string(line.first),
std::string(line.second));
}
}
};
class HTTPBalsaFrameTest : public QuicheTest {
protected:
void SetUp() override {
balsa_frame_.set_http_validation_policy(
HttpValidationPolicy::CreateDefault());
balsa_frame_.set_balsa_headers(&headers_);
balsa_frame_.set_balsa_visitor(&visitor_mock_);
balsa_frame_.set_is_request(true);
}
void VerifyFirstLineParsing(const std::string& firstline,
BalsaFrameEnums::ErrorCode error_code) {
balsa_frame_.ProcessInput(firstline.data(), firstline.size());
EXPECT_EQ(error_code, balsa_frame_.ErrorCode());
}
BalsaHeaders headers_;
BalsaHeaders trailer_;
BalsaFrame balsa_frame_;
NiceMock<BalsaVisitorMock> visitor_mock_;
};
// Test correct return value for HeaderFramingFound.
TEST_F(HTTPBalsaFrameTest, TestHeaderFramingFound) {
// Pattern \r\n\r\n should match kValidTerm1.
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, ' '));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
EXPECT_EQ(BalsaFrame::kValidTerm1,
BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
// Pattern \n\r\n should match kValidTerm1.
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\t'));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
EXPECT_EQ(BalsaFrame::kValidTerm1,
BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
// Pattern \r\n\n should match kValidTerm2.
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, 'a'));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
EXPECT_EQ(BalsaFrame::kValidTerm2,
BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
// Pattern \n\n should match kValidTerm2.
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '1'));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
EXPECT_EQ(BalsaFrame::kValidTerm2,
BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
// Other patterns should not match.
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, ':'));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\r'));
EXPECT_EQ(0, BalsaFrameTestPeer::HeaderFramingFound(&balsa_frame_, '\n'));
}
TEST_F(HTTPBalsaFrameTest, MissingColonInTrailer) {
const absl::string_view trailer = "kv\r\n\r\n";
BalsaFrame::Lines lines;
lines.push_back({0, 4});
lines.push_back({4, trailer.length()});
BalsaHeadersTestPeer::WriteFromFramer(&trailer_, trailer.data(),
trailer.length());
BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue(
&balsa_frame_, lines, true /*is_trailer*/, &trailer_);
// Note missing colon is not an error, just a warning.
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::TRAILER_MISSING_COLON, balsa_frame_.ErrorCode());
}
// Correctness of FindColonsAndParseIntoKeyValue is already verified for
// headers, so trailer related test is light.
TEST_F(HTTPBalsaFrameTest, FindColonsAndParseIntoKeyValueInTrailer) {
const absl::string_view trailer_line1 = "Fraction: 0.23\r\n";
const absl::string_view trailer_line2 = "Some:junk \r\n";
const absl::string_view trailer_line3 = "\r\n";
const std::string trailer =
absl::StrCat(trailer_line1, trailer_line2, trailer_line3);
BalsaFrame::Lines lines;
lines.push_back({0, trailer_line1.length()});
lines.push_back({trailer_line1.length(),
trailer_line1.length() + trailer_line2.length()});
lines.push_back(
{trailer_line1.length() + trailer_line2.length(), trailer.length()});
BalsaHeadersTestPeer::WriteFromFramer(&trailer_, trailer.data(),
trailer.length());
BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue(
&balsa_frame_, lines, true /*is_trailer*/, &trailer_);
EXPECT_FALSE(balsa_frame_.Error());
absl::string_view fraction = trailer_.GetHeader("Fraction");
EXPECT_EQ("0.23", fraction);
absl::string_view some = trailer_.GetHeader("Some");
EXPECT_EQ("junk", some);
}
TEST_F(HTTPBalsaFrameTest, InvalidTrailer) {
const absl::string_view trailer_line1 = "Fraction : 0.23\r\n";
const absl::string_view trailer_line2 = "Some\t :junk \r\n";
const absl::string_view trailer_line3 = "\r\n";
const std::string trailer =
absl::StrCat(trailer_line1, trailer_line2, trailer_line3);
BalsaFrame::Lines lines;
lines.push_back({0, trailer_line1.length()});
lines.push_back({trailer_line1.length(),
trailer_line1.length() + trailer_line2.length()});
lines.push_back(
{trailer_line1.length() + trailer_line2.length(), trailer.length()});
BalsaHeadersTestPeer::WriteFromFramer(&trailer_, trailer.data(),
trailer.length());
BalsaFrameTestPeer::FindColonsAndParseIntoKeyValue(
&balsa_frame_, lines, true /*is_trailer*/, &trailer_);
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::INVALID_TRAILER_NAME_CHARACTER,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, OneCharacterFirstLineParsedAsExpected) {
VerifyFirstLineParsing(
"a\r\n\r\n", BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD);
}
TEST_F(HTTPBalsaFrameTest,
OneCharacterFirstLineWithWhitespaceParsedAsExpected) {
VerifyFirstLineParsing(
"a \r\n\r\n", BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD);
}
TEST_F(HTTPBalsaFrameTest, WhitespaceOnlyFirstLineIsNotACompleteHeader) {
VerifyFirstLineParsing(" \n\n", BalsaFrameEnums::NO_REQUEST_LINE_IN_REQUEST);
}
TEST(HTTPBalsaFrame, RequestFirstLineParsedCorrectly) {
const char* request_tokens[3] = {"GET", "/jjsdjrqk", "HTTP/1.0"};
FirstLineParsedCorrectlyHelper(request_tokens, 0, true, " ");
FirstLineParsedCorrectlyHelper(request_tokens, 0, true, "\t");
FirstLineParsedCorrectlyHelper(request_tokens, 0, true, "\t ");
FirstLineParsedCorrectlyHelper(request_tokens, 0, true, " \t");
FirstLineParsedCorrectlyHelper(request_tokens, 0, true, " \t \t ");
}
TEST_F(HTTPBalsaFrameTest, NonnumericResponseCode) {
balsa_frame_.set_is_request(false);
VerifyFirstLineParsing("HTTP/1.1 0x3 Digits only\r\n\r\n",
BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT);
EXPECT_EQ("HTTP/1.1 0x3 Digits only", headers_.first_line());
}
TEST_F(HTTPBalsaFrameTest, NegativeResponseCode) {
balsa_frame_.set_is_request(false);
VerifyFirstLineParsing("HTTP/1.1 -11 No sign allowed\r\n\r\n",
BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT);
EXPECT_EQ("HTTP/1.1 -11 No sign allowed", headers_.first_line());
}
TEST_F(HTTPBalsaFrameTest, WithoutTrailingWhitespace) {
balsa_frame_.set_is_request(false);
VerifyFirstLineParsing(
"HTTP/1.1 101\r\n\r\n",
BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE);
EXPECT_EQ("HTTP/1.1 101", headers_.first_line());
}
TEST_F(HTTPBalsaFrameTest, TrailingWhitespace) {
balsa_frame_.set_is_request(false);
// b/69446061
std::string firstline = "HTTP/1.1 101 \r\n\r\n";
balsa_frame_.ProcessInput(firstline.data(), firstline.size());
EXPECT_EQ("HTTP/1.1 101 ", headers_.first_line());
}
TEST(HTTPBalsaFrame, ResponseFirstLineParsedCorrectly) {
const char* response_tokens[3] = {"HTTP/1.1", "200", "A reason\tphrase"};
FirstLineParsedCorrectlyHelper(response_tokens, 200, false, " ");
FirstLineParsedCorrectlyHelper(response_tokens, 200, false, "\t");
FirstLineParsedCorrectlyHelper(response_tokens, 200, false, "\t ");
FirstLineParsedCorrectlyHelper(response_tokens, 200, false, " \t");
FirstLineParsedCorrectlyHelper(response_tokens, 200, false, " \t \t ");
response_tokens[1] = "312";
FirstLineParsedCorrectlyHelper(response_tokens, 312, false, " ");
FirstLineParsedCorrectlyHelper(response_tokens, 312, false, "\t");
FirstLineParsedCorrectlyHelper(response_tokens, 312, false, "\t ");
FirstLineParsedCorrectlyHelper(response_tokens, 312, false, " \t");
FirstLineParsedCorrectlyHelper(response_tokens, 312, false, " \t \t ");
// Who knows what the future may hold w.r.t. response codes?!
response_tokens[1] = "4242";
FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, " ");
FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, "\t");
FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, "\t ");
FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, " \t");
FirstLineParsedCorrectlyHelper(response_tokens, 4242, false, " \t \t ");
}
void HeaderLineTestHelper(const char* firstline, bool is_request,
const std::pair<std::string, std::string>* headers,
size_t headers_len, const char* colon,
const char* line_ending) {
BalsaHeaders balsa_headers;
BalsaFrame framer;
framer.set_is_request(is_request);
framer.set_balsa_headers(&balsa_headers);
std::string message =
CreateMessage(firstline, headers, headers_len, colon, line_ending, "");
SCOPED_TRACE(EscapeString(message));
size_t bytes_consumed = framer.ProcessInput(message.data(), message.size());
EXPECT_EQ(message.size(), bytes_consumed);
VerifyHeaderLines(headers, headers_len, *framer.headers());
}
TEST(HTTPBalsaFrame, RequestLinesParsedProperly) {
SCOPED_TRACE("Testing that lines are properly parsed.");
const char firstline[] = "GET / HTTP/1.1\r\n";
const std::pair<std::string, std::string> headers[] = {
std::pair<std::string, std::string>("foo", "bar"),
std::pair<std::string, std::string>("duck", "water"),
std::pair<std::string, std::string>("goose", "neck"),
std::pair<std::string, std::string>("key_is_fine",
"value:includes:colons"),
std::pair<std::string, std::string>("trucks",
"along\rvalue\rincluding\rslash\rrs"),
std::pair<std::string, std::string>("monster", "truck"),
std::pair<std::string, std::string>("another_key", ":colons in value"),
std::pair<std::string, std::string>("another_key", "colons in value:"),
std::pair<std::string, std::string>("another_key",
"value includes\r\n continuation"),
std::pair<std::string, std::string>("key_without_continuations",
"multiple\n in\r\n the\n value"),
std::pair<std::string, std::string>("key_without_value",
""), // empty value
std::pair<std::string, std::string>("",
"value without key"), // empty key
std::pair<std::string, std::string>("", ""), // both key and value empty
std::pair<std::string, std::string>("normal_key", "normal_value"),
};
const size_t headers_len = ABSL_ARRAYSIZE(headers);
HeaderLineTestHelper(firstline, true, headers, headers_len, ": ", "\n");
HeaderLineTestHelper(firstline, true, headers, headers_len, ": ", "\r\n");
HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t", "\n");
HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t", "\r\n");
HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t ", "\n");
HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t ", "\r\n");
HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t\t", "\n");
HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t\t", "\r\n");
HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t \t", "\n");
HeaderLineTestHelper(firstline, true, headers, headers_len, ":\t \t", "\r\n");
}
TEST(HTTPBalsaFrame, ResponseLinesParsedProperly) {
SCOPED_TRACE("ResponseLineParsedProperly");
const char firstline[] = "HTTP/1.0 200 A reason\tphrase\r\n";
const std::pair<std::string, std::string> headers[] = {
std::pair<std::string, std::string>("foo", "bar"),
std::pair<std::string, std::string>("duck", "water"),
std::pair<std::string, std::string>("goose", "neck"),
std::pair<std::string, std::string>("key_is_fine",
"value:includes:colons"),
std::pair<std::string, std::string>("trucks",
"along\rvalue\rincluding\rslash\rrs"),
std::pair<std::string, std::string>("monster", "truck"),
std::pair<std::string, std::string>("another_key", ":colons in value"),
std::pair<std::string, std::string>("another_key", "colons in value:"),
std::pair<std::string, std::string>("another_key",
"value includes\r\n continuation"),
std::pair<std::string, std::string>("key_includes_no_continuations",
"multiple\n in\r\n the\n value"),
std::pair<std::string, std::string>("key_without_value",
""), // empty value
std::pair<std::string, std::string>("",
"value without key"), // empty key
std::pair<std::string, std::string>("", ""), // both key and value empty
std::pair<std::string, std::string>("normal_key", "normal_value"),
};
const size_t headers_len = ABSL_ARRAYSIZE(headers);
HeaderLineTestHelper(firstline, false, headers, headers_len, ": ", "\n");
HeaderLineTestHelper(firstline, false, headers, headers_len, ": ", "\r\n");
HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t", "\n");
HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t", "\r\n");
HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t ", "\n");
HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t ", "\r\n");
HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t\t", "\n");
HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t\t", "\r\n");
HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t \t", "\n");
HeaderLineTestHelper(firstline, false, headers, headers_len, ":\t \t",
"\r\n");
}
void WhitespaceHeaderTestHelper(
const std::string& message, bool is_request,
BalsaFrameEnums::ErrorCode expected_error_code) {
BalsaHeaders balsa_headers;
BalsaFrame framer;
framer.set_is_request(is_request);
framer.set_balsa_headers(&balsa_headers);
SCOPED_TRACE(EscapeString(message));
size_t bytes_consumed = framer.ProcessInput(message.data(), message.size());
EXPECT_EQ(message.size(), bytes_consumed);
if (expected_error_code == BalsaFrameEnums::NO_ERROR) {
EXPECT_EQ(false, framer.Error());
} else {
EXPECT_EQ(true, framer.Error());
}
EXPECT_EQ(expected_error_code, framer.ErrorCode());
}
TEST(HTTPBalsaFrame, WhitespaceInRequestsProcessedProperly) {
SCOPED_TRACE(
"Test that a request header with a line with spaces and no "
"data generates an error.");
WhitespaceHeaderTestHelper(
"GET / HTTP/1.1\r\n"
" \r\n"
"\r\n",
true, BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER);
WhitespaceHeaderTestHelper(
"GET / HTTP/1.1\r\n"
" \r\n"
"test: test\r\n"
"\r\n",
true, BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER);
SCOPED_TRACE("Test proper handling for line continuation in requests.");
WhitespaceHeaderTestHelper(
"GET / HTTP/1.1\r\n"
"test: test\r\n"
" continued\r\n"
"\r\n",
true, BalsaFrameEnums::NO_ERROR);
WhitespaceHeaderTestHelper(
"GET / HTTP/1.1\r\n"
"test: test\r\n"
" \r\n"
"\r\n",
true, BalsaFrameEnums::NO_ERROR);
}
TEST(HTTPBalsaFrame, WhitespaceInResponsesProcessedProperly) {
SCOPED_TRACE(
"Test that a response header with a line with spaces and no "
"data generates an error.");
WhitespaceHeaderTestHelper(
"HTTP/1.0 200 Reason\r\n"
" \r\nContent-Length: 0\r\n"
"\r\n",
false, BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER);
SCOPED_TRACE("Test proper handling for line continuation in responses.");
WhitespaceHeaderTestHelper(
"HTTP/1.0 200 Reason\r\n"
"test: test\r\n"
" continued\r\n"
"Content-Length: 0\r\n"
"\r\n",
false, BalsaFrameEnums::NO_ERROR);
WhitespaceHeaderTestHelper(
"HTTP/1.0 200 Reason\r\n"
"test: test\r\n"
" \r\n"
"Content-Length: 0\r\n"
"\r\n",
false, BalsaFrameEnums::NO_ERROR);
}
TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForTrivialRequest) {
std::string message = "GET /foobar HTTP/1.0\r\n\n";
FakeHeaders fake_headers;
{
InSequence s;
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("GET /foobar HTTP/1.0", "GET",
"/foobar", "HTTP/1.0"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
ASSERT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
}
TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForRequestWithBlankLines) {
std::string message = "\n\n\r\n\nGET /foobar HTTP/1.0\r\n\n";
FakeHeaders fake_headers;
{
InSequence s1;
// Yes, that is correct-- the framer 'eats' the blank-lines at the beginning
// and never notifies the visitor.
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("GET /foobar HTTP/1.0", "GET",
"/foobar", "HTTP/1.0"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput("GET /foobar HTTP/1.0\r\n\n"));
ASSERT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForRequestWitSplithBlankLines) {
std::string blanks =
"\n"
"\n"
"\r\n"
"\n";
std::string header_input = "GET /foobar HTTP/1.0\r\n\n";
FakeHeaders fake_headers;
{
InSequence s1;
// Yes, that is correct-- the framer 'eats' the blank-lines at the beginning
// and never notifies the visitor.
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("GET /foobar HTTP/1.0", "GET",
"/foobar", "HTTP/1.0"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput("GET /foobar HTTP/1.0\r\n\n"));
ASSERT_EQ(blanks.size(),
balsa_frame_.ProcessInput(blanks.data(), blanks.size()));
ASSERT_EQ(header_input.size(), balsa_frame_.ProcessInput(
header_input.data(), header_input.size()));
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForRequestWithZeroContentLength) {
std::string message =
"PUT /search?q=fo HTTP/1.1\n"
"content-length: 0 \n"
"\n";
FakeHeaders fake_headers;
fake_headers.AddKeyValue("content-length", "0");
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("PUT /search?q=fo HTTP/1.1", "PUT",
"/search?q=fo", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
ASSERT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForRequestWithMissingContentLength) {
std::string message =
"PUT /search?q=fo HTTP/1.1\n"
"\n";
auto error_code =
BalsaFrameEnums::BalsaFrameEnums::REQUIRED_BODY_BUT_NO_CONTENT_LENGTH;
EXPECT_CALL(visitor_mock_, HandleError(error_code));
balsa_frame_.ProcessInput(message.data(), message.size());
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(error_code, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForPermittedMissingContentLength) {
std::string message =
"PUT /search?q=fo HTTP/1.1\n"
"\n";
FakeHeaders fake_headers;
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("PUT /search?q=fo HTTP/1.1", "PUT",
"/search?q=fo", "HTTP/1.1"));
}
ASSERT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
}
TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenNothingInConnectionLine) {
// This is similar to the test above, but we use different whitespace
// throughout.
std::string message =
"PUT \t /search?q=fo \t HTTP/1.1 \t \r\n"
"Connection:\r\n"
"content-length: 0\r\n"
"\r\n";
FakeHeaders fake_headers;
fake_headers.AddKeyValue("Connection", "");
fake_headers.AddKeyValue("content-length", "0");
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1",
"PUT", "/search?q=fo", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
ASSERT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
}
TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenOnlyCommentsInConnectionLine) {
// This is similar to the test above, but we use different whitespace
// throughout.
std::string message =
"PUT \t /search?q=fo \t HTTP/1.1 \t \r\n"
"Connection: ,,,,,,,,\r\n"
"content-length: 0\r\n"
"\r\n";
FakeHeaders fake_headers;
fake_headers.AddKeyValue("Connection", ",,,,,,,,");
fake_headers.AddKeyValue("content-length", "0");
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1",
"PUT", "/search?q=fo", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
ASSERT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForRequestWithZeroContentLengthMk2) {
// This is similar to the test above, but we use different whitespace
// throughout.
std::string message =
"PUT \t /search?q=fo \t HTTP/1.1 \t \r\n"
"Connection: \t close \t\r\n"
"content-length: \t\t 0 \t\t \r\n"
"\r\n";
FakeHeaders fake_headers;
fake_headers.AddKeyValue("Connection", "close");
fake_headers.AddKeyValue("content-length", "0");
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1",
"PUT", "/search?q=fo", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
ASSERT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
}
TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenNoVisitorIsAssigned) {
std::string headers =
"GET / HTTP/1.1\r\n"
"Connection: close\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
std::string trailer =
"crass: monkeys\r\n"
"funky: monkeys\r\n"
"\r\n";
balsa_frame_.set_balsa_visitor(nullptr);
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, NothingBadHappensWhenNoVisitorIsAssignedInResponse) {
std::string headers =
"HTTP/1.1 502 Bad Gateway\r\n"
"Connection: close\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
std::string trailer =
"crass: monkeys\r\n"
"funky: monkeys\r\n"
"\r\n";
balsa_frame_.set_is_request(false);
balsa_frame_.set_balsa_trailer(&trailer_);
balsa_frame_.set_balsa_visitor(nullptr);
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
const absl::string_view crass = trailer_.GetHeader("crass");
EXPECT_EQ("monkeys", crass);
const absl::string_view funky = trailer_.GetHeader("funky");
EXPECT_EQ("monkeys", funky);
}
TEST_F(HTTPBalsaFrameTest, TransferEncodingIdentityIsIgnored) {
std::string headers =
"GET / HTTP/1.1\r\n"
"Connection: close\r\n"
"transfer-encoding: identity\r\n"
"content-length: 10\r\n"
"\r\n";
std::string body = "1234567890";
std::string message = (headers + body);
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
NothingBadHappensWhenAVisitorIsChangedToNULLInMidParsing) {
std::string headers =
"GET / HTTP/1.1\r\n"
"Connection: close\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
std::string trailer =
"crass: monkeys\r\n"
"funky: monkeys\r\n"
"\n";
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
balsa_frame_.set_balsa_visitor(nullptr);
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
ASSERT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
NothingBadHappensWhenAVisitorIsChangedToNULLInMidParsingInTrailer) {
std::string headers =
"HTTP/1.1 503 Server Not Available\r\n"
"Connection: close\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
std::string trailer =
"crass: monkeys\r\n"
"funky: monkeys\r\n"
"\n";
balsa_frame_.set_is_request(false);
balsa_frame_.set_balsa_trailer(&trailer_);
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
balsa_frame_.set_balsa_visitor(nullptr);
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
ASSERT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
const absl::string_view crass = trailer_.GetHeader("crass");
EXPECT_EQ("monkeys", crass);
const absl::string_view funky = trailer_.GetHeader("funky");
EXPECT_EQ("monkeys", funky);
}
TEST_F(HTTPBalsaFrameTest,
NothingBadHappensWhenNoVisitorAssignedAndChunkingErrorOccurs) {
std::string headers =
"GET / HTTP/1.1\r\n"
"Connection: close\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\r\n" // should overflow
"0\r\n";
std::string trailer =
"crass: monkeys\r\n"
"funky: monkeys\r\n"
"\n";
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
balsa_frame_.set_balsa_visitor(nullptr);
EXPECT_GE(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, FramerRecognizesSemicolonAsChunkSizeDelimiter) {
std::string headers =
"GET / HTTP/1.1\r\n"
"Connection: close\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"8; foo=bar\r\n"
"deadbeef\r\n"
"0\r\n"
"\r\n";
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
balsa_frame_.set_balsa_visitor(&visitor_mock_);
EXPECT_CALL(visitor_mock_, OnChunkLength(8));
EXPECT_CALL(visitor_mock_, OnChunkLength(0));
EXPECT_CALL(visitor_mock_, OnChunkExtensionInput("; foo=bar"));
EXPECT_CALL(visitor_mock_, OnChunkExtensionInput(""));
EXPECT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
}
TEST_F(HTTPBalsaFrameTest, NonAsciiCharacterInChunkLength) {
std::string headers =
"GET / HTTP/1.1\r\n"
"Connection: close\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"555\xAB\r\n" // Character overflowing 7 bits, see b/20238315
"0\r\n";
std::string trailer =
"crass: monkeys\r\n"
"funky: monkeys\r\n"
"\n";
FakeHeaders fake_headers;
fake_headers.AddKeyValue("Connection", "close");
fake_headers.AddKeyValue("transfer-encoding", "chunked");
auto error_code = BalsaFrameEnums::INVALID_CHUNK_LENGTH;
{
InSequence s1;
EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET",
"/", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnRawBodyInput("555\xAB"));
EXPECT_CALL(visitor_mock_, HandleError(error_code));
}
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
EXPECT_EQ(strlen("555\xAB"),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::INVALID_CHUNK_LENGTH, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, VisitorCalledAsExpectedWhenChunkingOverflowOccurs) {
std::string headers =
"GET / HTTP/1.1\r\n"
"Connection: close\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\r\n" // should overflow
"0\r\n";
std::string trailer =
"crass: monkeys\r\n"
"funky: monkeys\r\n"
"\n";
const char* chunk_read_before_overflow = "FFFFFFFFFFFFFFFFF";
FakeHeaders fake_headers;
fake_headers.AddKeyValue("Connection", "close");
fake_headers.AddKeyValue("transfer-encoding", "chunked");
auto error_code = BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW;
{
InSequence s1;
EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET",
"/", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnRawBodyInput(chunk_read_before_overflow));
EXPECT_CALL(visitor_mock_, HandleError(error_code));
}
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
EXPECT_EQ(strlen(chunk_read_before_overflow),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
VisitorCalledAsExpectedWhenInvalidChunkLengthOccurs) {
std::string headers =
"GET / HTTP/1.1\r\n"
"Connection: close\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"12z123 \r\n" // invalid chunk length
"0\r\n";
std::string trailer =
"crass: monkeys\r\n"
"funky: monkeys\r\n"
"\n";
FakeHeaders fake_headers;
fake_headers.AddKeyValue("Connection", "close");
fake_headers.AddKeyValue("transfer-encoding", "chunked");
auto error_code = BalsaFrameEnums::INVALID_CHUNK_LENGTH;
{
InSequence s1;
EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET",
"/", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnRawBodyInput("12z"));
EXPECT_CALL(visitor_mock_, HandleError(error_code));
}
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
EXPECT_EQ(3u, balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::INVALID_CHUNK_LENGTH, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForRequestWithContentLength) {
std::string message_headers =
"PUT \t /search?q=fo \t HTTP/1.1 \t \r\n"
"content-length: \t\t 20 \t\t \r\n"
"\r\n";
std::string message_body = "12345678901234567890";
std::string message =
std::string(message_headers) + std::string(message_body);
FakeHeaders fake_headers;
fake_headers.AddKeyValue("content-length", "20");
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1",
"PUT", "/search?q=fo", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnRawBodyInput(message_body));
EXPECT_CALL(visitor_mock_, OnBodyChunkInput(message_body));
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
ASSERT_EQ(message_headers.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
ASSERT_EQ(message_body.size(),
balsa_frame_.ProcessInput(message.data() + message_headers.size(),
message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForRequestWithOneCharContentLength) {
std::string message_headers =
"PUT \t /search?q=fo \t HTTP/1.1 \t \r\n"
"content-length: \t\t 2 \t\t \r\n"
"\r\n";
std::string message_body = "12";
std::string message =
std::string(message_headers) + std::string(message_body);
FakeHeaders fake_headers;
fake_headers.AddKeyValue("content-length", "2");
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("PUT \t /search?q=fo \t HTTP/1.1",
"PUT", "/search?q=fo", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnRawBodyInput(message_body));
EXPECT_CALL(visitor_mock_, OnBodyChunkInput(message_body));
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
ASSERT_EQ(message_headers.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
ASSERT_EQ(message_body.size(),
balsa_frame_.ProcessInput(message.data() + message_headers.size(),
message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForRequestWithTransferEncoding) {
std::string message_headers =
"DELETE /search?q=fo \t HTTP/1.1 \t \r\n"
"trAnsfer-eNcoding: chunked\r\n"
"\r\n";
std::string message_body =
"A chunkjed extension \r\n"
"01234567890 more crud including numbers 123123\r\n"
"3f\n"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
"0 last one\r\n"
"\r\n";
std::string message_body_data =
"0123456789"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
std::string message =
std::string(message_headers) + std::string(message_body);
FakeHeaders fake_headers;
fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("DELETE /search?q=fo \t HTTP/1.1",
"DELETE", "/search?q=fo", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnChunkLength(10));
EXPECT_CALL(visitor_mock_,
OnChunkExtensionInput(" chunkjed extension "));
EXPECT_CALL(visitor_mock_, OnChunkLength(63));
EXPECT_CALL(visitor_mock_, OnChunkExtensionInput(""));
EXPECT_CALL(visitor_mock_, OnChunkLength(0));
EXPECT_CALL(visitor_mock_, OnChunkExtensionInput(" last one"));
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
std::string body_input;
EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
.WillRepeatedly([&body_input](absl::string_view input) {
absl::StrAppend(&body_input, input);
});
std::string body_data;
EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_))
.WillRepeatedly([&body_data](absl::string_view input) {
absl::StrAppend(&body_data, input);
});
EXPECT_CALL(visitor_mock_, OnTrailerInput(_)).Times(0);
ASSERT_EQ(message_headers.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_EQ(message_body.size(),
balsa_frame_.ProcessInput(message.data() + message_headers.size(),
message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
EXPECT_EQ(message_body, body_input);
EXPECT_EQ(message_body_data, body_data);
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForRequestWithTransferEncodingAndTrailers) {
std::string message_headers =
"DELETE /search?q=fo \t HTTP/1.1 \t \r\n"
"trAnsfer-eNcoding: chunked\r\n"
"another_random_header: \r\n"
" \t \n"
" \t includes a continuation\n"
"\r\n";
std::string message_body =
"A chunkjed extension \r\n"
"01234567890 more crud including numbers 123123\r\n"
"3f\n"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
"1 \r\n"
"x \r\n"
"0 last one\r\n";
std::string trailer_data =
"a_trailer_key: and a trailer value\r\n"
"\r\n";
std::string message_body_data =
"0123456789"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
std::string message = (std::string(message_headers) +
std::string(message_body) + std::string(trailer_data));
FakeHeaders fake_headers;
fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
fake_headers.AddKeyValue("another_random_header", "includes a continuation");
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("DELETE /search?q=fo \t HTTP/1.1",
"DELETE", "/search?q=fo", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnChunkLength(10));
EXPECT_CALL(visitor_mock_, OnChunkLength(63));
EXPECT_CALL(visitor_mock_, OnChunkLength(1));
EXPECT_CALL(visitor_mock_, OnChunkLength(0));
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
std::string body_input;
EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
.WillRepeatedly([&body_input](absl::string_view input) {
absl::StrAppend(&body_input, input);
});
std::string body_data;
EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_))
.WillRepeatedly([&body_data](absl::string_view input) {
absl::StrAppend(&body_data, input);
});
EXPECT_CALL(visitor_mock_, OnTrailerInput(trailer_data));
EXPECT_CALL(visitor_mock_, OnChunkExtensionInput(_)).Times(AnyNumber());
ASSERT_EQ(message_headers.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_EQ(message_body.size() + trailer_data.size(),
balsa_frame_.ProcessInput(message.data() + message_headers.size(),
message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
EXPECT_EQ(message_body, body_input);
EXPECT_EQ(message_body_data, body_data);
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyWithRequestFirstLineWarningWithOnlyMethod) {
std::string message = "GET\n";
FakeHeaders fake_headers;
auto error_code = BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD;
{
InSequence s;
EXPECT_CALL(visitor_mock_, HandleWarning(error_code));
EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET", "GET", "", ""));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyWithRequestFirstLineWarningWithOnlyMethodAndWS) {
std::string message = "GET \n";
FakeHeaders fake_headers;
auto error_code = BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD;
{
InSequence s;
EXPECT_CALL(visitor_mock_, HandleWarning(error_code));
// The flag setting here intentionally alters the framer's behavior with
// trailing whitespace.
EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET ", "GET", "", ""));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyWithRequestFirstLineWarningWithMethodAndURI) {
std::string message = "GET /uri\n";
FakeHeaders fake_headers;
auto error_code =
BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI;
{
InSequence s;
EXPECT_CALL(visitor_mock_, HandleWarning(error_code));
EXPECT_CALL(visitor_mock_,
OnRequestFirstLineInput("GET /uri", "GET", "/uri", ""));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyWithResponseFirstLineError) {
std::string message = "HTTP/1.1\n\n";
FakeHeaders fake_headers;
balsa_frame_.set_is_request(false);
auto error_code = BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION;
{
InSequence s;
EXPECT_CALL(visitor_mock_, HandleError(error_code));
// The function returns before any of the following is called.
EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput).Times(0);
EXPECT_CALL(visitor_mock_, ProcessHeaders(_)).Times(0);
EXPECT_CALL(visitor_mock_, HeaderDone()).Times(0);
EXPECT_CALL(visitor_mock_, MessageDone()).Times(0);
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(_)).Times(0);
EXPECT_GE(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, FlagsErrorWithContentLengthOverflow) {
std::string message =
"HTTP/1.0 200 OK\r\n"
"content-length: 9999999999999999999999999999999999999999\n"
"\n";
balsa_frame_.set_is_request(false);
auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH;
EXPECT_CALL(visitor_mock_, HandleError(error_code));
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, FlagsErrorWithInvalidResponseCode) {
std::string message =
"HTTP/1.0 x OK\r\n"
"\n";
balsa_frame_.set_is_request(false);
auto error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT;
EXPECT_CALL(visitor_mock_, HandleError(error_code));
EXPECT_GE(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, FlagsErrorWithOverflowingResponseCode) {
std::string message =
"HTTP/1.0 999999999999999999999999999999999999999 OK\r\n"
"\n";
balsa_frame_.set_is_request(false);
auto error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT;
EXPECT_CALL(visitor_mock_, HandleError(error_code));
EXPECT_GE(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, FlagsErrorWithInvalidContentLength) {
std::string message =
"HTTP/1.0 200 OK\r\n"
"content-length: xxx\n"
"\n";
balsa_frame_.set_is_request(false);
auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH;
EXPECT_CALL(visitor_mock_, HandleError(error_code));
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, FlagsErrorWithNegativeContentLengthValue) {
std::string message =
"HTTP/1.0 200 OK\r\n"
"content-length: -20\n"
"\n";
balsa_frame_.set_is_request(false);
auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH;
EXPECT_CALL(visitor_mock_, HandleError(error_code));
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, FlagsErrorWithEmptyContentLengthValue) {
std::string message =
"HTTP/1.0 200 OK\r\n"
"content-length: \n"
"\n";
balsa_frame_.set_is_request(false);
auto error_code = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH;
EXPECT_CALL(visitor_mock_, HandleError(error_code));
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForTrivialResponse) {
std::string message =
"HTTP/1.0 200 OK\r\n"
"content-length: 0\n"
"\n";
FakeHeaders fake_headers;
fake_headers.AddKeyValue("content-length", "0");
balsa_frame_.set_is_request(false);
{
InSequence s;
EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput(
"HTTP/1.0 200 OK", "HTTP/1.0", "200", "OK"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message));
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForResponseWithSplitBlankLines) {
std::string blanks =
"\n"
"\r\n"
"\r\n";
std::string header_input =
"HTTP/1.0 200 OK\r\n"
"content-length: 0\n"
"\n";
FakeHeaders fake_headers;
fake_headers.AddKeyValue("content-length", "0");
balsa_frame_.set_is_request(false);
{
InSequence s;
EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput(
"HTTP/1.0 200 OK", "HTTP/1.0", "200", "OK"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(header_input));
EXPECT_EQ(blanks.size(),
balsa_frame_.ProcessInput(blanks.data(), blanks.size()));
EXPECT_EQ(header_input.size(), balsa_frame_.ProcessInput(
header_input.data(), header_input.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForResponseWithBlankLines) {
std::string blanks =
"\n"
"\r\n"
"\n"
"\n"
"\r\n"
"\r\n";
std::string header_input =
"HTTP/1.0 200 OK\r\n"
"content-length: 0\n"
"\n";
std::string message = blanks + header_input;
FakeHeaders fake_headers;
fake_headers.AddKeyValue("content-length", "0");
balsa_frame_.set_is_request(false);
{
InSequence s;
EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput(
"HTTP/1.0 200 OK", "HTTP/1.0", "200", "OK"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(header_input));
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, VisitorInvokedProperlyForResponseWithContentLength) {
std::string message_headers =
"HTTP/1.1 \t 200 Ok all is well\r\n"
"content-length: \t\t 20 \t\t \r\n"
"\r\n";
std::string message_body = "12345678901234567890";
std::string message =
std::string(message_headers) + std::string(message_body);
FakeHeaders fake_headers;
fake_headers.AddKeyValue("content-length", "20");
balsa_frame_.set_is_request(false);
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnResponseFirstLineInput("HTTP/1.1 \t 200 Ok all is well",
"HTTP/1.1", "200", "Ok all is well"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnRawBodyInput(message_body));
EXPECT_CALL(visitor_mock_, OnBodyChunkInput(message_body));
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
ASSERT_EQ(message_headers.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_EQ(message_body.size(),
balsa_frame_.ProcessInput(message.data() + message_headers.size(),
message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForResponseWithTransferEncoding) {
std::string message_headers =
"HTTP/1.1 \t 200 Ok all is well\r\n"
"trAnsfer-eNcoding: chunked\r\n"
"\r\n";
std::string message_body =
"A chunkjed extension \r\n"
"01234567890 more crud including numbers 123123\r\n"
"3f\n"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
"0 last one\r\n"
"\r\n";
std::string message_body_data =
"0123456789"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
std::string message =
std::string(message_headers) + std::string(message_body);
FakeHeaders fake_headers;
fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
balsa_frame_.set_is_request(false);
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnResponseFirstLineInput("HTTP/1.1 \t 200 Ok all is well",
"HTTP/1.1", "200", "Ok all is well"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnChunkLength(10));
EXPECT_CALL(visitor_mock_, OnChunkLength(63));
EXPECT_CALL(visitor_mock_, OnChunkLength(0));
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
std::string body_input;
EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
.WillRepeatedly([&body_input](absl::string_view input) {
absl::StrAppend(&body_input, input);
});
std::string body_data;
EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_))
.WillRepeatedly([&body_data](absl::string_view input) {
absl::StrAppend(&body_data, input);
});
EXPECT_CALL(visitor_mock_, OnTrailerInput(_)).Times(0);
ASSERT_EQ(message_headers.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_EQ(message_body.size(),
balsa_frame_.ProcessInput(message.data() + message_headers.size(),
message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
EXPECT_EQ(message_body, body_input);
EXPECT_EQ(message_body_data, body_data);
}
TEST_F(HTTPBalsaFrameTest,
VisitorInvokedProperlyForResponseWithTransferEncodingAndTrailers) {
std::string message_headers =
"HTTP/1.1 \t 200 Ok all is well\r\n"
"trAnsfer-eNcoding: chunked\r\n"
"\r\n";
std::string message_body =
"A chunkjed extension \r\n"
"01234567890 more crud including numbers 123123\r\n"
"3f\n"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
"0 last one\r\n";
std::string trailer_data =
"a_trailer_key: and a trailer value\r\n"
"\r\n";
std::string message_body_data =
"0123456789"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
std::string message = (std::string(message_headers) +
std::string(message_body) + std::string(trailer_data));
FakeHeaders fake_headers;
fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
FakeHeaders fake_headers_in_trailer;
fake_headers_in_trailer.AddKeyValue("a_trailer_key", "and a trailer value");
balsa_frame_.set_is_request(false);
balsa_frame_.set_balsa_trailer(&trailer_);
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnResponseFirstLineInput("HTTP/1.1 \t 200 Ok all is well",
"HTTP/1.1", "200", "Ok all is well"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnChunkLength(10));
EXPECT_CALL(visitor_mock_, OnChunkLength(63));
EXPECT_CALL(visitor_mock_, OnChunkLength(0));
EXPECT_CALL(visitor_mock_, ProcessTrailers(fake_headers_in_trailer));
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
std::string body_input;
EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
.WillRepeatedly([&body_input](absl::string_view input) {
absl::StrAppend(&body_input, input);
});
std::string body_data;
EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_))
.WillRepeatedly([&body_data](absl::string_view input) {
absl::StrAppend(&body_data, input);
});
EXPECT_CALL(visitor_mock_, OnTrailerInput(trailer_data));
ASSERT_EQ(message_headers.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_EQ(message_body.size() + trailer_data.size(),
balsa_frame_.ProcessInput(message.data() + message_headers.size(),
message.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
EXPECT_EQ(message_body, body_input);
EXPECT_EQ(message_body_data, body_data);
const absl::string_view a_trailer_key = trailer_.GetHeader("a_trailer_key");
EXPECT_EQ("and a trailer value", a_trailer_key);
}
TEST_F(
HTTPBalsaFrameTest,
VisitorInvokedProperlyForResponseWithTransferEncodingAndTrailersBytePer) {
std::string message_headers =
"HTTP/1.1 \t 200 Ok all is well\r\n"
"trAnsfer-eNcoding: chunked\r\n"
"\r\n";
std::string message_body =
"A chunkjed extension \r\n"
"01234567890 more crud including numbers 123123\r\n"
"3f\n"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
"0 last one\r\n";
std::string trailer_data =
"a_trailer_key: and a trailer value\r\n"
"\r\n";
std::string message_body_data =
"0123456789"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
std::string message = (std::string(message_headers) +
std::string(message_body) + std::string(trailer_data));
FakeHeaders fake_headers;
fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
FakeHeaders fake_headers_in_trailer;
fake_headers_in_trailer.AddKeyValue("a_trailer_key", "and a trailer value");
balsa_frame_.set_is_request(false);
balsa_frame_.set_balsa_trailer(&trailer_);
{
InSequence s1;
EXPECT_CALL(visitor_mock_,
OnResponseFirstLineInput("HTTP/1.1 \t 200 Ok all is well",
"HTTP/1.1", "200", "Ok all is well"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnChunkLength(10));
EXPECT_CALL(visitor_mock_, OnChunkLength(63));
EXPECT_CALL(visitor_mock_, OnChunkLength(0));
EXPECT_CALL(visitor_mock_, ProcessTrailers(fake_headers_in_trailer));
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(message_headers));
std::string body_input;
EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
.WillRepeatedly([&body_input](absl::string_view input) {
absl::StrAppend(&body_input, input);
});
std::string body_data;
EXPECT_CALL(visitor_mock_, OnBodyChunkInput(_))
.WillRepeatedly([&body_data](absl::string_view input) {
absl::StrAppend(&body_data, input);
});
std::string trailer_input;
EXPECT_CALL(visitor_mock_, OnTrailerInput(_))
.WillRepeatedly([&trailer_input](absl::string_view input) {
absl::StrAppend(&trailer_input, input);
});
for (size_t i = 0; i < message.size(); ++i) {
ASSERT_EQ(1u, balsa_frame_.ProcessInput(message.data() + i, 1));
}
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame_.ErrorCode());
EXPECT_EQ(message_body, body_input);
EXPECT_EQ(message_body_data, body_data);
EXPECT_EQ(trailer_data, trailer_input);
const absl::string_view a_trailer_key = trailer_.GetHeader("a_trailer_key");
EXPECT_EQ("and a trailer value", a_trailer_key);
}
TEST(HTTPBalsaFrame,
VisitorInvokedProperlyForResponseWithTransferEncodingAndTrailersRandom) {
TestSeed seed;
seed.Initialize(GetQuicheFlag(FLAGS_randseed));
RandomEngine rng;
rng.seed(seed.GetSeed());
for (int i = 0; i < 1000; ++i) {
std::string message_headers =
"HTTP/1.1 \t 200 Ok all is well\r\n"
"trAnsfer-eNcoding: chunked\r\n"
"\r\n";
std::string message_body =
"A chunkjed extension \r\n"
"01234567890 more crud including numbers 123123\r\n"
"3f\n"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
"0 last one\r\n";
std::string trailer_data =
"a_trailer_key: and a trailer value\r\n"
"\r\n";
std::string message_body_data =
"0123456789"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
std::string message =
(std::string(message_headers) + std::string(message_body) +
std::string(trailer_data));
FakeHeaders fake_headers;
fake_headers.AddKeyValue("trAnsfer-eNcoding", "chunked");
FakeHeaders fake_headers_in_trailer;
fake_headers_in_trailer.AddKeyValue("a_trailer_key", "and a trailer value");
StrictMock<BalsaVisitorMock> visitor_mock;
BalsaHeaders headers;
BalsaHeaders trailer;
BalsaFrame balsa_frame;
balsa_frame.set_is_request(false);
balsa_frame.set_balsa_headers(&headers);
balsa_frame.set_balsa_trailer(&trailer);
balsa_frame.set_balsa_visitor(&visitor_mock);
{
InSequence s1;
EXPECT_CALL(visitor_mock, OnResponseFirstLineInput(
"HTTP/1.1 \t 200 Ok all is well",
"HTTP/1.1", "200", "Ok all is well"));
EXPECT_CALL(visitor_mock, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock, HeaderDone());
EXPECT_CALL(visitor_mock, ProcessTrailers(fake_headers_in_trailer));
EXPECT_CALL(visitor_mock, MessageDone());
}
EXPECT_CALL(visitor_mock, OnHeaderInput(message_headers));
std::string body_input;
EXPECT_CALL(visitor_mock, OnRawBodyInput(_))
.WillRepeatedly([&body_input](absl::string_view input) {
absl::StrAppend(&body_input, input);
});
std::string body_data;
EXPECT_CALL(visitor_mock, OnBodyChunkInput(_))
.WillRepeatedly([&body_data](absl::string_view input) {
absl::StrAppend(&body_data, input);
});
std::string trailer_input;
EXPECT_CALL(visitor_mock, OnTrailerInput(_))
.WillRepeatedly([&trailer_input](absl::string_view input) {
absl::StrAppend(&trailer_input, input);
});
EXPECT_CALL(visitor_mock, OnChunkLength(_)).Times(AtLeast(1));
EXPECT_CALL(visitor_mock, OnChunkExtensionInput(_)).Times(AtLeast(1));
size_t count = 0;
size_t total_processed = 0;
for (size_t i = 0; i < message.size();) {
auto dist = std::uniform_int_distribution<>(0, message.size() - i + 1);
count = dist(rng);
size_t processed = balsa_frame.ProcessInput(message.data() + i, count);
ASSERT_GE(count, processed);
total_processed += processed;
i += processed;
}
EXPECT_EQ(message.size(), total_processed);
EXPECT_TRUE(balsa_frame.MessageFullyRead());
EXPECT_FALSE(balsa_frame.Error());
EXPECT_EQ(BalsaFrameEnums::NO_ERROR, balsa_frame.ErrorCode());
EXPECT_EQ(message_body, body_input);
EXPECT_EQ(message_body_data, body_data);
EXPECT_EQ(trailer_data, trailer_input);
const absl::string_view a_trailer_key = trailer.GetHeader("a_trailer_key");
EXPECT_EQ("and a trailer value", a_trailer_key);
}
}
TEST_F(HTTPBalsaFrameTest,
AppropriateActionTakenWhenHeadersTooLongWithTooMuchInput) {
std::string message =
"GET /asflkasfdhjsafdkljhasfdlkjhasdflkjhsafdlkjhh HTTP/1.1\r\n"
"\r\n";
const size_t kAmountLessThanHeaderLen = 10;
ASSERT_LE(kAmountLessThanHeaderLen, message.size());
auto error_code = BalsaFrameEnums::HEADERS_TOO_LONG;
EXPECT_CALL(visitor_mock_, HandleError(error_code));
balsa_frame_.set_max_header_length(message.size() - kAmountLessThanHeaderLen);
ASSERT_EQ(balsa_frame_.max_header_length(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, AppropriateActionTakenWhenHeadersTooLongWithBody) {
std::string message =
"PUT /foo HTTP/1.1\r\n"
"Content-Length: 4\r\n"
"header: xxxxxxxxx\r\n\r\n"
"B"; // body begin
auto error_code = BalsaFrameEnums::HEADERS_TOO_LONG;
EXPECT_CALL(visitor_mock_, HandleError(error_code));
// -2 because we have 1 byte of body, and we want to refuse
// this.
balsa_frame_.set_max_header_length(message.size() - 2);
ASSERT_EQ(balsa_frame_.max_header_length(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, AppropriateActionTakenWhenHeadersTooLongWhenReset) {
std::string message =
"GET /asflkasfdhjsafdkljhasfdlkjhasdflkjhsafdlkjhh HTTP/1.1\r\n"
"\r\n";
const size_t kAmountLessThanHeaderLen = 10;
ASSERT_LE(kAmountLessThanHeaderLen, message.size());
auto error_code = BalsaFrameEnums::HEADERS_TOO_LONG;
ASSERT_EQ(message.size() - 2,
balsa_frame_.ProcessInput(message.data(), message.size() - 2));
// Now set max header length to something smaller.
balsa_frame_.set_max_header_length(message.size() - kAmountLessThanHeaderLen);
EXPECT_CALL(visitor_mock_, HandleError(error_code));
ASSERT_EQ(0u,
balsa_frame_.ProcessInput(message.data() + message.size() - 2, 2));
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, balsa_frame_.ErrorCode());
}
class BalsaFrameParsingTest : public QuicheTest {
protected:
void SetUp() override {
balsa_frame_.set_http_validation_policy(
HttpValidationPolicy::CreateDefault());
balsa_frame_.set_is_request(true);
balsa_frame_.set_balsa_headers(&headers_);
balsa_frame_.set_balsa_visitor(&visitor_mock_);
}
void TestEmptyHeaderKeyHelper(const std::string& message) {
InSequence s;
EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET",
"/", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, OnHeaderInput(_));
EXPECT_CALL(visitor_mock_,
HandleError(BalsaFrameEnums::INVALID_HEADER_FORMAT));
ASSERT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
Mock::VerifyAndClearExpectations(&visitor_mock_);
}
void TestInvalidTrailerFormat(const std::string& trailer,
bool invalid_name_char) {
balsa_frame_.set_is_request(false);
balsa_frame_.set_balsa_trailer(&trailer_);
std::string headers =
"HTTP/1.0 200 ok\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
InSequence s;
EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput);
EXPECT_CALL(visitor_mock_, OnHeaderInput);
EXPECT_CALL(visitor_mock_, ProcessHeaders);
EXPECT_CALL(visitor_mock_, HeaderDone);
EXPECT_CALL(visitor_mock_, OnChunkLength(3));
EXPECT_CALL(visitor_mock_, OnChunkExtensionInput);
EXPECT_CALL(visitor_mock_, OnRawBodyInput);
EXPECT_CALL(visitor_mock_, OnBodyChunkInput);
EXPECT_CALL(visitor_mock_, OnChunkLength(0));
EXPECT_CALL(visitor_mock_, OnChunkExtensionInput);
EXPECT_CALL(visitor_mock_, OnRawBodyInput);
EXPECT_CALL(visitor_mock_, OnRawBodyInput);
const auto expected_error =
invalid_name_char ? BalsaFrameEnums::INVALID_TRAILER_NAME_CHARACTER
: BalsaFrameEnums::INVALID_TRAILER_FORMAT;
EXPECT_CALL(visitor_mock_, HandleError(expected_error)).Times(1);
EXPECT_CALL(visitor_mock_, ProcessTrailers(_)).Times(0);
EXPECT_CALL(visitor_mock_, MessageDone()).Times(0);
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(expected_error, balsa_frame_.ErrorCode());
Mock::VerifyAndClearExpectations(&visitor_mock_);
}
BalsaHeaders headers_;
BalsaHeaders trailer_;
BalsaFrame balsa_frame_;
StrictMock<BalsaVisitorMock> visitor_mock_;
};
TEST_F(BalsaFrameParsingTest, AppropriateActionTakenWhenHeaderColonsAreFunny) {
// Believe it or not, the following message is not structured willy-nilly.
// It is structured so that both codepaths in both SSE2 and non SSE2 paths
// for finding colons are exersized.
std::string message =
"GET / HTTP/1.1\r\n"
"a\r\n"
"b\r\n"
"c\r\n"
"d\r\n"
"e\r\n"
"f\r\n"
"g\r\n"
"h\r\n"
"i:\r\n"
"j\r\n"
"k\r\n"
"l\r\n"
"m\r\n"
"n\r\n"
"o\r\n"
"p\r\n"
"q\r\n"
"r\r\n"
"s\r\n"
"t\r\n"
"u\r\n"
"v\r\n"
"w\r\n"
"x\r\n"
"y\r\n"
"z\r\n"
"A\r\n"
"B\r\n"
": val\r\n"
"\r\n";
EXPECT_CALL(visitor_mock_, OnRequestFirstLineInput("GET / HTTP/1.1", "GET",
"/", "HTTP/1.1"));
EXPECT_CALL(visitor_mock_, OnHeaderInput(_));
EXPECT_CALL(visitor_mock_,
HandleWarning(BalsaFrameEnums::HEADER_MISSING_COLON))
.Times(27);
EXPECT_CALL(visitor_mock_,
HandleError(BalsaFrameEnums::INVALID_HEADER_FORMAT));
ASSERT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
}
TEST_F(BalsaFrameParsingTest, ErrorWhenHeaderKeyIsEmpty) {
std::string firstKeyIsEmpty =
"GET / HTTP/1.1\r\n"
": \r\n"
"a:b\r\n"
"c:d\r\n"
"\r\n";
TestEmptyHeaderKeyHelper(firstKeyIsEmpty);
balsa_frame_.Reset();
std::string laterKeyIsEmpty =
"GET / HTTP/1.1\r\n"
"a:b\r\n"
": \r\n"
"c:d\r\n"
"\r\n";
TestEmptyHeaderKeyHelper(laterKeyIsEmpty);
}
TEST_F(BalsaFrameParsingTest, InvalidTrailerFormat) {
std::string trailer =
":monkeys\n"
"\r\n";
TestInvalidTrailerFormat(trailer, false);
balsa_frame_.Reset();
std::string trailer2 =
" \r\n"
"test: test\r\n"
"\r\n";
TestInvalidTrailerFormat(trailer2, true);
balsa_frame_.Reset();
std::string trailer3 =
"a: b\r\n"
": test\r\n"
"\r\n";
TestInvalidTrailerFormat(trailer3, false);
}
TEST_F(HTTPBalsaFrameTest,
EnsureHeaderFramingFoundWithVariousCombinationsOfRN_RN) {
const std::string message =
"GET / HTTP/1.1\r\n"
"content-length: 0\r\n"
"a\r\n"
"b\r\n"
"c\r\n"
"d\r\n"
"e\r\n"
"f\r\n"
"g\r\n"
"h\r\n"
"i\r\n"
"\r\n";
EXPECT_EQ(message.size(),