blob: 14d8a21cab87c4efbf4f293f7085ca5e4ff04ba4 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/web_package/http_structured_header.h"
#include <string>
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace http_structured_header {
// Test cases are taken from https://github.com/httpwg/structured-header-tests.
TEST(StructuredHeaderTest, ParseItem) {
struct TestCase {
const char* name;
const char* raw;
const char* expected; // nullptr if parse error is expected
} cases[] = {
// Item
{"basic token - item", "a_b-c.d3:f%00/*", "a_b-c.d3:f%00/*"},
{"token with capitals - item", "fooBar", "fooBar"},
{"token starting with capitals - item", "FooBar", "FooBar"},
{"bad token - item", "abc$%!", nullptr},
{"leading whitespace", " foo", "foo"},
{"trailing whitespace", "foo ", "foo"},
// Number
{"basic integer", "42", "42"},
{"zero integer", "0", "0"},
{"comma", "2,3", nullptr},
{"long integer", "9223372036854775807", "9223372036854775807"},
{"too long integer", "9223372036854775808", nullptr},
// Byte Sequence
{"basic binary", "*aGVsbG8=*", "hello"},
{"empty binary", "**", ""},
{"bad paddding", "*aGVsbG8*", "hello"},
{"bad end delimiter", "*aGVsbG8=", nullptr},
{"extra whitespace", "*aGVsb G8=*", nullptr},
{"extra chars", "*aGVsbG!8=*", nullptr},
{"suffix chars", "*aGVsbG8=!*", nullptr},
{"non-zero pad bits", "*iZ==*", "\x89"},
{"non-ASCII binary", "*/+Ah*", "\xFF\xE0!"},
{"base64url binary", "*_-Ah*", nullptr},
// String
{"basic string", "\"foo\"", "foo"},
{"empty string", "\"\"", ""},
{"long string",
"\"foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
"foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
"foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
"foo foo foo foo foo foo foo foo foo foo foo foo foo foo \"",
"foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
"foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
"foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo "
"foo foo foo foo foo foo foo foo foo foo foo foo foo foo "},
{"whitespace string", "\" \"", " "},
{"non-ascii string", "\"f\xC3\xBC\xC3\xBC\"", nullptr},
{"tab in string", "\"\t\"", nullptr},
{"newline in string", "\" \n \"", nullptr},
{"single quoted string", "'foo'", nullptr},
{"unbalanced string", "\"foo", nullptr},
{"string quoting", "\"foo \\\"bar\\\" \\\\ baz\"", "foo \"bar\" \\ baz"},
{"bad string quoting", "\"foo \\,\"", nullptr},
{"ending string quote", "\"foo \\\"", nullptr},
{"abruptly ending string quote", "\"foo \\", nullptr},
};
for (const auto& c : cases) {
base::Optional<std::string> result = ParseItem(c.raw);
if (c.expected) {
EXPECT_TRUE(result.has_value()) << c.name;
EXPECT_EQ(*result, c.expected) << c.name;
} else {
EXPECT_FALSE(result.has_value()) << c.name;
}
}
}
TEST(StructuredHeaderTest, ParseListOfLists) {
struct TestCase {
const char* name;
const char* raw;
ListOfLists expected; // empty if parse error is expected
} cases[] = {
{"basic list of lists", "1;2, 42;43", {{"1", "2"}, {"42", "43"}}},
{"empty list of lists", "", {}},
{"single item list of lists", "42", {{"42"}}},
{"no whitespace list of lists", "1,42", {{"1"}, {"42"}}},
{"no inner whitespace list of lists",
"1;2, 42;43",
{{"1", "2"}, {"42", "43"}}},
{"extra whitespace list of lists", "1 , 42", {{"1"}, {"42"}}},
{"extra inner whitespace list of lists",
"1 ; 2,42 ; 43",
{{"1", "2"}, {"42", "43"}}},
{"trailing comma list of lists", "1;2, 42,", {}},
{"trailing semicolon list of lists", "1;2, 42;43;", {}},
{"leading comma list of lists", ",1;2, 42", {}},
{"leading semicolon list of lists", ";1;2, 42;43", {}},
{"empty item list of lists", "1,,42", {}},
{"empty inner item list of lists", "1;;2,42", {}},
};
for (const auto& c : cases) {
base::Optional<ListOfLists> result = ParseListOfLists(c.raw);
if (!c.expected.empty()) {
EXPECT_TRUE(result.has_value()) << c.name;
EXPECT_EQ(*result, c.expected) << c.name;
} else {
EXPECT_FALSE(result.has_value()) << c.name;
}
}
}
inline bool operator==(const ParameterisedIdentifier& lhs,
const ParameterisedIdentifier& rhs) {
return lhs.identifier == rhs.identifier && lhs.params == rhs.params;
}
TEST(StructuredHeaderTest, ParseParameterisedList) {
struct TestCase {
const char* name;
const char* raw;
ParameterisedList expected; // empty if parse error is expected
} cases[] = {
{"basic param-list",
"abc_123;a=1;b=2; cdef_456, ghi;q=\"9\";r=\"w\"",
{
{"abc_123", {{"a", "1"}, {"b", "2"}, {"cdef_456", ""}}},
{"ghi", {{"q", "9"}, {"r", "w"}}},
}},
{"empty param-list", "", {}},
{"single item param-list",
"text/html;q=1",
{{"text/html", {{"q", "1"}}}}},
{"no whitespace param-list",
"text/html,text/plain;q=1",
{{"text/html", {}}, {"text/plain", {{"q", "1"}}}}},
{"whitespace before = param-list", "text/html, text/plain;q =1", {}},
{"whitespace after = param-list", "text/html, text/plain;q= 1", {}},
{"extra whitespace param-list",
"text/html , text/plain ; q=1",
{{"text/html", {}}, {"text/plain", {{"q", "1"}}}}},
{"duplicate key", "abc;a=1;b=2;a=1", {}},
{"numeric key", "abc;a=1;1b=2;c=1", {}},
{"uppercase key", "abc;a=1;B=2;c=1", {}},
{"bad key", "abc;a=1;b!=2;c=1", {}},
{"another bad key", "abc;a=1;b==2;c=1", {}},
{"empty key name", "abc;a=1;=2;c=1", {}},
{"empty parameter", "abc;a=1;;c=1", {}},
{"empty list item", "abc;a=1,,def;b=1", {}},
{"extra semicolon", "abc;a=1;b=1;", {}},
{"extra comma", "abc;a=1,def;b=1,", {}},
{"leading semicolon", ";abc;a=1", {}},
{"leading comma", ",abc;a=1", {}},
};
for (const auto& c : cases) {
base::Optional<ParameterisedList> result = ParseParameterisedList(c.raw);
if (c.expected.empty()) {
EXPECT_FALSE(result.has_value()) << c.name;
continue;
}
EXPECT_TRUE(result.has_value()) << c.name;
EXPECT_EQ(result->size(), c.expected.size()) << c.name;
if (result->size() == c.expected.size()) {
for (size_t i = 0; i < c.expected.size(); ++i)
EXPECT_EQ((*result)[i], c.expected[i]) << c.name;
}
}
}
} // namespace http_structured_header
} // namespace content