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