1746 lines
54 KiB
C++
1746 lines
54 KiB
C++
// 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
|