| // Copyright 2019 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/structured_headers.h" |
| |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <optional> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/algorithm/container.h" |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/numbers.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "quiche/common/platform/api/quiche_logging.h" |
| |
| namespace quiche { |
| namespace structured_headers { |
| |
| namespace { |
| |
| #define DIGIT "0123456789" |
| #define LCALPHA "abcdefghijklmnopqrstuvwxyz" |
| #define UCALPHA "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| #define TCHAR DIGIT LCALPHA UCALPHA "!#$%&'*+-.^_`|~" |
| // https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-09#section-3.9 |
| constexpr char kTokenChars09[] = DIGIT UCALPHA LCALPHA "_-.:%*/"; |
| // https://www.rfc-editor.org/rfc/rfc8941.html#section-3.3.4 |
| constexpr char kTokenChars[] = TCHAR ":/"; |
| // https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-09#section-3.1 |
| constexpr char kKeyChars09[] = DIGIT LCALPHA "_-"; |
| // https://www.rfc-editor.org/rfc/rfc8941.html#section-3.1.2 |
| constexpr char kKeyChars[] = DIGIT LCALPHA "_-.*"; |
| constexpr char kSP[] = " "; |
| constexpr char kOWS[] = " \t"; |
| #undef DIGIT |
| #undef LCALPHA |
| #undef UCALPHA |
| |
| // https://www.rfc-editor.org/rfc/rfc8941.html#section-3.3.1 |
| constexpr int64_t kMaxInteger = 999'999'999'999'999L; |
| constexpr int64_t kMinInteger = -999'999'999'999'999L; |
| |
| // Smallest value which is too large for an sh-decimal. This is the smallest |
| // double which will round up to 1e12 when serialized, which exceeds the range |
| // for sh-decimal. Any float less than this should round down. This behaviour is |
| // verified by unit tests. |
| constexpr double kTooLargeDecimal = 1e12 - 0.0005; |
| |
| // Removes characters in remove from the beginning of s. |
| void StripLeft(absl::string_view& s, absl::string_view remove) { |
| size_t i = s.find_first_not_of(remove); |
| if (i == absl::string_view::npos) { |
| i = s.size(); |
| } |
| s.remove_prefix(i); |
| } |
| |
| // Parser for (a subset of) Structured Headers for HTTP defined in [SH09] and |
| // [RFC8941]. [SH09] compatibility is retained for use by Web Packaging, and can |
| // be removed once that spec is updated, and users have migrated to new headers. |
| // [SH09] https://tools.ietf.org/html/draft-ietf-httpbis-header-structure-09 |
| // [RFC8941] https://www.rfc-editor.org/rfc/rfc8941.html |
| class StructuredHeaderParser { |
| public: |
| enum DraftVersion { |
| kDraft09, |
| kFinal, |
| }; |
| explicit StructuredHeaderParser(absl::string_view str, DraftVersion version) |
| : input_(str), version_(version) { |
| // [SH09] 4.2 Step 1. |
| // Discard any leading OWS from input_string. |
| // [RFC8941] 4.2 Step 2. |
| // Discard any leading SP characters from input_string. |
| SkipWhitespaces(); |
| } |
| StructuredHeaderParser(const StructuredHeaderParser&) = delete; |
| StructuredHeaderParser& operator=(const StructuredHeaderParser&) = delete; |
| |
| // Callers should call this after ReadSomething(), to check if parser has |
| // consumed all the input successfully. |
| bool FinishParsing() { |
| // [SH09] 4.2 Step 7. |
| // Discard any leading OWS from input_string. |
| // [RFC8941] 4.2 Step 6. |
| // Discard any leading SP characters from input_string. |
| SkipWhitespaces(); |
| // [SH09] 4.2 Step 8. [RFC8941] 4.2 Step 7. |
| // If input_string is not empty, fail parsing. |
| return input_.empty(); |
| } |
| |
| // Parses a List of Lists ([SH09] 4.2.4). |
| std::optional<ListOfLists> ReadListOfLists() { |
| QUICHE_CHECK_EQ(version_, kDraft09); |
| ListOfLists result; |
| while (true) { |
| std::vector<Item> inner_list; |
| while (true) { |
| std::optional<Item> item(ReadBareItem()); |
| if (!item) return std::nullopt; |
| inner_list.push_back(std::move(*item)); |
| SkipWhitespaces(); |
| if (!ConsumeChar(';')) break; |
| SkipWhitespaces(); |
| } |
| result.push_back(std::move(inner_list)); |
| SkipWhitespaces(); |
| if (!ConsumeChar(',')) break; |
| SkipWhitespaces(); |
| } |
| return result; |
| } |
| |
| // Parses a List ([RFC8941] 4.2.1). |
| std::optional<List> ReadList() { |
| QUICHE_CHECK_EQ(version_, kFinal); |
| List members; |
| while (!input_.empty()) { |
| std::optional<ParameterizedMember> member(ReadItemOrInnerList()); |
| if (!member) return std::nullopt; |
| members.push_back(std::move(*member)); |
| SkipOWS(); |
| if (input_.empty()) break; |
| if (!ConsumeChar(',')) return std::nullopt; |
| SkipOWS(); |
| if (input_.empty()) return std::nullopt; |
| } |
| return members; |
| } |
| |
| // Parses an Item ([RFC8941] 4.2.3). |
| std::optional<ParameterizedItem> ReadItem() { |
| std::optional<Item> item = ReadBareItem(); |
| if (!item) return std::nullopt; |
| std::optional<Parameters> parameters = ReadParameters(); |
| if (!parameters) return std::nullopt; |
| return ParameterizedItem(std::move(*item), std::move(*parameters)); |
| } |
| |
| // Parses a bare Item ([RFC8941] 4.2.3.1, though this is also the algorithm |
| // for parsing an Item from [SH09] 4.2.7). |
| std::optional<Item> ReadBareItem() { |
| if (input_.empty()) { |
| QUICHE_DVLOG(1) << "ReadBareItem: unexpected EOF"; |
| return std::nullopt; |
| } |
| switch (input_.front()) { |
| case '"': |
| return ReadString(); |
| case '*': |
| if (version_ == kDraft09) return ReadByteSequence(); |
| return ReadToken(); |
| case ':': |
| if (version_ == kFinal) return ReadByteSequence(); |
| return std::nullopt; |
| case '?': |
| return ReadBoolean(); |
| default: |
| if (input_.front() == '-' || absl::ascii_isdigit(input_.front())) |
| return ReadNumber(); |
| if (absl::ascii_isalpha(input_.front())) return ReadToken(); |
| return std::nullopt; |
| } |
| } |
| |
| // Parses a Dictionary ([RFC8941] 4.2.2). |
| std::optional<Dictionary> ReadDictionary() { |
| QUICHE_CHECK_EQ(version_, kFinal); |
| Dictionary members; |
| while (!input_.empty()) { |
| std::optional<std::string> key(ReadKey()); |
| if (!key) return std::nullopt; |
| std::optional<ParameterizedMember> member; |
| if (ConsumeChar('=')) { |
| member = ReadItemOrInnerList(); |
| if (!member) return std::nullopt; |
| } else { |
| std::optional<Parameters> parameters = ReadParameters(); |
| if (!parameters) return std::nullopt; |
| member = ParameterizedMember{Item(true), std::move(*parameters)}; |
| } |
| members[*key] = std::move(*member); |
| SkipOWS(); |
| if (input_.empty()) break; |
| if (!ConsumeChar(',')) return std::nullopt; |
| SkipOWS(); |
| if (input_.empty()) return std::nullopt; |
| } |
| return members; |
| } |
| |
| // Parses a Parameterised List ([SH09] 4.2.5). |
| std::optional<ParameterisedList> ReadParameterisedList() { |
| QUICHE_CHECK_EQ(version_, kDraft09); |
| ParameterisedList items; |
| while (true) { |
| std::optional<ParameterisedIdentifier> item = |
| ReadParameterisedIdentifier(); |
| if (!item) return std::nullopt; |
| items.push_back(std::move(*item)); |
| SkipWhitespaces(); |
| if (!ConsumeChar(',')) return items; |
| SkipWhitespaces(); |
| } |
| } |
| |
| private: |
| // Parses a Parameterised Identifier ([SH09] 4.2.6). |
| std::optional<ParameterisedIdentifier> ReadParameterisedIdentifier() { |
| QUICHE_CHECK_EQ(version_, kDraft09); |
| std::optional<Item> primary_identifier = ReadToken(); |
| if (!primary_identifier) return std::nullopt; |
| |
| ParameterisedIdentifier::Parameters parameters; |
| |
| SkipWhitespaces(); |
| while (ConsumeChar(';')) { |
| SkipWhitespaces(); |
| |
| std::optional<std::string> name = ReadKey(); |
| if (!name) return std::nullopt; |
| |
| Item value; |
| if (ConsumeChar('=')) { |
| auto item = ReadBareItem(); |
| if (!item) return std::nullopt; |
| value = std::move(*item); |
| } |
| if (!parameters.emplace(*name, std::move(value)).second) { |
| QUICHE_DVLOG(1) << "ReadParameterisedIdentifier: duplicated parameter: " |
| << *name; |
| return std::nullopt; |
| } |
| SkipWhitespaces(); |
| } |
| return ParameterisedIdentifier(std::move(*primary_identifier), |
| std::move(parameters)); |
| } |
| |
| // Parses an Item or Inner List ([RFC8941] 4.2.1.1). |
| std::optional<ParameterizedMember> ReadItemOrInnerList() { |
| QUICHE_CHECK_EQ(version_, kFinal); |
| bool member_is_inner_list = (!input_.empty() && input_.front() == '('); |
| if (member_is_inner_list) { |
| return ReadInnerList(); |
| } else { |
| auto item = ReadItem(); |
| if (!item) return std::nullopt; |
| return ParameterizedMember(std::move(item->item), |
| std::move(item->params)); |
| } |
| } |
| |
| // Parses Parameters ([RFC8941] 4.2.3.2) |
| std::optional<Parameters> ReadParameters() { |
| Parameters parameters; |
| absl::flat_hash_set<std::string> keys; |
| |
| while (ConsumeChar(';')) { |
| SkipWhitespaces(); |
| |
| std::optional<std::string> name = ReadKey(); |
| if (!name) return std::nullopt; |
| bool is_duplicate_key = !keys.insert(*name).second; |
| |
| Item value{true}; |
| if (ConsumeChar('=')) { |
| auto item = ReadBareItem(); |
| if (!item) return std::nullopt; |
| value = std::move(*item); |
| } |
| if (is_duplicate_key) { |
| for (auto& param : parameters) { |
| if (param.first == name) { |
| param.second = std::move(value); |
| break; |
| } |
| } |
| } else { |
| parameters.emplace_back(std::move(*name), std::move(value)); |
| } |
| } |
| return parameters; |
| } |
| |
| // Parses an Inner List ([RFC8941] 4.2.1.2). |
| std::optional<ParameterizedMember> ReadInnerList() { |
| QUICHE_CHECK_EQ(version_, kFinal); |
| if (!ConsumeChar('(')) return std::nullopt; |
| std::vector<ParameterizedItem> inner_list; |
| while (true) { |
| SkipWhitespaces(); |
| if (ConsumeChar(')')) { |
| std::optional<Parameters> parameters = ReadParameters(); |
| if (!parameters) return std::nullopt; |
| return ParameterizedMember(std::move(inner_list), true, |
| std::move(*parameters)); |
| } |
| auto item = ReadItem(); |
| if (!item) return std::nullopt; |
| inner_list.push_back(std::move(*item)); |
| if (input_.empty() || (input_.front() != ' ' && input_.front() != ')')) |
| return std::nullopt; |
| } |
| QUICHE_NOTREACHED(); |
| return std::nullopt; |
| } |
| |
| // Parses a Key ([SH09] 4.2.2, [RFC8941] 4.2.3.3). |
| std::optional<std::string> ReadKey() { |
| if (version_ == kDraft09) { |
| if (input_.empty() || !absl::ascii_islower(input_.front())) { |
| LogParseError("ReadKey", "lcalpha"); |
| return std::nullopt; |
| } |
| } else { |
| if (input_.empty() || |
| (!absl::ascii_islower(input_.front()) && input_.front() != '*')) { |
| LogParseError("ReadKey", "lcalpha | *"); |
| return std::nullopt; |
| } |
| } |
| const char* allowed_chars = |
| (version_ == kDraft09 ? kKeyChars09 : kKeyChars); |
| size_t len = input_.find_first_not_of(allowed_chars); |
| if (len == absl::string_view::npos) len = input_.size(); |
| std::string key(input_.substr(0, len)); |
| input_.remove_prefix(len); |
| return key; |
| } |
| |
| // Parses a Token ([SH09] 4.2.10, [RFC8941] 4.2.6). |
| std::optional<Item> ReadToken() { |
| if (input_.empty() || |
| !(absl::ascii_isalpha(input_.front()) || input_.front() == '*')) { |
| LogParseError("ReadToken", "ALPHA"); |
| return std::nullopt; |
| } |
| size_t len = input_.find_first_not_of(version_ == kDraft09 ? kTokenChars09 |
| : kTokenChars); |
| if (len == absl::string_view::npos) len = input_.size(); |
| std::string token(input_.substr(0, len)); |
| input_.remove_prefix(len); |
| return Item(std::move(token), Item::kTokenType); |
| } |
| |
| // Parses a Number ([SH09] 4.2.8, [RFC8941] 4.2.4). |
| std::optional<Item> ReadNumber() { |
| bool is_negative = ConsumeChar('-'); |
| bool is_decimal = false; |
| size_t decimal_position = 0; |
| size_t i = 0; |
| for (; i < input_.size(); ++i) { |
| if (i > 0 && input_[i] == '.' && !is_decimal) { |
| is_decimal = true; |
| decimal_position = i; |
| continue; |
| } |
| if (!absl::ascii_isdigit(input_[i])) break; |
| } |
| if (i == 0) { |
| LogParseError("ReadNumber", "DIGIT"); |
| return std::nullopt; |
| } |
| if (!is_decimal) { |
| // [RFC8941] restricts the range of integers further. |
| if (version_ == kFinal && i > 15) { |
| LogParseError("ReadNumber", "integer too long"); |
| return std::nullopt; |
| } |
| } else { |
| if (version_ != kFinal && i > 16) { |
| LogParseError("ReadNumber", "float too long"); |
| return std::nullopt; |
| } |
| if (version_ == kFinal && decimal_position > 12) { |
| LogParseError("ReadNumber", "decimal too long"); |
| return std::nullopt; |
| } |
| if (i - decimal_position > (version_ == kFinal ? 4 : 7)) { |
| LogParseError("ReadNumber", "too many digits after decimal"); |
| return std::nullopt; |
| } |
| if (i == decimal_position) { |
| LogParseError("ReadNumber", "no digits after decimal"); |
| return std::nullopt; |
| } |
| } |
| std::string output_number_string(input_.substr(0, i)); |
| input_.remove_prefix(i); |
| |
| if (is_decimal) { |
| // Convert to a 64-bit double, and return if the conversion is |
| // successful. |
| double f; |
| if (!absl::SimpleAtod(output_number_string, &f)) return std::nullopt; |
| return Item(is_negative ? -f : f); |
| } else { |
| // Convert to a 64-bit signed integer, and return if the conversion is |
| // successful. |
| int64_t n; |
| if (!absl::SimpleAtoi(output_number_string, &n)) return std::nullopt; |
| QUICHE_CHECK(version_ != kFinal || |
| (n <= kMaxInteger && n >= kMinInteger)); |
| return Item(is_negative ? -n : n); |
| } |
| } |
| |
| // Parses a String ([SH09] 4.2.9, [RFC8941] 4.2.5). |
| std::optional<Item> ReadString() { |
| std::string s; |
| if (!ConsumeChar('"')) { |
| LogParseError("ReadString", "'\"'"); |
| return std::nullopt; |
| } |
| while (!ConsumeChar('"')) { |
| size_t i = 0; |
| for (; i < input_.size(); ++i) { |
| if (!absl::ascii_isprint(input_[i])) { |
| QUICHE_DVLOG(1) << "ReadString: non printable-ASCII character"; |
| return std::nullopt; |
| } |
| if (input_[i] == '"' || input_[i] == '\\') break; |
| } |
| if (i == input_.size()) { |
| QUICHE_DVLOG(1) << "ReadString: missing closing '\"'"; |
| return std::nullopt; |
| } |
| s.append(std::string(input_.substr(0, i))); |
| input_.remove_prefix(i); |
| if (ConsumeChar('\\')) { |
| if (input_.empty()) { |
| QUICHE_DVLOG(1) << "ReadString: backslash at string end"; |
| return std::nullopt; |
| } |
| if (input_[0] != '"' && input_[0] != '\\') { |
| QUICHE_DVLOG(1) << "ReadString: invalid escape"; |
| return std::nullopt; |
| } |
| s.push_back(input_.front()); |
| input_.remove_prefix(1); |
| } |
| } |
| return s; |
| } |
| |
| // Parses a Byte Sequence ([SH09] 4.2.11, [RFC8941] 4.2.7). |
| std::optional<Item> ReadByteSequence() { |
| char delimiter = (version_ == kDraft09 ? '*' : ':'); |
| if (!ConsumeChar(delimiter)) { |
| LogParseError("ReadByteSequence", "delimiter"); |
| return std::nullopt; |
| } |
| size_t len = input_.find(delimiter); |
| if (len == absl::string_view::npos) { |
| QUICHE_DVLOG(1) << "ReadByteSequence: missing closing delimiter"; |
| return std::nullopt; |
| } |
| std::string base64(input_.substr(0, len)); |
| // Append the necessary padding characters. |
| base64.resize((base64.size() + 3) / 4 * 4, '='); |
| |
| std::string binary; |
| if (!absl::Base64Unescape(base64, &binary)) { |
| QUICHE_DVLOG(1) << "ReadByteSequence: failed to decode base64: " |
| << base64; |
| return std::nullopt; |
| } |
| input_.remove_prefix(len); |
| ConsumeChar(delimiter); |
| return Item(std::move(binary), Item::kByteSequenceType); |
| } |
| |
| // Parses a Boolean ([RFC8941] 4.2.8). |
| // Note that this only parses ?0 and ?1 forms from SH version 10+, not the |
| // previous ?F and ?T, which were not needed by any consumers of SH version 9. |
| std::optional<Item> ReadBoolean() { |
| if (!ConsumeChar('?')) { |
| LogParseError("ReadBoolean", "'?'"); |
| return std::nullopt; |
| } |
| if (ConsumeChar('1')) { |
| return Item(true); |
| } |
| if (ConsumeChar('0')) { |
| return Item(false); |
| } |
| return std::nullopt; |
| } |
| |
| // There are several points in the specs where the handling of whitespace |
| // differs between Draft 9 and the final RFC. In those cases, Draft 9 allows |
| // any OWS character, while the RFC allows only a U+0020 SPACE. |
| void SkipWhitespaces() { |
| if (version_ == kDraft09) { |
| StripLeft(input_, kOWS); |
| } else { |
| StripLeft(input_, kSP); |
| } |
| } |
| |
| void SkipOWS() { StripLeft(input_, kOWS); } |
| |
| bool ConsumeChar(char expected) { |
| if (!input_.empty() && input_.front() == expected) { |
| input_.remove_prefix(1); |
| return true; |
| } |
| return false; |
| } |
| |
| void LogParseError(const char* func, const char* expected) { |
| QUICHE_DVLOG(1) << func << ": " << expected << " expected, got " |
| << (input_.empty() |
| ? "EOS" |
| : "'" + std::string(input_.substr(0, 1)) + "'"); |
| } |
| |
| absl::string_view input_; |
| DraftVersion version_; |
| }; |
| |
| // Serializer for (a subset of) Structured Field Values for HTTP defined in |
| // [RFC8941]. Note that this serializer does not attempt to support [SH09]. |
| class StructuredHeaderSerializer { |
| public: |
| StructuredHeaderSerializer() = default; |
| ~StructuredHeaderSerializer() = default; |
| StructuredHeaderSerializer(const StructuredHeaderSerializer&) = delete; |
| StructuredHeaderSerializer& operator=(const StructuredHeaderSerializer&) = |
| delete; |
| |
| std::string Output() { return output_.str(); } |
| |
| // Serializes a List ([RFC8941] 4.1.1). |
| bool WriteList(const List& value) { |
| bool first = true; |
| for (const auto& member : value) { |
| if (!first) output_ << ", "; |
| if (!WriteParameterizedMember(member)) return false; |
| first = false; |
| } |
| return true; |
| } |
| |
| // Serializes an Item ([RFC8941] 4.1.3). |
| bool WriteItem(const ParameterizedItem& value) { |
| if (!WriteBareItem(value.item)) return false; |
| return WriteParameters(value.params); |
| } |
| |
| // Serializes an Item ([RFC8941] 4.1.3). |
| bool WriteBareItem(const Item& value) { |
| if (value.is_string()) { |
| // Serializes a String ([RFC8941] 4.1.6). |
| output_ << "\""; |
| for (const char& c : value.GetString()) { |
| if (!absl::ascii_isprint(c)) return false; |
| if (c == '\\' || c == '\"') output_ << "\\"; |
| output_ << c; |
| } |
| output_ << "\""; |
| return true; |
| } |
| if (value.is_token()) { |
| // Serializes a Token ([RFC8941] 4.1.7). |
| if (!IsValidToken(value.GetString())) { |
| return false; |
| } |
| output_ << value.GetString(); |
| return true; |
| } |
| if (value.is_byte_sequence()) { |
| // Serializes a Byte Sequence ([RFC8941] 4.1.8). |
| output_ << ":"; |
| output_ << absl::Base64Escape(value.GetString()); |
| output_ << ":"; |
| return true; |
| } |
| if (value.is_integer()) { |
| // Serializes an Integer ([RFC8941] 4.1.4). |
| if (value.GetInteger() > kMaxInteger || value.GetInteger() < kMinInteger) |
| return false; |
| output_ << value.GetInteger(); |
| return true; |
| } |
| if (value.is_decimal()) { |
| // Serializes a Decimal ([RFC8941] 4.1.5). |
| double decimal_value = value.GetDecimal(); |
| if (!std::isfinite(decimal_value) || |
| fabs(decimal_value) >= kTooLargeDecimal) |
| return false; |
| |
| // Handle sign separately to simplify the rest of the formatting. |
| if (decimal_value < 0) output_ << "-"; |
| // Unconditionally take absolute value to ensure that -0 is serialized as |
| // "0.0", with no negative sign, as required by spec. (4.1.5, step 2). |
| decimal_value = fabs(decimal_value); |
| double remainder = fmod(decimal_value, 0.002); |
| if (remainder == 0.0005) { |
| // Value ended in exactly 0.0005, 0.0025, 0.0045, etc. Round down. |
| decimal_value -= 0.0005; |
| } else if (remainder == 0.0015) { |
| // Value ended in exactly 0.0015, 0.0035, 0,0055, etc. Round up. |
| decimal_value += 0.0005; |
| } else { |
| // Standard rounding will work in all other cases. |
| decimal_value = round(decimal_value * 1000.0) / 1000.0; |
| } |
| |
| // Use standard library functions to write the decimal, and then truncate |
| // if necessary to conform to spec. |
| |
| // Maximum is 12 integer digits, one decimal point, three fractional |
| // digits, and a null terminator. |
| char buffer[17]; |
| absl::SNPrintF(buffer, std::size(buffer), "%#.3f", decimal_value); |
| |
| // Strip any trailing 0s after the decimal point, but leave at least one |
| // digit after it in all cases. (So 1.230 becomes 1.23, but 1.000 becomes |
| // 1.0.) |
| absl::string_view formatted_number(buffer); |
| auto truncate_index = formatted_number.find_last_not_of('0'); |
| if (formatted_number[truncate_index] == '.') truncate_index++; |
| output_ << formatted_number.substr(0, truncate_index + 1); |
| return true; |
| } |
| if (value.is_boolean()) { |
| // Serializes a Boolean ([RFC8941] 4.1.9). |
| output_ << (value.GetBoolean() ? "?1" : "?0"); |
| return true; |
| } |
| return false; |
| } |
| |
| // Serializes a Dictionary ([RFC8941] 4.1.2). |
| bool WriteDictionary(const Dictionary& value) { |
| bool first = true; |
| for (const auto& [dict_key, dict_value] : value) { |
| if (!first) output_ << ", "; |
| if (!WriteKey(dict_key)) return false; |
| first = false; |
| if (!dict_value.member_is_inner_list && !dict_value.member.empty() && |
| dict_value.member.front().item.is_boolean() && |
| dict_value.member.front().item.GetBoolean()) { |
| if (!WriteParameters(dict_value.params)) return false; |
| } else { |
| output_ << "="; |
| if (!WriteParameterizedMember(dict_value)) return false; |
| } |
| } |
| return true; |
| } |
| |
| private: |
| bool WriteParameterizedMember(const ParameterizedMember& value) { |
| // Serializes a parameterized member ([RFC8941] 4.1.1). |
| if (value.member_is_inner_list) { |
| if (!WriteInnerList(value.member)) return false; |
| } else { |
| QUICHE_CHECK_EQ(value.member.size(), 1UL); |
| if (!WriteItem(value.member[0])) return false; |
| } |
| return WriteParameters(value.params); |
| } |
| |
| bool WriteInnerList(const std::vector<ParameterizedItem>& value) { |
| // Serializes an inner list ([RFC8941] 4.1.1.1). |
| output_ << "("; |
| bool first = true; |
| for (const ParameterizedItem& member : value) { |
| if (!first) output_ << " "; |
| if (!WriteItem(member)) return false; |
| first = false; |
| } |
| output_ << ")"; |
| return true; |
| } |
| |
| bool WriteParameters(const Parameters& value) { |
| // Serializes a parameter list ([RFC8941] 4.1.1.2). |
| for (const auto& param_name_and_value : value) { |
| const std::string& param_name = param_name_and_value.first; |
| const Item& param_value = param_name_and_value.second; |
| output_ << ";"; |
| if (!WriteKey(param_name)) return false; |
| if (!param_value.is_null()) { |
| if (param_value.is_boolean() && param_value.GetBoolean()) continue; |
| output_ << "="; |
| if (!WriteBareItem(param_value)) return false; |
| } |
| } |
| return true; |
| } |
| |
| bool WriteKey(const std::string& value) { |
| // Serializes a Key ([RFC8941] 4.1.1.3). |
| if (value.empty()) return false; |
| if (value.find_first_not_of(kKeyChars) != std::string::npos) return false; |
| if (!absl::ascii_islower(value[0]) && value[0] != '*') return false; |
| output_ << value; |
| return true; |
| } |
| |
| std::ostringstream output_; |
| }; |
| |
| } // namespace |
| |
| absl::string_view ItemTypeToString(Item::ItemType type) { |
| switch (type) { |
| case Item::kNullType: |
| return "null"; |
| case Item::kIntegerType: |
| return "integer"; |
| case Item::kDecimalType: |
| return "decimal"; |
| case Item::kStringType: |
| return "string"; |
| case Item::kTokenType: |
| return "token"; |
| case Item::kByteSequenceType: |
| return "byte sequence"; |
| case Item::kBooleanType: |
| return "boolean"; |
| } |
| return "[invalid type]"; |
| } |
| |
| bool IsValidToken(absl::string_view str) { |
| // Validate Token value per [RFC8941] 4.1.7. |
| if (str.empty() || |
| !(absl::ascii_isalpha(str.front()) || str.front() == '*')) { |
| return false; |
| } |
| if (str.find_first_not_of(kTokenChars) != std::string::npos) { |
| return false; |
| } |
| return true; |
| } |
| |
| Item::Item() {} |
| Item::Item(std::string value, Item::ItemType type) { |
| switch (type) { |
| case kStringType: |
| value_.emplace<kStringType>(std::move(value)); |
| break; |
| case kTokenType: |
| value_.emplace<kTokenType>(std::move(value)); |
| break; |
| case kByteSequenceType: |
| value_.emplace<kByteSequenceType>(std::move(value)); |
| break; |
| default: |
| QUICHE_CHECK(false); |
| break; |
| } |
| } |
| Item::Item(const char* value, Item::ItemType type) |
| : Item(std::string(value), type) {} |
| Item::Item(int64_t value) : value_(value) {} |
| Item::Item(double value) : value_(value) {} |
| Item::Item(bool value) : value_(value) {} |
| |
| bool operator==(const Item& lhs, const Item& rhs) { |
| return lhs.value_ == rhs.value_; |
| } |
| |
| ParameterizedItem::ParameterizedItem() = default; |
| ParameterizedItem::ParameterizedItem(const ParameterizedItem&) = default; |
| ParameterizedItem& ParameterizedItem::operator=(const ParameterizedItem&) = |
| default; |
| ParameterizedItem::ParameterizedItem(Item id, Parameters ps) |
| : item(std::move(id)), params(std::move(ps)) {} |
| ParameterizedItem::~ParameterizedItem() = default; |
| |
| ParameterizedMember::ParameterizedMember() = default; |
| ParameterizedMember::ParameterizedMember(const ParameterizedMember&) = default; |
| ParameterizedMember& ParameterizedMember::operator=( |
| const ParameterizedMember&) = default; |
| ParameterizedMember::ParameterizedMember(std::vector<ParameterizedItem> id, |
| bool member_is_inner_list, |
| Parameters ps) |
| : member(std::move(id)), |
| member_is_inner_list(member_is_inner_list), |
| params(std::move(ps)) {} |
| ParameterizedMember::ParameterizedMember(std::vector<ParameterizedItem> id, |
| Parameters ps) |
| : member(std::move(id)), |
| member_is_inner_list(true), |
| params(std::move(ps)) {} |
| ParameterizedMember::ParameterizedMember(Item id, Parameters ps) |
| : member({{std::move(id), {}}}), |
| member_is_inner_list(false), |
| params(std::move(ps)) {} |
| ParameterizedMember::~ParameterizedMember() = default; |
| |
| ParameterisedIdentifier::ParameterisedIdentifier() = default; |
| ParameterisedIdentifier::ParameterisedIdentifier( |
| const ParameterisedIdentifier&) = default; |
| ParameterisedIdentifier& ParameterisedIdentifier::operator=( |
| const ParameterisedIdentifier&) = default; |
| ParameterisedIdentifier::ParameterisedIdentifier(Item id, Parameters ps) |
| : identifier(std::move(id)), params(std::move(ps)) {} |
| ParameterisedIdentifier::~ParameterisedIdentifier() = default; |
| |
| Dictionary::Dictionary() = default; |
| Dictionary::Dictionary(const Dictionary&) = default; |
| Dictionary::Dictionary(Dictionary&&) = default; |
| Dictionary::Dictionary(std::vector<DictionaryMember> members) |
| : members_(std::move(members)) {} |
| Dictionary::~Dictionary() = default; |
| Dictionary::iterator Dictionary::begin() { return members_.begin(); } |
| Dictionary::const_iterator Dictionary::begin() const { |
| return members_.begin(); |
| } |
| Dictionary::iterator Dictionary::end() { return members_.end(); } |
| Dictionary::const_iterator Dictionary::end() const { return members_.end(); } |
| ParameterizedMember& Dictionary::operator[](std::size_t idx) { |
| return members_[idx].second; |
| } |
| const ParameterizedMember& Dictionary::operator[](std::size_t idx) const { |
| return members_[idx].second; |
| } |
| ParameterizedMember& Dictionary::at(std::size_t idx) { return (*this)[idx]; } |
| const ParameterizedMember& Dictionary::at(std::size_t idx) const { |
| return (*this)[idx]; |
| } |
| ParameterizedMember& Dictionary::operator[](absl::string_view key) { |
| auto it = find(key); |
| if (it != end()) return it->second; |
| return members_.emplace_back(key, ParameterizedMember()).second; |
| } |
| ParameterizedMember& Dictionary::at(absl::string_view key) { |
| auto it = find(key); |
| QUICHE_CHECK(it != end()) << "Provided key not found in dictionary"; |
| return it->second; |
| } |
| const ParameterizedMember& Dictionary::at(absl::string_view key) const { |
| auto it = find(key); |
| QUICHE_CHECK(it != end()) << "Provided key not found in dictionary"; |
| return it->second; |
| } |
| Dictionary::const_iterator Dictionary::find(absl::string_view key) const { |
| return absl::c_find_if( |
| members_, [key](const auto& member) { return member.first == key; }); |
| } |
| Dictionary::iterator Dictionary::find(absl::string_view key) { |
| return absl::c_find_if( |
| members_, [key](const auto& member) { return member.first == key; }); |
| } |
| bool Dictionary::empty() const { return members_.empty(); } |
| std::size_t Dictionary::size() const { return members_.size(); } |
| bool Dictionary::contains(absl::string_view key) const { |
| return find(key) != end(); |
| } |
| void Dictionary::clear() { members_.clear(); } |
| |
| std::optional<ParameterizedItem> ParseItem(absl::string_view str) { |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); |
| std::optional<ParameterizedItem> item = parser.ReadItem(); |
| if (item && parser.FinishParsing()) return item; |
| return std::nullopt; |
| } |
| |
| std::optional<Item> ParseBareItem(absl::string_view str) { |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); |
| std::optional<Item> item = parser.ReadBareItem(); |
| if (item && parser.FinishParsing()) return item; |
| return std::nullopt; |
| } |
| |
| std::optional<ParameterisedList> ParseParameterisedList(absl::string_view str) { |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kDraft09); |
| std::optional<ParameterisedList> param_list = parser.ReadParameterisedList(); |
| if (param_list && parser.FinishParsing()) return param_list; |
| return std::nullopt; |
| } |
| |
| std::optional<ListOfLists> ParseListOfLists(absl::string_view str) { |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kDraft09); |
| std::optional<ListOfLists> list_of_lists = parser.ReadListOfLists(); |
| if (list_of_lists && parser.FinishParsing()) return list_of_lists; |
| return std::nullopt; |
| } |
| |
| std::optional<List> ParseList(absl::string_view str) { |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); |
| std::optional<List> list = parser.ReadList(); |
| if (list && parser.FinishParsing()) return list; |
| return std::nullopt; |
| } |
| |
| std::optional<Dictionary> ParseDictionary(absl::string_view str) { |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); |
| std::optional<Dictionary> dictionary = parser.ReadDictionary(); |
| if (dictionary && parser.FinishParsing()) return dictionary; |
| return std::nullopt; |
| } |
| |
| std::optional<std::string> SerializeItem(const Item& value) { |
| StructuredHeaderSerializer s; |
| if (s.WriteItem(ParameterizedItem(value, {}))) return s.Output(); |
| return std::nullopt; |
| } |
| |
| std::optional<std::string> SerializeItem(const ParameterizedItem& value) { |
| StructuredHeaderSerializer s; |
| if (s.WriteItem(value)) return s.Output(); |
| return std::nullopt; |
| } |
| |
| std::optional<std::string> SerializeList(const List& value) { |
| StructuredHeaderSerializer s; |
| if (s.WriteList(value)) return s.Output(); |
| return std::nullopt; |
| } |
| |
| std::optional<std::string> SerializeDictionary(const Dictionary& value) { |
| StructuredHeaderSerializer s; |
| if (s.WriteDictionary(value)) return s.Output(); |
| return std::nullopt; |
| } |
| |
| } // namespace structured_headers |
| } // namespace quiche |