| // 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 "base/containers/contains.h" |
| #include "base/containers/flat_set.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 absl::nullopt if unsuccessful. |
| absl::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 absl::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 |
| absl::optional<HttpNoVarySearchData> 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 absl::nullopt; |
| } |
| |
| // 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. |
| // TODO(crbug.com/1378075) Find a way to communicate that header value |
| // is incorrect. |
| return absl::nullopt; |
| } |
| |
| 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 |
| absl::optional<HttpNoVarySearchData> |
| 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 base::StringPiece 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. |
| // TODO(crbug.com/1378075) Find a way to communicate that keys in the |
| // dictionary are incorrect. |
| if (!base::Contains(kValidKeys, key)) |
| return absl::nullopt; |
| } |
| |
| // 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 absl::nullopt; |
| 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 absl::nullopt; |
| no_vary_params = std::move(*keys); |
| } else if (params.member[0].item.is_boolean()) { |
| vary_by_default = !params.member[0].item.GetBoolean(); |
| } else { |
| return absl::nullopt; |
| } |
| } |
| |
| // 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 || !excepted_params.member_is_inner_list) |
| return absl::nullopt; |
| auto keys = ParseStringList(excepted_params.member); |
| if (!keys.has_value()) |
| return absl::nullopt; |
| 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 absl::nullopt; |
| } |
| |
| 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 no_vary_search; |
| } |
| |
| } // namespace net |