blob: e92b4044a2b8f87f0f97ef044bd03ec2011066be [file] [log] [blame]
// 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