// Copyright (c) 2012 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 "net/http/http_util.h" #include #include #include "base/strings/string_util.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { TEST(HttpUtilTest, IsSafeHeader) { static const char* const unsafe_headers[] = { "sec-", "sEc-", "sec-foo", "sEc-FoO", "proxy-", "pRoXy-", "proxy-foo", "pRoXy-FoO", "accept-charset", "accept-encoding", "access-control-request-headers", "access-control-request-method", "connection", "content-length", "cookie", "cookie2", "date", "dnt", "expect", "host", "keep-alive", "origin", "referer", "set-cookie", "te", "trailer", "transfer-encoding", "upgrade", "user-agent", "via", }; for (const auto* unsafe_header : unsafe_headers) { EXPECT_FALSE(HttpUtil::IsSafeHeader(unsafe_header)) << unsafe_header; EXPECT_FALSE(HttpUtil::IsSafeHeader(base::ToUpperASCII(unsafe_header))) << unsafe_header; } static const char* const safe_headers[] = { "foo", "x-", "x-foo", "content-disposition", "update", "accept-charseta", "accept_charset", "accept-encodinga", "accept_encoding", "access-control-request-headersa", "access-control-request-header", "access_control_request_header", "access-control-request-methoda", "access_control_request_method", "connectiona", "content-lengtha", "content_length", "content-transfer-encoding", "cookiea", "cookie2a", "cookie3", "content-transfer-encodinga", "content_transfer_encoding", "datea", "expecta", "hosta", "keep-alivea", "keep_alive", "origina", "referera", "referrer", "tea", "trailera", "transfer-encodinga", "transfer_encoding", "upgradea", "user-agenta", "user_agent", "viaa", }; for (const auto* safe_header : safe_headers) { EXPECT_TRUE(HttpUtil::IsSafeHeader(safe_header)) << safe_header; EXPECT_TRUE(HttpUtil::IsSafeHeader(base::ToUpperASCII(safe_header))) << safe_header; } } TEST(HttpUtilTest, HeadersIterator) { std::string headers = "foo: 1\t\r\nbar: hello world\r\nbaz: 3 \r\n"; HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n"); ASSERT_TRUE(it.GetNext()); EXPECT_EQ(std::string("foo"), it.name()); EXPECT_EQ(std::string("1"), it.values()); ASSERT_TRUE(it.GetNext()); EXPECT_EQ(std::string("bar"), it.name()); EXPECT_EQ(std::string("hello world"), it.values()); ASSERT_TRUE(it.GetNext()); EXPECT_EQ(std::string("baz"), it.name()); EXPECT_EQ(std::string("3"), it.values()); EXPECT_FALSE(it.GetNext()); } TEST(HttpUtilTest, HeadersIterator_MalformedLine) { std::string headers = "foo: 1\n: 2\n3\nbar: 4"; HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\n"); ASSERT_TRUE(it.GetNext()); EXPECT_EQ(std::string("foo"), it.name()); EXPECT_EQ(std::string("1"), it.values()); ASSERT_TRUE(it.GetNext()); EXPECT_EQ(std::string("bar"), it.name()); EXPECT_EQ(std::string("4"), it.values()); EXPECT_FALSE(it.GetNext()); } TEST(HttpUtilTest, HeadersIterator_MalformedName) { std::string headers = "[ignore me] /: 3\r\n"; HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n"); EXPECT_FALSE(it.GetNext()); } TEST(HttpUtilTest, HeadersIterator_MalformedNameFollowedByValidLine) { std::string headers = "[ignore me] /: 3\r\nbar: 4\n"; HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n"); ASSERT_TRUE(it.GetNext()); EXPECT_EQ(std::string("bar"), it.name()); EXPECT_EQ(std::string("4"), it.values()); EXPECT_FALSE(it.GetNext()); } TEST(HttpUtilTest, HeadersIterator_AdvanceTo) { std::string headers = "foo: 1\r\n: 2\r\n3\r\nbar: 4"; HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n"); EXPECT_TRUE(it.AdvanceTo("foo")); EXPECT_EQ("foo", it.name()); EXPECT_TRUE(it.AdvanceTo("bar")); EXPECT_EQ("bar", it.name()); EXPECT_FALSE(it.AdvanceTo("blat")); EXPECT_FALSE(it.GetNext()); // should be at end of headers } TEST(HttpUtilTest, HeadersIterator_Reset) { std::string headers = "foo: 1\r\n: 2\r\n3\r\nbar: 4"; HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n"); // Search past "foo". EXPECT_TRUE(it.AdvanceTo("bar")); // Now try advancing to "foo". This time it should fail since the iterator // position is past it. EXPECT_FALSE(it.AdvanceTo("foo")); it.Reset(); // Now that we reset the iterator position, we should find 'foo' EXPECT_TRUE(it.AdvanceTo("foo")); } TEST(HttpUtilTest, ValuesIterator) { std::string values = " must-revalidate, no-cache=\"foo, bar\"\t, private "; HttpUtil::ValuesIterator it(values.begin(), values.end(), ',', true /* ignore_empty_values */); ASSERT_TRUE(it.GetNext()); EXPECT_EQ(std::string("must-revalidate"), it.value()); ASSERT_TRUE(it.GetNext()); EXPECT_EQ(std::string("no-cache=\"foo, bar\""), it.value()); ASSERT_TRUE(it.GetNext()); EXPECT_EQ(std::string("private"), it.value()); EXPECT_FALSE(it.GetNext()); } TEST(HttpUtilTest, ValuesIterator_EmptyValues) { std::string values = ", foopy , \t ,,,"; HttpUtil::ValuesIterator it(values.begin(), values.end(), ',', true /* ignore_empty_values */); ASSERT_TRUE(it.GetNext()); EXPECT_EQ(std::string("foopy"), it.value()); EXPECT_FALSE(it.GetNext()); HttpUtil::ValuesIterator it_with_empty_values( values.begin(), values.end(), ',', false /* ignore_empty_values */); ASSERT_TRUE(it_with_empty_values.GetNext()); EXPECT_EQ(std::string(""), it_with_empty_values.value()); ASSERT_TRUE(it_with_empty_values.GetNext()); EXPECT_EQ(std::string("foopy"), it_with_empty_values.value()); ASSERT_TRUE(it_with_empty_values.GetNext()); EXPECT_EQ(std::string(""), it_with_empty_values.value()); ASSERT_TRUE(it_with_empty_values.GetNext()); EXPECT_EQ(std::string(""), it_with_empty_values.value()); ASSERT_TRUE(it_with_empty_values.GetNext()); EXPECT_EQ(std::string(""), it_with_empty_values.value()); ASSERT_TRUE(it_with_empty_values.GetNext()); EXPECT_EQ(std::string(""), it_with_empty_values.value()); EXPECT_FALSE(it_with_empty_values.GetNext()); } TEST(HttpUtilTest, ValuesIterator_Blanks) { std::string values = " \t "; HttpUtil::ValuesIterator it(values.begin(), values.end(), ',', true /* ignore_empty_values */); EXPECT_FALSE(it.GetNext()); HttpUtil::ValuesIterator it_with_empty_values( values.begin(), values.end(), ',', false /* ignore_empty_values */); ASSERT_TRUE(it_with_empty_values.GetNext()); EXPECT_EQ(std::string(""), it_with_empty_values.value()); EXPECT_FALSE(it_with_empty_values.GetNext()); } TEST(HttpUtilTest, Unquote) { // Replace " with ". EXPECT_STREQ("xyz\"abc", HttpUtil::Unquote("\"xyz\\\"abc\"").c_str()); // Replace with EXPECT_STREQ("xyz\\abc", HttpUtil::Unquote("\"xyz\\\\abc\"").c_str()); EXPECT_STREQ("xyz\\\\\\abc", HttpUtil::Unquote("\"xyz\\\\\\\\\\\\abc\"").c_str()); // Replace X with X EXPECT_STREQ("xyzXabc", HttpUtil::Unquote("\"xyz\\Xabc\"").c_str()); // Act as identity function on unquoted inputs. EXPECT_STREQ("X", HttpUtil::Unquote("X").c_str()); EXPECT_STREQ("\"", HttpUtil::Unquote("\"").c_str()); // Allow quotes in the middle of the input. EXPECT_STREQ("foo\"bar", HttpUtil::Unquote("\"foo\"bar\"").c_str()); // Allow the final quote to be escaped. EXPECT_STREQ("foo", HttpUtil::Unquote("\"foo\\\"").c_str()); } TEST(HttpUtilTest, StrictUnquote) { std::string out; // Replace " with ". EXPECT_TRUE(HttpUtil::StrictUnquote("\"xyz\\\"abc\"", &out)); EXPECT_STREQ("xyz\"abc", out.c_str()); // Replace with . EXPECT_TRUE(HttpUtil::StrictUnquote("\"xyz\\\\abc\"", &out)); EXPECT_STREQ("xyz\\abc", out.c_str()); EXPECT_TRUE(HttpUtil::StrictUnquote("\"xyz\\\\\\\\\\\\abc\"", &out)); EXPECT_STREQ("xyz\\\\\\abc", out.c_str()); // Replace X with X. EXPECT_TRUE(HttpUtil::StrictUnquote("\"xyz\\Xabc\"", &out)); EXPECT_STREQ("xyzXabc", out.c_str()); // Empty quoted string. EXPECT_TRUE(HttpUtil::StrictUnquote("\"\"", &out)); EXPECT_STREQ("", out.c_str()); // Return false on unquoted inputs. EXPECT_FALSE(HttpUtil::StrictUnquote("X", &out)); EXPECT_FALSE(HttpUtil::StrictUnquote("", &out)); // Return false on mismatched quotes. EXPECT_FALSE(HttpUtil::StrictUnquote("\"", &out)); EXPECT_FALSE(HttpUtil::StrictUnquote("\"xyz", &out)); EXPECT_FALSE(HttpUtil::StrictUnquote("\"abc'", &out)); // Return false on escaped terminal quote. EXPECT_FALSE(HttpUtil::StrictUnquote("\"abc\\\"", &out)); EXPECT_FALSE(HttpUtil::StrictUnquote("\"\\\"", &out)); // Allow escaped backslash before terminal quote. EXPECT_TRUE(HttpUtil::StrictUnquote("\"\\\\\"", &out)); EXPECT_STREQ("\\", out.c_str()); // Don't allow single quotes to act as quote marks. EXPECT_FALSE(HttpUtil::StrictUnquote("'x\"'", &out)); EXPECT_TRUE(HttpUtil::StrictUnquote("\"x'\"", &out)); EXPECT_STREQ("x'", out.c_str()); EXPECT_FALSE(HttpUtil::StrictUnquote("''", &out)); } TEST(HttpUtilTest, Quote) { EXPECT_STREQ("\"xyz\\\"abc\"", HttpUtil::Quote("xyz\"abc").c_str()); // Replace with EXPECT_STREQ("\"xyz\\\\abc\"", HttpUtil::Quote("xyz\\abc").c_str()); // Replace X with X EXPECT_STREQ("\"xyzXabc\"", HttpUtil::Quote("xyzXabc").c_str()); } TEST(HttpUtilTest, LocateEndOfHeaders) { struct { const char* const input; size_t expected_result; } tests[] = { {"\r\n", std::string::npos}, {"\n", std::string::npos}, {"\r", std::string::npos}, {"foo", std::string::npos}, {"\r\n\r\n", 4}, {"foo\r\nbar\r\n\r\n", 12}, {"foo\nbar\n\n", 9}, {"foo\r\nbar\r\n\r\njunk", 12}, {"foo\nbar\n\njunk", 9}, {"foo\nbar\n\r\njunk", 10}, {"foo\nbar\r\n\njunk", 10}, }; for (const auto& test : tests) { size_t input_len = strlen(test.input); size_t eoh = HttpUtil::LocateEndOfHeaders(test.input, input_len); EXPECT_EQ(test.expected_result, eoh); } } TEST(HttpUtilTest, LocateEndOfAdditionalHeaders) { struct { const char* const input; size_t expected_result; } tests[] = { {"\r\n", 2}, {"\n", 1}, {"\r", std::string::npos}, {"foo", std::string::npos}, {"\r\n\r\n", 2}, {"foo\r\nbar\r\n\r\n", 12}, {"foo\nbar\n\n", 9}, {"foo\r\nbar\r\n\r\njunk", 12}, {"foo\nbar\n\njunk", 9}, {"foo\nbar\n\r\njunk", 10}, {"foo\nbar\r\n\njunk", 10}, }; for (const auto& test : tests) { size_t input_len = strlen(test.input); size_t eoh = HttpUtil::LocateEndOfAdditionalHeaders(test.input, input_len); EXPECT_EQ(test.expected_result, eoh); } } TEST(HttpUtilTest, AssembleRawHeaders) { // clang-format off struct { const char* const input; // with '|' representing '\0' const char* const expected_result; // with '\0' changed to '|' } tests[] = { { "HTTP/1.0 200 OK\r\nFoo: 1\r\nBar: 2\r\n\r\n", "HTTP/1.0 200 OK|Foo: 1|Bar: 2||" }, { "HTTP/1.0 200 OK\nFoo: 1\nBar: 2\n\n", "HTTP/1.0 200 OK|Foo: 1|Bar: 2||" }, // Valid line continuation (single SP). { "HTTP/1.0 200 OK\n" "Foo: 1\n" " continuation\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Foo: 1 continuation|" "Bar: 2||" }, // Valid line continuation (single HT). { "HTTP/1.0 200 OK\n" "Foo: 1\n" "\tcontinuation\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Foo: 1 continuation|" "Bar: 2||" }, // Valid line continuation (multiple SP). { "HTTP/1.0 200 OK\n" "Foo: 1\n" " continuation\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Foo: 1 continuation|" "Bar: 2||" }, // Valid line continuation (multiple HT). { "HTTP/1.0 200 OK\n" "Foo: 1\n" "\t\t\tcontinuation\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Foo: 1 continuation|" "Bar: 2||" }, // Valid line continuation (mixed HT, SP). { "HTTP/1.0 200 OK\n" "Foo: 1\n" " \t \t continuation\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Foo: 1 continuation|" "Bar: 2||" }, // Valid multi-line continuation { "HTTP/1.0 200 OK\n" "Foo: 1\n" " continuation1\n" "\tcontinuation2\n" " continuation3\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Foo: 1 continuation1 continuation2 continuation3|" "Bar: 2||" }, // Continuation of quoted value. // This is different from what Firefox does, since it // will preserve the LWS. { "HTTP/1.0 200 OK\n" "Etag: \"34534-d3\n" " 134q\"\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Etag: \"34534-d3 134q\"|" "Bar: 2||" }, // Valid multi-line continuation, full LWS lines { "HTTP/1.0 200 OK\n" "Foo: 1\n" " \n" "\t\t\t\t\n" "\t continuation\n" "Bar: 2\n\n", // One SP per continued line = 3. "HTTP/1.0 200 OK|" "Foo: 1 continuation|" "Bar: 2||" }, // Valid multi-line continuation, all LWS { "HTTP/1.0 200 OK\n" "Foo: 1\n" " \n" "\t\t\t\t\n" "\t \n" "Bar: 2\n\n", // One SP per continued line = 3. "HTTP/1.0 200 OK|" "Foo: 1 |" "Bar: 2||" }, // Valid line continuation (No value bytes in first line). { "HTTP/1.0 200 OK\n" "Foo:\n" " value\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Foo: value|" "Bar: 2||" }, // Not a line continuation (can't continue status line). { "HTTP/1.0 200 OK\n" " Foo: 1\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" " Foo: 1|" "Bar: 2||" }, // Not a line continuation (can't continue status line). { "HTTP/1.0\n" " 200 OK\n" "Foo: 1\n" "Bar: 2\n\n", "HTTP/1.0|" " 200 OK|" "Foo: 1|" "Bar: 2||" }, // Not a line continuation (can't continue status line). { "HTTP/1.0 404\n" " Not Found\n" "Foo: 1\n" "Bar: 2\n\n", "HTTP/1.0 404|" " Not Found|" "Foo: 1|" "Bar: 2||" }, // Unterminated status line. { "HTTP/1.0 200 OK", "HTTP/1.0 200 OK||" }, // Single terminated, with headers { "HTTP/1.0 200 OK\n" "Foo: 1\n" "Bar: 2\n", "HTTP/1.0 200 OK|" "Foo: 1|" "Bar: 2||" }, // Not terminated, with headers { "HTTP/1.0 200 OK\n" "Foo: 1\n" "Bar: 2", "HTTP/1.0 200 OK|" "Foo: 1|" "Bar: 2||" }, // Not a line continuation (VT) { "HTTP/1.0 200 OK\n" "Foo: 1\n" "\vInvalidContinuation\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Foo: 1|" "\vInvalidContinuation|" "Bar: 2||" }, // Not a line continuation (formfeed) { "HTTP/1.0 200 OK\n" "Foo: 1\n" "\fInvalidContinuation\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Foo: 1|" "\fInvalidContinuation|" "Bar: 2||" }, // Not a line continuation -- can't continue header names. { "HTTP/1.0 200 OK\n" "Serv\n" " er: Apache\n" "\tInvalidContinuation\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Serv|" " er: Apache|" "\tInvalidContinuation|" "Bar: 2||" }, // Not a line continuation -- no value to continue. { "HTTP/1.0 200 OK\n" "Foo: 1\n" "garbage\n" " not-a-continuation\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" "Foo: 1|" "garbage|" " not-a-continuation|" "Bar: 2||", }, // Not a line continuation -- no valid name. { "HTTP/1.0 200 OK\n" ": 1\n" " garbage\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" ": 1|" " garbage|" "Bar: 2||", }, // Not a line continuation -- no valid name (whitespace) { "HTTP/1.0 200 OK\n" " : 1\n" " garbage\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|" " : 1|" " garbage|" "Bar: 2||", }, // Embed NULLs in the status line. They should not be understood // as line separators. { "HTTP/1.0 200 OK|Bar2:0|Baz2:1\r\nFoo: 1\r\nBar: 2\r\n\r\n", "HTTP/1.0 200 OKBar2:0Baz2:1|Foo: 1|Bar: 2||" }, // Embed NULLs in a header line. They should not be understood as // line separators. { "HTTP/1.0 200 OK\nFoo: 1|Foo2: 3\nBar: 2\n\n", "HTTP/1.0 200 OK|Foo: 1Foo2: 3|Bar: 2||" }, // The embedded NUL at the start of the line (before "Blah:") should not be // interpreted as LWS (as that would mistake it for a header line // continuation). { "HTTP/1.0 200 OK\n" "Foo: 1\n" "|Blah: 3\n" "Bar: 2\n\n", "HTTP/1.0 200 OK|Foo: 1|Blah: 3|Bar: 2||" }, }; // clang-format on for (const auto& test : tests) { std::string input = test.input; std::replace(input.begin(), input.end(), '|', '\0'); std::string raw = HttpUtil::AssembleRawHeaders(input); std::replace(raw.begin(), raw.end(), '\0', '|'); EXPECT_EQ(test.expected_result, raw); } } // Test SpecForRequest(). TEST(HttpUtilTest, RequestUrlSanitize) { struct { const char* const url; const char* const expected_spec; } tests[] = { { // Check that #hash is removed. "http://www.google.com:78/foobar?query=1#hash", "http://www.google.com:78/foobar?query=1", }, { // The reference may itself contain # -- strip all of it. "http://192.168.0.1?query=1#hash#10#11#13#14", "http://192.168.0.1/?query=1", }, { // Strip username/password. "http://user:pass@google.com", "http://google.com/", }, { // https scheme "https://www.google.com:78/foobar?query=1#hash", "https://www.google.com:78/foobar?query=1", }, { // WebSocket's ws scheme "ws://www.google.com:78/foobar?query=1#hash", "ws://www.google.com:78/foobar?query=1", }, { // WebSocket's wss scheme "wss://www.google.com:78/foobar?query=1#hash", "wss://www.google.com:78/foobar?query=1", } }; for (size_t i = 0; i < std::size(tests); ++i) { SCOPED_TRACE(i); GURL url(GURL(tests[i].url)); std::string expected_spec(tests[i].expected_spec); EXPECT_EQ(expected_spec, HttpUtil::SpecForRequest(url)); } } TEST(HttpUtilTest, GenerateAcceptLanguageHeader) { std::string header = HttpUtil::GenerateAcceptLanguageHeader(""); EXPECT_TRUE(header.empty()); header = HttpUtil::GenerateAcceptLanguageHeader("es"); EXPECT_EQ(std::string("es"), header); header = HttpUtil::GenerateAcceptLanguageHeader("en-US,fr,de"); EXPECT_EQ(std::string("en-US,fr;q=0.9,de;q=0.8"), header); header = HttpUtil::GenerateAcceptLanguageHeader("en-US,fr,de,ko,zh-CN,ja"); EXPECT_EQ( std::string("en-US,fr;q=0.9,de;q=0.8,ko;q=0.7,zh-CN;q=0.6,ja;q=0.5"), header); } // HttpResponseHeadersTest.GetMimeType also tests ParseContentType. TEST(HttpUtilTest, ParseContentType) { // clang-format off const struct { const char* const content_type; const char* const expected_mime_type; const char* const expected_charset; const bool expected_had_charset; const char* const expected_boundary; } tests[] = { { "text/html", "text/html", "", false, "" }, { "text/html;", "text/html", "", false, "" }, { "text/html; charset=utf-8", "text/html", "utf-8", true, "" }, // Parameter name is "charset ", not "charset". See https://crbug.com/772834. { "text/html; charset =utf-8", "text/html", "", false, "" }, { "text/html; charset= utf-8", "text/html", "utf-8", true, "" }, { "text/html; charset=utf-8 ", "text/html", "utf-8", true, "" }, { "text/html; boundary=\"WebKit-ada-df-dsf-adsfadsfs\"", "text/html", "", false, "WebKit-ada-df-dsf-adsfadsfs" }, // Parameter name is "boundary ", not "boundary". // See https://crbug.com/772834. { "text/html; boundary =\"WebKit-ada-df-dsf-adsfadsfs\"", "text/html", "", false, "" }, // Parameter value includes leading space. See https://crbug.com/772834. { "text/html; boundary= \"WebKit-ada-df-dsf-adsfadsfs\"", "text/html", "", false, "WebKit-ada-df-dsf-adsfadsfs" }, // Parameter value includes leading space. See https://crbug.com/772834. { "text/html; boundary= \"WebKit-ada-df-dsf-adsfadsfs\" ", "text/html", "", false, "WebKit-ada-df-dsf-adsfadsfs" }, { "text/html; boundary=\"WebKit-ada-df-dsf-adsfadsfs \"", "text/html", "", false, "WebKit-ada-df-dsf-adsfadsfs" }, { "text/html; boundary=WebKit-ada-df-dsf-adsfadsfs", "text/html", "", false, "WebKit-ada-df-dsf-adsfadsfs" }, { "text/html; charset", "text/html", "", false, "" }, { "text/html; charset=", "text/html", "", false, "" }, { "text/html; charset= ", "text/html", "", false, "" }, { "text/html; charset= ;", "text/html", "", false, "" }, // Empty quoted strings are allowed. { "text/html; charset=\"\"", "text/html", "", true, "" }, // Leading and trailing whitespace in quotes is trimmed. { "text/html; charset=\" \"", "text/html", "", true, "" }, { "text/html; charset=\" foo \"", "text/html", "foo", true, "" }, // With multiple values, should use the first one. { "text/html; charset=foo; charset=utf-8", "text/html", "foo", true, "" }, { "text/html; charset; charset=; charset=utf-8", "text/html", "utf-8", true, "" }, { "text/html; charset=utf-8; charset=; charset", "text/html", "utf-8", true, "" }, { "text/html; boundary=foo; boundary=bar", "text/html", "", false, "foo" }, // Stray quotes ignored. { "text/html; \"; \"\"; charset=utf-8", "text/html", "utf-8", true, "" }, // Non-leading quotes kept as-is. { "text/html; charset=u\"tf-8\"", "text/html", "u\"tf-8\"", true, "" }, { "text/html; charset=\"utf-8\"", "text/html", "utf-8", true, "" }, // No closing quote. { "text/html; charset=\"utf-8", "text/html", "utf-8", true, "" }, // Check that \ is treated as an escape character. { "text/html; charset=\"\\utf\\-\\8\"", "text/html", "utf-8", true, "" }, // More interseting escape character test - test escaped backslash, escaped // quote, and backslash at end of input in unterminated quoted string. { "text/html; charset=\"\\\\\\\"\\", "text/html", "\\\"\\", true, "" }, // Check quoted semicolon. { "text/html; charset=\";charset=utf-8;\"", "text/html", ";charset=utf-8;", true, "" }, // Unclear if this one should just return utf-8 or not. { "text/html; charset= \"utf-8\"", "text/html", "utf-8", true, "" }, // Regression test for https://crbug.com/772350: // Single quotes are not delimiters but must be treated as part of charset. { "text/html; charset='utf-8'", "text/html", "'utf-8'", true, "" }, // Empty subtype should be accepted. { "text/", "text/", "", false, "" }, // "*/*" is ignored unless it has params, or is not an exact match. { "*/*", "", "", false, "" }, { "*/*; charset=utf-8", "*/*", "utf-8", true, "" }, { "*/* ", "*/*", "", false, "" }, // Regression test for https://crbug.com/1326529 { "teXT/html", "text/html", "", false, ""}, // TODO(abarth): Add more interesting test cases. }; // clang-format on for (const auto& test : tests) { std::string mime_type; std::string charset; bool had_charset = false; std::string boundary; HttpUtil::ParseContentType(test.content_type, &mime_type, &charset, &had_charset, &boundary); EXPECT_EQ(test.expected_mime_type, mime_type) << "content_type=" << test.content_type; EXPECT_EQ(test.expected_charset, charset) << "content_type=" << test.content_type; EXPECT_EQ(test.expected_had_charset, had_charset) << "content_type=" << test.content_type; EXPECT_EQ(test.expected_boundary, boundary) << "content_type=" << test.content_type; } } TEST(HttpUtilTest, ParseContentResetCharset) { std::string mime_type; std::string charset; bool had_charset = false; std::string boundary; // Set mime (capitalization should be ignored), but not charset. HttpUtil::ParseContentType("Text/Html", &mime_type, &charset, &had_charset, &boundary); EXPECT_EQ("text/html", mime_type); EXPECT_EQ("", charset); EXPECT_FALSE(had_charset); // The same mime, add charset. HttpUtil::ParseContentType("tExt/hTml;charset=utf-8", &mime_type, &charset, &had_charset, &boundary); EXPECT_EQ("text/html", mime_type); EXPECT_EQ("utf-8", charset); EXPECT_TRUE(had_charset); // The same mime (different capitalization), but no charset - should not clear // charset. HttpUtil::ParseContentType("teXt/htMl", &mime_type, &charset, &had_charset, &boundary); EXPECT_EQ("text/html", mime_type); EXPECT_EQ("utf-8", charset); EXPECT_TRUE(had_charset); // A different mime will clear charset. HttpUtil::ParseContentType("texT/plaiN", &mime_type, &charset, &had_charset, &boundary); EXPECT_EQ("text/plain", mime_type); EXPECT_EQ("", charset); EXPECT_TRUE(had_charset); } TEST(HttpUtilTest, ParseContentRangeHeader) { const struct { const char* const content_range_header_spec; bool expected_return_value; int64_t expected_first_byte_position; int64_t expected_last_byte_position; int64_t expected_instance_length; } tests[] = { {"", false, -1, -1, -1}, {"megabytes 0-10/50", false, -1, -1, -1}, {"0-10/50", false, -1, -1, -1}, {"Bytes 0-50/51", true, 0, 50, 51}, {"bytes 0-50/51", true, 0, 50, 51}, {"bytes\t0-50/51", false, -1, -1, -1}, {" bytes 0-50/51", true, 0, 50, 51}, {" bytes 0 - 50 \t / \t51", true, 0, 50, 51}, {"bytes 0\t-\t50\t/\t51\t", true, 0, 50, 51}, {" \tbytes\t\t\t 0\t-\t50\t/\t51\t", true, 0, 50, 51}, {"\t bytes \t 0 - 50 / 5 1", false, -1, -1, -1}, {"\t bytes \t 0 - 5 0 / 51", false, -1, -1, -1}, {"bytes 50-0/51", false, -1, -1, -1}, {"bytes * /*", false, -1, -1, -1}, {"bytes * / * ", false, -1, -1, -1}, {"bytes 0-50/*", false, -1, -1, -1}, {"bytes 0-50 / * ", false, -1, -1, -1}, {"bytes 0-10000000000/10000000001", true, 0, 10000000000ll, 10000000001ll}, {"bytes 0-10000000000/10000000000", false, -1, -1, -1}, // 64 bit wraparound. {"bytes 0 - 9223372036854775807 / 100", false, -1, -1, -1}, // 64 bit wraparound. {"bytes 0 - 100 / -9223372036854775808", false, -1, -1, -1}, {"bytes */50", false, -1, -1, -1}, {"bytes 0-50/10", false, -1, -1, -1}, {"bytes 40-50/45", false, -1, -1, -1}, {"bytes 0-50/-10", false, -1, -1, -1}, {"bytes 0-0/1", true, 0, 0, 1}, {"bytes 0-40000000000000000000/40000000000000000001", false, -1, -1, -1}, {"bytes 1-/100", false, -1, -1, -1}, {"bytes -/100", false, -1, -1, -1}, {"bytes -1/100", false, -1, -1, -1}, {"bytes 0-1233/*", false, -1, -1, -1}, {"bytes -123 - -1/100", false, -1, -1, -1}, }; for (const auto& test : tests) { int64_t first_byte_position, last_byte_position, instance_length; EXPECT_EQ(test.expected_return_value, HttpUtil::ParseContentRangeHeaderFor206( test.content_range_header_spec, &first_byte_position, &last_byte_position, &instance_length)) << test.content_range_header_spec; EXPECT_EQ(test.expected_first_byte_position, first_byte_position) << test.content_range_header_spec; EXPECT_EQ(test.expected_last_byte_position, last_byte_position) << test.content_range_header_spec; EXPECT_EQ(test.expected_instance_length, instance_length) << test.content_range_header_spec; } } TEST(HttpUtilTest, ParseRetryAfterHeader) { base::Time::Exploded now_exploded = {2014, 11, 4, 5, 22, 39, 30, 0}; base::Time now; EXPECT_TRUE(base::Time::FromUTCExploded(now_exploded, &now)); base::Time::Exploded later_exploded = {2015, 1, 5, 1, 12, 34, 56, 0}; base::Time later; EXPECT_TRUE(base::Time::FromUTCExploded(later_exploded, &later)); const struct { const char* retry_after_string; bool expected_return_value; base::TimeDelta expected_retry_after; } tests[] = {{"", false, base::TimeDelta()}, {"-3", false, base::TimeDelta()}, {"-2", false, base::TimeDelta()}, {"-1", false, base::TimeDelta()}, {"+0", false, base::TimeDelta()}, {"+1", false, base::TimeDelta()}, {"0", true, base::Seconds(0)}, {"1", true, base::Seconds(1)}, {"2", true, base::Seconds(2)}, {"3", true, base::Seconds(3)}, {"60", true, base::Seconds(60)}, {"3600", true, base::Seconds(3600)}, {"86400", true, base::Seconds(86400)}, {"Thu, 1 Jan 2015 12:34:56 GMT", true, later - now}, {"Mon, 1 Jan 1900 12:34:56 GMT", false, base::TimeDelta()}}; for (size_t i = 0; i < std::size(tests); ++i) { base::TimeDelta retry_after; bool return_value = HttpUtil::ParseRetryAfterHeader( tests[i].retry_after_string, now, &retry_after); EXPECT_EQ(tests[i].expected_return_value, return_value) << "Test case " << i << ": expected " << tests[i].expected_return_value << " but got " << return_value << "."; if (tests[i].expected_return_value && return_value) { EXPECT_EQ(tests[i].expected_retry_after, retry_after) << "Test case " << i << ": expected " << tests[i].expected_retry_after.InSeconds() << "s but got " << retry_after.InSeconds() << "s."; } } } namespace { void CheckCurrentNameValuePair(HttpUtil::NameValuePairsIterator* parser, bool expect_valid, std::string expected_name, std::string expected_value) { ASSERT_EQ(expect_valid, parser->valid()); if (!expect_valid) { return; } // Let's make sure that these never change (i.e., when a quoted value is // unquoted, it should be cached on the first calls and not regenerated // later). std::string::const_iterator first_value_begin = parser->value_begin(); std::string::const_iterator first_value_end = parser->value_end(); ASSERT_EQ(expected_name, std::string(parser->name_begin(), parser->name_end())); ASSERT_EQ(expected_name, parser->name()); ASSERT_EQ(expected_value, std::string(parser->value_begin(), parser->value_end())); ASSERT_EQ(expected_value, parser->value()); // Make sure they didn't/don't change. ASSERT_TRUE(first_value_begin == parser->value_begin()); ASSERT_TRUE(first_value_end == parser->value_end()); } void CheckNextNameValuePair(HttpUtil::NameValuePairsIterator* parser, bool expect_next, bool expect_valid, std::string expected_name, std::string expected_value) { ASSERT_EQ(expect_next, parser->GetNext()); ASSERT_EQ(expect_valid, parser->valid()); if (!expect_next || !expect_valid) { return; } CheckCurrentNameValuePair(parser, expect_valid, expected_name, expected_value); } void CheckInvalidNameValuePair(std::string valid_part, std::string invalid_part) { std::string whole_string = valid_part + invalid_part; HttpUtil::NameValuePairsIterator valid_parser(valid_part.begin(), valid_part.end(), ';'); HttpUtil::NameValuePairsIterator invalid_parser(whole_string.begin(), whole_string.end(), ';'); ASSERT_TRUE(valid_parser.valid()); ASSERT_TRUE(invalid_parser.valid()); // Both parsers should return all the same values until "valid_parser" is // exhausted. while (valid_parser.GetNext()) { ASSERT_TRUE(invalid_parser.GetNext()); ASSERT_TRUE(valid_parser.valid()); ASSERT_TRUE(invalid_parser.valid()); ASSERT_EQ(valid_parser.name(), invalid_parser.name()); ASSERT_EQ(valid_parser.value(), invalid_parser.value()); } // valid_parser is exhausted and remains 'valid' ASSERT_TRUE(valid_parser.valid()); // invalid_parser's corresponding call to GetNext also returns false... ASSERT_FALSE(invalid_parser.GetNext()); // ...but the parser is in an invalid state. ASSERT_FALSE(invalid_parser.valid()); } } // namespace TEST(HttpUtilTest, NameValuePairsIteratorCopyAndAssign) { std::string data = "alpha=\"\\\"a\\\"\"; beta=\" b \"; cappa=\"c;\"; delta=\"d\""; HttpUtil::NameValuePairsIterator parser_a(data.begin(), data.end(), ';'); EXPECT_TRUE(parser_a.valid()); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser_a, true, true, "alpha", "\"a\"")); HttpUtil::NameValuePairsIterator parser_b(parser_a); // a and b now point to same location ASSERT_NO_FATAL_FAILURE( CheckCurrentNameValuePair(&parser_b, true, "alpha", "\"a\"")); ASSERT_NO_FATAL_FAILURE( CheckCurrentNameValuePair(&parser_a, true, "alpha", "\"a\"")); // advance a, no effect on b ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser_a, true, true, "beta", " b ")); ASSERT_NO_FATAL_FAILURE( CheckCurrentNameValuePair(&parser_b, true, "alpha", "\"a\"")); // assign b the current state of a, no effect on a parser_b = parser_a; ASSERT_NO_FATAL_FAILURE( CheckCurrentNameValuePair(&parser_b, true, "beta", " b ")); ASSERT_NO_FATAL_FAILURE( CheckCurrentNameValuePair(&parser_a, true, "beta", " b ")); // advance b, no effect on a ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser_b, true, true, "cappa", "c;")); ASSERT_NO_FATAL_FAILURE( CheckCurrentNameValuePair(&parser_a, true, "beta", " b ")); } TEST(HttpUtilTest, NameValuePairsIteratorEmptyInput) { std::string data; HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';'); EXPECT_TRUE(parser.valid()); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair( &parser, false, true, std::string(), std::string())); } TEST(HttpUtilTest, NameValuePairsIterator) { std::string data = "alpha=1; beta= 2 ;" "cappa =' 3; foo=';" "cappa =\" 3; foo=\";" "delta= \" \\\"4\\\" \"; e= \" '5'\"; e=6;" "f=\"\\\"\\h\\e\\l\\l\\o\\ \\w\\o\\r\\l\\d\\\"\";" "g=\"\"; h=\"hello\""; HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';'); EXPECT_TRUE(parser.valid()); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "alpha", "1")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "beta", "2")); // Single quotes shouldn't be treated as quotes. ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "cappa", "' 3")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "foo", "'")); // But double quotes should be, and can contain semi-colons and equal signs. ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "cappa", " 3; foo=")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "delta", " \"4\" ")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "e", " '5'")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "e", "6")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "f", "\"hello world\"")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "g", std::string())); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "h", "hello")); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair( &parser, false, true, std::string(), std::string())); } TEST(HttpUtilTest, NameValuePairsIteratorOptionalValues) { std::string data = "alpha=1; beta;cappa ; delta; e ; f=1"; // Test that the default parser requires values. HttpUtil::NameValuePairsIterator default_parser(data.begin(), data.end(), ';'); EXPECT_TRUE(default_parser.valid()); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&default_parser, true, true, "alpha", "1")); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(&default_parser, false, false, std::string(), std::string())); HttpUtil::NameValuePairsIterator values_required_parser( data.begin(), data.end(), ';', HttpUtil::NameValuePairsIterator::Values::REQUIRED, HttpUtil::NameValuePairsIterator::Quotes::NOT_STRICT); EXPECT_TRUE(values_required_parser.valid()); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(&values_required_parser, true, true, "alpha", "1")); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair( &values_required_parser, false, false, std::string(), std::string())); HttpUtil::NameValuePairsIterator parser( data.begin(), data.end(), ';', HttpUtil::NameValuePairsIterator::Values::NOT_REQUIRED, HttpUtil::NameValuePairsIterator::Quotes::NOT_STRICT); EXPECT_TRUE(parser.valid()); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "alpha", "1")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "beta", std::string())); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "cappa", std::string())); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "delta", std::string())); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "e", std::string())); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "f", "1")); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(&parser, false, true, std::string(), std::string())); EXPECT_TRUE(parser.valid()); } TEST(HttpUtilTest, NameValuePairsIteratorIllegalInputs) { ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", "; beta")); ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair(std::string(), "beta")); ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", "; \"beta\"=2")); ASSERT_NO_FATAL_FAILURE( CheckInvalidNameValuePair(std::string(), "\"beta\"=2")); ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", ";beta=")); ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", ";beta=;cappa=2")); // According to the spec this is an error, but it doesn't seem appropriate to // change our behaviour to be less permissive at this time. // See NameValuePairsIteratorExtraSeparators test // ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", ";; beta=2")); } // If we are going to support extra separators against the spec, let's just make // sure they work rationally. TEST(HttpUtilTest, NameValuePairsIteratorExtraSeparators) { std::string data = " ; ;;alpha=1; ;; ; beta= 2;cappa=3;;; ; "; HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';'); EXPECT_TRUE(parser.valid()); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "alpha", "1")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "beta", "2")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "cappa", "3")); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair( &parser, false, true, std::string(), std::string())); } // See comments on the implementation of NameValuePairsIterator::GetNext // regarding this derogation from the spec. TEST(HttpUtilTest, NameValuePairsIteratorMissingEndQuote) { std::string data = "name=\"value"; HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';'); EXPECT_TRUE(parser.valid()); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "name", "value")); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair( &parser, false, true, std::string(), std::string())); } TEST(HttpUtilTest, NameValuePairsIteratorStrictQuotesEscapedEndQuote) { std::string data = "foo=bar; name=\"value\\\""; HttpUtil::NameValuePairsIterator parser( data.begin(), data.end(), ';', HttpUtil::NameValuePairsIterator::Values::REQUIRED, HttpUtil::NameValuePairsIterator::Quotes::STRICT_QUOTES); EXPECT_TRUE(parser.valid()); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "foo", "bar")); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(&parser, false, false, std::string(), std::string())); } TEST(HttpUtilTest, NameValuePairsIteratorStrictQuotesQuoteInValue) { std::string data = "foo=\"bar\"; name=\"va\"lue\""; HttpUtil::NameValuePairsIterator parser( data.begin(), data.end(), ';', HttpUtil::NameValuePairsIterator::Values::REQUIRED, HttpUtil::NameValuePairsIterator::Quotes::STRICT_QUOTES); EXPECT_TRUE(parser.valid()); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "foo", "bar")); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(&parser, false, false, std::string(), std::string())); } TEST(HttpUtilTest, NameValuePairsIteratorStrictQuotesMissingEndQuote) { std::string data = "foo=\"bar\"; name=\"value"; HttpUtil::NameValuePairsIterator parser( data.begin(), data.end(), ';', HttpUtil::NameValuePairsIterator::Values::REQUIRED, HttpUtil::NameValuePairsIterator::Quotes::STRICT_QUOTES); EXPECT_TRUE(parser.valid()); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "foo", "bar")); ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(&parser, false, false, std::string(), std::string())); } TEST(HttpUtilTest, NameValuePairsIteratorStrictQuotesSingleQuotes) { std::string data = "foo=\"bar\"; name='value; ok=it'"; HttpUtil::NameValuePairsIterator parser( data.begin(), data.end(), ';', HttpUtil::NameValuePairsIterator::Values::REQUIRED, HttpUtil::NameValuePairsIterator::Quotes::STRICT_QUOTES); EXPECT_TRUE(parser.valid()); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "foo", "bar")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "name", "'value")); ASSERT_NO_FATAL_FAILURE( CheckNextNameValuePair(&parser, true, true, "ok", "it'")); } TEST(HttpUtilTest, HasValidators) { const char* const kMissing = ""; const char* const kEtagEmpty = "\"\""; const char* const kEtagStrong = "\"strong\""; const char* const kEtagWeak = "W/\"weak\""; const char* const kLastModified = "Tue, 15 Nov 1994 12:45:26 GMT"; const char* const kLastModifiedInvalid = "invalid"; const HttpVersion v0_9 = HttpVersion(0, 9); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kMissing, kMissing)); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kEtagStrong, kMissing)); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kEtagWeak, kMissing)); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kEtagEmpty, kMissing)); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kMissing, kLastModified)); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kEtagStrong, kLastModified)); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kEtagWeak, kLastModified)); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kEtagEmpty, kLastModified)); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kMissing, kLastModifiedInvalid)); EXPECT_FALSE( HttpUtil::HasValidators(v0_9, kEtagStrong, kLastModifiedInvalid)); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kEtagWeak, kLastModifiedInvalid)); EXPECT_FALSE(HttpUtil::HasValidators(v0_9, kEtagEmpty, kLastModifiedInvalid)); const HttpVersion v1_0 = HttpVersion(1, 0); EXPECT_FALSE(HttpUtil::HasValidators(v1_0, kMissing, kMissing)); EXPECT_FALSE(HttpUtil::HasValidators(v1_0, kEtagStrong, kMissing)); EXPECT_FALSE(HttpUtil::HasValidators(v1_0, kEtagWeak, kMissing)); EXPECT_FALSE(HttpUtil::HasValidators(v1_0, kEtagEmpty, kMissing)); EXPECT_TRUE(HttpUtil::HasValidators(v1_0, kMissing, kLastModified)); EXPECT_TRUE(HttpUtil::HasValidators(v1_0, kEtagStrong, kLastModified)); EXPECT_TRUE(HttpUtil::HasValidators(v1_0, kEtagWeak, kLastModified)); EXPECT_TRUE(HttpUtil::HasValidators(v1_0, kEtagEmpty, kLastModified)); EXPECT_FALSE(HttpUtil::HasValidators(v1_0, kMissing, kLastModifiedInvalid)); EXPECT_FALSE( HttpUtil::HasValidators(v1_0, kEtagStrong, kLastModifiedInvalid)); EXPECT_FALSE(HttpUtil::HasValidators(v1_0, kEtagWeak, kLastModifiedInvalid)); EXPECT_FALSE(HttpUtil::HasValidators(v1_0, kEtagEmpty, kLastModifiedInvalid)); const HttpVersion v1_1 = HttpVersion(1, 1); EXPECT_FALSE(HttpUtil::HasValidators(v1_1, kMissing, kMissing)); EXPECT_TRUE(HttpUtil::HasValidators(v1_1, kEtagStrong, kMissing)); EXPECT_TRUE(HttpUtil::HasValidators(v1_1, kEtagWeak, kMissing)); EXPECT_TRUE(HttpUtil::HasValidators(v1_1, kEtagEmpty, kMissing)); EXPECT_TRUE(HttpUtil::HasValidators(v1_1, kMissing, kLastModified)); EXPECT_TRUE(HttpUtil::HasValidators(v1_1, kEtagStrong, kLastModified)); EXPECT_TRUE(HttpUtil::HasValidators(v1_1, kEtagWeak, kLastModified)); EXPECT_TRUE(HttpUtil::HasValidators(v1_1, kEtagEmpty, kLastModified)); EXPECT_FALSE(HttpUtil::HasValidators(v1_1, kMissing, kLastModifiedInvalid)); EXPECT_TRUE(HttpUtil::HasValidators(v1_1, kEtagStrong, kLastModifiedInvalid)); EXPECT_TRUE(HttpUtil::HasValidators(v1_1, kEtagWeak, kLastModifiedInvalid)); EXPECT_TRUE(HttpUtil::HasValidators(v1_1, kEtagEmpty, kLastModifiedInvalid)); } TEST(HttpUtilTest, IsValidHeaderValue) { const char* const invalid_values[] = { "X-Requested-With: chrome${NUL}Sec-Unsafe: injected", "X-Requested-With: chrome\r\nSec-Unsafe: injected", "X-Requested-With: chrome\nSec-Unsafe: injected", "X-Requested-With: chrome\rSec-Unsafe: injected", }; for (const std::string& value : invalid_values) { std::string replaced = value; base::ReplaceSubstringsAfterOffset(&replaced, 0, "${NUL}", std::string(1, '\0')); EXPECT_FALSE(HttpUtil::IsValidHeaderValue(replaced)) << replaced; } // Check that all characters permitted by RFC7230 3.2.6 are allowed. std::string allowed = "\t"; for (char c = '\x20'; c < '\x7F'; ++c) { allowed.append(1, c); } for (int c = 0x80; c <= 0xFF; ++c) { allowed.append(1, static_cast(c)); } EXPECT_TRUE(HttpUtil::IsValidHeaderValue(allowed)); } TEST(HttpUtilTest, IsToken) { EXPECT_TRUE(HttpUtil::IsToken("valid")); EXPECT_TRUE(HttpUtil::IsToken("!")); EXPECT_TRUE(HttpUtil::IsToken("~")); EXPECT_FALSE(HttpUtil::IsToken("")); EXPECT_FALSE(HttpUtil::IsToken(base::StringPiece())); EXPECT_FALSE(HttpUtil::IsToken("hello, world")); EXPECT_FALSE(HttpUtil::IsToken(" ")); EXPECT_FALSE(HttpUtil::IsToken(base::StringPiece("\0", 1))); EXPECT_FALSE(HttpUtil::IsToken("\x01")); EXPECT_FALSE(HttpUtil::IsToken("\x7F")); EXPECT_FALSE(HttpUtil::IsToken("\x80")); EXPECT_FALSE(HttpUtil::IsToken("\xff")); } TEST(HttpUtilTest, IsLWS) { EXPECT_FALSE(HttpUtil::IsLWS('\v')); EXPECT_FALSE(HttpUtil::IsLWS('\0')); EXPECT_FALSE(HttpUtil::IsLWS('1')); EXPECT_FALSE(HttpUtil::IsLWS('a')); EXPECT_FALSE(HttpUtil::IsLWS('.')); EXPECT_FALSE(HttpUtil::IsLWS('\n')); EXPECT_FALSE(HttpUtil::IsLWS('\r')); EXPECT_TRUE(HttpUtil::IsLWS('\t')); EXPECT_TRUE(HttpUtil::IsLWS(' ')); } TEST(HttpUtilTest, IsControlChar) { EXPECT_FALSE(HttpUtil::IsControlChar('1')); EXPECT_FALSE(HttpUtil::IsControlChar('a')); EXPECT_FALSE(HttpUtil::IsControlChar('.')); EXPECT_FALSE(HttpUtil::IsControlChar('$')); EXPECT_FALSE(HttpUtil::IsControlChar('\x7E')); EXPECT_FALSE(HttpUtil::IsControlChar('\x80')); EXPECT_FALSE(HttpUtil::IsControlChar('\xFF')); EXPECT_TRUE(HttpUtil::IsControlChar('\0')); EXPECT_TRUE(HttpUtil::IsControlChar('\v')); EXPECT_TRUE(HttpUtil::IsControlChar('\n')); EXPECT_TRUE(HttpUtil::IsControlChar('\r')); EXPECT_TRUE(HttpUtil::IsControlChar('\t')); EXPECT_TRUE(HttpUtil::IsControlChar('\x01')); EXPECT_TRUE(HttpUtil::IsControlChar('\x7F')); } TEST(HttpUtilTest, ParseAcceptEncoding) { const struct { const char* const value; const char* const expected; } tests[] = { {"", "*"}, {"identity;q=1, *;q=0", "identity"}, {"identity", "identity"}, {"FOO, Bar", "bar|foo|identity"}, {"foo; q=1", "foo|identity"}, {"abc, foo; Q=1.0", "abc|foo|identity"}, {"abc, foo;q= 1.00 , bar", "abc|bar|foo|identity"}, {"abc, foo; q=1.000, bar", "abc|bar|foo|identity"}, {"abc, foo ; q = 0 , bar", "abc|bar|identity"}, {"abc, foo; q=0.0, bar", "abc|bar|identity"}, {"abc, foo; q=0.00, bar", "abc|bar|identity"}, {"abc, foo; q=0.000, bar", "abc|bar|identity"}, {"abc, foo; q=0.001, bar", "abc|bar|foo|identity"}, {"gzip", "gzip|identity|x-gzip"}, {"x-gzip", "gzip|identity|x-gzip"}, {"compress", "compress|identity|x-compress"}, {"x-compress", "compress|identity|x-compress"}, {"x-compress", "compress|identity|x-compress"}, {"foo bar", "INVALID"}, {"foo;", "INVALID"}, {"foo;w=1", "INVALID"}, {"foo;q+1", "INVALID"}, {"foo;q=2", "INVALID"}, {"foo;q=1.001", "INVALID"}, {"foo;q=0.", "INVALID"}, {"foo,\"bar\"", "INVALID"}, }; for (const auto& test : tests) { std::string value(test.value); std::string reformatted; std::set allowed_encodings; if (!HttpUtil::ParseAcceptEncoding(value, &allowed_encodings)) { reformatted = "INVALID"; } else { std::vector encodings_list; for (auto const& encoding : allowed_encodings) encodings_list.push_back(encoding); reformatted = base::JoinString(encodings_list, "|"); } EXPECT_STREQ(test.expected, reformatted.c_str()) << "value=\"" << value << "\""; } } TEST(HttpUtilTest, ParseContentEncoding) { const struct { const char* const value; const char* const expected; } tests[] = { {"", ""}, {"identity;q=1, *;q=0", "INVALID"}, {"identity", "identity"}, {"FOO, zergli , Bar", "bar|foo|zergli"}, {"foo, *", "INVALID"}, {"foo,\"bar\"", "INVALID"}, }; for (const auto& test : tests) { std::string value(test.value); std::string reformatted; std::set used_encodings; if (!HttpUtil::ParseContentEncoding(value, &used_encodings)) { reformatted = "INVALID"; } else { std::vector encodings_list; for (auto const& encoding : used_encodings) encodings_list.push_back(encoding); reformatted = base::JoinString(encodings_list, "|"); } EXPECT_STREQ(test.expected, reformatted.c_str()) << "value=\"" << value << "\""; } } // Test the expansion of the Language List. TEST(HttpUtilTest, ExpandLanguageList) { EXPECT_EQ("", HttpUtil::ExpandLanguageList("")); EXPECT_EQ("en-US,en", HttpUtil::ExpandLanguageList("en-US")); EXPECT_EQ("fr", HttpUtil::ExpandLanguageList("fr")); // The base language is added after all regional codes... EXPECT_EQ("en-US,en-CA,en", HttpUtil::ExpandLanguageList("en-US,en-CA")); // ... but before other language families. EXPECT_EQ("en-US,en-CA,en,fr", HttpUtil::ExpandLanguageList("en-US,en-CA,fr")); EXPECT_EQ("en-US,en-CA,en,fr,en-AU", HttpUtil::ExpandLanguageList("en-US,en-CA,fr,en-AU")); EXPECT_EQ("en-US,en-CA,en,fr-CA,fr", HttpUtil::ExpandLanguageList("en-US,en-CA,fr-CA")); // Add a base language even if it's already in the list. EXPECT_EQ("en-US,en,fr-CA,fr,it,es-AR,es,it-IT", HttpUtil::ExpandLanguageList("en-US,fr-CA,it,fr,es-AR,it-IT")); // Trims a whitespace. EXPECT_EQ("en-US,en,fr", HttpUtil::ExpandLanguageList("en-US, fr")); // Do not expand the single character subtag 'x' as a language. EXPECT_EQ("x-private-agreement-subtags", HttpUtil::ExpandLanguageList("x-private-agreement-subtags")); // Do not expand the single character subtag 'i' as a language. EXPECT_EQ("i-klingon", HttpUtil::ExpandLanguageList("i-klingon")); } } // namespace net