| // 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 <string> | |
| #include <utility> | |
| #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 = 0; | |
| while (i < s.size() && memchr(remove.data(), s[i], remove.size())) { | |
| ++i; | |
| } | |
| if (i > 0) 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). | |
| absl::optional<ListOfLists> ReadListOfLists() { | |
| QUICHE_CHECK_EQ(version_, kDraft09); | |
| ListOfLists result; | |
| while (true) { | |
| std::vector<Item> inner_list; | |
| while (true) { | |
| absl::optional<Item> item(ReadBareItem()); | |
| if (!item) return absl::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). | |
| absl::optional<List> ReadList() { | |
| QUICHE_CHECK_EQ(version_, kFinal); | |
| List members; | |
| while (!input_.empty()) { | |
| absl::optional<ParameterizedMember> member(ReadItemOrInnerList()); | |
| if (!member) return absl::nullopt; | |
| members.push_back(std::move(*member)); | |
| SkipOWS(); | |
| if (input_.empty()) break; | |
| if (!ConsumeChar(',')) return absl::nullopt; | |
| SkipOWS(); | |
| if (input_.empty()) return absl::nullopt; | |
| } | |
| return members; | |
| } | |
| // Parses an Item ([RFC8941] 4.2.3). | |
| absl::optional<ParameterizedItem> ReadItem() { | |
| absl::optional<Item> item = ReadBareItem(); | |
| if (!item) return absl::nullopt; | |
| absl::optional<Parameters> parameters = ReadParameters(); | |
| if (!parameters) return absl::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). | |
| absl::optional<Item> ReadBareItem() { | |
| if (input_.empty()) { | |
| QUICHE_DVLOG(1) << "ReadBareItem: unexpected EOF"; | |
| return absl::nullopt; | |
| } | |
| switch (input_.front()) { | |
| case '"': | |
| return ReadString(); | |
| case '*': | |
| if (version_ == kDraft09) return ReadByteSequence(); | |
| return ReadToken(); | |
| case ':': | |
| if (version_ == kFinal) return ReadByteSequence(); | |
| return absl::nullopt; | |
| case '?': | |
| return ReadBoolean(); | |
| default: | |
| if (input_.front() == '-' || absl::ascii_isdigit(input_.front())) | |
| return ReadNumber(); | |
| if (absl::ascii_isalpha(input_.front())) return ReadToken(); | |
| return absl::nullopt; | |
| } | |
| } | |
| // Parses a Dictionary ([RFC8941] 4.2.2). | |
| absl::optional<Dictionary> ReadDictionary() { | |
| QUICHE_CHECK_EQ(version_, kFinal); | |
| Dictionary members; | |
| while (!input_.empty()) { | |
| absl::optional<std::string> key(ReadKey()); | |
| if (!key) return absl::nullopt; | |
| absl::optional<ParameterizedMember> member; | |
| if (ConsumeChar('=')) { | |
| member = ReadItemOrInnerList(); | |
| if (!member) return absl::nullopt; | |
| } else { | |
| absl::optional<Parameters> parameters; | |
| parameters = ReadParameters(); | |
| if (!parameters) return absl::nullopt; | |
| member = ParameterizedMember{Item(true), std::move(*parameters)}; | |
| } | |
| members[*key] = std::move(*member); | |
| SkipOWS(); | |
| if (input_.empty()) break; | |
| if (!ConsumeChar(',')) return absl::nullopt; | |
| SkipOWS(); | |
| if (input_.empty()) return absl::nullopt; | |
| } | |
| return members; | |
| } | |
| // Parses a Parameterised List ([SH09] 4.2.5). | |
| absl::optional<ParameterisedList> ReadParameterisedList() { | |
| QUICHE_CHECK_EQ(version_, kDraft09); | |
| ParameterisedList items; | |
| while (true) { | |
| absl::optional<ParameterisedIdentifier> item = | |
| ReadParameterisedIdentifier(); | |
| if (!item) return absl::nullopt; | |
| items.push_back(std::move(*item)); | |
| SkipWhitespaces(); | |
| if (!ConsumeChar(',')) return items; | |
| SkipWhitespaces(); | |
| } | |
| } | |
| private: | |
| // Parses a Parameterised Identifier ([SH09] 4.2.6). | |
| absl::optional<ParameterisedIdentifier> ReadParameterisedIdentifier() { | |
| QUICHE_CHECK_EQ(version_, kDraft09); | |
| absl::optional<Item> primary_identifier = ReadToken(); | |
| if (!primary_identifier) return absl::nullopt; | |
| ParameterisedIdentifier::Parameters parameters; | |
| SkipWhitespaces(); | |
| while (ConsumeChar(';')) { | |
| SkipWhitespaces(); | |
| absl::optional<std::string> name = ReadKey(); | |
| if (!name) return absl::nullopt; | |
| Item value; | |
| if (ConsumeChar('=')) { | |
| auto item = ReadBareItem(); | |
| if (!item) return absl::nullopt; | |
| value = std::move(*item); | |
| } | |
| if (!parameters.emplace(*name, value).second) { | |
| QUICHE_DVLOG(1) << "ReadParameterisedIdentifier: duplicated parameter: " | |
| << *name; | |
| return absl::nullopt; | |
| } | |
| SkipWhitespaces(); | |
| } | |
| return ParameterisedIdentifier(std::move(*primary_identifier), | |
| std::move(parameters)); | |
| } | |
| // Parses an Item or Inner List ([RFC8941] 4.2.1.1). | |
| absl::optional<ParameterizedMember> ReadItemOrInnerList() { | |
| QUICHE_CHECK_EQ(version_, kFinal); | |
| std::vector<Item> member; | |
| bool member_is_inner_list = (!input_.empty() && input_.front() == '('); | |
| if (member_is_inner_list) { | |
| return ReadInnerList(); | |
| } else { | |
| auto item = ReadItem(); | |
| if (!item) return absl::nullopt; | |
| return ParameterizedMember(std::move(item->item), | |
| std::move(item->params)); | |
| } | |
| } | |
| // Parses Parameters ([RFC8941] 4.2.3.2) | |
| absl::optional<Parameters> ReadParameters() { | |
| Parameters parameters; | |
| absl::flat_hash_set<std::string> keys; | |
| while (ConsumeChar(';')) { | |
| SkipWhitespaces(); | |
| absl::optional<std::string> name = ReadKey(); | |
| if (!name) return absl::nullopt; | |
| bool is_duplicate_key = !keys.insert(*name).second; | |
| Item value{true}; | |
| if (ConsumeChar('=')) { | |
| auto item = ReadBareItem(); | |
| if (!item) return absl::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). | |
| absl::optional<ParameterizedMember> ReadInnerList() { | |
| QUICHE_CHECK_EQ(version_, kFinal); | |
| if (!ConsumeChar('(')) return absl::nullopt; | |
| std::vector<ParameterizedItem> inner_list; | |
| while (true) { | |
| SkipWhitespaces(); | |
| if (ConsumeChar(')')) { | |
| absl::optional<Parameters> parameters; | |
| parameters = ReadParameters(); | |
| if (!parameters) return absl::nullopt; | |
| return ParameterizedMember(std::move(inner_list), true, | |
| std::move(*parameters)); | |
| } | |
| auto item = ReadItem(); | |
| if (!item) return absl::nullopt; | |
| inner_list.push_back(std::move(*item)); | |
| if (input_.empty() || (input_.front() != ' ' && input_.front() != ')')) | |
| return absl::nullopt; | |
| } | |
| QUICHE_NOTREACHED(); | |
| return absl::nullopt; | |
| } | |
| // Parses a Key ([SH09] 4.2.2, [RFC8941] 4.2.3.3). | |
| absl::optional<std::string> ReadKey() { | |
| if (version_ == kDraft09) { | |
| if (input_.empty() || !absl::ascii_islower(input_.front())) { | |
| LogParseError("ReadKey", "lcalpha"); | |
| return absl::nullopt; | |
| } | |
| } else { | |
| if (input_.empty() || | |
| (!absl::ascii_islower(input_.front()) && input_.front() != '*')) { | |
| LogParseError("ReadKey", "lcalpha | *"); | |
| return absl::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). | |
| absl::optional<Item> ReadToken() { | |
| if (input_.empty() || | |
| !(absl::ascii_isalpha(input_.front()) || input_.front() == '*')) { | |
| LogParseError("ReadToken", "ALPHA"); | |
| return absl::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). | |
| absl::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 absl::nullopt; | |
| } | |
| if (!is_decimal) { | |
| // [RFC8941] restricts the range of integers further. | |
| if (version_ == kFinal && i > 15) { | |
| LogParseError("ReadNumber", "integer too long"); | |
| return absl::nullopt; | |
| } | |
| } else { | |
| if (version_ != kFinal && i > 16) { | |
| LogParseError("ReadNumber", "float too long"); | |
| return absl::nullopt; | |
| } | |
| if (version_ == kFinal && decimal_position > 12) { | |
| LogParseError("ReadNumber", "decimal too long"); | |
| return absl::nullopt; | |
| } | |
| if (i - decimal_position > (version_ == kFinal ? 4 : 7)) { | |
| LogParseError("ReadNumber", "too many digits after decimal"); | |
| return absl::nullopt; | |
| } | |
| if (i == decimal_position) { | |
| LogParseError("ReadNumber", "no digits after decimal"); | |
| return absl::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 absl::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 absl::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). | |
| absl::optional<Item> ReadString() { | |
| std::string s; | |
| if (!ConsumeChar('"')) { | |
| LogParseError("ReadString", "'\"'"); | |
| return absl::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 absl::nullopt; | |
| } | |
| if (input_[i] == '"' || input_[i] == '\\') break; | |
| } | |
| if (i == input_.size()) { | |
| QUICHE_DVLOG(1) << "ReadString: missing closing '\"'"; | |
| return absl::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 absl::nullopt; | |
| } | |
| if (input_[0] != '"' && input_[0] != '\\') { | |
| QUICHE_DVLOG(1) << "ReadString: invalid escape"; | |
| return absl::nullopt; | |
| } | |
| s.push_back(input_.front()); | |
| input_.remove_prefix(1); | |
| } | |
| } | |
| return s; | |
| } | |
| // Parses a Byte Sequence ([SH09] 4.2.11, [RFC8941] 4.2.7). | |
| absl::optional<Item> ReadByteSequence() { | |
| char delimiter = (version_ == kDraft09 ? '*' : ':'); | |
| if (!ConsumeChar(delimiter)) { | |
| LogParseError("ReadByteSequence", "delimiter"); | |
| return absl::nullopt; | |
| } | |
| size_t len = input_.find(delimiter); | |
| if (len == absl::string_view::npos) { | |
| QUICHE_DVLOG(1) << "ReadByteSequence: missing closing delimiter"; | |
| return absl::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 absl::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. | |
| absl::optional<Item> ReadBoolean() { | |
| if (!ConsumeChar('?')) { | |
| LogParseError("ReadBoolean", "'?'"); | |
| return absl::nullopt; | |
| } | |
| if (ConsumeChar('1')) { | |
| return Item(true); | |
| } | |
| if (ConsumeChar('0')) { | |
| return Item(false); | |
| } | |
| return absl::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 (value.GetString().empty() || | |
| !(absl::ascii_isalpha(value.GetString().front()) || | |
| value.GetString().front() == '*')) | |
| return false; | |
| if (value.GetString().find_first_not_of(kTokenChars) != std::string::npos) | |
| 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 | |
| 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(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( | |
| 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(std::vector<DictionaryMember> members) | |
| : members_(std::move(members)) {} | |
| Dictionary::~Dictionary() = default; | |
| std::vector<DictionaryMember>::iterator Dictionary::begin() { | |
| return members_.begin(); | |
| } | |
| std::vector<DictionaryMember>::const_iterator Dictionary::begin() const { | |
| return members_.begin(); | |
| } | |
| std::vector<DictionaryMember>::iterator Dictionary::end() { | |
| return members_.end(); | |
| } | |
| std::vector<DictionaryMember>::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 = absl::c_find_if( | |
| members_, [key](const auto& member) { return member.first == key; }); | |
| if (it != members_.end()) return it->second; | |
| members_.push_back({std::string(key), ParameterizedMember()}); | |
| return members_.back().second; | |
| } | |
| ParameterizedMember& Dictionary::at(absl::string_view key) { | |
| auto it = absl::c_find_if( | |
| members_, [key](const auto& member) { return member.first == key; }); | |
| QUICHE_CHECK(it != members_.end()) << "Provided key not found in dictionary"; | |
| return it->second; | |
| } | |
| const ParameterizedMember& Dictionary::at(absl::string_view key) const { | |
| auto it = absl::c_find_if( | |
| members_, [key](const auto& member) { return member.first == key; }); | |
| QUICHE_CHECK(it != members_.end()) << "Provided key not found in dictionary"; | |
| return it->second; | |
| } | |
| bool Dictionary::empty() const { return members_.empty(); } | |
| std::size_t Dictionary::size() const { return members_.size(); } | |
| bool Dictionary::contains(absl::string_view key) const { | |
| for (auto& member : members_) { | |
| if (member.first == key) return true; | |
| } | |
| return false; | |
| } | |
| absl::optional<ParameterizedItem> ParseItem(absl::string_view str) { | |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); | |
| absl::optional<ParameterizedItem> item = parser.ReadItem(); | |
| if (item && parser.FinishParsing()) return item; | |
| return absl::nullopt; | |
| } | |
| absl::optional<Item> ParseBareItem(absl::string_view str) { | |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); | |
| absl::optional<Item> item = parser.ReadBareItem(); | |
| if (item && parser.FinishParsing()) return item; | |
| return absl::nullopt; | |
| } | |
| absl::optional<ParameterisedList> ParseParameterisedList( | |
| absl::string_view str) { | |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kDraft09); | |
| absl::optional<ParameterisedList> param_list = parser.ReadParameterisedList(); | |
| if (param_list && parser.FinishParsing()) return param_list; | |
| return absl::nullopt; | |
| } | |
| absl::optional<ListOfLists> ParseListOfLists(absl::string_view str) { | |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kDraft09); | |
| absl::optional<ListOfLists> list_of_lists = parser.ReadListOfLists(); | |
| if (list_of_lists && parser.FinishParsing()) return list_of_lists; | |
| return absl::nullopt; | |
| } | |
| absl::optional<List> ParseList(absl::string_view str) { | |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); | |
| absl::optional<List> list = parser.ReadList(); | |
| if (list && parser.FinishParsing()) return list; | |
| return absl::nullopt; | |
| } | |
| absl::optional<Dictionary> ParseDictionary(const absl::string_view& str) { | |
| StructuredHeaderParser parser(str, StructuredHeaderParser::kFinal); | |
| absl::optional<Dictionary> dictionary = parser.ReadDictionary(); | |
| if (dictionary && parser.FinishParsing()) return dictionary; | |
| return absl::nullopt; | |
| } | |
| absl::optional<std::string> SerializeItem(const Item& value) { | |
| StructuredHeaderSerializer s; | |
| if (s.WriteItem(ParameterizedItem(value, {}))) return s.Output(); | |
| return absl::nullopt; | |
| } | |
| absl::optional<std::string> SerializeItem(const ParameterizedItem& value) { | |
| StructuredHeaderSerializer s; | |
| if (s.WriteItem(value)) return s.Output(); | |
| return absl::nullopt; | |
| } | |
| absl::optional<std::string> SerializeList(const List& value) { | |
| StructuredHeaderSerializer s; | |
| if (s.WriteList(value)) return s.Output(); | |
| return absl::nullopt; | |
| } | |
| absl::optional<std::string> SerializeDictionary(const Dictionary& value) { | |
| StructuredHeaderSerializer s; | |
| if (s.WriteDictionary(value)) return s.Output(); | |
| return absl::nullopt; | |
| } | |
| } // namespace structured_headers | |
| } // namespace quiche |