xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/common/structured_headers_test.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "quiche/common/structured_headers.h"
6 
7 #include <math.h>
8 
9 #include <limits>
10 #include <optional>
11 #include <string>
12 
13 #include "quiche/common/platform/api/quiche_test.h"
14 
15 namespace quiche {
16 namespace structured_headers {
17 namespace {
18 
19 // Helpers to make test cases clearer
20 
Token(std::string value)21 Item Token(std::string value) { return Item(value, Item::kTokenType); }
22 
Integer(int64_t value)23 Item Integer(int64_t value) { return Item(value); }
24 
25 // Parameter with null value, only used in Structured Headers Draft 09
NullParam(std::string key)26 std::pair<std::string, Item> NullParam(std::string key) {
27   return std::make_pair(key, Item());
28 }
29 
BooleanParam(std::string key,bool value)30 std::pair<std::string, Item> BooleanParam(std::string key, bool value) {
31   return std::make_pair(key, Item(value));
32 }
33 
DoubleParam(std::string key,double value)34 std::pair<std::string, Item> DoubleParam(std::string key, double value) {
35   return std::make_pair(key, Item(value));
36 }
37 
Param(std::string key,int64_t value)38 std::pair<std::string, Item> Param(std::string key, int64_t value) {
39   return std::make_pair(key, Item(value));
40 }
41 
Param(std::string key,std::string value)42 std::pair<std::string, Item> Param(std::string key, std::string value) {
43   return std::make_pair(key, Item(value));
44 }
45 
ByteSequenceParam(std::string key,std::string value)46 std::pair<std::string, Item> ByteSequenceParam(std::string key,
47                                                std::string value) {
48   return std::make_pair(key, Item(value, Item::kByteSequenceType));
49 }
50 
TokenParam(std::string key,std::string value)51 std::pair<std::string, Item> TokenParam(std::string key, std::string value) {
52   return std::make_pair(key, Token(value));
53 }
54 
55 // Test cases taken from https://github.com/httpwg/structured-header-tests can
56 // be found in structured_headers_generated_unittest.cc
57 
58 const struct ItemTestCase {
59   const char* name;
60   const char* raw;
61   const std::optional<Item> expected;  // nullopt if parse error is expected.
62   const char* canonical;  // nullptr if parse error is expected, or if canonical
63                           // format is identical to raw.
64 } item_test_cases[] = {
65     // Token
66     {"bad token - item", "abc$@%!", std::nullopt, nullptr},
67     {"leading whitespace", " foo", Token("foo"), "foo"},
68     {"trailing whitespace", "foo ", Token("foo"), "foo"},
69     {"leading asterisk", "*foo", Token("*foo"), nullptr},
70     // Number
71     {"long integer", "999999999999999", Integer(999999999999999L), nullptr},
72     {"long negative integer", "-999999999999999", Integer(-999999999999999L),
73      nullptr},
74     {"too long integer", "1000000000000000", std::nullopt, nullptr},
75     {"negative too long integer", "-1000000000000000", std::nullopt, nullptr},
76     {"integral decimal", "1.0", Item(1.0), nullptr},
77     // String
78     {"basic string", "\"foo\"", Item("foo"), nullptr},
79     {"non-ascii string", "\"f\xC3\xBC\xC3\xBC\"", std::nullopt, nullptr},
80     // Additional tests
81     {"valid quoting containing \\n", "\"\\\\n\"", Item("\\n"), nullptr},
82     {"valid quoting containing \\t", "\"\\\\t\"", Item("\\t"), nullptr},
83     {"valid quoting containing \\x", "\"\\\\x61\"", Item("\\x61"), nullptr},
84     {"c-style hex escape in string", "\"\\x61\"", std::nullopt, nullptr},
85     {"valid quoting containing \\u", "\"\\\\u0061\"", Item("\\u0061"), nullptr},
86     {"c-style unicode escape in string", "\"\\u0061\"", std::nullopt, nullptr},
87 };
88 
89 const ItemTestCase sh09_item_test_cases[] = {
90     // Integer
91     {"large integer", "9223372036854775807", Integer(9223372036854775807L),
92      nullptr},
93     {"large negative integer", "-9223372036854775807",
94      Integer(-9223372036854775807L), nullptr},
95     {"too large integer", "9223372036854775808", std::nullopt, nullptr},
96     {"too large negative integer", "-9223372036854775808", std::nullopt,
97      nullptr},
98     // Byte Sequence
99     {"basic binary", "*aGVsbG8=*", Item("hello", Item::kByteSequenceType),
100      nullptr},
101     {"empty binary", "**", Item("", Item::kByteSequenceType), nullptr},
102     {"bad paddding", "*aGVsbG8*", Item("hello", Item::kByteSequenceType),
103      "*aGVsbG8=*"},
104     {"bad end delimiter", "*aGVsbG8=", std::nullopt, nullptr},
105     {"extra whitespace", "*aGVsb G8=*", std::nullopt, nullptr},
106     {"extra chars", "*aGVsbG!8=*", std::nullopt, nullptr},
107     {"suffix chars", "*aGVsbG8=!*", std::nullopt, nullptr},
108     {"non-zero pad bits", "*iZ==*", Item("\x89", Item::kByteSequenceType),
109      "*iQ==*"},
110     {"non-ASCII binary", "*/+Ah*", Item("\xFF\xE0!", Item::kByteSequenceType),
111      nullptr},
112     {"base64url binary", "*_-Ah*", std::nullopt, nullptr},
113     {"token with leading asterisk", "*foo", std::nullopt, nullptr},
114 };
115 
116 // For Structured Headers Draft 15
117 const struct ParameterizedItemTestCase {
118   const char* name;
119   const char* raw;
120   const std::optional<ParameterizedItem>
121       expected;           // nullopt if parse error is expected.
122   const char* canonical;  // nullptr if parse error is expected, or if canonical
123                           // format is identical to raw.
124 } parameterized_item_test_cases[] = {
125     {"single parameter item",
126      "text/html;q=1.0",
127      {{Token("text/html"), {DoubleParam("q", 1)}}},
128      nullptr},
129     {"missing parameter value item",
130      "text/html;a;q=1.0",
131      {{Token("text/html"), {BooleanParam("a", true), DoubleParam("q", 1)}}},
132      nullptr},
133     {"missing terminal parameter value item",
134      "text/html;q=1.0;a",
135      {{Token("text/html"), {DoubleParam("q", 1), BooleanParam("a", true)}}},
136      nullptr},
137     {"duplicate parameter keys with different value",
138      "text/html;a=1;b=2;a=3.0",
139      {{Token("text/html"), {DoubleParam("a", 3), Param("b", 2L)}}},
140      "text/html;a=3.0;b=2"},
141     {"multiple duplicate parameter keys at different position",
142      "text/html;c=1;a=2;b;b=3.0;a",
143      {{Token("text/html"),
144        {Param("c", 1L), BooleanParam("a", true), DoubleParam("b", 3)}}},
145      "text/html;c=1;a;b=3.0"},
146     {"duplicate parameter keys with missing value",
147      "text/html;a;a=1",
148      {{Token("text/html"), {Param("a", 1L)}}},
149      "text/html;a=1"},
150     {"whitespace before = parameterised item", "text/html, text/plain;q =0.5",
151      std::nullopt, nullptr},
152     {"whitespace after = parameterised item", "text/html, text/plain;q= 0.5",
153      std::nullopt, nullptr},
154     {"whitespace before ; parameterised item", "text/html, text/plain ;q=0.5",
155      std::nullopt, nullptr},
156     {"whitespace after ; parameterised item",
157      "text/plain; q=0.5",
158      {{Token("text/plain"), {DoubleParam("q", 0.5)}}},
159      "text/plain;q=0.5"},
160     {"extra whitespace parameterised item",
161      "text/plain;  q=0.5;  charset=utf-8",
162      {{Token("text/plain"),
163        {DoubleParam("q", 0.5), TokenParam("charset", "utf-8")}}},
164      "text/plain;q=0.5;charset=utf-8"},
165 };
166 
167 // For Structured Headers Draft 15
168 const struct ListTestCase {
169   const char* name;
170   const char* raw;
171   const std::optional<List> expected;  // nullopt if parse error is expected.
172   const char* canonical;  // nullptr if parse error is expected, or if canonical
173                           // format is identical to raw.
174 } list_test_cases[] = {
175     // Lists of lists
176     {"extra whitespace list of lists",
177      "(1  42)",
178      {{{{{Integer(1L), {}}, {Integer(42L), {}}}, {}}}},
179      "(1 42)"},
180     // Parameterized Lists
181     {"basic parameterised list",
182      "abc_123;a=1;b=2; cdef_456, ghi;q=\"9\";r=\"+w\"",
183      {{{Token("abc_123"),
184         {Param("a", 1), Param("b", 2), BooleanParam("cdef_456", true)}},
185        {Token("ghi"), {Param("q", "9"), Param("r", "+w")}}}},
186      "abc_123;a=1;b=2;cdef_456, ghi;q=\"9\";r=\"+w\""},
187     // Parameterized inner lists
188     {"parameterised basic list of lists",
189      "(1;a=1.0 2), (42 43)",
190      {{{{{Integer(1L), {DoubleParam("a", 1.0)}}, {Integer(2L), {}}}, {}},
191        {{{Integer(42L), {}}, {Integer(43L), {}}}, {}}}},
192      nullptr},
193     {"parameters on inner members",
194      "(1;a=1.0 2;b=c), (42;d=?0 43;e=:Zmdo:)",
195      {{{{{Integer(1L), {DoubleParam("a", 1.0)}},
196          {Integer(2L), {TokenParam("b", "c")}}},
197         {}},
198        {{{Integer(42L), {BooleanParam("d", false)}},
199          {Integer(43L), {ByteSequenceParam("e", "fgh")}}},
200         {}}}},
201      nullptr},
202     {"parameters on inner lists",
203      "(1 2);a=1.0, (42 43);b=?0",
204      {{{{{Integer(1L), {}}, {Integer(2L), {}}}, {DoubleParam("a", 1.0)}},
205        {{{Integer(42L), {}}, {Integer(43L), {}}}, {BooleanParam("b", false)}}}},
206      nullptr},
207     {"default true values for parameters on inner list members",
208      "(1;a 2), (42 43;b)",
209      {{{{{Integer(1L), {BooleanParam("a", true)}}, {Integer(2L), {}}}, {}},
210        {{{Integer(42L), {}}, {Integer(43L), {BooleanParam("b", true)}}}, {}}}},
211      nullptr},
212     {"default true values for parameters on inner lists",
213      "(1 2);a, (42 43);b",
214      {{{{{Integer(1L), {}}, {Integer(2L), {}}}, {BooleanParam("a", true)}},
215        {{{Integer(42L), {}}, {Integer(43L), {}}}, {BooleanParam("b", true)}}}},
216      nullptr},
217     {"extra whitespace before semicolon in parameters on inner list member",
218      "(a;b ;c b)", std::nullopt, nullptr},
219     {"extra whitespace between parameters on inner list member",
220      "(a;b; c b)",
221      {{{{{Token("a"), {BooleanParam("b", true), BooleanParam("c", true)}},
222          {Token("b"), {}}},
223         {}}}},
224      "(a;b;c b)"},
225     {"extra whitespace before semicolon in parameters on inner list",
226      "(a b);c ;d, (e)", std::nullopt, nullptr},
227     {"extra whitespace between parameters on inner list",
228      "(a b);c; d, (e)",
229      {{{{{Token("a"), {}}, {Token("b"), {}}},
230         {BooleanParam("c", true), BooleanParam("d", true)}},
231        {{{Token("e"), {}}}, {}}}},
232      "(a b);c;d, (e)"},
233 };
234 
235 // For Structured Headers Draft 15
236 const struct DictionaryTestCase {
237   const char* name;
238   const char* raw;
239   const std::optional<Dictionary>
240       expected;           // nullopt if parse error is expected.
241   const char* canonical;  // nullptr if parse error is expected, or if canonical
242                           // format is identical to raw.
243 } dictionary_test_cases[] = {
244     {"basic dictionary",
245      "en=\"Applepie\", da=:aGVsbG8=:",
246      {Dictionary{{{"en", {Item("Applepie"), {}}},
247                   {"da", {Item("hello", Item::kByteSequenceType), {}}}}}},
248      nullptr},
249     {"tab separated dictionary",
250      "a=1\t,\tb=2",
251      {Dictionary{{{"a", {Integer(1L), {}}}, {"b", {Integer(2L), {}}}}}},
252      "a=1, b=2"},
253     {"missing value with params dictionary",
254      "a=1, b;foo=9, c=3",
255      {Dictionary{{{"a", {Integer(1L), {}}},
256                   {"b", {Item(true), {Param("foo", 9)}}},
257                   {"c", {Integer(3L), {}}}}}},
258      nullptr},
259     // Parameterised dictionary tests
260     {"parameterised inner list member dict",
261      "a=(\"1\";b=1;c=?0 \"2\");d=\"e\"",
262      {Dictionary{{{"a",
263                    {{{Item("1"), {Param("b", 1), BooleanParam("c", false)}},
264                      {Item("2"), {}}},
265                     {Param("d", "e")}}}}}},
266      nullptr},
267     {"explicit true value with parameter",
268      "a=?1;b=1",
269      {Dictionary{{{"a", {Item(true), {Param("b", 1)}}}}}},
270      "a;b=1"},
271     {"implicit true value with parameter",
272      "a;b=1",
273      {Dictionary{{{"a", {Item(true), {Param("b", 1)}}}}}},
274      nullptr},
275     {"implicit true value with implicitly-valued parameter",
276      "a;b",
277      {Dictionary{{{"a", {Item(true), {BooleanParam("b", true)}}}}}},
278      nullptr},
279 };
280 }  // namespace
281 
TEST(StructuredHeaderTest,ParseBareItem)282 TEST(StructuredHeaderTest, ParseBareItem) {
283   for (const auto& c : item_test_cases) {
284     SCOPED_TRACE(c.name);
285     std::optional<Item> result = ParseBareItem(c.raw);
286     EXPECT_EQ(result, c.expected);
287   }
288 }
289 
290 // For Structured Headers Draft 15, these tests include parameters on Items.
TEST(StructuredHeaderTest,ParseItem)291 TEST(StructuredHeaderTest, ParseItem) {
292   for (const auto& c : parameterized_item_test_cases) {
293     SCOPED_TRACE(c.name);
294     std::optional<ParameterizedItem> result = ParseItem(c.raw);
295     EXPECT_EQ(result, c.expected);
296   }
297 }
298 
299 // Structured Headers Draft 9 parsing rules are different than Draft 15, and
300 // some strings which are considered invalid in SH15 should parse in SH09.
301 // The SH09 Item parser is not directly exposed, but can be used indirectly by
302 // calling the parser for SH09-specific lists.
TEST(StructuredHeaderTest,ParseSH09Item)303 TEST(StructuredHeaderTest, ParseSH09Item) {
304   for (const auto& c : sh09_item_test_cases) {
305     SCOPED_TRACE(c.name);
306     std::optional<ListOfLists> result = ParseListOfLists(c.raw);
307     if (c.expected.has_value()) {
308       EXPECT_TRUE(result.has_value());
309       EXPECT_EQ(result->size(), 1UL);
310       EXPECT_EQ((*result)[0].size(), 1UL);
311       EXPECT_EQ((*result)[0][0], c.expected);
312     } else {
313       EXPECT_FALSE(result.has_value());
314     }
315   }
316 }
317 
318 // In Structured Headers Draft 9, floats can have more than three fractional
319 // digits, and can be larger than 1e12. This behaviour is exposed in the parser
320 // for SH09-specific lists, so test it through that interface.
TEST(StructuredHeaderTest,SH09HighPrecisionFloats)321 TEST(StructuredHeaderTest, SH09HighPrecisionFloats) {
322   // These values are exactly representable in binary floating point, so no
323   // accuracy issues are expected in this test.
324   std::optional<ListOfLists> result =
325       ParseListOfLists("1.03125;-1.03125;12345678901234.5;-12345678901234.5");
326   ASSERT_TRUE(result.has_value());
327   EXPECT_EQ(*result,
328             (ListOfLists{{Item(1.03125), Item(-1.03125), Item(12345678901234.5),
329                           Item(-12345678901234.5)}}));
330 
331   result = ParseListOfLists("123456789012345.0");
332   EXPECT_FALSE(result.has_value());
333 
334   result = ParseListOfLists("-123456789012345.0");
335   EXPECT_FALSE(result.has_value());
336 }
337 
338 // For Structured Headers Draft 9
TEST(StructuredHeaderTest,ParseListOfLists)339 TEST(StructuredHeaderTest, ParseListOfLists) {
340   static const struct TestCase {
341     const char* name;
342     const char* raw;
343     ListOfLists expected;  // empty if parse error is expected
344   } cases[] = {
345       {"basic list of lists",
346        "1;2, 42;43",
347        {{Integer(1L), Integer(2L)}, {Integer(42L), Integer(43L)}}},
348       {"empty list of lists", "", {}},
349       {"single item list of lists", "42", {{Integer(42L)}}},
350       {"no whitespace list of lists", "1,42", {{Integer(1L)}, {Integer(42L)}}},
351       {"no inner whitespace list of lists",
352        "1;2, 42;43",
353        {{Integer(1L), Integer(2L)}, {Integer(42L), Integer(43L)}}},
354       {"extra whitespace list of lists",
355        "1 , 42",
356        {{Integer(1L)}, {Integer(42L)}}},
357       {"extra inner whitespace list of lists",
358        "1 ; 2,42 ; 43",
359        {{Integer(1L), Integer(2L)}, {Integer(42L), Integer(43L)}}},
360       {"trailing comma list of lists", "1;2, 42,", {}},
361       {"trailing semicolon list of lists", "1;2, 42;43;", {}},
362       {"leading comma list of lists", ",1;2, 42", {}},
363       {"leading semicolon list of lists", ";1;2, 42;43", {}},
364       {"empty item list of lists", "1,,42", {}},
365       {"empty inner item list of lists", "1;;2,42", {}},
366   };
367   for (const auto& c : cases) {
368     SCOPED_TRACE(c.name);
369     std::optional<ListOfLists> result = ParseListOfLists(c.raw);
370     if (!c.expected.empty()) {
371       EXPECT_TRUE(result.has_value());
372       EXPECT_EQ(*result, c.expected);
373     } else {
374       EXPECT_FALSE(result.has_value());
375     }
376   }
377 }
378 
379 // For Structured Headers Draft 9
TEST(StructuredHeaderTest,ParseParameterisedList)380 TEST(StructuredHeaderTest, ParseParameterisedList) {
381   static const struct TestCase {
382     const char* name;
383     const char* raw;
384     ParameterisedList expected;  // empty if parse error is expected
385   } cases[] = {
386       {"basic param-list",
387        "abc_123;a=1;b=2; cdef_456, ghi;q=\"9\";r=\"w\"",
388        {
389            {Token("abc_123"),
390             {Param("a", 1), Param("b", 2), NullParam("cdef_456")}},
391            {Token("ghi"), {Param("q", "9"), Param("r", "w")}},
392        }},
393       {"empty param-list", "", {}},
394       {"single item param-list",
395        "text/html;q=1",
396        {{Token("text/html"), {Param("q", 1)}}}},
397       {"empty param-list", "", {}},
398       {"no whitespace param-list",
399        "text/html,text/plain;q=1",
400        {{Token("text/html"), {}}, {Token("text/plain"), {Param("q", 1)}}}},
401       {"whitespace before = param-list", "text/html, text/plain;q =1", {}},
402       {"whitespace after = param-list", "text/html, text/plain;q= 1", {}},
403       {"extra whitespace param-list",
404        "text/html  ,  text/plain ;  q=1",
405        {{Token("text/html"), {}}, {Token("text/plain"), {Param("q", 1)}}}},
406       {"duplicate key", "abc;a=1;b=2;a=1", {}},
407       {"numeric key", "abc;a=1;1b=2;c=1", {}},
408       {"uppercase key", "abc;a=1;B=2;c=1", {}},
409       {"bad key", "abc;a=1;b!=2;c=1", {}},
410       {"another bad key", "abc;a=1;b==2;c=1", {}},
411       {"empty key name", "abc;a=1;=2;c=1", {}},
412       {"empty parameter", "abc;a=1;;c=1", {}},
413       {"empty list item", "abc;a=1,,def;b=1", {}},
414       {"extra semicolon", "abc;a=1;b=1;", {}},
415       {"extra comma", "abc;a=1,def;b=1,", {}},
416       {"leading semicolon", ";abc;a=1", {}},
417       {"leading comma", ",abc;a=1", {}},
418   };
419   for (const auto& c : cases) {
420     SCOPED_TRACE(c.name);
421     std::optional<ParameterisedList> result = ParseParameterisedList(c.raw);
422     if (c.expected.empty()) {
423       EXPECT_FALSE(result.has_value());
424       continue;
425     }
426     EXPECT_TRUE(result.has_value());
427     EXPECT_EQ(result->size(), c.expected.size());
428     if (result->size() == c.expected.size()) {
429       for (size_t i = 0; i < c.expected.size(); ++i) {
430         EXPECT_EQ((*result)[i], c.expected[i]);
431       }
432     }
433   }
434 }
435 
436 // For Structured Headers Draft 15
TEST(StructuredHeaderTest,ParseList)437 TEST(StructuredHeaderTest, ParseList) {
438   for (const auto& c : list_test_cases) {
439     SCOPED_TRACE(c.name);
440     std::optional<List> result = ParseList(c.raw);
441     EXPECT_EQ(result, c.expected);
442   }
443 }
444 
445 // For Structured Headers Draft 15
TEST(StructuredHeaderTest,ParseDictionary)446 TEST(StructuredHeaderTest, ParseDictionary) {
447   for (const auto& c : dictionary_test_cases) {
448     SCOPED_TRACE(c.name);
449     std::optional<Dictionary> result = ParseDictionary(c.raw);
450     EXPECT_EQ(result, c.expected);
451   }
452 }
453 
454 // Serializer tests are all exclusively for Structured Headers Draft 15
455 
TEST(StructuredHeaderTest,SerializeItem)456 TEST(StructuredHeaderTest, SerializeItem) {
457   for (const auto& c : item_test_cases) {
458     SCOPED_TRACE(c.name);
459     if (c.expected) {
460       std::optional<std::string> result = SerializeItem(*c.expected);
461       EXPECT_TRUE(result.has_value());
462       EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw));
463     }
464   }
465 }
466 
TEST(StructuredHeaderTest,SerializeParameterizedItem)467 TEST(StructuredHeaderTest, SerializeParameterizedItem) {
468   for (const auto& c : parameterized_item_test_cases) {
469     SCOPED_TRACE(c.name);
470     if (c.expected) {
471       std::optional<std::string> result = SerializeItem(*c.expected);
472       EXPECT_TRUE(result.has_value());
473       EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw));
474     }
475   }
476 }
477 
TEST(StructuredHeaderTest,UnserializableItems)478 TEST(StructuredHeaderTest, UnserializableItems) {
479   // Test that items with unknown type are not serialized.
480   EXPECT_FALSE(SerializeItem(Item()).has_value());
481 }
482 
TEST(StructuredHeaderTest,UnserializableTokens)483 TEST(StructuredHeaderTest, UnserializableTokens) {
484   static const struct UnserializableString {
485     const char* name;
486     const char* value;
487   } bad_tokens[] = {
488       {"empty token", ""},
489       {"contains high ascii", "a\xff"},
490       {"contains nonprintable character", "a\x7f"},
491       {"contains C0", "a\x01"},
492       {"UTF-8 encoded", "a\xc3\xa9"},
493       {"contains TAB", "a\t"},
494       {"contains LF", "a\n"},
495       {"contains CR", "a\r"},
496       {"contains SP", "a "},
497       {"begins with digit", "9token"},
498       {"begins with hyphen", "-token"},
499       {"begins with LF", "\ntoken"},
500       {"begins with SP", " token"},
501       {"begins with colon", ":token"},
502       {"begins with percent", "%token"},
503       {"begins with period", ".token"},
504       {"begins with slash", "/token"},
505   };
506   for (const auto& bad_token : bad_tokens) {
507     SCOPED_TRACE(bad_token.name);
508     std::optional<std::string> serialization =
509         SerializeItem(Token(bad_token.value));
510     EXPECT_FALSE(serialization.has_value()) << *serialization;
511   }
512 }
513 
TEST(StructuredHeaderTest,UnserializableKeys)514 TEST(StructuredHeaderTest, UnserializableKeys) {
515   static const struct UnserializableString {
516     const char* name;
517     const char* value;
518   } bad_keys[] = {
519       {"empty key", ""},
520       {"contains high ascii", "a\xff"},
521       {"contains nonprintable character", "a\x7f"},
522       {"contains C0", "a\x01"},
523       {"UTF-8 encoded", "a\xc3\xa9"},
524       {"contains TAB", "a\t"},
525       {"contains LF", "a\n"},
526       {"contains CR", "a\r"},
527       {"contains SP", "a "},
528       {"begins with uppercase", "Atoken"},
529       {"begins with digit", "9token"},
530       {"begins with hyphen", "-token"},
531       {"begins with LF", "\ntoken"},
532       {"begins with SP", " token"},
533       {"begins with colon", ":token"},
534       {"begins with percent", "%token"},
535       {"begins with period", ".token"},
536       {"begins with slash", "/token"},
537   };
538   for (const auto& bad_key : bad_keys) {
539     SCOPED_TRACE(bad_key.name);
540     std::optional<std::string> serialization =
541         SerializeItem(ParameterizedItem("a", {{bad_key.value, "a"}}));
542     EXPECT_FALSE(serialization.has_value()) << *serialization;
543   }
544 }
545 
TEST(StructuredHeaderTest,UnserializableStrings)546 TEST(StructuredHeaderTest, UnserializableStrings) {
547   static const struct UnserializableString {
548     const char* name;
549     const char* value;
550   } bad_strings[] = {
551       {"contains high ascii", "a\xff"},
552       {"contains nonprintable character", "a\x7f"},
553       {"UTF-8 encoded", "a\xc3\xa9"},
554       {"contains TAB", "a\t"},
555       {"contains LF", "a\n"},
556       {"contains CR", "a\r"},
557       {"contains C0", "a\x01"},
558   };
559   for (const auto& bad_string : bad_strings) {
560     SCOPED_TRACE(bad_string.name);
561     std::optional<std::string> serialization =
562         SerializeItem(Item(bad_string.value));
563     EXPECT_FALSE(serialization.has_value()) << *serialization;
564   }
565 }
566 
TEST(StructuredHeaderTest,UnserializableIntegers)567 TEST(StructuredHeaderTest, UnserializableIntegers) {
568   EXPECT_FALSE(SerializeItem(Integer(1e15L)).has_value());
569   EXPECT_FALSE(SerializeItem(Integer(-1e15L)).has_value());
570 }
571 
TEST(StructuredHeaderTest,UnserializableDecimals)572 TEST(StructuredHeaderTest, UnserializableDecimals) {
573   for (double value :
574        {std::numeric_limits<double>::quiet_NaN(),
575         std::numeric_limits<double>::infinity(),
576         -std::numeric_limits<double>::infinity(), 1e12, 1e12 - 0.0001,
577         1e12 - 0.0005, -1e12, -1e12 + 0.0001, -1e12 + 0.0005}) {
578     auto x = SerializeItem(Item(value));
579     EXPECT_FALSE(SerializeItem(Item(value)).has_value());
580   }
581 }
582 
583 // These values cannot be directly parsed from headers, but are valid doubles
584 // which can be serialized as sh-floats (though rounding is expected.)
TEST(StructuredHeaderTest,SerializeUnparseableDecimals)585 TEST(StructuredHeaderTest, SerializeUnparseableDecimals) {
586   struct UnparseableDecimal {
587     const char* name;
588     double value;
589     const char* canonical;
590   } float_test_cases[] = {
591       {"negative 0", -0.0, "0.0"},
592       {"0.0001", 0.0001, "0.0"},
593       {"0.0000001", 0.0000001, "0.0"},
594       {"1.0001", 1.0001, "1.0"},
595       {"1.0009", 1.0009, "1.001"},
596       {"round positive odd decimal", 0.0015, "0.002"},
597       {"round positive even decimal", 0.0025, "0.002"},
598       {"round negative odd decimal", -0.0015, "-0.002"},
599       {"round negative even decimal", -0.0025, "-0.002"},
600       {"round decimal up to integer part", 9.9995, "10.0"},
601       {"subnormal numbers", std::numeric_limits<double>::denorm_min(), "0.0"},
602       {"round up to 10 digits", 1e9 - 0.0000001, "1000000000.0"},
603       {"round up to 11 digits", 1e10 - 0.000001, "10000000000.0"},
604       {"round up to 12 digits", 1e11 - 0.00001, "100000000000.0"},
605       {"largest serializable float", nextafter(1e12 - 0.0005, 0),
606        "999999999999.999"},
607       {"largest serializable negative float", -nextafter(1e12 - 0.0005, 0),
608        "-999999999999.999"},
609       // This will fail if we simply truncate the fractional portion.
610       {"float rounds up to next int", 3.9999999, "4.0"},
611       // This will fail if we first round to >3 digits, and then round again to
612       // 3 digits.
613       {"don't double round", 3.99949, "3.999"},
614       // This will fail if we first round to 3 digits, and then round again to
615       // max_avail_digits.
616       {"don't double round", 123456789.99949, "123456789.999"},
617   };
618   for (const auto& test_case : float_test_cases) {
619     SCOPED_TRACE(test_case.name);
620     std::optional<std::string> serialization =
621         SerializeItem(Item(test_case.value));
622     EXPECT_TRUE(serialization.has_value());
623     EXPECT_EQ(*serialization, test_case.canonical);
624   }
625 }
626 
TEST(StructuredHeaderTest,SerializeList)627 TEST(StructuredHeaderTest, SerializeList) {
628   for (const auto& c : list_test_cases) {
629     SCOPED_TRACE(c.name);
630     if (c.expected) {
631       std::optional<std::string> result = SerializeList(*c.expected);
632       EXPECT_TRUE(result.has_value());
633       EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw));
634     }
635   }
636 }
637 
TEST(StructuredHeaderTest,UnserializableLists)638 TEST(StructuredHeaderTest, UnserializableLists) {
639   static const struct UnserializableList {
640     const char* name;
641     const List value;
642   } bad_lists[] = {
643       {"Null item as member", {{Item(), {}}}},
644       {"Unserializable item as member", {{Token("\n"), {}}}},
645       {"Key is empty", {{Token("abc"), {Param("", 1)}}}},
646       {"Key containswhitespace", {{Token("abc"), {Param("a\n", 1)}}}},
647       {"Key contains UTF8", {{Token("abc"), {Param("a\xc3\xa9", 1)}}}},
648       {"Key contains unprintable characters",
649        {{Token("abc"), {Param("a\x7f", 1)}}}},
650       {"Key contains disallowed characters",
651        {{Token("abc"), {Param("a:", 1)}}}},
652       {"Param value is unserializable", {{Token("abc"), {{"a", Token("\n")}}}}},
653       {"Inner list contains unserializable item",
654        {{std::vector<ParameterizedItem>{{Token("\n"), {}}}, {}}}},
655   };
656   for (const auto& bad_list : bad_lists) {
657     SCOPED_TRACE(bad_list.name);
658     std::optional<std::string> serialization = SerializeList(bad_list.value);
659     EXPECT_FALSE(serialization.has_value()) << *serialization;
660   }
661 }
662 
TEST(StructuredHeaderTest,SerializeDictionary)663 TEST(StructuredHeaderTest, SerializeDictionary) {
664   for (const auto& c : dictionary_test_cases) {
665     SCOPED_TRACE(c.name);
666     if (c.expected) {
667       std::optional<std::string> result = SerializeDictionary(*c.expected);
668       EXPECT_TRUE(result.has_value());
669       EXPECT_EQ(result.value(), std::string(c.canonical ? c.canonical : c.raw));
670     }
671   }
672 }
673 
TEST(StructuredHeaderTest,DictionaryConstructors)674 TEST(StructuredHeaderTest, DictionaryConstructors) {
675   const std::string key0 = "key0";
676   const std::string key1 = "key1";
677   const ParameterizedMember member0{Item("Applepie"), {}};
678   const ParameterizedMember member1{Item("hello", Item::kByteSequenceType), {}};
679 
680   Dictionary dict;
681   EXPECT_TRUE(dict.empty());
682   EXPECT_EQ(0U, dict.size());
683   dict[key0] = member0;
684   EXPECT_FALSE(dict.empty());
685   EXPECT_EQ(1U, dict.size());
686 
687   const Dictionary dict_copy = dict;
688   EXPECT_FALSE(dict_copy.empty());
689   EXPECT_EQ(1U, dict_copy.size());
690   EXPECT_EQ(dict, dict_copy);
691 
692   const Dictionary dict_init{{{key0, member0}, {key1, member1}}};
693   EXPECT_FALSE(dict_init.empty());
694   EXPECT_EQ(2U, dict_init.size());
695   EXPECT_EQ(member0, dict_init.at(key0));
696   EXPECT_EQ(member1, dict_init.at(key1));
697 }
698 
TEST(StructuredHeaderTest,DictionaryClear)699 TEST(StructuredHeaderTest, DictionaryClear) {
700   const std::string key0 = "key0";
701   const ParameterizedMember member0{Item("Applepie"), {}};
702 
703   Dictionary dict({{key0, member0}});
704   EXPECT_EQ(1U, dict.size());
705   EXPECT_FALSE(dict.empty());
706   EXPECT_TRUE(dict.contains(key0));
707 
708   dict.clear();
709   EXPECT_EQ(0U, dict.size());
710   EXPECT_TRUE(dict.empty());
711   EXPECT_FALSE(dict.contains(key0));
712 }
713 
TEST(StructuredHeaderTest,DictionaryAccessors)714 TEST(StructuredHeaderTest, DictionaryAccessors) {
715   const std::string key0 = "key0";
716   const std::string key1 = "key1";
717 
718   const ParameterizedMember nonempty_member0{Item("Applepie"), {}};
719   const ParameterizedMember nonempty_member1{
720       Item("hello", Item::kByteSequenceType), {}};
721   const ParameterizedMember empty_member;
722 
723   Dictionary dict{{{key0, nonempty_member0}}};
724   EXPECT_TRUE(dict.contains(key0));
725   EXPECT_EQ(nonempty_member0, dict[key0]);
726   EXPECT_EQ(&dict[key0], &dict.at(key0));
727   EXPECT_EQ(&dict[key0], &dict[0]);
728   EXPECT_EQ(&dict[key0], &dict.at(0));
729 
730   {
731     auto it = dict.find(key0);
732     ASSERT_TRUE(it != dict.end());
733     EXPECT_EQ(it->first, key0);
734     EXPECT_EQ(it->second, nonempty_member0);
735   }
736 
737   // Even if the key does not yet exist in |dict|, operator[]() should
738   // automatically create an empty entry.
739   ASSERT_FALSE(dict.contains(key1));
740   EXPECT_TRUE(dict.find(key1) == dict.end());
741   ParameterizedMember& member1 = dict[key1];
742   EXPECT_TRUE(dict.contains(key1));
743   EXPECT_EQ(empty_member, member1);
744   EXPECT_EQ(&member1, &dict[key1]);
745   EXPECT_EQ(&member1, &dict.at(key1));
746   EXPECT_EQ(&member1, &dict[1]);
747   EXPECT_EQ(&member1, &dict.at(1));
748 
749   member1 = nonempty_member1;
750   EXPECT_EQ(nonempty_member1, dict[key1]);
751   EXPECT_EQ(&dict[key1], &dict.at(key1));
752   EXPECT_EQ(&dict[key1], &dict[1]);
753   EXPECT_EQ(&dict[key1], &dict.at(1));
754 
755   // at(StringPiece) and indexed accessors have const overloads.
756   const Dictionary& dict_ref = dict;
757   EXPECT_EQ(&member1, &dict_ref.at(key1));
758   EXPECT_EQ(&member1, &dict_ref[1]);
759   EXPECT_EQ(&member1, &dict_ref.at(1));
760 }
761 
TEST(StructuredHeaderTest,UnserializableDictionary)762 TEST(StructuredHeaderTest, UnserializableDictionary) {
763   static const struct UnserializableDictionary {
764     const char* name;
765     const Dictionary value;
766   } bad_dictionaries[] = {
767       {"Unserializable dict key", Dictionary{{{"ABC", {Token("abc"), {}}}}}},
768       {"Dictionary item is unserializable",
769        Dictionary{{{"abc", {Token("abc="), {}}}}}},
770       {"Param value is unserializable",
771        Dictionary{{{"abc", {Token("abc"), {{"a", Token("\n")}}}}}}},
772       {"Dictionary inner-list contains unserializable item",
773        Dictionary{
774            {{"abc",
775              {std::vector<ParameterizedItem>{{Token("abc="), {}}}, {}}}}}},
776   };
777   for (const auto& bad_dictionary : bad_dictionaries) {
778     SCOPED_TRACE(bad_dictionary.name);
779     std::optional<std::string> serialization =
780         SerializeDictionary(bad_dictionary.value);
781     EXPECT_FALSE(serialization.has_value()) << *serialization;
782   }
783 }
784 
785 }  // namespace structured_headers
786 }  // namespace quiche
787