blob: 7dfc344f349edadee5bfa01fec651cd34ba92563 [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_headers.h"
#include <sys/types.h>
#include <cstdint>
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "quiche/balsa/balsa_enums.h"
#include "quiche/balsa/header_properties.h"
#include "quiche/common/platform/api/quiche_header_policy.h"
#include "quiche/common/platform/api/quiche_logging.h"
namespace {
constexpr absl::string_view kContentLength("Content-Length");
constexpr absl::string_view kCookie("Cookie");
constexpr absl::string_view kHost("Host");
constexpr absl::string_view kTransferEncoding("Transfer-Encoding");
// The following list defines list of headers that Envoy considers multivalue.
// Headers on this list are coalesced by EFG in order to provide forward
// compatibility with Envoy behavior. See b/143490671 for details.
// Date, Last-Modified and Location are excluded because they're found on Chrome
// HttpUtil::IsNonCoalescingHeader() list.
#define ALL_ENVOY_HEADERS(HEADER_FUNC) \
HEADER_FUNC("Accept") \
HEADER_FUNC("Accept-Encoding") \
HEADER_FUNC("Access-Control-Request-Headers") \
HEADER_FUNC("Access-Control-Request-Method") \
HEADER_FUNC("Access-Control-Allow-Origin") \
HEADER_FUNC("Access-Control-Allow-Headers") \
HEADER_FUNC("Access-Control-Allow-Methods") \
HEADER_FUNC("Access-Control-Allow-Credentials") \
HEADER_FUNC("Access-Control-Expose-Headers") \
HEADER_FUNC("Access-Control-Max-Age") \
HEADER_FUNC("Authorization") \
HEADER_FUNC("Cache-Control") \
HEADER_FUNC("X-Client-Trace-Id") \
HEADER_FUNC("Connection") \
HEADER_FUNC("Content-Encoding") \
HEADER_FUNC("Content-Length") \
HEADER_FUNC("Content-Type") \
/* HEADER_FUNC("Date") */ \
HEADER_FUNC("Envoy-Attempt-Count") \
HEADER_FUNC("Envoy-Degraded") \
HEADER_FUNC("Envoy-Decorator-Operation") \
HEADER_FUNC("Envoy-Downstream-Service-Cluster") \
HEADER_FUNC("Envoy-Downstream-Service-Node") \
HEADER_FUNC("Envoy-Expected-Request-Timeout-Ms") \
HEADER_FUNC("Envoy-External-Address") \
HEADER_FUNC("Envoy-Force-Trace") \
HEADER_FUNC("Envoy-Hedge-On-Per-Try-Timeout") \
HEADER_FUNC("Envoy-Immediate-Health-Check-Fail") \
HEADER_FUNC("Envoy-Internal-Request") \
HEADER_FUNC("Envoy-Ip-Tags") \
HEADER_FUNC("Envoy-Max-Retries") \
HEADER_FUNC("Envoy-Original-Path") \
HEADER_FUNC("Envoy-Original-Url") \
HEADER_FUNC("Envoy-Overloaded") \
HEADER_FUNC("Envoy-Rate-Limited") \
HEADER_FUNC("Envoy-Retry-On") \
HEADER_FUNC("Envoy-Retry-Grpc-On") \
HEADER_FUNC("Envoy-Retriable-StatusCodes") \
HEADER_FUNC("Envoy-Retriable-HeaderNames") \
HEADER_FUNC("Envoy-Upstream-AltStatName") \
HEADER_FUNC("Envoy-Upstream-Canary") \
HEADER_FUNC("Envoy-Upstream-HealthCheckedCluster") \
HEADER_FUNC("Envoy-Upstream-RequestPerTryTimeoutMs") \
HEADER_FUNC("Envoy-Upstream-RequestTimeoutAltResponse") \
HEADER_FUNC("Envoy-Upstream-RequestTimeoutMs") \
HEADER_FUNC("Envoy-Upstream-ServiceTime") \
HEADER_FUNC("Etag") \
HEADER_FUNC("Expect") \
HEADER_FUNC("X-Forwarded-Client-Cert") \
HEADER_FUNC("X-Forwarded-For") \
HEADER_FUNC("X-Forwarded-Proto") \
HEADER_FUNC("Grpc-Accept-Encoding") \
HEADER_FUNC("Grpc-Message") \
HEADER_FUNC("Grpc-Status") \
HEADER_FUNC("Grpc-Timeout") \
HEADER_FUNC("Host") \
HEADER_FUNC("Keep-Alive") \
/* HEADER_FUNC("Last-Modified") */ \
/* HEADER_FUNC("Location") */ \
HEADER_FUNC("Method") \
HEADER_FUNC("No-Chunks") \
HEADER_FUNC("Origin") \
HEADER_FUNC("X-Ot-Span-Context") \
HEADER_FUNC("Path") \
HEADER_FUNC("Protocol") \
HEADER_FUNC("Proxy-Connection") \
HEADER_FUNC("Referer") \
HEADER_FUNC("X-Request-Id") \
HEADER_FUNC("Scheme") \
HEADER_FUNC("Server") \
HEADER_FUNC("Status") \
HEADER_FUNC("TE") \
HEADER_FUNC("Transfer-Encoding") \
HEADER_FUNC("Upgrade") \
HEADER_FUNC("User-Agent") \
HEADER_FUNC("Vary") \
HEADER_FUNC("Via")
// HEADER_FUNC to insert "name" into the MultivaluedHeadersSet of Envoy headers.
#define MULTIVALUE_ENVOY_HEADER(name) {name},
absl::string_view::difference_type FindIgnoreCase(absl::string_view haystack,
absl::string_view needle) {
absl::string_view::difference_type pos = 0;
while (haystack.size() >= needle.size()) {
if (absl::StartsWithIgnoreCase(haystack, needle)) {
return pos;
}
++pos;
haystack.remove_prefix(1);
}
return absl::string_view::npos;
}
absl::string_view::difference_type RemoveLeadingWhitespace(
absl::string_view* text) {
size_t count = 0;
const char* ptr = text->data();
while (count < text->size() && absl::ascii_isspace(*ptr)) {
count++;
ptr++;
}
text->remove_prefix(count);
return count;
}
absl::string_view::difference_type RemoveTrailingWhitespace(
absl::string_view* text) {
size_t count = 0;
const char* ptr = text->data() + text->size() - 1;
while (count < text->size() && absl::ascii_isspace(*ptr)) {
++count;
--ptr;
}
text->remove_suffix(count);
return count;
}
absl::string_view::difference_type RemoveWhitespaceContext(
absl::string_view* text) {
return RemoveLeadingWhitespace(text) + RemoveTrailingWhitespace(text);
}
} // namespace
namespace quiche {
const size_t BalsaBuffer::kDefaultBlocksize;
const BalsaHeaders::MultivaluedHeadersSet&
BalsaHeaders::multivalued_envoy_headers() {
static const MultivaluedHeadersSet* multivalued_envoy_headers =
new MultivaluedHeadersSet({ALL_ENVOY_HEADERS(MULTIVALUE_ENVOY_HEADER)});
return *multivalued_envoy_headers;
}
void BalsaHeaders::ParseTokenList(absl::string_view header_value,
HeaderTokenList* tokens) {
if (header_value.empty()) {
return;
}
auto start = header_value.begin();
auto end = header_value.end();
while (true) {
// Cast `*start` to unsigned char to make values above 127 rank as expected
// on platforms with signed char, where such values are represented as
// negative numbers before the cast.
// search for first nonwhitespace, non separator char.
while (*start == ',' || static_cast<unsigned char>(*start) <= ' ') {
++start;
if (start == end) {
return;
}
}
// found. marked.
auto nws = start;
// search for next whitspace or separator char.
while (*start != ',' && static_cast<unsigned char>(*start) > ' ') {
++start;
if (start == end) {
if (nws != start) {
tokens->push_back(absl::string_view(nws, start - nws));
}
return;
}
}
tokens->push_back(absl::string_view(nws, start - nws));
}
}
// This can be called after a std::move() operation, so things might be
// in an unspecified state after the move.
void BalsaHeaders::Clear() {
balsa_buffer_.Clear();
transfer_encoding_is_chunked_ = false;
content_length_ = 0;
content_length_status_ = BalsaHeadersEnums::NO_CONTENT_LENGTH;
parsed_response_code_ = 0;
firstline_buffer_base_idx_ = 0;
whitespace_1_idx_ = 0;
non_whitespace_1_idx_ = 0;
whitespace_2_idx_ = 0;
non_whitespace_2_idx_ = 0;
whitespace_3_idx_ = 0;
non_whitespace_3_idx_ = 0;
whitespace_4_idx_ = 0;
header_lines_.clear();
header_lines_.shrink_to_fit();
}
void BalsaHeaders::CopyFrom(const BalsaHeaders& other) {
// Protect against copying with self.
if (this == &other) {
return;
}
balsa_buffer_.CopyFrom(other.balsa_buffer_);
transfer_encoding_is_chunked_ = other.transfer_encoding_is_chunked_;
content_length_ = other.content_length_;
content_length_status_ = other.content_length_status_;
parsed_response_code_ = other.parsed_response_code_;
firstline_buffer_base_idx_ = other.firstline_buffer_base_idx_;
whitespace_1_idx_ = other.whitespace_1_idx_;
non_whitespace_1_idx_ = other.non_whitespace_1_idx_;
whitespace_2_idx_ = other.whitespace_2_idx_;
non_whitespace_2_idx_ = other.non_whitespace_2_idx_;
whitespace_3_idx_ = other.whitespace_3_idx_;
non_whitespace_3_idx_ = other.non_whitespace_3_idx_;
whitespace_4_idx_ = other.whitespace_4_idx_;
header_lines_ = other.header_lines_;
}
void BalsaHeaders::AddAndMakeDescription(absl::string_view key,
absl::string_view value,
HeaderLineDescription* d) {
QUICHE_CHECK(d != nullptr);
if (enforce_header_policy_) {
QuicheHandleHeaderPolicy(key);
}
// + 2 to size for ": "
size_t line_size = key.size() + 2 + value.size();
BalsaBuffer::Blocks::size_type block_buffer_idx = 0;
char* storage = balsa_buffer_.Reserve(line_size, &block_buffer_idx);
size_t base_idx = storage - GetPtr(block_buffer_idx);
char* cur_loc = storage;
memcpy(cur_loc, key.data(), key.size());
cur_loc += key.size();
*cur_loc = ':';
++cur_loc;
*cur_loc = ' ';
++cur_loc;
memcpy(cur_loc, value.data(), value.size());
*d = HeaderLineDescription(
base_idx, base_idx + key.size(), base_idx + key.size() + 2,
base_idx + key.size() + 2 + value.size(), block_buffer_idx);
}
void BalsaHeaders::AppendAndMakeDescription(absl::string_view key,
absl::string_view value,
HeaderLineDescription* d) {
// Figure out how much space we need to reserve for the new header size.
size_t old_value_size = d->last_char_idx - d->value_begin_idx;
if (old_value_size == 0) {
AddAndMakeDescription(key, value, d);
return;
}
absl::string_view old_value(GetPtr(d->buffer_base_idx) + d->value_begin_idx,
old_value_size);
BalsaBuffer::Blocks::size_type block_buffer_idx = 0;
// + 3 because we potentially need to add ": ", and "," to the line.
size_t new_size = key.size() + 3 + old_value_size + value.size();
char* storage = balsa_buffer_.Reserve(new_size, &block_buffer_idx);
size_t base_idx = storage - GetPtr(block_buffer_idx);
absl::string_view first_value = old_value;
absl::string_view second_value = value;
char* cur_loc = storage;
memcpy(cur_loc, key.data(), key.size());
cur_loc += key.size();
*cur_loc = ':';
++cur_loc;
*cur_loc = ' ';
++cur_loc;
memcpy(cur_loc, first_value.data(), first_value.size());
cur_loc += first_value.size();
*cur_loc = ',';
++cur_loc;
memcpy(cur_loc, second_value.data(), second_value.size());
*d = HeaderLineDescription(base_idx, base_idx + key.size(),
base_idx + key.size() + 2, base_idx + new_size,
block_buffer_idx);
}
// Reset internal flags for chunked transfer encoding or content length if a
// header we're removing is one of those headers.
void BalsaHeaders::MaybeClearSpecialHeaderValues(absl::string_view key) {
if (absl::EqualsIgnoreCase(key, kContentLength)) {
if (transfer_encoding_is_chunked_) {
return;
}
content_length_status_ = BalsaHeadersEnums::NO_CONTENT_LENGTH;
content_length_ = 0;
return;
}
if (absl::EqualsIgnoreCase(key, kTransferEncoding)) {
transfer_encoding_is_chunked_ = false;
}
}
// Removes all keys value pairs with key 'key' starting at 'start'.
void BalsaHeaders::RemoveAllOfHeaderStartingAt(absl::string_view key,
HeaderLines::iterator start) {
MaybeClearSpecialHeaderValues(key);
while (start != header_lines_.end()) {
start->skip = true;
++start;
start = GetHeaderLinesIterator(key, start);
}
}
void BalsaHeaders::ReplaceOrAppendHeader(absl::string_view key,
absl::string_view value) {
const HeaderLines::iterator end = header_lines_.end();
const HeaderLines::iterator begin = header_lines_.begin();
HeaderLines::iterator i = GetHeaderLinesIterator(key, begin);
if (i != end) {
// First, remove all of the header lines including this one. We want to
// remove before replacing, in case our replacement ends up being appended
// at the end (and thus would be removed by this call)
RemoveAllOfHeaderStartingAt(key, i);
// Now, take the first instance and replace it. This will remove the
// 'skipped' tag if the replacement is done in-place.
AddAndMakeDescription(key, value, &(*i));
return;
}
AppendHeader(key, value);
}
void BalsaHeaders::AppendHeader(absl::string_view key,
absl::string_view value) {
HeaderLineDescription hld;
AddAndMakeDescription(key, value, &hld);
header_lines_.push_back(hld);
}
void BalsaHeaders::AppendToHeader(absl::string_view key,
absl::string_view value) {
HeaderLines::iterator i = GetHeaderLinesIterator(key, header_lines_.begin());
if (i == header_lines_.end()) {
// The header did not exist already. Instead of appending to an existing
// header simply append the key/value pair to the headers.
AppendHeader(key, value);
return;
}
HeaderLineDescription hld = *i;
AppendAndMakeDescription(key, value, &hld);
// Invalidate the old header line and add the new one.
i->skip = true;
header_lines_.push_back(hld);
}
void BalsaHeaders::AppendToHeaderWithCommaAndSpace(absl::string_view key,
absl::string_view value) {
HeaderLines::iterator i = GetHeaderLinesIteratorForLastMultivaluedHeader(key);
if (i == header_lines_.end()) {
// The header did not exist already. Instead of appending to an existing
// header simply append the key/value pair to the headers. No extra
// space will be added before the value.
AppendHeader(key, value);
return;
}
std::string space_and_value = absl::StrCat(" ", value);
HeaderLineDescription hld = *i;
AppendAndMakeDescription(key, space_and_value, &hld);
// Invalidate the old header line and add the new one.
i->skip = true;
header_lines_.push_back(hld);
}
absl::string_view BalsaHeaders::GetValueFromHeaderLineDescription(
const HeaderLineDescription& line) const {
QUICHE_DCHECK_GE(line.last_char_idx, line.value_begin_idx);
return absl::string_view(GetPtr(line.buffer_base_idx) + line.value_begin_idx,
line.last_char_idx - line.value_begin_idx);
}
absl::string_view BalsaHeaders::GetHeader(absl::string_view key) const {
QUICHE_DCHECK(!header_properties::IsMultivaluedHeader(key))
<< "Header '" << key << "' may consist of multiple lines. Do not "
<< "use BalsaHeaders::GetHeader() or you may be missing some of its "
<< "values.";
const HeaderLines::const_iterator end = header_lines_.end();
HeaderLines::const_iterator i = GetConstHeaderLinesIterator(key);
if (i == end) {
return absl::string_view();
}
return GetValueFromHeaderLineDescription(*i);
}
BalsaHeaders::const_header_lines_iterator BalsaHeaders::GetHeaderPosition(
absl::string_view key) const {
const HeaderLines::const_iterator end = header_lines_.end();
HeaderLines::const_iterator i = GetConstHeaderLinesIterator(key);
if (i == end) {
// TODO(tgreer) Convert from HeaderLines::const_iterator to
// const_header_lines_iterator without calling lines().end(), which is
// nontrivial. Look for other needless calls to lines().end(), or make
// lines().end() trivial.
return lines().end();
}
return const_header_lines_iterator(this, (i - header_lines_.begin()));
}
BalsaHeaders::const_header_lines_key_iterator BalsaHeaders::GetIteratorForKey(
absl::string_view key) const {
HeaderLines::const_iterator i = GetConstHeaderLinesIterator(key);
if (i == header_lines_.end()) {
return header_lines_key_end();
}
return const_header_lines_key_iterator(this, (i - header_lines_.begin()),
key);
}
BalsaHeaders::HeaderLines::const_iterator
BalsaHeaders::GetConstHeaderLinesIterator(absl::string_view key) const {
const HeaderLines::const_iterator end = header_lines_.end();
for (HeaderLines::const_iterator i = header_lines_.begin(); i != end; ++i) {
const HeaderLineDescription& line = *i;
if (line.skip) {
continue;
}
const absl::string_view current_key(
GetPtr(line.buffer_base_idx) + line.first_char_idx,
line.key_end_idx - line.first_char_idx);
if (absl::EqualsIgnoreCase(current_key, key)) {
QUICHE_DCHECK_GE(line.last_char_idx, line.value_begin_idx);
return i;
}
}
return end;
}
BalsaHeaders::HeaderLines::iterator BalsaHeaders::GetHeaderLinesIterator(
absl::string_view key, BalsaHeaders::HeaderLines::iterator start) {
const HeaderLines::iterator end = header_lines_.end();
for (HeaderLines::iterator i = start; i != end; ++i) {
const HeaderLineDescription& line = *i;
if (line.skip) {
continue;
}
const absl::string_view current_key(
GetPtr(line.buffer_base_idx) + line.first_char_idx,
line.key_end_idx - line.first_char_idx);
if (absl::EqualsIgnoreCase(current_key, key)) {
QUICHE_DCHECK_GE(line.last_char_idx, line.value_begin_idx);
return i;
}
}
return end;
}
BalsaHeaders::HeaderLines::iterator
BalsaHeaders::GetHeaderLinesIteratorForLastMultivaluedHeader(
absl::string_view key) {
const HeaderLines::iterator end = header_lines_.end();
HeaderLines::iterator last_found_match;
bool found_a_match = false;
for (HeaderLines::iterator i = header_lines_.begin(); i != end; ++i) {
const HeaderLineDescription& line = *i;
if (line.skip) {
continue;
}
const absl::string_view current_key(
GetPtr(line.buffer_base_idx) + line.first_char_idx,
line.key_end_idx - line.first_char_idx);
if (absl::EqualsIgnoreCase(current_key, key)) {
QUICHE_DCHECK_GE(line.last_char_idx, line.value_begin_idx);
last_found_match = i;
found_a_match = true;
}
}
return (found_a_match ? last_found_match : end);
}
void BalsaHeaders::GetAllOfHeader(absl::string_view key,
std::vector<absl::string_view>* out) const {
for (const_header_lines_key_iterator it = GetIteratorForKey(key);
it != lines().end(); ++it) {
out->push_back(it->second);
}
}
void BalsaHeaders::GetAllOfHeaderIncludeRemoved(
absl::string_view key, std::vector<absl::string_view>* out) const {
const HeaderLines::const_iterator begin = header_lines_.begin();
const HeaderLines::const_iterator end = header_lines_.end();
for (bool add_removed : {false, true}) {
for (HeaderLines::const_iterator i = begin; i != end; ++i) {
const HeaderLineDescription& line = *i;
if ((!add_removed && line.skip) || (add_removed && !line.skip)) {
continue;
}
const absl::string_view current_key(
GetPtr(line.buffer_base_idx) + line.first_char_idx,
line.key_end_idx - line.first_char_idx);
if (absl::EqualsIgnoreCase(current_key, key)) {
QUICHE_DCHECK_GE(line.last_char_idx, line.value_begin_idx);
out->push_back(GetValueFromHeaderLineDescription(line));
}
}
}
}
namespace {
// Helper function for HeaderHasValue that checks that the specified region
// within line is preceded by whitespace and a comma or beginning of line,
// and followed by whitespace and a comma or end of line.
bool SurroundedOnlyBySpacesAndCommas(absl::string_view::difference_type idx,
absl::string_view::difference_type end_idx,
absl::string_view line) {
for (idx = idx - 1; idx >= 0; --idx) {
if (line[idx] == ',') {
break;
}
if (line[idx] != ' ') {
return false;
}
}
for (; end_idx < static_cast<int64_t>(line.size()); ++end_idx) {
if (line[end_idx] == ',') {
break;
}
if (line[end_idx] != ' ') {
return false;
}
}
return true;
}
} // namespace
bool BalsaHeaders::HeaderHasValueHelper(absl::string_view key,
absl::string_view value,
bool case_sensitive) const {
for (const_header_lines_key_iterator it = GetIteratorForKey(key);
it != lines().end(); ++it) {
absl::string_view line = it->second;
absl::string_view::size_type idx =
case_sensitive ? line.find(value, 0) : FindIgnoreCase(line, value);
while (idx != absl::string_view::npos) {
absl::string_view::difference_type end_idx = idx + value.size();
if (SurroundedOnlyBySpacesAndCommas(idx, end_idx, line)) {
return true;
}
idx = line.find(value, idx + 1);
}
}
return false;
}
bool BalsaHeaders::HasNonEmptyHeader(absl::string_view key) const {
for (const_header_lines_key_iterator it = GetIteratorForKey(key);
it != header_lines_key_end(); ++it) {
if (!it->second.empty()) {
return true;
}
}
return false;
}
std::string BalsaHeaders::GetAllOfHeaderAsString(absl::string_view key) const {
// Use custom formatter to ignore header key and join only header values.
// absl::AlphaNumFormatter is the default formatter for absl::StrJoin().
auto formatter = [](std::string* out,
std::pair<absl::string_view, absl::string_view> header) {
return absl::AlphaNumFormatter()(out, header.second);
};
return absl::StrJoin(GetIteratorForKey(key), header_lines_key_end(), ",",
formatter);
}
void BalsaHeaders::RemoveAllOfHeaderInList(const HeaderTokenList& keys) {
if (keys.empty()) {
return;
}
// This extra copy sacrifices some performance to prevent the possible
// mistakes that the caller does not lower case the headers in keys.
// Better performance can be achieved by asking caller to lower case
// the keys and RemoveAllOfheaderInlist just does lookup.
absl::flat_hash_set<std::string> lowercase_keys;
for (const auto& key : keys) {
MaybeClearSpecialHeaderValues(key);
lowercase_keys.insert(absl::AsciiStrToLower(key));
}
for (HeaderLineDescription& line : header_lines_) {
if (line.skip) {
continue;
}
// Remove the header if it matches any of the keys to remove.
const size_t key_len = line.key_end_idx - line.first_char_idx;
absl::string_view key(GetPtr(line.buffer_base_idx) + line.first_char_idx,
key_len);
std::string lowercase_key = absl::AsciiStrToLower(key);
if (lowercase_keys.count(lowercase_key) != 0) {
line.skip = true;
}
}
}
void BalsaHeaders::RemoveAllOfHeader(absl::string_view key) {
HeaderLines::iterator it = GetHeaderLinesIterator(key, header_lines_.begin());
RemoveAllOfHeaderStartingAt(key, it);
}
void BalsaHeaders::RemoveAllHeadersWithPrefix(absl::string_view prefix) {
for (HeaderLines::size_type i = 0; i < header_lines_.size(); ++i) {
if (header_lines_[i].skip) {
continue;
}
HeaderLineDescription& line = header_lines_[i];
const size_t key_len = line.key_end_idx - line.first_char_idx;
if (key_len < prefix.size()) {
continue;
}
const absl::string_view current_key_prefix(
GetPtr(line.buffer_base_idx) + line.first_char_idx, prefix.size());
if (absl::EqualsIgnoreCase(current_key_prefix, prefix)) {
const absl::string_view current_key(
GetPtr(line.buffer_base_idx) + line.first_char_idx, key_len);
MaybeClearSpecialHeaderValues(current_key);
line.skip = true;
}
}
}
bool BalsaHeaders::HasHeadersWithPrefix(absl::string_view prefix) const {
for (HeaderLines::size_type i = 0; i < header_lines_.size(); ++i) {
if (header_lines_[i].skip) {
continue;
}
const HeaderLineDescription& line = header_lines_[i];
if (line.key_end_idx - line.first_char_idx < prefix.size()) {
continue;
}
const absl::string_view current_key_prefix(
GetPtr(line.buffer_base_idx) + line.first_char_idx, prefix.size());
if (absl::EqualsIgnoreCase(current_key_prefix, prefix)) {
return true;
}
}
return false;
}
void BalsaHeaders::GetAllOfHeaderWithPrefix(
absl::string_view prefix,
std::vector<std::pair<absl::string_view, absl::string_view>>* out) const {
for (HeaderLines::size_type i = 0; i < header_lines_.size(); ++i) {
if (header_lines_[i].skip) {
continue;
}
const HeaderLineDescription& line = header_lines_[i];
absl::string_view key(GetPtr(line.buffer_base_idx) + line.first_char_idx,
line.key_end_idx - line.first_char_idx);
if (absl::StartsWithIgnoreCase(key, prefix)) {
out->push_back(std::make_pair(
key,
absl::string_view(GetPtr(line.buffer_base_idx) + line.value_begin_idx,
line.last_char_idx - line.value_begin_idx)));
}
}
}
void BalsaHeaders::GetAllHeadersWithLimit(
std::vector<std::pair<absl::string_view, absl::string_view>>* out,
int limit) const {
for (HeaderLines::size_type i = 0; i < header_lines_.size(); ++i) {
if (limit >= 0 && out->size() >= static_cast<size_t>(limit)) {
return;
}
if (header_lines_[i].skip) {
continue;
}
const HeaderLineDescription& line = header_lines_[i];
absl::string_view key(GetPtr(line.buffer_base_idx) + line.first_char_idx,
line.key_end_idx - line.first_char_idx);
out->push_back(std::make_pair(
key,
absl::string_view(GetPtr(line.buffer_base_idx) + line.value_begin_idx,
line.last_char_idx - line.value_begin_idx)));
}
}
size_t BalsaHeaders::RemoveValue(absl::string_view key,
absl::string_view search_value) {
// Remove whitespace around search value.
absl::string_view needle = search_value;
RemoveWhitespaceContext(&needle);
QUICHE_BUG_IF(bug_22783_2, needle != search_value)
<< "Search value should not be surrounded by spaces.";
// We have nothing to do for empty needle strings.
if (needle.empty()) {
return 0;
}
// The return value: number of removed values.
size_t removals = 0;
// Iterate over all header lines matching key with skip=false.
for (HeaderLines::iterator it =
GetHeaderLinesIterator(key, header_lines_.begin());
it != header_lines_.end(); it = GetHeaderLinesIterator(key, ++it)) {
HeaderLineDescription* line = &(*it);
// If needle given to us is longer than this header, don't consider it.
if (line->ValuesLength() < needle.size()) {
continue;
}
// If the values are equivalent, just remove the whole line.
char* buf = GetPtr(line->buffer_base_idx); // The head of our buffer.
char* value_begin = buf + line->value_begin_idx;
// StringPiece containing values that have yet to be processed. The head of
// this stringpiece will continually move forward, and its tail
// (head+length) will always remain the same.
absl::string_view values(value_begin, line->ValuesLength());
RemoveWhitespaceContext(&values);
if (values.size() == needle.size()) {
if (values == needle) {
line->skip = true;
removals++;
}
continue;
}
// Find all occurrences of the needle to be removed.
char* insertion = value_begin;
while (values.size() >= needle.size()) {
// Strip leading whitespace.
ssize_t cur_leading_whitespace = RemoveLeadingWhitespace(&values);
// See if we've got a match (at least as a prefix).
bool found = absl::StartsWith(values, needle);
// Find the entirety of this value (including trailing comma if existent).
const size_t next_comma =
values.find(',', /* pos = */ (found ? needle.size() : 0));
const bool comma_found = next_comma != absl::string_view::npos;
const size_t cur_size = (comma_found ? next_comma + 1 : values.size());
// Make sure that our prefix match is a full match.
if (found && cur_size != needle.size()) {
absl::string_view cur(values.data(), cur_size);
if (comma_found) {
cur.remove_suffix(1);
}
RemoveTrailingWhitespace(&cur);
found = (cur.size() == needle.size());
}
// Move as necessary (avoid move just for the sake of leading whitespace).
if (found) {
removals++;
// Remove trailing comma if we happen to have found the last value.
if (!comma_found) {
// We modify insertion since it'll be used to update last_char_idx.
insertion--;
}
} else {
if (insertion + cur_leading_whitespace != values.data()) {
// Has the side-effect of also copying any trailing whitespace.
memmove(insertion, values.data(), cur_size);
insertion += cur_size;
} else {
insertion += cur_leading_whitespace + cur_size;
}
}
// No longer consider the current value. (Increment.)
values.remove_prefix(cur_size);
}
// Move remaining data.
if (!values.empty()) {
if (insertion != values.data()) {
memmove(insertion, values.data(), values.size());
}
insertion += values.size();
}
// Set new line size.
if (insertion <= value_begin) {
// All values removed.
line->skip = true;
} else {
line->last_char_idx = insertion - buf;
}
}
return removals;
}
size_t BalsaHeaders::GetSizeForWriteBuffer() const {
// First add the space required for the first line + line separator.
size_t write_buf_size = whitespace_4_idx_ - non_whitespace_1_idx_ + 2;
// Then add the space needed for each header line to write out + line
// separator.
const HeaderLines::size_type end = header_lines_.size();
for (HeaderLines::size_type i = 0; i < end; ++i) {
const HeaderLineDescription& line = header_lines_[i];
if (!line.skip) {
// Add the key size and ": ".
write_buf_size += line.key_end_idx - line.first_char_idx + 2;
// Add the value size and the line separator.
write_buf_size += line.last_char_idx - line.value_begin_idx + 2;
}
}
// Finally tack on the terminal line separator.
return write_buf_size + 2;
}
void BalsaHeaders::DumpToString(std::string* str) const {
DumpToPrefixedString(" ", str);
}
std::string BalsaHeaders::DebugString() const {
std::string s;
DumpToString(&s);
return s;
}
bool BalsaHeaders::ForEachHeader(
std::function<bool(const absl::string_view key,
const absl::string_view value)>
fn) const {
int s = header_lines_.size();
for (int i = 0; i < s; ++i) {
const HeaderLineDescription& desc = header_lines_[i];
if (!desc.skip && desc.KeyLength() > 0) {
const char* stream_begin = GetPtr(desc.buffer_base_idx);
if (!fn(absl::string_view(stream_begin + desc.first_char_idx,
desc.KeyLength()),
absl::string_view(stream_begin + desc.value_begin_idx,
desc.ValuesLength()))) {
return false;
}
}
}
return true;
}
void BalsaHeaders::DumpToPrefixedString(const char* spaces,
std::string* str) const {
const absl::string_view firstline = first_line();
const int buffer_length = GetReadableBytesFromHeaderStream();
// First check whether the header object is empty.
if (firstline.empty() && buffer_length == 0) {
absl::StrAppend(str, "\n", spaces, "<empty header>\n");
return;
}
// Then check whether the header is in a partially parsed state. If so, just
// dump the raw data.
if (!FramerIsDoneWriting()) {
absl::StrAppendFormat(str, "\n%s<incomplete header len: %d>\n%s%.*s\n",
spaces, buffer_length, spaces, buffer_length,
OriginalHeaderStreamBegin());
return;
}
// If the header is complete, then just dump them with the logical key value
// pair.
str->reserve(str->size() + GetSizeForWriteBuffer());
absl::StrAppend(str, "\n", spaces, firstline, "\n");
for (const auto& line : lines()) {
absl::StrAppend(str, spaces, line.first, ": ", line.second, "\n");
}
}
void BalsaHeaders::SetContentLength(size_t length) {
// If the content-length is already the one we want, don't do anything.
if (content_length_status_ == BalsaHeadersEnums::VALID_CONTENT_LENGTH &&
content_length_ == length) {
return;
}
// If header state indicates that there is either a content length or
// transfer encoding header, remove them before adding the new content
// length. There is always the possibility that client can manually add
// either header directly and cause content_length_status_ or
// transfer_encoding_is_chunked_ to be inconsistent with the actual header.
// In the interest of efficiency, however, we will assume that clients will
// use the header object correctly and thus we will not scan the all headers
// each time this function is called.
if (content_length_status_ != BalsaHeadersEnums::NO_CONTENT_LENGTH) {
RemoveAllOfHeader(kContentLength);
} else if (transfer_encoding_is_chunked_) {
RemoveAllOfHeader(kTransferEncoding);
}
content_length_status_ = BalsaHeadersEnums::VALID_CONTENT_LENGTH;
content_length_ = length;
AppendHeader(kContentLength, absl::StrCat(length));
}
void BalsaHeaders::SetTransferEncodingToChunkedAndClearContentLength() {
if (transfer_encoding_is_chunked_) {
return;
}
if (content_length_status_ != BalsaHeadersEnums::NO_CONTENT_LENGTH) {
// Per https://httpwg.org/specs/rfc7230.html#header.content-length, we can't
// send both transfer-encoding and content-length.
ClearContentLength();
}
ReplaceOrAppendHeader(kTransferEncoding, "chunked");
transfer_encoding_is_chunked_ = true;
}
void BalsaHeaders::SetNoTransferEncoding() {
if (transfer_encoding_is_chunked_) {
// clears transfer_encoding_is_chunked_
RemoveAllOfHeader(kTransferEncoding);
}
}
void BalsaHeaders::ClearContentLength() { RemoveAllOfHeader(kContentLength); }
bool BalsaHeaders::IsEmpty() const {
return balsa_buffer_.GetTotalBytesUsed() == 0;
}
absl::string_view BalsaHeaders::Authority() const { return GetHeader(kHost); }
void BalsaHeaders::ReplaceOrAppendAuthority(absl::string_view value) {
ReplaceOrAppendHeader(kHost, value);
}
void BalsaHeaders::RemoveAuthority() { RemoveAllOfHeader(kHost); }
void BalsaHeaders::ApplyToCookie(
std::function<void(absl::string_view cookie)> f) const {
f(GetHeader(kCookie));
}
void BalsaHeaders::SetResponseFirstline(absl::string_view version,
size_t parsed_response_code,
absl::string_view reason_phrase) {
SetFirstlineFromStringPieces(version, absl::StrCat(parsed_response_code),
reason_phrase);
parsed_response_code_ = parsed_response_code;
}
void BalsaHeaders::SetFirstlineFromStringPieces(absl::string_view firstline_a,
absl::string_view firstline_b,
absl::string_view firstline_c) {
size_t line_size =
(firstline_a.size() + firstline_b.size() + firstline_c.size() + 2);
char* storage = balsa_buffer_.Reserve(line_size, &firstline_buffer_base_idx_);
char* cur_loc = storage;
memcpy(cur_loc, firstline_a.data(), firstline_a.size());
cur_loc += firstline_a.size();
*cur_loc = ' ';
++cur_loc;
memcpy(cur_loc, firstline_b.data(), firstline_b.size());
cur_loc += firstline_b.size();
*cur_loc = ' ';
++cur_loc;
memcpy(cur_loc, firstline_c.data(), firstline_c.size());
whitespace_1_idx_ = storage - BeginningOfFirstLine();
non_whitespace_1_idx_ = whitespace_1_idx_;
whitespace_2_idx_ = non_whitespace_1_idx_ + firstline_a.size();
non_whitespace_2_idx_ = whitespace_2_idx_ + 1;
whitespace_3_idx_ = non_whitespace_2_idx_ + firstline_b.size();
non_whitespace_3_idx_ = whitespace_3_idx_ + 1;
whitespace_4_idx_ = non_whitespace_3_idx_ + firstline_c.size();
}
void BalsaHeaders::SetRequestMethod(absl::string_view method) {
// This is the first of the three parts of the firstline.
if (method.size() <= (whitespace_2_idx_ - non_whitespace_1_idx_)) {
non_whitespace_1_idx_ = whitespace_2_idx_ - method.size();
if (!method.empty()) {
char* stream_begin = BeginningOfFirstLine();
memcpy(stream_begin + non_whitespace_1_idx_, method.data(),
method.size());
}
} else {
// The new method is too large to fit in the space available for the old
// one, so we have to reformat the firstline.
SetRequestFirstlineFromStringPieces(method, request_uri(),
request_version());
}
}
void BalsaHeaders::SetResponseVersion(absl::string_view version) {
// Note: There is no difference between request_method() and
// response_Version(). Thus, a function to set one is equivalent to a
// function to set the other. We maintain two functions for this as it is
// much more descriptive, and makes code more understandable.
SetRequestMethod(version);
}
void BalsaHeaders::SetRequestUri(absl::string_view uri) {
SetRequestFirstlineFromStringPieces(request_method(), uri, request_version());
}
void BalsaHeaders::SetResponseCode(absl::string_view code) {
// Note: There is no difference between request_uri() and response_code().
// Thus, a function to set one is equivalent to a function to set the other.
// We maintain two functions for this as it is much more descriptive, and
// makes code more understandable.
SetRequestUri(code);
}
void BalsaHeaders::SetParsedResponseCodeAndUpdateFirstline(
size_t parsed_response_code) {
parsed_response_code_ = parsed_response_code;
SetResponseCode(absl::StrCat(parsed_response_code));
}
void BalsaHeaders::SetRequestVersion(absl::string_view version) {
// This is the last of the three parts of the firstline.
// Since whitespace_3_idx and non_whitespace_3_idx may point to the same
// place, we ensure below that any available space includes space for a
// literal space (' ') character between the second component and the third
// component.
bool fits_in_space_allowed =
version.size() + 1 <= whitespace_4_idx_ - whitespace_3_idx_;
if (!fits_in_space_allowed) {
// If the new version is too large, then reformat the firstline.
SetRequestFirstlineFromStringPieces(request_method(), request_uri(),
version);
return;
}
char* stream_begin = BeginningOfFirstLine();
*(stream_begin + whitespace_3_idx_) = ' ';
non_whitespace_3_idx_ = whitespace_3_idx_ + 1;
whitespace_4_idx_ = non_whitespace_3_idx_ + version.size();
memcpy(stream_begin + non_whitespace_3_idx_, version.data(), version.size());
}
void BalsaHeaders::SetResponseReasonPhrase(absl::string_view reason) {
// Note: There is no difference between request_version() and
// response_reason_phrase(). Thus, a function to set one is equivalent to a
// function to set the other. We maintain two functions for this as it is
// much more descriptive, and makes code more understandable.
SetRequestVersion(reason);
}
void BalsaHeaders::RemoveLastTokenFromHeaderValue(absl::string_view key) {
BalsaHeaders::HeaderLines::iterator it =
GetHeaderLinesIterator(key, header_lines_.begin());
if (it == header_lines_.end()) {
QUICHE_DLOG(WARNING)
<< "Attempting to remove last token from a non-existent "
<< "header \"" << key << "\"";
return;
}
// Find the last line with that key.
BalsaHeaders::HeaderLines::iterator header_line;
do {
header_line = it;
it = GetHeaderLinesIterator(key, it + 1);
} while (it != header_lines_.end());
// Tokenize just that line.
BalsaHeaders::HeaderTokenList tokens;
// Find where this line is stored.
const char* stream_begin = GetPtr(header_line->buffer_base_idx);
absl::string_view value(
stream_begin + header_line->value_begin_idx,
header_line->last_char_idx - header_line->value_begin_idx);
// Tokenize.
ParseTokenList(value, &tokens);
if (tokens.empty()) {
QUICHE_DLOG(WARNING)
<< "Attempting to remove a token from an empty header value "
<< "for header \"" << key << "\"";
header_line->skip = true; // remove the whole line
} else if (tokens.size() == 1) {
header_line->skip = true; // remove the whole line
} else {
// Shrink the line size and leave the extra data in the buffer.
absl::string_view new_last_token = tokens[tokens.size() - 2];
const char* last_char_address =
new_last_token.data() + new_last_token.size() - 1;
const char* stream_begin = GetPtr(header_line->buffer_base_idx);
header_line->last_char_idx = last_char_address - stream_begin + 1;
}
}
bool BalsaHeaders::ResponseCanHaveBody(int response_code) {
// For responses, can't have a body if the request was a HEAD, or if it is
// one of these response-codes. rfc2616 section 4.3
if (response_code >= 100 && response_code < 200) {
// 1xx responses can't have bodies.
return false;
}
// No content and Not modified responses have no body.
return (response_code != 204) && (response_code != 304);
}
} // namespace quiche