blob: bf8b36c50a153265e1f7ae75569b62ee5cf5321c [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/public/cpp/parsed_headers.h"
#include <string>
#include <string_view>
#include <tuple>
#include "base/feature_list.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/expected.h"
#include "net/base/features.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/parsed_headers.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
namespace {
mojom::ParsedHeadersPtr ParseHeaders(std::string_view headers) {
std::string raw_headers = net::HttpUtil::AssembleRawHeaders(headers);
auto parsed = base::MakeRefCounted<net::HttpResponseHeaders>(raw_headers);
return network::PopulateParsedHeaders(parsed.get(), GURL("https://a.com"));
}
TEST(NoVarySearchPrefetchTest, ParsingNVSReturnsDefaultURLVariance) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Set-Cookie: a\r\n"
"Set-Cookie: b\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(parsed_headers->no_vary_search_with_parse_error);
EXPECT_TRUE(
parsed_headers->no_vary_search_with_parse_error->is_parse_error());
EXPECT_EQ(mojom::NoVarySearchParseError::kOk,
parsed_headers->no_vary_search_with_parse_error->get_parse_error());
}
TEST(NoVarySearchPrefetchTest, ParsingNVSReturnsDefaultValue) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Set-Cookie: a\r\n"
"Set-Cookie: b\r\n"
"No-Vary-Search: params=?0\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(parsed_headers->no_vary_search_with_parse_error);
EXPECT_TRUE(
parsed_headers->no_vary_search_with_parse_error->is_parse_error());
EXPECT_EQ(mojom::NoVarySearchParseError::kDefaultValue,
parsed_headers->no_vary_search_with_parse_error->get_parse_error());
}
TEST(NoVarySearchPrefetchTest, ParsingNVSReturnsNotDictionary) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Set-Cookie: a\r\n"
"Set-Cookie: b\r\n"
"No-Vary-Search: (a)\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(parsed_headers->no_vary_search_with_parse_error);
EXPECT_TRUE(
parsed_headers->no_vary_search_with_parse_error->is_parse_error());
EXPECT_EQ(mojom::NoVarySearchParseError::kNotDictionary,
parsed_headers->no_vary_search_with_parse_error->get_parse_error());
}
TEST(NoVarySearchPrefetchTest, ParsingNVSReturnsUnknownDictionaryKey) {
if (base::FeatureList::IsEnabled(
net::features::kNoVarySearchIgnoreUnrecognizedKeys)) {
GTEST_SKIP() << "unrecognized keys are now ignored";
}
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Set-Cookie: a\r\n"
"Set-Cookie: b\r\n"
"No-Vary-Search: a\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(parsed_headers->no_vary_search_with_parse_error);
EXPECT_TRUE(
parsed_headers->no_vary_search_with_parse_error->is_parse_error());
EXPECT_EQ(mojom::NoVarySearchParseError::kUnknownDictionaryKey,
parsed_headers->no_vary_search_with_parse_error->get_parse_error());
}
TEST(NoVarySearchPrefetchTest, ParsingNVSReturnsNonBooleanKeyOrder) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Set-Cookie: a\r\n"
"Set-Cookie: b\r\n"
"No-Vary-Search: key-order=a\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(parsed_headers->no_vary_search_with_parse_error);
EXPECT_TRUE(
parsed_headers->no_vary_search_with_parse_error->is_parse_error());
EXPECT_EQ(mojom::NoVarySearchParseError::kNonBooleanKeyOrder,
parsed_headers->no_vary_search_with_parse_error->get_parse_error());
}
TEST(NoVarySearchPrefetchTest, ParsingNVSReturnsParamsNotStringList) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Set-Cookie: a\r\n"
"Set-Cookie: b\r\n"
"No-Vary-Search: params=a\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(parsed_headers->no_vary_search_with_parse_error);
EXPECT_TRUE(
parsed_headers->no_vary_search_with_parse_error->is_parse_error());
EXPECT_EQ(mojom::NoVarySearchParseError::kParamsNotStringList,
parsed_headers->no_vary_search_with_parse_error->get_parse_error());
}
TEST(NoVarySearchPrefetchTest, ParsingNVSReturnsExceptNotStringList) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Set-Cookie: a\r\n"
"Set-Cookie: b\r\n"
"No-Vary-Search: params, except=a\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(parsed_headers->no_vary_search_with_parse_error);
EXPECT_TRUE(
parsed_headers->no_vary_search_with_parse_error->is_parse_error());
EXPECT_EQ(mojom::NoVarySearchParseError::kExceptNotStringList,
parsed_headers->no_vary_search_with_parse_error->get_parse_error());
}
TEST(NoVarySearchPrefetchTest, ParsingNVSReturnsExceptWithoutTrueParams) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Set-Cookie: a\r\n"
"Set-Cookie: b\r\n"
"No-Vary-Search: except=()\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(parsed_headers->no_vary_search_with_parse_error);
EXPECT_TRUE(
parsed_headers->no_vary_search_with_parse_error->is_parse_error());
EXPECT_EQ(mojom::NoVarySearchParseError::kExceptWithoutTrueParams,
parsed_headers->no_vary_search_with_parse_error->get_parse_error());
}
struct NoVarySearchTestData {
const char* raw_headers;
const std::vector<std::string> expected_no_vary_params;
const std::vector<std::string> expected_vary_params;
const bool expected_vary_on_key_order;
const bool expected_vary_by_default;
};
class NoVarySearchPrefetchTest
: public ::testing::Test,
public ::testing::WithParamInterface<NoVarySearchTestData> {};
TEST_P(NoVarySearchPrefetchTest, ParsingSuccess) {
const auto& test_data = GetParam();
std::string headers =
net::HttpUtil::AssembleRawHeaders(test_data.raw_headers);
auto parsed = base::MakeRefCounted<net::HttpResponseHeaders>(headers);
const auto parsed_headers =
network::PopulateParsedHeaders(parsed.get(), GURL("https://a.com"));
EXPECT_TRUE(parsed_headers);
ASSERT_TRUE(parsed_headers->no_vary_search_with_parse_error);
ASSERT_TRUE(
parsed_headers->no_vary_search_with_parse_error->is_no_vary_search());
auto& no_vary_search =
parsed_headers->no_vary_search_with_parse_error->get_no_vary_search();
ASSERT_TRUE(no_vary_search->search_variance);
if (test_data.expected_vary_by_default) {
EXPECT_THAT(no_vary_search->search_variance->get_no_vary_params(),
test_data.expected_no_vary_params);
} else {
EXPECT_THAT(no_vary_search->search_variance->get_vary_params(),
test_data.expected_vary_params);
}
EXPECT_EQ(no_vary_search->vary_on_key_order,
test_data.expected_vary_on_key_order);
}
NoVarySearchTestData response_headers_tests[] = {
// params set to a list of strings with one element.
{
"HTTP/1.1 200 OK\r\n"
R"(No-Vary-Search: params=("a"))"
"\r\n\r\n", // raw_headers
std::vector<std::string>({"a"}), // expected_no_vary_params
{}, // expected_vary_params
true, // expected_vary_on_key_order
true, // expected_vary_by_default
},
// params set to true.
{
"HTTP/1.1 200 OK\r\n"
"No-Vary-Search: params\r\n\r\n", // raw_headers
{}, // expected_no_vary_params
{}, // expected_vary_params
true, // expected_vary_on_key_order
false, // expected_vary_by_default
},
// Vary on one search param.
{
"HTTP/1.1 200 OK\r\n"
"No-Vary-Search: params\r\n"
R"(No-Vary-Search: except=("a"))"
"\r\n\r\n", // raw_headers
{}, // expected_no_vary_params
std::vector<std::string>({"a"}), // expected_vary_params
true, // expected_vary_on_key_order
false, // expected_vary_by_default
},
// Don't vary on search params order.
{
"HTTP/1.1 200 OK\r\n"
"No-Vary-Search: key-order\r\n\r\n", // raw_headers
{}, // expected_no_vary_params
{}, // expected_vary_params
false, // expected_vary_on_key_order
true, // expected_vary_by_default
},
};
INSTANTIATE_TEST_SUITE_P(NoVarySearchPrefetchTest,
NoVarySearchPrefetchTest,
testing::ValuesIn(response_headers_tests));
TEST(ParseHeadersClientHintsTest, AcceptCHAndClearCHWithoutClearSiteDataTest) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Accept-CH: sec-ch-dpr\r\n"
"Critical-CH: sec-ch-dpr\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_FALSE(
parsed_headers->client_hints_ignored_due_to_clear_site_data_header);
EXPECT_TRUE(parsed_headers->accept_ch);
EXPECT_EQ(parsed_headers->accept_ch->size(), 1u);
EXPECT_EQ(parsed_headers->accept_ch->at(0),
network::mojom::WebClientHintsType::kDpr);
EXPECT_TRUE(parsed_headers->critical_ch);
EXPECT_EQ(parsed_headers->critical_ch->size(), 1u);
EXPECT_EQ(parsed_headers->critical_ch->at(0),
network::mojom::WebClientHintsType::kDpr);
}
TEST(ParseHeadersClientHintsTest,
AcceptCHAndClearCHWithClearSiteDataCacheTest) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Accept-CH: sec-ch-dpr\r\n"
"Critical-CH: sec-ch-dpr\r\n"
"Clear-Site-Data: \"cache\"\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(
parsed_headers->client_hints_ignored_due_to_clear_site_data_header);
EXPECT_FALSE(parsed_headers->accept_ch);
EXPECT_FALSE(parsed_headers->critical_ch);
}
TEST(ParseHeadersClientHintsTest,
AcceptCHAndClearCHWithClearSiteDataClientHintsTest) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Accept-CH: sec-ch-dpr\r\n"
"Critical-CH: sec-ch-dpr\r\n"
"Clear-Site-Data: \"clientHints\"\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(
parsed_headers->client_hints_ignored_due_to_clear_site_data_header);
EXPECT_FALSE(parsed_headers->accept_ch);
EXPECT_FALSE(parsed_headers->critical_ch);
}
TEST(ParseHeadersClientHintsTest,
AcceptCHAndClearCHWithClearSiteDataCookiesTest) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Accept-CH: sec-ch-dpr\r\n"
"Critical-CH: sec-ch-dpr\r\n"
"Clear-Site-Data: \"cookies\"\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(
parsed_headers->client_hints_ignored_due_to_clear_site_data_header);
EXPECT_FALSE(parsed_headers->accept_ch);
EXPECT_FALSE(parsed_headers->critical_ch);
}
TEST(ParseHeadersClientHintsTest,
AcceptCHAndClearCHWithClearSiteDataStorageTest) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Accept-CH: sec-ch-dpr\r\n"
"Critical-CH: sec-ch-dpr\r\n"
"Clear-Site-Data: \"storage\"\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_FALSE(
parsed_headers->client_hints_ignored_due_to_clear_site_data_header);
EXPECT_TRUE(parsed_headers->accept_ch);
EXPECT_EQ(parsed_headers->accept_ch->size(), 1u);
EXPECT_EQ(parsed_headers->accept_ch->at(0),
network::mojom::WebClientHintsType::kDpr);
EXPECT_TRUE(parsed_headers->critical_ch);
EXPECT_EQ(parsed_headers->critical_ch->size(), 1u);
EXPECT_EQ(parsed_headers->critical_ch->at(0),
network::mojom::WebClientHintsType::kDpr);
}
TEST(ParseHeadersClientHintsTest, AcceptCHAndClearCHWithClearSiteDataAllTest) {
const std::string_view& headers =
"HTTP/1.1 200 OK\r\n"
"Accept-CH: sec-ch-dpr\r\n"
"Critical-CH: sec-ch-dpr\r\n"
"Clear-Site-Data: \"*\"\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
EXPECT_TRUE(parsed_headers);
EXPECT_TRUE(
parsed_headers->client_hints_ignored_due_to_clear_site_data_header);
EXPECT_FALSE(parsed_headers->accept_ch);
EXPECT_FALSE(parsed_headers->critical_ch);
}
TEST(ParsedHeadersTest, CookieIndices) {
base::test::ScopedFeatureList enable{features::kCookieIndicesHeader};
const std::string_view headers =
"HTTP/1.1 200 OK\r\n"
"Cookie-Indices: \"logged_in\", \"user_lang\"\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
ASSERT_TRUE(parsed_headers);
EXPECT_THAT(
parsed_headers->cookie_indices,
::testing::Optional(::testing::ElementsAre("logged_in", "user_lang")));
}
TEST(ParsedHeadersTest, IntegrityPolicy) {
base::test::ScopedFeatureList enable{features::kIntegrityPolicyScript};
const std::string_view headers =
"HTTP/1.1 200 OK\r\n"
"Integrity-Policy: blocked-destinations=(script)\r\n"
"Integrity-Policy-Report-Only: blocked-destinations=(script)\r\n\r\n";
const auto parsed_headers = ParseHeaders(headers);
ASSERT_TRUE(parsed_headers);
EXPECT_EQ(parsed_headers->integrity_policy.blocked_destinations.size(), 1u);
EXPECT_EQ(
parsed_headers->integrity_policy_report_only.blocked_destinations.size(),
1u);
}
} // namespace
} // namespace network