naiveproxy/src/net/http/http_no_vary_search_data.cc
2024-10-06 12:16:12 +08:00

228 lines
8.0 KiB
C++

// 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 "net/http/http_no_vary_search_data.h"
#include <string_view>
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/types/expected.h"
#include "net/base/url_search_params.h"
#include "net/base/url_util.h"
#include "net/http/http_response_headers.h"
#include "net/http/structured_headers.h"
#include "url/gurl.h"
namespace net {
namespace {
// Tries to parse a list of ParameterizedItem as a list of strings.
// Returns std::nullopt if unsuccessful.
std::optional<std::vector<std::string>> ParseStringList(
const std::vector<structured_headers::ParameterizedItem>& items) {
std::vector<std::string> keys;
keys.reserve(items.size());
for (const auto& item : items) {
if (!item.item.is_string()) {
return std::nullopt;
}
keys.push_back(UnescapePercentEncodedUrl(item.item.GetString()));
}
return keys;
}
} // namespace
HttpNoVarySearchData::HttpNoVarySearchData() = default;
HttpNoVarySearchData::HttpNoVarySearchData(const HttpNoVarySearchData&) =
default;
HttpNoVarySearchData::HttpNoVarySearchData(HttpNoVarySearchData&&) = default;
HttpNoVarySearchData::~HttpNoVarySearchData() = default;
HttpNoVarySearchData& HttpNoVarySearchData::operator=(
const HttpNoVarySearchData&) = default;
HttpNoVarySearchData& HttpNoVarySearchData::operator=(HttpNoVarySearchData&&) =
default;
bool HttpNoVarySearchData::AreEquivalent(const GURL& a, const GURL& b) const {
// Check urls without query and reference (fragment) for equality first.
GURL::Replacements replacements;
replacements.ClearRef();
replacements.ClearQuery();
if (a.ReplaceComponents(replacements) != b.ReplaceComponents(replacements)) {
return false;
}
// If equal, look at how HttpNoVarySearchData argument affects
// search params variance.
UrlSearchParams a_search_params(a);
UrlSearchParams b_search_params(b);
// Ignore all the query search params that the URL is not varying on.
if (vary_by_default()) {
a_search_params.DeleteAllWithNames(no_vary_params());
b_search_params.DeleteAllWithNames(no_vary_params());
} else {
a_search_params.DeleteAllExceptWithNames(vary_params());
b_search_params.DeleteAllExceptWithNames(vary_params());
}
// Sort the params if the order of the search params in the query
// is ignored.
if (!vary_on_key_order()) {
a_search_params.Sort();
b_search_params.Sort();
}
// Check Search Params for equality
// All search params, in order, need to have the same keys and the same
// values.
return a_search_params.params() == b_search_params.params();
}
// static
HttpNoVarySearchData HttpNoVarySearchData::CreateFromNoVaryParams(
const std::vector<std::string>& no_vary_params,
bool vary_on_key_order) {
HttpNoVarySearchData no_vary_search;
no_vary_search.vary_on_key_order_ = vary_on_key_order;
no_vary_search.no_vary_params_.insert(no_vary_params.cbegin(),
no_vary_params.cend());
return no_vary_search;
}
// static
HttpNoVarySearchData HttpNoVarySearchData::CreateFromVaryParams(
const std::vector<std::string>& vary_params,
bool vary_on_key_order) {
HttpNoVarySearchData no_vary_search;
no_vary_search.vary_on_key_order_ = vary_on_key_order;
no_vary_search.vary_by_default_ = false;
no_vary_search.vary_params_.insert(vary_params.cbegin(), vary_params.cend());
return no_vary_search;
}
// static
base::expected<HttpNoVarySearchData, HttpNoVarySearchData::ParseErrorEnum>
HttpNoVarySearchData::ParseFromHeaders(
const HttpResponseHeaders& response_headers) {
std::string normalized_header;
if (!response_headers.GetNormalizedHeader("No-Vary-Search",
&normalized_header)) {
// This means there is no No-Vary-Search header. Return nullopt.
return base::unexpected(ParseErrorEnum::kOk);
}
// The no-vary-search header is a dictionary type structured field.
const auto dict = structured_headers::ParseDictionary(normalized_header);
if (!dict.has_value()) {
// We don't recognize anything else. So this is an authoring error.
return base::unexpected(ParseErrorEnum::kNotDictionary);
}
return ParseNoVarySearchDictionary(dict.value());
}
const base::flat_set<std::string>& HttpNoVarySearchData::no_vary_params()
const {
return no_vary_params_;
}
const base::flat_set<std::string>& HttpNoVarySearchData::vary_params() const {
return vary_params_;
}
bool HttpNoVarySearchData::vary_on_key_order() const {
return vary_on_key_order_;
}
bool HttpNoVarySearchData::vary_by_default() const {
return vary_by_default_;
}
// static
base::expected<HttpNoVarySearchData, HttpNoVarySearchData::ParseErrorEnum>
HttpNoVarySearchData::ParseNoVarySearchDictionary(
const structured_headers::Dictionary& dict) {
static constexpr const char* kKeyOrder = "key-order";
static constexpr const char* kParams = "params";
static constexpr const char* kExcept = "except";
constexpr std::string_view kValidKeys[] = {kKeyOrder, kParams, kExcept};
base::flat_set<std::string> no_vary_params;
base::flat_set<std::string> vary_params;
bool vary_on_key_order = true;
bool vary_by_default = true;
// If the dictionary contains unknown keys, fail parsing.
for (const auto& [key, value] : dict) {
// We don't recognize any other key. So this is an authoring error.
if (!base::Contains(kValidKeys, key)) {
return base::unexpected(ParseErrorEnum::kUnknownDictionaryKey);
}
}
// Populate `vary_on_key_order` based on the `key-order` key.
if (dict.contains(kKeyOrder)) {
const auto& key_order = dict.at(kKeyOrder);
if (key_order.member_is_inner_list ||
!key_order.member[0].item.is_boolean()) {
return base::unexpected(ParseErrorEnum::kNonBooleanKeyOrder);
}
vary_on_key_order = !key_order.member[0].item.GetBoolean();
}
// Populate `no_vary_params` or `vary_by_default` based on the "params" key.
if (dict.contains(kParams)) {
const auto& params = dict.at(kParams);
if (params.member_is_inner_list) {
auto keys = ParseStringList(params.member);
if (!keys.has_value()) {
return base::unexpected(ParseErrorEnum::kParamsNotStringList);
}
no_vary_params = std::move(*keys);
} else if (params.member[0].item.is_boolean()) {
vary_by_default = !params.member[0].item.GetBoolean();
} else {
return base::unexpected(ParseErrorEnum::kParamsNotStringList);
}
}
// Populate `vary_params` based on the "except" key.
// This should be present only if "params" was true
// (i.e., params don't vary by default).
if (dict.contains(kExcept)) {
const auto& excepted_params = dict.at(kExcept);
if (vary_by_default) {
return base::unexpected(ParseErrorEnum::kExceptWithoutTrueParams);
}
if (!excepted_params.member_is_inner_list) {
return base::unexpected(ParseErrorEnum::kExceptNotStringList);
}
auto keys = ParseStringList(excepted_params.member);
if (!keys.has_value()) {
return base::unexpected(ParseErrorEnum::kExceptNotStringList);
}
vary_params = std::move(*keys);
}
// "params" controls both `vary_by_default` and `no_vary_params`. Check to
// make sure that when "params" is a boolean, `no_vary_params` is empty.
if (!vary_by_default)
DCHECK(no_vary_params.empty());
if (no_vary_params.empty() && vary_params.empty() && vary_by_default &&
vary_on_key_order) {
// If header is present but it's value is equivalent to only default values
// then it is the same as if there were no header present.
return base::unexpected(ParseErrorEnum::kDefaultValue);
}
HttpNoVarySearchData no_vary_search;
no_vary_search.no_vary_params_ = std::move(no_vary_params);
no_vary_search.vary_params_ = std::move(vary_params);
no_vary_search.vary_on_key_order_ = vary_on_key_order;
no_vary_search.vary_by_default_ = vary_by_default;
return base::ok(no_vary_search);
}
} // namespace net