blob: 4618bbcf3b22086def731665faa0a9e7d715fecd [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, OnHeader, (absl::string_view key, absl::string_view value),
(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_trailer(&trailer_);
balsa_frame_.set_balsa_visitor(&visitor_mock_);
balsa_frame_.set_is_request(true);
EXPECT_CALL(visitor_mock_, OnHeader).Times(AnyNumber());
}
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::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);
}
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);
}
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::BALSA_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, 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;
// OnHeader() visitor method is called as soon as headers are parsed.
EXPECT_CALL(visitor_mock_, OnHeader("Connection", "close"));
EXPECT_CALL(visitor_mock_, OnHeader("transfer-encoding", "chunked"));
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()));
EXPECT_CALL(visitor_mock_, OnHeader("crass", "monkeys"));
EXPECT_CALL(visitor_mock_, OnHeader("funky", "monkeys"));
FakeHeaders fake_trailers;
fake_trailers.AddKeyValue("crass", "monkeys");
fake_trailers.AddKeyValue("funky", "monkeys");
EXPECT_CALL(visitor_mock_, ProcessTrailers(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());
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, 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());
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::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());
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::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");
{
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::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_, 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::BALSA_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);
{
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::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);
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(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;
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, OnHeader);
EXPECT_CALL(visitor_mock, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock, HeaderDone());
EXPECT_CALL(visitor_mock, OnHeader);
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::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);
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) {
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_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_, OnHeader).Times(AnyNumber());
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_, OnHeader).Times(AnyNumber());
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);
EXPECT_CALL(visitor_mock_, OnHeader).Times(AnyNumber());
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_, OnHeader("i", ""));
EXPECT_CALL(visitor_mock_, OnHeader("", "val"));
EXPECT_CALL(visitor_mock_,
HandleWarning(BalsaFrameEnums::HEADER_MISSING_COLON))
.Times(27);
EXPECT_CALL(visitor_mock_,
HandleError(BalsaFrameEnums::INVALID_HEADER_FORMAT));
ASSERT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
}
TEST_F(BalsaFrameParsingTest, ErrorWhenHeaderKeyIsEmpty) {
std::string firstKeyIsEmpty =
"GET / HTTP/1.1\r\n"
": \r\n"
"a:b\r\n"
"c:d\r\n"
"\r\n";
TestEmptyHeaderKeyHelper(firstKeyIsEmpty);
balsa_frame_.Reset();
std::string laterKeyIsEmpty =
"GET / HTTP/1.1\r\n"
"a:b\r\n"
": \r\n"
"c:d\r\n"
"\r\n";
TestEmptyHeaderKeyHelper(laterKeyIsEmpty);
}
TEST_F(BalsaFrameParsingTest, InvalidTrailerFormat) {
std::string trailer =
":monkeys\n"
"\r\n";
TestInvalidTrailerFormat(trailer, false);
balsa_frame_.Reset();
std::string trailer2 =
" \r\n"
"test: test\r\n"
"\r\n";
TestInvalidTrailerFormat(trailer2, true);
balsa_frame_.Reset();
std::string trailer3 =
"a: b\r\n"
": test\r\n"
"\r\n";
TestInvalidTrailerFormat(trailer3, false);
}
TEST_F(HTTPBalsaFrameTest,
EnsureHeaderFramingFoundWithVariousCombinationsOfRN_RN) {
const std::string message =
"GET / HTTP/1.1\r\n"
"content-length: 0\r\n"
"a\r\n"
"b\r\n"
"c\r\n"
"d\r\n"
"e\r\n"
"f\r\n"
"g\r\n"
"h\r\n"
"i\r\n"
"\r\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.Error())
<< BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
EnsureHeaderFramingFoundWithVariousCombinationsOfRN_N) {
const std::string message =
"GET / HTTP/1.1\n"
"content-length: 0\n"
"a\n"
"b\n"
"c\n"
"d\n"
"e\n"
"f\n"
"g\n"
"h\n"
"i\n"
"\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.Error())
<< BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
EnsureHeaderFramingFoundWithVariousCombinationsOfRN_RN_N) {
const std::string message =
"GET / HTTP/1.1\n"
"content-length: 0\r\n"
"a\r\n"
"b\n"
"c\r\n"
"d\n"
"e\r\n"
"f\n"
"g\r\n"
"h\n"
"i\r\n"
"\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.Error())
<< BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest,
EnsureHeaderFramingFoundWithVariousCombinationsOfRN_N_RN) {
const std::string message =
"GET / HTTP/1.1\n"
"content-length: 0\r\n"
"a\n"
"b\r\n"
"c\n"
"d\r\n"
"e\n"
"f\r\n"
"g\n"
"h\r\n"
"i\n"
"\r\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.Error())
<< BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, ReadUntilCloseStateEnteredAsExpectedAndNotExited) {
std::string message =
"HTTP/1.1 200 OK\r\n"
"\r\n";
balsa_frame_.set_is_request(false);
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_FALSE(balsa_frame_.Error())
<< BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
EXPECT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE, balsa_frame_.ParseState());
std::string gobldygook = "-198324-9182-43981-23498-98342-jasldfn-1294hj";
for (int i = 0; i < 1000; ++i) {
EXPECT_EQ(gobldygook.size(),
balsa_frame_.ProcessInput(gobldygook.data(), gobldygook.size()));
EXPECT_FALSE(balsa_frame_.Error())
<< BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode());
EXPECT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE, balsa_frame_.ParseState());
}
}
TEST_F(HTTPBalsaFrameTest,
BytesSafeToSpliceAndBytesSplicedWorksWithContentLength) {
std::string header =
"HTTP/1.1 200 OK\r\n"
"content-length: 1000\r\n"
"\r\n";
balsa_frame_.set_is_request(false);
size_t bytes_safe_to_splice = 1000;
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_EQ(header.size(),
balsa_frame_.ProcessInput(header.data(), header.size()));
EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice());
while (bytes_safe_to_splice > 0) {
balsa_frame_.BytesSpliced(1);
bytes_safe_to_splice -= 1;
ASSERT_FALSE(balsa_frame_.Error())
<< BalsaFrameEnums::ParseStateToString(balsa_frame_.ParseState()) << " "
<< BalsaFrameEnums::ErrorCodeToString(balsa_frame_.ErrorCode())
<< " with bytes_safe_to_splice: " << bytes_safe_to_splice
<< " and BytesSafeToSplice(): " << balsa_frame_.BytesSafeToSplice();
}
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
}
TEST_F(HTTPBalsaFrameTest, BytesSplicedFlagsErrorsWhenNotInProperState) {
balsa_frame_.set_is_request(false);
balsa_frame_.BytesSpliced(1);
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO,
balsa_frame_.ErrorCode());
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
}
TEST_F(HTTPBalsaFrameTest,
BytesSplicedFlagsErrorsWhenTooMuchSplicedForContentLen) {
std::string header =
"HTTP/1.1 200 OK\r\n"
"content-length: 1000\r\n"
"\r\n";
balsa_frame_.set_is_request(false);
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_EQ(header.size(),
balsa_frame_.ProcessInput(header.data(), header.size()));
EXPECT_EQ(1000u, balsa_frame_.BytesSafeToSplice());
balsa_frame_.BytesSpliced(1001);
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(
BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT,
balsa_frame_.ErrorCode());
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
}
TEST_F(HTTPBalsaFrameTest, BytesSplicedWorksAsExpectedForReadUntilClose) {
std::string header =
"HTTP/1.1 200 OK\r\n"
"\r\n";
balsa_frame_.set_is_request(false);
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_EQ(header.size(),
balsa_frame_.ProcessInput(header.data(), header.size()));
EXPECT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE, balsa_frame_.ParseState());
EXPECT_EQ(std::numeric_limits<size_t>::max(),
balsa_frame_.BytesSafeToSplice());
for (int i = 0; i < 1000; ++i) {
EXPECT_EQ(std::numeric_limits<size_t>::max(),
balsa_frame_.BytesSafeToSplice());
balsa_frame_.BytesSpliced(12312312);
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
}
EXPECT_EQ(std::numeric_limits<size_t>::max(),
balsa_frame_.BytesSafeToSplice());
}
TEST_F(HTTPBalsaFrameTest,
BytesSplicedFlagsErrorsWhenTooMuchSplicedForChunked) {
std::string header =
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string body_fragment = "a\r\n";
balsa_frame_.set_is_request(false);
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_EQ(header.size(),
balsa_frame_.ProcessInput(header.data(), header.size()));
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_EQ(
body_fragment.size(),
balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size()));
EXPECT_EQ(10u, balsa_frame_.BytesSafeToSplice());
balsa_frame_.BytesSpliced(11);
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(
BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT,
balsa_frame_.ErrorCode());
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
}
TEST_F(HTTPBalsaFrameTest, BytesSafeToSpliceAndBytesSplicedWorksWithChunks) {
std::string header =
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
balsa_frame_.set_is_request(false);
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_EQ(header.size(),
balsa_frame_.ProcessInput(header.data(), header.size()));
{
std::string body_fragment = "3e8\r\n";
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
size_t bytes_safe_to_splice = 1000;
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_EQ(
body_fragment.size(),
balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size()));
EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice());
while (bytes_safe_to_splice > 0) {
balsa_frame_.BytesSpliced(1);
bytes_safe_to_splice -= 1;
ASSERT_FALSE(balsa_frame_.Error());
}
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_FALSE(balsa_frame_.Error());
}
{
std::string body_fragment = "\r\n7d0\r\n";
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
size_t bytes_safe_to_splice = 2000;
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_EQ(
body_fragment.size(),
balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size()));
EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice());
while (bytes_safe_to_splice > 0) {
balsa_frame_.BytesSpliced(1);
bytes_safe_to_splice -= 1;
ASSERT_FALSE(balsa_frame_.Error());
}
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_FALSE(balsa_frame_.Error());
}
{
std::string body_fragment = "\r\n1\r\n";
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
size_t bytes_safe_to_splice = 1;
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_EQ(
body_fragment.size(),
balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size()));
EXPECT_EQ(bytes_safe_to_splice, balsa_frame_.BytesSafeToSplice());
while (bytes_safe_to_splice > 0) {
balsa_frame_.BytesSpliced(1);
bytes_safe_to_splice -= 1;
ASSERT_FALSE(balsa_frame_.Error());
}
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_FALSE(balsa_frame_.Error());
}
{
std::string body_fragment = "\r\n0\r\n\r\n";
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_EQ(
body_fragment.size(),
balsa_frame_.ProcessInput(body_fragment.data(), body_fragment.size()));
EXPECT_EQ(0u, balsa_frame_.BytesSafeToSplice());
EXPECT_FALSE(balsa_frame_.Error());
}
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
}
TEST_F(HTTPBalsaFrameTest, TwoDifferentContentHeadersIsAnError) {
std::string header =
"HTTP/1.1 200 OK\r\n"
"content-length: 12\r\n"
"content-length: 14\r\n"
"\r\n";
balsa_frame_.set_is_request(false);
balsa_frame_.ProcessInput(header.data(), header.size());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::MULTIPLE_CONTENT_LENGTH_KEYS,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, TwoSameContentHeadersIsNotAnError) {
std::string header =
"POST / HTTP/1.1\r\n"
"content-length: 1\r\n"
"content-length: 1\r\n"
"\r\n"
"1";
balsa_frame_.ProcessInput(header.data(), header.size());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
EXPECT_FALSE(balsa_frame_.Error());
balsa_frame_.ProcessInput(header.data(), header.size());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
}
TEST_F(HTTPBalsaFrameTest, TwoTransferEncodingHeadersIsAnError) {
std::string header =
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked\r\n"
"transfer-encoding: identity\r\n"
"\r\n";
balsa_frame_.set_is_request(false);
balsa_frame_.ProcessInput(header.data(), header.size());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::MULTIPLE_TRANSFER_ENCODING_KEYS,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, TwoTransferEncodingTokensIsAnError) {
std::string header =
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked, identity\r\n"
"\r\n";
balsa_frame_.set_is_request(false);
balsa_frame_.ProcessInput(header.data(), header.size());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, UnknownTransferEncodingTokenIsAnError) {
std::string header =
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked-identity\r\n"
"\r\n";
balsa_frame_.set_is_request(false);
balsa_frame_.ProcessInput(header.data(), header.size());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING,
balsa_frame_.ErrorCode());
}
class DetachOnDoneFramer : public NoOpBalsaVisitor {
public:
DetachOnDoneFramer() {
framer_.set_balsa_headers(&headers_);
framer_.set_balsa_visitor(this);
}
void MessageDone() override { framer_.set_balsa_headers(nullptr); }
BalsaFrame* framer() { return &framer_; }
protected:
BalsaFrame framer_;
BalsaHeaders headers_;
};
TEST(HTTPBalsaFrame, TestDetachOnDone) {
DetachOnDoneFramer framer;
const char* message = "GET HTTP/1.1\r\n\r\n";
// Frame the whole message. The framer will call MessageDone which will set
// the headers to nullptr.
framer.framer()->ProcessInput(message, strlen(message));
EXPECT_TRUE(framer.framer()->MessageFullyRead());
EXPECT_FALSE(framer.framer()->Error());
}
// We simply extend DetachOnDoneFramer so that we do not have
// to provide trivial implementation for various functions.
class ModifyMaxHeaderLengthFramerInFirstLine : public DetachOnDoneFramer {
public:
void MessageDone() override {}
// This sets to max_header_length to a low number and
// this would cause us to reject the query. Even though
// our original headers length was acceptable.
void OnRequestFirstLineInput(absl::string_view /*line_input*/,
absl::string_view /*method_input*/,
absl::string_view /*request_uri*/,
absl::string_view /*version_input*/
) override {
framer_.set_max_header_length(1);
}
};
// In this case we have already processed the headers and called on
// the visitor HeadersDone and hence its too late to reduce the
// max_header_length here.
class ModifyMaxHeaderLengthFramerInHeaderDone : public DetachOnDoneFramer {
public:
void MessageDone() override {}
void HeaderDone() override { framer_.set_max_header_length(1); }
};
TEST(HTTPBalsaFrame, ChangeMaxHeadersLengthOnFirstLine) {
std::string message =
"PUT /foo HTTP/1.1\r\n"
"Content-Length: 2\r\n"
"header: xxxxxxxxx\r\n\r\n"
"B"; // body begin
ModifyMaxHeaderLengthFramerInFirstLine balsa_frame;
balsa_frame.framer()->set_is_request(true);
balsa_frame.framer()->set_max_header_length(message.size() - 1);
balsa_frame.framer()->ProcessInput(message.data(), message.size());
EXPECT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG,
balsa_frame.framer()->ErrorCode());
}
TEST(HTTPBalsaFrame, ChangeMaxHeadersLengthOnHeaderDone) {
std::string message =
"PUT /foo HTTP/1.1\r\n"
"Content-Length: 2\r\n"
"header: xxxxxxxxx\r\n\r\n"
"B"; // body begin
ModifyMaxHeaderLengthFramerInHeaderDone balsa_frame;
balsa_frame.framer()->set_is_request(true);
balsa_frame.framer()->set_max_header_length(message.size() - 1);
balsa_frame.framer()->ProcessInput(message.data(), message.size());
EXPECT_EQ(0, balsa_frame.framer()->ErrorCode());
}
// This is a simple test to ensure the simple case that we accept
// a query which has headers size same as the max_header_length.
// (i.e., there is no off by one error).
TEST(HTTPBalsaFrame, HeadersSizeSameAsMaxLengthIsAccepted) {
std::string message =
"GET /foo HTTP/1.1\r\n"
"header: xxxxxxxxx\r\n\r\n";
ModifyMaxHeaderLengthFramerInHeaderDone balsa_frame;
balsa_frame.framer()->set_is_request(true);
balsa_frame.framer()->set_max_header_length(message.size());
balsa_frame.framer()->ProcessInput(message.data(), message.size());
EXPECT_EQ(0, balsa_frame.framer()->ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, KeyHasSpaces) {
const std::string message =
"GET / HTTP/1.1\r\n"
"key has spaces: lock\r\n"
"\r\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, SpaceBeforeColon) {
const std::string message =
"GET / HTTP/1.1\r\n"
"key : lock\r\n"
"\r\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, SpaceBeforeColonNotAfter) {
const std::string message =
"GET / HTTP/1.1\r\n"
"key :lock\r\n"
"\r\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, KeyHasTabs) {
const std::string message =
"GET / HTTP/1.1\r\n"
"key\thas\ttabs: lock\r\n"
"\r\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, TabBeforeColon) {
const std::string message =
"GET / HTTP/1.1\r\n"
"key\t: lock\r\n"
"\r\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, KeyHasContinuation) {
const std::string message =
"GET / HTTP/1.1\r\n"
"key\n includes continuation: but not value\r\n"
"\r\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, KeyHasMultipleContinuations) {
const std::string message =
"GET / HTTP/1.1\r\n"
"key\n includes\r\n multiple\n continuations: but not value\r\n"
"\r\n";
EXPECT_EQ(message.size(),
balsa_frame_.ProcessInput(message.data(), message.size()));
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER,
balsa_frame_.ErrorCode());
}
// Missing colon is a warning, not an error.
TEST_F(HTTPBalsaFrameTest, TrailerMissingColon) {
std::string headers =
"HTTP/1.0 302 Redirect\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
std::string trailer =
"crass_monkeys\n"
"\r\n";
balsa_frame_.set_is_request(false);
EXPECT_CALL(visitor_mock_,
HandleWarning(BalsaFrameEnums::TRAILER_MISSING_COLON));
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::TRAILER_MISSING_COLON, balsa_frame_.ErrorCode());
EXPECT_FALSE(trailer_.HasHeader("crass"));
EXPECT_TRUE(trailer_.HasHeader("crass_monkeys"));
const absl::string_view crass_monkeys = trailer_.GetHeader("crass_monkeys");
EXPECT_TRUE(crass_monkeys.empty());
}
// This tests multiple headers in trailer. We currently do not and have no plan
// to support Trailer field in headers to limit valid field-name in trailer.
// Test that we aren't confused by the non-alphanumeric characters in the
// trailer, especially ':'.
TEST_F(HTTPBalsaFrameTest, MultipleHeadersInTrailer) {
std::string headers =
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\n"
"0\n";
std::map<std::string, std::string> trailer;
trailer["X-Trace"] =
"http://trace.example.com/trace?host="
"foobar.example.com&start=2012-06-03_15:59:06&rpc_duration=0.243349";
trailer["Date"] = "Sun, 03 Jun 2012 22:59:06 GMT";
trailer["Content-Type"] = "text/html";
trailer["X-Backends"] = "127.0.0.1_0,foo.example.com:39359";
trailer["X-Request-Trace"] =
"foo.example.com:39359,127.0.0.1_1,"
"foo.example.com:39359,127.0.0.1_0,"
"foo.example.com:39359";
trailer["X-Service-Trace"] = "default";
trailer["X-Service"] = "default";
std::map<std::string, std::string>::const_iterator iter;
std::string trailer_data;
TestSeed seed;
seed.Initialize(GetQuicheCommandLineFlag(FLAGS_randseed));
RandomEngine rng;
rng.seed(seed.GetSeed());
FakeHeaders fake_headers_in_trailer;
for (iter = trailer.begin(); iter != trailer.end(); ++iter) {
trailer_data += iter->first;
trailer_data += ":";
std::stringstream leading_whitespace_for_value;
AppendRandomWhitespace(rng, &leading_whitespace_for_value);
trailer_data += leading_whitespace_for_value.str();
trailer_data += iter->second;
std::stringstream trailing_whitespace_for_value;
AppendRandomWhitespace(rng, &trailing_whitespace_for_value);
trailer_data += trailing_whitespace_for_value.str();
trailer_data += random_line_term(rng);
fake_headers_in_trailer.AddKeyValue(iter->first, iter->second);
}
trailer_data += random_line_term(rng);
FakeHeaders fake_headers;
fake_headers.AddKeyValue("transfer-encoding", "chunked");
{
InSequence s1;
EXPECT_CALL(visitor_mock_, OnResponseFirstLineInput(
"HTTP/1.1 200 OK", "HTTP/1.1", "200", "OK"));
EXPECT_CALL(visitor_mock_, ProcessHeaders(fake_headers));
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, OnChunkLength(3));
EXPECT_CALL(visitor_mock_, OnChunkLength(0));
EXPECT_CALL(visitor_mock_, ProcessTrailers(fake_headers_in_trailer));
EXPECT_CALL(visitor_mock_, OnTrailerInput(trailer_data));
EXPECT_CALL(visitor_mock_, MessageDone());
}
EXPECT_CALL(visitor_mock_, OnHeaderInput(headers));
std::string body_input;
EXPECT_CALL(visitor_mock_, OnRawBodyInput(_))
.WillRepeatedly([&body_input](absl::string_view input) {
absl::StrAppend(&body_input, input);
});
EXPECT_CALL(visitor_mock_, OnBodyChunkInput("123"));
balsa_frame_.set_is_request(false);
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_EQ(trailer_data.size(), balsa_frame_.ProcessInput(
trailer_data.data(), trailer_data.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
EXPECT_EQ(chunks, body_input);
for (iter = trailer.begin(); iter != trailer.end(); ++iter) {
const absl::string_view value = trailer_.GetHeader(iter->first);
EXPECT_EQ(iter->second, value);
}
}
// Test if trailer is not set (the common case), everything will be fine.
TEST_F(HTTPBalsaFrameTest, NothingBadHappensWithNULLTrailer) {
std::string headers =
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
std::string trailer =
"crass: monkeys\r\n"
"funky: monkeys\r\n"
"\n";
balsa_frame_.set_is_request(false);
balsa_frame_.set_balsa_visitor(nullptr);
balsa_frame_.set_balsa_trailer(nullptr);
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
ASSERT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
}
// Test Reset() correctly resets trailer related states.
TEST_F(HTTPBalsaFrameTest, FrameAndResetAndFrameAgain) {
std::string headers =
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
std::string trailer =
"k: v\n"
"\n";
balsa_frame_.set_is_request(false);
balsa_frame_.set_balsa_visitor(nullptr);
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
ASSERT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
absl::string_view value = trailer_.GetHeader("k");
EXPECT_EQ("v", value);
balsa_frame_.Reset();
headers =
"HTTP/1.1 404 Error\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
chunks =
"4\r\n"
"1234\r\n"
"0\r\n";
trailer =
"nk: nv\n"
"\n";
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
ASSERT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
value = trailer_.GetHeader("k");
EXPECT_TRUE(value.empty());
value = trailer_.GetHeader("nk");
EXPECT_EQ("nv", value);
}
TEST_F(HTTPBalsaFrameTest, TrackInvalidChars) {
EXPECT_FALSE(balsa_frame_.track_invalid_chars());
}
// valid chars are 9 (tab), 10 (LF), 13(CR), and 32-255
TEST_F(HTTPBalsaFrameTest, InvalidCharsInHeaderValueWarning) {
balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
// nulls are double escaped since otherwise this initialized wrong
const std::string kEscapedInvalid1 =
"GET /foo HTTP/1.1\r\n"
"Bogus-Head: val\\x00\r\n"
"More-Invalid: \\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\r\n"
"And-More: \x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D"
"\x1E\x1F\r\n\r\n";
std::string message;
// now we convert to real embedded nulls
absl::CUnescape(kEscapedInvalid1, &message);
EXPECT_CALL(visitor_mock_,
HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
balsa_frame_.ProcessInput(message.data(), message.size());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
}
// Header names reject invalid chars even at the warning level.
TEST_F(HTTPBalsaFrameTest, InvalidCharsInHeaderKeyError) {
balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
// nulls are double escaped since otherwise this initialized wrong
const std::string kEscapedInvalid1 =
"GET /foo HTTP/1.1\r\n"
"Bogus\\x00-Head: val\r\n\r\n";
std::string message;
// now we convert to real embedded nulls
absl::CUnescape(kEscapedInvalid1, &message);
EXPECT_CALL(visitor_mock_,
HandleError(BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER));
balsa_frame_.ProcessInput(message.data(), message.size());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
}
TEST_F(HTTPBalsaFrameTest, InvalidCharsInHeaderError) {
balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kError);
const std::string kEscapedInvalid =
"GET /foo HTTP/1.1\r\n"
"Smuggle-Me: \\x00GET /bar HTTP/1.1\r\n"
"Another-Header: value\r\n\r\n";
std::string message;
absl::CUnescape(kEscapedInvalid, &message);
EXPECT_CALL(visitor_mock_,
HandleError(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
balsa_frame_.ProcessInput(message.data(), message.size());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
}
class HTTPBalsaFrameTestOneChar : public HTTPBalsaFrameTest,
public testing::WithParamInterface<char> {
public:
char GetCharUnderTest() { return GetParam(); }
};
TEST_P(HTTPBalsaFrameTestOneChar, InvalidCharsWarningSet) {
balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
const std::string kRequest =
"GET /foo HTTP/1.1\r\n"
"Bogus-Char-Goes-Here: ";
const std::string kEnding = "\r\n\r\n";
std::string message = kRequest;
const char c = GetCharUnderTest();
message.append(1, c);
message.append(kEnding);
if (c == 9 || c == 10 || c == 13) {
// valid char
EXPECT_CALL(visitor_mock_,
HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER))
.Times(0);
balsa_frame_.ProcessInput(message.data(), message.size());
EXPECT_THAT(balsa_frame_.get_invalid_chars(), IsEmpty());
} else {
// invalid char
absl::flat_hash_map<char, int> expected_count = {{c, 1}};
EXPECT_CALL(visitor_mock_,
HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
balsa_frame_.ProcessInput(message.data(), message.size());
EXPECT_EQ(balsa_frame_.get_invalid_chars(), expected_count);
}
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
}
INSTANTIATE_TEST_SUITE_P(TestInvalidCharSet, HTTPBalsaFrameTestOneChar,
Range<char>(0, 32));
TEST_F(HTTPBalsaFrameTest, InvalidCharEndOfLine) {
balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
const std::string kInvalid1 =
"GET /foo HTTP/1.1\r\n"
"Header-Key: headervalue\\x00\r\n"
"Legit-Header: legitvalue\r\n\r\n";
std::string message;
absl::CUnescape(kInvalid1, &message);
EXPECT_CALL(visitor_mock_,
HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
balsa_frame_.ProcessInput(message.data(), message.size());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
}
TEST_F(HTTPBalsaFrameTest, InvalidCharInFirstLine) {
balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
const std::string kInvalid1 =
"GET /foo \\x00HTTP/1.1\r\n"
"Legit-Header: legitvalue\r\n\r\n";
std::string message;
absl::CUnescape(kInvalid1, &message);
EXPECT_CALL(visitor_mock_,
HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
balsa_frame_.ProcessInput(message.data(), message.size());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
}
TEST_F(HTTPBalsaFrameTest, InvalidCharsAreCounted) {
balsa_frame_.set_invalid_chars_level(BalsaFrame::InvalidCharsLevel::kWarning);
const std::string kInvalid1 =
"GET /foo \\x00\\x00\\x00HTTP/1.1\r\n"
"Bogus-Header: \\x00\\x04\\x04value\r\n\r\n";
std::string message;
absl::CUnescape(kInvalid1, &message);
EXPECT_CALL(visitor_mock_,
HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER));
balsa_frame_.ProcessInput(message.data(), message.size());
absl::flat_hash_map<char, int> expected_count = {{'\0', 4}, {'\4', 2}};
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_EQ(balsa_frame_.get_invalid_chars(), expected_count);
absl::flat_hash_map<char, int> empty_count;
balsa_frame_.Reset();
EXPECT_EQ(balsa_frame_.get_invalid_chars(), empty_count);
}
// Test gibberish in headers and trailer. GFE does not crash but garbage in
// garbage out.
TEST_F(HTTPBalsaFrameTest, GibberishInHeadersAndTrailer) {
// Use static_cast<char> for values exceeding SCHAR_MAX to make sure this
// compiles on platforms where char is signed.
const char kGibberish1[] = {static_cast<char>(138), static_cast<char>(175),
static_cast<char>(233), 0};
const char kGibberish2[] = {'?',
'?',
static_cast<char>(128),
static_cast<char>(255),
static_cast<char>(129),
static_cast<char>(254),
0};
const char kGibberish3[] = "foo: bar : eeep : baz";
std::string gibberish_headers =
absl::StrCat(kGibberish1, ":", kGibberish2, "\r\n", kGibberish3, "\r\n");
std::string headers = absl::StrCat(
"HTTP/1.1 200 OK\r\n"
"transfer-encoding: chunked\r\n",
gibberish_headers, "\r\n");
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
std::string trailer = absl::StrCat("k: v\n", gibberish_headers, "\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()));
ASSERT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
// Transfer-encoding can be multi-valued, so GetHeader does not work.
EXPECT_TRUE(headers_.transfer_encoding_is_chunked());
absl::string_view field_value = headers_.GetHeader(kGibberish1);
EXPECT_EQ(kGibberish2, field_value);
field_value = headers_.GetHeader("foo");
EXPECT_EQ("bar : eeep : baz", field_value);
field_value = trailer_.GetHeader("k");
EXPECT_EQ("v", field_value);
field_value = trailer_.GetHeader(kGibberish1);
EXPECT_EQ(kGibberish2, field_value);
field_value = trailer_.GetHeader("foo");
EXPECT_EQ("bar : eeep : baz", field_value);
}
// Note we reuse the header length limit because trailer is just multiple
// headers.
TEST_F(HTTPBalsaFrameTest, TrailerTooLong) {
std::string headers =
"HTTP/1.0 200 ok\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
std::string trailer =
"very : long trailer\n"
"should:cause\r\n"
"trailer :too long error\n"
"\r\n";
balsa_frame_.set_is_request(false);
ASSERT_LT(headers.size(), trailer.size());
balsa_frame_.set_max_header_length(headers.size());
EXPECT_CALL(visitor_mock_, HandleError(BalsaFrameEnums::TRAILER_TOO_LONG));
EXPECT_CALL(visitor_mock_, ProcessTrailers(_)).Times(0);
EXPECT_CALL(visitor_mock_, MessageDone()).Times(0);
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_EQ(balsa_frame_.max_header_length(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_FALSE(balsa_frame_.MessageFullyRead());
EXPECT_TRUE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::TRAILER_TOO_LONG, balsa_frame_.ErrorCode());
}
// If the `trailer_` object in the framer is set to `nullptr`,
// ProcessTrailers() will not be called.
TEST_F(HTTPBalsaFrameTest,
NoProcessTrailersCallWhenFramerHasNullTrailerObject) {
std::string headers =
"HTTP/1.0 200 ok\r\n"
"transfer-encoding: chunked\r\n"
"\r\n";
std::string chunks =
"3\r\n"
"123\r\n"
"0\r\n";
std::string trailer =
"trailer_key : trailer_value\n"
"\r\n";
balsa_frame_.set_is_request(false);
balsa_frame_.set_balsa_trailer(nullptr);
EXPECT_CALL(visitor_mock_, ProcessTrailers(_)).Times(0);
ASSERT_EQ(headers.size(),
balsa_frame_.ProcessInput(headers.data(), headers.size()));
ASSERT_EQ(chunks.size(),
balsa_frame_.ProcessInput(chunks.data(), chunks.size()));
EXPECT_EQ(trailer.size(),
balsa_frame_.ProcessInput(trailer.data(), trailer.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
}
// Handle two sets of headers when set up properly and the first is 100
// Continue.
TEST_F(HTTPBalsaFrameTest, Support100Continue) {
std::string initial_headers =
"HTTP/1.1 100 Continue\r\n"
"\r\n";
std::string real_headers =
"HTTP/1.1 200 OK\r\n"
"content-length: 3\r\n"
"\r\n";
std::string body = "foo";
balsa_frame_.set_is_request(false);
BalsaHeaders continue_headers;
balsa_frame_.set_continue_headers(&continue_headers);
ASSERT_EQ(initial_headers.size(),
balsa_frame_.ProcessInput(initial_headers.data(),
initial_headers.size()));
ASSERT_EQ(real_headers.size(),
balsa_frame_.ProcessInput(real_headers.data(), real_headers.size()))
<< balsa_frame_.ErrorCode();
ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
}
// Handle two sets of headers when set up properly and the first is 100
// Continue and it meets the conditions for b/62408297
TEST_F(HTTPBalsaFrameTest, Support100Continue401Unauthorized) {
std::string initial_headers =
"HTTP/1.1 100 Continue\r\n"
"\r\n";
std::string real_headers =
"HTTP/1.1 401 Unauthorized\r\n"
"content-length: 3\r\n"
"\r\n";
std::string body = "foo";
balsa_frame_.set_is_request(false);
BalsaHeaders continue_headers;
balsa_frame_.set_continue_headers(&continue_headers);
ASSERT_EQ(initial_headers.size(),
balsa_frame_.ProcessInput(initial_headers.data(),
initial_headers.size()));
ASSERT_EQ(real_headers.size(),
balsa_frame_.ProcessInput(real_headers.data(), real_headers.size()))
<< balsa_frame_.ErrorCode();
ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
}
TEST_F(HTTPBalsaFrameTest, Support100ContinueRunTogether) {
std::string both_headers =
"HTTP/1.1 100 Continue\r\n"
"\r\n"
"HTTP/1.1 200 OK\r\n"
"content-length: 3\r\n"
"\r\n";
std::string body = "foo";
{
InSequence s;
EXPECT_CALL(visitor_mock_, ContinueHeaderDone());
EXPECT_CALL(visitor_mock_, HeaderDone());
EXPECT_CALL(visitor_mock_, MessageDone());
}
balsa_frame_.set_is_request(false);
BalsaHeaders continue_headers;
balsa_frame_.set_continue_headers(&continue_headers);
ASSERT_EQ(both_headers.size(),
balsa_frame_.ProcessInput(both_headers.data(), both_headers.size()))
<< balsa_frame_.ErrorCode();
ASSERT_EQ(body.size(), balsa_frame_.ProcessInput(body.data(), body.size()));
EXPECT_TRUE(balsa_frame_.MessageFullyRead());
EXPECT_FALSE(balsa_frame_.Error());
EXPECT_EQ(BalsaFrameEnums::BALSA_NO_ERROR, balsa_frame_.ErrorCode());
}
} // namespace
} // namespace test
} // namespace quiche