| // Copyright 2012 The Chromium Authors |
| // 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 <algorithm> |
| #include <limits> |
| |
| #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", |
| "access-control-request-private-network", |
| "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", |
| // Following 3 headers are safe if there is no forbidden method in values. |
| "x-http-method", |
| "x-http-method-override", |
| "x-method-override", |
| }; |
| 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; |
| } |
| |
| static const char* const disallowed_with_forbidden_methods_headers[] = { |
| "x-http-method", |
| "x-http-method-override", |
| "x-method-override", |
| }; |
| static const struct { |
| const char* value; |
| bool is_safe; |
| } disallowed_values[] = {{"connect", false}, |
| {"trace", false}, |
| {"track", false}, |
| {"CONNECT", false}, |
| {"cOnnEcT", false}, |
| {"get", true}, |
| {"get,post", true}, |
| {"get,connect", false}, |
| {"get, connect", false}, |
| {"get,connect ", false}, |
| {"get,connect ,post", false}, |
| {"get,,,,connect", false}, |
| {"trace,get,PUT", false}}; |
| for (const auto* header : disallowed_with_forbidden_methods_headers) { |
| for (const auto& test_case : disallowed_values) { |
| EXPECT_EQ(test_case.is_safe, |
| HttpUtil::IsSafeHeader(header, test_case.value)) |
| << header << ": " << test_case.value; |
| } |
| } |
| } |
| |
| 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 <backslash> " with ". |
| EXPECT_STREQ("xyz\"abc", HttpUtil::Unquote("\"xyz\\\"abc\"").c_str()); |
| |
| // Replace <backslash> <backslash> with <backslash> |
| EXPECT_STREQ("xyz\\abc", HttpUtil::Unquote("\"xyz\\\\abc\"").c_str()); |
| EXPECT_STREQ("xyz\\\\\\abc", |
| HttpUtil::Unquote("\"xyz\\\\\\\\\\\\abc\"").c_str()); |
| |
| // Replace <backslash> 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 <backslash> " with ". |
| EXPECT_TRUE(HttpUtil::StrictUnquote("\"xyz\\\"abc\"", &out)); |
| EXPECT_STREQ("xyz\"abc", out.c_str()); |
| |
| // Replace <backslash> <backslash> with <backslash>. |
| 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 <backslash> 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 <backslash> <backslash> with <backslash> |
| EXPECT_STREQ("\"xyz\\\\abc\"", HttpUtil::Quote("xyz\\abc").c_str()); |
| |
| // Replace <backslash> 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<char>(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<std::string> allowed_encodings; |
| if (!HttpUtil::ParseAcceptEncoding(value, &allowed_encodings)) { |
| reformatted = "INVALID"; |
| } else { |
| std::vector<std::string> 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<std::string> used_encodings; |
| if (!HttpUtil::ParseContentEncoding(value, &used_encodings)) { |
| reformatted = "INVALID"; |
| } else { |
| std::vector<std::string> 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 |