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