| // 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. | 
 |  | 
 | #ifndef QUICHE_COMMON_STRUCTURED_HEADERS_H_ | 
 | #define QUICHE_COMMON_STRUCTURED_HEADERS_H_ | 
 |  | 
 | #include <cstddef> | 
 | #include <cstdint> | 
 | #include <map> | 
 | #include <optional> | 
 | #include <string> | 
 | #include <tuple> | 
 | #include <utility> | 
 | #include <variant> | 
 | #include <vector> | 
 |  | 
 | #include "absl/strings/string_view.h" | 
 | #include "quiche/common/platform/api/quiche_export.h" | 
 | #include "quiche/common/platform/api/quiche_logging.h" | 
 |  | 
 | namespace quiche { | 
 | namespace structured_headers { | 
 |  | 
 | // This file implements parsing of HTTP structured headers, as defined in | 
 | // RFC8941 (https://www.rfc-editor.org/rfc/rfc8941.html). For compatibility with | 
 | // the shipped implementation of Web Packaging, this file also supports a | 
 | // previous revision of the standard, referred to here as "Draft 9". | 
 | // (https://datatracker.ietf.org/doc/draft-ietf-httpbis-header-structure/09/) | 
 | // | 
 | // The major difference between the two revisions is in the various list | 
 | // formats: Draft 9 describes "parameterised lists" and "lists-of-lists", while | 
 | // the final RFC uses a single "list" syntax, whose members may be inner lists. | 
 | // There should be no ambiguity, however, as the code which calls this parser | 
 | // should be expecting only a single type for a given header. | 
 | // | 
 | // References within the code are tagged with either [SH09] or [RFC8941], | 
 | // depending on which revision they refer to. | 
 | // | 
 | // Currently supported data types are: | 
 | //  Item: | 
 | //   integer: 123 | 
 | //   string: "abc" | 
 | //   token: abc | 
 | //   byte sequence: *YWJj* | 
 | //  Parameterised list: abc_123;a=1;b=2; cdef_456, ghi;q="9";r="w" | 
 | //  List-of-lists: "foo";"bar", "baz", "bat"; "one" | 
 | //  List: "foo", "bar", "It was the best of times." | 
 | //        ("foo" "bar"), ("baz"), ("bat" "one"), () | 
 | //        abc;a=1;b=2; cde_456, (ghi jkl);q="9";r=w | 
 | //  Dictionary: a=(1 2), b=3, c=4;aa=bb, d=(5 6);valid=?0 | 
 | // | 
 | // Functions are provided to parse each of these, which are intended to be | 
 | // called with the complete value of an HTTP header (that is, any | 
 | // sub-structure will be handled internally by the parser; the exported | 
 | // functions are not intended to be called on partial header strings.) Input | 
 | // values should be ASCII byte strings (non-ASCII characters should not be | 
 | // present in Structured Header values, and will cause the entire header to fail | 
 | // to parse.) | 
 |  | 
 | class QUICHE_EXPORT Item { | 
 |  public: | 
 |   enum ItemType { | 
 |     kNullType, | 
 |     kIntegerType, | 
 |     kDecimalType, | 
 |     kStringType, | 
 |     kTokenType, | 
 |     kByteSequenceType, | 
 |     kBooleanType | 
 |   }; | 
 |   Item(); | 
 |   explicit Item(int64_t value); | 
 |   explicit Item(double value); | 
 |   explicit Item(bool value); | 
 |  | 
 |   // Constructors for string-like items: Strings, Tokens and Byte Sequences. | 
 |   Item(const char* value, Item::ItemType type = kStringType); | 
 |   Item(std::string value, Item::ItemType type = kStringType); | 
 |  | 
 |   QUICHE_EXPORT friend bool operator==(const Item& lhs, const Item& rhs); | 
 |   inline friend bool operator!=(const Item& lhs, const Item& rhs) { | 
 |     return !(lhs == rhs); | 
 |   } | 
 |  | 
 |   bool is_null() const { return Type() == kNullType; } | 
 |   bool is_integer() const { return Type() == kIntegerType; } | 
 |   bool is_decimal() const { return Type() == kDecimalType; } | 
 |   bool is_string() const { return Type() == kStringType; } | 
 |   bool is_token() const { return Type() == kTokenType; } | 
 |   bool is_byte_sequence() const { return Type() == kByteSequenceType; } | 
 |   bool is_boolean() const { return Type() == kBooleanType; } | 
 |  | 
 |   int64_t GetInteger() const { | 
 |     const auto* value = std::get_if<int64_t>(&value_); | 
 |     QUICHE_CHECK(value); | 
 |     return *value; | 
 |   } | 
 |   double GetDecimal() const { | 
 |     const auto* value = std::get_if<double>(&value_); | 
 |     QUICHE_CHECK(value); | 
 |     return *value; | 
 |   } | 
 |   bool GetBoolean() const { | 
 |     const auto* value = std::get_if<bool>(&value_); | 
 |     QUICHE_CHECK(value); | 
 |     return *value; | 
 |   } | 
 |   // TODO(iclelland): Split up accessors for String, Token and Byte Sequence. | 
 |   const std::string& GetString() const { | 
 |     struct Visitor { | 
 |       const std::string* operator()(const std::monostate&) { return nullptr; } | 
 |       const std::string* operator()(const int64_t&) { return nullptr; } | 
 |       const std::string* operator()(const double&) { return nullptr; } | 
 |       const std::string* operator()(const std::string& value) { return &value; } | 
 |       const std::string* operator()(const bool&) { return nullptr; } | 
 |     }; | 
 |     const std::string* value = std::visit(Visitor(), value_); | 
 |     QUICHE_CHECK(value); | 
 |     return *value; | 
 |   } | 
 |  | 
 |   // Transfers ownership of the underlying String, Token, or Byte Sequence. | 
 |   std::string TakeString() && { | 
 |     struct Visitor { | 
 |       std::string* operator()(std::monostate&) { return nullptr; } | 
 |       std::string* operator()(int64_t&) { return nullptr; } | 
 |       std::string* operator()(double&) { return nullptr; } | 
 |       std::string* operator()(std::string& value) { return &value; } | 
 |       std::string* operator()(bool&) { return nullptr; } | 
 |     }; | 
 |     std::string* value = std::visit(Visitor(), value_); | 
 |     QUICHE_CHECK(value); | 
 |     return std::move(*value); | 
 |   } | 
 |  | 
 |   ItemType Type() const { return static_cast<ItemType>(value_.index()); } | 
 |  | 
 |  private: | 
 |   std::variant<std::monostate, int64_t, double, std::string, std::string, | 
 |                std::string, bool> | 
 |       value_; | 
 | }; | 
 |  | 
 | // Returns a human-readable representation of an ItemType. | 
 | QUICHE_EXPORT absl::string_view ItemTypeToString(Item::ItemType type); | 
 |  | 
 | // Returns `true` if the string is a valid Token value. | 
 | QUICHE_EXPORT bool IsValidToken(absl::string_view str); | 
 |  | 
 | // Holds a ParameterizedIdentifier (draft 9 only). The contained Item must be a | 
 | // Token, and there may be any number of parameters. Parameter ordering is not | 
 | // significant. | 
 | struct QUICHE_EXPORT ParameterisedIdentifier { | 
 |   using Parameters = std::map<std::string, Item>; | 
 |  | 
 |   Item identifier; | 
 |   Parameters params; | 
 |  | 
 |   ParameterisedIdentifier(); | 
 |   ParameterisedIdentifier(const ParameterisedIdentifier&); | 
 |   ParameterisedIdentifier& operator=(const ParameterisedIdentifier&); | 
 |   ParameterisedIdentifier(Item, Parameters); | 
 |   ~ParameterisedIdentifier(); | 
 | }; | 
 |  | 
 | inline bool operator==(const ParameterisedIdentifier& lhs, | 
 |                        const ParameterisedIdentifier& rhs) { | 
 |   return std::tie(lhs.identifier, lhs.params) == | 
 |          std::tie(rhs.identifier, rhs.params); | 
 | } | 
 |  | 
 | using Parameters = std::vector<std::pair<std::string, Item>>; | 
 |  | 
 | struct QUICHE_EXPORT ParameterizedItem { | 
 |   Item item; | 
 |   Parameters params; | 
 |  | 
 |   ParameterizedItem(); | 
 |   ParameterizedItem(const ParameterizedItem&); | 
 |   ParameterizedItem& operator=(const ParameterizedItem&); | 
 |   ParameterizedItem(Item, Parameters); | 
 |   ~ParameterizedItem(); | 
 | }; | 
 |  | 
 | inline bool operator==(const ParameterizedItem& lhs, | 
 |                        const ParameterizedItem& rhs) { | 
 |   return std::tie(lhs.item, lhs.params) == std::tie(rhs.item, rhs.params); | 
 | } | 
 |  | 
 | inline bool operator!=(const ParameterizedItem& lhs, | 
 |                        const ParameterizedItem& rhs) { | 
 |   return !(lhs == rhs); | 
 | } | 
 |  | 
 | // Holds a ParameterizedMember, which may be either an single Item, or an Inner | 
 | // List of ParameterizedItems, along with any number of parameters. Parameter | 
 | // ordering is significant. | 
 | struct QUICHE_EXPORT ParameterizedMember { | 
 |   std::vector<ParameterizedItem> member; | 
 |   // If false, then |member| should only hold one Item. | 
 |   bool member_is_inner_list = false; | 
 |  | 
 |   Parameters params; | 
 |  | 
 |   ParameterizedMember(); | 
 |   ParameterizedMember(const ParameterizedMember&); | 
 |   ParameterizedMember& operator=(const ParameterizedMember&); | 
 |   ParameterizedMember(std::vector<ParameterizedItem>, bool member_is_inner_list, | 
 |                       Parameters); | 
 |   // Shorthand constructor for a member which is an inner list. | 
 |   ParameterizedMember(std::vector<ParameterizedItem>, Parameters); | 
 |   // Shorthand constructor for a member which is a single Item. | 
 |   ParameterizedMember(Item, Parameters); | 
 |   ~ParameterizedMember(); | 
 | }; | 
 |  | 
 | inline bool operator==(const ParameterizedMember& lhs, | 
 |                        const ParameterizedMember& rhs) { | 
 |   return std::tie(lhs.member, lhs.member_is_inner_list, lhs.params) == | 
 |          std::tie(rhs.member, rhs.member_is_inner_list, rhs.params); | 
 | } | 
 |  | 
 | using DictionaryMember = std::pair<std::string, ParameterizedMember>; | 
 |  | 
 | // Structured Headers RFC8941 Dictionary. | 
 | class QUICHE_EXPORT Dictionary { | 
 |  public: | 
 |   using iterator = std::vector<DictionaryMember>::iterator; | 
 |   using const_iterator = std::vector<DictionaryMember>::const_iterator; | 
 |  | 
 |   Dictionary(); | 
 |   Dictionary(const Dictionary&); | 
 |   Dictionary(Dictionary&&); | 
 |   explicit Dictionary(std::vector<DictionaryMember> members); | 
 |   ~Dictionary(); | 
 |   Dictionary& operator=(const Dictionary&) = default; | 
 |   Dictionary& operator=(Dictionary&&) = default; | 
 |   iterator begin(); | 
 |   const_iterator begin() const; | 
 |   iterator end(); | 
 |   const_iterator end() const; | 
 |  | 
 |   // operator[](size_t) and at(size_t) will both abort the program in case of | 
 |   // out of bounds access. | 
 |   ParameterizedMember& operator[](std::size_t idx); | 
 |   const ParameterizedMember& operator[](std::size_t idx) const; | 
 |   ParameterizedMember& at(std::size_t idx); | 
 |   const ParameterizedMember& at(std::size_t idx) const; | 
 |  | 
 |   // Consistent with std::map, if |key| does not exist in the Dictionary, then | 
 |   // operator[](absl::string_view) will create an entry for it, but at() will | 
 |   // abort the entire program. | 
 |   ParameterizedMember& operator[](absl::string_view key); | 
 |   ParameterizedMember& at(absl::string_view key); | 
 |   const ParameterizedMember& at(absl::string_view key) const; | 
 |  | 
 |   const_iterator find(absl::string_view key) const; | 
 |   iterator find(absl::string_view key); | 
 |  | 
 |   void clear(); | 
 |  | 
 |   bool empty() const; | 
 |   std::size_t size() const; | 
 |   bool contains(absl::string_view key) const; | 
 |   friend bool operator==(const Dictionary& lhs, const Dictionary& rhs); | 
 |   friend bool operator!=(const Dictionary& lhs, const Dictionary& rhs); | 
 |  | 
 |  private: | 
 |   // Uses a vector to hold pairs of key and dictionary member. This makes | 
 |   // look up by index and serialization much easier. | 
 |   std::vector<DictionaryMember> members_; | 
 | }; | 
 |  | 
 | inline bool operator==(const Dictionary& lhs, const Dictionary& rhs) { | 
 |   return lhs.members_ == rhs.members_; | 
 | } | 
 |  | 
 | inline bool operator!=(const Dictionary& lhs, const Dictionary& rhs) { | 
 |   return !(lhs == rhs); | 
 | } | 
 |  | 
 | // Structured Headers Draft 09 Parameterised List. | 
 | using ParameterisedList = std::vector<ParameterisedIdentifier>; | 
 | // Structured Headers Draft 09 List of Lists. | 
 | using ListOfLists = std::vector<std::vector<Item>>; | 
 | // Structured Headers RFC8941 List. | 
 | using List = std::vector<ParameterizedMember>; | 
 |  | 
 | // Returns the result of parsing the header value as an Item, if it can be | 
 | // parsed as one, or nullopt if it cannot. Note that this uses the Draft 15 | 
 | // parsing rules, and so applies tighter range limits to integers. | 
 | QUICHE_EXPORT std::optional<ParameterizedItem> ParseItem(absl::string_view str); | 
 |  | 
 | // Returns the result of parsing the header value as an Item with no parameters, | 
 | // or nullopt if it cannot. Note that this uses the Draft 15 parsing rules, and | 
 | // so applies tighter range limits to integers. | 
 | QUICHE_EXPORT std::optional<Item> ParseBareItem(absl::string_view str); | 
 |  | 
 | // Returns the result of parsing the header value as a Parameterised List, if it | 
 | // can be parsed as one, or nullopt if it cannot. Note that parameter keys will | 
 | // be returned as strings, which are guaranteed to be ASCII-encoded. List items, | 
 | // as well as parameter values, will be returned as Items. This method uses the | 
 | // Draft 09 parsing rules for Items, so integers have the 64-bit int range. | 
 | // Structured-Headers Draft 09 only. | 
 | QUICHE_EXPORT std::optional<ParameterisedList> ParseParameterisedList( | 
 |     absl::string_view str); | 
 |  | 
 | // Returns the result of parsing the header value as a List of Lists, if it can | 
 | // be parsed as one, or nullopt if it cannot. Inner list items will be returned | 
 | // as Items. This method uses the Draft 09 parsing rules for Items, so integers | 
 | // have the 64-bit int range. | 
 | // Structured-Headers Draft 09 only. | 
 | QUICHE_EXPORT std::optional<ListOfLists> ParseListOfLists( | 
 |     absl::string_view str); | 
 |  | 
 | // Returns the result of parsing the header value as a general List, if it can | 
 | // be parsed as one, or nullopt if it cannot. | 
 | // Structured-Headers Draft 15 only. | 
 | QUICHE_EXPORT std::optional<List> ParseList(absl::string_view str); | 
 |  | 
 | // Returns the result of parsing the header value as a general Dictionary, if it | 
 | // can be parsed as one, or nullopt if it cannot. Structured-Headers Draft 15 | 
 | // only. | 
 | QUICHE_EXPORT std::optional<Dictionary> ParseDictionary(absl::string_view str); | 
 |  | 
 | // Serialization is implemented for Structured-Headers Draft 15 only. | 
 | QUICHE_EXPORT std::optional<std::string> SerializeItem(const Item& value); | 
 | QUICHE_EXPORT std::optional<std::string> SerializeItem( | 
 |     const ParameterizedItem& value); | 
 | QUICHE_EXPORT std::optional<std::string> SerializeList(const List& value); | 
 | QUICHE_EXPORT std::optional<std::string> SerializeDictionary( | 
 |     const Dictionary& value); | 
 |  | 
 | }  // namespace structured_headers | 
 | }  // namespace quiche | 
 |  | 
 | #endif  // QUICHE_COMMON_STRUCTURED_HEADERS_H_ |