Move structured headers to third_party so that the library can be used server side as well
Protected by only importing library to quiche not integrating into any production code.
PiperOrigin-RevId: 435111926
diff --git a/common/structured_headers.cc b/common/structured_headers.cc
new file mode 100644
index 0000000..aead6a3
--- /dev/null
+++ b/common/structured_headers.cc
@@ -0,0 +1,909 @@
+// 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 "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 "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()) {
+ 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) {
+ 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])) {
+ DVLOG(1) << "ReadString: non printable-ASCII character";
+ return absl::nullopt;
+ }
+ if (input_[i] == '"' || input_[i] == '\\') break;
+ }
+ if (i == input_.size()) {
+ 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()) {
+ DVLOG(1) << "ReadString: backslash at string end";
+ return absl::nullopt;
+ }
+ if (input_[0] != '"' && input_[0] != '\\') {
+ 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) {
+ 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)) {
+ 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) {
+ 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(const std::string& value, Item::ItemType type)
+ : type_(type), string_value_(value) {}
+Item::Item(std::string&& value, Item::ItemType type)
+ : type_(type), string_value_(std::move(value)) {
+ QUICHE_CHECK(type_ == kStringType || type_ == kTokenType ||
+ type_ == kByteSequenceType);
+}
+Item::Item(const char* value, Item::ItemType type)
+ : Item(std::string(value), type) {}
+Item::Item(int64_t value) : type_(kIntegerType), integer_value_(value) {}
+Item::Item(double value) : type_(kDecimalType), decimal_value_(value) {}
+Item::Item(bool value) : type_(kBooleanType), boolean_value_(value) {}
+
+bool operator==(const Item& lhs, const Item& rhs) {
+ if (lhs.type_ != rhs.type_) return false;
+ switch (lhs.type_) {
+ case Item::kNullType:
+ return true;
+ case Item::kStringType:
+ case Item::kTokenType:
+ case Item::kByteSequenceType:
+ return lhs.string_value_ == rhs.string_value_;
+ case Item::kIntegerType:
+ return lhs.integer_value_ == rhs.integer_value_;
+ case Item::kDecimalType:
+ return lhs.decimal_value_ == rhs.decimal_value_;
+ case Item::kBooleanType:
+ return lhs.boolean_value_ == rhs.boolean_value_;
+ }
+ QUICHE_NOTREACHED();
+ return false;
+}
+
+ParameterizedItem::ParameterizedItem(const ParameterizedItem&) = default;
+ParameterizedItem& ParameterizedItem::operator=(const ParameterizedItem&) =
+ default;
+ParameterizedItem::ParameterizedItem(Item id, const Parameters& ps)
+ : item(std::move(id)), params(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,
+ const Parameters& ps)
+ : member(std::move(id)),
+ member_is_inner_list(member_is_inner_list),
+ params(ps) {}
+ParameterizedMember::ParameterizedMember(std::vector<ParameterizedItem> id,
+ const Parameters& ps)
+ : member(std::move(id)), member_is_inner_list(true), params(ps) {}
+ParameterizedMember::ParameterizedMember(Item id, const Parameters& ps)
+ : member({{std::move(id), {}}}), member_is_inner_list(false), params(ps) {}
+ParameterizedMember::~ParameterizedMember() = default;
+
+ParameterisedIdentifier::ParameterisedIdentifier(
+ const ParameterisedIdentifier&) = default;
+ParameterisedIdentifier& ParameterisedIdentifier::operator=(
+ const ParameterisedIdentifier&) = default;
+ParameterisedIdentifier::ParameterisedIdentifier(Item id, const Parameters& ps)
+ : identifier(std::move(id)), params(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
diff --git a/common/structured_headers.h b/common/structured_headers.h
new file mode 100644
index 0000000..b5525db
--- /dev/null
+++ b/common/structured_headers.h
@@ -0,0 +1,306 @@
+// 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 <algorithm>
+#include <map>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "common/platform/api/quiche_export.h"
+#include "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_PRIVATE 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(const std::string& value, Item::ItemType type = kStringType);
+ Item(std::string&& value, Item::ItemType type = kStringType);
+
+ QUICHE_EXPORT_PRIVATE 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 {
+ QUICHE_CHECK_EQ(type_, kIntegerType);
+ return integer_value_;
+ }
+ double GetDecimal() const {
+ QUICHE_CHECK_EQ(type_, kDecimalType);
+ return decimal_value_;
+ }
+ bool GetBoolean() const {
+ QUICHE_CHECK_EQ(type_, kBooleanType);
+ return boolean_value_;
+ }
+ // TODO(iclelland): Split up accessors for String, Token and Byte Sequence.
+ const std::string& GetString() const {
+ QUICHE_CHECK(type_ == kStringType || type_ == kTokenType ||
+ type_ == kByteSequenceType);
+ return string_value_;
+ }
+
+ ItemType Type() const { return type_; }
+
+ private:
+ ItemType type_ = kNullType;
+ // TODO(iclelland): Make this class more memory-efficient, replacing the
+ // values here with a union or std::variant (when available).
+ int64_t integer_value_ = 0;
+ std::string string_value_;
+ double decimal_value_;
+ bool boolean_value_;
+};
+
+// 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_PRIVATE ParameterisedIdentifier {
+ using Parameters = std::map<std::string, Item>;
+
+ Item identifier;
+ Parameters params;
+
+ ParameterisedIdentifier(const ParameterisedIdentifier&);
+ ParameterisedIdentifier& operator=(const ParameterisedIdentifier&);
+ ParameterisedIdentifier(Item, const 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_PRIVATE ParameterizedItem {
+ Item item;
+ Parameters params;
+
+ ParameterizedItem(const ParameterizedItem&);
+ ParameterizedItem& operator=(const ParameterizedItem&);
+ ParameterizedItem(Item, const 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_PRIVATE 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,
+ const Parameters&);
+ // Shorthand constructor for a member which is an inner list.
+ ParameterizedMember(std::vector<ParameterizedItem>, const Parameters&);
+ // Shorthand constructor for a member which is a single Item.
+ ParameterizedMember(Item, const 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_PRIVATE Dictionary {
+ public:
+ using iterator = std::vector<DictionaryMember>::iterator;
+ using const_iterator = std::vector<DictionaryMember>::const_iterator;
+
+ Dictionary();
+ Dictionary(const Dictionary&);
+ explicit Dictionary(std::vector<DictionaryMember> members);
+ ~Dictionary();
+ Dictionary& operator=(const 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;
+
+ 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_PRIVATE absl::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_PRIVATE absl::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_PRIVATE absl::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_PRIVATE absl::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_PRIVATE absl::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_PRIVATE absl::optional<Dictionary> ParseDictionary(
+ const absl::string_view& str);
+
+// Serialization is implemented for Structured-Headers Draft 15 only.
+QUICHE_EXPORT_PRIVATE absl::optional<std::string> SerializeItem(
+ const Item& value);
+QUICHE_EXPORT_PRIVATE absl::optional<std::string> SerializeItem(
+ const ParameterizedItem& value);
+QUICHE_EXPORT_PRIVATE absl::optional<std::string> SerializeList(
+ const List& value);
+QUICHE_EXPORT_PRIVATE absl::optional<std::string> SerializeDictionary(
+ const Dictionary& value);
+
+} // namespace structured_headers
+} // namespace quiche
+
+#endif // QUICHE_COMMON_STRUCTURED_HEADERS_H_
diff --git a/common/structured_headers_fuzzer.cc b/common/structured_headers_fuzzer.cc
new file mode 100644
index 0000000..2201881
--- /dev/null
+++ b/common/structured_headers_fuzzer.cc
@@ -0,0 +1,22 @@
+// Copyright 2018 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 "absl/strings/string_view.h"
+#include "common/structured_headers.h"
+
+namespace quiche {
+namespace structured_headers {
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ absl::string_view input(reinterpret_cast<const char*>(data), size);
+ ParseItem(input);
+ ParseListOfLists(input);
+ ParseList(input);
+ ParseDictionary(input);
+ ParseParameterisedList(input);
+ return 0;
+}
+
+} // namespace structured_headers
+} // namespace quiche
diff --git a/common/structured_headers_generated_test.cc b/common/structured_headers_generated_test.cc
new file mode 100644
index 0000000..f2f40b8
--- /dev/null
+++ b/common/structured_headers_generated_test.cc
@@ -0,0 +1,3944 @@
+// 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 "common/platform/api/quiche_test.h"
+#include "common/structured_headers.h"
+
+// This file contains tests cases for the Structured Header parser and
+// serializer, taken from the public test case repository at
+// https://github.com/httpwg/structured-field-tests. All of the tests are named,
+// so a given test case can be found in the JSON files in that repository by
+// searching for the test name. This file is generated, with the test cases
+// being automatically translated from the JSON source to C++ unit tests. Please
+// do not modify, as the contents will be overwritten when this is re-generated.
+
+// Generated on 2022-03-15 from structured-field-tests.git @
+// faed1f92942abd4fb5d61b1f9f0dc359f499f1d7.
+
+namespace quiche {
+namespace structured_headers {
+namespace {
+
+// Helpers to make test cases clearer
+
+Item Integer(int64_t value) { return Item(value); }
+
+std::pair<std::string, Item> BooleanParam(std::string key, bool value) {
+ return std::make_pair(key, Item(value));
+}
+
+std::pair<std::string, Item> DoubleParam(std::string key, double value) {
+ return std::make_pair(key, Item(value));
+}
+
+std::pair<std::string, Item> Param(std::string key, int64_t value) {
+ return std::make_pair(key, Item(value));
+}
+
+std::pair<std::string, Item> Param(std::string key, std::string value) {
+ return std::make_pair(key, Item(value));
+}
+
+std::pair<std::string, Item> TokenParam(std::string key, std::string value) {
+ return std::make_pair(key, Item(value, Item::kTokenType));
+}
+
+const struct ParameterizedItemTestCase {
+ const char* name;
+ const char* raw;
+ size_t raw_len;
+ const absl::optional<ParameterizedItem>
+ expected; // nullopt if parse error is expected.
+ const char* canonical; // nullptr if parse error is expected, or if canonical
+ // format is identical to raw.
+} parameterized_item_test_cases[] = {
+ // binary.json
+ {"basic binary",
+ ":aGVsbG8=:",
+ 10,
+ {{Item("hello", Item::kByteSequenceType), {}}},
+ nullptr},
+ {"empty binary",
+ "::",
+ 2,
+ {{Item("", Item::kByteSequenceType), {}}},
+ nullptr},
+ {"bad paddding",
+ ":aGVsbG8:",
+ 9,
+ {{Item("hello", Item::kByteSequenceType), {}}},
+ ":aGVsbG8=:"},
+ {"bad end delimiter", ":aGVsbG8=", 9, absl::nullopt, nullptr},
+ {"extra whitespace", ":aGVsb G8=:", 11, absl::nullopt, nullptr},
+ {"extra chars", ":aGVsbG!8=:", 11, absl::nullopt, nullptr},
+ {"suffix chars", ":aGVsbG8=!:", 11, absl::nullopt, nullptr},
+ {"non-zero pad bits",
+ ":iZ==:",
+ 6,
+ {{Item("\211", Item::kByteSequenceType), {}}},
+ ":iQ==:"},
+ {"non-ASCII binary",
+ ":/+Ah:",
+ 6,
+ {{Item("\377\340!", Item::kByteSequenceType), {}}},
+ nullptr},
+ {"base64url binary", ":_-Ah:", 6, absl::nullopt, nullptr},
+ // boolean.json
+ {"basic true boolean", "?1", 2, {{Item(true), {}}}, nullptr},
+ {"basic false boolean", "?0", 2, {{Item(false), {}}}, nullptr},
+ {"unknown boolean", "?Q", 2, absl::nullopt, nullptr},
+ {"whitespace boolean", "? 1", 3, absl::nullopt, nullptr},
+ {"negative zero boolean", "?-0", 3, absl::nullopt, nullptr},
+ {"T boolean", "?T", 2, absl::nullopt, nullptr},
+ {"F boolean", "?F", 2, absl::nullopt, nullptr},
+ {"t boolean", "?t", 2, absl::nullopt, nullptr},
+ {"f boolean", "?f", 2, absl::nullopt, nullptr},
+ {"spelled-out True boolean", "?True", 5, absl::nullopt, nullptr},
+ {"spelled-out False boolean", "?False", 6, absl::nullopt, nullptr},
+ // examples.json
+ {"Foo-Example",
+ "2; foourl=\"https://foo.example.com/\"",
+ 36,
+ {{Integer(2), {Param("foourl", "https://foo.example.com/")}}},
+ "2;foourl=\"https://foo.example.com/\""},
+ {"Example-IntHeader",
+ "1; a; b=?0",
+ 10,
+ {{Integer(1), {BooleanParam("a", true), BooleanParam("b", false)}}},
+ "1;a;b=?0"},
+ {"Example-IntItemHeader", "5", 1, {{Integer(5), {}}}, nullptr},
+ {"Example-IntItemHeader (params)",
+ "5; foo=bar",
+ 10,
+ {{Integer(5), {TokenParam("foo", "bar")}}},
+ "5;foo=bar"},
+ {"Example-IntegerHeader", "42", 2, {{Integer(42), {}}}, nullptr},
+ {"Example-FloatHeader", "4.5", 3, {{Item(4.500000), {}}}, nullptr},
+ {"Example-StringHeader",
+ "\"hello world\"",
+ 13,
+ {{Item("hello world"), {}}},
+ nullptr},
+ {"Example-BinaryHdr",
+ ":cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==:",
+ 46,
+ {{Item("pretend this is binary content.", Item::kByteSequenceType), {}}},
+ nullptr},
+ {"Example-BoolHdr", "?1", 2, {{Item(true), {}}}, nullptr},
+ // item.json
+ {"empty item", "", 0, absl::nullopt, nullptr},
+ {"leading space", " \t 1", 4, absl::nullopt, nullptr},
+ {"trailing space", "1 \t ", 4, absl::nullopt, nullptr},
+ {"leading and trailing space", " 1 ", 5, {{Integer(1), {}}}, "1"},
+ {"leading and trailing whitespace", " 1 ", 8, {{Integer(1), {}}}, "1"},
+ // number-generated.json
+ {"1 digits of zero", "0", 1, {{Integer(0), {}}}, "0"},
+ {"1 digit small integer", "1", 1, {{Integer(1), {}}}, nullptr},
+ {"1 digit large integer", "9", 1, {{Integer(9), {}}}, nullptr},
+ {"2 digits of zero", "00", 2, {{Integer(0), {}}}, "0"},
+ {"2 digit small integer", "11", 2, {{Integer(11), {}}}, nullptr},
+ {"2 digit large integer", "99", 2, {{Integer(99), {}}}, nullptr},
+ {"3 digits of zero", "000", 3, {{Integer(0), {}}}, "0"},
+ {"3 digit small integer", "111", 3, {{Integer(111), {}}}, nullptr},
+ {"3 digit large integer", "999", 3, {{Integer(999), {}}}, nullptr},
+ {"4 digits of zero", "0000", 4, {{Integer(0), {}}}, "0"},
+ {"4 digit small integer", "1111", 4, {{Integer(1111), {}}}, nullptr},
+ {"4 digit large integer", "9999", 4, {{Integer(9999), {}}}, nullptr},
+ {"5 digits of zero", "00000", 5, {{Integer(0), {}}}, "0"},
+ {"5 digit small integer", "11111", 5, {{Integer(11111), {}}}, nullptr},
+ {"5 digit large integer", "99999", 5, {{Integer(99999), {}}}, nullptr},
+ {"6 digits of zero", "000000", 6, {{Integer(0), {}}}, "0"},
+ {"6 digit small integer", "111111", 6, {{Integer(111111), {}}}, nullptr},
+ {"6 digit large integer", "999999", 6, {{Integer(999999), {}}}, nullptr},
+ {"7 digits of zero", "0000000", 7, {{Integer(0), {}}}, "0"},
+ {"7 digit small integer", "1111111", 7, {{Integer(1111111), {}}}, nullptr},
+ {"7 digit large integer", "9999999", 7, {{Integer(9999999), {}}}, nullptr},
+ {"8 digits of zero", "00000000", 8, {{Integer(0), {}}}, "0"},
+ {"8 digit small integer",
+ "11111111",
+ 8,
+ {{Integer(11111111), {}}},
+ nullptr},
+ {"8 digit large integer",
+ "99999999",
+ 8,
+ {{Integer(99999999), {}}},
+ nullptr},
+ {"9 digits of zero", "000000000", 9, {{Integer(0), {}}}, "0"},
+ {"9 digit small integer",
+ "111111111",
+ 9,
+ {{Integer(111111111), {}}},
+ nullptr},
+ {"9 digit large integer",
+ "999999999",
+ 9,
+ {{Integer(999999999), {}}},
+ nullptr},
+ {"10 digits of zero", "0000000000", 10, {{Integer(0), {}}}, "0"},
+ {"10 digit small integer",
+ "1111111111",
+ 10,
+ {{Integer(1111111111), {}}},
+ nullptr},
+ {"10 digit large integer",
+ "9999999999",
+ 10,
+ {{Integer(9999999999), {}}},
+ nullptr},
+ {"11 digits of zero", "00000000000", 11, {{Integer(0), {}}}, "0"},
+ {"11 digit small integer",
+ "11111111111",
+ 11,
+ {{Integer(11111111111), {}}},
+ nullptr},
+ {"11 digit large integer",
+ "99999999999",
+ 11,
+ {{Integer(99999999999), {}}},
+ nullptr},
+ {"12 digits of zero", "000000000000", 12, {{Integer(0), {}}}, "0"},
+ {"12 digit small integer",
+ "111111111111",
+ 12,
+ {{Integer(111111111111), {}}},
+ nullptr},
+ {"12 digit large integer",
+ "999999999999",
+ 12,
+ {{Integer(999999999999), {}}},
+ nullptr},
+ {"13 digits of zero", "0000000000000", 13, {{Integer(0), {}}}, "0"},
+ {"13 digit small integer",
+ "1111111111111",
+ 13,
+ {{Integer(1111111111111), {}}},
+ nullptr},
+ {"13 digit large integer",
+ "9999999999999",
+ 13,
+ {{Integer(9999999999999), {}}},
+ nullptr},
+ {"14 digits of zero", "00000000000000", 14, {{Integer(0), {}}}, "0"},
+ {"14 digit small integer",
+ "11111111111111",
+ 14,
+ {{Integer(11111111111111), {}}},
+ nullptr},
+ {"14 digit large integer",
+ "99999999999999",
+ 14,
+ {{Integer(99999999999999), {}}},
+ nullptr},
+ {"15 digits of zero", "000000000000000", 15, {{Integer(0), {}}}, "0"},
+ {"15 digit small integer",
+ "111111111111111",
+ 15,
+ {{Integer(111111111111111), {}}},
+ nullptr},
+ {"15 digit large integer",
+ "999999999999999",
+ 15,
+ {{Integer(999999999999999), {}}},
+ nullptr},
+ {"2 digit 0, 1 fractional small decimal",
+ "0.1",
+ 3,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"2 digit, 1 fractional 0 decimal",
+ "1.0",
+ 3,
+ {{Item(1.000000), {}}},
+ "1.0"},
+ {"2 digit, 1 fractional small decimal",
+ "1.1",
+ 3,
+ {{Item(1.100000), {}}},
+ nullptr},
+ {"2 digit, 1 fractional large decimal",
+ "9.9",
+ 3,
+ {{Item(9.900000), {}}},
+ nullptr},
+ {"3 digit 0, 2 fractional small decimal",
+ "0.11",
+ 4,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"3 digit, 2 fractional 0 decimal",
+ "1.00",
+ 4,
+ {{Item(1.000000), {}}},
+ "1.0"},
+ {"3 digit, 2 fractional small decimal",
+ "1.11",
+ 4,
+ {{Item(1.110000), {}}},
+ nullptr},
+ {"3 digit, 2 fractional large decimal",
+ "9.99",
+ 4,
+ {{Item(9.990000), {}}},
+ nullptr},
+ {"4 digit 0, 3 fractional small decimal",
+ "0.111",
+ 5,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"4 digit, 3 fractional 0 decimal",
+ "1.000",
+ 5,
+ {{Item(1.000000), {}}},
+ "1.0"},
+ {"4 digit, 3 fractional small decimal",
+ "1.111",
+ 5,
+ {{Item(1.111000), {}}},
+ nullptr},
+ {"4 digit, 3 fractional large decimal",
+ "9.999",
+ 5,
+ {{Item(9.999000), {}}},
+ nullptr},
+ {"3 digit 0, 1 fractional small decimal",
+ "00.1",
+ 4,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"3 digit, 1 fractional 0 decimal",
+ "11.0",
+ 4,
+ {{Item(11.000000), {}}},
+ "11.0"},
+ {"3 digit, 1 fractional small decimal",
+ "11.1",
+ 4,
+ {{Item(11.100000), {}}},
+ nullptr},
+ {"3 digit, 1 fractional large decimal",
+ "99.9",
+ 4,
+ {{Item(99.900000), {}}},
+ nullptr},
+ {"4 digit 0, 2 fractional small decimal",
+ "00.11",
+ 5,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"4 digit, 2 fractional 0 decimal",
+ "11.00",
+ 5,
+ {{Item(11.000000), {}}},
+ "11.0"},
+ {"4 digit, 2 fractional small decimal",
+ "11.11",
+ 5,
+ {{Item(11.110000), {}}},
+ nullptr},
+ {"4 digit, 2 fractional large decimal",
+ "99.99",
+ 5,
+ {{Item(99.990000), {}}},
+ nullptr},
+ {"5 digit 0, 3 fractional small decimal",
+ "00.111",
+ 6,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"5 digit, 3 fractional 0 decimal",
+ "11.000",
+ 6,
+ {{Item(11.000000), {}}},
+ "11.0"},
+ {"5 digit, 3 fractional small decimal",
+ "11.111",
+ 6,
+ {{Item(11.111000), {}}},
+ nullptr},
+ {"5 digit, 3 fractional large decimal",
+ "99.999",
+ 6,
+ {{Item(99.999000), {}}},
+ nullptr},
+ {"4 digit 0, 1 fractional small decimal",
+ "000.1",
+ 5,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"4 digit, 1 fractional 0 decimal",
+ "111.0",
+ 5,
+ {{Item(111.000000), {}}},
+ "111.0"},
+ {"4 digit, 1 fractional small decimal",
+ "111.1",
+ 5,
+ {{Item(111.100000), {}}},
+ nullptr},
+ {"4 digit, 1 fractional large decimal",
+ "999.9",
+ 5,
+ {{Item(999.900000), {}}},
+ nullptr},
+ {"5 digit 0, 2 fractional small decimal",
+ "000.11",
+ 6,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"5 digit, 2 fractional 0 decimal",
+ "111.00",
+ 6,
+ {{Item(111.000000), {}}},
+ "111.0"},
+ {"5 digit, 2 fractional small decimal",
+ "111.11",
+ 6,
+ {{Item(111.110000), {}}},
+ nullptr},
+ {"5 digit, 2 fractional large decimal",
+ "999.99",
+ 6,
+ {{Item(999.990000), {}}},
+ nullptr},
+ {"6 digit 0, 3 fractional small decimal",
+ "000.111",
+ 7,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"6 digit, 3 fractional 0 decimal",
+ "111.000",
+ 7,
+ {{Item(111.000000), {}}},
+ "111.0"},
+ {"6 digit, 3 fractional small decimal",
+ "111.111",
+ 7,
+ {{Item(111.111000), {}}},
+ nullptr},
+ {"6 digit, 3 fractional large decimal",
+ "999.999",
+ 7,
+ {{Item(999.999000), {}}},
+ nullptr},
+ {"5 digit 0, 1 fractional small decimal",
+ "0000.1",
+ 6,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"5 digit, 1 fractional 0 decimal",
+ "1111.0",
+ 6,
+ {{Item(1111.000000), {}}},
+ "1111.0"},
+ {"5 digit, 1 fractional small decimal",
+ "1111.1",
+ 6,
+ {{Item(1111.100000), {}}},
+ nullptr},
+ {"5 digit, 1 fractional large decimal",
+ "9999.9",
+ 6,
+ {{Item(9999.900000), {}}},
+ nullptr},
+ {"6 digit 0, 2 fractional small decimal",
+ "0000.11",
+ 7,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"6 digit, 2 fractional 0 decimal",
+ "1111.00",
+ 7,
+ {{Item(1111.000000), {}}},
+ "1111.0"},
+ {"6 digit, 2 fractional small decimal",
+ "1111.11",
+ 7,
+ {{Item(1111.110000), {}}},
+ nullptr},
+ {"6 digit, 2 fractional large decimal",
+ "9999.99",
+ 7,
+ {{Item(9999.990000), {}}},
+ nullptr},
+ {"7 digit 0, 3 fractional small decimal",
+ "0000.111",
+ 8,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"7 digit, 3 fractional 0 decimal",
+ "1111.000",
+ 8,
+ {{Item(1111.000000), {}}},
+ "1111.0"},
+ {"7 digit, 3 fractional small decimal",
+ "1111.111",
+ 8,
+ {{Item(1111.111000), {}}},
+ nullptr},
+ {"7 digit, 3 fractional large decimal",
+ "9999.999",
+ 8,
+ {{Item(9999.999000), {}}},
+ nullptr},
+ {"6 digit 0, 1 fractional small decimal",
+ "00000.1",
+ 7,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"6 digit, 1 fractional 0 decimal",
+ "11111.0",
+ 7,
+ {{Item(11111.000000), {}}},
+ "11111.0"},
+ {"6 digit, 1 fractional small decimal",
+ "11111.1",
+ 7,
+ {{Item(11111.100000), {}}},
+ nullptr},
+ {"6 digit, 1 fractional large decimal",
+ "99999.9",
+ 7,
+ {{Item(99999.900000), {}}},
+ nullptr},
+ {"7 digit 0, 2 fractional small decimal",
+ "00000.11",
+ 8,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"7 digit, 2 fractional 0 decimal",
+ "11111.00",
+ 8,
+ {{Item(11111.000000), {}}},
+ "11111.0"},
+ {"7 digit, 2 fractional small decimal",
+ "11111.11",
+ 8,
+ {{Item(11111.110000), {}}},
+ nullptr},
+ {"7 digit, 2 fractional large decimal",
+ "99999.99",
+ 8,
+ {{Item(99999.990000), {}}},
+ nullptr},
+ {"8 digit 0, 3 fractional small decimal",
+ "00000.111",
+ 9,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"8 digit, 3 fractional 0 decimal",
+ "11111.000",
+ 9,
+ {{Item(11111.000000), {}}},
+ "11111.0"},
+ {"8 digit, 3 fractional small decimal",
+ "11111.111",
+ 9,
+ {{Item(11111.111000), {}}},
+ nullptr},
+ {"8 digit, 3 fractional large decimal",
+ "99999.999",
+ 9,
+ {{Item(99999.999000), {}}},
+ nullptr},
+ {"7 digit 0, 1 fractional small decimal",
+ "000000.1",
+ 8,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"7 digit, 1 fractional 0 decimal",
+ "111111.0",
+ 8,
+ {{Item(111111.000000), {}}},
+ "111111.0"},
+ {"7 digit, 1 fractional small decimal",
+ "111111.1",
+ 8,
+ {{Item(111111.100000), {}}},
+ nullptr},
+ {"7 digit, 1 fractional large decimal",
+ "999999.9",
+ 8,
+ {{Item(999999.900000), {}}},
+ nullptr},
+ {"8 digit 0, 2 fractional small decimal",
+ "000000.11",
+ 9,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"8 digit, 2 fractional 0 decimal",
+ "111111.00",
+ 9,
+ {{Item(111111.000000), {}}},
+ "111111.0"},
+ {"8 digit, 2 fractional small decimal",
+ "111111.11",
+ 9,
+ {{Item(111111.110000), {}}},
+ nullptr},
+ {"8 digit, 2 fractional large decimal",
+ "999999.99",
+ 9,
+ {{Item(999999.990000), {}}},
+ nullptr},
+ {"9 digit 0, 3 fractional small decimal",
+ "000000.111",
+ 10,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"9 digit, 3 fractional 0 decimal",
+ "111111.000",
+ 10,
+ {{Item(111111.000000), {}}},
+ "111111.0"},
+ {"9 digit, 3 fractional small decimal",
+ "111111.111",
+ 10,
+ {{Item(111111.111000), {}}},
+ nullptr},
+ {"9 digit, 3 fractional large decimal",
+ "999999.999",
+ 10,
+ {{Item(999999.999000), {}}},
+ nullptr},
+ {"8 digit 0, 1 fractional small decimal",
+ "0000000.1",
+ 9,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"8 digit, 1 fractional 0 decimal",
+ "1111111.0",
+ 9,
+ {{Item(1111111.000000), {}}},
+ "1111111.0"},
+ {"8 digit, 1 fractional small decimal",
+ "1111111.1",
+ 9,
+ {{Item(1111111.100000), {}}},
+ nullptr},
+ {"8 digit, 1 fractional large decimal",
+ "9999999.9",
+ 9,
+ {{Item(9999999.900000), {}}},
+ nullptr},
+ {"9 digit 0, 2 fractional small decimal",
+ "0000000.11",
+ 10,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"9 digit, 2 fractional 0 decimal",
+ "1111111.00",
+ 10,
+ {{Item(1111111.000000), {}}},
+ "1111111.0"},
+ {"9 digit, 2 fractional small decimal",
+ "1111111.11",
+ 10,
+ {{Item(1111111.110000), {}}},
+ nullptr},
+ {"9 digit, 2 fractional large decimal",
+ "9999999.99",
+ 10,
+ {{Item(9999999.990000), {}}},
+ nullptr},
+ {"10 digit 0, 3 fractional small decimal",
+ "0000000.111",
+ 11,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"10 digit, 3 fractional 0 decimal",
+ "1111111.000",
+ 11,
+ {{Item(1111111.000000), {}}},
+ "1111111.0"},
+ {"10 digit, 3 fractional small decimal",
+ "1111111.111",
+ 11,
+ {{Item(1111111.111000), {}}},
+ nullptr},
+ {"10 digit, 3 fractional large decimal",
+ "9999999.999",
+ 11,
+ {{Item(9999999.999000), {}}},
+ nullptr},
+ {"9 digit 0, 1 fractional small decimal",
+ "00000000.1",
+ 10,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"9 digit, 1 fractional 0 decimal",
+ "11111111.0",
+ 10,
+ {{Item(11111111.000000), {}}},
+ "11111111.0"},
+ {"9 digit, 1 fractional small decimal",
+ "11111111.1",
+ 10,
+ {{Item(11111111.100000), {}}},
+ nullptr},
+ {"9 digit, 1 fractional large decimal",
+ "99999999.9",
+ 10,
+ {{Item(99999999.900000), {}}},
+ nullptr},
+ {"10 digit 0, 2 fractional small decimal",
+ "00000000.11",
+ 11,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"10 digit, 2 fractional 0 decimal",
+ "11111111.00",
+ 11,
+ {{Item(11111111.000000), {}}},
+ "11111111.0"},
+ {"10 digit, 2 fractional small decimal",
+ "11111111.11",
+ 11,
+ {{Item(11111111.110000), {}}},
+ nullptr},
+ {"10 digit, 2 fractional large decimal",
+ "99999999.99",
+ 11,
+ {{Item(99999999.990000), {}}},
+ nullptr},
+ {"11 digit 0, 3 fractional small decimal",
+ "00000000.111",
+ 12,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"11 digit, 3 fractional 0 decimal",
+ "11111111.000",
+ 12,
+ {{Item(11111111.000000), {}}},
+ "11111111.0"},
+ {"11 digit, 3 fractional small decimal",
+ "11111111.111",
+ 12,
+ {{Item(11111111.111000), {}}},
+ nullptr},
+ {"11 digit, 3 fractional large decimal",
+ "99999999.999",
+ 12,
+ {{Item(99999999.999000), {}}},
+ nullptr},
+ {"10 digit 0, 1 fractional small decimal",
+ "000000000.1",
+ 11,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"10 digit, 1 fractional 0 decimal",
+ "111111111.0",
+ 11,
+ {{Item(111111111.000000), {}}},
+ "111111111.0"},
+ {"10 digit, 1 fractional small decimal",
+ "111111111.1",
+ 11,
+ {{Item(111111111.100000), {}}},
+ nullptr},
+ {"10 digit, 1 fractional large decimal",
+ "999999999.9",
+ 11,
+ {{Item(999999999.900000), {}}},
+ nullptr},
+ {"11 digit 0, 2 fractional small decimal",
+ "000000000.11",
+ 12,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"11 digit, 2 fractional 0 decimal",
+ "111111111.00",
+ 12,
+ {{Item(111111111.000000), {}}},
+ "111111111.0"},
+ {"11 digit, 2 fractional small decimal",
+ "111111111.11",
+ 12,
+ {{Item(111111111.110000), {}}},
+ nullptr},
+ {"11 digit, 2 fractional large decimal",
+ "999999999.99",
+ 12,
+ {{Item(999999999.990000), {}}},
+ nullptr},
+ {"12 digit 0, 3 fractional small decimal",
+ "000000000.111",
+ 13,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"12 digit, 3 fractional 0 decimal",
+ "111111111.000",
+ 13,
+ {{Item(111111111.000000), {}}},
+ "111111111.0"},
+ {"12 digit, 3 fractional small decimal",
+ "111111111.111",
+ 13,
+ {{Item(111111111.111000), {}}},
+ nullptr},
+ {"12 digit, 3 fractional large decimal",
+ "999999999.999",
+ 13,
+ {{Item(999999999.999000), {}}},
+ nullptr},
+ {"11 digit 0, 1 fractional small decimal",
+ "0000000000.1",
+ 12,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"11 digit, 1 fractional 0 decimal",
+ "1111111111.0",
+ 12,
+ {{Item(1111111111.000000), {}}},
+ "1111111111.0"},
+ {"11 digit, 1 fractional small decimal",
+ "1111111111.1",
+ 12,
+ {{Item(1111111111.100000), {}}},
+ nullptr},
+ {"11 digit, 1 fractional large decimal",
+ "9999999999.9",
+ 12,
+ {{Item(9999999999.900000), {}}},
+ nullptr},
+ {"12 digit 0, 2 fractional small decimal",
+ "0000000000.11",
+ 13,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"12 digit, 2 fractional 0 decimal",
+ "1111111111.00",
+ 13,
+ {{Item(1111111111.000000), {}}},
+ "1111111111.0"},
+ {"12 digit, 2 fractional small decimal",
+ "1111111111.11",
+ 13,
+ {{Item(1111111111.110000), {}}},
+ nullptr},
+ {"12 digit, 2 fractional large decimal",
+ "9999999999.99",
+ 13,
+ {{Item(9999999999.990000), {}}},
+ nullptr},
+ {"13 digit 0, 3 fractional small decimal",
+ "0000000000.111",
+ 14,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"13 digit, 3 fractional 0 decimal",
+ "1111111111.000",
+ 14,
+ {{Item(1111111111.000000), {}}},
+ "1111111111.0"},
+ {"13 digit, 3 fractional small decimal",
+ "1111111111.111",
+ 14,
+ {{Item(1111111111.111000), {}}},
+ nullptr},
+ {"13 digit, 3 fractional large decimal",
+ "9999999999.999",
+ 14,
+ {{Item(9999999999.999001), {}}},
+ nullptr},
+ {"12 digit 0, 1 fractional small decimal",
+ "00000000000.1",
+ 13,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"12 digit, 1 fractional 0 decimal",
+ "11111111111.0",
+ 13,
+ {{Item(11111111111.000000), {}}},
+ "11111111111.0"},
+ {"12 digit, 1 fractional small decimal",
+ "11111111111.1",
+ 13,
+ {{Item(11111111111.100000), {}}},
+ nullptr},
+ {"12 digit, 1 fractional large decimal",
+ "99999999999.9",
+ 13,
+ {{Item(99999999999.899994), {}}},
+ nullptr},
+ {"13 digit 0, 2 fractional small decimal",
+ "00000000000.11",
+ 14,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"13 digit, 2 fractional 0 decimal",
+ "11111111111.00",
+ 14,
+ {{Item(11111111111.000000), {}}},
+ "11111111111.0"},
+ {"13 digit, 2 fractional small decimal",
+ "11111111111.11",
+ 14,
+ {{Item(11111111111.110001), {}}},
+ nullptr},
+ {"13 digit, 2 fractional large decimal",
+ "99999999999.99",
+ 14,
+ {{Item(99999999999.990005), {}}},
+ nullptr},
+ {"14 digit 0, 3 fractional small decimal",
+ "00000000000.111",
+ 15,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"14 digit, 3 fractional 0 decimal",
+ "11111111111.000",
+ 15,
+ {{Item(11111111111.000000), {}}},
+ "11111111111.0"},
+ {"14 digit, 3 fractional small decimal",
+ "11111111111.111",
+ 15,
+ {{Item(11111111111.111000), {}}},
+ nullptr},
+ {"14 digit, 3 fractional large decimal",
+ "99999999999.999",
+ 15,
+ {{Item(99999999999.998993), {}}},
+ nullptr},
+ {"13 digit 0, 1 fractional small decimal",
+ "000000000000.1",
+ 14,
+ {{Item(0.100000), {}}},
+ "0.1"},
+ {"13 digit, 1 fractional 0 decimal",
+ "111111111111.0",
+ 14,
+ {{Item(111111111111.000000), {}}},
+ "111111111111.0"},
+ {"13 digit, 1 fractional small decimal",
+ "111111111111.1",
+ 14,
+ {{Item(111111111111.100006), {}}},
+ nullptr},
+ {"13 digit, 1 fractional large decimal",
+ "999999999999.9",
+ 14,
+ {{Item(999999999999.900024), {}}},
+ nullptr},
+ {"14 digit 0, 2 fractional small decimal",
+ "000000000000.11",
+ 15,
+ {{Item(0.110000), {}}},
+ "0.11"},
+ {"14 digit, 2 fractional 0 decimal",
+ "111111111111.00",
+ 15,
+ {{Item(111111111111.000000), {}}},
+ "111111111111.0"},
+ {"14 digit, 2 fractional small decimal",
+ "111111111111.11",
+ 15,
+ {{Item(111111111111.110001), {}}},
+ nullptr},
+ {"14 digit, 2 fractional large decimal",
+ "999999999999.99",
+ 15,
+ {{Item(999999999999.989990), {}}},
+ nullptr},
+ {"15 digit 0, 3 fractional small decimal",
+ "000000000000.111",
+ 16,
+ {{Item(0.111000), {}}},
+ "0.111"},
+ {"15 digit, 3 fractional 0 decimal",
+ "111111111111.000",
+ 16,
+ {{Item(111111111111.000000), {}}},
+ "111111111111.0"},
+ {"15 digit, 3 fractional small decimal",
+ "111111111111.111",
+ 16,
+ {{Item(111111111111.110992), {}}},
+ nullptr},
+ {"15 digit, 3 fractional large decimal",
+ "999999999999.999",
+ 16,
+ {{Item(999999999999.999023), {}}},
+ nullptr},
+ {"too many digit 0 decimal", "000000000000000.0", 17, absl::nullopt,
+ nullptr},
+ {"too many fractional digits 0 decimal", "000000000000.0000", 17,
+ absl::nullopt, nullptr},
+ {"too many digit 9 decimal", "999999999999999.9", 17, absl::nullopt,
+ nullptr},
+ {"too many fractional digits 9 decimal", "999999999999.9999", 17,
+ absl::nullopt, nullptr},
+ // number.json
+ {"basic integer", "42", 2, {{Integer(42), {}}}, nullptr},
+ {"zero integer", "0", 1, {{Integer(0), {}}}, nullptr},
+ {"negative zero", "-0", 2, {{Integer(0), {}}}, "0"},
+ {"double negative zero", "--0", 3, absl::nullopt, nullptr},
+ {"negative integer", "-42", 3, {{Integer(-42), {}}}, nullptr},
+ {"leading 0 integer", "042", 3, {{Integer(42), {}}}, "42"},
+ {"leading 0 negative integer", "-042", 4, {{Integer(-42), {}}}, "-42"},
+ {"leading 0 zero", "00", 2, {{Integer(0), {}}}, "0"},
+ {"comma", "2,3", 3, absl::nullopt, nullptr},
+ {"negative non-DIGIT first character", "-a23", 4, absl::nullopt, nullptr},
+ {"sign out of place", "4-2", 3, absl::nullopt, nullptr},
+ {"whitespace after sign", "- 42", 4, absl::nullopt, nullptr},
+ {"long integer",
+ "123456789012345",
+ 15,
+ {{Integer(123456789012345), {}}},
+ nullptr},
+ {"long negative integer",
+ "-123456789012345",
+ 16,
+ {{Integer(-123456789012345), {}}},
+ nullptr},
+ {"too long integer", "1234567890123456", 16, absl::nullopt, nullptr},
+ {"negative too long integer", "-1234567890123456", 17, absl::nullopt,
+ nullptr},
+ {"simple decimal", "1.23", 4, {{Item(1.230000), {}}}, nullptr},
+ {"negative decimal", "-1.23", 5, {{Item(-1.230000), {}}}, nullptr},
+ {"decimal, whitespace after decimal", "1. 23", 5, absl::nullopt, nullptr},
+ {"decimal, whitespace before decimal", "1 .23", 5, absl::nullopt, nullptr},
+ {"negative decimal, whitespace after sign", "- 1.23", 6, absl::nullopt,
+ nullptr},
+ {"tricky precision decimal",
+ "123456789012.1",
+ 14,
+ {{Item(123456789012.100006), {}}},
+ nullptr},
+ {"double decimal decimal", "1.5.4", 5, absl::nullopt, nullptr},
+ {"adjacent double decimal decimal", "1..4", 4, absl::nullopt, nullptr},
+ {"decimal with three fractional digits",
+ "1.123",
+ 5,
+ {{Item(1.123000), {}}},
+ nullptr},
+ {"negative decimal with three fractional digits",
+ "-1.123",
+ 6,
+ {{Item(-1.123000), {}}},
+ nullptr},
+ {"decimal with four fractional digits", "1.1234", 6, absl::nullopt,
+ nullptr},
+ {"negative decimal with four fractional digits", "-1.1234", 7,
+ absl::nullopt, nullptr},
+ {"decimal with thirteen integer digits", "1234567890123.0", 15,
+ absl::nullopt, nullptr},
+ {"negative decimal with thirteen integer digits", "-1234567890123.0", 16,
+ absl::nullopt, nullptr},
+ // string-generated.json
+ {"0x00 in string", "\" \000 \"", 5, absl::nullopt, nullptr},
+ {"0x01 in string", "\" \001 \"", 5, absl::nullopt, nullptr},
+ {"0x02 in string", "\" \002 \"", 5, absl::nullopt, nullptr},
+ {"0x03 in string", "\" \003 \"", 5, absl::nullopt, nullptr},
+ {"0x04 in string", "\" \004 \"", 5, absl::nullopt, nullptr},
+ {"0x05 in string", "\" \005 \"", 5, absl::nullopt, nullptr},
+ {"0x06 in string", "\" \006 \"", 5, absl::nullopt, nullptr},
+ {"0x07 in string", "\" \a \"", 5, absl::nullopt, nullptr},
+ {"0x08 in string", "\" \b \"", 5, absl::nullopt, nullptr},
+ {"0x09 in string", "\" \t \"", 5, absl::nullopt, nullptr},
+ {"0x0a in string", "\" \n \"", 5, absl::nullopt, nullptr},
+ {"0x0b in string", "\" \v \"", 5, absl::nullopt, nullptr},
+ {"0x0c in string", "\" \f \"", 5, absl::nullopt, nullptr},
+ {"0x0d in string", "\" \r \"", 5, absl::nullopt, nullptr},
+ {"0x0e in string", "\" \016 \"", 5, absl::nullopt, nullptr},
+ {"0x0f in string", "\" \017 \"", 5, absl::nullopt, nullptr},
+ {"0x10 in string", "\" \020 \"", 5, absl::nullopt, nullptr},
+ {"0x11 in string", "\" \021 \"", 5, absl::nullopt, nullptr},
+ {"0x12 in string", "\" \022 \"", 5, absl::nullopt, nullptr},
+ {"0x13 in string", "\" \023 \"", 5, absl::nullopt, nullptr},
+ {"0x14 in string", "\" \024 \"", 5, absl::nullopt, nullptr},
+ {"0x15 in string", "\" \025 \"", 5, absl::nullopt, nullptr},
+ {"0x16 in string", "\" \026 \"", 5, absl::nullopt, nullptr},
+ {"0x17 in string", "\" \027 \"", 5, absl::nullopt, nullptr},
+ {"0x18 in string", "\" \030 \"", 5, absl::nullopt, nullptr},
+ {"0x19 in string", "\" \031 \"", 5, absl::nullopt, nullptr},
+ {"0x1a in string", "\" \032 \"", 5, absl::nullopt, nullptr},
+ {"0x1b in string", "\" \033 \"", 5, absl::nullopt, nullptr},
+ {"0x1c in string", "\" \034 \"", 5, absl::nullopt, nullptr},
+ {"0x1d in string", "\" \035 \"", 5, absl::nullopt, nullptr},
+ {"0x1e in string", "\" \036 \"", 5, absl::nullopt, nullptr},
+ {"0x1f in string", "\" \037 \"", 5, absl::nullopt, nullptr},
+ {"0x20 in string", "\" \"", 5, {{Item(" "), {}}}, nullptr},
+ {"0x21 in string", "\" ! \"", 5, {{Item(" ! "), {}}}, nullptr},
+ {"0x22 in string", "\" \" \"", 5, absl::nullopt, nullptr},
+ {"0x23 in string", "\" # \"", 5, {{Item(" # "), {}}}, nullptr},
+ {"0x24 in string", "\" $ \"", 5, {{Item(" $ "), {}}}, nullptr},
+ {"0x25 in string", "\" % \"", 5, {{Item(" % "), {}}}, nullptr},
+ {"0x26 in string", "\" & \"", 5, {{Item(" & "), {}}}, nullptr},
+ {"0x27 in string", "\" ' \"", 5, {{Item(" ' "), {}}}, nullptr},
+ {"0x28 in string", "\" ( \"", 5, {{Item(" ( "), {}}}, nullptr},
+ {"0x29 in string", "\" ) \"", 5, {{Item(" ) "), {}}}, nullptr},
+ {"0x2a in string", "\" * \"", 5, {{Item(" * "), {}}}, nullptr},
+ {"0x2b in string", "\" + \"", 5, {{Item(" + "), {}}}, nullptr},
+ {"0x2c in string", "\" , \"", 5, {{Item(" , "), {}}}, nullptr},
+ {"0x2d in string", "\" - \"", 5, {{Item(" - "), {}}}, nullptr},
+ {"0x2e in string", "\" . \"", 5, {{Item(" . "), {}}}, nullptr},
+ {"0x2f in string", "\" / \"", 5, {{Item(" / "), {}}}, nullptr},
+ {"0x30 in string", "\" 0 \"", 5, {{Item(" 0 "), {}}}, nullptr},
+ {"0x31 in string", "\" 1 \"", 5, {{Item(" 1 "), {}}}, nullptr},
+ {"0x32 in string", "\" 2 \"", 5, {{Item(" 2 "), {}}}, nullptr},
+ {"0x33 in string", "\" 3 \"", 5, {{Item(" 3 "), {}}}, nullptr},
+ {"0x34 in string", "\" 4 \"", 5, {{Item(" 4 "), {}}}, nullptr},
+ {"0x35 in string", "\" 5 \"", 5, {{Item(" 5 "), {}}}, nullptr},
+ {"0x36 in string", "\" 6 \"", 5, {{Item(" 6 "), {}}}, nullptr},
+ {"0x37 in string", "\" 7 \"", 5, {{Item(" 7 "), {}}}, nullptr},
+ {"0x38 in string", "\" 8 \"", 5, {{Item(" 8 "), {}}}, nullptr},
+ {"0x39 in string", "\" 9 \"", 5, {{Item(" 9 "), {}}}, nullptr},
+ {"0x3a in string", "\" : \"", 5, {{Item(" : "), {}}}, nullptr},
+ {"0x3b in string", "\" ; \"", 5, {{Item(" ; "), {}}}, nullptr},
+ {"0x3c in string", "\" < \"", 5, {{Item(" < "), {}}}, nullptr},
+ {"0x3d in string", "\" = \"", 5, {{Item(" = "), {}}}, nullptr},
+ {"0x3e in string", "\" > \"", 5, {{Item(" > "), {}}}, nullptr},
+ {"0x3f in string", "\" ? \"", 5, {{Item(" ? "), {}}}, nullptr},
+ {"0x40 in string", "\" @ \"", 5, {{Item(" @ "), {}}}, nullptr},
+ {"0x41 in string", "\" A \"", 5, {{Item(" A "), {}}}, nullptr},
+ {"0x42 in string", "\" B \"", 5, {{Item(" B "), {}}}, nullptr},
+ {"0x43 in string", "\" C \"", 5, {{Item(" C "), {}}}, nullptr},
+ {"0x44 in string", "\" D \"", 5, {{Item(" D "), {}}}, nullptr},
+ {"0x45 in string", "\" E \"", 5, {{Item(" E "), {}}}, nullptr},
+ {"0x46 in string", "\" F \"", 5, {{Item(" F "), {}}}, nullptr},
+ {"0x47 in string", "\" G \"", 5, {{Item(" G "), {}}}, nullptr},
+ {"0x48 in string", "\" H \"", 5, {{Item(" H "), {}}}, nullptr},
+ {"0x49 in string", "\" I \"", 5, {{Item(" I "), {}}}, nullptr},
+ {"0x4a in string", "\" J \"", 5, {{Item(" J "), {}}}, nullptr},
+ {"0x4b in string", "\" K \"", 5, {{Item(" K "), {}}}, nullptr},
+ {"0x4c in string", "\" L \"", 5, {{Item(" L "), {}}}, nullptr},
+ {"0x4d in string", "\" M \"", 5, {{Item(" M "), {}}}, nullptr},
+ {"0x4e in string", "\" N \"", 5, {{Item(" N "), {}}}, nullptr},
+ {"0x4f in string", "\" O \"", 5, {{Item(" O "), {}}}, nullptr},
+ {"0x50 in string", "\" P \"", 5, {{Item(" P "), {}}}, nullptr},
+ {"0x51 in string", "\" Q \"", 5, {{Item(" Q "), {}}}, nullptr},
+ {"0x52 in string", "\" R \"", 5, {{Item(" R "), {}}}, nullptr},
+ {"0x53 in string", "\" S \"", 5, {{Item(" S "), {}}}, nullptr},
+ {"0x54 in string", "\" T \"", 5, {{Item(" T "), {}}}, nullptr},
+ {"0x55 in string", "\" U \"", 5, {{Item(" U "), {}}}, nullptr},
+ {"0x56 in string", "\" V \"", 5, {{Item(" V "), {}}}, nullptr},
+ {"0x57 in string", "\" W \"", 5, {{Item(" W "), {}}}, nullptr},
+ {"0x58 in string", "\" X \"", 5, {{Item(" X "), {}}}, nullptr},
+ {"0x59 in string", "\" Y \"", 5, {{Item(" Y "), {}}}, nullptr},
+ {"0x5a in string", "\" Z \"", 5, {{Item(" Z "), {}}}, nullptr},
+ {"0x5b in string", "\" [ \"", 5, {{Item(" [ "), {}}}, nullptr},
+ {"0x5c in string", "\" \\ \"", 5, absl::nullopt, nullptr},
+ {"0x5d in string", "\" ] \"", 5, {{Item(" ] "), {}}}, nullptr},
+ {"0x5e in string", "\" ^ \"", 5, {{Item(" ^ "), {}}}, nullptr},
+ {"0x5f in string", "\" _ \"", 5, {{Item(" _ "), {}}}, nullptr},
+ {"0x60 in string", "\" ` \"", 5, {{Item(" ` "), {}}}, nullptr},
+ {"0x61 in string", "\" a \"", 5, {{Item(" a "), {}}}, nullptr},
+ {"0x62 in string", "\" b \"", 5, {{Item(" b "), {}}}, nullptr},
+ {"0x63 in string", "\" c \"", 5, {{Item(" c "), {}}}, nullptr},
+ {"0x64 in string", "\" d \"", 5, {{Item(" d "), {}}}, nullptr},
+ {"0x65 in string", "\" e \"", 5, {{Item(" e "), {}}}, nullptr},
+ {"0x66 in string", "\" f \"", 5, {{Item(" f "), {}}}, nullptr},
+ {"0x67 in string", "\" g \"", 5, {{Item(" g "), {}}}, nullptr},
+ {"0x68 in string", "\" h \"", 5, {{Item(" h "), {}}}, nullptr},
+ {"0x69 in string", "\" i \"", 5, {{Item(" i "), {}}}, nullptr},
+ {"0x6a in string", "\" j \"", 5, {{Item(" j "), {}}}, nullptr},
+ {"0x6b in string", "\" k \"", 5, {{Item(" k "), {}}}, nullptr},
+ {"0x6c in string", "\" l \"", 5, {{Item(" l "), {}}}, nullptr},
+ {"0x6d in string", "\" m \"", 5, {{Item(" m "), {}}}, nullptr},
+ {"0x6e in string", "\" n \"", 5, {{Item(" n "), {}}}, nullptr},
+ {"0x6f in string", "\" o \"", 5, {{Item(" o "), {}}}, nullptr},
+ {"0x70 in string", "\" p \"", 5, {{Item(" p "), {}}}, nullptr},
+ {"0x71 in string", "\" q \"", 5, {{Item(" q "), {}}}, nullptr},
+ {"0x72 in string", "\" r \"", 5, {{Item(" r "), {}}}, nullptr},
+ {"0x73 in string", "\" s \"", 5, {{Item(" s "), {}}}, nullptr},
+ {"0x74 in string", "\" t \"", 5, {{Item(" t "), {}}}, nullptr},
+ {"0x75 in string", "\" u \"", 5, {{Item(" u "), {}}}, nullptr},
+ {"0x76 in string", "\" v \"", 5, {{Item(" v "), {}}}, nullptr},
+ {"0x77 in string", "\" w \"", 5, {{Item(" w "), {}}}, nullptr},
+ {"0x78 in string", "\" x \"", 5, {{Item(" x "), {}}}, nullptr},
+ {"0x79 in string", "\" y \"", 5, {{Item(" y "), {}}}, nullptr},
+ {"0x7a in string", "\" z \"", 5, {{Item(" z "), {}}}, nullptr},
+ {"0x7b in string", "\" { \"", 5, {{Item(" { "), {}}}, nullptr},
+ {"0x7c in string", "\" | \"", 5, {{Item(" | "), {}}}, nullptr},
+ {"0x7d in string", "\" } \"", 5, {{Item(" } "), {}}}, nullptr},
+ {"0x7e in string", "\" ~ \"", 5, {{Item(" ~ "), {}}}, nullptr},
+ {"0x7f in string", "\" \177 \"", 5, absl::nullopt, nullptr},
+ {"Escaped 0x00 in string", "\"\\\000\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x01 in string", "\"\\\001\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x02 in string", "\"\\\002\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x03 in string", "\"\\\003\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x04 in string", "\"\\\004\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x05 in string", "\"\\\005\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x06 in string", "\"\\\006\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x07 in string", "\"\\\a\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x08 in string", "\"\\\b\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x09 in string", "\"\\\t\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x0a in string", "\"\\\n\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x0b in string", "\"\\\v\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x0c in string", "\"\\\f\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x0d in string", "\"\\\r\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x0e in string", "\"\\\016\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x0f in string", "\"\\\017\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x10 in string", "\"\\\020\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x11 in string", "\"\\\021\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x12 in string", "\"\\\022\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x13 in string", "\"\\\023\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x14 in string", "\"\\\024\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x15 in string", "\"\\\025\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x16 in string", "\"\\\026\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x17 in string", "\"\\\027\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x18 in string", "\"\\\030\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x19 in string", "\"\\\031\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x1a in string", "\"\\\032\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x1b in string", "\"\\\033\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x1c in string", "\"\\\034\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x1d in string", "\"\\\035\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x1e in string", "\"\\\036\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x1f in string", "\"\\\037\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x20 in string", "\"\\ \"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x21 in string", "\"\\!\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x22 in string", "\"\\\"\"", 4, {{Item("\""), {}}}, nullptr},
+ {"Escaped 0x23 in string", "\"\\#\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x24 in string", "\"\\$\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x25 in string", "\"\\%\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x26 in string", "\"\\&\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x27 in string", "\"\\'\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x28 in string", "\"\\(\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x29 in string", "\"\\)\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x2a in string", "\"\\*\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x2b in string", "\"\\+\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x2c in string", "\"\\,\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x2d in string", "\"\\-\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x2e in string", "\"\\.\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x2f in string", "\"\\/\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x30 in string", "\"\\0\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x31 in string", "\"\\1\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x32 in string", "\"\\2\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x33 in string", "\"\\3\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x34 in string", "\"\\4\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x35 in string", "\"\\5\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x36 in string", "\"\\6\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x37 in string", "\"\\7\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x38 in string", "\"\\8\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x39 in string", "\"\\9\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x3a in string", "\"\\:\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x3b in string", "\"\\;\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x3c in string", "\"\\<\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x3d in string", "\"\\=\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x3e in string", "\"\\>\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x3f in string", "\"\\?\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x40 in string", "\"\\@\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x41 in string", "\"\\A\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x42 in string", "\"\\B\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x43 in string", "\"\\C\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x44 in string", "\"\\D\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x45 in string", "\"\\E\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x46 in string", "\"\\F\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x47 in string", "\"\\G\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x48 in string", "\"\\H\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x49 in string", "\"\\I\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x4a in string", "\"\\J\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x4b in string", "\"\\K\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x4c in string", "\"\\L\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x4d in string", "\"\\M\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x4e in string", "\"\\N\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x4f in string", "\"\\O\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x50 in string", "\"\\P\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x51 in string", "\"\\Q\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x52 in string", "\"\\R\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x53 in string", "\"\\S\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x54 in string", "\"\\T\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x55 in string", "\"\\U\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x56 in string", "\"\\V\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x57 in string", "\"\\W\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x58 in string", "\"\\X\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x59 in string", "\"\\Y\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x5a in string", "\"\\Z\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x5b in string", "\"\\[\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x5c in string", "\"\\\\\"", 4, {{Item("\\"), {}}}, nullptr},
+ {"Escaped 0x5d in string", "\"\\]\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x5e in string", "\"\\^\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x5f in string", "\"\\_\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x60 in string", "\"\\`\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x61 in string", "\"\\a\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x62 in string", "\"\\b\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x63 in string", "\"\\c\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x64 in string", "\"\\d\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x65 in string", "\"\\e\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x66 in string", "\"\\f\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x67 in string", "\"\\g\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x68 in string", "\"\\h\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x69 in string", "\"\\i\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x6a in string", "\"\\j\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x6b in string", "\"\\k\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x6c in string", "\"\\l\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x6d in string", "\"\\m\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x6e in string", "\"\\n\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x6f in string", "\"\\o\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x70 in string", "\"\\p\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x71 in string", "\"\\q\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x72 in string", "\"\\r\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x73 in string", "\"\\s\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x74 in string", "\"\\t\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x75 in string", "\"\\u\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x76 in string", "\"\\v\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x77 in string", "\"\\w\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x78 in string", "\"\\x\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x79 in string", "\"\\y\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x7a in string", "\"\\z\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x7b in string", "\"\\{\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x7c in string", "\"\\|\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x7d in string", "\"\\}\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x7e in string", "\"\\~\"", 4, absl::nullopt, nullptr},
+ {"Escaped 0x7f in string", "\"\\\177\"", 4, absl::nullopt, nullptr},
+ // string.json
+ {"basic string", "\"foo bar\"", 9, {{Item("foo bar"), {}}}, nullptr},
+ {"empty string", "\"\"", 2, {{Item(""), {}}}, nullptr},
+ {"long string",
+ "\"foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
+ "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
+ "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
+ "foo foo foo foo foo foo foo foo foo foo foo foo \"",
+ 262,
+ {{Item("foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
+ "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
+ "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
+ "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
+ "foo "),
+ {}}},
+ nullptr},
+ {"whitespace string", "\" \"", 5, {{Item(" "), {}}}, nullptr},
+ {"non-ascii string", "\"f\374\374\"", 5, absl::nullopt, nullptr},
+ {"tab in string", "\"\\t\"", 4, absl::nullopt, nullptr},
+ {"newline in string", "\" \\n \"", 6, absl::nullopt, nullptr},
+ {"single quoted string", "'foo'", 5, absl::nullopt, nullptr},
+ {"unbalanced string", "\"foo", 4, absl::nullopt, nullptr},
+ {"string quoting",
+ "\"foo \\\"bar\\\" \\\\ baz\"",
+ 20,
+ {{Item("foo \"bar\" \\ baz"), {}}},
+ nullptr},
+ {"bad string quoting", "\"foo \\,\"", 8, absl::nullopt, nullptr},
+ {"ending string quote", "\"foo \\\"", 7, absl::nullopt, nullptr},
+ {"abruptly ending string quote", "\"foo \\", 6, absl::nullopt, nullptr},
+ // token-generated.json
+ {"0x00 in token", "a\000a", 3, absl::nullopt, nullptr},
+ {"0x01 in token", "a\001a", 3, absl::nullopt, nullptr},
+ {"0x02 in token", "a\002a", 3, absl::nullopt, nullptr},
+ {"0x03 in token", "a\003a", 3, absl::nullopt, nullptr},
+ {"0x04 in token", "a\004a", 3, absl::nullopt, nullptr},
+ {"0x05 in token", "a\005a", 3, absl::nullopt, nullptr},
+ {"0x06 in token", "a\006a", 3, absl::nullopt, nullptr},
+ {"0x07 in token", "a\aa", 3, absl::nullopt, nullptr},
+ {"0x08 in token", "a\ba", 3, absl::nullopt, nullptr},
+ {"0x09 in token", "a\ta", 3, absl::nullopt, nullptr},
+ {"0x0a in token", "a\na", 3, absl::nullopt, nullptr},
+ {"0x0b in token", "a\va", 3, absl::nullopt, nullptr},
+ {"0x0c in token", "a\fa", 3, absl::nullopt, nullptr},
+ {"0x0d in token", "a\ra", 3, absl::nullopt, nullptr},
+ {"0x0e in token", "a\016a", 3, absl::nullopt, nullptr},
+ {"0x0f in token", "a\017a", 3, absl::nullopt, nullptr},
+ {"0x10 in token", "a\020a", 3, absl::nullopt, nullptr},
+ {"0x11 in token", "a\021a", 3, absl::nullopt, nullptr},
+ {"0x12 in token", "a\022a", 3, absl::nullopt, nullptr},
+ {"0x13 in token", "a\023a", 3, absl::nullopt, nullptr},
+ {"0x14 in token", "a\024a", 3, absl::nullopt, nullptr},
+ {"0x15 in token", "a\025a", 3, absl::nullopt, nullptr},
+ {"0x16 in token", "a\026a", 3, absl::nullopt, nullptr},
+ {"0x17 in token", "a\027a", 3, absl::nullopt, nullptr},
+ {"0x18 in token", "a\030a", 3, absl::nullopt, nullptr},
+ {"0x19 in token", "a\031a", 3, absl::nullopt, nullptr},
+ {"0x1a in token", "a\032a", 3, absl::nullopt, nullptr},
+ {"0x1b in token", "a\033a", 3, absl::nullopt, nullptr},
+ {"0x1c in token", "a\034a", 3, absl::nullopt, nullptr},
+ {"0x1d in token", "a\035a", 3, absl::nullopt, nullptr},
+ {"0x1e in token", "a\036a", 3, absl::nullopt, nullptr},
+ {"0x1f in token", "a\037a", 3, absl::nullopt, nullptr},
+ {"0x20 in token", "a a", 3, absl::nullopt, nullptr},
+ {"0x21 in token", "a!a", 3, {{Item("a!a", Item::kTokenType), {}}}, nullptr},
+ {"0x22 in token", "a\"a", 3, absl::nullopt, nullptr},
+ {"0x23 in token", "a#a", 3, {{Item("a#a", Item::kTokenType), {}}}, nullptr},
+ {"0x24 in token", "a$a", 3, {{Item("a$a", Item::kTokenType), {}}}, nullptr},
+ {"0x25 in token", "a%a", 3, {{Item("a%a", Item::kTokenType), {}}}, nullptr},
+ {"0x26 in token", "a&a", 3, {{Item("a&a", Item::kTokenType), {}}}, nullptr},
+ {"0x27 in token", "a'a", 3, {{Item("a'a", Item::kTokenType), {}}}, nullptr},
+ {"0x28 in token", "a(a", 3, absl::nullopt, nullptr},
+ {"0x29 in token", "a)a", 3, absl::nullopt, nullptr},
+ {"0x2a in token", "a*a", 3, {{Item("a*a", Item::kTokenType), {}}}, nullptr},
+ {"0x2b in token", "a+a", 3, {{Item("a+a", Item::kTokenType), {}}}, nullptr},
+ {"0x2c in token", "a,a", 3, absl::nullopt, nullptr},
+ {"0x2d in token", "a-a", 3, {{Item("a-a", Item::kTokenType), {}}}, nullptr},
+ {"0x2e in token", "a.a", 3, {{Item("a.a", Item::kTokenType), {}}}, nullptr},
+ {"0x2f in token", "a/a", 3, {{Item("a/a", Item::kTokenType), {}}}, nullptr},
+ {"0x30 in token", "a0a", 3, {{Item("a0a", Item::kTokenType), {}}}, nullptr},
+ {"0x31 in token", "a1a", 3, {{Item("a1a", Item::kTokenType), {}}}, nullptr},
+ {"0x32 in token", "a2a", 3, {{Item("a2a", Item::kTokenType), {}}}, nullptr},
+ {"0x33 in token", "a3a", 3, {{Item("a3a", Item::kTokenType), {}}}, nullptr},
+ {"0x34 in token", "a4a", 3, {{Item("a4a", Item::kTokenType), {}}}, nullptr},
+ {"0x35 in token", "a5a", 3, {{Item("a5a", Item::kTokenType), {}}}, nullptr},
+ {"0x36 in token", "a6a", 3, {{Item("a6a", Item::kTokenType), {}}}, nullptr},
+ {"0x37 in token", "a7a", 3, {{Item("a7a", Item::kTokenType), {}}}, nullptr},
+ {"0x38 in token", "a8a", 3, {{Item("a8a", Item::kTokenType), {}}}, nullptr},
+ {"0x39 in token", "a9a", 3, {{Item("a9a", Item::kTokenType), {}}}, nullptr},
+ {"0x3a in token", "a:a", 3, {{Item("a:a", Item::kTokenType), {}}}, nullptr},
+ {"0x3b in token",
+ "a;a",
+ 3,
+ {{Item("a", Item::kTokenType), {BooleanParam("a", true)}}},
+ nullptr},
+ {"0x3c in token", "a<a", 3, absl::nullopt, nullptr},
+ {"0x3d in token", "a=a", 3, absl::nullopt, nullptr},
+ {"0x3e in token", "a>a", 3, absl::nullopt, nullptr},
+ {"0x3f in token", "a?a", 3, absl::nullopt, nullptr},
+ {"0x40 in token", "a@a", 3, absl::nullopt, nullptr},
+ {"0x41 in token", "aAa", 3, {{Item("aAa", Item::kTokenType), {}}}, nullptr},
+ {"0x42 in token", "aBa", 3, {{Item("aBa", Item::kTokenType), {}}}, nullptr},
+ {"0x43 in token", "aCa", 3, {{Item("aCa", Item::kTokenType), {}}}, nullptr},
+ {"0x44 in token", "aDa", 3, {{Item("aDa", Item::kTokenType), {}}}, nullptr},
+ {"0x45 in token", "aEa", 3, {{Item("aEa", Item::kTokenType), {}}}, nullptr},
+ {"0x46 in token", "aFa", 3, {{Item("aFa", Item::kTokenType), {}}}, nullptr},
+ {"0x47 in token", "aGa", 3, {{Item("aGa", Item::kTokenType), {}}}, nullptr},
+ {"0x48 in token", "aHa", 3, {{Item("aHa", Item::kTokenType), {}}}, nullptr},
+ {"0x49 in token", "aIa", 3, {{Item("aIa", Item::kTokenType), {}}}, nullptr},
+ {"0x4a in token", "aJa", 3, {{Item("aJa", Item::kTokenType), {}}}, nullptr},
+ {"0x4b in token", "aKa", 3, {{Item("aKa", Item::kTokenType), {}}}, nullptr},
+ {"0x4c in token", "aLa", 3, {{Item("aLa", Item::kTokenType), {}}}, nullptr},
+ {"0x4d in token", "aMa", 3, {{Item("aMa", Item::kTokenType), {}}}, nullptr},
+ {"0x4e in token", "aNa", 3, {{Item("aNa", Item::kTokenType), {}}}, nullptr},
+ {"0x4f in token", "aOa", 3, {{Item("aOa", Item::kTokenType), {}}}, nullptr},
+ {"0x50 in token", "aPa", 3, {{Item("aPa", Item::kTokenType), {}}}, nullptr},
+ {"0x51 in token", "aQa", 3, {{Item("aQa", Item::kTokenType), {}}}, nullptr},
+ {"0x52 in token", "aRa", 3, {{Item("aRa", Item::kTokenType), {}}}, nullptr},
+ {"0x53 in token", "aSa", 3, {{Item("aSa", Item::kTokenType), {}}}, nullptr},
+ {"0x54 in token", "aTa", 3, {{Item("aTa", Item::kTokenType), {}}}, nullptr},
+ {"0x55 in token", "aUa", 3, {{Item("aUa", Item::kTokenType), {}}}, nullptr},
+ {"0x56 in token", "aVa", 3, {{Item("aVa", Item::kTokenType), {}}}, nullptr},
+ {"0x57 in token", "aWa", 3, {{Item("aWa", Item::kTokenType), {}}}, nullptr},
+ {"0x58 in token", "aXa", 3, {{Item("aXa", Item::kTokenType), {}}}, nullptr},
+ {"0x59 in token", "aYa", 3, {{Item("aYa", Item::kTokenType), {}}}, nullptr},
+ {"0x5a in token", "aZa", 3, {{Item("aZa", Item::kTokenType), {}}}, nullptr},
+ {"0x5b in token", "a[a", 3, absl::nullopt, nullptr},
+ {"0x5c in token", "a\\a", 3, absl::nullopt, nullptr},
+ {"0x5d in token", "a]a", 3, absl::nullopt, nullptr},
+ {"0x5e in token", "a^a", 3, {{Item("a^a", Item::kTokenType), {}}}, nullptr},
+ {"0x5f in token", "a_a", 3, {{Item("a_a", Item::kTokenType), {}}}, nullptr},
+ {"0x60 in token", "a`a", 3, {{Item("a`a", Item::kTokenType), {}}}, nullptr},
+ {"0x61 in token", "aaa", 3, {{Item("aaa", Item::kTokenType), {}}}, nullptr},
+ {"0x62 in token", "aba", 3, {{Item("aba", Item::kTokenType), {}}}, nullptr},
+ {"0x63 in token", "aca", 3, {{Item("aca", Item::kTokenType), {}}}, nullptr},
+ {"0x64 in token", "ada", 3, {{Item("ada", Item::kTokenType), {}}}, nullptr},
+ {"0x65 in token", "aea", 3, {{Item("aea", Item::kTokenType), {}}}, nullptr},
+ {"0x66 in token", "afa", 3, {{Item("afa", Item::kTokenType), {}}}, nullptr},
+ {"0x67 in token", "aga", 3, {{Item("aga", Item::kTokenType), {}}}, nullptr},
+ {"0x68 in token", "aha", 3, {{Item("aha", Item::kTokenType), {}}}, nullptr},
+ {"0x69 in token", "aia", 3, {{Item("aia", Item::kTokenType), {}}}, nullptr},
+ {"0x6a in token", "aja", 3, {{Item("aja", Item::kTokenType), {}}}, nullptr},
+ {"0x6b in token", "aka", 3, {{Item("aka", Item::kTokenType), {}}}, nullptr},
+ {"0x6c in token", "ala", 3, {{Item("ala", Item::kTokenType), {}}}, nullptr},
+ {"0x6d in token", "ama", 3, {{Item("ama", Item::kTokenType), {}}}, nullptr},
+ {"0x6e in token", "ana", 3, {{Item("ana", Item::kTokenType), {}}}, nullptr},
+ {"0x6f in token", "aoa", 3, {{Item("aoa", Item::kTokenType), {}}}, nullptr},
+ {"0x70 in token", "apa", 3, {{Item("apa", Item::kTokenType), {}}}, nullptr},
+ {"0x71 in token", "aqa", 3, {{Item("aqa", Item::kTokenType), {}}}, nullptr},
+ {"0x72 in token", "ara", 3, {{Item("ara", Item::kTokenType), {}}}, nullptr},
+ {"0x73 in token", "asa", 3, {{Item("asa", Item::kTokenType), {}}}, nullptr},
+ {"0x74 in token", "ata", 3, {{Item("ata", Item::kTokenType), {}}}, nullptr},
+ {"0x75 in token", "aua", 3, {{Item("aua", Item::kTokenType), {}}}, nullptr},
+ {"0x76 in token", "ava", 3, {{Item("ava", Item::kTokenType), {}}}, nullptr},
+ {"0x77 in token", "awa", 3, {{Item("awa", Item::kTokenType), {}}}, nullptr},
+ {"0x78 in token", "axa", 3, {{Item("axa", Item::kTokenType), {}}}, nullptr},
+ {"0x79 in token", "aya", 3, {{Item("aya", Item::kTokenType), {}}}, nullptr},
+ {"0x7a in token", "aza", 3, {{Item("aza", Item::kTokenType), {}}}, nullptr},
+ {"0x7b in token", "a{a", 3, absl::nullopt, nullptr},
+ {"0x7c in token", "a|a", 3, {{Item("a|a", Item::kTokenType), {}}}, nullptr},
+ {"0x7d in token", "a}a", 3, absl::nullopt, nullptr},
+ {"0x7e in token", "a~a", 3, {{Item("a~a", Item::kTokenType), {}}}, nullptr},
+ {"0x7f in token", "a\177a", 3, absl::nullopt, nullptr},
+ {"0x00 starting an token", "\000a", 2, absl::nullopt, nullptr},
+ {"0x01 starting an token", "\001a", 2, absl::nullopt, nullptr},
+ {"0x02 starting an token", "\002a", 2, absl::nullopt, nullptr},
+ {"0x03 starting an token", "\003a", 2, absl::nullopt, nullptr},
+ {"0x04 starting an token", "\004a", 2, absl::nullopt, nullptr},
+ {"0x05 starting an token", "\005a", 2, absl::nullopt, nullptr},
+ {"0x06 starting an token", "\006a", 2, absl::nullopt, nullptr},
+ {"0x07 starting an token", "\aa", 2, absl::nullopt, nullptr},
+ {"0x08 starting an token", "\ba", 2, absl::nullopt, nullptr},
+ {"0x09 starting an token", "\ta", 2, absl::nullopt, nullptr},
+ {"0x0a starting an token", "\na", 2, absl::nullopt, nullptr},
+ {"0x0b starting an token", "\va", 2, absl::nullopt, nullptr},
+ {"0x0c starting an token", "\fa", 2, absl::nullopt, nullptr},
+ {"0x0d starting an token", "\ra", 2, absl::nullopt, nullptr},
+ {"0x0e starting an token", "\016a", 2, absl::nullopt, nullptr},
+ {"0x0f starting an token", "\017a", 2, absl::nullopt, nullptr},
+ {"0x10 starting an token", "\020a", 2, absl::nullopt, nullptr},
+ {"0x11 starting an token", "\021a", 2, absl::nullopt, nullptr},
+ {"0x12 starting an token", "\022a", 2, absl::nullopt, nullptr},
+ {"0x13 starting an token", "\023a", 2, absl::nullopt, nullptr},
+ {"0x14 starting an token", "\024a", 2, absl::nullopt, nullptr},
+ {"0x15 starting an token", "\025a", 2, absl::nullopt, nullptr},
+ {"0x16 starting an token", "\026a", 2, absl::nullopt, nullptr},
+ {"0x17 starting an token", "\027a", 2, absl::nullopt, nullptr},
+ {"0x18 starting an token", "\030a", 2, absl::nullopt, nullptr},
+ {"0x19 starting an token", "\031a", 2, absl::nullopt, nullptr},
+ {"0x1a starting an token", "\032a", 2, absl::nullopt, nullptr},
+ {"0x1b starting an token", "\033a", 2, absl::nullopt, nullptr},
+ {"0x1c starting an token", "\034a", 2, absl::nullopt, nullptr},
+ {"0x1d starting an token", "\035a", 2, absl::nullopt, nullptr},
+ {"0x1e starting an token", "\036a", 2, absl::nullopt, nullptr},
+ {"0x1f starting an token", "\037a", 2, absl::nullopt, nullptr},
+ {"0x20 starting an token",
+ " a",
+ 2,
+ {{Item("a", Item::kTokenType), {}}},
+ "a"},
+ {"0x21 starting an token", "!a", 2, absl::nullopt, nullptr},
+ {"0x22 starting an token", "\"a", 2, absl::nullopt, nullptr},
+ {"0x23 starting an token", "#a", 2, absl::nullopt, nullptr},
+ {"0x24 starting an token", "$a", 2, absl::nullopt, nullptr},
+ {"0x25 starting an token", "%a", 2, absl::nullopt, nullptr},
+ {"0x26 starting an token", "&a", 2, absl::nullopt, nullptr},
+ {"0x27 starting an token", "'a", 2, absl::nullopt, nullptr},
+ {"0x28 starting an token", "(a", 2, absl::nullopt, nullptr},
+ {"0x29 starting an token", ")a", 2, absl::nullopt, nullptr},
+ {"0x2a starting an token",
+ "*a",
+ 2,
+ {{Item("*a", Item::kTokenType), {}}},
+ nullptr},
+ {"0x2b starting an token", "+a", 2, absl::nullopt, nullptr},
+ {"0x2c starting an token", ",a", 2, absl::nullopt, nullptr},
+ {"0x2d starting an token", "-a", 2, absl::nullopt, nullptr},
+ {"0x2e starting an token", ".a", 2, absl::nullopt, nullptr},
+ {"0x2f starting an token", "/a", 2, absl::nullopt, nullptr},
+ {"0x30 starting an token", "0a", 2, absl::nullopt, nullptr},
+ {"0x31 starting an token", "1a", 2, absl::nullopt, nullptr},
+ {"0x32 starting an token", "2a", 2, absl::nullopt, nullptr},
+ {"0x33 starting an token", "3a", 2, absl::nullopt, nullptr},
+ {"0x34 starting an token", "4a", 2, absl::nullopt, nullptr},
+ {"0x35 starting an token", "5a", 2, absl::nullopt, nullptr},
+ {"0x36 starting an token", "6a", 2, absl::nullopt, nullptr},
+ {"0x37 starting an token", "7a", 2, absl::nullopt, nullptr},
+ {"0x38 starting an token", "8a", 2, absl::nullopt, nullptr},
+ {"0x39 starting an token", "9a", 2, absl::nullopt, nullptr},
+ {"0x3a starting an token", ":a", 2, absl::nullopt, nullptr},
+ {"0x3b starting an token", ";a", 2, absl::nullopt, nullptr},
+ {"0x3c starting an token", "<a", 2, absl::nullopt, nullptr},
+ {"0x3d starting an token", "=a", 2, absl::nullopt, nullptr},
+ {"0x3e starting an token", ">a", 2, absl::nullopt, nullptr},
+ {"0x3f starting an token", "?a", 2, absl::nullopt, nullptr},
+ {"0x40 starting an token", "@a", 2, absl::nullopt, nullptr},
+ {"0x41 starting an token",
+ "Aa",
+ 2,
+ {{Item("Aa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x42 starting an token",
+ "Ba",
+ 2,
+ {{Item("Ba", Item::kTokenType), {}}},
+ nullptr},
+ {"0x43 starting an token",
+ "Ca",
+ 2,
+ {{Item("Ca", Item::kTokenType), {}}},
+ nullptr},
+ {"0x44 starting an token",
+ "Da",
+ 2,
+ {{Item("Da", Item::kTokenType), {}}},
+ nullptr},
+ {"0x45 starting an token",
+ "Ea",
+ 2,
+ {{Item("Ea", Item::kTokenType), {}}},
+ nullptr},
+ {"0x46 starting an token",
+ "Fa",
+ 2,
+ {{Item("Fa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x47 starting an token",
+ "Ga",
+ 2,
+ {{Item("Ga", Item::kTokenType), {}}},
+ nullptr},
+ {"0x48 starting an token",
+ "Ha",
+ 2,
+ {{Item("Ha", Item::kTokenType), {}}},
+ nullptr},
+ {"0x49 starting an token",
+ "Ia",
+ 2,
+ {{Item("Ia", Item::kTokenType), {}}},
+ nullptr},
+ {"0x4a starting an token",
+ "Ja",
+ 2,
+ {{Item("Ja", Item::kTokenType), {}}},
+ nullptr},
+ {"0x4b starting an token",
+ "Ka",
+ 2,
+ {{Item("Ka", Item::kTokenType), {}}},
+ nullptr},
+ {"0x4c starting an token",
+ "La",
+ 2,
+ {{Item("La", Item::kTokenType), {}}},
+ nullptr},
+ {"0x4d starting an token",
+ "Ma",
+ 2,
+ {{Item("Ma", Item::kTokenType), {}}},
+ nullptr},
+ {"0x4e starting an token",
+ "Na",
+ 2,
+ {{Item("Na", Item::kTokenType), {}}},
+ nullptr},
+ {"0x4f starting an token",
+ "Oa",
+ 2,
+ {{Item("Oa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x50 starting an token",
+ "Pa",
+ 2,
+ {{Item("Pa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x51 starting an token",
+ "Qa",
+ 2,
+ {{Item("Qa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x52 starting an token",
+ "Ra",
+ 2,
+ {{Item("Ra", Item::kTokenType), {}}},
+ nullptr},
+ {"0x53 starting an token",
+ "Sa",
+ 2,
+ {{Item("Sa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x54 starting an token",
+ "Ta",
+ 2,
+ {{Item("Ta", Item::kTokenType), {}}},
+ nullptr},
+ {"0x55 starting an token",
+ "Ua",
+ 2,
+ {{Item("Ua", Item::kTokenType), {}}},
+ nullptr},
+ {"0x56 starting an token",
+ "Va",
+ 2,
+ {{Item("Va", Item::kTokenType), {}}},
+ nullptr},
+ {"0x57 starting an token",
+ "Wa",
+ 2,
+ {{Item("Wa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x58 starting an token",
+ "Xa",
+ 2,
+ {{Item("Xa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x59 starting an token",
+ "Ya",
+ 2,
+ {{Item("Ya", Item::kTokenType), {}}},
+ nullptr},
+ {"0x5a starting an token",
+ "Za",
+ 2,
+ {{Item("Za", Item::kTokenType), {}}},
+ nullptr},
+ {"0x5b starting an token", "[a", 2, absl::nullopt, nullptr},
+ {"0x5c starting an token", "\\a", 2, absl::nullopt, nullptr},
+ {"0x5d starting an token", "]a", 2, absl::nullopt, nullptr},
+ {"0x5e starting an token", "^a", 2, absl::nullopt, nullptr},
+ {"0x5f starting an token", "_a", 2, absl::nullopt, nullptr},
+ {"0x60 starting an token", "`a", 2, absl::nullopt, nullptr},
+ {"0x61 starting an token",
+ "aa",
+ 2,
+ {{Item("aa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x62 starting an token",
+ "ba",
+ 2,
+ {{Item("ba", Item::kTokenType), {}}},
+ nullptr},
+ {"0x63 starting an token",
+ "ca",
+ 2,
+ {{Item("ca", Item::kTokenType), {}}},
+ nullptr},
+ {"0x64 starting an token",
+ "da",
+ 2,
+ {{Item("da", Item::kTokenType), {}}},
+ nullptr},
+ {"0x65 starting an token",
+ "ea",
+ 2,
+ {{Item("ea", Item::kTokenType), {}}},
+ nullptr},
+ {"0x66 starting an token",
+ "fa",
+ 2,
+ {{Item("fa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x67 starting an token",
+ "ga",
+ 2,
+ {{Item("ga", Item::kTokenType), {}}},
+ nullptr},
+ {"0x68 starting an token",
+ "ha",
+ 2,
+ {{Item("ha", Item::kTokenType), {}}},
+ nullptr},
+ {"0x69 starting an token",
+ "ia",
+ 2,
+ {{Item("ia", Item::kTokenType), {}}},
+ nullptr},
+ {"0x6a starting an token",
+ "ja",
+ 2,
+ {{Item("ja", Item::kTokenType), {}}},
+ nullptr},
+ {"0x6b starting an token",
+ "ka",
+ 2,
+ {{Item("ka", Item::kTokenType), {}}},
+ nullptr},
+ {"0x6c starting an token",
+ "la",
+ 2,
+ {{Item("la", Item::kTokenType), {}}},
+ nullptr},
+ {"0x6d starting an token",
+ "ma",
+ 2,
+ {{Item("ma", Item::kTokenType), {}}},
+ nullptr},
+ {"0x6e starting an token",
+ "na",
+ 2,
+ {{Item("na", Item::kTokenType), {}}},
+ nullptr},
+ {"0x6f starting an token",
+ "oa",
+ 2,
+ {{Item("oa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x70 starting an token",
+ "pa",
+ 2,
+ {{Item("pa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x71 starting an token",
+ "qa",
+ 2,
+ {{Item("qa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x72 starting an token",
+ "ra",
+ 2,
+ {{Item("ra", Item::kTokenType), {}}},
+ nullptr},
+ {"0x73 starting an token",
+ "sa",
+ 2,
+ {{Item("sa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x74 starting an token",
+ "ta",
+ 2,
+ {{Item("ta", Item::kTokenType), {}}},
+ nullptr},
+ {"0x75 starting an token",
+ "ua",
+ 2,
+ {{Item("ua", Item::kTokenType), {}}},
+ nullptr},
+ {"0x76 starting an token",
+ "va",
+ 2,
+ {{Item("va", Item::kTokenType), {}}},
+ nullptr},
+ {"0x77 starting an token",
+ "wa",
+ 2,
+ {{Item("wa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x78 starting an token",
+ "xa",
+ 2,
+ {{Item("xa", Item::kTokenType), {}}},
+ nullptr},
+ {"0x79 starting an token",
+ "ya",
+ 2,
+ {{Item("ya", Item::kTokenType), {}}},
+ nullptr},
+ {"0x7a starting an token",
+ "za",
+ 2,
+ {{Item("za", Item::kTokenType), {}}},
+ nullptr},
+ {"0x7b starting an token", "{a", 2, absl::nullopt, nullptr},
+ {"0x7c starting an token", "|a", 2, absl::nullopt, nullptr},
+ {"0x7d starting an token", "}a", 2, absl::nullopt, nullptr},
+ {"0x7e starting an token", "~a", 2, absl::nullopt, nullptr},
+ {"0x7f starting an token", "\177a", 2, absl::nullopt, nullptr},
+ // token.json
+ {"basic token - item",
+ "a_b-c.d3:f%00/*",
+ 15,
+ {{Item("a_b-c.d3:f%00/*", Item::kTokenType), {}}},
+ nullptr},
+ {"token with capitals - item",
+ "fooBar",
+ 6,
+ {{Item("fooBar", Item::kTokenType), {}}},
+ nullptr},
+ {"token starting with capitals - item",
+ "FooBar",
+ 6,
+ {{Item("FooBar", Item::kTokenType), {}}},
+ nullptr},
+};
+
+const struct ListTestCase {
+ const char* name;
+ const char* raw;
+ size_t raw_len;
+ const absl::optional<List> expected; // nullopt if parse error is expected.
+ const char* canonical; // nullptr if parse error is expected, or if canonical
+ // format is identical to raw.
+} list_test_cases[] = {
+ // examples.json
+ {"Example-StrListHeader",
+ "\"foo\", \"bar\", \"It was the best of times.\"",
+ 41,
+ {{{Item("foo"), {}},
+ {Item("bar"), {}},
+ {Item("It was the best of times."), {}}}},
+ nullptr},
+ {"Example-Hdr (list on one line)",
+ "foo, bar",
+ 8,
+ {{{Item("foo", Item::kTokenType), {}},
+ {Item("bar", Item::kTokenType), {}}}},
+ nullptr},
+ {"Example-Hdr (list on two lines)",
+ "foo, bar",
+ 8,
+ {{{Item("foo", Item::kTokenType), {}},
+ {Item("bar", Item::kTokenType), {}}}},
+ "foo, bar"},
+ {"Example-StrListListHeader",
+ "(\"foo\" \"bar\"), (\"baz\"), (\"bat\" \"one\"), ()",
+ 41,
+ {{{{{Item("foo"), {}}, {Item("bar"), {}}}, {}},
+ {{{Item("baz"), {}}}, {}},
+ {{{Item("bat"), {}}, {Item("one"), {}}}, {}},
+ {std::vector<ParameterizedItem>(), {}}}},
+ nullptr},
+ {"Example-ListListParam",
+ "(\"foo\"; a=1;b=2);lvl=5, (\"bar\" \"baz\");lvl=1",
+ 43,
+ {{{{{Item("foo"), {Param("a", 1), Param("b", 2)}}}, {Param("lvl", 5)}},
+ {{{Item("bar"), {}}, {Item("baz"), {}}}, {Param("lvl", 1)}}}},
+ "(\"foo\";a=1;b=2);lvl=5, (\"bar\" \"baz\");lvl=1"},
+ {"Example-ParamListHeader",
+ "abc;a=1;b=2; cde_456, (ghi;jk=4 l);q=\"9\";r=w",
+ 44,
+ {{{Item("abc", Item::kTokenType),
+ {Param("a", 1), Param("b", 2), BooleanParam("cde_456", true)}},
+ {{{Item("ghi", Item::kTokenType), {Param("jk", 4)}},
+ {Item("l", Item::kTokenType), {}}},
+ {Param("q", "9"), TokenParam("r", "w")}}}},
+ "abc;a=1;b=2;cde_456, (ghi;jk=4 l);q=\"9\";r=w"},
+ // key-generated.json
+ {"0x00 in parameterised list key", "foo; a\000a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x01 in parameterised list key", "foo; a\001a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x02 in parameterised list key", "foo; a\002a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x03 in parameterised list key", "foo; a\003a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x04 in parameterised list key", "foo; a\004a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x05 in parameterised list key", "foo; a\005a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x06 in parameterised list key", "foo; a\006a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x07 in parameterised list key", "foo; a\aa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x08 in parameterised list key", "foo; a\ba=1", 10, absl::nullopt,
+ nullptr},
+ {"0x09 in parameterised list key", "foo; a\ta=1", 10, absl::nullopt,
+ nullptr},
+ {"0x0a in parameterised list key", "foo; a\na=1", 10, absl::nullopt,
+ nullptr},
+ {"0x0b in parameterised list key", "foo; a\va=1", 10, absl::nullopt,
+ nullptr},
+ {"0x0c in parameterised list key", "foo; a\fa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x0d in parameterised list key", "foo; a\ra=1", 10, absl::nullopt,
+ nullptr},
+ {"0x0e in parameterised list key", "foo; a\016a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x0f in parameterised list key", "foo; a\017a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x10 in parameterised list key", "foo; a\020a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x11 in parameterised list key", "foo; a\021a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x12 in parameterised list key", "foo; a\022a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x13 in parameterised list key", "foo; a\023a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x14 in parameterised list key", "foo; a\024a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x15 in parameterised list key", "foo; a\025a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x16 in parameterised list key", "foo; a\026a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x17 in parameterised list key", "foo; a\027a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x18 in parameterised list key", "foo; a\030a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x19 in parameterised list key", "foo; a\031a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x1a in parameterised list key", "foo; a\032a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x1b in parameterised list key", "foo; a\033a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x1c in parameterised list key", "foo; a\034a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x1d in parameterised list key", "foo; a\035a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x1e in parameterised list key", "foo; a\036a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x1f in parameterised list key", "foo; a\037a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x20 in parameterised list key", "foo; a a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x21 in parameterised list key", "foo; a!a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x22 in parameterised list key", "foo; a\"a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x23 in parameterised list key", "foo; a#a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x24 in parameterised list key", "foo; a$a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x25 in parameterised list key", "foo; a%a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x26 in parameterised list key", "foo; a&a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x27 in parameterised list key", "foo; a'a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x28 in parameterised list key", "foo; a(a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x29 in parameterised list key", "foo; a)a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x2a in parameterised list key",
+ "foo; a*a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a*a", 1)}}}},
+ "foo;a*a=1"},
+ {"0x2b in parameterised list key", "foo; a+a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x2c in parameterised list key", "foo; a,a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x2d in parameterised list key",
+ "foo; a-a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a-a", 1)}}}},
+ "foo;a-a=1"},
+ {"0x2e in parameterised list key",
+ "foo; a.a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a.a", 1)}}}},
+ "foo;a.a=1"},
+ {"0x2f in parameterised list key", "foo; a/a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x30 in parameterised list key",
+ "foo; a0a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a0a", 1)}}}},
+ "foo;a0a=1"},
+ {"0x31 in parameterised list key",
+ "foo; a1a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a1a", 1)}}}},
+ "foo;a1a=1"},
+ {"0x32 in parameterised list key",
+ "foo; a2a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a2a", 1)}}}},
+ "foo;a2a=1"},
+ {"0x33 in parameterised list key",
+ "foo; a3a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a3a", 1)}}}},
+ "foo;a3a=1"},
+ {"0x34 in parameterised list key",
+ "foo; a4a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a4a", 1)}}}},
+ "foo;a4a=1"},
+ {"0x35 in parameterised list key",
+ "foo; a5a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a5a", 1)}}}},
+ "foo;a5a=1"},
+ {"0x36 in parameterised list key",
+ "foo; a6a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a6a", 1)}}}},
+ "foo;a6a=1"},
+ {"0x37 in parameterised list key",
+ "foo; a7a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a7a", 1)}}}},
+ "foo;a7a=1"},
+ {"0x38 in parameterised list key",
+ "foo; a8a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a8a", 1)}}}},
+ "foo;a8a=1"},
+ {"0x39 in parameterised list key",
+ "foo; a9a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a9a", 1)}}}},
+ "foo;a9a=1"},
+ {"0x3a in parameterised list key", "foo; a:a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x3b in parameterised list key",
+ "foo; a;a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a", 1)}}}},
+ "foo;a=1"},
+ {"0x3c in parameterised list key", "foo; a<a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x3d in parameterised list key", "foo; a=a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x3e in parameterised list key", "foo; a>a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x3f in parameterised list key", "foo; a?a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x40 in parameterised list key", "foo; a@a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x41 in parameterised list key", "foo; aAa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x42 in parameterised list key", "foo; aBa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x43 in parameterised list key", "foo; aCa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x44 in parameterised list key", "foo; aDa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x45 in parameterised list key", "foo; aEa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x46 in parameterised list key", "foo; aFa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x47 in parameterised list key", "foo; aGa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x48 in parameterised list key", "foo; aHa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x49 in parameterised list key", "foo; aIa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x4a in parameterised list key", "foo; aJa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x4b in parameterised list key", "foo; aKa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x4c in parameterised list key", "foo; aLa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x4d in parameterised list key", "foo; aMa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x4e in parameterised list key", "foo; aNa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x4f in parameterised list key", "foo; aOa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x50 in parameterised list key", "foo; aPa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x51 in parameterised list key", "foo; aQa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x52 in parameterised list key", "foo; aRa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x53 in parameterised list key", "foo; aSa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x54 in parameterised list key", "foo; aTa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x55 in parameterised list key", "foo; aUa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x56 in parameterised list key", "foo; aVa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x57 in parameterised list key", "foo; aWa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x58 in parameterised list key", "foo; aXa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x59 in parameterised list key", "foo; aYa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x5a in parameterised list key", "foo; aZa=1", 10, absl::nullopt,
+ nullptr},
+ {"0x5b in parameterised list key", "foo; a[a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x5c in parameterised list key", "foo; a\\a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x5d in parameterised list key", "foo; a]a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x5e in parameterised list key", "foo; a^a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x5f in parameterised list key",
+ "foo; a_a=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("a_a", 1)}}}},
+ "foo;a_a=1"},
+ {"0x60 in parameterised list key", "foo; a`a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x61 in parameterised list key",
+ "foo; aaa=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aaa", 1)}}}},
+ "foo;aaa=1"},
+ {"0x62 in parameterised list key",
+ "foo; aba=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aba", 1)}}}},
+ "foo;aba=1"},
+ {"0x63 in parameterised list key",
+ "foo; aca=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aca", 1)}}}},
+ "foo;aca=1"},
+ {"0x64 in parameterised list key",
+ "foo; ada=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("ada", 1)}}}},
+ "foo;ada=1"},
+ {"0x65 in parameterised list key",
+ "foo; aea=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aea", 1)}}}},
+ "foo;aea=1"},
+ {"0x66 in parameterised list key",
+ "foo; afa=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("afa", 1)}}}},
+ "foo;afa=1"},
+ {"0x67 in parameterised list key",
+ "foo; aga=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aga", 1)}}}},
+ "foo;aga=1"},
+ {"0x68 in parameterised list key",
+ "foo; aha=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aha", 1)}}}},
+ "foo;aha=1"},
+ {"0x69 in parameterised list key",
+ "foo; aia=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aia", 1)}}}},
+ "foo;aia=1"},
+ {"0x6a in parameterised list key",
+ "foo; aja=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aja", 1)}}}},
+ "foo;aja=1"},
+ {"0x6b in parameterised list key",
+ "foo; aka=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aka", 1)}}}},
+ "foo;aka=1"},
+ {"0x6c in parameterised list key",
+ "foo; ala=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("ala", 1)}}}},
+ "foo;ala=1"},
+ {"0x6d in parameterised list key",
+ "foo; ama=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("ama", 1)}}}},
+ "foo;ama=1"},
+ {"0x6e in parameterised list key",
+ "foo; ana=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("ana", 1)}}}},
+ "foo;ana=1"},
+ {"0x6f in parameterised list key",
+ "foo; aoa=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aoa", 1)}}}},
+ "foo;aoa=1"},
+ {"0x70 in parameterised list key",
+ "foo; apa=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("apa", 1)}}}},
+ "foo;apa=1"},
+ {"0x71 in parameterised list key",
+ "foo; aqa=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aqa", 1)}}}},
+ "foo;aqa=1"},
+ {"0x72 in parameterised list key",
+ "foo; ara=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("ara", 1)}}}},
+ "foo;ara=1"},
+ {"0x73 in parameterised list key",
+ "foo; asa=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("asa", 1)}}}},
+ "foo;asa=1"},
+ {"0x74 in parameterised list key",
+ "foo; ata=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("ata", 1)}}}},
+ "foo;ata=1"},
+ {"0x75 in parameterised list key",
+ "foo; aua=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aua", 1)}}}},
+ "foo;aua=1"},
+ {"0x76 in parameterised list key",
+ "foo; ava=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("ava", 1)}}}},
+ "foo;ava=1"},
+ {"0x77 in parameterised list key",
+ "foo; awa=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("awa", 1)}}}},
+ "foo;awa=1"},
+ {"0x78 in parameterised list key",
+ "foo; axa=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("axa", 1)}}}},
+ "foo;axa=1"},
+ {"0x79 in parameterised list key",
+ "foo; aya=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aya", 1)}}}},
+ "foo;aya=1"},
+ {"0x7a in parameterised list key",
+ "foo; aza=1",
+ 10,
+ {{{Item("foo", Item::kTokenType), {Param("aza", 1)}}}},
+ "foo;aza=1"},
+ {"0x7b in parameterised list key", "foo; a{a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x7c in parameterised list key", "foo; a|a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x7d in parameterised list key", "foo; a}a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x7e in parameterised list key", "foo; a~a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x7f in parameterised list key", "foo; a\177a=1", 10, absl::nullopt,
+ nullptr},
+ {"0x00 starting a parameterised list key", "foo; \000a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x01 starting a parameterised list key", "foo; \001a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x02 starting a parameterised list key", "foo; \002a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x03 starting a parameterised list key", "foo; \003a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x04 starting a parameterised list key", "foo; \004a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x05 starting a parameterised list key", "foo; \005a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x06 starting a parameterised list key", "foo; \006a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x07 starting a parameterised list key", "foo; \aa=1", 9, absl::nullopt,
+ nullptr},
+ {"0x08 starting a parameterised list key", "foo; \ba=1", 9, absl::nullopt,
+ nullptr},
+ {"0x09 starting a parameterised list key", "foo; \ta=1", 9, absl::nullopt,
+ nullptr},
+ {"0x0a starting a parameterised list key", "foo; \na=1", 9, absl::nullopt,
+ nullptr},
+ {"0x0b starting a parameterised list key", "foo; \va=1", 9, absl::nullopt,
+ nullptr},
+ {"0x0c starting a parameterised list key", "foo; \fa=1", 9, absl::nullopt,
+ nullptr},
+ {"0x0d starting a parameterised list key", "foo; \ra=1", 9, absl::nullopt,
+ nullptr},
+ {"0x0e starting a parameterised list key", "foo; \016a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x0f starting a parameterised list key", "foo; \017a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x10 starting a parameterised list key", "foo; \020a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x11 starting a parameterised list key", "foo; \021a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x12 starting a parameterised list key", "foo; \022a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x13 starting a parameterised list key", "foo; \023a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x14 starting a parameterised list key", "foo; \024a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x15 starting a parameterised list key", "foo; \025a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x16 starting a parameterised list key", "foo; \026a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x17 starting a parameterised list key", "foo; \027a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x18 starting a parameterised list key", "foo; \030a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x19 starting a parameterised list key", "foo; \031a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x1a starting a parameterised list key", "foo; \032a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x1b starting a parameterised list key", "foo; \033a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x1c starting a parameterised list key", "foo; \034a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x1d starting a parameterised list key", "foo; \035a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x1e starting a parameterised list key", "foo; \036a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x1f starting a parameterised list key", "foo; \037a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x20 starting a parameterised list key",
+ "foo; a=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("a", 1)}}}},
+ "foo;a=1"},
+ {"0x21 starting a parameterised list key", "foo; !a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x22 starting a parameterised list key", "foo; \"a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x23 starting a parameterised list key", "foo; #a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x24 starting a parameterised list key", "foo; $a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x25 starting a parameterised list key", "foo; %a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x26 starting a parameterised list key", "foo; &a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x27 starting a parameterised list key", "foo; 'a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x28 starting a parameterised list key", "foo; (a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x29 starting a parameterised list key", "foo; )a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x2a starting a parameterised list key",
+ "foo; *a=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("*a", 1)}}}},
+ "foo;*a=1"},
+ {"0x2b starting a parameterised list key", "foo; +a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x2c starting a parameterised list key", "foo; ,a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x2d starting a parameterised list key", "foo; -a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x2e starting a parameterised list key", "foo; .a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x2f starting a parameterised list key", "foo; /a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x30 starting a parameterised list key", "foo; 0a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x31 starting a parameterised list key", "foo; 1a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x32 starting a parameterised list key", "foo; 2a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x33 starting a parameterised list key", "foo; 3a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x34 starting a parameterised list key", "foo; 4a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x35 starting a parameterised list key", "foo; 5a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x36 starting a parameterised list key", "foo; 6a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x37 starting a parameterised list key", "foo; 7a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x38 starting a parameterised list key", "foo; 8a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x39 starting a parameterised list key", "foo; 9a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x3a starting a parameterised list key", "foo; :a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x3b starting a parameterised list key", "foo; ;a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x3c starting a parameterised list key", "foo; <a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x3d starting a parameterised list key", "foo; =a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x3e starting a parameterised list key", "foo; >a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x3f starting a parameterised list key", "foo; ?a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x40 starting a parameterised list key", "foo; @a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x41 starting a parameterised list key", "foo; Aa=1", 9, absl::nullopt,
+ nullptr},
+ {"0x42 starting a parameterised list key", "foo; Ba=1", 9, absl::nullopt,
+ nullptr},
+ {"0x43 starting a parameterised list key", "foo; Ca=1", 9, absl::nullopt,
+ nullptr},
+ {"0x44 starting a parameterised list key", "foo; Da=1", 9, absl::nullopt,
+ nullptr},
+ {"0x45 starting a parameterised list key", "foo; Ea=1", 9, absl::nullopt,
+ nullptr},
+ {"0x46 starting a parameterised list key", "foo; Fa=1", 9, absl::nullopt,
+ nullptr},
+ {"0x47 starting a parameterised list key", "foo; Ga=1", 9, absl::nullopt,
+ nullptr},
+ {"0x48 starting a parameterised list key", "foo; Ha=1", 9, absl::nullopt,
+ nullptr},
+ {"0x49 starting a parameterised list key", "foo; Ia=1", 9, absl::nullopt,
+ nullptr},
+ {"0x4a starting a parameterised list key", "foo; Ja=1", 9, absl::nullopt,
+ nullptr},
+ {"0x4b starting a parameterised list key", "foo; Ka=1", 9, absl::nullopt,
+ nullptr},
+ {"0x4c starting a parameterised list key", "foo; La=1", 9, absl::nullopt,
+ nullptr},
+ {"0x4d starting a parameterised list key", "foo; Ma=1", 9, absl::nullopt,
+ nullptr},
+ {"0x4e starting a parameterised list key", "foo; Na=1", 9, absl::nullopt,
+ nullptr},
+ {"0x4f starting a parameterised list key", "foo; Oa=1", 9, absl::nullopt,
+ nullptr},
+ {"0x50 starting a parameterised list key", "foo; Pa=1", 9, absl::nullopt,
+ nullptr},
+ {"0x51 starting a parameterised list key", "foo; Qa=1", 9, absl::nullopt,
+ nullptr},
+ {"0x52 starting a parameterised list key", "foo; Ra=1", 9, absl::nullopt,
+ nullptr},
+ {"0x53 starting a parameterised list key", "foo; Sa=1", 9, absl::nullopt,
+ nullptr},
+ {"0x54 starting a parameterised list key", "foo; Ta=1", 9, absl::nullopt,
+ nullptr},
+ {"0x55 starting a parameterised list key", "foo; Ua=1", 9, absl::nullopt,
+ nullptr},
+ {"0x56 starting a parameterised list key", "foo; Va=1", 9, absl::nullopt,
+ nullptr},
+ {"0x57 starting a parameterised list key", "foo; Wa=1", 9, absl::nullopt,
+ nullptr},
+ {"0x58 starting a parameterised list key", "foo; Xa=1", 9, absl::nullopt,
+ nullptr},
+ {"0x59 starting a parameterised list key", "foo; Ya=1", 9, absl::nullopt,
+ nullptr},
+ {"0x5a starting a parameterised list key", "foo; Za=1", 9, absl::nullopt,
+ nullptr},
+ {"0x5b starting a parameterised list key", "foo; [a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x5c starting a parameterised list key", "foo; \\a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x5d starting a parameterised list key", "foo; ]a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x5e starting a parameterised list key", "foo; ^a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x5f starting a parameterised list key", "foo; _a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x60 starting a parameterised list key", "foo; `a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x61 starting a parameterised list key",
+ "foo; aa=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("aa", 1)}}}},
+ "foo;aa=1"},
+ {"0x62 starting a parameterised list key",
+ "foo; ba=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ba", 1)}}}},
+ "foo;ba=1"},
+ {"0x63 starting a parameterised list key",
+ "foo; ca=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ca", 1)}}}},
+ "foo;ca=1"},
+ {"0x64 starting a parameterised list key",
+ "foo; da=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("da", 1)}}}},
+ "foo;da=1"},
+ {"0x65 starting a parameterised list key",
+ "foo; ea=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ea", 1)}}}},
+ "foo;ea=1"},
+ {"0x66 starting a parameterised list key",
+ "foo; fa=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("fa", 1)}}}},
+ "foo;fa=1"},
+ {"0x67 starting a parameterised list key",
+ "foo; ga=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ga", 1)}}}},
+ "foo;ga=1"},
+ {"0x68 starting a parameterised list key",
+ "foo; ha=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ha", 1)}}}},
+ "foo;ha=1"},
+ {"0x69 starting a parameterised list key",
+ "foo; ia=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ia", 1)}}}},
+ "foo;ia=1"},
+ {"0x6a starting a parameterised list key",
+ "foo; ja=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ja", 1)}}}},
+ "foo;ja=1"},
+ {"0x6b starting a parameterised list key",
+ "foo; ka=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ka", 1)}}}},
+ "foo;ka=1"},
+ {"0x6c starting a parameterised list key",
+ "foo; la=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("la", 1)}}}},
+ "foo;la=1"},
+ {"0x6d starting a parameterised list key",
+ "foo; ma=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ma", 1)}}}},
+ "foo;ma=1"},
+ {"0x6e starting a parameterised list key",
+ "foo; na=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("na", 1)}}}},
+ "foo;na=1"},
+ {"0x6f starting a parameterised list key",
+ "foo; oa=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("oa", 1)}}}},
+ "foo;oa=1"},
+ {"0x70 starting a parameterised list key",
+ "foo; pa=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("pa", 1)}}}},
+ "foo;pa=1"},
+ {"0x71 starting a parameterised list key",
+ "foo; qa=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("qa", 1)}}}},
+ "foo;qa=1"},
+ {"0x72 starting a parameterised list key",
+ "foo; ra=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ra", 1)}}}},
+ "foo;ra=1"},
+ {"0x73 starting a parameterised list key",
+ "foo; sa=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("sa", 1)}}}},
+ "foo;sa=1"},
+ {"0x74 starting a parameterised list key",
+ "foo; ta=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ta", 1)}}}},
+ "foo;ta=1"},
+ {"0x75 starting a parameterised list key",
+ "foo; ua=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ua", 1)}}}},
+ "foo;ua=1"},
+ {"0x76 starting a parameterised list key",
+ "foo; va=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("va", 1)}}}},
+ "foo;va=1"},
+ {"0x77 starting a parameterised list key",
+ "foo; wa=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("wa", 1)}}}},
+ "foo;wa=1"},
+ {"0x78 starting a parameterised list key",
+ "foo; xa=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("xa", 1)}}}},
+ "foo;xa=1"},
+ {"0x79 starting a parameterised list key",
+ "foo; ya=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("ya", 1)}}}},
+ "foo;ya=1"},
+ {"0x7a starting a parameterised list key",
+ "foo; za=1",
+ 9,
+ {{{Item("foo", Item::kTokenType), {Param("za", 1)}}}},
+ "foo;za=1"},
+ {"0x7b starting a parameterised list key", "foo; {a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x7c starting a parameterised list key", "foo; |a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x7d starting a parameterised list key", "foo; }a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x7e starting a parameterised list key", "foo; ~a=1", 9, absl::nullopt,
+ nullptr},
+ {"0x7f starting a parameterised list key", "foo; \177a=1", 9, absl::nullopt,
+ nullptr},
+ // list.json
+ {"basic list",
+ "1, 42",
+ 5,
+ {{{Integer(1), {}}, {Integer(42), {}}}},
+ nullptr},
+ {"empty list", "", 0, {List()}, nullptr},
+ {"leading SP list",
+ " 42, 43",
+ 8,
+ {{{Integer(42), {}}, {Integer(43), {}}}},
+ "42, 43"},
+ {"single item list", "42", 2, {{{Integer(42), {}}}}, nullptr},
+ {"no whitespace list",
+ "1,42",
+ 4,
+ {{{Integer(1), {}}, {Integer(42), {}}}},
+ "1, 42"},
+ {"extra whitespace list",
+ "1 , 42",
+ 6,
+ {{{Integer(1), {}}, {Integer(42), {}}}},
+ "1, 42"},
+ {"tab separated list",
+ "1\t,\t42",
+ 6,
+ {{{Integer(1), {}}, {Integer(42), {}}}},
+ "1, 42"},
+ {"two line list",
+ "1, 42",
+ 5,
+ {{{Integer(1), {}}, {Integer(42), {}}}},
+ "1, 42"},
+ {"trailing comma list", "1, 42,", 6, absl::nullopt, nullptr},
+ {"empty item list", "1,,42", 5, absl::nullopt, nullptr},
+ {"empty item list (multiple field lines)", "1, , 42", 7, absl::nullopt,
+ nullptr},
+ // listlist.json
+ {"basic list of lists",
+ "(1 2), (42 43)",
+ 14,
+ {{{{{Integer(1), {}}, {Integer(2), {}}}, {}},
+ {{{Integer(42), {}}, {Integer(43), {}}}, {}}}},
+ nullptr},
+ {"single item list of lists",
+ "(42)",
+ 4,
+ {{{{{Integer(42), {}}}, {}}}},
+ nullptr},
+ {"empty item list of lists",
+ "()",
+ 2,
+ {{{std::vector<ParameterizedItem>(), {}}}},
+ nullptr},
+ {"empty middle item list of lists",
+ "(1),(),(42)",
+ 11,
+ {{{{{Integer(1), {}}}, {}},
+ {std::vector<ParameterizedItem>(), {}},
+ {{{Integer(42), {}}}, {}}}},
+ "(1), (), (42)"},
+ {"extra whitespace list of lists",
+ "( 1 42 )",
+ 11,
+ {{{{{Integer(1), {}}, {Integer(42), {}}}, {}}}},
+ "(1 42)"},
+ {"wrong whitespace list of lists", "(1\t 42)", 7, absl::nullopt, nullptr},
+ {"no trailing parenthesis list of lists", "(1 42", 5, absl::nullopt,
+ nullptr},
+ {"no trailing parenthesis middle list of lists", "(1 2, (42 43)", 13,
+ absl::nullopt, nullptr},
+ {"no spaces in inner-list", "(abc\"def\"?0123*dXZ3*xyz)", 24, absl::nullopt,
+ nullptr},
+ {"no closing parenthesis", "(", 1, absl::nullopt, nullptr},
+ // param-list.json
+ {"basic parameterised list",
+ "abc_123;a=1;b=2; cdef_456, ghi;q=9;r=\"+w\"",
+ 41,
+ {{{Item("abc_123", Item::kTokenType),
+ {Param("a", 1), Param("b", 2), BooleanParam("cdef_456", true)}},
+ {Item("ghi", Item::kTokenType), {Param("q", 9), Param("r", "+w")}}}},
+ "abc_123;a=1;b=2;cdef_456, ghi;q=9;r=\"+w\""},
+ {"single item parameterised list",
+ "text/html;q=1.0",
+ 15,
+ {{{Item("text/html", Item::kTokenType), {DoubleParam("q", 1.000000)}}}},
+ nullptr},
+ {"missing parameter value parameterised list",
+ "text/html;a;q=1.0",
+ 17,
+ {{{Item("text/html", Item::kTokenType),
+ {BooleanParam("a", true), DoubleParam("q", 1.000000)}}}},
+ nullptr},
+ {"missing terminal parameter value parameterised list",
+ "text/html;q=1.0;a",
+ 17,
+ {{{Item("text/html", Item::kTokenType),
+ {DoubleParam("q", 1.000000), BooleanParam("a", true)}}}},
+ nullptr},
+ {"no whitespace parameterised list",
+ "text/html,text/plain;q=0.5",
+ 26,
+ {{{Item("text/html", Item::kTokenType), {}},
+ {Item("text/plain", Item::kTokenType), {DoubleParam("q", 0.500000)}}}},
+ "text/html, text/plain;q=0.5"},
+ {"whitespace before = parameterised list", "text/html, text/plain;q =0.5",
+ 28, absl::nullopt, nullptr},
+ {"whitespace after = parameterised list", "text/html, text/plain;q= 0.5",
+ 28, absl::nullopt, nullptr},
+ {"whitespace before ; parameterised list", "text/html, text/plain ;q=0.5",
+ 28, absl::nullopt, nullptr},
+ {"whitespace after ; parameterised list",
+ "text/html, text/plain; q=0.5",
+ 28,
+ {{{Item("text/html", Item::kTokenType), {}},
+ {Item("text/plain", Item::kTokenType), {DoubleParam("q", 0.500000)}}}},
+ "text/html, text/plain;q=0.5"},
+ {"extra whitespace parameterised list",
+ "text/html , text/plain; q=0.5; charset=utf-8",
+ 48,
+ {{{Item("text/html", Item::kTokenType), {}},
+ {Item("text/plain", Item::kTokenType),
+ {DoubleParam("q", 0.500000), TokenParam("charset", "utf-8")}}}},
+ "text/html, text/plain;q=0.5;charset=utf-8"},
+ {"two lines parameterised list",
+ "text/html, text/plain;q=0.5",
+ 27,
+ {{{Item("text/html", Item::kTokenType), {}},
+ {Item("text/plain", Item::kTokenType), {DoubleParam("q", 0.500000)}}}},
+ "text/html, text/plain;q=0.5"},
+ {"trailing comma parameterised list", "text/html,text/plain;q=0.5,", 27,
+ absl::nullopt, nullptr},
+ {"empty item parameterised list", "text/html,,text/plain;q=0.5,", 28,
+ absl::nullopt, nullptr},
+ // param-listlist.json
+ {"parameterised inner list",
+ "(abc_123);a=1;b=2, cdef_456",
+ 27,
+ {{{{{Item("abc_123", Item::kTokenType), {}}},
+ {Param("a", 1), Param("b", 2)}},
+ {Item("cdef_456", Item::kTokenType), {}}}},
+ nullptr},
+ {"parameterised inner list item",
+ "(abc_123;a=1;b=2;cdef_456)",
+ 26,
+ {{{{{Item("abc_123", Item::kTokenType),
+ {Param("a", 1), Param("b", 2), BooleanParam("cdef_456", true)}}},
+ {}}}},
+ nullptr},
+ {"parameterised inner list with parameterised item",
+ "(abc_123;a=1;b=2);cdef_456",
+ 26,
+ {{{{{Item("abc_123", Item::kTokenType), {Param("a", 1), Param("b", 2)}}},
+ {BooleanParam("cdef_456", true)}}}},
+ nullptr},
+ // token.json
+ {"basic token - list",
+ "a_b-c3/*",
+ 8,
+ {{{Item("a_b-c3/*", Item::kTokenType), {}}}},
+ nullptr},
+ {"token with capitals - list",
+ "fooBar",
+ 6,
+ {{{Item("fooBar", Item::kTokenType), {}}}},
+ nullptr},
+ {"token starting with capitals - list",
+ "FooBar",
+ 6,
+ {{{Item("FooBar", Item::kTokenType), {}}}},
+ nullptr},
+};
+
+const struct DictionaryTestCase {
+ const char* name;
+ const char* raw;
+ size_t raw_len;
+ const absl::optional<Dictionary>
+ expected; // nullopt if parse error is expected.
+ const char* canonical; // nullptr if parse error is expected, or if canonical
+ // format is identical to raw.
+} dictionary_test_cases[] = {
+ // dictionary.json
+ {"basic dictionary",
+ "en=\"Applepie\", da=:w4ZibGV0w6ZydGUK:",
+ 36,
+ {Dictionary{
+ {{"en", {Item("Applepie"), {}}},
+ {"da",
+ {Item("\303\206blet\303\246rte\n", Item::kByteSequenceType), {}}}}}},
+ nullptr},
+ {"empty dictionary", "", 0, {Dictionary{{}}}, nullptr},
+ {"single item dictionary",
+ "a=1",
+ 3,
+ {Dictionary{{{"a", {Integer(1), {}}}}}},
+ nullptr},
+ {"list item dictionary",
+ "a=(1 2)",
+ 7,
+ {Dictionary{{{"a", {{{Integer(1), {}}, {Integer(2), {}}}, {}}}}}},
+ nullptr},
+ {"single list item dictionary",
+ "a=(1)",
+ 5,
+ {Dictionary{{{"a", {{{Integer(1), {}}}, {}}}}}},
+ nullptr},
+ {"empty list item dictionary",
+ "a=()",
+ 4,
+ {Dictionary{{{"a", {std::vector<ParameterizedItem>(), {}}}}}},
+ nullptr},
+ {"no whitespace dictionary",
+ "a=1,b=2",
+ 7,
+ {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Integer(2), {}}}}}},
+ "a=1, b=2"},
+ {"extra whitespace dictionary",
+ "a=1 , b=2",
+ 10,
+ {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Integer(2), {}}}}}},
+ "a=1, b=2"},
+ {"tab separated dictionary",
+ "a=1\t,\tb=2",
+ 9,
+ {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Integer(2), {}}}}}},
+ "a=1, b=2"},
+ {"leading whitespace dictionary",
+ " a=1 , b=2",
+ 15,
+ {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Integer(2), {}}}}}},
+ "a=1, b=2"},
+ {"whitespace before = dictionary", "a =1, b=2", 9, absl::nullopt, nullptr},
+ {"whitespace after = dictionary", "a=1, b= 2", 9, absl::nullopt, nullptr},
+ {"two lines dictionary",
+ "a=1, b=2",
+ 8,
+ {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Integer(2), {}}}}}},
+ "a=1, b=2"},
+ {"missing value dictionary",
+ "a=1, b, c=3",
+ 11,
+ {Dictionary{{{"a", {Integer(1), {}}},
+ {"b", {Item(true), {}}},
+ {"c", {Integer(3), {}}}}}},
+ nullptr},
+ {"all missing value dictionary",
+ "a, b, c",
+ 7,
+ {Dictionary{{{"a", {Item(true), {}}},
+ {"b", {Item(true), {}}},
+ {"c", {Item(true), {}}}}}},
+ nullptr},
+ {"start missing value dictionary",
+ "a, b=2",
+ 6,
+ {Dictionary{{{"a", {Item(true), {}}}, {"b", {Integer(2), {}}}}}},
+ nullptr},
+ {"end missing value dictionary",
+ "a=1, b",
+ 6,
+ {Dictionary{{{"a", {Integer(1), {}}}, {"b", {Item(true), {}}}}}},
+ nullptr},
+ {"missing value with params dictionary",
+ "a=1, b;foo=9, c=3",
+ 17,
+ {Dictionary{{{"a", {Integer(1), {}}},
+ {"b", {Item(true), {Param("foo", 9)}}},
+ {"c", {Integer(3), {}}}}}},
+ nullptr},
+ {"explicit true value with params dictionary",
+ "a=1, b=?1;foo=9, c=3",
+ 20,
+ {Dictionary{{{"a", {Integer(1), {}}},
+ {"b", {Item(true), {Param("foo", 9)}}},
+ {"c", {Integer(3), {}}}}}},
+ "a=1, b;foo=9, c=3"},
+ {"trailing comma dictionary", "a=1, b=2,", 9, absl::nullopt, nullptr},
+ {"empty item dictionary", "a=1,,b=2,", 9, absl::nullopt, nullptr},
+ {"duplicate key dictionary",
+ "a=1,b=2,a=3",
+ 11,
+ {Dictionary{{{"a", {Integer(3), {}}}, {"b", {Integer(2), {}}}}}},
+ "a=3, b=2"},
+ {"numeric key dictionary", "a=1,1b=2,a=1", 12, absl::nullopt, nullptr},
+ {"uppercase key dictionary", "a=1,B=2,a=1", 11, absl::nullopt, nullptr},
+ {"bad key dictionary", "a=1,b!=2,a=1", 12, absl::nullopt, nullptr},
+ // examples.json
+ {"Example-DictHeader",
+ "en=\"Applepie\", da=:w4ZibGV0w6ZydGU=:",
+ 36,
+ {Dictionary{
+ {{"en", {Item("Applepie"), {}}},
+ {"da",
+ {Item("\303\206blet\303\246rte", Item::kByteSequenceType), {}}}}}},
+ nullptr},
+ {"Example-DictHeader (boolean values)",
+ "a=?0, b, c; foo=bar",
+ 19,
+ {Dictionary{{{"a", {Item(false), {}}},
+ {"b", {Item(true), {}}},
+ {"c", {Item(true), {TokenParam("foo", "bar")}}}}}},
+ "a=?0, b, c;foo=bar"},
+ {"Example-DictListHeader",
+ "rating=1.5, feelings=(joy sadness)",
+ 34,
+ {Dictionary{{{"rating", {Item(1.500000), {}}},
+ {"feelings",
+ {{{Item("joy", Item::kTokenType), {}},
+ {Item("sadness", Item::kTokenType), {}}},
+ {}}}}}},
+ nullptr},
+ {"Example-MixDict",
+ "a=(1 2), b=3, c=4;aa=bb, d=(5 6);valid",
+ 38,
+ {Dictionary{{{"a", {{{Integer(1), {}}, {Integer(2), {}}}, {}}},
+ {"b", {Integer(3), {}}},
+ {"c", {Integer(4), {TokenParam("aa", "bb")}}},
+ {"d",
+ {{{Integer(5), {}}, {Integer(6), {}}},
+ {BooleanParam("valid", true)}}}}}},
+ "a=(1 2), b=3, c=4;aa=bb, d=(5 6);valid"},
+ {"Example-Hdr (dictionary on one line)",
+ "foo=1, bar=2",
+ 12,
+ {Dictionary{{{"foo", {Integer(1), {}}}, {"bar", {Integer(2), {}}}}}},
+ nullptr},
+ {"Example-Hdr (dictionary on two lines)",
+ "foo=1, bar=2",
+ 12,
+ {Dictionary{{{"foo", {Integer(1), {}}}, {"bar", {Integer(2), {}}}}}},
+ "foo=1, bar=2"},
+ // key-generated.json
+ {"0x00 as a single-character dictionary key", "\000=1", 3, absl::nullopt,
+ nullptr},
+ {"0x01 as a single-character dictionary key", "\001=1", 3, absl::nullopt,
+ nullptr},
+ {"0x02 as a single-character dictionary key", "\002=1", 3, absl::nullopt,
+ nullptr},
+ {"0x03 as a single-character dictionary key", "\003=1", 3, absl::nullopt,
+ nullptr},
+ {"0x04 as a single-character dictionary key", "\004=1", 3, absl::nullopt,
+ nullptr},
+ {"0x05 as a single-character dictionary key", "\005=1", 3, absl::nullopt,
+ nullptr},
+ {"0x06 as a single-character dictionary key", "\006=1", 3, absl::nullopt,
+ nullptr},
+ {"0x07 as a single-character dictionary key", "\a=1", 3, absl::nullopt,
+ nullptr},
+ {"0x08 as a single-character dictionary key", "\b=1", 3, absl::nullopt,
+ nullptr},
+ {"0x09 as a single-character dictionary key", "\t=1", 3, absl::nullopt,
+ nullptr},
+ {"0x0a as a single-character dictionary key", "\n=1", 3, absl::nullopt,
+ nullptr},
+ {"0x0b as a single-character dictionary key", "\v=1", 3, absl::nullopt,
+ nullptr},
+ {"0x0c as a single-character dictionary key", "\f=1", 3, absl::nullopt,
+ nullptr},
+ {"0x0d as a single-character dictionary key", "\r=1", 3, absl::nullopt,
+ nullptr},
+ {"0x0e as a single-character dictionary key", "\016=1", 3, absl::nullopt,
+ nullptr},
+ {"0x0f as a single-character dictionary key", "\017=1", 3, absl::nullopt,
+ nullptr},
+ {"0x10 as a single-character dictionary key", "\020=1", 3, absl::nullopt,
+ nullptr},
+ {"0x11 as a single-character dictionary key", "\021=1", 3, absl::nullopt,
+ nullptr},
+ {"0x12 as a single-character dictionary key", "\022=1", 3, absl::nullopt,
+ nullptr},
+ {"0x13 as a single-character dictionary key", "\023=1", 3, absl::nullopt,
+ nullptr},
+ {"0x14 as a single-character dictionary key", "\024=1", 3, absl::nullopt,
+ nullptr},
+ {"0x15 as a single-character dictionary key", "\025=1", 3, absl::nullopt,
+ nullptr},
+ {"0x16 as a single-character dictionary key", "\026=1", 3, absl::nullopt,
+ nullptr},
+ {"0x17 as a single-character dictionary key", "\027=1", 3, absl::nullopt,
+ nullptr},
+ {"0x18 as a single-character dictionary key", "\030=1", 3, absl::nullopt,
+ nullptr},
+ {"0x19 as a single-character dictionary key", "\031=1", 3, absl::nullopt,
+ nullptr},
+ {"0x1a as a single-character dictionary key", "\032=1", 3, absl::nullopt,
+ nullptr},
+ {"0x1b as a single-character dictionary key", "\033=1", 3, absl::nullopt,
+ nullptr},
+ {"0x1c as a single-character dictionary key", "\034=1", 3, absl::nullopt,
+ nullptr},
+ {"0x1d as a single-character dictionary key", "\035=1", 3, absl::nullopt,
+ nullptr},
+ {"0x1e as a single-character dictionary key", "\036=1", 3, absl::nullopt,
+ nullptr},
+ {"0x1f as a single-character dictionary key", "\037=1", 3, absl::nullopt,
+ nullptr},
+ {"0x20 as a single-character dictionary key", "=1", 2, absl::nullopt,
+ nullptr},
+ {"0x21 as a single-character dictionary key", "!=1", 3, absl::nullopt,
+ nullptr},
+ {"0x22 as a single-character dictionary key", "\"=1", 3, absl::nullopt,
+ nullptr},
+ {"0x23 as a single-character dictionary key", "#=1", 3, absl::nullopt,
+ nullptr},
+ {"0x24 as a single-character dictionary key", "$=1", 3, absl::nullopt,
+ nullptr},
+ {"0x25 as a single-character dictionary key", "%=1", 3, absl::nullopt,
+ nullptr},
+ {"0x26 as a single-character dictionary key", "&=1", 3, absl::nullopt,
+ nullptr},
+ {"0x27 as a single-character dictionary key", "'=1", 3, absl::nullopt,
+ nullptr},
+ {"0x28 as a single-character dictionary key", "(=1", 3, absl::nullopt,
+ nullptr},
+ {"0x29 as a single-character dictionary key", ")=1", 3, absl::nullopt,
+ nullptr},
+ {"0x2a as a single-character dictionary key",
+ "*=1",
+ 3,
+ {Dictionary{{{"*", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x2b as a single-character dictionary key", "+=1", 3, absl::nullopt,
+ nullptr},
+ {"0x2c as a single-character dictionary key", ",=1", 3, absl::nullopt,
+ nullptr},
+ {"0x2d as a single-character dictionary key", "-=1", 3, absl::nullopt,
+ nullptr},
+ {"0x2e as a single-character dictionary key", ".=1", 3, absl::nullopt,
+ nullptr},
+ {"0x2f as a single-character dictionary key", "/=1", 3, absl::nullopt,
+ nullptr},
+ {"0x30 as a single-character dictionary key", "0=1", 3, absl::nullopt,
+ nullptr},
+ {"0x31 as a single-character dictionary key", "1=1", 3, absl::nullopt,
+ nullptr},
+ {"0x32 as a single-character dictionary key", "2=1", 3, absl::nullopt,
+ nullptr},
+ {"0x33 as a single-character dictionary key", "3=1", 3, absl::nullopt,
+ nullptr},
+ {"0x34 as a single-character dictionary key", "4=1", 3, absl::nullopt,
+ nullptr},
+ {"0x35 as a single-character dictionary key", "5=1", 3, absl::nullopt,
+ nullptr},
+ {"0x36 as a single-character dictionary key", "6=1", 3, absl::nullopt,
+ nullptr},
+ {"0x37 as a single-character dictionary key", "7=1", 3, absl::nullopt,
+ nullptr},
+ {"0x38 as a single-character dictionary key", "8=1", 3, absl::nullopt,
+ nullptr},
+ {"0x39 as a single-character dictionary key", "9=1", 3, absl::nullopt,
+ nullptr},
+ {"0x3a as a single-character dictionary key", ":=1", 3, absl::nullopt,
+ nullptr},
+ {"0x3b as a single-character dictionary key", ";=1", 3, absl::nullopt,
+ nullptr},
+ {"0x3c as a single-character dictionary key", "<=1", 3, absl::nullopt,
+ nullptr},
+ {"0x3d as a single-character dictionary key", "==1", 3, absl::nullopt,
+ nullptr},
+ {"0x3e as a single-character dictionary key", ">=1", 3, absl::nullopt,
+ nullptr},
+ {"0x3f as a single-character dictionary key", "?=1", 3, absl::nullopt,
+ nullptr},
+ {"0x40 as a single-character dictionary key", "@=1", 3, absl::nullopt,
+ nullptr},
+ {"0x41 as a single-character dictionary key", "A=1", 3, absl::nullopt,
+ nullptr},
+ {"0x42 as a single-character dictionary key", "B=1", 3, absl::nullopt,
+ nullptr},
+ {"0x43 as a single-character dictionary key", "C=1", 3, absl::nullopt,
+ nullptr},
+ {"0x44 as a single-character dictionary key", "D=1", 3, absl::nullopt,
+ nullptr},
+ {"0x45 as a single-character dictionary key", "E=1", 3, absl::nullopt,
+ nullptr},
+ {"0x46 as a single-character dictionary key", "F=1", 3, absl::nullopt,
+ nullptr},
+ {"0x47 as a single-character dictionary key", "G=1", 3, absl::nullopt,
+ nullptr},
+ {"0x48 as a single-character dictionary key", "H=1", 3, absl::nullopt,
+ nullptr},
+ {"0x49 as a single-character dictionary key", "I=1", 3, absl::nullopt,
+ nullptr},
+ {"0x4a as a single-character dictionary key", "J=1", 3, absl::nullopt,
+ nullptr},
+ {"0x4b as a single-character dictionary key", "K=1", 3, absl::nullopt,
+ nullptr},
+ {"0x4c as a single-character dictionary key", "L=1", 3, absl::nullopt,
+ nullptr},
+ {"0x4d as a single-character dictionary key", "M=1", 3, absl::nullopt,
+ nullptr},
+ {"0x4e as a single-character dictionary key", "N=1", 3, absl::nullopt,
+ nullptr},
+ {"0x4f as a single-character dictionary key", "O=1", 3, absl::nullopt,
+ nullptr},
+ {"0x50 as a single-character dictionary key", "P=1", 3, absl::nullopt,
+ nullptr},
+ {"0x51 as a single-character dictionary key", "Q=1", 3, absl::nullopt,
+ nullptr},
+ {"0x52 as a single-character dictionary key", "R=1", 3, absl::nullopt,
+ nullptr},
+ {"0x53 as a single-character dictionary key", "S=1", 3, absl::nullopt,
+ nullptr},
+ {"0x54 as a single-character dictionary key", "T=1", 3, absl::nullopt,
+ nullptr},
+ {"0x55 as a single-character dictionary key", "U=1", 3, absl::nullopt,
+ nullptr},
+ {"0x56 as a single-character dictionary key", "V=1", 3, absl::nullopt,
+ nullptr},
+ {"0x57 as a single-character dictionary key", "W=1", 3, absl::nullopt,
+ nullptr},
+ {"0x58 as a single-character dictionary key", "X=1", 3, absl::nullopt,
+ nullptr},
+ {"0x59 as a single-character dictionary key", "Y=1", 3, absl::nullopt,
+ nullptr},
+ {"0x5a as a single-character dictionary key", "Z=1", 3, absl::nullopt,
+ nullptr},
+ {"0x5b as a single-character dictionary key", "[=1", 3, absl::nullopt,
+ nullptr},
+ {"0x5c as a single-character dictionary key", "\\=1", 3, absl::nullopt,
+ nullptr},
+ {"0x5d as a single-character dictionary key", "]=1", 3, absl::nullopt,
+ nullptr},
+ {"0x5e as a single-character dictionary key", "^=1", 3, absl::nullopt,
+ nullptr},
+ {"0x5f as a single-character dictionary key", "_=1", 3, absl::nullopt,
+ nullptr},
+ {"0x60 as a single-character dictionary key", "`=1", 3, absl::nullopt,
+ nullptr},
+ {"0x61 as a single-character dictionary key",
+ "a=1",
+ 3,
+ {Dictionary{{{"a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x62 as a single-character dictionary key",
+ "b=1",
+ 3,
+ {Dictionary{{{"b", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x63 as a single-character dictionary key",
+ "c=1",
+ 3,
+ {Dictionary{{{"c", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x64 as a single-character dictionary key",
+ "d=1",
+ 3,
+ {Dictionary{{{"d", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x65 as a single-character dictionary key",
+ "e=1",
+ 3,
+ {Dictionary{{{"e", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x66 as a single-character dictionary key",
+ "f=1",
+ 3,
+ {Dictionary{{{"f", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x67 as a single-character dictionary key",
+ "g=1",
+ 3,
+ {Dictionary{{{"g", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x68 as a single-character dictionary key",
+ "h=1",
+ 3,
+ {Dictionary{{{"h", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x69 as a single-character dictionary key",
+ "i=1",
+ 3,
+ {Dictionary{{{"i", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6a as a single-character dictionary key",
+ "j=1",
+ 3,
+ {Dictionary{{{"j", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6b as a single-character dictionary key",
+ "k=1",
+ 3,
+ {Dictionary{{{"k", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6c as a single-character dictionary key",
+ "l=1",
+ 3,
+ {Dictionary{{{"l", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6d as a single-character dictionary key",
+ "m=1",
+ 3,
+ {Dictionary{{{"m", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6e as a single-character dictionary key",
+ "n=1",
+ 3,
+ {Dictionary{{{"n", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6f as a single-character dictionary key",
+ "o=1",
+ 3,
+ {Dictionary{{{"o", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x70 as a single-character dictionary key",
+ "p=1",
+ 3,
+ {Dictionary{{{"p", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x71 as a single-character dictionary key",
+ "q=1",
+ 3,
+ {Dictionary{{{"q", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x72 as a single-character dictionary key",
+ "r=1",
+ 3,
+ {Dictionary{{{"r", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x73 as a single-character dictionary key",
+ "s=1",
+ 3,
+ {Dictionary{{{"s", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x74 as a single-character dictionary key",
+ "t=1",
+ 3,
+ {Dictionary{{{"t", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x75 as a single-character dictionary key",
+ "u=1",
+ 3,
+ {Dictionary{{{"u", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x76 as a single-character dictionary key",
+ "v=1",
+ 3,
+ {Dictionary{{{"v", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x77 as a single-character dictionary key",
+ "w=1",
+ 3,
+ {Dictionary{{{"w", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x78 as a single-character dictionary key",
+ "x=1",
+ 3,
+ {Dictionary{{{"x", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x79 as a single-character dictionary key",
+ "y=1",
+ 3,
+ {Dictionary{{{"y", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x7a as a single-character dictionary key",
+ "z=1",
+ 3,
+ {Dictionary{{{"z", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x7b as a single-character dictionary key", "{=1", 3, absl::nullopt,
+ nullptr},
+ {"0x7c as a single-character dictionary key", "|=1", 3, absl::nullopt,
+ nullptr},
+ {"0x7d as a single-character dictionary key", "}=1", 3, absl::nullopt,
+ nullptr},
+ {"0x7e as a single-character dictionary key", "~=1", 3, absl::nullopt,
+ nullptr},
+ {"0x7f as a single-character dictionary key", "\177=1", 3, absl::nullopt,
+ nullptr},
+ {"0x00 in dictionary key", "a\000a=1", 5, absl::nullopt, nullptr},
+ {"0x01 in dictionary key", "a\001a=1", 5, absl::nullopt, nullptr},
+ {"0x02 in dictionary key", "a\002a=1", 5, absl::nullopt, nullptr},
+ {"0x03 in dictionary key", "a\003a=1", 5, absl::nullopt, nullptr},
+ {"0x04 in dictionary key", "a\004a=1", 5, absl::nullopt, nullptr},
+ {"0x05 in dictionary key", "a\005a=1", 5, absl::nullopt, nullptr},
+ {"0x06 in dictionary key", "a\006a=1", 5, absl::nullopt, nullptr},
+ {"0x07 in dictionary key", "a\aa=1", 5, absl::nullopt, nullptr},
+ {"0x08 in dictionary key", "a\ba=1", 5, absl::nullopt, nullptr},
+ {"0x09 in dictionary key", "a\ta=1", 5, absl::nullopt, nullptr},
+ {"0x0a in dictionary key", "a\na=1", 5, absl::nullopt, nullptr},
+ {"0x0b in dictionary key", "a\va=1", 5, absl::nullopt, nullptr},
+ {"0x0c in dictionary key", "a\fa=1", 5, absl::nullopt, nullptr},
+ {"0x0d in dictionary key", "a\ra=1", 5, absl::nullopt, nullptr},
+ {"0x0e in dictionary key", "a\016a=1", 5, absl::nullopt, nullptr},
+ {"0x0f in dictionary key", "a\017a=1", 5, absl::nullopt, nullptr},
+ {"0x10 in dictionary key", "a\020a=1", 5, absl::nullopt, nullptr},
+ {"0x11 in dictionary key", "a\021a=1", 5, absl::nullopt, nullptr},
+ {"0x12 in dictionary key", "a\022a=1", 5, absl::nullopt, nullptr},
+ {"0x13 in dictionary key", "a\023a=1", 5, absl::nullopt, nullptr},
+ {"0x14 in dictionary key", "a\024a=1", 5, absl::nullopt, nullptr},
+ {"0x15 in dictionary key", "a\025a=1", 5, absl::nullopt, nullptr},
+ {"0x16 in dictionary key", "a\026a=1", 5, absl::nullopt, nullptr},
+ {"0x17 in dictionary key", "a\027a=1", 5, absl::nullopt, nullptr},
+ {"0x18 in dictionary key", "a\030a=1", 5, absl::nullopt, nullptr},
+ {"0x19 in dictionary key", "a\031a=1", 5, absl::nullopt, nullptr},
+ {"0x1a in dictionary key", "a\032a=1", 5, absl::nullopt, nullptr},
+ {"0x1b in dictionary key", "a\033a=1", 5, absl::nullopt, nullptr},
+ {"0x1c in dictionary key", "a\034a=1", 5, absl::nullopt, nullptr},
+ {"0x1d in dictionary key", "a\035a=1", 5, absl::nullopt, nullptr},
+ {"0x1e in dictionary key", "a\036a=1", 5, absl::nullopt, nullptr},
+ {"0x1f in dictionary key", "a\037a=1", 5, absl::nullopt, nullptr},
+ {"0x20 in dictionary key", "a a=1", 5, absl::nullopt, nullptr},
+ {"0x21 in dictionary key", "a!a=1", 5, absl::nullopt, nullptr},
+ {"0x22 in dictionary key", "a\"a=1", 5, absl::nullopt, nullptr},
+ {"0x23 in dictionary key", "a#a=1", 5, absl::nullopt, nullptr},
+ {"0x24 in dictionary key", "a$a=1", 5, absl::nullopt, nullptr},
+ {"0x25 in dictionary key", "a%a=1", 5, absl::nullopt, nullptr},
+ {"0x26 in dictionary key", "a&a=1", 5, absl::nullopt, nullptr},
+ {"0x27 in dictionary key", "a'a=1", 5, absl::nullopt, nullptr},
+ {"0x28 in dictionary key", "a(a=1", 5, absl::nullopt, nullptr},
+ {"0x29 in dictionary key", "a)a=1", 5, absl::nullopt, nullptr},
+ {"0x2a in dictionary key",
+ "a*a=1",
+ 5,
+ {Dictionary{{{"a*a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x2b in dictionary key", "a+a=1", 5, absl::nullopt, nullptr},
+ {"0x2c in dictionary key",
+ "a,a=1",
+ 5,
+ {Dictionary{{{"a", {Integer(1), {}}}}}},
+ "a=1"},
+ {"0x2d in dictionary key",
+ "a-a=1",
+ 5,
+ {Dictionary{{{"a-a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x2e in dictionary key",
+ "a.a=1",
+ 5,
+ {Dictionary{{{"a.a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x2f in dictionary key", "a/a=1", 5, absl::nullopt, nullptr},
+ {"0x30 in dictionary key",
+ "a0a=1",
+ 5,
+ {Dictionary{{{"a0a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x31 in dictionary key",
+ "a1a=1",
+ 5,
+ {Dictionary{{{"a1a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x32 in dictionary key",
+ "a2a=1",
+ 5,
+ {Dictionary{{{"a2a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x33 in dictionary key",
+ "a3a=1",
+ 5,
+ {Dictionary{{{"a3a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x34 in dictionary key",
+ "a4a=1",
+ 5,
+ {Dictionary{{{"a4a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x35 in dictionary key",
+ "a5a=1",
+ 5,
+ {Dictionary{{{"a5a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x36 in dictionary key",
+ "a6a=1",
+ 5,
+ {Dictionary{{{"a6a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x37 in dictionary key",
+ "a7a=1",
+ 5,
+ {Dictionary{{{"a7a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x38 in dictionary key",
+ "a8a=1",
+ 5,
+ {Dictionary{{{"a8a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x39 in dictionary key",
+ "a9a=1",
+ 5,
+ {Dictionary{{{"a9a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x3a in dictionary key", "a:a=1", 5, absl::nullopt, nullptr},
+ {"0x3b in dictionary key",
+ "a;a=1",
+ 5,
+ {Dictionary{{{"a", {Item(true), {Param("a", 1)}}}}}},
+ nullptr},
+ {"0x3c in dictionary key", "a<a=1", 5, absl::nullopt, nullptr},
+ {"0x3d in dictionary key", "a=a=1", 5, absl::nullopt, nullptr},
+ {"0x3e in dictionary key", "a>a=1", 5, absl::nullopt, nullptr},
+ {"0x3f in dictionary key", "a?a=1", 5, absl::nullopt, nullptr},
+ {"0x40 in dictionary key", "a@a=1", 5, absl::nullopt, nullptr},
+ {"0x41 in dictionary key", "aAa=1", 5, absl::nullopt, nullptr},
+ {"0x42 in dictionary key", "aBa=1", 5, absl::nullopt, nullptr},
+ {"0x43 in dictionary key", "aCa=1", 5, absl::nullopt, nullptr},
+ {"0x44 in dictionary key", "aDa=1", 5, absl::nullopt, nullptr},
+ {"0x45 in dictionary key", "aEa=1", 5, absl::nullopt, nullptr},
+ {"0x46 in dictionary key", "aFa=1", 5, absl::nullopt, nullptr},
+ {"0x47 in dictionary key", "aGa=1", 5, absl::nullopt, nullptr},
+ {"0x48 in dictionary key", "aHa=1", 5, absl::nullopt, nullptr},
+ {"0x49 in dictionary key", "aIa=1", 5, absl::nullopt, nullptr},
+ {"0x4a in dictionary key", "aJa=1", 5, absl::nullopt, nullptr},
+ {"0x4b in dictionary key", "aKa=1", 5, absl::nullopt, nullptr},
+ {"0x4c in dictionary key", "aLa=1", 5, absl::nullopt, nullptr},
+ {"0x4d in dictionary key", "aMa=1", 5, absl::nullopt, nullptr},
+ {"0x4e in dictionary key", "aNa=1", 5, absl::nullopt, nullptr},
+ {"0x4f in dictionary key", "aOa=1", 5, absl::nullopt, nullptr},
+ {"0x50 in dictionary key", "aPa=1", 5, absl::nullopt, nullptr},
+ {"0x51 in dictionary key", "aQa=1", 5, absl::nullopt, nullptr},
+ {"0x52 in dictionary key", "aRa=1", 5, absl::nullopt, nullptr},
+ {"0x53 in dictionary key", "aSa=1", 5, absl::nullopt, nullptr},
+ {"0x54 in dictionary key", "aTa=1", 5, absl::nullopt, nullptr},
+ {"0x55 in dictionary key", "aUa=1", 5, absl::nullopt, nullptr},
+ {"0x56 in dictionary key", "aVa=1", 5, absl::nullopt, nullptr},
+ {"0x57 in dictionary key", "aWa=1", 5, absl::nullopt, nullptr},
+ {"0x58 in dictionary key", "aXa=1", 5, absl::nullopt, nullptr},
+ {"0x59 in dictionary key", "aYa=1", 5, absl::nullopt, nullptr},
+ {"0x5a in dictionary key", "aZa=1", 5, absl::nullopt, nullptr},
+ {"0x5b in dictionary key", "a[a=1", 5, absl::nullopt, nullptr},
+ {"0x5c in dictionary key", "a\\a=1", 5, absl::nullopt, nullptr},
+ {"0x5d in dictionary key", "a]a=1", 5, absl::nullopt, nullptr},
+ {"0x5e in dictionary key", "a^a=1", 5, absl::nullopt, nullptr},
+ {"0x5f in dictionary key",
+ "a_a=1",
+ 5,
+ {Dictionary{{{"a_a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x60 in dictionary key", "a`a=1", 5, absl::nullopt, nullptr},
+ {"0x61 in dictionary key",
+ "aaa=1",
+ 5,
+ {Dictionary{{{"aaa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x62 in dictionary key",
+ "aba=1",
+ 5,
+ {Dictionary{{{"aba", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x63 in dictionary key",
+ "aca=1",
+ 5,
+ {Dictionary{{{"aca", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x64 in dictionary key",
+ "ada=1",
+ 5,
+ {Dictionary{{{"ada", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x65 in dictionary key",
+ "aea=1",
+ 5,
+ {Dictionary{{{"aea", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x66 in dictionary key",
+ "afa=1",
+ 5,
+ {Dictionary{{{"afa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x67 in dictionary key",
+ "aga=1",
+ 5,
+ {Dictionary{{{"aga", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x68 in dictionary key",
+ "aha=1",
+ 5,
+ {Dictionary{{{"aha", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x69 in dictionary key",
+ "aia=1",
+ 5,
+ {Dictionary{{{"aia", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6a in dictionary key",
+ "aja=1",
+ 5,
+ {Dictionary{{{"aja", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6b in dictionary key",
+ "aka=1",
+ 5,
+ {Dictionary{{{"aka", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6c in dictionary key",
+ "ala=1",
+ 5,
+ {Dictionary{{{"ala", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6d in dictionary key",
+ "ama=1",
+ 5,
+ {Dictionary{{{"ama", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6e in dictionary key",
+ "ana=1",
+ 5,
+ {Dictionary{{{"ana", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6f in dictionary key",
+ "aoa=1",
+ 5,
+ {Dictionary{{{"aoa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x70 in dictionary key",
+ "apa=1",
+ 5,
+ {Dictionary{{{"apa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x71 in dictionary key",
+ "aqa=1",
+ 5,
+ {Dictionary{{{"aqa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x72 in dictionary key",
+ "ara=1",
+ 5,
+ {Dictionary{{{"ara", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x73 in dictionary key",
+ "asa=1",
+ 5,
+ {Dictionary{{{"asa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x74 in dictionary key",
+ "ata=1",
+ 5,
+ {Dictionary{{{"ata", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x75 in dictionary key",
+ "aua=1",
+ 5,
+ {Dictionary{{{"aua", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x76 in dictionary key",
+ "ava=1",
+ 5,
+ {Dictionary{{{"ava", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x77 in dictionary key",
+ "awa=1",
+ 5,
+ {Dictionary{{{"awa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x78 in dictionary key",
+ "axa=1",
+ 5,
+ {Dictionary{{{"axa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x79 in dictionary key",
+ "aya=1",
+ 5,
+ {Dictionary{{{"aya", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x7a in dictionary key",
+ "aza=1",
+ 5,
+ {Dictionary{{{"aza", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x7b in dictionary key", "a{a=1", 5, absl::nullopt, nullptr},
+ {"0x7c in dictionary key", "a|a=1", 5, absl::nullopt, nullptr},
+ {"0x7d in dictionary key", "a}a=1", 5, absl::nullopt, nullptr},
+ {"0x7e in dictionary key", "a~a=1", 5, absl::nullopt, nullptr},
+ {"0x7f in dictionary key", "a\177a=1", 5, absl::nullopt, nullptr},
+ {"0x00 starting an dictionary key", "\000a=1", 4, absl::nullopt, nullptr},
+ {"0x01 starting an dictionary key", "\001a=1", 4, absl::nullopt, nullptr},
+ {"0x02 starting an dictionary key", "\002a=1", 4, absl::nullopt, nullptr},
+ {"0x03 starting an dictionary key", "\003a=1", 4, absl::nullopt, nullptr},
+ {"0x04 starting an dictionary key", "\004a=1", 4, absl::nullopt, nullptr},
+ {"0x05 starting an dictionary key", "\005a=1", 4, absl::nullopt, nullptr},
+ {"0x06 starting an dictionary key", "\006a=1", 4, absl::nullopt, nullptr},
+ {"0x07 starting an dictionary key", "\aa=1", 4, absl::nullopt, nullptr},
+ {"0x08 starting an dictionary key", "\ba=1", 4, absl::nullopt, nullptr},
+ {"0x09 starting an dictionary key", "\ta=1", 4, absl::nullopt, nullptr},
+ {"0x0a starting an dictionary key", "\na=1", 4, absl::nullopt, nullptr},
+ {"0x0b starting an dictionary key", "\va=1", 4, absl::nullopt, nullptr},
+ {"0x0c starting an dictionary key", "\fa=1", 4, absl::nullopt, nullptr},
+ {"0x0d starting an dictionary key", "\ra=1", 4, absl::nullopt, nullptr},
+ {"0x0e starting an dictionary key", "\016a=1", 4, absl::nullopt, nullptr},
+ {"0x0f starting an dictionary key", "\017a=1", 4, absl::nullopt, nullptr},
+ {"0x10 starting an dictionary key", "\020a=1", 4, absl::nullopt, nullptr},
+ {"0x11 starting an dictionary key", "\021a=1", 4, absl::nullopt, nullptr},
+ {"0x12 starting an dictionary key", "\022a=1", 4, absl::nullopt, nullptr},
+ {"0x13 starting an dictionary key", "\023a=1", 4, absl::nullopt, nullptr},
+ {"0x14 starting an dictionary key", "\024a=1", 4, absl::nullopt, nullptr},
+ {"0x15 starting an dictionary key", "\025a=1", 4, absl::nullopt, nullptr},
+ {"0x16 starting an dictionary key", "\026a=1", 4, absl::nullopt, nullptr},
+ {"0x17 starting an dictionary key", "\027a=1", 4, absl::nullopt, nullptr},
+ {"0x18 starting an dictionary key", "\030a=1", 4, absl::nullopt, nullptr},
+ {"0x19 starting an dictionary key", "\031a=1", 4, absl::nullopt, nullptr},
+ {"0x1a starting an dictionary key", "\032a=1", 4, absl::nullopt, nullptr},
+ {"0x1b starting an dictionary key", "\033a=1", 4, absl::nullopt, nullptr},
+ {"0x1c starting an dictionary key", "\034a=1", 4, absl::nullopt, nullptr},
+ {"0x1d starting an dictionary key", "\035a=1", 4, absl::nullopt, nullptr},
+ {"0x1e starting an dictionary key", "\036a=1", 4, absl::nullopt, nullptr},
+ {"0x1f starting an dictionary key", "\037a=1", 4, absl::nullopt, nullptr},
+ {"0x20 starting an dictionary key",
+ " a=1",
+ 4,
+ {Dictionary{{{"a", {Integer(1), {}}}}}},
+ "a=1"},
+ {"0x21 starting an dictionary key", "!a=1", 4, absl::nullopt, nullptr},
+ {"0x22 starting an dictionary key", "\"a=1", 4, absl::nullopt, nullptr},
+ {"0x23 starting an dictionary key", "#a=1", 4, absl::nullopt, nullptr},
+ {"0x24 starting an dictionary key", "$a=1", 4, absl::nullopt, nullptr},
+ {"0x25 starting an dictionary key", "%a=1", 4, absl::nullopt, nullptr},
+ {"0x26 starting an dictionary key", "&a=1", 4, absl::nullopt, nullptr},
+ {"0x27 starting an dictionary key", "'a=1", 4, absl::nullopt, nullptr},
+ {"0x28 starting an dictionary key", "(a=1", 4, absl::nullopt, nullptr},
+ {"0x29 starting an dictionary key", ")a=1", 4, absl::nullopt, nullptr},
+ {"0x2a starting an dictionary key",
+ "*a=1",
+ 4,
+ {Dictionary{{{"*a", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x2b starting an dictionary key", "+a=1", 4, absl::nullopt, nullptr},
+ {"0x2c starting an dictionary key", ",a=1", 4, absl::nullopt, nullptr},
+ {"0x2d starting an dictionary key", "-a=1", 4, absl::nullopt, nullptr},
+ {"0x2e starting an dictionary key", ".a=1", 4, absl::nullopt, nullptr},
+ {"0x2f starting an dictionary key", "/a=1", 4, absl::nullopt, nullptr},
+ {"0x30 starting an dictionary key", "0a=1", 4, absl::nullopt, nullptr},
+ {"0x31 starting an dictionary key", "1a=1", 4, absl::nullopt, nullptr},
+ {"0x32 starting an dictionary key", "2a=1", 4, absl::nullopt, nullptr},
+ {"0x33 starting an dictionary key", "3a=1", 4, absl::nullopt, nullptr},
+ {"0x34 starting an dictionary key", "4a=1", 4, absl::nullopt, nullptr},
+ {"0x35 starting an dictionary key", "5a=1", 4, absl::nullopt, nullptr},
+ {"0x36 starting an dictionary key", "6a=1", 4, absl::nullopt, nullptr},
+ {"0x37 starting an dictionary key", "7a=1", 4, absl::nullopt, nullptr},
+ {"0x38 starting an dictionary key", "8a=1", 4, absl::nullopt, nullptr},
+ {"0x39 starting an dictionary key", "9a=1", 4, absl::nullopt, nullptr},
+ {"0x3a starting an dictionary key", ":a=1", 4, absl::nullopt, nullptr},
+ {"0x3b starting an dictionary key", ";a=1", 4, absl::nullopt, nullptr},
+ {"0x3c starting an dictionary key", "<a=1", 4, absl::nullopt, nullptr},
+ {"0x3d starting an dictionary key", "=a=1", 4, absl::nullopt, nullptr},
+ {"0x3e starting an dictionary key", ">a=1", 4, absl::nullopt, nullptr},
+ {"0x3f starting an dictionary key", "?a=1", 4, absl::nullopt, nullptr},
+ {"0x40 starting an dictionary key", "@a=1", 4, absl::nullopt, nullptr},
+ {"0x41 starting an dictionary key", "Aa=1", 4, absl::nullopt, nullptr},
+ {"0x42 starting an dictionary key", "Ba=1", 4, absl::nullopt, nullptr},
+ {"0x43 starting an dictionary key", "Ca=1", 4, absl::nullopt, nullptr},
+ {"0x44 starting an dictionary key", "Da=1", 4, absl::nullopt, nullptr},
+ {"0x45 starting an dictionary key", "Ea=1", 4, absl::nullopt, nullptr},
+ {"0x46 starting an dictionary key", "Fa=1", 4, absl::nullopt, nullptr},
+ {"0x47 starting an dictionary key", "Ga=1", 4, absl::nullopt, nullptr},
+ {"0x48 starting an dictionary key", "Ha=1", 4, absl::nullopt, nullptr},
+ {"0x49 starting an dictionary key", "Ia=1", 4, absl::nullopt, nullptr},
+ {"0x4a starting an dictionary key", "Ja=1", 4, absl::nullopt, nullptr},
+ {"0x4b starting an dictionary key", "Ka=1", 4, absl::nullopt, nullptr},
+ {"0x4c starting an dictionary key", "La=1", 4, absl::nullopt, nullptr},
+ {"0x4d starting an dictionary key", "Ma=1", 4, absl::nullopt, nullptr},
+ {"0x4e starting an dictionary key", "Na=1", 4, absl::nullopt, nullptr},
+ {"0x4f starting an dictionary key", "Oa=1", 4, absl::nullopt, nullptr},
+ {"0x50 starting an dictionary key", "Pa=1", 4, absl::nullopt, nullptr},
+ {"0x51 starting an dictionary key", "Qa=1", 4, absl::nullopt, nullptr},
+ {"0x52 starting an dictionary key", "Ra=1", 4, absl::nullopt, nullptr},
+ {"0x53 starting an dictionary key", "Sa=1", 4, absl::nullopt, nullptr},
+ {"0x54 starting an dictionary key", "Ta=1", 4, absl::nullopt, nullptr},
+ {"0x55 starting an dictionary key", "Ua=1", 4, absl::nullopt, nullptr},
+ {"0x56 starting an dictionary key", "Va=1", 4, absl::nullopt, nullptr},
+ {"0x57 starting an dictionary key", "Wa=1", 4, absl::nullopt, nullptr},
+ {"0x58 starting an dictionary key", "Xa=1", 4, absl::nullopt, nullptr},
+ {"0x59 starting an dictionary key", "Ya=1", 4, absl::nullopt, nullptr},
+ {"0x5a starting an dictionary key", "Za=1", 4, absl::nullopt, nullptr},
+ {"0x5b starting an dictionary key", "[a=1", 4, absl::nullopt, nullptr},
+ {"0x5c starting an dictionary key", "\\a=1", 4, absl::nullopt, nullptr},
+ {"0x5d starting an dictionary key", "]a=1", 4, absl::nullopt, nullptr},
+ {"0x5e starting an dictionary key", "^a=1", 4, absl::nullopt, nullptr},
+ {"0x5f starting an dictionary key", "_a=1", 4, absl::nullopt, nullptr},
+ {"0x60 starting an dictionary key", "`a=1", 4, absl::nullopt, nullptr},
+ {"0x61 starting an dictionary key",
+ "aa=1",
+ 4,
+ {Dictionary{{{"aa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x62 starting an dictionary key",
+ "ba=1",
+ 4,
+ {Dictionary{{{"ba", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x63 starting an dictionary key",
+ "ca=1",
+ 4,
+ {Dictionary{{{"ca", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x64 starting an dictionary key",
+ "da=1",
+ 4,
+ {Dictionary{{{"da", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x65 starting an dictionary key",
+ "ea=1",
+ 4,
+ {Dictionary{{{"ea", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x66 starting an dictionary key",
+ "fa=1",
+ 4,
+ {Dictionary{{{"fa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x67 starting an dictionary key",
+ "ga=1",
+ 4,
+ {Dictionary{{{"ga", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x68 starting an dictionary key",
+ "ha=1",
+ 4,
+ {Dictionary{{{"ha", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x69 starting an dictionary key",
+ "ia=1",
+ 4,
+ {Dictionary{{{"ia", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6a starting an dictionary key",
+ "ja=1",
+ 4,
+ {Dictionary{{{"ja", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6b starting an dictionary key",
+ "ka=1",
+ 4,
+ {Dictionary{{{"ka", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6c starting an dictionary key",
+ "la=1",
+ 4,
+ {Dictionary{{{"la", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6d starting an dictionary key",
+ "ma=1",
+ 4,
+ {Dictionary{{{"ma", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6e starting an dictionary key",
+ "na=1",
+ 4,
+ {Dictionary{{{"na", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x6f starting an dictionary key",
+ "oa=1",
+ 4,
+ {Dictionary{{{"oa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x70 starting an dictionary key",
+ "pa=1",
+ 4,
+ {Dictionary{{{"pa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x71 starting an dictionary key",
+ "qa=1",
+ 4,
+ {Dictionary{{{"qa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x72 starting an dictionary key",
+ "ra=1",
+ 4,
+ {Dictionary{{{"ra", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x73 starting an dictionary key",
+ "sa=1",
+ 4,
+ {Dictionary{{{"sa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x74 starting an dictionary key",
+ "ta=1",
+ 4,
+ {Dictionary{{{"ta", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x75 starting an dictionary key",
+ "ua=1",
+ 4,
+ {Dictionary{{{"ua", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x76 starting an dictionary key",
+ "va=1",
+ 4,
+ {Dictionary{{{"va", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x77 starting an dictionary key",
+ "wa=1",
+ 4,
+ {Dictionary{{{"wa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x78 starting an dictionary key",
+ "xa=1",
+ 4,
+ {Dictionary{{{"xa", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x79 starting an dictionary key",
+ "ya=1",
+ 4,
+ {Dictionary{{{"ya", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x7a starting an dictionary key",
+ "za=1",
+ 4,
+ {Dictionary{{{"za", {Integer(1), {}}}}}},
+ nullptr},
+ {"0x7b starting an dictionary key", "{a=1", 4, absl::nullopt, nullptr},
+ {"0x7c starting an dictionary key", "|a=1", 4, absl::nullopt, nullptr},
+ {"0x7d starting an dictionary key", "}a=1", 4, absl::nullopt, nullptr},
+ {"0x7e starting an dictionary key", "~a=1", 4, absl::nullopt, nullptr},
+ {"0x7f starting an dictionary key", "\177a=1", 4, absl::nullopt, nullptr},
+ // param-dict.json
+ {"basic parameterised dict",
+ "abc=123;a=1;b=2, def=456, ghi=789;q=9;r=\"+w\"",
+ 44,
+ {Dictionary{{{"abc", {Integer(123), {Param("a", 1), Param("b", 2)}}},
+ {"def", {Integer(456), {}}},
+ {"ghi", {Integer(789), {Param("q", 9), Param("r", "+w")}}}}}},
+ nullptr},
+ {"single item parameterised dict",
+ "a=b; q=1.0",
+ 10,
+ {Dictionary{
+ {{"a", {Item("b", Item::kTokenType), {DoubleParam("q", 1.000000)}}}}}},
+ "a=b;q=1.0"},
+ {"list item parameterised dictionary",
+ "a=(1 2); q=1.0",
+ 14,
+ {Dictionary{{{"a",
+ {{{Integer(1), {}}, {Integer(2), {}}},
+ {DoubleParam("q", 1.000000)}}}}}},
+ "a=(1 2);q=1.0"},
+ {"missing parameter value parameterised dict",
+ "a=3;c;d=5",
+ 9,
+ {Dictionary{
+ {{"a", {Integer(3), {BooleanParam("c", true), Param("d", 5)}}}}}},
+ nullptr},
+ {"terminal missing parameter value parameterised dict",
+ "a=3;c=5;d",
+ 9,
+ {Dictionary{
+ {{"a", {Integer(3), {Param("c", 5), BooleanParam("d", true)}}}}}},
+ nullptr},
+ {"no whitespace parameterised dict",
+ "a=b;c=1,d=e;f=2",
+ 15,
+ {Dictionary{{{"a", {Item("b", Item::kTokenType), {Param("c", 1)}}},
+ {"d", {Item("e", Item::kTokenType), {Param("f", 2)}}}}}},
+ "a=b;c=1, d=e;f=2"},
+ {"whitespace before = parameterised dict", "a=b;q =0.5", 10, absl::nullopt,
+ nullptr},
+ {"whitespace after = parameterised dict", "a=b;q= 0.5", 10, absl::nullopt,
+ nullptr},
+ {"whitespace before ; parameterised dict", "a=b ;q=0.5", 10, absl::nullopt,
+ nullptr},
+ {"whitespace after ; parameterised dict",
+ "a=b; q=0.5",
+ 10,
+ {Dictionary{
+ {{"a", {Item("b", Item::kTokenType), {DoubleParam("q", 0.500000)}}}}}},
+ "a=b;q=0.5"},
+ {"extra whitespace parameterised dict",
+ "a=b; c=1 , d=e; f=2; g=3",
+ 27,
+ {Dictionary{
+ {{"a", {Item("b", Item::kTokenType), {Param("c", 1)}}},
+ {"d",
+ {Item("e", Item::kTokenType), {Param("f", 2), Param("g", 3)}}}}}},
+ "a=b;c=1, d=e;f=2;g=3"},
+ {"two lines parameterised list",
+ "a=b;c=1, d=e;f=2",
+ 16,
+ {Dictionary{{{"a", {Item("b", Item::kTokenType), {Param("c", 1)}}},
+ {"d", {Item("e", Item::kTokenType), {Param("f", 2)}}}}}},
+ "a=b;c=1, d=e;f=2"},
+ {"trailing comma parameterised list", "a=b; q=1.0,", 11, absl::nullopt,
+ nullptr},
+ {"empty item parameterised list", "a=b; q=1.0,,c=d", 15, absl::nullopt,
+ nullptr},
+};
+
+} // namespace
+
+TEST(StructuredHeaderGeneratedTest, ParseItem) {
+ for (const auto& c : parameterized_item_test_cases) {
+ if (c.raw) {
+ SCOPED_TRACE(c.name);
+ std::string raw{c.raw, c.raw_len};
+ absl::optional<ParameterizedItem> result = ParseItem(raw);
+ EXPECT_EQ(result, c.expected);
+ }
+ }
+}
+
+TEST(StructuredHeaderGeneratedTest, ParseList) {
+ for (const auto& c : list_test_cases) {
+ if (c.raw) {
+ SCOPED_TRACE(c.name);
+ std::string raw{c.raw, c.raw_len};
+ absl::optional<List> result = ParseList(raw);
+ EXPECT_EQ(result, c.expected);
+ }
+ }
+}
+
+TEST(StructuredHeaderGeneratedTest, ParseDictionary) {
+ for (const auto& c : dictionary_test_cases) {
+ if (c.raw) {
+ SCOPED_TRACE(c.name);
+ std::string raw{c.raw, c.raw_len};
+ absl::optional<Dictionary> result = ParseDictionary(raw);
+ EXPECT_EQ(result, c.expected);
+ }
+ }
+}
+
+TEST(StructuredHeaderGeneratedTest, SerializeItem) {
+ for (const auto& c : parameterized_item_test_cases) {
+ SCOPED_TRACE(c.name);
+ if (c.expected) {
+ absl::optional<std::string> result = SerializeItem(*c.expected);
+ if (c.raw || c.canonical) {
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(result.value(),
+ std::string(c.canonical ? c.canonical : c.raw));
+ } else {
+ EXPECT_FALSE(result.has_value());
+ }
+ }
+ }
+}
+
+TEST(StructuredHeaderGeneratedTest, SerializeList) {
+ for (const auto& c : list_test_cases) {
+ SCOPED_TRACE(c.name);
+ if (c.expected) {
+ absl::optional<std::string> result = SerializeList(*c.expected);
+ if (c.raw || c.canonical) {
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(result.value(),
+ std::string(c.canonical ? c.canonical : c.raw));
+ } else {
+ EXPECT_FALSE(result.has_value());
+ }
+ }
+ }
+}
+
+TEST(StructuredHeaderGeneratedTest, SerializeDictionary) {
+ for (const auto& c : dictionary_test_cases) {
+ SCOPED_TRACE(c.name);
+ if (c.expected) {
+ absl::optional<std::string> result = SerializeDictionary(*c.expected);
+ if (c.raw || c.canonical) {
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(result.value(),
+ std::string(c.canonical ? c.canonical : c.raw));
+ } else {
+ EXPECT_FALSE(result.has_value());
+ }
+ }
+ }
+}
+
+} // namespace structured_headers
+} // namespace quiche
diff --git a/common/structured_headers_test.cc b/common/structured_headers_test.cc
new file mode 100644
index 0000000..87dacd8
--- /dev/null
+++ b/common/structured_headers_test.cc
@@ -0,0 +1,762 @@
+// 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 "common/structured_headers.h"
+
+#include <math.h>
+
+#include <limits>
+#include <string>
+
+#include "common/platform/api/quiche_test.h"
+
+namespace quiche {
+namespace structured_headers {
+namespace {
+
+// Helpers to make test cases clearer
+
+Item Token(std::string value) { return Item(value, Item::kTokenType); }
+
+Item Integer(int64_t value) { return Item(value); }
+
+// Parameter with null value, only used in Structured Headers Draft 09
+std::pair<std::string, Item> NullParam(std::string key) {
+ return std::make_pair(key, Item());
+}
+
+std::pair<std::string, Item> BooleanParam(std::string key, bool value) {
+ return std::make_pair(key, Item(value));
+}
+
+std::pair<std::string, Item> DoubleParam(std::string key, double value) {
+ return std::make_pair(key, Item(value));
+}
+
+std::pair<std::string, Item> Param(std::string key, int64_t value) {
+ return std::make_pair(key, Item(value));
+}
+
+std::pair<std::string, Item> Param(std::string key, std::string value) {
+ return std::make_pair(key, Item(value));
+}
+
+std::pair<std::string, Item> ByteSequenceParam(std::string key,
+ std::string value) {
+ return std::make_pair(key, Item(value, Item::kByteSequenceType));
+}
+
+std::pair<std::string, Item> TokenParam(std::string key, std::string value) {
+ return std::make_pair(key, Token(value));
+}
+
+// Test cases taken from https://github.com/httpwg/structured-header-tests can
+// be found in structured_headers_generated_unittest.cc
+
+const struct ItemTestCase {
+ const char* name;
+ const char* raw;
+ const absl::optional<Item> expected; // nullopt if parse error is expected.
+ const char* canonical; // nullptr if parse error is expected, or if canonical
+ // format is identical to raw.
+} item_test_cases[] = {
+ // Token
+ {"bad token - item", "abc$@%!", absl::nullopt, nullptr},
+ {"leading whitespace", " foo", Token("foo"), "foo"},
+ {"trailing whitespace", "foo ", Token("foo"), "foo"},
+ {"leading asterisk", "*foo", Token("*foo"), nullptr},
+ // Number
+ {"long integer", "999999999999999", Integer(999999999999999L), nullptr},
+ {"long negative integer", "-999999999999999", Integer(-999999999999999L),
+ nullptr},
+ {"too long integer", "1000000000000000", absl::nullopt, nullptr},
+ {"negative too long integer", "-1000000000000000", absl::nullopt, nullptr},
+ {"integral decimal", "1.0", Item(1.0), nullptr},
+ // String
+ {"basic string", "\"foo\"", Item("foo"), nullptr},
+ {"non-ascii string", "\"f\xC3\xBC\xC3\xBC\"", absl::nullopt, nullptr},
+ // Additional tests
+ {"valid quoting containing \\n", "\"\\\\n\"", Item("\\n"), nullptr},
+ {"valid quoting containing \\t", "\"\\\\t\"", Item("\\t"), nullptr},
+ {"valid quoting containing \\x", "\"\\\\x61\"", Item("\\x61"), nullptr},
+ {"c-style hex escape in string", "\"\\x61\"", absl::nullopt, nullptr},
+ {"valid quoting containing \\u", "\"\\\\u0061\"", Item("\\u0061"), nullptr},
+ {"c-style unicode escape in string", "\"\\u0061\"", absl::nullopt, nullptr},
+};
+
+const ItemTestCase sh09_item_test_cases[] = {
+ // Integer
+ {"large integer", "9223372036854775807", Integer(9223372036854775807L),
+ nullptr},
+ {"large negative integer", "-9223372036854775807",
+ Integer(-9223372036854775807L), nullptr},
+ {"too large integer", "9223372036854775808", absl::nullopt, nullptr},
+ {"too large negative integer", "-9223372036854775808", absl::nullopt,
+ nullptr},
+ // Byte Sequence
+ {"basic binary", "*aGVsbG8=*", Item("hello", Item::kByteSequenceType),
+ nullptr},
+ {"empty binary", "**", Item("", Item::kByteSequenceType), nullptr},
+ {"bad paddding", "*aGVsbG8*", Item("hello", Item::kByteSequenceType),
+ "*aGVsbG8=*"},
+ {"bad end delimiter", "*aGVsbG8=", absl::nullopt, nullptr},
+ {"extra whitespace", "*aGVsb G8=*", absl::nullopt, nullptr},
+ {"extra chars", "*aGVsbG!8=*", absl::nullopt, nullptr},
+ {"suffix chars", "*aGVsbG8=!*", absl::nullopt, nullptr},
+ {"non-zero pad bits", "*iZ==*", Item("\x89", Item::kByteSequenceType),
+ "*iQ==*"},
+ {"non-ASCII binary", "*/+Ah*", Item("\xFF\xE0!", Item::kByteSequenceType),
+ nullptr},
+ {"base64url binary", "*_-Ah*", absl::nullopt, nullptr},
+ {"token with leading asterisk", "*foo", absl::nullopt, nullptr},
+};
+
+// For Structured Headers Draft 15
+const struct ParameterizedItemTestCase {
+ const char* name;
+ const char* raw;
+ const absl::optional<ParameterizedItem>
+ expected; // nullopt if parse error is expected.
+ const char* canonical; // nullptr if parse error is expected, or if canonical
+ // format is identical to raw.
+} parameterized_item_test_cases[] = {
+ {"single parameter item",
+ "text/html;q=1.0",
+ {{Token("text/html"), {DoubleParam("q", 1)}}},
+ nullptr},
+ {"missing parameter value item",
+ "text/html;a;q=1.0",
+ {{Token("text/html"), {BooleanParam("a", true), DoubleParam("q", 1)}}},
+ nullptr},
+ {"missing terminal parameter value item",
+ "text/html;q=1.0;a",
+ {{Token("text/html"), {DoubleParam("q", 1), BooleanParam("a", true)}}},
+ nullptr},
+ {"duplicate parameter keys with different value",
+ "text/html;a=1;b=2;a=3.0",
+ {{Token("text/html"), {DoubleParam("a", 3), Param("b", 2L)}}},
+ "text/html;a=3.0;b=2"},
+ {"multiple duplicate parameter keys at different position",
+ "text/html;c=1;a=2;b;b=3.0;a",
+ {{Token("text/html"),
+ {Param("c", 1L), BooleanParam("a", true), DoubleParam("b", 3)}}},
+ "text/html;c=1;a;b=3.0"},
+ {"duplicate parameter keys with missing value",
+ "text/html;a;a=1",
+ {{Token("text/html"), {Param("a", 1L)}}},
+ "text/html;a=1"},
+ {"whitespace before = parameterised item", "text/html, text/plain;q =0.5",
+ absl::nullopt, nullptr},
+ {"whitespace after = parameterised item", "text/html, text/plain;q= 0.5",
+ absl::nullopt, nullptr},
+ {"whitespace before ; parameterised item", "text/html, text/plain ;q=0.5",
+ absl::nullopt, nullptr},
+ {"whitespace after ; parameterised item",
+ "text/plain; q=0.5",
+ {{Token("text/plain"), {DoubleParam("q", 0.5)}}},
+ "text/plain;q=0.5"},
+ {"extra whitespace parameterised item",
+ "text/plain; q=0.5; charset=utf-8",
+ {{Token("text/plain"),
+ {DoubleParam("q", 0.5), TokenParam("charset", "utf-8")}}},
+ "text/plain;q=0.5;charset=utf-8"},
+};
+
+// For Structured Headers Draft 15
+const struct ListTestCase {
+ const char* name;
+ const char* raw;
+ const absl::optional<List> expected; // nullopt if parse error is expected.
+ const char* canonical; // nullptr if parse error is expected, or if canonical
+ // format is identical to raw.
+} list_test_cases[] = {
+ // Lists of lists
+ {"extra whitespace list of lists",
+ "(1 42)",
+ {{{{{Integer(1L), {}}, {Integer(42L), {}}}, {}}}},
+ "(1 42)"},
+ // Parameterized Lists
+ {"basic parameterised list",
+ "abc_123;a=1;b=2; cdef_456, ghi;q=\"9\";r=\"+w\"",
+ {{{Token("abc_123"),
+ {Param("a", 1), Param("b", 2), BooleanParam("cdef_456", true)}},
+ {Token("ghi"), {Param("q", "9"), Param("r", "+w")}}}},
+ "abc_123;a=1;b=2;cdef_456, ghi;q=\"9\";r=\"+w\""},
+ // Parameterized inner lists
+ {"parameterised basic list of lists",
+ "(1;a=1.0 2), (42 43)",
+ {{{{{Integer(1L), {DoubleParam("a", 1.0)}}, {Integer(2L), {}}}, {}},
+ {{{Integer(42L), {}}, {Integer(43L), {}}}, {}}}},
+ nullptr},
+ {"parameters on inner members",
+ "(1;a=1.0 2;b=c), (42;d=?0 43;e=:Zmdo:)",
+ {{{{{Integer(1L), {DoubleParam("a", 1.0)}},
+ {Integer(2L), {TokenParam("b", "c")}}},
+ {}},
+ {{{Integer(42L), {BooleanParam("d", false)}},
+ {Integer(43L), {ByteSequenceParam("e", "fgh")}}},
+ {}}}},
+ nullptr},
+ {"parameters on inner lists",
+ "(1 2);a=1.0, (42 43);b=?0",
+ {{{{{Integer(1L), {}}, {Integer(2L), {}}}, {DoubleParam("a", 1.0)}},
+ {{{Integer(42L), {}}, {Integer(43L), {}}}, {BooleanParam("b", false)}}}},
+ nullptr},
+ {"default true values for parameters on inner list members",
+ "(1;a 2), (42 43;b)",
+ {{{{{Integer(1L), {BooleanParam("a", true)}}, {Integer(2L), {}}}, {}},
+ {{{Integer(42L), {}}, {Integer(43L), {BooleanParam("b", true)}}}, {}}}},
+ nullptr},
+ {"default true values for parameters on inner lists",
+ "(1 2);a, (42 43);b",
+ {{{{{Integer(1L), {}}, {Integer(2L), {}}}, {BooleanParam("a", true)}},
+ {{{Integer(42L), {}}, {Integer(43L), {}}}, {BooleanParam("b", true)}}}},
+ nullptr},
+ {"extra whitespace before semicolon in parameters on inner list member",
+ "(a;b ;c b)", absl::nullopt, nullptr},
+ {"extra whitespace between parameters on inner list member",
+ "(a;b; c b)",
+ {{{{{Token("a"), {BooleanParam("b", true), BooleanParam("c", true)}},
+ {Token("b"), {}}},
+ {}}}},
+ "(a;b;c b)"},
+ {"extra whitespace before semicolon in parameters on inner list",
+ "(a b);c ;d, (e)", absl::nullopt, nullptr},
+ {"extra whitespace between parameters on inner list",
+ "(a b);c; d, (e)",
+ {{{{{Token("a"), {}}, {Token("b"), {}}},
+ {BooleanParam("c", true), BooleanParam("d", true)}},
+ {{{Token("e"), {}}}, {}}}},
+ "(a b);c;d, (e)"},
+};
+
+// For Structured Headers Draft 15
+const struct DictionaryTestCase {
+ const char* name;
+ const char* raw;
+ const absl::optional<Dictionary>
+ expected; // nullopt if parse error is expected.
+ const char* canonical; // nullptr if parse error is expected, or if canonical
+ // format is identical to raw.
+} dictionary_test_cases[] = {
+ {"basic dictionary",
+ "en=\"Applepie\", da=:aGVsbG8=:",
+ {Dictionary{{{"en", {Item("Applepie"), {}}},
+ {"da", {Item("hello", Item::kByteSequenceType), {}}}}}},
+ nullptr},
+ {"tab separated dictionary",
+ "a=1\t,\tb=2",
+ {Dictionary{{{"a", {Integer(1L), {}}}, {"b", {Integer(2L), {}}}}}},
+ "a=1, b=2"},
+ {"missing value with params dictionary",
+ "a=1, b;foo=9, c=3",
+ {Dictionary{{{"a", {Integer(1L), {}}},
+ {"b", {Item(true), {Param("foo", 9)}}},
+ {"c", {Integer(3L), {}}}}}},
+ nullptr},
+ // Parameterised dictionary tests
+ {"parameterised inner list member dict",
+ "a=(\"1\";b=1;c=?0 \"2\");d=\"e\"",
+ {Dictionary{{{"a",
+ {{{Item("1"), {Param("b", 1), BooleanParam("c", false)}},
+ {Item("2"), {}}},
+ {Param("d", "e")}}}}}},
+ nullptr},
+ {"explicit true value with parameter",
+ "a=?1;b=1",
+ {Dictionary{{{"a", {Item(true), {Param("b", 1)}}}}}},
+ "a;b=1"},
+ {"implicit true value with parameter",
+ "a;b=1",
+ {Dictionary{{{"a", {Item(true), {Param("b", 1)}}}}}},
+ nullptr},
+ {"implicit true value with implicitly-valued parameter",
+ "a;b",
+ {Dictionary{{{"a", {Item(true), {BooleanParam("b", true)}}}}}},
+ nullptr},
+};
+} // namespace
+
+TEST(StructuredHeaderTest, ParseBareItem) {
+ for (const auto& c : item_test_cases) {
+ SCOPED_TRACE(c.name);
+ absl::optional<Item> result = ParseBareItem(c.raw);
+ EXPECT_EQ(result, c.expected);
+ }
+}
+
+// For Structured Headers Draft 15, these tests include parameters on Items.
+TEST(StructuredHeaderTest, ParseItem) {
+ for (const auto& c : parameterized_item_test_cases) {
+ SCOPED_TRACE(c.name);
+ absl::optional<ParameterizedItem> result = ParseItem(c.raw);
+ EXPECT_EQ(result, c.expected);
+ }
+}
+
+// Structured Headers Draft 9 parsing rules are different than Draft 15, and
+// some strings which are considered invalid in SH15 should parse in SH09.
+// The SH09 Item parser is not directly exposed, but can be used indirectly by
+// calling the parser for SH09-specific lists.
+TEST(StructuredHeaderTest, ParseSH09Item) {
+ for (const auto& c : sh09_item_test_cases) {
+ SCOPED_TRACE(c.name);
+ absl::optional<ListOfLists> result = ParseListOfLists(c.raw);
+ if (c.expected.has_value()) {
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(result->size(), 1UL);
+ EXPECT_EQ((*result)[0].size(), 1UL);
+ EXPECT_EQ((*result)[0][0], c.expected);
+ } else {
+ EXPECT_FALSE(result.has_value());
+ }
+ }
+}
+
+// In Structured Headers Draft 9, floats can have more than three fractional
+// digits, and can be larger than 1e12. This behaviour is exposed in the parser
+// for SH09-specific lists, so test it through that interface.
+TEST(StructuredHeaderTest, SH09HighPrecisionFloats) {
+ // These values are exactly representable in binary floating point, so no
+ // accuracy issues are expected in this test.
+ absl::optional<ListOfLists> result =
+ ParseListOfLists("1.03125;-1.03125;12345678901234.5;-12345678901234.5");
+ ASSERT_TRUE(result.has_value());
+ EXPECT_EQ(*result,
+ (ListOfLists{{Item(1.03125), Item(-1.03125), Item(12345678901234.5),
+ Item(-12345678901234.5)}}));
+
+ result = ParseListOfLists("123456789012345.0");
+ EXPECT_FALSE(result.has_value());
+
+ result = ParseListOfLists("-123456789012345.0");
+ EXPECT_FALSE(result.has_value());
+}
+
+// For Structured Headers Draft 9
+TEST(StructuredHeaderTest, ParseListOfLists) {
+ static const struct TestCase {
+ const char* name;
+ const char* raw;
+ ListOfLists expected; // empty if parse error is expected
+ } cases[] = {
+ {"basic list of lists",
+ "1;2, 42;43",
+ {{Integer(1L), Integer(2L)}, {Integer(42L), Integer(43L)}}},
+ {"empty list of lists", "", {}},
+ {"single item list of lists", "42", {{Integer(42L)}}},
+ {"no whitespace list of lists", "1,42", {{Integer(1L)}, {Integer(42L)}}},
+ {"no inner whitespace list of lists",
+ "1;2, 42;43",
+ {{Integer(1L), Integer(2L)}, {Integer(42L), Integer(43L)}}},
+ {"extra whitespace list of lists",
+ "1 , 42",
+ {{Integer(1L)}, {Integer(42L)}}},
+ {"extra inner whitespace list of lists",
+ "1 ; 2,42 ; 43",
+ {{Integer(1L), Integer(2L)}, {Integer(42L), Integer(43L)}}},
+ {"trailing comma list of lists", "1;2, 42,", {}},
+ {"trailing semicolon list of lists", "1;2, 42;43;", {}},
+ {"leading comma list of lists", ",1;2, 42", {}},
+ {"leading semicolon list of lists", ";1;2, 42;43", {}},
+ {"empty item list of lists", "1,,42", {}},
+ {"empty inner item list of lists", "1;;2,42", {}},
+ };
+ for (const auto& c : cases) {
+ SCOPED_TRACE(c.name);
+ absl::optional<ListOfLists> result = ParseListOfLists(c.raw);
+ if (!c.expected.empty()) {
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(*result, c.expected);
+ } else {
+ EXPECT_FALSE(result.has_value());
+ }
+ }
+}
+
+// For Structured Headers Draft 9
+TEST(StructuredHeaderTest, ParseParameterisedList) {
+ static const struct TestCase {
+ const char* name;
+ const char* raw;
+ ParameterisedList expected; // empty if parse error is expected
+ } cases[] = {
+ {"basic param-list",
+ "abc_123;a=1;b=2; cdef_456, ghi;q=\"9\";r=\"w\"",
+ {
+ {Token("abc_123"),
+ {Param("a", 1), Param("b", 2), NullParam("cdef_456")}},
+ {Token("ghi"), {Param("q", "9"), Param("r", "w")}},
+ }},
+ {"empty param-list", "", {}},
+ {"single item param-list",
+ "text/html;q=1",
+ {{Token("text/html"), {Param("q", 1)}}}},
+ {"empty param-list", "", {}},
+ {"no whitespace param-list",
+ "text/html,text/plain;q=1",
+ {{Token("text/html"), {}}, {Token("text/plain"), {Param("q", 1)}}}},
+ {"whitespace before = param-list", "text/html, text/plain;q =1", {}},
+ {"whitespace after = param-list", "text/html, text/plain;q= 1", {}},
+ {"extra whitespace param-list",
+ "text/html , text/plain ; q=1",
+ {{Token("text/html"), {}}, {Token("text/plain"), {Param("q", 1)}}}},
+ {"duplicate key", "abc;a=1;b=2;a=1", {}},
+ {"numeric key", "abc;a=1;1b=2;c=1", {}},
+ {"uppercase key", "abc;a=1;B=2;c=1", {}},
+ {"bad key", "abc;a=1;b!=2;c=1", {}},
+ {"another bad key", "abc;a=1;b==2;c=1", {}},
+ {"empty key name", "abc;a=1;=2;c=1", {}},
+ {"empty parameter", "abc;a=1;;c=1", {}},
+ {"empty list item", "abc;a=1,,def;b=1", {}},
+ {"extra semicolon", "abc;a=1;b=1;", {}},
+ {"extra comma", "abc;a=1,def;b=1,", {}},
+ {"leading semicolon", ";abc;a=1", {}},
+ {"leading comma", ",abc;a=1", {}},
+ };
+ for (const auto& c : cases) {
+ SCOPED_TRACE(c.name);
+ absl::optional<ParameterisedList> result = ParseParameterisedList(c.raw);
+ if (c.expected.empty()) {
+ EXPECT_FALSE(result.has_value());
+ continue;
+ }
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(result->size(), c.expected.size());
+ if (result->size() == c.expected.size()) {
+ for (size_t i = 0; i < c.expected.size(); ++i) {
+ EXPECT_EQ((*result)[i], c.expected[i]);
+ }
+ }
+ }
+}
+
+// For Structured Headers Draft 15
+TEST(StructuredHeaderTest, ParseList) {
+ for (const auto& c : list_test_cases) {
+ SCOPED_TRACE(c.name);
+ absl::optional<List> result = ParseList(c.raw);
+ EXPECT_EQ(result, c.expected);
+ }
+}
+
+// For Structured Headers Draft 15
+TEST(StructuredHeaderTest, ParseDictionary) {
+ for (const auto& c : dictionary_test_cases) {
+ SCOPED_TRACE(c.name);
+ absl::optional<Dictionary> result = ParseDictionary(c.raw);
+ EXPECT_EQ(result, c.expected);
+ }
+}
+
+// Serializer tests are all exclusively for Structured Headers Draft 15
+
+TEST(StructuredHeaderTest, SerializeItem) {
+ for (const auto& c : item_test_cases) {
+ SCOPED_TRACE(c.name);
+ if (c.expected) {
+ absl::optional<std::string> result = SerializeItem(*c.expected);
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw));
+ }
+ }
+}
+
+TEST(StructuredHeaderTest, SerializeParameterizedItem) {
+ for (const auto& c : parameterized_item_test_cases) {
+ SCOPED_TRACE(c.name);
+ if (c.expected) {
+ absl::optional<std::string> result = SerializeItem(*c.expected);
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw));
+ }
+ }
+}
+
+TEST(StructuredHeaderTest, UnserializableItems) {
+ // Test that items with unknown type are not serialized.
+ EXPECT_FALSE(SerializeItem(Item()).has_value());
+}
+
+TEST(StructuredHeaderTest, UnserializableTokens) {
+ static const struct UnserializableString {
+ const char* name;
+ const char* value;
+ } bad_tokens[] = {
+ {"empty token", ""},
+ {"contains high ascii", "a\xff"},
+ {"contains nonprintable character", "a\x7f"},
+ {"contains C0", "a\x01"},
+ {"UTF-8 encoded", "a\xc3\xa9"},
+ {"contains TAB", "a\t"},
+ {"contains LF", "a\n"},
+ {"contains CR", "a\r"},
+ {"contains SP", "a "},
+ {"begins with digit", "9token"},
+ {"begins with hyphen", "-token"},
+ {"begins with LF", "\ntoken"},
+ {"begins with SP", " token"},
+ {"begins with colon", ":token"},
+ {"begins with percent", "%token"},
+ {"begins with period", ".token"},
+ {"begins with slash", "/token"},
+ };
+ for (const auto& bad_token : bad_tokens) {
+ SCOPED_TRACE(bad_token.name);
+ absl::optional<std::string> serialization =
+ SerializeItem(Token(bad_token.value));
+ EXPECT_FALSE(serialization.has_value()) << *serialization;
+ }
+}
+
+TEST(StructuredHeaderTest, UnserializableKeys) {
+ static const struct UnserializableString {
+ const char* name;
+ const char* value;
+ } bad_keys[] = {
+ {"empty key", ""},
+ {"contains high ascii", "a\xff"},
+ {"contains nonprintable character", "a\x7f"},
+ {"contains C0", "a\x01"},
+ {"UTF-8 encoded", "a\xc3\xa9"},
+ {"contains TAB", "a\t"},
+ {"contains LF", "a\n"},
+ {"contains CR", "a\r"},
+ {"contains SP", "a "},
+ {"begins with uppercase", "Atoken"},
+ {"begins with digit", "9token"},
+ {"begins with hyphen", "-token"},
+ {"begins with LF", "\ntoken"},
+ {"begins with SP", " token"},
+ {"begins with colon", ":token"},
+ {"begins with percent", "%token"},
+ {"begins with period", ".token"},
+ {"begins with slash", "/token"},
+ };
+ for (const auto& bad_key : bad_keys) {
+ SCOPED_TRACE(bad_key.name);
+ absl::optional<std::string> serialization =
+ SerializeItem(ParameterizedItem("a", {{bad_key.value, "a"}}));
+ EXPECT_FALSE(serialization.has_value()) << *serialization;
+ }
+}
+
+TEST(StructuredHeaderTest, UnserializableStrings) {
+ static const struct UnserializableString {
+ const char* name;
+ const char* value;
+ } bad_strings[] = {
+ {"contains high ascii", "a\xff"},
+ {"contains nonprintable character", "a\x7f"},
+ {"UTF-8 encoded", "a\xc3\xa9"},
+ {"contains TAB", "a\t"},
+ {"contains LF", "a\n"},
+ {"contains CR", "a\r"},
+ {"contains C0", "a\x01"},
+ };
+ for (const auto& bad_string : bad_strings) {
+ SCOPED_TRACE(bad_string.name);
+ absl::optional<std::string> serialization =
+ SerializeItem(Item(bad_string.value));
+ EXPECT_FALSE(serialization.has_value()) << *serialization;
+ }
+}
+
+TEST(StructuredHeaderTest, UnserializableIntegers) {
+ EXPECT_FALSE(SerializeItem(Integer(1e15L)).has_value());
+ EXPECT_FALSE(SerializeItem(Integer(-1e15L)).has_value());
+}
+
+TEST(StructuredHeaderTest, UnserializableDecimals) {
+ for (double value :
+ {std::numeric_limits<double>::quiet_NaN(),
+ std::numeric_limits<double>::infinity(),
+ -std::numeric_limits<double>::infinity(), 1e12, 1e12 - 0.0001,
+ 1e12 - 0.0005, -1e12, -1e12 + 0.0001, -1e12 + 0.0005}) {
+ auto x = SerializeItem(Item(value));
+ EXPECT_FALSE(SerializeItem(Item(value)).has_value());
+ }
+}
+
+// These values cannot be directly parsed from headers, but are valid doubles
+// which can be serialized as sh-floats (though rounding is expected.)
+TEST(StructuredHeaderTest, SerializeUnparseableDecimals) {
+ struct UnparseableDecimal {
+ const char* name;
+ double value;
+ const char* canonical;
+ } float_test_cases[] = {
+ {"negative 0", -0.0, "0.0"},
+ {"0.0001", 0.0001, "0.0"},
+ {"0.0000001", 0.0000001, "0.0"},
+ {"1.0001", 1.0001, "1.0"},
+ {"1.0009", 1.0009, "1.001"},
+ {"round positive odd decimal", 0.0015, "0.002"},
+ {"round positive even decimal", 0.0025, "0.002"},
+ {"round negative odd decimal", -0.0015, "-0.002"},
+ {"round negative even decimal", -0.0025, "-0.002"},
+ {"round decimal up to integer part", 9.9995, "10.0"},
+ {"subnormal numbers", std::numeric_limits<double>::denorm_min(), "0.0"},
+ {"round up to 10 digits", 1e9 - 0.0000001, "1000000000.0"},
+ {"round up to 11 digits", 1e10 - 0.000001, "10000000000.0"},
+ {"round up to 12 digits", 1e11 - 0.00001, "100000000000.0"},
+ {"largest serializable float", nextafter(1e12 - 0.0005, 0),
+ "999999999999.999"},
+ {"largest serializable negative float", -nextafter(1e12 - 0.0005, 0),
+ "-999999999999.999"},
+ // This will fail if we simply truncate the fractional portion.
+ {"float rounds up to next int", 3.9999999, "4.0"},
+ // This will fail if we first round to >3 digits, and then round again to
+ // 3 digits.
+ {"don't double round", 3.99949, "3.999"},
+ // This will fail if we first round to 3 digits, and then round again to
+ // max_avail_digits.
+ {"don't double round", 123456789.99949, "123456789.999"},
+ };
+ for (const auto& test_case : float_test_cases) {
+ SCOPED_TRACE(test_case.name);
+ absl::optional<std::string> serialization =
+ SerializeItem(Item(test_case.value));
+ EXPECT_TRUE(serialization.has_value());
+ EXPECT_EQ(*serialization, test_case.canonical);
+ }
+}
+
+TEST(StructuredHeaderTest, SerializeList) {
+ for (const auto& c : list_test_cases) {
+ SCOPED_TRACE(c.name);
+ if (c.expected) {
+ absl::optional<std::string> result = SerializeList(*c.expected);
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw));
+ }
+ }
+}
+
+TEST(StructuredHeaderTest, UnserializableLists) {
+ static const struct UnserializableList {
+ const char* name;
+ const List value;
+ } bad_lists[] = {
+ {"Null item as member", {{Item(), {}}}},
+ {"Unserializable item as member", {{Token("\n"), {}}}},
+ {"Key is empty", {{Token("abc"), {Param("", 1)}}}},
+ {"Key containswhitespace", {{Token("abc"), {Param("a\n", 1)}}}},
+ {"Key contains UTF8", {{Token("abc"), {Param("a\xc3\xa9", 1)}}}},
+ {"Key contains unprintable characters",
+ {{Token("abc"), {Param("a\x7f", 1)}}}},
+ {"Key contains disallowed characters",
+ {{Token("abc"), {Param("a:", 1)}}}},
+ {"Param value is unserializable", {{Token("abc"), {{"a", Token("\n")}}}}},
+ {"Inner list contains unserializable item",
+ {{std::vector<ParameterizedItem>{{Token("\n"), {}}}, {}}}},
+ };
+ for (const auto& bad_list : bad_lists) {
+ SCOPED_TRACE(bad_list.name);
+ absl::optional<std::string> serialization = SerializeList(bad_list.value);
+ EXPECT_FALSE(serialization.has_value()) << *serialization;
+ }
+}
+
+TEST(StructuredHeaderTest, SerializeDictionary) {
+ for (const auto& c : dictionary_test_cases) {
+ SCOPED_TRACE(c.name);
+ if (c.expected) {
+ absl::optional<std::string> result = SerializeDictionary(*c.expected);
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw));
+ }
+ }
+}
+
+TEST(StructuredHeaderTest, DictionaryConstructors) {
+ const std::string key0 = "key0";
+ const std::string key1 = "key1";
+ const ParameterizedMember member0{Item("Applepie"), {}};
+ const ParameterizedMember member1{Item("hello", Item::kByteSequenceType), {}};
+
+ Dictionary dict;
+ EXPECT_TRUE(dict.empty());
+ EXPECT_EQ(0U, dict.size());
+ dict[key0] = member0;
+ EXPECT_FALSE(dict.empty());
+ EXPECT_EQ(1U, dict.size());
+
+ const Dictionary dict_copy = dict;
+ EXPECT_FALSE(dict_copy.empty());
+ EXPECT_EQ(1U, dict_copy.size());
+ EXPECT_EQ(dict, dict_copy);
+
+ const Dictionary dict_init{{{key0, member0}, {key1, member1}}};
+ EXPECT_FALSE(dict_init.empty());
+ EXPECT_EQ(2U, dict_init.size());
+ EXPECT_EQ(member0, dict_init.at(key0));
+ EXPECT_EQ(member1, dict_init.at(key1));
+}
+
+TEST(StructuredHeaderTest, DictionaryAccessors) {
+ const std::string key0 = "key0";
+ const std::string key1 = "key1";
+
+ const ParameterizedMember nonempty_member0{Item("Applepie"), {}};
+ const ParameterizedMember nonempty_member1{
+ Item("hello", Item::kByteSequenceType), {}};
+ const ParameterizedMember empty_member;
+
+ Dictionary dict{{{key0, nonempty_member0}}};
+ EXPECT_TRUE(dict.contains(key0));
+ EXPECT_EQ(nonempty_member0, dict[key0]);
+ EXPECT_EQ(&dict[key0], &dict.at(key0));
+ EXPECT_EQ(&dict[key0], &dict[0]);
+ EXPECT_EQ(&dict[key0], &dict.at(0));
+
+ // Even if the key does not yet exist in |dict|, operator[]() should
+ // automatically create an empty entry.
+ ASSERT_FALSE(dict.contains(key1));
+ ParameterizedMember& member1 = dict[key1];
+ EXPECT_TRUE(dict.contains(key1));
+ EXPECT_EQ(empty_member, member1);
+ EXPECT_EQ(&member1, &dict[key1]);
+ EXPECT_EQ(&member1, &dict.at(key1));
+ EXPECT_EQ(&member1, &dict[1]);
+ EXPECT_EQ(&member1, &dict.at(1));
+
+ member1 = nonempty_member1;
+ EXPECT_EQ(nonempty_member1, dict[key1]);
+ EXPECT_EQ(&dict[key1], &dict.at(key1));
+ EXPECT_EQ(&dict[key1], &dict[1]);
+ EXPECT_EQ(&dict[key1], &dict.at(1));
+
+ // at(StringPiece) and indexed accessors have const overloads.
+ const Dictionary& dict_ref = dict;
+ EXPECT_EQ(&member1, &dict_ref.at(key1));
+ EXPECT_EQ(&member1, &dict_ref[1]);
+ EXPECT_EQ(&member1, &dict_ref.at(1));
+}
+
+TEST(StructuredHeaderTest, UnserializableDictionary) {
+ static const struct UnserializableDictionary {
+ const char* name;
+ const Dictionary value;
+ } bad_dictionaries[] = {
+ {"Unserializable dict key", Dictionary{{{"ABC", {Token("abc"), {}}}}}},
+ {"Dictionary item is unserializable",
+ Dictionary{{{"abc", {Token("abc="), {}}}}}},
+ {"Param value is unserializable",
+ Dictionary{{{"abc", {Token("abc"), {{"a", Token("\n")}}}}}}},
+ {"Dictionary inner-list contains unserializable item",
+ Dictionary{
+ {{"abc",
+ {std::vector<ParameterizedItem>{{Token("abc="), {}}}, {}}}}}},
+ };
+ for (const auto& bad_dictionary : bad_dictionaries) {
+ SCOPED_TRACE(bad_dictionary.name);
+ absl::optional<std::string> serialization =
+ SerializeDictionary(bad_dictionary.value);
+ EXPECT_FALSE(serialization.has_value()) << *serialization;
+ }
+}
+
+} // namespace structured_headers
+} // namespace quiche