Internal change
PiperOrigin-RevId: 442098384
diff --git a/quiche/common/balsa/balsa_enums.cc b/quiche/common/balsa/balsa_enums.cc
new file mode 100644
index 0000000..1bafa62
--- /dev/null
+++ b/quiche/common/balsa/balsa_enums.cc
@@ -0,0 +1,115 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/balsa/balsa_enums.h"
+
+namespace quiche {
+
+const char* BalsaFrameEnums::ParseStateToString(
+ BalsaFrameEnums::ParseState error_code) {
+ switch (error_code) {
+ case ERROR:
+ return "ERROR";
+ case READING_HEADER_AND_FIRSTLINE:
+ return "READING_HEADER_AND_FIRSTLINE";
+ case READING_CHUNK_LENGTH:
+ return "READING_CHUNK_LENGTH";
+ case READING_CHUNK_EXTENSION:
+ return "READING_CHUNK_EXTENSION";
+ case READING_CHUNK_DATA:
+ return "READING_CHUNK_DATA";
+ case READING_CHUNK_TERM:
+ return "READING_CHUNK_TERM";
+ case READING_LAST_CHUNK_TERM:
+ return "READING_LAST_CHUNK_TERM";
+ case READING_TRAILER:
+ return "READING_TRAILER";
+ case READING_UNTIL_CLOSE:
+ return "READING_UNTIL_CLOSE";
+ case READING_CONTENT:
+ return "READING_CONTENT";
+ case MESSAGE_FULLY_READ:
+ return "MESSAGE_FULLY_READ";
+ case NUM_STATES:
+ return "UNKNOWN_STATE";
+ }
+ return "UNKNOWN_STATE";
+}
+
+const char* BalsaFrameEnums::ErrorCodeToString(
+ BalsaFrameEnums::ErrorCode error_code) {
+ switch (error_code) {
+ case NO_ERROR:
+ return "NO_ERROR";
+ case NO_STATUS_LINE_IN_RESPONSE:
+ return "NO_STATUS_LINE_IN_RESPONSE";
+ case NO_REQUEST_LINE_IN_REQUEST:
+ return "NO_REQUEST_LINE_IN_REQUEST";
+ case FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION:
+ return "FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION";
+ case FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD:
+ return "FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD";
+ case FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE:
+ return "FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE";
+ case FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI:
+ return "FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI";
+ case FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE:
+ return "FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE";
+ case FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION:
+ return "FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION";
+ case FAILED_CONVERTING_STATUS_CODE_TO_INT:
+ return "FAILED_CONVERTING_STATUS_CODE_TO_INT";
+ case HEADERS_TOO_LONG:
+ return "HEADERS_TOO_LONG";
+ case UNPARSABLE_CONTENT_LENGTH:
+ return "UNPARSABLE_CONTENT_LENGTH";
+ case HTTP2_CONTENT_LENGTH_ERROR:
+ return "HTTP2_CONTENT_LENGTH_ERROR";
+ case MAYBE_BODY_BUT_NO_CONTENT_LENGTH:
+ return "MAYBE_BODY_BUT_NO_CONTENT_LENGTH";
+ case REQUIRED_BODY_BUT_NO_CONTENT_LENGTH:
+ return "REQUIRED_BODY_BUT_NO_CONTENT_LENGTH";
+ case HEADER_MISSING_COLON:
+ return "HEADER_MISSING_COLON";
+ case INVALID_CHUNK_LENGTH:
+ return "INVALID_CHUNK_LENGTH";
+ case CHUNK_LENGTH_OVERFLOW:
+ return "CHUNK_LENGTH_OVERFLOW";
+ case CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO:
+ return "CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO";
+ case CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT:
+ return "CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT";
+ case MULTIPLE_CONTENT_LENGTH_KEYS:
+ return "MULTIPLE_CONTENT_LENGTH_KEYS";
+ case MULTIPLE_TRANSFER_ENCODING_KEYS:
+ return "MULTIPLE_TRANSFER_ENCODING_KEYS";
+ case UNKNOWN_TRANSFER_ENCODING:
+ return "UNKNOWN_TRANSFER_ENCODING";
+ case BOTH_TRANSFER_ENCODING_AND_CONTENT_LENGTH:
+ return "BOTH_TRANSFER_ENCODING_AND_CONTENT_LENGTH";
+ case INVALID_HEADER_FORMAT:
+ return "INVALID_HEADER_FORMAT";
+ case HTTP2_INVALID_HEADER_FORMAT:
+ return "HTTP2_INVALID_HEADER_FORMAT";
+ case INVALID_TRAILER_FORMAT:
+ return "INVALID_TRAILER_FORMAT";
+ case TRAILER_TOO_LONG:
+ return "TRAILER_TOO_LONG";
+ case TRAILER_MISSING_COLON:
+ return "TRAILER_MISSING_COLON";
+ case INTERNAL_LOGIC_ERROR:
+ return "INTERNAL_LOGIC_ERROR";
+ case INVALID_HEADER_CHARACTER:
+ return "INVALID_HEADER_CHARACTER";
+ case INVALID_HEADER_NAME_CHARACTER:
+ return "INVALID_HEADER_NAME_CHARACTER";
+ case INVALID_TRAILER_NAME_CHARACTER:
+ return "INVALID_TRAILER_NAME_CHARACTER";
+ case NUM_ERROR_CODES:
+ return "UNKNOWN_ERROR";
+ }
+ return "UNKNOWN_ERROR";
+}
+
+} // namespace quiche
diff --git a/quiche/common/balsa/balsa_enums.h b/quiche/common/balsa/balsa_enums.h
new file mode 100644
index 0000000..0136305
--- /dev/null
+++ b/quiche/common/balsa/balsa_enums.h
@@ -0,0 +1,126 @@
+// Copyright 2021 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.
+
+#ifndef QUICHE_COMMON_BALSA_BALSA_ENUMS_H_
+#define QUICHE_COMMON_BALSA_BALSA_ENUMS_H_
+
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quiche {
+
+struct QUICHE_EXPORT_PRIVATE BalsaFrameEnums {
+ enum ParseState {
+ ERROR,
+ READING_HEADER_AND_FIRSTLINE,
+ READING_CHUNK_LENGTH,
+ READING_CHUNK_EXTENSION,
+ READING_CHUNK_DATA,
+ READING_CHUNK_TERM,
+ READING_LAST_CHUNK_TERM,
+ READING_TRAILER,
+ READING_UNTIL_CLOSE,
+ READING_CONTENT,
+ MESSAGE_FULLY_READ,
+ NUM_STATES,
+ };
+
+ enum ErrorCode {
+ // A sentinel value for convenience, none of the callbacks should ever see
+ // this error code.
+ NO_ERROR = 0,
+
+ // Header parsing errors
+ // Note that adding one to many of the REQUEST errors yields the
+ // appropriate RESPONSE error.
+ // Particularly, when parsing the first line of a request or response,
+ // there are three sequences of non-whitespace regardless of whether or
+ // not it is a request or response. These are listed below, in order.
+ //
+ // firstline_a firstline_b firstline_c
+ // REQ: method request_uri version
+ // RESP: version statuscode reason
+ //
+ // As you can see, the first token is the 'method' field for a request,
+ // and 'version' field for a response. We call the first non whitespace
+ // token firstline_a, the second firstline_b, and the third token
+ // followed by [^\r\n]*) firstline_c.
+ //
+ // This organization is important, as it lets us determine the error code
+ // to use without a branch based on is_response. Instead, we simply add
+ // is_response to the response error code-- If is_response is true, then
+ // we'll get the response error code, thanks to the fact that the error
+ // code numbers are organized to ensure that response error codes always
+ // precede request error codes.
+ // | Triggered
+ // | while processing
+ // | this NONWS
+ // | sequence...
+ NO_STATUS_LINE_IN_RESPONSE, // |
+ NO_REQUEST_LINE_IN_REQUEST, // |
+ FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION, // | firstline_a
+ FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD, // | firstline_a
+ FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE, // | firstline_b
+ FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI, // | firstline_b
+ FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE, // | firstline_c
+ FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION, // | firstline_c
+
+ FAILED_CONVERTING_STATUS_CODE_TO_INT,
+
+ HEADERS_TOO_LONG,
+ UNPARSABLE_CONTENT_LENGTH,
+ // Warning: there may be a body but there was no content-length/chunked
+ // encoding
+ MAYBE_BODY_BUT_NO_CONTENT_LENGTH,
+
+ // This is used if a body is required for a request.
+ REQUIRED_BODY_BUT_NO_CONTENT_LENGTH,
+
+ HEADER_MISSING_COLON,
+
+ // Chunking errors
+ INVALID_CHUNK_LENGTH,
+ CHUNK_LENGTH_OVERFLOW,
+
+ // Other errors.
+ CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO,
+ CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT,
+ MULTIPLE_CONTENT_LENGTH_KEYS,
+ MULTIPLE_TRANSFER_ENCODING_KEYS,
+ UNKNOWN_TRANSFER_ENCODING,
+ BOTH_TRANSFER_ENCODING_AND_CONTENT_LENGTH,
+ INVALID_HEADER_FORMAT,
+ HTTP2_INVALID_HEADER_FORMAT,
+ HTTP2_CONTENT_LENGTH_ERROR,
+
+ // Trailer errors.
+ INVALID_TRAILER_FORMAT,
+ TRAILER_TOO_LONG,
+ TRAILER_MISSING_COLON,
+
+ // A detected internal inconsistency was found.
+ INTERNAL_LOGIC_ERROR,
+
+ // A control character was found in a header key or value
+ INVALID_HEADER_CHARACTER,
+ INVALID_HEADER_NAME_CHARACTER,
+ INVALID_TRAILER_NAME_CHARACTER,
+
+ NUM_ERROR_CODES
+ };
+ static const char* ParseStateToString(ParseState error_code);
+ static const char* ErrorCodeToString(ErrorCode error_code);
+};
+
+struct QUICHE_EXPORT_PRIVATE BalsaHeadersEnums {
+ enum ContentLengthStatus {
+ INVALID_CONTENT_LENGTH,
+ CONTENT_LENGTH_OVERFLOW,
+ NO_CONTENT_LENGTH,
+ VALID_CONTENT_LENGTH,
+ };
+};
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_BALSA_BALSA_ENUMS_H_
diff --git a/quiche/common/balsa/balsa_frame.cc b/quiche/common/balsa/balsa_frame.cc
new file mode 100644
index 0000000..39853e3
--- /dev/null
+++ b/quiche/common/balsa/balsa_frame.cc
@@ -0,0 +1,1301 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/balsa/balsa_frame.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+
+#include "absl/strings/match.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/string_view.h"
+#include "quiche/common/balsa/balsa_enums.h"
+#include "quiche/common/balsa/balsa_headers.h"
+#include "quiche/common/balsa/balsa_visitor_interface.h"
+#include "quiche/common/balsa/header_properties.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+
+namespace quiche {
+
+namespace {
+
+const size_t kContinueStatusCode = 100;
+
+constexpr absl::string_view kChunked = "chunked";
+constexpr absl::string_view kContentLength = "content-length";
+constexpr absl::string_view kIdentity = "identity";
+constexpr absl::string_view kTransferEncoding = "transfer-encoding";
+
+std::array<bool, 256> buildInvalidHeaderKeyCharLookupTable() {
+ std::array<bool, 256> invalidCharTable;
+ invalidCharTable.fill(false);
+ for (char c : BalsaFrame::kInvalidHeaderKeyCharList) {
+ invalidCharTable[c] = true;
+ }
+ return invalidCharTable;
+}
+
+inline bool IsInvalidHeaderKeyChar(char c) {
+ static const std::array<bool, 256> invalidHeaderKeyCharTable =
+ buildInvalidHeaderKeyCharLookupTable();
+
+ return invalidHeaderKeyCharTable[c];
+}
+
+} // namespace
+
+void BalsaFrame::Reset() {
+ last_char_was_slash_r_ = false;
+ saw_non_newline_char_ = false;
+ start_was_space_ = true;
+ chunk_length_character_extracted_ = false;
+ // is_request_ = true; // not reset between messages.
+ allow_reading_until_close_for_request_ = false;
+ // request_was_head_ = false; // not reset between messages.
+ // max_header_length_ = 16 * 1024; // not reset between messages.
+ // visitor_ = &do_nothing_visitor_; // not reset between messages.
+ chunk_length_remaining_ = 0;
+ content_length_remaining_ = 0;
+ last_slash_n_loc_ = nullptr;
+ last_recorded_slash_n_loc_ = nullptr;
+ last_slash_n_idx_ = 0;
+ term_chars_ = 0;
+ parse_state_ = BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE;
+ last_error_ = BalsaFrameEnums::NO_ERROR;
+ invalid_chars_.clear();
+ lines_.clear();
+ if (continue_headers_ != nullptr) {
+ continue_headers_->Clear();
+ }
+ if (headers_ != nullptr) {
+ headers_->Clear();
+ }
+ trailer_lines_.clear();
+ start_of_trailer_line_ = 0;
+ trailer_length_ = 0;
+ if (trailer_ != nullptr) {
+ trailer_->Clear();
+ }
+}
+
+namespace {
+
+// Within the line bounded by [current, end), parses a single "island",
+// comprising a (possibly empty) span of whitespace followed by a (possibly
+// empty) span of non-whitespace.
+//
+// Returns a pointer to the first whitespace character beyond this island, or
+// returns end if no additional whitespace characters are present after this
+// island. (I.e., returnvalue == end || *returnvalue > ' ')
+//
+// Upon return, the whitespace span are the characters
+// whose indices fall in [*first_whitespace, *first_nonwhite), while the
+// non-whitespace span are the characters whose indices fall in
+// [*first_nonwhite, returnvalue - begin).
+inline const char* ParseOneIsland(const char* current, const char* begin,
+ const char* end, size_t* first_whitespace,
+ size_t* first_nonwhite) {
+ *first_whitespace = current - begin;
+ while (current < end && *current <= ' ') {
+ ++current;
+ }
+ *first_nonwhite = current - begin;
+ while (current<end&& * current> ' ') {
+ ++current;
+ }
+ return current;
+}
+
+} // namespace
+
+// Summary:
+// Parses the first line of either a request or response.
+// Note that in the case of a detected warning, error_code will be set
+// but the function will not return false.
+// Exactly zero or one warning or error (but not both) may be detected
+// by this function.
+// Note that this function will not write the data of the first-line
+// into the header's buffer (that should already have been done elsewhere).
+//
+// Pre-conditions:
+// begin != end
+// *begin should be a character which is > ' '. This implies that there
+// is at least one non-whitespace characters between [begin, end).
+// headers is a valid pointer to a BalsaHeaders class.
+// error_code is a valid pointer to a BalsaFrameEnums::ErrorCode value.
+// Entire first line must exist between [begin, end)
+// Exactly zero or one newlines -may- exist between [begin, end)
+// [begin, end) should exist in the header's buffer.
+//
+// Side-effects:
+// headers will be modified
+// error_code may be modified if either a warning or error is detected
+//
+// Returns:
+// True if no error (as opposed to warning) is detected.
+// False if an error (as opposed to warning) is detected.
+
+//
+// If there is indeed non-whitespace in the line, then the following
+// will take care of this for you:
+// while (*begin <= ' ') ++begin;
+// ProcessFirstLine(begin, end, is_request, &headers, &error_code);
+//
+
+bool ParseHTTPFirstLine(const char* begin, const char* end, bool is_request,
+ BalsaHeaders* headers,
+ BalsaFrameEnums::ErrorCode* error_code) {
+ while (begin < end && (end[-1] == '\n' || end[-1] == '\r')) {
+ --end;
+ }
+
+ const char* current =
+ ParseOneIsland(begin, begin, end, &headers->whitespace_1_idx_,
+ &headers->non_whitespace_1_idx_);
+ current = ParseOneIsland(current, begin, end, &headers->whitespace_2_idx_,
+ &headers->non_whitespace_2_idx_);
+ current = ParseOneIsland(current, begin, end, &headers->whitespace_3_idx_,
+ &headers->non_whitespace_3_idx_);
+
+ // Clean up any trailing whitespace that comes after the third island
+ const char* last = end;
+ while (current <= last && *last <= ' ') {
+ --last;
+ }
+ headers->whitespace_4_idx_ = last - begin + 1;
+
+ // Either the passed-in line is empty, or it starts with a non-whitespace
+ // character.
+ QUICHE_DCHECK(begin == end || *begin > ' ');
+
+ QUICHE_DCHECK_EQ(0u, headers->whitespace_1_idx_);
+ QUICHE_DCHECK_EQ(0u, headers->non_whitespace_1_idx_);
+
+ // If the line isn't empty, it has at least one non-whitespace character (see
+ // first QUICHE_DCHECK), which will have been identified as a non-empty
+ // [non_whitespace_1_idx_, whitespace_2_idx_).
+ QUICHE_DCHECK(begin == end ||
+ headers->non_whitespace_1_idx_ < headers->whitespace_2_idx_);
+
+ if (headers->non_whitespace_2_idx_ == headers->whitespace_3_idx_) {
+ // This error may be triggered if the second token is empty, OR there's no
+ // WS after the first token; we don't bother to distinguish exactly which.
+ // (I'm not sure why we distinguish different kinds of parse error at all,
+ // actually.)
+ // FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD for request
+ // FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION for response
+ *error_code = static_cast<BalsaFrameEnums::ErrorCode>(
+ BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION +
+ static_cast<int>(is_request));
+ if (!is_request) { // FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION
+ return false;
+ }
+ }
+ if (headers->whitespace_3_idx_ == headers->non_whitespace_3_idx_) {
+ if (*error_code == BalsaFrameEnums::NO_ERROR) {
+ // FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD for request
+ // FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION for response
+ *error_code = static_cast<BalsaFrameEnums::ErrorCode>(
+ BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE +
+ static_cast<int>(is_request));
+ }
+ }
+
+ if (!is_request) {
+ headers->parsed_response_code_ = 0;
+ // If the response code is non-empty:
+ if (headers->non_whitespace_2_idx_ < headers->whitespace_3_idx_) {
+ if (!absl::SimpleAtoi(
+ absl::string_view(begin + headers->non_whitespace_2_idx_,
+ headers->non_whitespace_3_idx_ -
+ headers->non_whitespace_2_idx_),
+ &headers->parsed_response_code_)) {
+ *error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT;
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+// begin - beginning of the firstline
+// end - end of the firstline
+//
+// A precondition for this function is that there is non-whitespace between
+// [begin, end). If this precondition is not met, the function will not perform
+// as expected (and bad things may happen, and it will eat your first, second,
+// and third unborn children!).
+//
+// Another precondition for this function is that [begin, end) includes
+// at most one newline, which must be at the end of the line.
+void BalsaFrame::ProcessFirstLine(const char* begin, const char* end) {
+ BalsaFrameEnums::ErrorCode previous_error = last_error_;
+ if (!ParseHTTPFirstLine(begin, end, is_request_, headers_, &last_error_)) {
+ parse_state_ = BalsaFrameEnums::ERROR;
+ HandleError(last_error_);
+ return;
+ }
+ if (previous_error != last_error_) {
+ HandleWarning(last_error_);
+ }
+
+ if (is_request_) {
+ size_t version_length =
+ headers_->whitespace_4_idx_ - headers_->non_whitespace_3_idx_;
+ visitor_->OnRequestFirstLineInput(
+ begin + headers_->non_whitespace_1_idx_,
+ headers_->whitespace_4_idx_ - headers_->non_whitespace_1_idx_,
+ begin + headers_->non_whitespace_1_idx_,
+ headers_->whitespace_2_idx_ - headers_->non_whitespace_1_idx_,
+ begin + headers_->non_whitespace_2_idx_,
+ headers_->whitespace_3_idx_ - headers_->non_whitespace_2_idx_,
+ begin + headers_->non_whitespace_3_idx_, version_length);
+ if (version_length == 0) {
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ }
+ } else {
+ visitor_->OnResponseFirstLineInput(
+ begin + headers_->non_whitespace_1_idx_,
+ headers_->whitespace_4_idx_ - headers_->non_whitespace_1_idx_,
+ begin + headers_->non_whitespace_1_idx_,
+ headers_->whitespace_2_idx_ - headers_->non_whitespace_1_idx_,
+ begin + headers_->non_whitespace_2_idx_,
+ headers_->whitespace_3_idx_ - headers_->non_whitespace_2_idx_,
+ begin + headers_->non_whitespace_3_idx_,
+ headers_->whitespace_4_idx_ - headers_->non_whitespace_3_idx_);
+ }
+}
+
+// 'stream_begin' points to the first character of the headers buffer.
+// 'line_begin' points to the first character of the line.
+// 'current' points to a char which is ':'.
+// 'line_end' points to the position of '\n' + 1.
+// 'line_begin' points to the position of first character of line.
+void BalsaFrame::CleanUpKeyValueWhitespace(
+ const char* stream_begin, const char* line_begin, const char* current,
+ const char* line_end, HeaderLineDescription* current_header_line) {
+ const char* colon_loc = current;
+ QUICHE_DCHECK_LT(colon_loc, line_end);
+ QUICHE_DCHECK_EQ(':', *colon_loc);
+ QUICHE_DCHECK_EQ(':', *current);
+ QUICHE_DCHECK_GE(' ', *line_end)
+ << "\"" << std::string(line_begin, line_end) << "\"";
+
+ // TODO(fenix): Investigate whether or not the bounds tests in the
+ // while loops here are redundant, and if so, remove them.
+ --current;
+ while (current > line_begin && *current <= ' ') {
+ --current;
+ }
+ current += static_cast<int>(current != colon_loc);
+ current_header_line->key_end_idx = current - stream_begin;
+
+ current = colon_loc;
+ QUICHE_DCHECK_EQ(':', *current);
+ ++current;
+ while (current < line_end && *current <= ' ') {
+ ++current;
+ }
+ current_header_line->value_begin_idx = current - stream_begin;
+
+ QUICHE_DCHECK_GE(current_header_line->key_end_idx,
+ current_header_line->first_char_idx);
+ QUICHE_DCHECK_GE(current_header_line->value_begin_idx,
+ current_header_line->key_end_idx);
+ QUICHE_DCHECK_GE(current_header_line->last_char_idx,
+ current_header_line->value_begin_idx);
+}
+
+bool BalsaFrame::FindColonsAndParseIntoKeyValue(const Lines& lines,
+ bool is_trailer,
+ BalsaHeaders* headers) {
+ QUICHE_DCHECK(!lines.empty());
+ const char* stream_begin = headers->OriginalHeaderStreamBegin();
+ // The last line is always just a newline (and is uninteresting).
+ const Lines::size_type lines_size_m1 = lines.size() - 1;
+ // For a trailer, there is no first line, so lines[0] is the first header
+ // . For real headers, the first line takes lines[0], so real header starts
+ // at index 1.
+ int first_header_idx = (is_trailer ? 0 : 1);
+ const char* current = stream_begin + lines[first_header_idx].first;
+ // This code is a bit more subtle than it may appear at first glance.
+ // This code looks for a colon in the current line... but it also looks
+ // beyond the current line. If there is no colon in the current line, then
+ // for each subsequent line (until the colon which -has- been found is
+ // associated with a line), no searching for a colon will be performed. In
+ // this way, we minimize the amount of bytes we have scanned for a colon.
+ for (Lines::size_type i = first_header_idx; i < lines_size_m1;) {
+ const char* line_begin = stream_begin + lines[i].first;
+
+ // Here we handle possible continuations. Note that we do not replace
+ // the '\n' in the line before a continuation (at least, as of now),
+ // which implies that any code which looks for a value must deal with
+ // "\r\n", etc -within- the line (and not just at the end of it).
+ for (++i; i < lines_size_m1; ++i) {
+ const char c = *(stream_begin + lines[i].first);
+ if (c > ' ') {
+ // Not a continuation, so stop. Note that if the 'original' i = 1,
+ // and the next line is not a continuation, we'll end up with i = 2
+ // when we break. This handles the incrementing of i for the outer
+ // loop.
+ break;
+ }
+
+ // Space and tab are valid starts to continuation lines.
+ // https://tools.ietf.org/html/rfc7230#section-3.2.4 says that a proxy
+ // can choose to reject or normalize continuation lines.
+ if ((c != ' ' && c != '\t') ||
+ http_validation_policy().disallow_header_continuation_lines()) {
+ HandleError(is_trailer ? BalsaFrameEnums::INVALID_TRAILER_FORMAT
+ : BalsaFrameEnums::INVALID_HEADER_FORMAT);
+ return false;
+ }
+
+ // If disallow_header_continuation_lines() is false, we neither reject nor
+ // normalize continuation lines, in violation of RFC7230.
+ }
+ const char* line_end = stream_begin + lines[i - 1].second;
+ QUICHE_DCHECK_LT(line_begin - stream_begin, line_end - stream_begin);
+
+ // We cleanup the whitespace at the end of the line before doing anything
+ // else of interest as it allows us to do nothing when irregularly formatted
+ // headers are parsed (e.g. those with only keys, only values, or no colon).
+ //
+ // We're guaranteed to have *line_end > ' ' while line_end >= line_begin.
+ --line_end;
+ QUICHE_DCHECK_EQ('\n', *line_end)
+ << "\"" << std::string(line_begin, line_end) << "\"";
+ while (*line_end <= ' ' && line_end > line_begin) {
+ --line_end;
+ }
+ ++line_end;
+ QUICHE_DCHECK_GE(' ', *line_end);
+ QUICHE_DCHECK_LT(line_begin, line_end);
+
+ // We use '0' for the block idx, because we're always writing to the first
+ // block from the framer (we do this because the framer requires that the
+ // entire header sequence be in a contiguous buffer).
+ headers->header_lines_.push_back(HeaderLineDescription(
+ line_begin - stream_begin, line_end - stream_begin,
+ line_end - stream_begin, line_end - stream_begin, 0));
+ if (current >= line_end) {
+ if (http_validation_policy().require_header_colon()) {
+ HandleError(is_trailer ? BalsaFrameEnums::TRAILER_MISSING_COLON
+ : BalsaFrameEnums::HEADER_MISSING_COLON);
+ return false;
+ }
+ HandleWarning(is_trailer ? BalsaFrameEnums::TRAILER_MISSING_COLON
+ : BalsaFrameEnums::HEADER_MISSING_COLON);
+ // Then the next colon will not be found within this header line-- time
+ // to try again with another header-line.
+ continue;
+ } else if (current < line_begin) {
+ // When this condition is true, the last detected colon was part of a
+ // previous line. We reset to the beginning of the line as we don't care
+ // about the presence of any colon before the beginning of the current
+ // line.
+ current = line_begin;
+ }
+ for (; current < line_end; ++current) {
+ if (*current == ':') {
+ break;
+ }
+
+ if (http_validation_policy().enforce_header_characters() &&
+ IsInvalidHeaderKeyChar(*current)) {
+ // Generally invalid characters were found earlier.
+ HandleError(is_trailer
+ ? BalsaFrameEnums::INVALID_TRAILER_NAME_CHARACTER
+ : BalsaFrameEnums::INVALID_HEADER_NAME_CHARACTER);
+ return false;
+ }
+ }
+
+ if (current == line_end) {
+ // There was no colon in the line. The arguments we passed into the
+ // construction for the HeaderLineDescription object should be OK-- it
+ // assumes that the entire content is 'key' by default (which is true, as
+ // there was no colon, there can be no value). Note that this is a
+ // construct which is technically not allowed by the spec.
+
+ // In strict mode, we do treat this invalid value-less key as an error.
+ if (http_validation_policy().require_header_colon()) {
+ HandleError(is_trailer ? BalsaFrameEnums::TRAILER_MISSING_COLON
+ : BalsaFrameEnums::HEADER_MISSING_COLON);
+ return false;
+ }
+ HandleWarning(is_trailer ? BalsaFrameEnums::TRAILER_MISSING_COLON
+ : BalsaFrameEnums::HEADER_MISSING_COLON);
+ continue;
+ }
+
+ QUICHE_DCHECK_EQ(*current, ':');
+ QUICHE_DCHECK_LE(current - stream_begin, line_end - stream_begin);
+ QUICHE_DCHECK_LE(stream_begin - stream_begin, current - stream_begin);
+
+ HeaderLineDescription& current_header_line = headers->header_lines_.back();
+ current_header_line.key_end_idx = current - stream_begin;
+ current_header_line.value_begin_idx = current_header_line.key_end_idx;
+ if (current < line_end) {
+ ++current_header_line.key_end_idx;
+
+ CleanUpKeyValueWhitespace(stream_begin, line_begin, current, line_end,
+ ¤t_header_line);
+ }
+ }
+
+ return true;
+}
+
+void BalsaFrame::HandleWarning(BalsaFrameEnums::ErrorCode error_code) {
+ last_error_ = error_code;
+ visitor_->HandleWarning(last_error_);
+}
+
+void BalsaFrame::HandleError(BalsaFrameEnums::ErrorCode error_code) {
+ last_error_ = error_code;
+ parse_state_ = BalsaFrameEnums::ERROR;
+ visitor_->HandleError(last_error_);
+}
+
+BalsaHeadersEnums::ContentLengthStatus BalsaFrame::ProcessContentLengthLine(
+ HeaderLines::size_type line_idx, size_t* length) {
+ const HeaderLineDescription& header_line = headers_->header_lines_[line_idx];
+ const char* stream_begin = headers_->OriginalHeaderStreamBegin();
+ const char* line_end = stream_begin + header_line.last_char_idx;
+ const char* value_begin = (stream_begin + header_line.value_begin_idx);
+
+ if (value_begin >= line_end) {
+ // There is no non-whitespace value data.
+ DVLOG(1) << "invalid content-length -- no non-whitespace value data";
+ return BalsaHeadersEnums::INVALID_CONTENT_LENGTH;
+ }
+
+ *length = 0;
+ while (value_begin < line_end) {
+ if (*value_begin < '0' || *value_begin > '9') {
+ // bad! content-length found, and couldn't parse all of it!
+ DVLOG(1) << "invalid content-length - non numeric character detected";
+ return BalsaHeadersEnums::INVALID_CONTENT_LENGTH;
+ }
+ const size_t kMaxDiv10 = std::numeric_limits<size_t>::max() / 10;
+ size_t length_x_10 = *length * 10;
+ const char c = *value_begin - '0';
+ if (*length > kMaxDiv10 ||
+ (std::numeric_limits<size_t>::max() - length_x_10) < c) {
+ DVLOG(1) << "content-length overflow";
+ return BalsaHeadersEnums::CONTENT_LENGTH_OVERFLOW;
+ }
+ *length = length_x_10 + c;
+ ++value_begin;
+ }
+ DVLOG(1) << "content_length parsed: " << *length;
+ return BalsaHeadersEnums::VALID_CONTENT_LENGTH;
+}
+
+void BalsaFrame::ProcessTransferEncodingLine(HeaderLines::size_type line_idx) {
+ const HeaderLineDescription& header_line = headers_->header_lines_[line_idx];
+ const char* stream_begin = headers_->OriginalHeaderStreamBegin();
+ const char* line_end = stream_begin + header_line.last_char_idx;
+ const char* value_begin = stream_begin + header_line.value_begin_idx;
+ size_t value_length = line_end - value_begin;
+
+ if (absl::EqualsIgnoreCase(absl::string_view(value_begin, value_length),
+ kChunked)) {
+ headers_->transfer_encoding_is_chunked_ = true;
+ } else if (absl::EqualsIgnoreCase(
+ absl::string_view(value_begin, value_length), kIdentity)) {
+ headers_->transfer_encoding_is_chunked_ = false;
+ } else {
+ HandleError(BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING);
+ return;
+ }
+}
+
+bool BalsaFrame::CheckHeaderLinesForInvalidChars(const Lines& lines,
+ const BalsaHeaders* headers) {
+ // Read from the beginning of the first line to the end of the last line.
+ // Note we need to add the first line's offset as in the case of a trailer
+ // it's non-zero.
+ const char* stream_begin =
+ headers->OriginalHeaderStreamBegin() + lines.front().first;
+ const char* stream_end =
+ headers->OriginalHeaderStreamBegin() + lines.back().second;
+ bool found_invalid = false;
+
+ for (const char* c = stream_begin; c < stream_end; c++) {
+ if (header_properties::IsInvalidHeaderChar(*c)) {
+ found_invalid = true;
+ invalid_chars_[*c]++;
+ }
+ }
+
+ return found_invalid;
+}
+
+void BalsaFrame::ProcessHeaderLines(const Lines& lines, bool is_trailer,
+ BalsaHeaders* headers) {
+ QUICHE_DCHECK(!lines.empty());
+ DVLOG(1) << "******@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@**********\n";
+
+ if (is_request() && track_invalid_chars()) {
+ if (CheckHeaderLinesForInvalidChars(lines, headers)) {
+ if (invalid_chars_error_enabled()) {
+ HandleError(BalsaFrameEnums::INVALID_HEADER_CHARACTER);
+ return;
+ } else {
+ HandleWarning(BalsaFrameEnums::INVALID_HEADER_CHARACTER);
+ }
+ }
+ }
+
+ // There is no need to attempt to process headers (resp. trailers)
+ // if no header (resp. trailer) lines exist.
+ //
+ // The last line of the message, which is an empty line, is never a header
+ // (resp. trailer) line. Furthermore, the first line of the message is not
+ // a header line. Therefore there are at least two (resp. one) lines in the
+ // message which are not header (resp. trailer) lines.
+ //
+ // Thus, we test to see if we have more than two (resp. one) lines total
+ // before attempting to parse any header (resp. trailer) lines.
+ if (lines.size() <= (is_trailer ? 1 : 2)) {
+ return;
+ }
+
+ HeaderLines::size_type content_length_idx = 0;
+ HeaderLines::size_type transfer_encoding_idx = 0;
+ const char* stream_begin = headers->OriginalHeaderStreamBegin();
+ // Parse the rest of the header or trailer data into key-value pairs.
+ if (!FindColonsAndParseIntoKeyValue(lines, is_trailer, headers)) {
+ return;
+ }
+ // At this point, we've parsed all of the headers/trailers. Time to look
+ // for those headers which we require for framing or for format errors.
+ const HeaderLines::size_type lines_size = headers->header_lines_.size();
+ for (HeaderLines::size_type i = 0; i < lines_size; ++i) {
+ const HeaderLineDescription& line = headers->header_lines_[i];
+ const char* key_begin = stream_begin + line.first_char_idx;
+ const size_t key_len = line.key_end_idx - line.first_char_idx;
+ const char c = key_len != 0u ? *key_begin : ' ';
+ DVLOG(2) << "[" << i << "]: " << std::string(key_begin, key_len) << " c: '"
+ << c << "' key_len: " << key_len;
+
+ // If a header begins with either lowercase or uppercase 'c' or 't', then
+ // the header may be one of content-length, connection, content-encoding
+ // or transfer-encoding. These headers are special, as they change the way
+ // that the message is framed, and so the framer is required to search
+ // for them. However, first check for a formatting error, and skip
+ // special header treatment on trailer lines (when is_trailer is true).
+
+ if (c == ' ') {
+ parse_state_ = BalsaFrameEnums::ERROR;
+ HandleError(is_trailer ? BalsaFrameEnums::INVALID_TRAILER_FORMAT
+ : BalsaFrameEnums::INVALID_HEADER_FORMAT);
+ return;
+ } else if (is_trailer) {
+ continue;
+ } else if (absl::EqualsIgnoreCase(absl::string_view(key_begin, key_len),
+ kContentLength)) {
+ size_t length = 0;
+ BalsaHeadersEnums::ContentLengthStatus content_length_status =
+ ProcessContentLengthLine(i, &length);
+ if (content_length_idx != 0) { // then we've already seen one!
+ if ((headers->content_length_status_ != content_length_status) ||
+ ((headers->content_length_status_ ==
+ BalsaHeadersEnums::VALID_CONTENT_LENGTH) &&
+ (http_validation_policy().disallow_multiple_content_length() ||
+ length != headers->content_length_))) {
+ HandleError(BalsaFrameEnums::MULTIPLE_CONTENT_LENGTH_KEYS);
+ return;
+ }
+ continue;
+ } else {
+ content_length_idx = i + 1;
+ headers->content_length_status_ = content_length_status;
+ headers->content_length_ = length;
+ content_length_remaining_ = length;
+ }
+ } else if (absl::EqualsIgnoreCase(absl::string_view(key_begin, key_len),
+ kTransferEncoding)) {
+ if (transfer_encoding_idx != 0) {
+ HandleError(BalsaFrameEnums::MULTIPLE_TRANSFER_ENCODING_KEYS);
+ return;
+ }
+ transfer_encoding_idx = i + 1;
+ }
+ }
+
+ if (!is_trailer) {
+ if (http_validation_policy()
+ .disallow_transfer_encoding_with_content_length() &&
+ content_length_idx != 0 && transfer_encoding_idx != 0) {
+ HandleError(BalsaFrameEnums::BOTH_TRANSFER_ENCODING_AND_CONTENT_LENGTH);
+ return;
+ }
+ if (headers->transfer_encoding_is_chunked_) {
+ headers->content_length_ = 0;
+ headers->content_length_status_ = BalsaHeadersEnums::NO_CONTENT_LENGTH;
+ content_length_remaining_ = 0;
+ }
+ if (transfer_encoding_idx != 0) {
+ ProcessTransferEncodingLine(transfer_encoding_idx - 1);
+ }
+ }
+}
+
+void BalsaFrame::AssignParseStateAfterHeadersHaveBeenParsed() {
+ // 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
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ int response_code = headers_->parsed_response_code_;
+ if (is_request_ || (!request_was_head_ &&
+ BalsaHeaders::ResponseCanHaveBody(response_code))) {
+ // Then we can have a body.
+ if (headers_->transfer_encoding_is_chunked_) {
+ // Note that
+ // if ( Transfer-Encoding: chunked && Content-length: )
+ // then Transfer-Encoding: chunked trumps.
+ // This is as specified in the spec.
+ // rfc2616 section 4.4.3
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_LENGTH;
+ } else {
+ // Errors parsing content-length definitely can cause
+ // protocol errors/warnings
+ switch (headers_->content_length_status_) {
+ // If we have a content-length, and it is parsed
+ // properly, there are two options.
+ // 1) zero content, in which case the message is done, and
+ // 2) nonzero content, in which case we have to
+ // consume the body.
+ case BalsaHeadersEnums::VALID_CONTENT_LENGTH:
+ if (headers_->content_length_ == 0) {
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ } else {
+ parse_state_ = BalsaFrameEnums::READING_CONTENT;
+ }
+ break;
+ case BalsaHeadersEnums::CONTENT_LENGTH_OVERFLOW:
+ case BalsaHeadersEnums::INVALID_CONTENT_LENGTH:
+ // If there were characters left-over after parsing the
+ // content length, we should flag an error and stop.
+ HandleError(BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH);
+ break;
+ // We can have: no transfer-encoding, no content length, and no
+ // connection: close...
+ // Unfortunately, this case doesn't seem to be covered in the spec.
+ // We'll assume that the safest thing to do here is what the google
+ // binaries before 2008 already do, which is to assume that
+ // everything until the connection is closed is body.
+ case BalsaHeadersEnums::NO_CONTENT_LENGTH:
+ if (is_request_) {
+ absl::string_view method = headers_->request_method();
+ // POSTs and PUTs should have a detectable body length. If they
+ // do not we consider it an error.
+ if (method != "POST" && method != "PUT") {
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ break;
+ } else if (!allow_reading_until_close_for_request_) {
+ HandleError(BalsaFrameEnums::REQUIRED_BODY_BUT_NO_CONTENT_LENGTH);
+ break;
+ }
+ }
+ parse_state_ = BalsaFrameEnums::READING_UNTIL_CLOSE;
+ HandleWarning(BalsaFrameEnums::MAYBE_BODY_BUT_NO_CONTENT_LENGTH);
+ break;
+ // The COV_NF_... statements here provide hints to the apparatus
+ // which computes coverage reports/ratios that this code is never
+ // intended to be executed, and should technically be impossible.
+ // COV_NF_START
+ default:
+ LOG(FATAL) << "Saw a content_length_status: "
+ << headers_->content_length_status_
+ << " which is unknown.";
+ // COV_NF_END
+ }
+ }
+ }
+}
+
+size_t BalsaFrame::ProcessHeaders(const char* message_start,
+ size_t message_length) {
+ const char* const original_message_start = message_start;
+ const char* const message_end = message_start + message_length;
+ const char* message_current = message_start;
+ const char* checkpoint = message_start;
+
+ if (message_length == 0) {
+ return message_current - original_message_start;
+ }
+
+ while (message_current < message_end) {
+ size_t base_idx = headers_->GetReadableBytesFromHeaderStream();
+
+ // Yes, we could use strchr (assuming null termination), or
+ // memchr, but as it turns out that is slower than this tight loop
+ // for the input that we see.
+ if (!saw_non_newline_char_) {
+ do {
+ const char c = *message_current;
+ if (c != '\r' && c != '\n') {
+ if (c <= ' ') {
+ HandleError(BalsaFrameEnums::NO_REQUEST_LINE_IN_REQUEST);
+ return message_current - original_message_start;
+ }
+ break;
+ }
+ ++message_current;
+ if (message_current == message_end) {
+ return message_current - original_message_start;
+ }
+ } while (true);
+ saw_non_newline_char_ = true;
+ message_start = message_current;
+ checkpoint = message_current;
+ }
+ while (message_current < message_end) {
+ if (*message_current != '\n') {
+ ++message_current;
+ continue;
+ }
+ const size_t relative_idx = message_current - message_start;
+ const size_t message_current_idx = 1 + base_idx + relative_idx;
+ lines_.push_back(std::make_pair(last_slash_n_idx_, message_current_idx));
+ if (lines_.size() == 1) {
+ headers_->WriteFromFramer(checkpoint, 1 + message_current - checkpoint);
+ checkpoint = message_current + 1;
+ const char* begin = headers_->OriginalHeaderStreamBegin();
+
+ DVLOG(1) << "First line " << std::string(begin, lines_[0].second);
+ DVLOG(1) << "is_request_: " << is_request_;
+ ProcessFirstLine(begin, begin + lines_[0].second);
+ if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ) {
+ break;
+ } else if (parse_state_ == BalsaFrameEnums::ERROR) {
+ return message_current - original_message_start;
+ }
+ }
+ const size_t chars_since_last_slash_n =
+ (message_current_idx - last_slash_n_idx_);
+ last_slash_n_idx_ = message_current_idx;
+ if (chars_since_last_slash_n > 2) {
+ // false positive.
+ ++message_current;
+ continue;
+ }
+ if ((chars_since_last_slash_n == 1) ||
+ (((message_current > message_start) &&
+ (*(message_current - 1) == '\r')) ||
+ (last_char_was_slash_r_))) {
+ break;
+ }
+ ++message_current;
+ }
+
+ if (message_current == message_end) {
+ continue;
+ }
+
+ ++message_current;
+ QUICHE_DCHECK(message_current >= message_start);
+ if (message_current > message_start) {
+ headers_->WriteFromFramer(checkpoint, message_current - checkpoint);
+ }
+
+ // Check if we have exceeded maximum headers length
+ // Although we check for this limit before and after we call this function
+ // we check it here as well to make sure that in case the visitor changed
+ // the max_header_length_ (for example after processing the first line)
+ // we handle it gracefully.
+ if (headers_->GetReadableBytesFromHeaderStream() > max_header_length_) {
+ HandleError(BalsaFrameEnums::HEADERS_TOO_LONG);
+ return message_current - original_message_start;
+ }
+
+ // Since we know that we won't be writing any more bytes of the header,
+ // we tell that to the headers object. The headers object may make
+ // more efficient allocation decisions when this is signaled.
+ headers_->DoneWritingFromFramer();
+ {
+ const char* readable_ptr = nullptr;
+ size_t readable_size = 0;
+ headers_->GetReadablePtrFromHeaderStream(&readable_ptr, &readable_size);
+ visitor_->OnHeaderInput(readable_ptr, readable_size);
+ }
+
+ // Ok, now that we've written everything into our header buffer, it is
+ // time to process the header lines (extract proper values for headers
+ // which are important for framing).
+ ProcessHeaderLines(lines_, false /*is_trailer*/, headers_);
+ if (parse_state_ == BalsaFrameEnums::ERROR) {
+ return message_current - original_message_start;
+ }
+
+ if (continue_headers_ != nullptr &&
+ headers_->parsed_response_code_ == kContinueStatusCode) {
+ // Save the headers from this 100 Continue response but reset everything
+ // else to prepare for the next set of headers.
+ BalsaHeaders saved_continue_headers = std::move(*headers_);
+ Reset();
+ *continue_headers_ = std::move(saved_continue_headers);
+ visitor_->ContinueHeaderDone();
+ checkpoint = message_start = message_current;
+ continue;
+ } else {
+ AssignParseStateAfterHeadersHaveBeenParsed();
+ if (parse_state_ == BalsaFrameEnums::ERROR) {
+ return message_current - original_message_start;
+ }
+ visitor_->ProcessHeaders(*headers_);
+ visitor_->HeaderDone();
+ if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ) {
+ visitor_->MessageDone();
+ }
+ }
+ return message_current - original_message_start;
+ }
+ // If we've gotten to here, it means that we've consumed all of the
+ // available input. We need to record whether or not the last character we
+ // saw was a '\r' so that a subsequent call to ProcessInput correctly finds
+ // a header framing that is split across the two calls.
+ last_char_was_slash_r_ = (*(message_end - 1) == '\r');
+ QUICHE_DCHECK(message_current >= message_start);
+ if (message_current > message_start) {
+ headers_->WriteFromFramer(checkpoint, message_current - checkpoint);
+ }
+ return message_current - original_message_start;
+}
+
+size_t BalsaFrame::BytesSafeToSplice() const {
+ switch (parse_state_) {
+ case BalsaFrameEnums::READING_CHUNK_DATA:
+ return chunk_length_remaining_;
+ case BalsaFrameEnums::READING_UNTIL_CLOSE:
+ return std::numeric_limits<size_t>::max();
+ case BalsaFrameEnums::READING_CONTENT:
+ return content_length_remaining_;
+ default:
+ return 0;
+ }
+}
+
+void BalsaFrame::BytesSpliced(size_t bytes_spliced) {
+ switch (parse_state_) {
+ case BalsaFrameEnums::READING_CHUNK_DATA:
+ if (chunk_length_remaining_ >= bytes_spliced) {
+ chunk_length_remaining_ -= bytes_spliced;
+ if (chunk_length_remaining_ == 0) {
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_TERM;
+ }
+ return;
+ } else {
+ HandleError(BalsaFrameEnums::
+ CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT);
+ return;
+ }
+
+ case BalsaFrameEnums::READING_UNTIL_CLOSE:
+ return;
+
+ case BalsaFrameEnums::READING_CONTENT:
+ if (content_length_remaining_ >= bytes_spliced) {
+ content_length_remaining_ -= bytes_spliced;
+ if (content_length_remaining_ == 0) {
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ visitor_->MessageDone();
+ }
+ return;
+ } else {
+ HandleError(BalsaFrameEnums::
+ CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT);
+ return;
+ }
+
+ default:
+ HandleError(BalsaFrameEnums::CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO);
+ return;
+ }
+}
+
+size_t BalsaFrame::ProcessInput(const char* input, size_t size) {
+ const char* current = input;
+ const char* on_entry = current;
+ const char* end = current + size;
+
+ QUICHE_DCHECK(headers_ != nullptr);
+ if (headers_ == nullptr) {
+ return 0;
+ }
+
+ if (parse_state_ == BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE) {
+ const size_t header_length = headers_->GetReadableBytesFromHeaderStream();
+ // Yes, we still have to check this here as the user can change the
+ // max_header_length amount!
+ // Also it is possible that we have reached the maximum allowed header size,
+ // and we have more to consume (remember we are still inside
+ // READING_HEADER_AND_FIRSTLINE) in which case we directly declare an error.
+ if (header_length > max_header_length_ ||
+ (header_length == max_header_length_ && size > 0)) {
+ HandleError(BalsaFrameEnums::HEADERS_TOO_LONG);
+ return current - input;
+ }
+ const size_t bytes_to_process =
+ std::min(max_header_length_ - header_length, size);
+ current += ProcessHeaders(input, bytes_to_process);
+ // If we are still reading headers check if we have crossed the headers
+ // limit. Note that we check for >= as opposed to >. This is because if
+ // header_length_after equals max_header_length_ and we are still in the
+ // parse_state_ BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE we know for
+ // sure that the headers limit will be crossed later on
+ if (parse_state_ == BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE) {
+ // Note that headers_ is valid only if we are still reading headers.
+ const size_t header_length_after =
+ headers_->GetReadableBytesFromHeaderStream();
+ if (header_length_after >= max_header_length_) {
+ HandleError(BalsaFrameEnums::HEADERS_TOO_LONG);
+ }
+ }
+ return current - input;
+ }
+
+ if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ ||
+ parse_state_ == BalsaFrameEnums::ERROR) {
+ // Can do nothing more 'till we're reset.
+ return current - input;
+ }
+
+ QUICHE_DCHECK_LE(current, end);
+ if (current == end) {
+ return current - input;
+ }
+
+ while (true) {
+ switch (parse_state_) {
+ case BalsaFrameEnums::READING_CHUNK_LENGTH:
+ // In this state we read the chunk length.
+ // Note that once we hit a character which is not in:
+ // [0-9;A-Fa-f\n], we transition to a different state.
+ //
+ QUICHE_DCHECK_LE(current, end);
+ while (true) {
+ if (current == end) {
+ visitor_->OnRawBodyInput(on_entry, current - on_entry);
+ return current - input;
+ }
+
+ const char c = *current;
+ ++current;
+
+ static const signed char kBad = -1;
+ static const signed char kDelimiter = -2;
+
+ // valid cases:
+ // "09123\n" // -> 09123
+ // "09123\r\n" // -> 09123
+ // "09123 \n" // -> 09123
+ // "09123 \r\n" // -> 09123
+ // "09123 12312\n" // -> 09123
+ // "09123 12312\r\n" // -> 09123
+ // "09123; foo=bar\n" // -> 09123
+ // "09123; foo=bar\r\n" // -> 09123
+ // "FFFFFFFFFFFFFFFF\r\n" // -> FFFFFFFFFFFFFFFF
+ // "FFFFFFFFFFFFFFFF 22\r\n" // -> FFFFFFFFFFFFFFFF
+ // invalid cases:
+ // "[ \t]+[^\n]*\n"
+ // "FFFFFFFFFFFFFFFFF\r\n" (would overflow)
+ // "\r\n"
+ // "\n"
+ signed char addition = kBad;
+ // clang-format off
+ switch (c) {
+ case '0': addition = 0; break;
+ case '1': addition = 1; break;
+ case '2': addition = 2; break;
+ case '3': addition = 3; break;
+ case '4': addition = 4; break;
+ case '5': addition = 5; break;
+ case '6': addition = 6; break;
+ case '7': addition = 7; break;
+ case '8': addition = 8; break;
+ case '9': addition = 9; break;
+ case 'a': addition = 0xA; break;
+ case 'b': addition = 0xB; break;
+ case 'c': addition = 0xC; break;
+ case 'd': addition = 0xD; break;
+ case 'e': addition = 0xE; break;
+ case 'f': addition = 0xF; break;
+ case 'A': addition = 0xA; break;
+ case 'B': addition = 0xB; break;
+ case 'C': addition = 0xC; break;
+ case 'D': addition = 0xD; break;
+ case 'E': addition = 0xE; break;
+ case 'F': addition = 0xF; break;
+ case '\t':
+ case '\n':
+ case '\r':
+ case ' ':
+ case ';':
+ addition = kDelimiter;
+ break;
+ default:
+ // Leave addition == kBad
+ break;
+ }
+ // clang-format on
+ if (addition >= 0) {
+ chunk_length_character_extracted_ = true;
+ size_t length_x_16 = chunk_length_remaining_ * 16;
+ const size_t kMaxDiv16 = std::numeric_limits<size_t>::max() / 16;
+ if ((chunk_length_remaining_ > kMaxDiv16) ||
+ (std::numeric_limits<size_t>::max() - length_x_16) <
+ static_cast<size_t>(addition)) {
+ // overflow -- asked for a chunk-length greater than 2^64 - 1!!
+ visitor_->OnRawBodyInput(on_entry, current - on_entry);
+ HandleError(BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW);
+ return current - input;
+ }
+ chunk_length_remaining_ = length_x_16 + addition;
+ continue;
+ }
+
+ if (!chunk_length_character_extracted_ || addition == kBad) {
+ // ^[0-9;A-Fa-f][ \t\n] -- was not matched, either because no
+ // characters were converted, or an unexpected character was
+ // seen.
+ visitor_->OnRawBodyInput(on_entry, current - on_entry);
+ HandleError(BalsaFrameEnums::INVALID_CHUNK_LENGTH);
+ return current - input;
+ }
+
+ break;
+ }
+
+ --current;
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_EXTENSION;
+ visitor_->OnChunkLength(chunk_length_remaining_);
+ continue;
+
+ case BalsaFrameEnums::READING_CHUNK_EXTENSION: {
+ // TODO(phython): Convert this scanning to be 16 bytes at a time if
+ // there is data to be read.
+ const char* extensions_start = current;
+ size_t extensions_length = 0;
+ QUICHE_DCHECK_LE(current, end);
+ while (true) {
+ if (current == end) {
+ visitor_->OnChunkExtensionInput(extensions_start,
+ extensions_length);
+ visitor_->OnRawBodyInput(on_entry, current - on_entry);
+ return current - input;
+ }
+ const char c = *current;
+ if (c == '\r' || c == '\n') {
+ extensions_length = (extensions_start == current)
+ ? 0
+ : current - extensions_start - 1;
+ }
+
+ ++current;
+ if (c == '\n') {
+ break;
+ }
+ }
+
+ chunk_length_character_extracted_ = false;
+ visitor_->OnChunkExtensionInput(extensions_start, extensions_length);
+
+ if (chunk_length_remaining_ != 0) {
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_DATA;
+ continue;
+ }
+
+ HeaderFramingFound('\n');
+ parse_state_ = BalsaFrameEnums::READING_LAST_CHUNK_TERM;
+ continue;
+ }
+
+ case BalsaFrameEnums::READING_CHUNK_DATA:
+ while (current < end) {
+ if (chunk_length_remaining_ == 0) {
+ break;
+ }
+ // read in the chunk
+ size_t bytes_remaining = end - current;
+ size_t consumed_bytes = (chunk_length_remaining_ < bytes_remaining)
+ ? chunk_length_remaining_
+ : bytes_remaining;
+ const char* tmp_current = current + consumed_bytes;
+ visitor_->OnRawBodyInput(on_entry, tmp_current - on_entry);
+ visitor_->OnBodyChunkInput(current, consumed_bytes);
+ on_entry = current = tmp_current;
+ chunk_length_remaining_ -= consumed_bytes;
+ }
+
+ if (chunk_length_remaining_ == 0) {
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_TERM;
+ continue;
+ }
+
+ visitor_->OnRawBodyInput(on_entry, current - on_entry);
+ return current - input;
+
+ case BalsaFrameEnums::READING_CHUNK_TERM:
+ QUICHE_DCHECK_LE(current, end);
+ while (true) {
+ if (current == end) {
+ visitor_->OnRawBodyInput(on_entry, current - on_entry);
+ return current - input;
+ }
+
+ const char c = *current;
+ ++current;
+
+ if (c == '\n') {
+ break;
+ }
+ }
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_LENGTH;
+ continue;
+
+ case BalsaFrameEnums::READING_LAST_CHUNK_TERM:
+ QUICHE_DCHECK_LE(current, end);
+ while (true) {
+ if (current == end) {
+ visitor_->OnRawBodyInput(on_entry, current - on_entry);
+ return current - input;
+ }
+
+ const char c = *current;
+ if (HeaderFramingFound(c) != 0) {
+ // If we've found a "\r\n\r\n", then the message
+ // is done.
+ ++current;
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ visitor_->OnRawBodyInput(on_entry, current - on_entry);
+ visitor_->MessageDone();
+ return current - input;
+ }
+
+ // If not, however, since the spec only suggests that the
+ // client SHOULD indicate the presence of trailers, we get to
+ // *test* that they did or didn't.
+ // If all of the bytes we've seen since:
+ // OPTIONAL_WS 0 OPTIONAL_STUFF CRLF
+ // are either '\r', or '\n', then we can assume that we don't yet
+ // know if we need to parse headers, or if the next byte will make
+ // the HeaderFramingFound condition (above) true.
+ if (!HeaderFramingMayBeFound()) {
+ break;
+ }
+
+ // If HeaderFramingMayBeFound(), then we have seen only characters
+ // '\r' or '\n'.
+ ++current;
+
+ // Lets try again! There is no state change here.
+ }
+
+ // If (!HeaderFramingMayBeFound()), then we know that we must be
+ // reading the first non CRLF character of a trailer.
+ parse_state_ = BalsaFrameEnums::READING_TRAILER;
+ visitor_->OnRawBodyInput(on_entry, current - on_entry);
+ on_entry = current;
+ continue;
+
+ // TODO(yongfa): No leading whitespace is allowed before field-name per
+ // RFC2616. Leading whitespace will cause header parsing error too.
+ case BalsaFrameEnums::READING_TRAILER:
+ while (current < end) {
+ const char c = *current;
+ ++current;
+ ++trailer_length_;
+ if (trailer_ != nullptr) {
+ // Reuse the header length limit for trailer, which is just a bunch
+ // of headers.
+ if (trailer_length_ > max_header_length_) {
+ --current;
+ HandleError(BalsaFrameEnums::TRAILER_TOO_LONG);
+ return current - input;
+ }
+ if (LineFramingFound(c)) {
+ trailer_lines_.push_back(
+ std::make_pair(start_of_trailer_line_, trailer_length_));
+ start_of_trailer_line_ = trailer_length_;
+ }
+ }
+ if (HeaderFramingFound(c) != 0) {
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ if (trailer_ != nullptr) {
+ trailer_->WriteFromFramer(on_entry, current - on_entry);
+ trailer_->DoneWritingFromFramer();
+ ProcessHeaderLines(trailer_lines_, true /*is_trailer*/, trailer_);
+ if (parse_state_ == BalsaFrameEnums::ERROR) {
+ return current - input;
+ }
+ visitor_->ProcessTrailers(*trailer_);
+ }
+ visitor_->OnTrailerInput(on_entry, current - on_entry);
+ visitor_->MessageDone();
+ return current - input;
+ }
+ }
+ if (trailer_ != nullptr) {
+ trailer_->WriteFromFramer(on_entry, current - on_entry);
+ }
+ visitor_->OnTrailerInput(on_entry, current - on_entry);
+ return current - input;
+
+ case BalsaFrameEnums::READING_UNTIL_CLOSE: {
+ const size_t bytes_remaining = end - current;
+ if (bytes_remaining > 0) {
+ visitor_->OnRawBodyInput(current, bytes_remaining);
+ visitor_->OnBodyChunkInput(current, bytes_remaining);
+ current += bytes_remaining;
+ }
+ return current - input;
+ }
+
+ case BalsaFrameEnums::READING_CONTENT:
+ while ((content_length_remaining_ != 0u) && current < end) {
+ // read in the content
+ const size_t bytes_remaining = end - current;
+ const size_t consumed_bytes =
+ (content_length_remaining_ < bytes_remaining)
+ ? content_length_remaining_
+ : bytes_remaining;
+ visitor_->OnRawBodyInput(current, consumed_bytes);
+ visitor_->OnBodyChunkInput(current, consumed_bytes);
+ current += consumed_bytes;
+ content_length_remaining_ -= consumed_bytes;
+ }
+ if (content_length_remaining_ == 0) {
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ visitor_->MessageDone();
+ }
+ return current - input;
+
+ default:
+ // The state-machine should never be in a state that isn't handled
+ // above. This is a glaring logic error, and we should do something
+ // drastic to ensure that this gets looked-at and fixed.
+ LOG(FATAL) << "Unknown state: " << parse_state_ // COV_NF_LINE
+ << " memory corruption?!"; // COV_NF_LINE
+ }
+ }
+}
+
+const int32_t BalsaFrame::kValidTerm1;
+const int32_t BalsaFrame::kValidTerm1Mask;
+const int32_t BalsaFrame::kValidTerm2;
+const int32_t BalsaFrame::kValidTerm2Mask;
+
+} // namespace quiche
diff --git a/quiche/common/balsa/balsa_frame.h b/quiche/common/balsa/balsa_frame.h
new file mode 100644
index 0000000..5cc00c7
--- /dev/null
+++ b/quiche/common/balsa/balsa_frame.h
@@ -0,0 +1,324 @@
+// 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.
+
+#ifndef QUICHE_COMMON_BALSA_BALSA_FRAME_H_
+#define QUICHE_COMMON_BALSA_BALSA_FRAME_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+#include "quiche/common/balsa/balsa_enums.h"
+#include "quiche/common/balsa/balsa_headers.h"
+#include "quiche/common/balsa/balsa_visitor_interface.h"
+#include "quiche/common/balsa/framer_interface.h"
+#include "quiche/common/balsa/http_validation_policy.h"
+#include "quiche/common/balsa/noop_balsa_visitor.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quiche {
+
+namespace test {
+class BalsaFrameTestPeer;
+} // namespace test
+
+// BalsaFrame is a lightweight HTTP framer.
+class QUICHE_EXPORT_PRIVATE BalsaFrame : public FramerInterface {
+ public:
+ typedef std::vector<std::pair<size_t, size_t> > Lines;
+
+ typedef BalsaHeaders::HeaderLineDescription HeaderLineDescription;
+ typedef BalsaHeaders::HeaderLines HeaderLines;
+ typedef BalsaHeaders::HeaderTokenList HeaderTokenList;
+
+ // Only applied in strict mode.
+ // Control characters, including \t, \n, \r, as well as space and
+ // (),/;<=>?@[\]{} and \x7f (see
+ // https://tools.ietf.org/html/rfc7230#section-3.2.6).
+ static constexpr char kInvalidHeaderKeyCharList[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
+ 0x1E, 0x1F, ' ', '(', ')', ',', '/', ';', '<', '=',
+ '>', '?', '@', '[', '\\', ']', '{', '}', 0x7f};
+
+ enum class InvalidCharsLevel { kOff, kWarning, kError };
+
+ // TODO(fenix): get rid of the 'kValidTerm*' stuff by using the 'since last
+ // index' strategy. Note that this implies getting rid of the HeaderFramed()
+
+ static constexpr int32_t kValidTerm1 = '\n' << 16 | '\r' << 8 | '\n';
+ static constexpr int32_t kValidTerm1Mask = 0xFF << 16 | 0xFF << 8 | 0xFF;
+ static constexpr int32_t kValidTerm2 = '\n' << 8 | '\n';
+ static constexpr int32_t kValidTerm2Mask = 0xFF << 8 | 0xFF;
+ BalsaFrame()
+ : last_char_was_slash_r_(false),
+ saw_non_newline_char_(false),
+ start_was_space_(true),
+ chunk_length_character_extracted_(false),
+ is_request_(true),
+ allow_reading_until_close_for_request_(false),
+ request_was_head_(false),
+ max_header_length_(16 * 1024),
+ visitor_(&do_nothing_visitor_),
+ chunk_length_remaining_(0),
+ content_length_remaining_(0),
+ last_slash_n_loc_(nullptr),
+ last_recorded_slash_n_loc_(nullptr),
+ last_slash_n_idx_(0),
+ term_chars_(0),
+ parse_state_(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE),
+ last_error_(BalsaFrameEnums::NO_ERROR),
+ continue_headers_(nullptr),
+ headers_(nullptr),
+ start_of_trailer_line_(0),
+ trailer_length_(0),
+ trailer_(nullptr),
+ invalid_chars_level_(InvalidCharsLevel::kOff),
+ http_validation_policy_(HttpValidationPolicy::CreateDefault()) {}
+
+ ~BalsaFrame() override {}
+
+ // Reset reinitializes all the member variables of the framer and clears the
+ // attached header object (but doesn't change the pointer value headers_).
+ void Reset();
+
+ // The method set_balsa_headers clears the headers provided and attaches them
+ // to the framer. This is a required step before the framer will process any
+ // input message data.
+ // To detach the header object from the framer, use
+ // set_balsa_headers(nullptr).
+ void set_balsa_headers(BalsaHeaders* headers) {
+ if (headers_ != headers) {
+ headers_ = headers;
+ }
+ if (headers_ != nullptr) {
+ // Clear the headers if they are non-null, even if the new headers are
+ // the same as the old.
+ headers_->Clear();
+ }
+ }
+
+ // If set to non-null, allow 100 Continue headers before the main headers.
+ void set_continue_headers(BalsaHeaders* continue_headers) {
+ if (continue_headers_ != continue_headers) {
+ continue_headers_ = continue_headers;
+ }
+ if (continue_headers_ != nullptr) {
+ // Clear the headers if they are non-null, even if the new headers are
+ // the same as the old.
+ continue_headers_->Clear();
+ }
+ }
+
+ // The method set_balsa_trailer clears the trailer provided and attaches it
+ // to the framer. This is a required step before the framer will process any
+ // input message data.
+ // To detach the trailer object from the framer, use
+ // set_balsa_trailer(nullptr).
+ void set_balsa_trailer(BalsaHeaders* trailer) {
+ if (trailer != nullptr && is_request()) {
+ GFE_BUG(bug_1317_1) << "Trailer in request is not allowed.";
+ return;
+ }
+
+ if (trailer_ != trailer) {
+ trailer_ = trailer;
+ }
+ if (trailer_ != nullptr) {
+ // Clear the trailer if it is non-null, even if the new trailer is
+ // the same as the old.
+ trailer_->Clear();
+ }
+ }
+
+ void set_balsa_visitor(BalsaVisitorInterface* visitor) {
+ visitor_ = visitor;
+ if (visitor_ == nullptr) {
+ visitor_ = &do_nothing_visitor_;
+ }
+ }
+
+ void set_invalid_chars_level(InvalidCharsLevel v) {
+ invalid_chars_level_ = v;
+ }
+
+ bool track_invalid_chars() {
+ return invalid_chars_level_ != InvalidCharsLevel::kOff;
+ }
+
+ bool invalid_chars_error_enabled() {
+ return invalid_chars_level_ == InvalidCharsLevel::kError;
+ }
+
+ void set_http_validation_policy(const quiche::HttpValidationPolicy& policy) {
+ http_validation_policy_ = policy;
+ }
+ const quiche::HttpValidationPolicy& http_validation_policy() const {
+ return http_validation_policy_;
+ }
+
+ void set_is_request(bool is_request) { is_request_ = is_request; }
+
+ bool is_request() const { return is_request_; }
+
+ void set_request_was_head(bool request_was_head) {
+ request_was_head_ = request_was_head;
+ }
+
+ void set_max_header_length(size_t max_header_length) {
+ max_header_length_ = max_header_length;
+ }
+
+ size_t max_header_length() const { return max_header_length_; }
+
+ bool MessageFullyRead() const {
+ return parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ;
+ }
+
+ BalsaFrameEnums::ParseState ParseState() const { return parse_state_; }
+
+ bool Error() const { return parse_state_ == BalsaFrameEnums::ERROR; }
+
+ BalsaFrameEnums::ErrorCode ErrorCode() const { return last_error_; }
+
+ const absl::flat_hash_map<char, int>& get_invalid_chars() const {
+ return invalid_chars_;
+ }
+
+ const BalsaHeaders* headers() const { return headers_; }
+ BalsaHeaders* mutable_headers() { return headers_; }
+
+ const BalsaHeaders* trailer() const { return trailer_; }
+ BalsaHeaders* mutable_trailer() { return trailer_; }
+
+ size_t BytesSafeToSplice() const;
+ void BytesSpliced(size_t bytes_spliced);
+
+ size_t ProcessInput(const char* input, size_t size) override;
+
+ void set_allow_reading_until_close_for_request(bool set) {
+ allow_reading_until_close_for_request_ = set;
+ }
+
+ // For websockets and possibly other uses, we suspend the usual expectations
+ // about when a message has a body and how long it should be.
+ void AllowArbitraryBody() {
+ parse_state_ = BalsaFrameEnums::READING_UNTIL_CLOSE;
+ }
+
+ protected:
+ inline BalsaHeadersEnums::ContentLengthStatus ProcessContentLengthLine(
+ size_t line_idx, size_t* length);
+
+ inline void ProcessTransferEncodingLine(size_t line_idx);
+
+ void ProcessFirstLine(const char* begin, const char* end);
+
+ void CleanUpKeyValueWhitespace(const char* stream_begin,
+ const char* line_begin, const char* current,
+ const char* line_end,
+ HeaderLineDescription* current_header_line);
+
+ void ProcessHeaderLines(const Lines& lines, bool is_trailer,
+ BalsaHeaders* headers);
+
+ // Returns true if there are invalid characters, false otherwise.
+ // Will also update counts per invalid character in invalid_chars_.
+ bool CheckHeaderLinesForInvalidChars(const Lines& lines,
+ const BalsaHeaders* headers);
+
+ inline size_t ProcessHeaders(const char* message_start,
+ size_t message_length);
+
+ void AssignParseStateAfterHeadersHaveBeenParsed();
+
+ inline bool LineFramingFound(char current_char) {
+ return current_char == '\n';
+ }
+
+ // TODO(fenix): get rid of the following function and its uses (and
+ // replace with something more efficient).
+ // Return header framing pattern. Non-zero return value indicates found,
+ // which has two possible outcomes: kValidTerm1, which means \n\r\n
+ // or kValidTerm2, which means \n\n. Zero return value means not found.
+ inline int32_t HeaderFramingFound(char current_char) {
+ // Note that the 'if (current_char == '\n' ...)' test exists to ensure that
+ // the HeaderFramingMayBeFound test works properly. In benchmarking done on
+ // 2/13/2008, the 'if' actually speeds up performance of the function
+ // anyway..
+ if (current_char == '\n' || current_char == '\r') {
+ term_chars_ <<= 8;
+ // This is necessary IFF architecture has > 8 bit char. Alas, I'm
+ // paranoid.
+ term_chars_ |= current_char & 0xFF;
+
+ if ((term_chars_ & kValidTerm1Mask) == kValidTerm1) {
+ term_chars_ = 0;
+ return kValidTerm1;
+ }
+ if ((term_chars_ & kValidTerm2Mask) == kValidTerm2) {
+ term_chars_ = 0;
+ return kValidTerm2;
+ }
+ } else {
+ term_chars_ = 0;
+ }
+ return 0;
+ }
+
+ inline bool HeaderFramingMayBeFound() const { return term_chars_ != 0; }
+
+ private:
+ friend class test::BalsaFrameTestPeer;
+
+ // Calls HandleError() and returns false on error.
+ bool FindColonsAndParseIntoKeyValue(const Lines& lines, bool is_trailer,
+ BalsaHeaders* headers);
+
+ void HandleError(BalsaFrameEnums::ErrorCode error_code);
+ void HandleWarning(BalsaFrameEnums::ErrorCode error_code);
+
+ bool last_char_was_slash_r_;
+ bool saw_non_newline_char_;
+ bool start_was_space_;
+ bool chunk_length_character_extracted_;
+ bool is_request_; // This is not reset in Reset()
+ // Generally, requests are not allowed to frame with connection: close. For
+ // protocols which do their own protocol-specific chunking, such as streamed
+ // stubby, we allow connection close semantics for requests.
+ bool allow_reading_until_close_for_request_;
+ bool request_was_head_; // This is not reset in Reset()
+ size_t max_header_length_; // This is not reset in Reset()
+ BalsaVisitorInterface* visitor_;
+ size_t chunk_length_remaining_;
+ size_t content_length_remaining_;
+ const char* last_slash_n_loc_;
+ const char* last_recorded_slash_n_loc_;
+ size_t last_slash_n_idx_;
+ uint32_t term_chars_;
+ BalsaFrameEnums::ParseState parse_state_;
+ BalsaFrameEnums::ErrorCode last_error_;
+ absl::flat_hash_map<char, int> invalid_chars_;
+
+ Lines lines_;
+
+ BalsaHeaders* continue_headers_; // This is not reset to nullptr in Reset().
+ BalsaHeaders* headers_; // This is not reset to nullptr in Reset().
+ NoOpBalsaVisitor do_nothing_visitor_;
+
+ Lines trailer_lines_;
+ size_t start_of_trailer_line_;
+ size_t trailer_length_;
+ BalsaHeaders* trailer_; // Does not own and is not reset to nullptr
+ // in Reset().
+ InvalidCharsLevel invalid_chars_level_; // This is not reset in Reset()
+
+ quiche::HttpValidationPolicy http_validation_policy_;
+};
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_BALSA_BALSA_FRAME_H_
diff --git a/quiche/common/balsa/balsa_headers.cc b/quiche/common/balsa/balsa_headers.cc
new file mode 100644
index 0000000..4c57b58
--- /dev/null
+++ b/quiche/common/balsa/balsa_headers.cc
@@ -0,0 +1,1111 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/balsa/balsa_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_join.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/string_view_utils.h"
+#include "quiche/common/balsa/balsa_enums.h"
+#include "quiche/common/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},
+
+} // 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;
+ }
+ const char* start = header_value.begin();
+ const char* end = header_value.end();
+ while (true) {
+ // search for first nonwhitespace, non separator char.
+ while (*start == ',' || *start <= ' ') {
+ ++start;
+ if (start == end) {
+ return;
+ }
+ }
+ // found. marked.
+ const char* nws = start;
+
+ // search for next whitspace or separator char.
+ while (*start != ',' && *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;
+ } else 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(stringpiece_ssize_type idx,
+ stringpiece_ssize_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)
+ : strings::FindIgnoreCase(line, value);
+ while (idx != absl::string_view::npos) {
+ stringpiece_ssize_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;
+ strings::RemoveWhitespaceContext(&needle);
+ GFE_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());
+ strings::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 =
+ strings::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).
+ bool comma_found = false;
+ size_t cur_size = 0;
+
+ const size_t next_comma =
+ values.find(',', /* pos = */ found ? needle.size() : 0);
+ comma_found = next_comma != absl::string_view::npos;
+ 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);
+ }
+ strings::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
+ // litteral 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) {
+ 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());
+ } else {
+ // The new version is too large to fit in the space available for the old
+ // one, so we have to reformat the firstline.
+ SetRequestFirstlineFromStringPieces(request_method(), request_uri(),
+ version);
+ }
+}
+
+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()) {
+ 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()) {
+ 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
diff --git a/quiche/common/balsa/balsa_headers.h b/quiche/common/balsa/balsa_headers.h
new file mode 100644
index 0000000..e8b4efd
--- /dev/null
+++ b/quiche/common/balsa/balsa_headers.h
@@ -0,0 +1,1462 @@
+// 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.
+
+// A lightweight implementation for storing HTTP headers.
+
+#ifndef QUICHE_COMMON_BALSA_BALSA_HEADERS_H_
+#define QUICHE_COMMON_BALSA_BALSA_HEADERS_H_
+
+#include <cstddef>
+#include <cstring>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/iterator/range.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "quiche/common/balsa/balsa_enums.h"
+#include "quiche/common/balsa/header_api.h"
+#include "quiche/common/balsa/standard_header_map.h"
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+
+namespace gfe2 {
+class Http2HeaderValidator;
+} // namespace gfe2
+
+namespace quiche {
+
+class BalsaHeaders;
+
+namespace test {
+class BalsaHeadersTestPeer;
+} // namespace test
+
+// WARNING:
+// Note that -no- char* returned by any function in this
+// file is null-terminated.
+
+// This class exists to service the specific needs of BalsaHeaders.
+//
+// Functional goals:
+// 1) provide a backing-store for all of the StringPieces that BalsaHeaders
+// returns. Every StringPiece returned from BalsaHeaders should remain
+// valid until the BalsaHeader's object is cleared, or the header-line is
+// erased.
+// 2) provide a backing-store for BalsaFrame, which requires contiguous memory
+// for its fast-path parsing functions. Note that the cost of copying is
+// less than the cost of requiring the parser to do slow-path parsing, as
+// it would have to check for bounds every byte, instead of every 16 bytes.
+//
+// This class is optimized for the case where headers are stored in one of two
+// buffers. It doesn't make a lot of effort to densely pack memory-- in fact,
+// it -may- be somewhat memory inefficient. This possible inefficiency allows a
+// certain simplicity of implementation and speed which makes it worthwhile.
+// If, in the future, better memory density is required, it should be possible
+// to reuse the abstraction presented by this object to achieve those goals.
+//
+// In the most common use-case, this memory inefficiency should be relatively
+// small.
+//
+// Alternate implementations of BalsaBuffer may include:
+// - vector of strings, one per header line (similar to HTTPHeaders)
+// - densely packed strings:
+// - keep a sorted array/map of free-space linked lists or numbers.
+// - use the entry that most closely first your needs.
+// - at this point, perhaps just use a vector of strings, and let
+// the allocator do the right thing.
+//
+class QUICHE_EXPORT_PRIVATE BalsaBuffer {
+ public:
+ static constexpr size_t kDefaultBlocksize = 4096;
+ // We have two friends here. These exist as friends as we
+ // want to allow access to the constructors for the test
+ // class and the Balsa* classes. We put this into the
+ // header file as we want this class to be inlined into the
+ // BalsaHeaders implementation, yet be testable.
+ friend class BalsaBufferTestSpouse;
+ friend class BalsaHeaders;
+
+ // The BufferBlock is a structure used internally by the
+ // BalsaBuffer class to store the base buffer pointers to
+ // each block, as well as the important metadata for buffer
+ // sizes and bytes free. It *may* be possible to replace this
+ // with a vector<char>, but it's unclear whether moving a vector
+ // can invalidate pointers into it. LWG issue 2321 proposes to fix this.
+ struct QUICHE_EXPORT_PRIVATE BufferBlock {
+ public:
+ std::unique_ptr<char[]> buffer;
+ size_t buffer_size = 0;
+ size_t bytes_free = 0;
+
+ size_t bytes_used() const { return buffer_size - bytes_free; }
+ char* start_of_unused_bytes() const { return buffer.get() + bytes_used(); }
+
+ BufferBlock() {}
+
+ BufferBlock(std::unique_ptr<char[]> buf, size_t size, size_t free)
+ : buffer(std::move(buf)), buffer_size(size), bytes_free(free) {}
+
+ BufferBlock(const BufferBlock&) = delete;
+ BufferBlock& operator=(const BufferBlock&) = delete;
+ BufferBlock(BufferBlock&&) = default;
+ BufferBlock& operator=(BufferBlock&&) = default;
+
+ // Note: allocating a fresh buffer even if we could reuse an old one may let
+ // us shed memory, and invalidates old StringPieces (making them easier to
+ // catch with asan).
+ void CopyFrom(const BufferBlock& rhs) {
+ QUICHE_DCHECK(this != &rhs);
+ buffer_size = rhs.buffer_size;
+ bytes_free = rhs.bytes_free;
+ if (rhs.buffer == nullptr) {
+ buffer = nullptr;
+ } else {
+ buffer = absl::make_unique<char[]>(buffer_size);
+ memcpy(buffer.get(), rhs.buffer.get(), rhs.bytes_used());
+ }
+ }
+ };
+
+ typedef std::vector<BufferBlock> Blocks;
+
+ BalsaBuffer(const BalsaBuffer&) = delete;
+ BalsaBuffer& operator=(const BalsaBuffer&) = delete;
+ BalsaBuffer(BalsaBuffer&&) = default;
+ BalsaBuffer& operator=(BalsaBuffer&&) = default;
+
+ // Returns the total amount of memory reserved by the buffer blocks.
+ size_t GetTotalBufferBlockSize() const {
+ size_t buffer_size = 0;
+ for (Blocks::const_iterator iter = blocks_.begin(); iter != blocks_.end();
+ ++iter) {
+ buffer_size += iter->buffer_size;
+ }
+ return buffer_size;
+ }
+
+ // Returns the total amount of memory used by the buffer blocks.
+ size_t GetTotalBytesUsed() const {
+ size_t bytes_used = 0;
+ for (const auto& b : blocks_) {
+ bytes_used += b.bytes_used();
+ }
+ return bytes_used;
+ }
+
+ const char* GetPtr(Blocks::size_type block_idx) const {
+ QUICHE_DCHECK_LT(block_idx, blocks_.size())
+ << block_idx << ", " << blocks_.size();
+ return block_idx >= blocks_.size() ? nullptr
+ : blocks_[block_idx].buffer.get();
+ }
+
+ char* GetPtr(Blocks::size_type block_idx) {
+ QUICHE_DCHECK_LT(block_idx, blocks_.size())
+ << block_idx << ", " << blocks_.size();
+ return block_idx >= blocks_.size() ? nullptr
+ : blocks_[block_idx].buffer.get();
+ }
+
+ // This function is different from Reserve(), as it ensures that the data
+ // stored via subsequent calls to this function are all contiguous (and in
+ // the order in which these writes happened). This is essentially the same
+ // as a string append.
+ //
+ // You may call this function at any time between object
+ // construction/Clear(), and the calling of the
+ // NoMoreWriteToContiguousBuffer() function.
+ //
+ // You must not call this function after the NoMoreWriteToContiguousBuffer()
+ // function is called, unless a Clear() has been called since.
+ // If you do, the program will abort().
+ //
+ // This condition is placed upon this code so that calls to Reserve() can
+ // append to the buffer in the first block safely, and without invaliding
+ // the StringPiece which it returns.
+ //
+ // This function's main intended user is the BalsaFrame class, which,
+ // for reasons of efficiency, requires that the buffer from which it parses
+ // the headers be contiguous.
+ //
+ void WriteToContiguousBuffer(absl::string_view sp) {
+ if (sp.empty()) {
+ return;
+ }
+ QUICHE_CHECK(can_write_to_contiguous_buffer_);
+
+ if (blocks_.empty()) {
+ blocks_.push_back(AllocBlock());
+ }
+
+ QUICHE_DCHECK_GE(blocks_.size(), 1u);
+ if (blocks_[0].buffer == nullptr && sp.size() <= blocksize_) {
+ blocks_[0] = AllocBlock();
+ memcpy(blocks_[0].start_of_unused_bytes(), sp.data(), sp.size());
+ } else if (blocks_[0].bytes_free < sp.size()) {
+ // the first block isn't big enough, resize it.
+ const size_t old_storage_size_used = blocks_[0].bytes_used();
+ // Increase to at least 2*old_storage_size_used; if sp.size() is larger,
+ // we'll increase by that amount.
+ const size_t new_storage_size =
+ old_storage_size_used + (old_storage_size_used < sp.size()
+ ? sp.size()
+ : old_storage_size_used);
+ std::unique_ptr<char[]> new_storage{new char[new_storage_size]};
+ char* old_storage = blocks_[0].buffer.get();
+ if (old_storage_size_used != 0u) {
+ memcpy(new_storage.get(), old_storage, old_storage_size_used);
+ }
+ memcpy(new_storage.get() + old_storage_size_used, sp.data(), sp.size());
+ blocks_[0].buffer = std::move(new_storage);
+ blocks_[0].bytes_free = new_storage_size - old_storage_size_used;
+ blocks_[0].buffer_size = new_storage_size;
+ } else {
+ memcpy(blocks_[0].start_of_unused_bytes(), sp.data(), sp.size());
+ }
+ blocks_[0].bytes_free -= sp.size();
+ }
+
+ void NoMoreWriteToContiguousBuffer() {
+ can_write_to_contiguous_buffer_ = false;
+ }
+
+ // Reserves "permanent" storage of the size indicated. Returns a pointer to
+ // the beginning of that storage, and assigns the index of the block used to
+ // block_buffer_idx. This function uses the first block IFF the
+ // NoMoreWriteToContiguousBuffer function has been called since the last
+ // Clear/Construction.
+ char* Reserve(size_t size, Blocks::size_type* block_buffer_idx) {
+ if (blocks_.empty()) {
+ blocks_.push_back(AllocBlock());
+ }
+
+ // There should always be a 'first_block', even if it
+ // contains nothing.
+ QUICHE_DCHECK_GE(blocks_.size(), 1u);
+ BufferBlock* block = nullptr;
+ Blocks::size_type block_idx = can_write_to_contiguous_buffer_ ? 1 : 0;
+ for (; block_idx < blocks_.size(); ++block_idx) {
+ if (blocks_[block_idx].bytes_free >= size) {
+ block = &blocks_[block_idx];
+ break;
+ }
+ }
+ if (block == nullptr) {
+ if (blocksize_ < size) {
+ blocks_.push_back(AllocCustomBlock(size));
+ } else {
+ blocks_.push_back(AllocBlock());
+ }
+ block = &blocks_.back();
+ }
+
+ char* storage = block->start_of_unused_bytes();
+ block->bytes_free -= size;
+ if (block_buffer_idx != nullptr) {
+ *block_buffer_idx = block_idx;
+ }
+ return storage;
+ }
+
+ void Clear() {
+ blocks_.clear();
+ blocks_.shrink_to_fit();
+ can_write_to_contiguous_buffer_ = true;
+ }
+
+ void CopyFrom(const BalsaBuffer& b) {
+ blocks_.resize(b.blocks_.size());
+ for (Blocks::size_type i = 0; i < blocks_.size(); ++i) {
+ blocks_[i].CopyFrom(b.blocks_[i]);
+ }
+ blocksize_ = b.blocksize_;
+ can_write_to_contiguous_buffer_ = b.can_write_to_contiguous_buffer_;
+ }
+
+ const char* StartOfFirstBlock() const {
+ QUICHE_BUG_IF(bug_if_1182_1, blocks_.empty())
+ << "First block not allocated yet!";
+ return blocks_.empty() ? nullptr : blocks_[0].buffer.get();
+ }
+
+ const char* EndOfFirstBlock() const {
+ QUICHE_BUG_IF(bug_if_1182_2, blocks_.empty())
+ << "First block not allocated yet!";
+ return blocks_.empty() ? nullptr : blocks_[0].start_of_unused_bytes();
+ }
+
+ size_t GetReadableBytesOfFirstBlock() const {
+ return blocks_.empty() ? 0 : blocks_[0].bytes_used();
+ }
+
+ bool can_write_to_contiguous_buffer() const {
+ return can_write_to_contiguous_buffer_;
+ }
+ size_t blocksize() const { return blocksize_; }
+ Blocks::size_type num_blocks() const { return blocks_.size(); }
+ size_t buffer_size(size_t idx) const { return blocks_[idx].buffer_size; }
+ size_t bytes_used(size_t idx) const { return blocks_[idx].bytes_used(); }
+
+ private:
+ BalsaBuffer()
+ : blocksize_(kDefaultBlocksize), can_write_to_contiguous_buffer_(true) {}
+
+ explicit BalsaBuffer(size_t blocksize)
+ : blocksize_(blocksize), can_write_to_contiguous_buffer_(true) {}
+
+ BufferBlock AllocBlock() { return AllocCustomBlock(blocksize_); }
+
+ BufferBlock AllocCustomBlock(size_t blocksize) {
+ return BufferBlock{absl::make_unique<char[]>(blocksize), blocksize,
+ blocksize};
+ }
+
+ // A container of BufferBlocks
+ Blocks blocks_;
+
+ // The default allocation size for a block.
+ // In general, blocksize_ bytes will be allocated for
+ // each buffer.
+ size_t blocksize_;
+
+ // If set to true, then the first block cannot be used for Reserve() calls as
+ // the WriteToContiguous... function will modify the base pointer for this
+ // block, and the Reserve() calls need to be sure that the base pointer will
+ // not be changing in order to provide the user with StringPieces which
+ // continue to be valid.
+ bool can_write_to_contiguous_buffer_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// All of the functions in the BalsaHeaders class use string pieces, by either
+// using the StringPiece class, or giving an explicit size and char* (as these
+// are the native representation for these string pieces).
+// This is done for several reasons.
+// 1) This minimizes copying/allocation/deallocation as compared to using
+// string parameters
+// 2) This reduces the number of strlen() calls done (as the length of any
+// string passed in is relatively likely to be known at compile time, and for
+// those strings passed back we obviate the need for a strlen() to determine
+// the size of new storage allocations if a new allocation is required.
+// 3) This class attempts to store all of its data in two linear buffers in
+// order to enhance the speed of parsing and writing out to a buffer. As a
+// result, many string pieces are -not- terminated by '\0', and are not
+// c-strings. Since this is the case, we must delineate the length of the
+// string explicitly via a length.
+//
+// WARNING: The side effect of using StringPiece is that if the underlying
+// buffer changes (due to modifying the headers) the StringPieces which point
+// to the data which was modified, may now contain "garbage", and should not
+// be dereferenced.
+// For example, If you fetch some component of the first-line, (request or
+// response), and then you modify the first line, the StringPieces you
+// originally received from the original first-line may no longer be valid).
+//
+// StringPieces pointing to pieces of header lines which have not been
+// erased() or modified should be valid until the object is cleared or
+// destroyed.
+//
+// Key comparisons are case-insensitive.
+
+class QUICHE_EXPORT_PRIVATE BalsaHeaders : public HeaderApi {
+ public:
+ // Each header line is parsed into a HeaderLineDescription, which maintains
+ // pointers into the BalsaBuffer.
+ struct HeaderLineDescription;
+
+ using HeaderTokenList = std::vector<absl::string_view>;
+
+ // An iterator for walking through all the header lines.
+ class const_header_lines_iterator;
+
+ // An iterator that only stops at lines with a particular key
+ // (case-insensitive). See also GetIteratorForKey.
+ //
+ // Check against header_lines_key_end() to determine when iteration is
+ // finished. lines().end() will also work.
+ class const_header_lines_key_iterator;
+
+ // Set of names of headers that might have multiple values. The GFE2 and
+ // Envoy/GFE3 use different sets, and CoalesceOption::kCoalesce can be used
+ // to match Envoy behavior in WriteToBuffer().
+ using MultivaluedHeadersSet =
+ absl::flat_hash_set<absl::string_view, StringPieceCaseHash,
+ StringPieceCaseEqual>;
+
+ // Map of key => vector<value>, where vector contains ordered list of all
+ // values for |key| (ignoring the casing).
+ using MultivaluedHeadersValuesMap =
+ absl::flat_hash_map<absl::string_view, std::vector<absl::string_view>,
+ StringPieceCaseHash, StringPieceCaseEqual>;
+
+ // TODO(fenix): Revisit the amount of bytes initially allocated to the second
+ // block of the balsa_buffer_. It may make sense to pre-allocate some amount
+ // (roughly the amount we'd append in new headers such as X-User-Ip, etc.)
+ BalsaHeaders()
+ : balsa_buffer_(4096),
+ 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),
+ transfer_encoding_is_chunked_(false) {}
+
+ explicit BalsaHeaders(size_t bufsize)
+ : balsa_buffer_(bufsize),
+ 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),
+ transfer_encoding_is_chunked_(false) {}
+
+ // Copying BalsaHeaders is expensive, so require that it be visible.
+ BalsaHeaders(const BalsaHeaders&) = delete;
+ BalsaHeaders& operator=(const BalsaHeaders&) = delete;
+ BalsaHeaders(BalsaHeaders&&) = default;
+ BalsaHeaders& operator=(BalsaHeaders&&) = default;
+
+ // Returns a range that represents all of the header lines.
+ absl::iterator_range<const_header_lines_iterator> lines() const;
+
+ // Returns an iterator range consisting of the header lines matching key.
+ // String backing 'key' must remain valid for lifetime of range.
+ absl::iterator_range<const_header_lines_key_iterator> lines(
+ absl::string_view key) const;
+
+ // Returns a forward-only iterator that only stops at lines matching key.
+ // String backing 'key' must remain valid for lifetime of iterator.
+ //
+ // Check returned iterator against header_lines_key_end() to determine when
+ // iteration is finished.
+ //
+ // Consider calling lines(key)--it may be more readable.
+ const_header_lines_key_iterator GetIteratorForKey(
+ absl::string_view key) const;
+
+ const_header_lines_key_iterator header_lines_key_end() const;
+
+ void erase(const const_header_lines_iterator& it);
+
+ void Clear();
+
+ // Explicit copy functions to avoid risk of accidental copies.
+ BalsaHeaders Copy() const {
+ BalsaHeaders copy;
+ copy.CopyFrom(*this);
+ return copy;
+ }
+ void CopyFrom(const BalsaHeaders& other);
+
+ // Replaces header entries with key 'key' if they exist, or appends
+ // a new header if none exist. See 'AppendHeader' below for additional
+ // comments about ContentLength and TransferEncoding headers. Note that this
+ // will allocate new storage every time that it is called.
+ // TODO(fenix): modify this function to reuse existing storage
+ // if it is available.
+ void ReplaceOrAppendHeader(absl::string_view key,
+ absl::string_view value) override;
+
+ // Append a new header entry to the header object. Clients who wish to append
+ // Content-Length header should use SetContentLength() method instead of
+ // adding the content length header using AppendHeader (manually adding the
+ // content length header will not update the content_length_ and
+ // content_length_status_ values).
+ // Similarly, clients who wish to add or remove the transfer encoding header
+ // in order to apply or remove chunked encoding should use
+ // SetTransferEncodingToChunkedAndClearContentLength() or
+ // SetNoTransferEncoding() instead.
+ void AppendHeader(absl::string_view key, absl::string_view value) override;
+
+ // Appends ',value' to an existing header named 'key'. If no header with the
+ // correct key exists, it will call AppendHeader(key, value). Calling this
+ // function on a key which exists several times in the headers will produce
+ // unpredictable results.
+ void AppendToHeader(absl::string_view key, absl::string_view value) override;
+
+ // Appends ', value' to an existing header named 'key'. If no header with the
+ // correct key exists, it will call AppendHeader(key, value). Calling this
+ // function on a key which exists several times in the headers will produce
+ // unpredictable results.
+ void AppendToHeaderWithCommaAndSpace(absl::string_view key,
+ absl::string_view value) override;
+
+ // Returns the value corresponding to the given header key. Returns an empty
+ // string if the header key does not exist. For headers that may consist of
+ // multiple lines, use GetAllOfHeader() instead.
+ absl::string_view GetHeader(absl::string_view key) const override;
+
+ // Iterates over all currently valid header lines, appending their
+ // values into the vector 'out', in top-to-bottom order.
+ // Header-lines which have been erased are not currently valid, and
+ // will not have their values appended. Empty values will be
+ // represented as empty string. If 'key' doesn't exist in the headers at
+ // all, out will not be changed. We do not clear the vector out
+ // before adding new entries. If there are header lines with matching
+ // key but empty value then they are also added to the vector out.
+ // (Basically empty values are not treated in any special manner).
+ //
+ // Example:
+ // Input header:
+ // "GET / HTTP/1.0\r\n"
+ // "key1: v1\r\n"
+ // "key1: \r\n"
+ // "key1:\r\n"
+ // "key1: v1\r\n"
+ // "key1:v2\r\n"
+ //
+ // vector out is initially: ["foo"]
+ // vector out after GetAllOfHeader("key1", &out) is:
+ // ["foo", "v1", "", "", "v1", "v2"]
+ //
+ // See gfe::header_properties::IsMultivaluedHeader() for which headers
+ // GFE treats as being multivalued.
+
+ // Make all methods in this overload set visible, and override just one.
+ using HeaderApi::GetAllOfHeader;
+ void GetAllOfHeader(absl::string_view key,
+ std::vector<absl::string_view>* out) const override;
+
+ // Same as above, but iterates over all header lines including removed ones.
+ // Appends their values into the vector 'out' in top-to-bottom order,
+ // first all valid headers then all that were removed.
+ void GetAllOfHeaderIncludeRemoved(absl::string_view key,
+ std::vector<absl::string_view>* out) const;
+
+ // Joins all values for `key` into a comma-separated string.
+ std::string GetAllOfHeaderAsString(absl::string_view key) const override;
+
+ // Determine if a given header is present. Case-insensitive.
+ inline bool HasHeader(absl::string_view key) const override {
+ return GetConstHeaderLinesIterator(key) != header_lines_.end();
+ }
+
+ // Goes through all headers with key 'key' and checks to see if one of the
+ // values is 'value'. Returns true if there are headers with the desired key
+ // and value, false otherwise. Case-insensitive for the key; case-sensitive
+ // for the value.
+ bool HeaderHasValue(absl::string_view key,
+ absl::string_view value) const override {
+ return HeaderHasValueHelper(key, value, true);
+ }
+ // Same as above, but also case-insensitive for the value.
+ bool HeaderHasValueIgnoreCase(absl::string_view key,
+ absl::string_view value) const override {
+ return HeaderHasValueHelper(key, value, false);
+ }
+
+ // Returns true iff any header 'key' exists with non-empty value.
+ bool HasNonEmptyHeader(absl::string_view key) const override;
+
+ const_header_lines_iterator GetHeaderPosition(absl::string_view key) const;
+
+ // Removes all headers in given set |keys| at once efficiently. Keys
+ // are case insensitive.
+ //
+ // Alternatives considered:
+ //
+ // 1. Use string_hash_set<>, the caller (such as ClearHopByHopHeaders) lower
+ // cases the keys and RemoveAllOfHeaderInList just does lookup. This according
+ // to microbenchmark gives the best performance because it does not require
+ // an extra copy of the hash table. However, it is not taken because of the
+ // possible risk that caller could forget to lowercase the keys.
+ //
+ // 2. Use flat_hash_set<StringPiece, StringPieceCaseHash,StringPieceCaseEqual>
+ // or string_hash_set<StringPieceCaseHash, StringPieceCaseEqual>. Both appear
+ // to have (much) worse performance with WithoutDupToken and LongHeader case
+ // in microbenchmark.
+ void RemoveAllOfHeaderInList(const HeaderTokenList& keys) override;
+
+ void RemoveAllOfHeader(absl::string_view key) override;
+
+ // Removes all headers starting with 'key' [case insensitive]
+ void RemoveAllHeadersWithPrefix(absl::string_view key) override;
+
+ // Returns true if we have at least one header with given prefix
+ // [case insensitive]. Currently for test use only.
+ bool HasHeadersWithPrefix(absl::string_view key) const override;
+
+ // Returns the key value pairs for all headers where the header key begins
+ // with the specified prefix.
+ void GetAllOfHeaderWithPrefix(
+ absl::string_view prefix,
+ std::vector<std::pair<absl::string_view, absl::string_view>>* out)
+ const override;
+
+ void GetAllHeadersWithLimit(
+ std::vector<std::pair<absl::string_view, absl::string_view>>* out,
+ int limit) const override;
+
+ // Removes all values equal to a given value from header lines with given key.
+ // All string operations done here are case-sensitive.
+ // If a header line has only values matching the given value, the entire
+ // line is removed.
+ // If the given value is found in a multi-value header line mixed with other
+ // values, the line is edited in-place to remove the values.
+ // Returns the number of occurrences of value that were removed.
+ // This method runs in linear time.
+ size_t RemoveValue(absl::string_view key, absl::string_view value);
+
+ // Returns the upper bound on the required buffer space to fully write out
+ // the header object (this include the first line, all header lines, and the
+ // final line separator that marks the ending of the header).
+ size_t GetSizeForWriteBuffer() const override;
+
+ // Indicates if to serialize headers with lower-case header keys.
+ enum class CaseOption { kNoModification, kLowercase, kPropercase };
+
+ // Indicates if to coalesce headers with multiple values to match Envoy/GFE3.
+ enum class CoalesceOption { kNoCoalesce, kCoalesce };
+
+ // The following WriteHeader* methods are template member functions that
+ // place one requirement on the Buffer class: it must implement a Write
+ // method that takes a pointer and a length. The buffer passed in is not
+ // required to be stretchable. For non-stretchable buffers, the user must
+ // call GetSizeForWriteBuffer() to find out the upper bound on the output
+ // buffer space required to make sure that the entire header is serialized.
+ // BalsaHeaders will not check that there is adequate space in the buffer
+ // object during the write.
+
+ // Writes the entire header and the final line separator that marks the end
+ // of the HTTP header section to the buffer. After this method returns, no
+ // more header data should be written to the buffer.
+ template <typename Buffer>
+ void WriteHeaderAndEndingToBuffer(Buffer* buffer, CaseOption case_option,
+ CoalesceOption coalesce_option) const {
+ WriteToBuffer(buffer, case_option, coalesce_option);
+ WriteHeaderEndingToBuffer(buffer);
+ }
+
+ template <typename Buffer>
+ void WriteHeaderAndEndingToBuffer(Buffer* buffer) const {
+ WriteHeaderAndEndingToBuffer(buffer, CaseOption::kNoModification,
+ CoalesceOption::kNoCoalesce);
+ }
+
+ // Writes the final line separator to the buffer to terminate the HTTP header
+ // section. After this method returns, no more header data should be written
+ // to the buffer.
+ template <typename Buffer>
+ static void WriteHeaderEndingToBuffer(Buffer* buffer) {
+ buffer->WriteString("\r\n");
+ }
+
+ // Writes the entire header to the buffer without the line separator that
+ // terminates the HTTP header. This lets users append additional header lines
+ // using WriteHeaderLineToBuffer and then terminate the header with
+ // WriteHeaderEndingToBuffer as the header is serialized to the buffer,
+ // without having to first copy the header.
+ template <typename Buffer>
+ void WriteToBuffer(Buffer* buffer, CaseOption case_option,
+ CoalesceOption coalesce_option) const;
+
+ template <typename Buffer>
+ void WriteToBuffer(Buffer* buffer) const {
+ WriteToBuffer(buffer, CaseOption::kNoModification,
+ CoalesceOption::kNoCoalesce);
+ }
+
+ // Used by WriteToBuffer to coalesce multiple values of headers listed in
+ // |multivalued_headers| into a single comma-separated value. Public for test.
+ template <typename Buffer>
+ void WriteToBufferCoalescingMultivaluedHeaders(
+ Buffer* buffer, const MultivaluedHeadersSet& multivalued_headers,
+ CaseOption case_option) const;
+
+ // Populates |multivalues| with values of |header_lines_| with keys present
+ // in |multivalued_headers| set.
+ void GetValuesOfMultivaluedHeaders(
+ const MultivaluedHeadersSet& multivalued_headers,
+ MultivaluedHeadersValuesMap* multivalues) const;
+
+ static std::string ToPropercase(absl::string_view header) {
+ std::string copy = std::string(header);
+ bool should_uppercase = true;
+ for (char& c : copy) {
+ if (!absl::ascii_isalnum(c)) {
+ should_uppercase = true;
+ } else if (should_uppercase) {
+ c = absl::ascii_toupper(c);
+ should_uppercase = false;
+ } else {
+ c = absl::ascii_tolower(c);
+ }
+ }
+ return copy;
+ }
+
+ template <typename Buffer>
+ void WriteHeaderKeyToBuffer(Buffer* buffer, absl::string_view key,
+ CaseOption case_option) const {
+ if (case_option == CaseOption::kLowercase) {
+ buffer->WriteString(absl::AsciiStrToLower(key));
+ } else if (case_option == CaseOption::kPropercase) {
+ const auto& header_set = quiche::GetStandardHeaderSet();
+ auto it = header_set.find(key);
+ if (it != header_set.end()) {
+ buffer->WriteString(*it);
+ } else {
+ buffer->WriteString(ToPropercase(key));
+ }
+ } else {
+ buffer->WriteString(key);
+ }
+ }
+
+ // Takes a header line in the form of a key/value pair and append it to the
+ // buffer. This function should be called after WriteToBuffer to
+ // append additional header lines to the header without copying the header.
+ // When the user is done with appending to the buffer,
+ // WriteHeaderEndingToBuffer must be used to terminate the HTTP
+ // header in the buffer. This method is a no-op if key is empty.
+ template <typename Buffer>
+ void WriteHeaderLineToBuffer(Buffer* buffer, absl::string_view key,
+ absl::string_view value,
+ CaseOption case_option) const {
+ // If the key is empty, we don't want to write the rest because it
+ // will not be a well-formed header line.
+ if (!key.empty()) {
+ WriteHeaderKeyToBuffer(buffer, key, case_option);
+ buffer->WriteString(": ");
+ buffer->WriteString(value);
+ buffer->WriteString("\r\n");
+ }
+ }
+
+ // Takes a header line in the form of a key and vector of values and appends
+ // it to the buffer. This function should be called after WriteToBuffer to
+ // append additional header lines to the header without copying the header.
+ // When the user is done with appending to the buffer,
+ // WriteHeaderEndingToBuffer must be used to terminate the HTTP
+ // header in the buffer. This method is a no-op if the |key| is empty.
+ template <typename Buffer>
+ void WriteHeaderLineValuesToBuffer(
+ Buffer* buffer, absl::string_view key,
+ const std::vector<absl::string_view>& values,
+ CaseOption case_option) const {
+ // If the key is empty, we don't want to write the rest because it
+ // will not be a well-formed header line.
+ if (!key.empty()) {
+ WriteHeaderKeyToBuffer(buffer, key, case_option);
+ buffer->WriteString(": ");
+ for (auto it = values.begin();;) {
+ buffer->WriteString(*it);
+ if (++it == values.end()) {
+ break;
+ }
+ buffer->WriteString(",");
+ }
+ buffer->WriteString("\r\n");
+ }
+ }
+
+ // Dump the textural representation of the header object to a string, which
+ // is suitable for writing out to logs. All CRLF will be printed out as \n.
+ // This function can be called on a header object in any state. Raw header
+ // data will be printed out if the header object is not completely parsed,
+ // e.g., when there was an error in the middle of parsing.
+ // The header content is appended to the string; the original content is not
+ // cleared.
+ // If used in test cases, WillNotWriteFromFramer() may be of interest.
+ void DumpToString(std::string* str) const;
+ std::string DebugString() const override;
+
+ bool ForEachHeader(std::function<bool(const absl::string_view key,
+ const absl::string_view value)>
+ fn) const override;
+
+ void DumpToPrefixedString(const char* spaces, std::string* str) const;
+
+ absl::string_view first_line() const {
+ QUICHE_DCHECK_GE(whitespace_4_idx_, non_whitespace_1_idx_);
+ return whitespace_4_idx_ == non_whitespace_1_idx_
+ ? ""
+ : absl::string_view(
+ BeginningOfFirstLine() + non_whitespace_1_idx_,
+ whitespace_4_idx_ - non_whitespace_1_idx_);
+ }
+ std::string first_line_of_request() const override {
+ return std::string(first_line());
+ }
+
+ // Returns the parsed value of the response code if it has been parsed.
+ // Guaranteed to return 0 when unparsed (though it is a much better idea to
+ // verify that the BalsaFrame had no errors while parsing).
+ // This may return response codes which are outside the normal bounds of
+ // HTTP response codes-- it is up to the user of this class to ensure that
+ // the response code is one which is interpretable.
+ size_t parsed_response_code() const override { return parsed_response_code_; }
+
+ absl::string_view request_method() const override {
+ QUICHE_DCHECK_GE(whitespace_2_idx_, non_whitespace_1_idx_);
+ return whitespace_2_idx_ == non_whitespace_1_idx_
+ ? ""
+ : absl::string_view(
+ BeginningOfFirstLine() + non_whitespace_1_idx_,
+ whitespace_2_idx_ - non_whitespace_1_idx_);
+ }
+
+ absl::string_view response_version() const override {
+ // Note: There is no difference between request_method() and
+ // response_version(). They both could be called
+ // GetFirstTokenFromFirstline()... but that wouldn't be anywhere near as
+ // descriptive.
+ return request_method();
+ }
+
+ absl::string_view request_uri() const override {
+ QUICHE_DCHECK_GE(whitespace_3_idx_, non_whitespace_2_idx_);
+ return whitespace_3_idx_ == non_whitespace_2_idx_
+ ? ""
+ : absl::string_view(
+ BeginningOfFirstLine() + non_whitespace_2_idx_,
+ whitespace_3_idx_ - non_whitespace_2_idx_);
+ }
+
+ absl::string_view response_code() const override {
+ // Note: There is no difference between request_uri() and response_code().
+ // They both could be called GetSecondtTokenFromFirstline(), but, as noted
+ // in an earlier comment, that wouldn't be as descriptive.
+ return request_uri();
+ }
+
+ absl::string_view request_version() const override {
+ QUICHE_DCHECK_GE(whitespace_4_idx_, non_whitespace_3_idx_);
+ return whitespace_4_idx_ == non_whitespace_3_idx_
+ ? ""
+ : absl::string_view(
+ BeginningOfFirstLine() + non_whitespace_3_idx_,
+ whitespace_4_idx_ - non_whitespace_3_idx_);
+ }
+
+ absl::string_view response_reason_phrase() const override {
+ // Note: There is no difference between request_version() and
+ // response_reason_phrase(). They both could be called
+ // GetThirdTokenFromFirstline(), but, as noted in an earlier comment, that
+ // wouldn't be as descriptive.
+ return request_version();
+ }
+
+ void SetRequestFirstlineFromStringPieces(absl::string_view method,
+ absl::string_view uri,
+ absl::string_view version) {
+ SetFirstlineFromStringPieces(method, uri, version);
+ }
+
+ void SetResponseFirstline(absl::string_view version,
+ size_t parsed_response_code,
+ absl::string_view reason_phrase);
+
+ // These functions are exactly the same, except that their names are
+ // different. This is done so that the code using this class is more
+ // expressive.
+ void SetRequestMethod(absl::string_view method) override;
+ void SetResponseVersion(absl::string_view version) override;
+
+ void SetRequestUri(absl::string_view uri) override;
+ void SetResponseCode(absl::string_view code) override;
+ void set_parsed_response_code(size_t parsed_response_code) {
+ parsed_response_code_ = parsed_response_code;
+ }
+ void SetParsedResponseCodeAndUpdateFirstline(
+ size_t parsed_response_code) override;
+
+ // These functions are exactly the same, except that their names are
+ // different. This is done so that the code using this class is more
+ // expressive.
+ void SetRequestVersion(absl::string_view version) override;
+ void SetResponseReasonPhrase(absl::string_view reason_phrase) override;
+
+ // Simple accessors to some of the internal state
+ bool transfer_encoding_is_chunked() const {
+ return transfer_encoding_is_chunked_;
+ }
+
+ static bool ResponseCodeImpliesNoBody(size_t code) {
+ // From HTTP spec section 6.1.1 all 1xx responses must not have a body,
+ // as well as 204 No Content and 304 Not Modified.
+ return ((code >= 100) && (code <= 199)) || (code == 204) || (code == 304);
+ }
+
+ // Note: never check this for requests. Nothing bad will happen if you do,
+ // but spec does not allow requests framed by connection close.
+ // TODO(vitaliyl): refactor.
+ bool is_framed_by_connection_close() const {
+ // We declare that response is framed by connection close if it has no
+ // content-length, no transfer encoding, and is allowed to have a body by
+ // the HTTP spec.
+ // parsed_response_code_ is 0 for requests, so ResponseCodeImpliesNoBody
+ // will return false.
+ return (content_length_status_ == BalsaHeadersEnums::NO_CONTENT_LENGTH) &&
+ !transfer_encoding_is_chunked_ &&
+ !ResponseCodeImpliesNoBody(parsed_response_code_);
+ }
+
+ size_t content_length() const override { return content_length_; }
+ BalsaHeadersEnums::ContentLengthStatus content_length_status() const {
+ return content_length_status_;
+ }
+ bool content_length_valid() const override {
+ return content_length_status_ == BalsaHeadersEnums::VALID_CONTENT_LENGTH;
+ }
+
+ // SetContentLength, SetTransferEncodingToChunkedAndClearContentLength, and
+ // SetNoTransferEncoding modifies the header object to use
+ // content-length and transfer-encoding headers in a consistent
+ // manner. They set all internal flags and status so client can get
+ // a consistent view from various accessors.
+ void SetContentLength(size_t length) override;
+ // Sets transfer-encoding to chunked and updates internal state.
+ void SetTransferEncodingToChunkedAndClearContentLength() override;
+ // Removes transfer-encoding headers and updates internal state.
+ void SetNoTransferEncoding() override;
+
+ // If you have a response that needs framing by connection close, use this
+ // method instead of RemoveAllOfHeader("Content-Length"). Has no effect if
+ // transfer_encoding_is_chunked().
+ void ClearContentLength();
+
+ // This should be called if balsa headers are created entirely manually (not
+ // by any of the framer classes) to make sure that function calls like
+ // DumpToString will work correctly.
+ void WillNotWriteFromFramer() {
+ balsa_buffer_.NoMoreWriteToContiguousBuffer();
+ }
+
+ // True if DoneWritingFromFramer or WillNotWriteFromFramer is called.
+ bool FramerIsDoneWriting() const {
+ return !balsa_buffer_.can_write_to_contiguous_buffer();
+ }
+
+ bool IsEmpty() const override;
+
+ // From HeaderApi and ConstHeaderApi.
+ absl::string_view Authority() const override;
+ void ReplaceOrAppendAuthority(absl::string_view value) override;
+ void RemoveAuthority() override;
+ void ApplyToCookie(
+ std::function<void(absl::string_view cookie)> f) const override;
+
+ void set_enforce_header_policy(bool enforce) override {
+ enforce_header_policy_ = enforce;
+ }
+
+ // Removes the last token from the header value. In the presence of multiple
+ // header lines with given key, will remove the last token of the last line.
+ // Can be useful if the last encoding has to be removed.
+ void RemoveLastTokenFromHeaderValue(absl::string_view key);
+
+ // Gets the list of names of headers that are multivalued in Envoy.
+ static const MultivaluedHeadersSet& multivalued_envoy_headers();
+
+ // Returns true if HTTP responses with this response code have bodies.
+ static bool ResponseCanHaveBody(int response_code);
+
+ // Given a pointer to the beginning and the end of the header value
+ // in some buffer, populates tokens list with beginning and end indices
+ // of all tokens present in the value string.
+ static void ParseTokenList(absl::string_view header_value,
+ HeaderTokenList* tokens);
+
+ private:
+ typedef std::vector<HeaderLineDescription> HeaderLines;
+
+ class iterator_base;
+
+ friend class BalsaFrame;
+ friend class gfe2::Http2HeaderValidator;
+ friend class SpdyPayloadFramer;
+ friend class HTTPMessage;
+ friend class test::BalsaHeadersTestPeer;
+
+ friend bool ParseHTTPFirstLine(const char* begin, const char* end,
+ bool is_request, BalsaHeaders* headers,
+ BalsaFrameEnums::ErrorCode* error_code);
+
+ // Reverse iterators have been removed for lack of use, refer to
+ // cl/30618773 in case they are needed.
+
+ const char* BeginningOfFirstLine() const {
+ return GetPtr(firstline_buffer_base_idx_);
+ }
+
+ char* BeginningOfFirstLine() { return GetPtr(firstline_buffer_base_idx_); }
+
+ char* GetPtr(BalsaBuffer::Blocks::size_type block_idx) {
+ return balsa_buffer_.GetPtr(block_idx);
+ }
+
+ const char* GetPtr(BalsaBuffer::Blocks::size_type block_idx) const {
+ return balsa_buffer_.GetPtr(block_idx);
+ }
+
+ void WriteFromFramer(const char* ptr, size_t size) {
+ balsa_buffer_.WriteToContiguousBuffer(absl::string_view(ptr, size));
+ }
+
+ void DoneWritingFromFramer() {
+ balsa_buffer_.NoMoreWriteToContiguousBuffer();
+ }
+
+ const char* OriginalHeaderStreamBegin() const {
+ return balsa_buffer_.StartOfFirstBlock();
+ }
+
+ const char* OriginalHeaderStreamEnd() const {
+ return balsa_buffer_.EndOfFirstBlock();
+ }
+
+ size_t GetReadableBytesFromHeaderStream() const {
+ return balsa_buffer_.GetReadableBytesOfFirstBlock();
+ }
+
+ void GetReadablePtrFromHeaderStream(const char** p, size_t* s) {
+ *p = OriginalHeaderStreamBegin();
+ *s = GetReadableBytesFromHeaderStream();
+ }
+
+ absl::string_view GetValueFromHeaderLineDescription(
+ const HeaderLineDescription& line) const;
+
+ void AddAndMakeDescription(absl::string_view key, absl::string_view value,
+ HeaderLineDescription* d);
+
+ void AppendAndMakeDescription(absl::string_view key, absl::string_view value,
+ HeaderLineDescription* d);
+
+ // Removes all header lines with the given key starting at start.
+ void RemoveAllOfHeaderStartingAt(absl::string_view key,
+ HeaderLines::iterator start);
+
+ HeaderLines::const_iterator GetConstHeaderLinesIterator(
+ absl::string_view key) const;
+
+ HeaderLines::iterator GetHeaderLinesIterator(absl::string_view key,
+ HeaderLines::iterator start);
+
+ HeaderLines::iterator GetHeaderLinesIteratorForLastMultivaluedHeader(
+ absl::string_view key);
+
+ template <typename IteratorType>
+ const IteratorType HeaderLinesBeginHelper() const;
+
+ template <typename IteratorType>
+ const IteratorType HeaderLinesEndHelper() const;
+
+ // Helper function for HeaderHasValue and HeaderHasValueIgnoreCase that
+ // does most of the work.
+ bool HeaderHasValueHelper(absl::string_view key, absl::string_view value,
+ bool case_sensitive) const;
+
+ // Called by header removal methods to reset internal values for transfer
+ // encoding or content length if we're removing the corresponding headers.
+ void MaybeClearSpecialHeaderValues(absl::string_view key);
+
+ void SetFirstlineFromStringPieces(absl::string_view firstline_a,
+ absl::string_view firstline_b,
+ absl::string_view firstline_c);
+ BalsaBuffer balsa_buffer_;
+
+ size_t content_length_;
+ BalsaHeadersEnums::ContentLengthStatus content_length_status_;
+ size_t parsed_response_code_;
+ // HTTP firstlines all have the following structure:
+ // LWS NONWS LWS NONWS LWS NONWS NOTCRLF CRLF
+ // [\t \r\n]+ [^\t ]+ [\t ]+ [^\t ]+ [\t ]+ [^\t ]+ [^\r\n]+ "\r\n"
+ // ws1 nws1 ws2 nws2 ws3 nws3 ws4
+ // | [-------) [-------) [----------------)
+ // REQ: method request_uri version
+ // RESP: version statuscode reason
+ //
+ // The first NONWS->LWS component we'll call firstline_a.
+ // The second firstline_b, and the third firstline_c.
+ //
+ // firstline_a goes from nws1 to (but not including) ws2
+ // firstline_b goes from nws2 to (but not including) ws3
+ // firstline_c goes from nws3 to (but not including) ws4
+ //
+ // In the code:
+ // ws1 == whitespace_1_idx_
+ // nws1 == non_whitespace_1_idx_
+ // ws2 == whitespace_2_idx_
+ // nws2 == non_whitespace_2_idx_
+ // ws3 == whitespace_3_idx_
+ // nws3 == non_whitespace_3_idx_
+ // ws4 == whitespace_4_idx_
+ BalsaBuffer::Blocks::size_type firstline_buffer_base_idx_;
+ size_t whitespace_1_idx_;
+ size_t non_whitespace_1_idx_;
+ size_t whitespace_2_idx_;
+ size_t non_whitespace_2_idx_;
+ size_t whitespace_3_idx_;
+ size_t non_whitespace_3_idx_;
+ size_t whitespace_4_idx_;
+
+ bool transfer_encoding_is_chunked_;
+
+ // If true, QUICHE_BUG if a header that starts with an invalid prefix is
+ // explicitly set.
+ bool enforce_header_policy_ = true;
+
+ HeaderLines header_lines_;
+};
+
+// Succinctly describes one header line as indices into a buffer.
+struct BalsaHeaders::HeaderLineDescription {
+ HeaderLineDescription(size_t first_character_index, size_t key_end_index,
+ size_t value_begin_index, size_t last_character_index,
+ size_t buffer_base_index)
+ : first_char_idx(first_character_index),
+ key_end_idx(key_end_index),
+ value_begin_idx(value_begin_index),
+ last_char_idx(last_character_index),
+ buffer_base_idx(buffer_base_index),
+ skip(false) {}
+
+ HeaderLineDescription()
+ : first_char_idx(0),
+ key_end_idx(0),
+ value_begin_idx(0),
+ last_char_idx(0),
+ buffer_base_idx(0),
+ skip(false) {}
+
+ size_t KeyLength() const {
+ QUICHE_DCHECK_GE(key_end_idx, first_char_idx);
+ return key_end_idx - first_char_idx;
+ }
+ size_t ValuesLength() const {
+ QUICHE_DCHECK_GE(last_char_idx, value_begin_idx);
+ return last_char_idx - value_begin_idx;
+ }
+
+ size_t first_char_idx;
+ size_t key_end_idx;
+ size_t value_begin_idx;
+ size_t last_char_idx;
+ BalsaBuffer::Blocks::size_type buffer_base_idx;
+ bool skip;
+};
+
+// Base class for iterating the headers in a BalsaHeaders object, returning a
+// pair of string_view's for each header.
+class BalsaHeaders::iterator_base
+ : public std::iterator<std::forward_iterator_tag,
+ std::pair<absl::string_view, absl::string_view>> {
+ public:
+ // default constructor.
+ iterator_base() : headers_(nullptr), idx_(0) {}
+
+ // copy constructor.
+ iterator_base(const iterator_base& it)
+ : headers_(it.headers_), idx_(it.idx_) {}
+
+ std::pair<absl::string_view, absl::string_view>& operator*() const {
+ return Lookup(idx_);
+ }
+
+ std::pair<absl::string_view, absl::string_view>* operator->() const {
+ return &(this->operator*());
+ }
+
+ bool operator==(const BalsaHeaders::iterator_base& it) const {
+ return idx_ == it.idx_;
+ }
+
+ bool operator<(const BalsaHeaders::iterator_base& it) const {
+ return idx_ < it.idx_;
+ }
+
+ bool operator<=(const BalsaHeaders::iterator_base& it) const {
+ return idx_ <= it.idx_;
+ }
+
+ bool operator!=(const BalsaHeaders::iterator_base& it) const {
+ return !(*this == it);
+ }
+
+ bool operator>(const BalsaHeaders::iterator_base& it) const {
+ return it < *this;
+ }
+
+ bool operator>=(const BalsaHeaders::iterator_base& it) const {
+ return it <= *this;
+ }
+
+ // This mainly exists so that we can have interesting output for
+ // unittesting. The EXPECT_EQ, EXPECT_NE functions require that
+ // operator<< work for the classes it sees. It would be better if there
+ // was an additional traits-like system for the gUnit output... but oh
+ // well.
+ friend QUICHE_EXPORT_PRIVATE std::ostream& operator<<(
+ std::ostream& os, const iterator_base& it) {
+ os << "[" << it.headers_ << ", " << it.idx_ << "]";
+ return os;
+ }
+
+ private:
+ friend class BalsaHeaders;
+
+ iterator_base(const BalsaHeaders* headers, HeaderLines::size_type index)
+ : headers_(headers), idx_(index) {}
+
+ void increment() {
+ const HeaderLines& header_lines = headers_->header_lines_;
+ const HeaderLines::size_type header_lines_size = header_lines.size();
+ const HeaderLines::size_type original_idx = idx_;
+ do {
+ ++idx_;
+ } while (idx_ < header_lines_size && header_lines[idx_].skip == true);
+ // The condition below exists so that ++(end() - 1) == end(), even
+ // if there are only 'skip == true' elements between the end() iterator
+ // and the end of the vector of HeaderLineDescriptions.
+ // TODO(fenix): refactor this list so that we don't have to do
+ // linear scanning through skipped headers (and this condition is
+ // then unnecessary)
+ if (idx_ == header_lines_size) {
+ idx_ = original_idx + 1;
+ }
+ }
+
+ std::pair<absl::string_view, absl::string_view>& Lookup(
+ HeaderLines::size_type index) const {
+ QUICHE_DCHECK_LT(index, headers_->header_lines_.size());
+ const HeaderLineDescription& line = headers_->header_lines_[index];
+ const char* stream_begin = headers_->GetPtr(line.buffer_base_idx);
+ value_ = std::make_pair(
+ absl::string_view(stream_begin + line.first_char_idx, line.KeyLength()),
+ absl::string_view(stream_begin + line.value_begin_idx,
+ line.ValuesLength()));
+ return value_;
+ }
+
+ const BalsaHeaders* headers_;
+ HeaderLines::size_type idx_;
+ mutable std::pair<absl::string_view, absl::string_view> value_;
+};
+
+// A const iterator for all the header lines.
+class BalsaHeaders::const_header_lines_iterator
+ : public BalsaHeaders::iterator_base {
+ public:
+ const_header_lines_iterator() : iterator_base() {}
+
+ const_header_lines_iterator(const const_header_lines_iterator& it)
+ : iterator_base(it.headers_, it.idx_) {}
+
+ const_header_lines_iterator& operator++() {
+ iterator_base::increment();
+ return *this;
+ }
+
+ private:
+ friend class BalsaHeaders;
+
+ const_header_lines_iterator(const BalsaHeaders* headers,
+ HeaderLines::size_type index)
+ : iterator_base(headers, index) {}
+};
+
+// A const iterator that stops only on header lines for a particular key.
+class BalsaHeaders::const_header_lines_key_iterator
+ : public BalsaHeaders::iterator_base {
+ public:
+ const_header_lines_key_iterator& operator++() {
+ do {
+ iterator_base::increment();
+ } while (!AtEnd() && !absl::EqualsIgnoreCase(key_, (**this).first));
+ return *this;
+ }
+
+ // Only forward-iteration makes sense, so no operator-- defined.
+
+ private:
+ friend class BalsaHeaders;
+
+ const_header_lines_key_iterator(const BalsaHeaders* headers,
+ HeaderLines::size_type index,
+ absl::string_view key)
+ : iterator_base(headers, index), key_(key) {}
+
+ // Should only be used for creating an end iterator.
+ const_header_lines_key_iterator(const BalsaHeaders* headers,
+ HeaderLines::size_type index)
+ : iterator_base(headers, index) {}
+
+ bool AtEnd() const { return *this >= headers_->lines().end(); }
+
+ absl::string_view key_;
+};
+
+inline absl::iterator_range<BalsaHeaders::const_header_lines_iterator>
+BalsaHeaders::lines() const {
+ return {HeaderLinesBeginHelper<const_header_lines_iterator>(),
+ HeaderLinesEndHelper<const_header_lines_iterator>()};
+}
+
+inline absl::iterator_range<BalsaHeaders::const_header_lines_key_iterator>
+BalsaHeaders::lines(absl::string_view key) const {
+ return {GetIteratorForKey(key), header_lines_key_end()};
+}
+
+inline BalsaHeaders::const_header_lines_key_iterator
+BalsaHeaders::header_lines_key_end() const {
+ return HeaderLinesEndHelper<const_header_lines_key_iterator>();
+}
+
+inline void BalsaHeaders::erase(const const_header_lines_iterator& it) {
+ QUICHE_DCHECK_EQ(it.headers_, this);
+ QUICHE_DCHECK_LT(it.idx_, header_lines_.size());
+ QUICHE_DCHECK_GE(it.idx_, 0u);
+ header_lines_[it.idx_].skip = true;
+}
+
+template <typename Buffer>
+void BalsaHeaders::WriteToBuffer(Buffer* buffer, CaseOption case_option,
+ CoalesceOption coalesce_option) const {
+ // write the first line.
+ const absl::string_view firstline = first_line();
+ if (!firstline.empty()) {
+ buffer->WriteString(firstline);
+ }
+ buffer->WriteString("\r\n");
+ if (coalesce_option != CoalesceOption::kCoalesce) {
+ 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) {
+ continue;
+ }
+ const char* line_ptr = GetPtr(line.buffer_base_idx);
+ WriteHeaderLineToBuffer(
+ buffer,
+ absl::string_view(line_ptr + line.first_char_idx, line.KeyLength()),
+ absl::string_view(line_ptr + line.value_begin_idx,
+ line.ValuesLength()),
+ case_option);
+ }
+ } else {
+ WriteToBufferCoalescingMultivaluedHeaders(
+ buffer, multivalued_envoy_headers(), case_option);
+ }
+}
+
+inline void BalsaHeaders::GetValuesOfMultivaluedHeaders(
+ const MultivaluedHeadersSet& multivalued_headers,
+ MultivaluedHeadersValuesMap* multivalues) const {
+ multivalues->reserve(header_lines_.capacity());
+
+ // Find lines that need to be coalesced and store them in |multivalues|.
+ for (const auto& line : header_lines_) {
+ if (line.skip) {
+ continue;
+ }
+ const char* line_ptr = GetPtr(line.buffer_base_idx);
+ absl::string_view header_key =
+ absl::string_view(line_ptr + line.first_char_idx, line.KeyLength());
+ // If this is multivalued header, it may need to be coalesced.
+ if (multivalued_headers.contains(header_key)) {
+ absl::string_view header_value = absl::string_view(
+ line_ptr + line.value_begin_idx, line.ValuesLength());
+ // Add |header_value| to the vector of values for this |header_key|,
+ // therefore preserving the order of values for the same key.
+ (*multivalues)[header_key].push_back(header_value);
+ }
+ }
+}
+
+template <typename Buffer>
+void BalsaHeaders::WriteToBufferCoalescingMultivaluedHeaders(
+ Buffer* buffer, const MultivaluedHeadersSet& multivalued_headers,
+ CaseOption case_option) const {
+ MultivaluedHeadersValuesMap multivalues;
+ GetValuesOfMultivaluedHeaders(multivalued_headers, &multivalues);
+
+ // Write out header lines while coalescing those that need to be coalesced.
+ for (const auto& line : header_lines_) {
+ if (line.skip) {
+ continue;
+ }
+ const char* line_ptr = GetPtr(line.buffer_base_idx);
+ absl::string_view header_key =
+ absl::string_view(line_ptr + line.first_char_idx, line.KeyLength());
+ auto header_multivalue = multivalues.find(header_key);
+ // If current line doesn't need to be coalesced (as it is either not
+ // multivalue, or has just a single value so it equals to current line),
+ // then just write it out.
+ if (header_multivalue == multivalues.end() ||
+ header_multivalue->second.size() == 1) {
+ WriteHeaderLineToBuffer(buffer, header_key,
+ absl::string_view(line_ptr + line.value_begin_idx,
+ line.ValuesLength()),
+ case_option);
+ } else {
+ // If this line needs to be coalesced, then write all its values and clear
+ // them, so the subsequent same header keys will not be written.
+ if (!header_multivalue->second.empty()) {
+ WriteHeaderLineValuesToBuffer(buffer, header_key,
+ header_multivalue->second, case_option);
+ // Clear the multivalue list as it is already written out, so subsequent
+ // same header keys will not be written.
+ header_multivalue->second.clear();
+ }
+ }
+ }
+}
+
+template <typename IteratorType>
+const IteratorType BalsaHeaders::HeaderLinesBeginHelper() const {
+ if (header_lines_.empty()) {
+ return IteratorType(this, 0);
+ }
+ const HeaderLines::size_type header_lines_size = header_lines_.size();
+ for (HeaderLines::size_type i = 0; i < header_lines_size; ++i) {
+ if (header_lines_[i].skip == false) {
+ return IteratorType(this, i);
+ }
+ }
+ return IteratorType(this, 0);
+}
+
+template <typename IteratorType>
+const IteratorType BalsaHeaders::HeaderLinesEndHelper() const {
+ if (header_lines_.empty()) {
+ return IteratorType(this, 0);
+ }
+ const HeaderLines::size_type header_lines_size = header_lines_.size();
+ HeaderLines::size_type i = header_lines_size;
+ do {
+ --i;
+ if (header_lines_[i].skip == false) {
+ return IteratorType(this, i + 1);
+ }
+ } while (i != 0);
+ return IteratorType(this, 0);
+}
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_BALSA_BALSA_HEADERS_H_
diff --git a/quiche/common/balsa/balsa_visitor_interface.h b/quiche/common/balsa/balsa_visitor_interface.h
new file mode 100644
index 0000000..a8fcce3
--- /dev/null
+++ b/quiche/common/balsa/balsa_visitor_interface.h
@@ -0,0 +1,172 @@
+// Copyright 2021 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.
+
+#ifndef QUICHE_COMMON_BALSA_BALSA_VISITOR_INTERFACE_H_
+#define QUICHE_COMMON_BALSA_BALSA_VISITOR_INTERFACE_H_
+
+#include <cstddef>
+
+#include "quiche/common/balsa/balsa_enums.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quiche {
+
+class BalsaHeaders;
+
+// By default the BalsaFrame instantiates a class derived from this interface
+// which does absolutely nothing. If you'd prefer to have interesting
+// functionality execute when any of the below functions are called by the
+// BalsaFrame, then you should subclass it, and set an instantiation of your
+// subclass as the current visitor for the BalsaFrame class using
+// BalsaFrame::set_visitor().
+class QUICHE_EXPORT_PRIVATE BalsaVisitorInterface {
+ public:
+ virtual ~BalsaVisitorInterface() {}
+
+ // Summary:
+ // This is how the BalsaFrame passes you the raw input which it knows to
+ // be a part of the body. To be clear, every byte of the Balsa which isn't
+ // part of the header (or its framing), or trailers will be passed through
+ // this function. This includes data as well as chunking framing.
+ // Arguments:
+ // input - contains the bytes available for read.
+ // size - contains the number of bytes it is safe to read from input.
+ virtual void OnRawBodyInput(const char* input, size_t size) = 0;
+
+ // Summary:
+ // This is like OnRawBodyInput, but it will only include those parts of
+ // the body which would be stored by a program such as wget, i.e. the bytes
+ // indicating chunking will have been removed. Trailers will not be
+ // passed in through this function-- they'll be passed in through
+ // OnTrailerInput.
+ // Arguments:
+ // input - contains the bytes available for read.
+ // size - contains the number of bytes it is safe to read from input.
+ virtual void OnBodyChunkInput(const char* input, size_t size) = 0;
+
+ // Summary:
+ // BalsaFrame passes the raw header data through this function. This is
+ // not cleaned up in any way.
+ // Arguments:
+ // input - contains the bytes available for read.
+ // size - contains the number of bytes it is safe to read from input.
+ virtual void OnHeaderInput(const char* input, size_t size) = 0;
+
+ // Summary:
+ // BalsaFrame passes the raw trailer data through this function. This is
+ // not cleaned up in any way. Note that trailers only occur in a message
+ // if there was a chunked encoding, and not always then.
+ //
+ // Arguments:
+ // input - contains the bytes available for read.
+ // size - contains the number of bytes it is safe to read from input.
+ virtual void OnTrailerInput(const char* input, size_t size) = 0;
+
+ // Summary:
+ // Since the BalsaFrame already has to parse the headers in order to
+ // determine proper framing, it might as well pass the parsed and
+ // cleaned-up results to whatever might need it. This function exists for
+ // that purpose-- parsed headers are passed into this function.
+ // Arguments:
+ // headers - contains the parsed headers in the order in which
+ // they occurred in the header.
+ virtual void ProcessHeaders(const BalsaHeaders& headers) = 0;
+
+ // Summary:
+ // Since the BalsaFrame already has to parse the trailer, it might as well
+ // pass the parsed and cleaned-up results to whatever might need it.
+ // This function exists for that purpose-- parsed trailer is passed into
+ // this function. This will not be called if the trailer_ object is
+ // not set in the framer, even if trailer exists in request/response.
+ // Arguments:
+ // trailer - contains the parsed headers in the order in which
+ // they occurred in the trailer.
+ virtual void ProcessTrailers(const BalsaHeaders& trailer) = 0;
+
+ // Summary:
+ // Called when the first line of the message is parsed, in this case, for a
+ // request.
+ // Arguments:
+ // line_input - pointer to the beginning of the first line string.
+ // line_length - length of the first line string. (i.e. the numer of
+ // bytes it is safe to read from line_ptr)
+ // method_input - pointer to the beginning of the method string
+ // method_length - length of the method string (i.e. the number
+ // of bytes it is safe to read from method_input)
+ // request_uri_input - pointer to the beginning of the request uri
+ // string.
+ // request_uri_length - length of the method string (i.e. the number
+ // of bytes it is safe to read from method_input)
+ // version_input - pointer to the beginning of the version string.
+ // version_length - length of the version string (i.e. the number
+ // of bytes it i ssafe to read from version_input)
+ virtual void OnRequestFirstLineInput(
+ const char* line_input, size_t line_length, const char* method_input,
+ size_t method_length, const char* request_uri_input,
+ size_t request_uri_length, const char* version_input,
+ size_t version_length) = 0;
+
+ // Summary:
+ // Called when the first line of the message is parsed, in this case, for a
+ // response.
+ // Arguments:
+ // line_input - pointer to the beginning of the first line string.
+ // line_length - length of the first line string. (i.e. the numer of
+ // bytes it is safe to read from line_ptr)
+ // version_input - pointer to the beginning of the version string.
+ // version_length - length of the version string (i.e. the number
+ // of bytes it i ssafe to read from version_input)
+ // status_input - pointer to the beginning of the status string
+ // status_length - length of the status string (i.e. the number
+ // of bytes it is safe to read from status_input)
+ // reason_input - pointer to the beginning of the reason string
+ // reason_length - length of the reason string (i.e. the number
+ // of bytes it is safe to read from reason_input)
+ virtual void OnResponseFirstLineInput(
+ const char* line_input, size_t line_length, const char* version_input,
+ size_t version_length, const char* status_input, size_t status_length,
+ const char* reason_input, size_t reason_length) = 0;
+
+ // Called when a chunk length is parsed.
+ // Arguments:
+ // chunk length - the length of the next incoming chunk.
+ virtual void OnChunkLength(size_t chunk_length) = 0;
+
+ // Summary:
+ // BalsaFrame passes the raw chunk extension data through this function.
+ // The data is not cleaned up at all.
+ //
+ // Arguments:
+ // input - contains the bytes available for read.
+ // size - contains the number of bytes it is safe to read from input.
+ virtual void OnChunkExtensionInput(const char* input, size_t size) = 0;
+
+ // Summary:
+ // Called when the header is framed and processed.
+ virtual void HeaderDone() = 0;
+
+ // Summary:
+ // Called when the 100 Continue headers are framed and processed.
+ virtual void ContinueHeaderDone() = 0;
+
+ // Summary:
+ // Called when the message is framed and processed.
+ virtual void MessageDone() = 0;
+
+ // Summary:
+ // Called when an error is detected
+ // Arguments:
+ // error_code - the error which is to be reported
+ virtual void HandleError(BalsaFrameEnums::ErrorCode error_code) = 0;
+
+ // Summary:
+ // Called when something meriting a warning is detected
+ // Arguments:
+ // error_code - the warning which is to be reported
+ virtual void HandleWarning(BalsaFrameEnums::ErrorCode error_code) = 0;
+};
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_BALSA_BALSA_VISITOR_INTERFACE_H_
diff --git a/quiche/common/balsa/framer_interface.h b/quiche/common/balsa/framer_interface.h
new file mode 100644
index 0000000..68ee3ca
--- /dev/null
+++ b/quiche/common/balsa/framer_interface.h
@@ -0,0 +1,24 @@
+// Copyright 2021 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.
+
+#ifndef QUICHE_COMMON_BALSA_FRAMER_INTERFACE_H_
+#define QUICHE_COMMON_BALSA_FRAMER_INTERFACE_H_
+
+#include <cstddef>
+
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quiche {
+
+// A minimal interface supported by BalsaFrame and other framer types. For use
+// in HttpReader.
+class QUICHE_EXPORT_PRIVATE FramerInterface {
+ public:
+ virtual ~FramerInterface() {}
+ virtual size_t ProcessInput(const char* input, size_t length) = 0;
+};
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_BALSA_FRAMER_INTERFACE_H_
diff --git a/quiche/common/balsa/header_api.h b/quiche/common/balsa/header_api.h
new file mode 100644
index 0000000..c8ec740
--- /dev/null
+++ b/quiche/common/balsa/header_api.h
@@ -0,0 +1,274 @@
+// 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.
+
+#ifndef QUICHE_COMMON_BALSA_HEADER_API_H_
+#define QUICHE_COMMON_BALSA_HEADER_API_H_
+
+#include <cstddef>
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_lower_case_string.h"
+
+namespace quiche {
+
+// An API so we can reuse functions for BalsaHeaders and Envoy's HeaderMap.
+// Contains only const member functions, so it can wrap const HeaderMaps;
+// non-const functions are in HeaderApi.
+//
+// Depending on the implementation, the headers may act like HTTP/1 headers
+// (BalsaHeaders) or HTTP/2 headers (HeaderMap). For HTTP-version-specific
+// headers or pseudoheaders like "host" or ":authority", use this API's
+// implementation-independent member functions, like Authority(). Looking those
+// headers up by name is deprecated and may QUICHE_DCHECK-fail.
+// For the differences between HTTP/1 and HTTP/2 headers, see RFC 7540:
+// https://tools.ietf.org/html/rfc7540#section-8.1.2
+//
+// Operations on header keys are case-insensitive while operations on header
+// values are case-sensitive.
+//
+// Some methods have overloads which accept Envoy-style LowerCaseStrings. Often
+// these keys are accessible from Envoy::Http::Headers::get().SomeHeader,
+// already lowercaseified. It's faster to avoid converting them to and from
+// lowercase. Additionally, some implementations of ConstHeaderApi might take
+// advantage of a constant-time lookup for inlined headers.
+class QUICHE_EXPORT_PRIVATE ConstHeaderApi {
+ public:
+ virtual ~ConstHeaderApi() {}
+
+ // Determine whether the headers are empty.
+ virtual bool IsEmpty() const = 0;
+
+ // Returns the header entry for the first instance with key |key|
+ // If header isn't present, returns absl::string_view().
+ virtual absl::string_view GetHeader(absl::string_view key) const = 0;
+
+ virtual absl::string_view GetHeader(const QuicheLowerCaseString& key) const {
+ // Default impl for BalsaHeaders, etc.
+ return GetHeader(key.get());
+ }
+
+ // Collects all of the header entries with key |key| and returns them in |out|
+ // Headers are returned in the order they are inserted.
+ virtual void GetAllOfHeader(absl::string_view key,
+ std::vector<absl::string_view>* out) const = 0;
+ virtual std::vector<absl::string_view> GetAllOfHeader(
+ absl::string_view key) const {
+ std::vector<absl::string_view> out;
+ GetAllOfHeader(key, &out);
+ return out;
+ }
+ virtual void GetAllOfHeader(const QuicheLowerCaseString& key,
+ std::vector<absl::string_view>* out) const {
+ return GetAllOfHeader(key.get(), out);
+ }
+
+ // Determine if a given header is present.
+ virtual bool HasHeader(absl::string_view key) const = 0;
+
+ // Determines if a given header is present with non-empty value.
+ virtual bool HasNonEmptyHeader(absl::string_view key) const = 0;
+
+ // Goes through all headers with key |key| and checks to see if one of the
+ // values is |value|. Returns true if there are headers with the desired key
+ // and value, false otherwise.
+ virtual bool HeaderHasValue(absl::string_view key,
+ absl::string_view value) const = 0;
+
+ // Same as above, but value is treated as case insensitive.
+ virtual bool HeaderHasValueIgnoreCase(absl::string_view key,
+ absl::string_view value) const = 0;
+
+ // Joins all values for header entries with `key` into a comma-separated
+ // string. Headers are returned in the order they are inserted.
+ virtual std::string GetAllOfHeaderAsString(absl::string_view key) const = 0;
+ virtual std::string GetAllOfHeaderAsString(
+ const QuicheLowerCaseString& key) const {
+ return GetAllOfHeaderAsString(key.get());
+ }
+
+ // Returns true if we have at least one header with given prefix
+ // [case insensitive]. Currently for test use only.
+ virtual bool HasHeadersWithPrefix(absl::string_view key) const = 0;
+
+ // Returns the key value pairs for all headers where the header key begins
+ // with the specified prefix.
+ // Headers are returned in the order they are inserted.
+ virtual void GetAllOfHeaderWithPrefix(
+ absl::string_view prefix,
+ std::vector<std::pair<absl::string_view, absl::string_view>>* out)
+ const = 0;
+
+ // Returns the key value pairs for all headers in this object. If 'limit' is
+ // >= 0, return at most 'limit' headers.
+ virtual void GetAllHeadersWithLimit(
+ std::vector<std::pair<absl::string_view, absl::string_view>>* out,
+ int limit) const = 0;
+
+ // Returns a textual representation of the header object. The format of the
+ // string may depend on the underlying implementation.
+ virtual std::string DebugString() const = 0;
+
+ // Applies the argument function to each header line. If the argument
+ // function returns false, iteration stops and ForEachHeader returns false;
+ // otherwise, ForEachHeader returns true.
+ virtual bool ForEachHeader(std::function<bool(const absl::string_view key,
+ const absl::string_view value)>
+ fn) const = 0;
+
+ // Returns the upper bound byte size of the headers. This can be used to size
+ // a Buffer when serializing headers.
+ virtual size_t GetSizeForWriteBuffer() const = 0;
+
+ // Returns the response code for response headers. If no status code exists,
+ // the return value is implementation-specific.
+ virtual absl::string_view response_code() const = 0;
+
+ // Returns the response code for response headers or 0 if no status code
+ // exists.
+ virtual size_t parsed_response_code() const = 0;
+
+ // Returns the response reason phrase; the stored one for HTTP/1 headers, or a
+ // phrase determined from the response code for HTTP/2 headers..
+ virtual absl::string_view response_reason_phrase() const = 0;
+
+ // Return the HTTP first line of this request, generally of the format:
+ // GET /path/ HTTP/1.1
+ // TODO(b/110421449): deprecate this method.
+ virtual std::string first_line_of_request() const = 0;
+
+ // Return the method for this request, such as GET or POST.
+ virtual absl::string_view request_method() const = 0;
+
+ // Return the request URI from the first line of this request, such as
+ // "/path/".
+ virtual absl::string_view request_uri() const = 0;
+
+ // Return the version portion of the first line of this request, such as
+ // "HTTP/1.1".
+ // TODO(b/110421449): deprecate this method.
+ virtual absl::string_view request_version() const = 0;
+
+ virtual absl::string_view response_version() const = 0;
+
+ // Returns the authority portion of a request, or an empty string if missing.
+ // This is the value of the host header for HTTP/1 headers and the value of
+ // the :authority pseudo-header for HTTP/2 headers.
+ virtual absl::string_view Authority() const = 0;
+
+ // Call the provided function on the cookie, avoiding
+ // copies if possible. The cookie is the value of the Cookie header; for
+ // HTTP/2 headers, if there are multiple Cookie headers, they will be joined
+ // by "; ", per go/rfc/7540#section-8.1.2.5. If there is no Cookie header,
+ // cookie.data() will be nullptr. The lifetime of the cookie isn't guaranteed
+ // to extend beyond this call.
+ virtual void ApplyToCookie(
+ std::function<void(absl::string_view cookie)> f) const = 0;
+
+ virtual size_t content_length() const = 0;
+ virtual bool content_length_valid() const = 0;
+
+ // TODO(b/118501626): Add functions for working with other headers and
+ // pseudo-headers whose presence or value depends on HTTP version, including:
+ // :method, :scheme, :path, connection, and cookie.
+};
+
+// An API so we can reuse functions for BalsaHeaders and Envoy's HeaderMap.
+// Inherits const functions from ConstHeaderApi and adds non-const functions,
+// for use with non-const HeaderMaps.
+//
+// For HTTP-version-specific headers and pseudo-headers, the same caveats apply
+// as with ConstHeaderApi.
+//
+// Operations on header keys are case-insensitive while operations on header
+// values are case-sensitive.
+class QUICHE_EXPORT_PRIVATE HeaderApi : public virtual ConstHeaderApi {
+ public:
+ // Replaces header entries with key |key| if they exist, or appends
+ // a new header if none exist.
+ virtual void ReplaceOrAppendHeader(absl::string_view key,
+ absl::string_view value) = 0;
+
+ // Removes all headers in given set of |keys| at once
+ virtual void RemoveAllOfHeaderInList(
+ const std::vector<absl::string_view>& keys) = 0;
+
+ // Removes all headers with key |key|.
+ virtual void RemoveAllOfHeader(absl::string_view key) = 0;
+
+ // Append a new header entry to the header object with key |key| and value
+ // |value|.
+ virtual void AppendHeader(absl::string_view key, absl::string_view value) = 0;
+
+ // Removes all headers starting with 'key' [case insensitive]
+ virtual void RemoveAllHeadersWithPrefix(absl::string_view key) = 0;
+
+ // Appends ',value' to an existing header named 'key'. If no header with the
+ // correct key exists, it will call AppendHeader(key, value). Calling this
+ // function on a key which exists several times in the headers will produce
+ // unpredictable results.
+ virtual void AppendToHeader(absl::string_view key,
+ absl::string_view value) = 0;
+
+ // Appends ', value' to an existing header named 'key'. If no header with the
+ // correct key exists, it will call AppendHeader(key, value). Calling this
+ // function on a key which exists several times in the headers will produce
+ // unpredictable results.
+ virtual void AppendToHeaderWithCommaAndSpace(absl::string_view key,
+ absl::string_view value) = 0;
+
+ // Set the header or pseudo-header corresponding to the authority portion of a
+ // request: host for HTTP/1 headers, or :authority for HTTP/2 headers.
+ virtual void ReplaceOrAppendAuthority(absl::string_view value) = 0;
+ virtual void RemoveAuthority() = 0;
+
+ // These set portions of the first line for HTTP/1 headers, or the
+ // corresponding pseudo-headers for HTTP/2 headers.
+ virtual void SetRequestMethod(absl::string_view method) = 0;
+ virtual void SetResponseCode(absl::string_view code) = 0;
+ // As SetResponseCode, but slightly faster for BalsaHeaders if the caller
+ // represents the response code as an integer and not a string.
+ virtual void SetParsedResponseCodeAndUpdateFirstline(
+ size_t parsed_response_code) = 0;
+
+ // Sets the request URI.
+ //
+ // For HTTP/1 headers, sets the request URI portion of the first line (the
+ // second token). Doesn't parse the URI; leaves the Host header unchanged.
+ //
+ // For HTTP/2 headers, sets the :path pseudo-header, and also :scheme and
+ // :authority if they're present in the URI; otherwise, leaves :scheme and
+ // :authority unchanged.
+ //
+ // The caller is responsible for verifying that the URI is in a valid format.
+ virtual void SetRequestUri(absl::string_view uri) = 0;
+
+ // These are only meaningful for HTTP/1 headers; for HTTP/2 headers, they do
+ // nothing.
+ virtual void SetRequestVersion(absl::string_view version) = 0;
+ virtual void SetResponseVersion(absl::string_view version) = 0;
+ virtual void SetResponseReasonPhrase(absl::string_view reason_phrase) = 0;
+
+ // SetContentLength, SetTransferEncodingToChunkedAndClearContentLength, and
+ // SetNoTransferEncoding modifies the header object to use
+ // content-length and transfer-encoding headers in a consistent
+ // manner. They set all internal flags and status, if applicable, so client
+ // can get a consistent view from various accessors.
+ virtual void SetContentLength(size_t length) = 0;
+ // Sets transfer-encoding to chunked and updates internal state.
+ virtual void SetTransferEncodingToChunkedAndClearContentLength() = 0;
+ // Removes transfer-encoding headers and updates internal state.
+ virtual void SetNoTransferEncoding() = 0;
+
+ // If true, QUICHE_BUG if a header that starts with an invalid prefix is
+ // explicitly set. Not implemented for Envoy headers; can only be set false.
+ virtual void set_enforce_header_policy(bool enforce) = 0;
+};
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_BALSA_HEADER_API_H_
diff --git a/quiche/common/balsa/header_properties.cc b/quiche/common/balsa/header_properties.cc
new file mode 100644
index 0000000..415bc4d
--- /dev/null
+++ b/quiche/common/balsa/header_properties.cc
@@ -0,0 +1,95 @@
+#include "quiche/common/balsa/header_properties.h"
+
+#include <array>
+
+#include "absl/container/flat_hash_set.h"
+#include "absl/strings/string_view.h"
+#include "quiche/common/quiche_text_utils.h"
+
+namespace quiche::header_properties {
+
+namespace {
+
+using MultivaluedHeadersSet =
+ absl::flat_hash_set<absl::string_view, StringPieceCaseHash,
+ StringPieceCaseEqual>;
+
+MultivaluedHeadersSet* buildMultivaluedHeaders() {
+ return new MultivaluedHeadersSet({
+ "accept",
+ "accept-charset",
+ "accept-encoding",
+ "accept-language",
+ "accept-ranges",
+ // The follow four headers are all CORS standard headers
+ "access-control-allow-headers",
+ "access-control-allow-methods",
+ "access-control-expose-headers",
+ "access-control-request-headers",
+ "allow",
+ "cache-control",
+ // IETF draft makes this have cache-control syntax
+ "cdn-cache-control",
+ "connection",
+ "content-encoding",
+ "content-language",
+ "expect",
+ "if-match",
+ "if-none-match",
+ // See RFC 5988 section 5
+ "link",
+ "pragma",
+ "proxy-authenticate",
+ "te",
+ // Used in the opening handshake of the WebSocket protocol.
+ "sec-websocket-extensions",
+ // Not mentioned in RFC 2616, but it can have multiple values.
+ "set-cookie",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ "vary",
+ "via",
+ "warning",
+ "www-authenticate",
+ // De facto standard not in the RFCs
+ "x-forwarded-for",
+ // Internal Google usage gives this cache-control syntax
+ "x-go" /**/ "ogle-cache-control",
+ });
+}
+
+std::array<bool, 256> buildInvalidCharLookupTable() {
+ std::array<bool, 256> invalidCharTable;
+ invalidCharTable.fill(false);
+ for (char c : kInvalidHeaderCharList) {
+ invalidCharTable[c] = true;
+ }
+ return invalidCharTable;
+}
+
+} // anonymous namespace
+
+bool IsMultivaluedHeader(absl::string_view header) {
+ static const MultivaluedHeadersSet* const multivalued_headers =
+ buildMultivaluedHeaders();
+ return multivalued_headers->contains(header);
+}
+
+bool IsInvalidHeaderChar(char c) {
+ static const std::array<bool, 256> invalidCharTable =
+ buildInvalidCharLookupTable();
+
+ return invalidCharTable[c];
+}
+
+bool HasInvalidHeaderChars(absl::string_view value) {
+ for (const char c : value) {
+ if (IsInvalidHeaderChar(c)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace quiche::header_properties
diff --git a/quiche/common/balsa/header_properties.h b/quiche/common/balsa/header_properties.h
new file mode 100644
index 0000000..a747fab
--- /dev/null
+++ b/quiche/common/balsa/header_properties.h
@@ -0,0 +1,35 @@
+// 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.
+
+#ifndef QUICHE_COMMON_BALSA_HEADER_PROPERTIES_H_
+#define QUICHE_COMMON_BALSA_HEADER_PROPERTIES_H_
+
+#include "absl/strings/string_view.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quiche::header_properties {
+
+// Returns true if RFC 2616 Section 14 (or other relevant standards or
+// practices) indicates that header can have multiple values. Note that nothing
+// stops clients from sending multiple values of other headers, so this may not
+// be perfectly reliable in practice.
+QUICHE_EXPORT_PRIVATE bool IsMultivaluedHeader(absl::string_view header);
+
+// An array of characters that are invalid in HTTP header field values,
+// according to RFC 7230 Section 3.2. Valid low characters not in this array
+// are \t (0x09), \n (0x0A), and \r (0x0D).
+// Note that HTTP header field names are even more restrictive.
+inline constexpr char kInvalidHeaderCharList[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0B,
+ 0x0C, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x7F};
+
+// Returns true if the given `c` is invalid in a header field or the `value` has
+// invalid characters.
+QUICHE_EXPORT_PRIVATE bool IsInvalidHeaderChar(char c);
+QUICHE_EXPORT_PRIVATE bool HasInvalidHeaderChars(absl::string_view value);
+
+} // namespace quiche::header_properties
+
+#endif // QUICHE_COMMON_BALSA_HEADER_PROPERTIES_H_
diff --git a/quiche/common/balsa/header_properties_test.cc b/quiche/common/balsa/header_properties_test.cc
new file mode 100644
index 0000000..ffb6b83
--- /dev/null
+++ b/quiche/common/balsa/header_properties_test.cc
@@ -0,0 +1,49 @@
+#include "quiche/common/balsa/header_properties.h"
+
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace quiche::header_properties::test {
+namespace {
+
+TEST(HeaderPropertiesTest, IsMultivaluedHeaderIsCaseInsensitive) {
+ EXPECT_TRUE(IsMultivaluedHeader("content-encoding"));
+ EXPECT_TRUE(IsMultivaluedHeader("Content-Encoding"));
+ EXPECT_TRUE(IsMultivaluedHeader("set-cookie"));
+ EXPECT_TRUE(IsMultivaluedHeader("sEt-cOOkie"));
+ EXPECT_TRUE(IsMultivaluedHeader("X-Goo" /**/ "gle-Cache-Control"));
+ EXPECT_TRUE(IsMultivaluedHeader("access-control-expose-HEADERS"));
+
+ EXPECT_FALSE(IsMultivaluedHeader("set-cook"));
+ EXPECT_FALSE(IsMultivaluedHeader("content-length"));
+ EXPECT_FALSE(IsMultivaluedHeader("Content-Length"));
+}
+
+TEST(HeaderPropertiesTest, IsInvalidHeaderChar) {
+ EXPECT_TRUE(IsInvalidHeaderChar(0x00));
+ EXPECT_TRUE(IsInvalidHeaderChar(0x06));
+ EXPECT_TRUE(IsInvalidHeaderChar(0x1F));
+ EXPECT_TRUE(IsInvalidHeaderChar(0x7F));
+
+ EXPECT_FALSE(IsInvalidHeaderChar(' '));
+ EXPECT_FALSE(IsInvalidHeaderChar('\t'));
+ EXPECT_FALSE(IsInvalidHeaderChar('\r'));
+ EXPECT_FALSE(IsInvalidHeaderChar('\n'));
+ EXPECT_FALSE(IsInvalidHeaderChar(0x42));
+}
+
+TEST(HeaderPropertiesTest, HasInvalidHeaderChars) {
+ const char with_null[] = "Here's l\x00king at you, kid";
+ EXPECT_TRUE(HasInvalidHeaderChars(std::string(with_null, sizeof(with_null))));
+ EXPECT_TRUE(HasInvalidHeaderChars("Why's \x06 afraid of \x07? \x07\x08\x09"));
+ EXPECT_TRUE(HasInvalidHeaderChars("\x1Flower power"));
+ EXPECT_TRUE(HasInvalidHeaderChars("\x7Flowers more powers"));
+
+ EXPECT_FALSE(HasInvalidHeaderChars("Plenty of space"));
+ EXPECT_FALSE(HasInvalidHeaderChars("Keeping \tabs"));
+ EXPECT_FALSE(HasInvalidHeaderChars("Al\right"));
+ EXPECT_FALSE(HasInvalidHeaderChars("\new day"));
+ EXPECT_FALSE(HasInvalidHeaderChars("\x42 is a nice character"));
+}
+
+} // namespace
+} // namespace quiche::header_properties::test
diff --git a/quiche/common/balsa/http_validation_policy.cc b/quiche/common/balsa/http_validation_policy.cc
new file mode 100644
index 0000000..b0418ec
--- /dev/null
+++ b/quiche/common/balsa/http_validation_policy.cc
@@ -0,0 +1,30 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/balsa/http_validation_policy.h"
+
+#include <tuple>
+
+#include "quiche/common/platform/api/quiche_logging.h"
+
+namespace quiche {
+
+HttpValidationPolicy::HttpValidationPolicy(bool enforce_header_keys,
+ bool enforce_all)
+ : enforce_header_keys_(enforce_header_keys), enforce_all_(enforce_all) {
+ if (enforce_all_) {
+ QUICHE_DCHECK(enforce_header_keys_);
+ }
+}
+
+HttpValidationPolicy HttpValidationPolicy::CreateDefault() {
+ return HttpValidationPolicy(true, false);
+}
+
+bool HttpValidationPolicy::operator==(const HttpValidationPolicy& other) const {
+ return std::tie(enforce_header_keys_, enforce_all_) ==
+ std::tie(other.enforce_header_keys_, other.enforce_all_);
+}
+
+} // namespace quiche
diff --git a/quiche/common/balsa/http_validation_policy.h b/quiche/common/balsa/http_validation_policy.h
new file mode 100644
index 0000000..3b75ec7
--- /dev/null
+++ b/quiche/common/balsa/http_validation_policy.h
@@ -0,0 +1,62 @@
+// 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.
+
+#ifndef QUICHE_COMMON_BALSA_HTTP_VALIDATION_POLICY_H_
+#define QUICHE_COMMON_BALSA_HTTP_VALIDATION_POLICY_H_
+
+#include <ostream>
+
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quiche {
+
+// An HttpValidationPolicy captures policy choices affecting parsing of HTTP
+// requests. It offers individual Boolean member functions to be consulted
+// during the parsing of an HTTP request.
+class QUICHE_EXPORT_PRIVATE HttpValidationPolicy {
+ public:
+ HttpValidationPolicy(bool enforce_header_keys, bool enforce_all);
+
+ static HttpValidationPolicy CreateDefault();
+
+ // A header name has to be drawn from a set of allowed characters.
+ bool enforce_header_characters() const { return enforce_header_keys_; }
+
+ // https://tools.ietf.org/html/rfc7230#section-3.2.4 deprecates "folding"
+ // of long header lines onto continuation lines.
+ bool disallow_header_continuation_lines() const { return enforce_all_; }
+
+ // A valid header line requires a header name and a colon.
+ bool require_header_colon() const { return enforce_all_; }
+
+ // https://tools.ietf.org/html/rfc7230#section-3.3.2 disallows multiple
+ // Content-Length header fields with the same value.
+ bool disallow_multiple_content_length() const { return enforce_all_; }
+
+ // https://tools.ietf.org/html/rfc7230#section-3.3.2 disallows
+ // Transfer-Encoding and Content-Length header fields together.
+ bool disallow_transfer_encoding_with_content_length() const {
+ return enforce_all_;
+ }
+
+ bool operator==(const HttpValidationPolicy& other) const;
+
+ friend QUICHE_EXPORT_PRIVATE std::ostream& operator<<(
+ std::ostream& os, const HttpValidationPolicy& policy) {
+ os << "HttpValidationPolicy(enforce_header_keys_="
+ << policy.enforce_header_keys_
+ << ", enforce_all_=" << policy.enforce_all_ << ")";
+ return os;
+ }
+
+ private:
+ // Enforce more standard-compliant parsing of HTTP headers.
+ bool enforce_header_keys_;
+ // Enforce "everything": set for strictest possible parsing.
+ bool enforce_all_;
+};
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_BALSA_HTTP_VALIDATION_POLICY_H_
diff --git a/quiche/common/balsa/noop_balsa_visitor.h b/quiche/common/balsa/noop_balsa_visitor.h
new file mode 100644
index 0000000..b107dbd
--- /dev/null
+++ b/quiche/common/balsa/noop_balsa_visitor.h
@@ -0,0 +1,57 @@
+// Copyright 2021 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.
+
+#ifndef QUICHE_COMMON_BALSA_NOOP_BALSA_VISITOR_H_
+#define QUICHE_COMMON_BALSA_NOOP_BALSA_VISITOR_H_
+
+#include <cstddef>
+
+#include "quiche/common/balsa/balsa_visitor_interface.h"
+#include "quiche/common/platform/api/quiche_export.h"
+
+namespace quiche {
+
+class BalsaHeaders;
+
+// Provides empty BalsaVisitorInterface overrides for convenience.
+// Intended to be used as a base class for BalsaVisitorInterface subclasses that
+// only need to override a small number of methods.
+class QUICHE_EXPORT_PRIVATE NoOpBalsaVisitor : public BalsaVisitorInterface {
+ public:
+ NoOpBalsaVisitor() = default;
+
+ NoOpBalsaVisitor(const NoOpBalsaVisitor&) = delete;
+ NoOpBalsaVisitor& operator=(const NoOpBalsaVisitor&) = delete;
+
+ ~NoOpBalsaVisitor() override {}
+
+ void OnRawBodyInput(const char* /*input*/, size_t /*size*/) override {}
+ void OnBodyChunkInput(const char* /*input*/, size_t /*size*/) override {}
+ void OnHeaderInput(const char* /*input*/, size_t /*size*/) override {}
+ void OnTrailerInput(const char* /*input*/, size_t /*size*/) override {}
+ void ProcessHeaders(const BalsaHeaders& /*headers*/) override {}
+ void ProcessTrailers(const BalsaHeaders& /*trailer*/) override {}
+
+ void OnRequestFirstLineInput(
+ const char* /*line_input*/, size_t /*line_length*/,
+ const char* /*method_input*/, size_t /*method_length*/,
+ const char* /*request_uri_input*/, size_t /*request_uri_length*/,
+ const char* /*version_input*/, size_t /*version_length*/) override {}
+ void OnResponseFirstLineInput(
+ const char* /*line_input*/, size_t /*line_length*/,
+ const char* /*version_input*/, size_t /*version_length*/,
+ const char* /*status_input*/, size_t /*status_length*/,
+ const char* /*reason_input*/, size_t /*reason_length*/) override {}
+ void OnChunkLength(size_t /*chunk_length*/) override {}
+ void OnChunkExtensionInput(const char* /*input*/, size_t /*size*/) override {}
+ void ContinueHeaderDone() override {}
+ void HeaderDone() override {}
+ void MessageDone() override {}
+ void HandleError(BalsaFrameEnums::ErrorCode /*error_code*/) override {}
+ void HandleWarning(BalsaFrameEnums::ErrorCode /*error_code*/) override {}
+};
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_BALSA_NOOP_BALSA_VISITOR_H_
diff --git a/quiche/common/balsa/simple_buffer.cc b/quiche/common/balsa/simple_buffer.cc
new file mode 100644
index 0000000..e441422
--- /dev/null
+++ b/quiche/common/balsa/simple_buffer.cc
@@ -0,0 +1,159 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/balsa/simple_buffer.h"
+
+#include <cstring>
+#include <memory>
+
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+
+namespace quiche {
+
+static const int kInitialSimpleBufferSize = 10;
+
+SimpleBuffer::SimpleBuffer()
+ : storage_(new char[kInitialSimpleBufferSize]),
+ write_idx_(0),
+ read_idx_(0),
+ storage_size_(kInitialSimpleBufferSize) {}
+
+SimpleBuffer::SimpleBuffer(int size)
+ : write_idx_(0), read_idx_(0), storage_size_(size) {
+ // Callers may try to allocate overly large blocks, but negative sizes are
+ // obviously wrong.
+ QUICHE_CHECK_GE(size, 0);
+ storage_ = new char[size];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int SimpleBuffer::Write(const char* bytes, int size) {
+ if (size < 0) {
+ QUICHE_BUG(simple_buffer_write_negative_size)
+ << "size must not be negative: " << size;
+ return 0;
+ }
+
+ bool has_room = ((storage_size_ - write_idx_) >= size);
+ if (!has_room) {
+ Reserve(size);
+ }
+ memcpy(storage_ + write_idx_, bytes, size);
+ AdvanceWritablePtr(size);
+ return size;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int SimpleBuffer::Read(char* bytes, int size) {
+ if (size < 0) {
+ QUICHE_BUG(simple_buffer_read_negative_size)
+ << "size must not be negative: " << size;
+ return 0;
+ }
+
+ char* read_ptr = nullptr;
+ int read_size = 0;
+ GetReadablePtr(&read_ptr, &read_size);
+ if (read_size > size) {
+ read_size = size;
+ }
+ memcpy(bytes, read_ptr, read_size);
+ AdvanceReadablePtr(read_size);
+ return read_size;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Attempts to reserve a contiguous block of buffer space either by reclaiming
+// consumed data or by allocating a larger buffer.
+void SimpleBuffer::Reserve(int size) {
+ if (size < 0) {
+ QUICHE_BUG(simple_buffer_reserve_negative_size)
+ << "size must not be negative: " << size;
+ return;
+ }
+
+ if (size == 0 || storage_size_ - write_idx_ >= size) {
+ return;
+ }
+
+ char* read_ptr = nullptr;
+ int read_size = 0;
+ GetReadablePtr(&read_ptr, &read_size);
+
+ if (read_size + size <= storage_size_) {
+ // Can reclaim space from consumed bytes by shifting.
+ memmove(storage_, read_ptr, read_size);
+ read_idx_ = 0;
+ write_idx_ = read_size;
+ return;
+ }
+
+ // The new buffer needs to be at least `read_size + size` bytes.
+ // At least double the buffer to amortize allocation costs.
+ int new_storage_size = 2 * storage_size_;
+ if (new_storage_size < size + read_size) {
+ new_storage_size = size + read_size;
+ }
+
+ char* new_storage = new char[new_storage_size];
+ memcpy(new_storage, read_ptr, read_size);
+ delete[] storage_;
+
+ read_idx_ = 0;
+ write_idx_ = read_size;
+ storage_ = new_storage;
+ storage_size_ = new_storage_size;
+}
+
+void SimpleBuffer::AdvanceReadablePtr(int amount_to_advance) {
+ if (amount_to_advance < 0) {
+ QUICHE_BUG(simple_buffer_advance_read_negative_arg)
+ << "amount_to_advance must not be negative: " << amount_to_advance;
+ return;
+ }
+
+ read_idx_ += amount_to_advance;
+ if (read_idx_ > write_idx_) {
+ QUICHE_BUG(simple_buffer_read_ptr_too_far)
+ << "error: readable pointer advanced beyond writable one";
+ read_idx_ = write_idx_;
+ }
+
+ if (read_idx_ == write_idx_) {
+ // Buffer is empty, rewind `read_idx_` and `write_idx_` so that next write
+ // happens at the beginning of buffer instead of cutting free space in two.
+ Clear();
+ }
+}
+
+void SimpleBuffer::AdvanceWritablePtr(int amount_to_advance) {
+ if (amount_to_advance < 0) {
+ QUICHE_BUG(simple_buffer_advance_write_negative_arg)
+ << "amount_to_advance must not be negative: " << amount_to_advance;
+ return;
+ }
+
+ write_idx_ += amount_to_advance;
+ if (write_idx_ > storage_size_) {
+ QUICHE_BUG(simple_buffer_write_ptr_too_far)
+ << "error: writable pointer advanced beyond end of storage";
+ write_idx_ = storage_size_;
+ }
+}
+
+QuicheMemSlice SimpleBuffer::ReleaseAsSlice() {
+ if (write_idx_ == 0) {
+ return QuicheMemSlice();
+ }
+ QuicheMemSlice slice(std::unique_ptr<char[]>(storage_), write_idx_);
+ Clear();
+ storage_ = nullptr;
+ storage_size_ = 0;
+ return slice;
+}
+} // namespace quiche
diff --git a/quiche/common/balsa/simple_buffer.h b/quiche/common/balsa/simple_buffer.h
new file mode 100644
index 0000000..52f3400
--- /dev/null
+++ b/quiche/common/balsa/simple_buffer.h
@@ -0,0 +1,116 @@
+// 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.
+
+#ifndef QUICHE_COMMON_BALSA_SIMPLE_BUFFER_H_
+#define QUICHE_COMMON_BALSA_SIMPLE_BUFFER_H_
+
+#include "absl/strings/string_view.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_mem_slice.h"
+
+namespace quiche {
+
+namespace test {
+class SimpleBufferTest;
+} // namespace test
+
+// SimpleBuffer stores data in a contiguous region. It can grow on demand,
+// which involves moving its data. It keeps track of a read and a write
+// position. Reading consumes data.
+class QUICHE_EXPORT_PRIVATE SimpleBuffer {
+ public:
+ SimpleBuffer();
+ // Create SimpleBuffer with `size` reserved capacity.
+ explicit SimpleBuffer(int size);
+
+ SimpleBuffer(const SimpleBuffer&) = delete;
+ SimpleBuffer& operator=(const SimpleBuffer&) = delete;
+
+ virtual ~SimpleBuffer() { delete[] storage_; }
+
+ // Returns the number of bytes that can be read from the buffer.
+ int ReadableBytes() const { return write_idx_ - read_idx_; }
+
+ bool Empty() const { return read_idx_ == write_idx_; }
+
+ // Copies `size` bytes to the buffer. Returns size.
+ int Write(const char* bytes, int size);
+ int WriteString(absl::string_view piece) {
+ return Write(piece.data(), piece.size());
+ }
+
+ // Gets a pointer into the buffer that can be written to. Stores the number
+ // of characters which are allowed to be written in `*size`. The pointer and
+ // size can be used in functions like recv() or read(). If `*size` is zero
+ // upon returning from this function, then it is unsafe to dereference `*ptr`.
+ // Writing to this region after calling any other non-const method results in
+ // undefined behavior.
+ void GetWritablePtr(char** ptr, int* size) const {
+ *ptr = storage_ + write_idx_;
+ *size = storage_size_ - write_idx_;
+ }
+
+ // Gets a pointer that can be read from. This pointer (and size) can be used
+ // in functions like send() or write(). If `*size` is zero upon returning
+ // from this function, then it is unsafe to dereference `*ptr`. Reading from
+ // this region after calling any other non-const method results in undefined
+ // behavior.
+ void GetReadablePtr(char** ptr, int* size) const {
+ *ptr = storage_ + read_idx_;
+ *size = write_idx_ - read_idx_;
+ }
+
+ // Returns the readable region as a string_view. Reading from this region
+ // after calling any other non-const method results in undefined behavior.
+ absl::string_view GetReadableRegion() const {
+ return absl::string_view(storage_ + read_idx_, write_idx_ - read_idx_);
+ }
+
+ // Reads bytes out of the buffer, and writes them into `bytes`. Returns the
+ // number of bytes read. Consumes bytes from the buffer.
+ int Read(char* bytes, int size);
+
+ // Marks all data consumed, making the entire reserved buffer available for
+ // write. Does not resize or free up any memory.
+ void Clear() { read_idx_ = write_idx_ = 0; }
+
+ // Makes sure at least `size` bytes can be written into the buffer. This can
+ // be an expensive operation: costing a new and a delete, and copying of all
+ // existing data. Even if the existing buffer does not need to be resized,
+ // unread data may need to be moved to consolidate fragmented free space.
+ void Reserve(int size);
+
+ // Marks the oldest `amount_to_advance` bytes as consumed.
+ // `amount_to_advance` must not be negative and it must not exceed
+ // ReadableBytes().
+ void AdvanceReadablePtr(int amount_to_advance);
+
+ // Marks the first `amount_to_advance` bytes of the writable area written.
+ // `amount_to_advance` must not be negative and it must not exceed the size of
+ // the writable area, returned as the `size` outparam of GetWritablePtr().
+ void AdvanceWritablePtr(int amount_to_advance);
+
+ // Releases the current contents of the SimpleBuffer and returns them as a
+ // MemSlice. Logically, has the same effect as calling Clear().
+ QuicheMemSlice ReleaseAsSlice();
+
+ private:
+ friend class test::SimpleBufferTest;
+
+ // The buffer owned by this class starts at `*storage_` and is `storage_size_`
+ // bytes long.
+ // `0 <= read_idx_ <= write_idx_ <= storage_size_` must always hold.
+ // If `read_idx_ == write_idx_`, then they must be equal to zero.
+ // The first `read_idx_` bytes of the buffer are consumed,
+ // the next `write_idx_ - read_idx_` bytes are the readable region, and the
+ // remaining `storage_size_ - write_idx_` bytes are the writable region.
+ char* storage_;
+ int write_idx_;
+ int read_idx_;
+ int storage_size_;
+};
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_BALSA_SIMPLE_BUFFER_H_
diff --git a/quiche/common/balsa/simple_buffer_test.cc b/quiche/common/balsa/simple_buffer_test.cc
new file mode 100644
index 0000000..d8b134e
--- /dev/null
+++ b/quiche/common/balsa/simple_buffer_test.cc
@@ -0,0 +1,364 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/balsa/simple_buffer.h"
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace quiche {
+
+namespace test {
+
+namespace {
+
+// Buffer full of 40 char strings.
+const char ibuf[] = {
+ "123456789!@#$%^&*()abcdefghijklmnopqrstu"
+ "123456789!@#$%^&*()abcdefghijklmnopqrstu"
+ "123456789!@#$%^&*()abcdefghijklmnopqrstu"
+ "123456789!@#$%^&*()abcdefghijklmnopqrstu"
+ "123456789!@#$%^&*()abcdefghijklmnopqrstu"};
+
+} // namespace
+
+class SimpleBufferTest : public QuicheTest {
+ public:
+ static char* storage(SimpleBuffer& buffer) { return buffer.storage_; }
+ static int write_idx(SimpleBuffer& buffer) { return buffer.write_idx_; }
+ static int read_idx(SimpleBuffer& buffer) { return buffer.read_idx_; }
+ static int storage_size(SimpleBuffer& buffer) { return buffer.storage_size_; }
+};
+
+namespace {
+
+TEST_F(SimpleBufferTest, TestCreationWithSize) {
+ SimpleBuffer buffer(5);
+ EXPECT_EQ(5, storage_size(buffer));
+}
+
+// Make sure that a zero-sized initial buffer does not throw things off.
+TEST_F(SimpleBufferTest, TestCreationWithZeroSize) {
+ SimpleBuffer buffer(0);
+ EXPECT_EQ(0, storage_size(buffer));
+ EXPECT_EQ(4, buffer.Write(ibuf, 4));
+ EXPECT_EQ(4, write_idx(buffer));
+ EXPECT_EQ(4, storage_size(buffer));
+ EXPECT_EQ(4, buffer.ReadableBytes());
+}
+
+TEST(SimpleBufferDeathTest, TestCreationWithNegativeSize) {
+ EXPECT_DEATH(SimpleBuffer buffer(-1), "Check failed");
+}
+
+TEST_F(SimpleBufferTest, TestBasics) {
+ SimpleBuffer buffer;
+
+ EXPECT_TRUE(buffer.Empty());
+ EXPECT_EQ("", buffer.GetReadableRegion());
+ EXPECT_EQ(10, storage_size(buffer));
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(0, write_idx(buffer));
+
+ char* readable_ptr = nullptr;
+ int readable_size = 0;
+ buffer.GetReadablePtr(&readable_ptr, &readable_size);
+ char* writeable_ptr = nullptr;
+ int writable_size = 0;
+ buffer.GetWritablePtr(&writeable_ptr, &writable_size);
+
+ EXPECT_EQ(storage(buffer), readable_ptr);
+ EXPECT_EQ(0, readable_size);
+ EXPECT_EQ(storage(buffer), writeable_ptr);
+ EXPECT_EQ(10, writable_size);
+ EXPECT_EQ(0, buffer.ReadableBytes());
+
+ const SimpleBuffer buffer2;
+ EXPECT_EQ(0, buffer2.ReadableBytes());
+}
+
+TEST_F(SimpleBufferTest, TestBasicWR) {
+ SimpleBuffer buffer;
+
+ EXPECT_EQ(4, buffer.Write(ibuf, 4));
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(4, write_idx(buffer));
+ EXPECT_EQ(10, storage_size(buffer));
+ EXPECT_EQ(4, buffer.ReadableBytes());
+ EXPECT_EQ("1234", buffer.GetReadableRegion());
+ int bytes_written = 4;
+ EXPECT_TRUE(!buffer.Empty());
+
+ char* readable_ptr = nullptr;
+ int readable_size = 0;
+ buffer.GetReadablePtr(&readable_ptr, &readable_size);
+ char* writeable_ptr = nullptr;
+ int writable_size = 0;
+ buffer.GetWritablePtr(&writeable_ptr, &writable_size);
+
+ EXPECT_EQ(storage(buffer), readable_ptr);
+ EXPECT_EQ(4, readable_size);
+ EXPECT_EQ(storage(buffer) + 4, writeable_ptr);
+ EXPECT_EQ(6, writable_size);
+
+ char obuf[ABSL_ARRAYSIZE(ibuf)];
+ int bytes_read = 0;
+ EXPECT_EQ(4, buffer.Read(obuf + bytes_read, 40));
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(0, write_idx(buffer));
+ EXPECT_EQ(10, storage_size(buffer));
+ EXPECT_EQ(0, buffer.ReadableBytes());
+ EXPECT_EQ("", buffer.GetReadableRegion());
+ bytes_read += 4;
+ EXPECT_TRUE(buffer.Empty());
+ buffer.GetReadablePtr(&readable_ptr, &readable_size);
+ buffer.GetWritablePtr(&writeable_ptr, &writable_size);
+ EXPECT_EQ(storage(buffer), readable_ptr);
+ EXPECT_EQ(0, readable_size);
+ EXPECT_EQ(storage(buffer), writeable_ptr);
+ EXPECT_EQ(10, writable_size);
+
+ EXPECT_EQ(bytes_written, bytes_read);
+ for (int i = 0; i < bytes_read; ++i) {
+ EXPECT_EQ(obuf[i], ibuf[i]);
+ }
+
+ // More R/W tests.
+ EXPECT_EQ(10, buffer.Write(ibuf + bytes_written, 10));
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(10, write_idx(buffer));
+ EXPECT_EQ(10, storage_size(buffer));
+ EXPECT_EQ(10, buffer.ReadableBytes());
+ bytes_written += 10;
+
+ EXPECT_TRUE(!buffer.Empty());
+
+ EXPECT_EQ(6, buffer.Read(obuf + bytes_read, 6));
+ EXPECT_EQ(6, read_idx(buffer));
+ EXPECT_EQ(10, write_idx(buffer));
+ EXPECT_EQ(10, storage_size(buffer));
+ EXPECT_EQ(4, buffer.ReadableBytes());
+ bytes_read += 6;
+
+ EXPECT_TRUE(!buffer.Empty());
+
+ EXPECT_EQ(4, buffer.Read(obuf + bytes_read, 7));
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(0, write_idx(buffer));
+ EXPECT_EQ(10, storage_size(buffer));
+ EXPECT_EQ(0, buffer.ReadableBytes());
+ bytes_read += 4;
+
+ EXPECT_TRUE(buffer.Empty());
+
+ EXPECT_EQ(bytes_written, bytes_read);
+ for (int i = 0; i < bytes_read; ++i) {
+ EXPECT_EQ(obuf[i], ibuf[i]);
+ }
+}
+
+TEST_F(SimpleBufferTest, TestReserve) {
+ SimpleBuffer buffer;
+
+ // Reserve by expanding the buffer.
+ const int initial_size = storage_size(buffer);
+ buffer.Reserve(initial_size + 1);
+ EXPECT_EQ(2 * initial_size, storage_size(buffer));
+
+ buffer.AdvanceWritablePtr(initial_size);
+ buffer.AdvanceReadablePtr(initial_size - 2);
+ EXPECT_EQ(initial_size, write_idx(buffer));
+ EXPECT_EQ(2 * initial_size, storage_size(buffer));
+
+ // Reserve by moving data around. `storage_size` does not change.
+ buffer.Reserve(initial_size + 1);
+ EXPECT_EQ(2, write_idx(buffer));
+ EXPECT_EQ(2 * initial_size, storage_size(buffer));
+}
+
+TEST_F(SimpleBufferTest, TestExtend) {
+ SimpleBuffer buffer;
+
+ // Test a write which should not extend the buffer.
+ EXPECT_EQ(7, buffer.Write(ibuf, 7));
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(7, write_idx(buffer));
+ EXPECT_EQ(10, storage_size(buffer));
+ EXPECT_EQ(7, buffer.ReadableBytes());
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(7, write_idx(buffer));
+ EXPECT_EQ(10, storage_size(buffer));
+ EXPECT_EQ(7, buffer.ReadableBytes());
+ int bytes_written = 7;
+
+ // Test a write which should extend the buffer.
+ EXPECT_EQ(4, buffer.Write(ibuf + bytes_written, 4));
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(11, write_idx(buffer));
+ EXPECT_EQ(20, storage_size(buffer));
+ EXPECT_EQ(11, buffer.ReadableBytes());
+ bytes_written += 4;
+
+ char obuf[ABSL_ARRAYSIZE(ibuf)];
+ EXPECT_EQ(11, buffer.Read(obuf, 11));
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(0, write_idx(buffer));
+ EXPECT_EQ(20, storage_size(buffer));
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(0, write_idx(buffer));
+ EXPECT_EQ(0, buffer.ReadableBytes());
+
+ const int bytes_read = 11;
+ EXPECT_EQ(bytes_written, bytes_read);
+ for (int i = 0; i < bytes_read; ++i) {
+ EXPECT_EQ(obuf[i], ibuf[i]);
+ }
+}
+
+TEST_F(SimpleBufferTest, TestClear) {
+ SimpleBuffer buffer;
+
+ buffer.Clear();
+ EXPECT_EQ(0, read_idx(buffer));
+ EXPECT_EQ(0, write_idx(buffer));
+ EXPECT_EQ(10, storage_size(buffer));
+ EXPECT_EQ(0, buffer.ReadableBytes());
+ EXPECT_EQ(10, storage_size(buffer));
+}
+
+TEST_F(SimpleBufferTest, TestLongWrite) {
+ SimpleBuffer buffer;
+
+ std::string s1 = "HTTP/1.1 500 Service Unavailable";
+ buffer.Write(s1.data(), s1.size());
+ buffer.Write("\r\n", 2);
+ std::string key = "Connection";
+ std::string value = "close";
+ buffer.Write(key.data(), key.size());
+ buffer.Write(": ", 2);
+ buffer.Write(value.data(), value.size());
+ buffer.Write("\r\n", 2);
+ buffer.Write("\r\n", 2);
+ std::string message =
+ "<html><head>\n"
+ "<meta http-equiv=\"content-type\""
+ " content=\"text/html;charset=us-ascii\">\n"
+ "<style><!--\n"
+ "body {font-family: arial,sans-serif}\n"
+ "div.nav {margin-top: 1ex}\n"
+ "div.nav A {font-size: 10pt; font-family: arial,sans-serif}\n"
+ "span.nav {font-size: 10pt; font-family: arial,sans-serif;"
+ " font-weight: bold}\n"
+ "div.nav A,span.big {font-size: 12pt; color: #0000cc}\n"
+ "div.nav A {font-size: 10pt; color: black}\n"
+ "A.l:link {color: #6f6f6f}\n"
+ "A.u:link {color: green}\n"
+ "//--></style>\n"
+ "</head>\n"
+ "<body text=#000000 bgcolor=#ffffff>\n"
+ "<table border=0 cellpadding=2 cellspacing=0 width=100%>"
+ "<tr><td rowspan=3 width=1% nowrap>\n"
+ "<b>"
+ "<font face=times color=#0039b6 size=10>G</font>"
+ "<font face=times color=#c41200 size=10>o</font>"
+ "<font face=times color=#f3c518 size=10>o</font>"
+ "<font face=times color=#0039b6 size=10>g</font>"
+ "<font face=times color=#30a72f size=10>l</font>"
+ "<font face=times color=#c41200 size=10>e</font>"
+ " </b>\n"
+ "<td> </td></tr>\n"
+ "<tr><td bgcolor=#3366cc><font face=arial,sans-serif color=#ffffff>"
+ " <b>Error</b></td></tr>\n"
+ "<tr><td> </td></tr></table>\n"
+ "<blockquote>\n"
+ "<H1> Internal Server Error</H1>\n"
+ " This server was unable to complete the request\n"
+ "<p></blockquote>\n"
+ "<table width=100% cellpadding=0 cellspacing=0>"
+ "<tr><td bgcolor=#3366cc><img alt=\"\" width=1 height=4></td></tr>"
+ "</table>"
+ "</body></html>\n";
+ buffer.Write(message.data(), message.size());
+ const std::string correct_result =
+ "HTTP/1.1 500 Service Unavailable\r\n"
+ "Connection: close\r\n"
+ "\r\n"
+ "<html><head>\n"
+ "<meta http-equiv=\"content-type\""
+ " content=\"text/html;charset=us-ascii\">\n"
+ "<style><!--\n"
+ "body {font-family: arial,sans-serif}\n"
+ "div.nav {margin-top: 1ex}\n"
+ "div.nav A {font-size: 10pt; font-family: arial,sans-serif}\n"
+ "span.nav {font-size: 10pt; font-family: arial,sans-serif;"
+ " font-weight: bold}\n"
+ "div.nav A,span.big {font-size: 12pt; color: #0000cc}\n"
+ "div.nav A {font-size: 10pt; color: black}\n"
+ "A.l:link {color: #6f6f6f}\n"
+ "A.u:link {color: green}\n"
+ "//--></style>\n"
+ "</head>\n"
+ "<body text=#000000 bgcolor=#ffffff>\n"
+ "<table border=0 cellpadding=2 cellspacing=0 width=100%>"
+ "<tr><td rowspan=3 width=1% nowrap>\n"
+ "<b>"
+ "<font face=times color=#0039b6 size=10>G</font>"
+ "<font face=times color=#c41200 size=10>o</font>"
+ "<font face=times color=#f3c518 size=10>o</font>"
+ "<font face=times color=#0039b6 size=10>g</font>"
+ "<font face=times color=#30a72f size=10>l</font>"
+ "<font face=times color=#c41200 size=10>e</font>"
+ " </b>\n"
+ "<td> </td></tr>\n"
+ "<tr><td bgcolor=#3366cc><font face=arial,sans-serif color=#ffffff>"
+ " <b>Error</b></td></tr>\n"
+ "<tr><td> </td></tr></table>\n"
+ "<blockquote>\n"
+ "<H1> Internal Server Error</H1>\n"
+ " This server was unable to complete the request\n"
+ "<p></blockquote>\n"
+ "<table width=100% cellpadding=0 cellspacing=0>"
+ "<tr><td bgcolor=#3366cc><img alt=\"\" width=1 height=4></td></tr>"
+ "</table>"
+ "</body></html>\n";
+ EXPECT_EQ(correct_result, buffer.GetReadableRegion());
+}
+
+TEST_F(SimpleBufferTest, ReleaseAsSlice) {
+ SimpleBuffer buffer;
+
+ buffer.WriteString("abc");
+ QuicheMemSlice slice = buffer.ReleaseAsSlice();
+ EXPECT_EQ("abc", slice.AsStringView());
+
+ char* readable_ptr = nullptr;
+ int readable_size = 0;
+ buffer.GetReadablePtr(&readable_ptr, &readable_size);
+ EXPECT_EQ(0, readable_size);
+
+ buffer.WriteString("def");
+ slice = buffer.ReleaseAsSlice();
+ buffer.GetReadablePtr(&readable_ptr, &readable_size);
+ EXPECT_EQ(0, readable_size);
+ EXPECT_EQ("def", slice.AsStringView());
+}
+
+TEST_F(SimpleBufferTest, EmptyBufferReleaseAsSlice) {
+ SimpleBuffer buffer;
+ char* readable_ptr = nullptr;
+ int readable_size = 0;
+
+ QuicheMemSlice slice = buffer.ReleaseAsSlice();
+ buffer.GetReadablePtr(&readable_ptr, &readable_size);
+ EXPECT_EQ(0, readable_size);
+ EXPECT_TRUE(slice.empty());
+}
+
+} // namespace
+
+} // namespace test
+
+} // namespace quiche
diff --git a/quiche/common/balsa/standard_header_map.cc b/quiche/common/balsa/standard_header_map.cc
new file mode 100644
index 0000000..0cdab01
--- /dev/null
+++ b/quiche/common/balsa/standard_header_map.cc
@@ -0,0 +1,143 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/common/balsa/standard_header_map.h"
+
+namespace quiche {
+
+const StandardHttpHeaderNameSet& GetStandardHeaderSet() {
+ static const StandardHttpHeaderNameSet* const header_map =
+ new StandardHttpHeaderNameSet({
+ {"Accept"},
+ {"Accept-Charset"},
+ {"Accept-CH"},
+ {"Accept-CH-Lifetime"},
+ {"Accept-Encoding"},
+ {"Accept-Language"},
+ {"Accept-Ranges"},
+ {"Access-Control-Allow-Credentials"},
+ {"Access-Control-Allow-Headers"},
+ {"Access-Control-Allow-Methods"},
+ {"Access-Control-Allow-Origin"},
+ {"Access-Control-Expose-Headers"},
+ {"Access-Control-Max-Age"},
+ {"Access-Control-Request-Headers"},
+ {"Access-Control-Request-Method"},
+ {"Age"},
+ {"Allow"},
+ {"Authorization"},
+ {"Cache-Control"},
+ {"Connection"},
+ {"Content-Disposition"},
+ {"Content-Encoding"},
+ {"Content-Language"},
+ {"Content-Length"},
+ {"Content-Location"},
+ {"Content-Range"},
+ {"Content-Security-Policy"},
+ {"Content-Security-Policy-Report-Only"},
+ {"X-Content-Security-Policy"},
+ {"X-Content-Security-Policy-Report-Only"},
+ {"X-WebKit-CSP"},
+ {"X-WebKit-CSP-Report-Only"},
+ {"Content-Type"},
+ {"Content-MD5"},
+ {"X-Content-Type-Options"},
+ {"Cookie"},
+ {"Cookie2"},
+ {"Cross-Origin-Resource-Policy"},
+ {"Cross-Origin-Opener-Policy"},
+ {"Date"},
+ {"DAV"},
+ {"Depth"},
+ {"Destination"},
+ {"DNT"},
+ {"DPR"},
+ {"Early-Data"},
+ {"ETag"},
+ {"Expect"},
+ {"Expires"},
+ {"Follow-Only-When-Prerender-Shown"},
+ {"Forwarded"},
+ {"From"},
+ {"Host"},
+ {"HTTP2-Settings"},
+ {"If"},
+ {"If-Match"},
+ {"If-Modified-Since"},
+ {"If-None-Match"},
+ {"If-Range"},
+ {"If-Unmodified-Since"},
+ {"Keep-Alive"},
+ {"Label"},
+ {"Last-Modified"},
+ {"Link"},
+ {"Location"},
+ {"Lock-Token"},
+ {"Max-Forwards"},
+ {"MS-Author-Via"},
+ {"Origin"},
+ {"Overwrite"},
+ {"P3P"},
+ {"Ping-From"},
+ {"Ping-To"},
+ {"Pragma"},
+ {"Proxy-Connection"},
+ {"Proxy-Authenticate"},
+ {"Public-Key-Pins"},
+ {"Public-Key-Pins-Report-Only"},
+ {"Range"},
+ {"Referer"},
+ {"Referrer-Policy"},
+ {"Refresh"},
+ {"Report-To"},
+ {"Retry-After"},
+ {"Sec-Fetch-Dest"},
+ {"Sec-Fetch-Mode"},
+ {"Sec-Fetch-Site"},
+ {"Sec-Fetch-User"},
+ {"Sec-Metadata"},
+ {"Sec-Token-Binding"},
+ {"Sec-Provided-Token-Binding-ID"},
+ {"Sec-Referred-Token-Binding-ID"},
+ {"Sec-WebSocket-Accept"},
+ {"Sec-WebSocket-Extensions"},
+ {"Sec-WebSocket-Key"},
+ {"Sec-WebSocket-Protocol"},
+ {"Sec-WebSocket-Version"},
+ {"Server"},
+ {"Server-Timing"},
+ {"Service-Worker"},
+ {"Service-Worker-Allowed"},
+ {"Service-Worker-Navigation-Preload"},
+ {"Set-Cookie"},
+ {"Set-Cookie2"},
+ {"Status-URI"},
+ {"Strict-Transport-Security"},
+ {"SourceMap"},
+ {"Timeout"},
+ {"Timing-Allow-Origin"},
+ {"Tk"},
+ {"Trailer"},
+ {"Trailers"},
+ {"Transfer-Encoding"},
+ {"TE"},
+ {"Upgrade"},
+ {"Upgrade-Insecure-Requests"},
+ {"User-Agent"},
+ {"X-OperaMini-Phone-UA"},
+ {"X-UCBrowser-UA"},
+ {"X-UCBrowser-Device-UA"},
+ {"X-Device-User-Agent"},
+ {"Vary"},
+ {"Via"},
+ {"CDN-Loop"},
+ {"Warning"},
+ {"WWW-Authenticate"},
+ });
+
+ return *header_map;
+}
+
+} // namespace quiche
diff --git a/quiche/common/balsa/standard_header_map.h b/quiche/common/balsa/standard_header_map.h
new file mode 100644
index 0000000..3e9dbb7
--- /dev/null
+++ b/quiche/common/balsa/standard_header_map.h
@@ -0,0 +1,24 @@
+// Copyright 2021 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.
+
+#ifndef QUICHE_COMMON_BALSA_STANDARD_HEADER_MAP_H_
+#define QUICHE_COMMON_BALSA_STANDARD_HEADER_MAP_H_
+
+#include "absl/container/flat_hash_set.h"
+#include "absl/strings/string_view.h"
+#include "quiche/common/quiche_text_utils.h"
+
+namespace quiche {
+
+// This specifies an absl::flat_hash_set with case-insensitive lookup and
+// hashing
+using StandardHttpHeaderNameSet =
+ absl::flat_hash_set<absl::string_view, StringPieceCaseHash,
+ StringPieceCaseEqual>;
+
+const StandardHttpHeaderNameSet& GetStandardHeaderSet();
+
+} // namespace quiche
+
+#endif // QUICHE_COMMON_BALSA_STANDARD_HEADER_MAP_H_