1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/http/http_no_vary_search_data.h"
6
7 #include "base/containers/contains.h"
8 #include "base/containers/flat_set.h"
9 #include "base/types/expected.h"
10 #include "net/base/url_search_params.h"
11 #include "net/base/url_util.h"
12 #include "net/http/http_response_headers.h"
13 #include "net/http/structured_headers.h"
14 #include "url/gurl.h"
15
16 namespace net {
17
18 namespace {
19 // Tries to parse a list of ParameterizedItem as a list of strings.
20 // Returns absl::nullopt if unsuccessful.
ParseStringList(const std::vector<structured_headers::ParameterizedItem> & items)21 absl::optional<std::vector<std::string>> ParseStringList(
22 const std::vector<structured_headers::ParameterizedItem>& items) {
23 std::vector<std::string> keys;
24 keys.reserve(items.size());
25 for (const auto& item : items) {
26 if (!item.item.is_string()) {
27 return absl::nullopt;
28 }
29 keys.push_back(UnescapePercentEncodedUrl(item.item.GetString()));
30 }
31 return keys;
32 }
33
34 } // namespace
35
36 HttpNoVarySearchData::HttpNoVarySearchData() = default;
37 HttpNoVarySearchData::HttpNoVarySearchData(const HttpNoVarySearchData&) =
38 default;
39 HttpNoVarySearchData::HttpNoVarySearchData(HttpNoVarySearchData&&) = default;
40 HttpNoVarySearchData::~HttpNoVarySearchData() = default;
41 HttpNoVarySearchData& HttpNoVarySearchData::operator=(
42 const HttpNoVarySearchData&) = default;
43 HttpNoVarySearchData& HttpNoVarySearchData::operator=(HttpNoVarySearchData&&) =
44 default;
45
AreEquivalent(const GURL & a,const GURL & b) const46 bool HttpNoVarySearchData::AreEquivalent(const GURL& a, const GURL& b) const {
47 // Check urls without query and reference (fragment) for equality first.
48 GURL::Replacements replacements;
49 replacements.ClearRef();
50 replacements.ClearQuery();
51 if (a.ReplaceComponents(replacements) != b.ReplaceComponents(replacements)) {
52 return false;
53 }
54
55 // If equal, look at how HttpNoVarySearchData argument affects
56 // search params variance.
57 UrlSearchParams a_search_params(a);
58 UrlSearchParams b_search_params(b);
59 // Ignore all the query search params that the URL is not varying on.
60 if (vary_by_default()) {
61 a_search_params.DeleteAllWithNames(no_vary_params());
62 b_search_params.DeleteAllWithNames(no_vary_params());
63 } else {
64 a_search_params.DeleteAllExceptWithNames(vary_params());
65 b_search_params.DeleteAllExceptWithNames(vary_params());
66 }
67 // Sort the params if the order of the search params in the query
68 // is ignored.
69 if (!vary_on_key_order()) {
70 a_search_params.Sort();
71 b_search_params.Sort();
72 }
73 // Check Search Params for equality
74 // All search params, in order, need to have the same keys and the same
75 // values.
76 return a_search_params.params() == b_search_params.params();
77 }
78
79 // static
CreateFromNoVaryParams(const std::vector<std::string> & no_vary_params,bool vary_on_key_order)80 HttpNoVarySearchData HttpNoVarySearchData::CreateFromNoVaryParams(
81 const std::vector<std::string>& no_vary_params,
82 bool vary_on_key_order) {
83 HttpNoVarySearchData no_vary_search;
84 no_vary_search.vary_on_key_order_ = vary_on_key_order;
85 no_vary_search.no_vary_params_.insert(no_vary_params.cbegin(),
86 no_vary_params.cend());
87 return no_vary_search;
88 }
89
90 // static
CreateFromVaryParams(const std::vector<std::string> & vary_params,bool vary_on_key_order)91 HttpNoVarySearchData HttpNoVarySearchData::CreateFromVaryParams(
92 const std::vector<std::string>& vary_params,
93 bool vary_on_key_order) {
94 HttpNoVarySearchData no_vary_search;
95 no_vary_search.vary_on_key_order_ = vary_on_key_order;
96 no_vary_search.vary_by_default_ = false;
97 no_vary_search.vary_params_.insert(vary_params.cbegin(), vary_params.cend());
98 return no_vary_search;
99 }
100
101 // static
102 base::expected<HttpNoVarySearchData, HttpNoVarySearchData::ParseErrorEnum>
ParseFromHeaders(const HttpResponseHeaders & response_headers)103 HttpNoVarySearchData::ParseFromHeaders(
104 const HttpResponseHeaders& response_headers) {
105 std::string normalized_header;
106 if (!response_headers.GetNormalizedHeader("No-Vary-Search",
107 &normalized_header)) {
108 // This means there is no No-Vary-Search header. Return nullopt.
109 return base::unexpected(ParseErrorEnum::kOk);
110 }
111
112 // The no-vary-search header is a dictionary type structured field.
113 const auto dict = structured_headers::ParseDictionary(normalized_header);
114 if (!dict.has_value()) {
115 // We don't recognize anything else. So this is an authoring error.
116 return base::unexpected(ParseErrorEnum::kNotDictionary);
117 }
118
119 return ParseNoVarySearchDictionary(dict.value());
120 }
121
no_vary_params() const122 const base::flat_set<std::string>& HttpNoVarySearchData::no_vary_params()
123 const {
124 return no_vary_params_;
125 }
126
vary_params() const127 const base::flat_set<std::string>& HttpNoVarySearchData::vary_params() const {
128 return vary_params_;
129 }
130
vary_on_key_order() const131 bool HttpNoVarySearchData::vary_on_key_order() const {
132 return vary_on_key_order_;
133 }
vary_by_default() const134 bool HttpNoVarySearchData::vary_by_default() const {
135 return vary_by_default_;
136 }
137
138 // static
139 base::expected<HttpNoVarySearchData, HttpNoVarySearchData::ParseErrorEnum>
ParseNoVarySearchDictionary(const structured_headers::Dictionary & dict)140 HttpNoVarySearchData::ParseNoVarySearchDictionary(
141 const structured_headers::Dictionary& dict) {
142 static constexpr const char* kKeyOrder = "key-order";
143 static constexpr const char* kParams = "params";
144 static constexpr const char* kExcept = "except";
145 constexpr base::StringPiece kValidKeys[] = {kKeyOrder, kParams, kExcept};
146
147 base::flat_set<std::string> no_vary_params;
148 base::flat_set<std::string> vary_params;
149 bool vary_on_key_order = true;
150 bool vary_by_default = true;
151
152 // If the dictionary contains unknown keys, fail parsing.
153 for (const auto& [key, value] : dict) {
154 // We don't recognize any other key. So this is an authoring error.
155 if (!base::Contains(kValidKeys, key)) {
156 return base::unexpected(ParseErrorEnum::kUnknownDictionaryKey);
157 }
158 }
159
160 // Populate `vary_on_key_order` based on the `key-order` key.
161 if (dict.contains(kKeyOrder)) {
162 const auto& key_order = dict.at(kKeyOrder);
163 if (key_order.member_is_inner_list ||
164 !key_order.member[0].item.is_boolean()) {
165 return base::unexpected(ParseErrorEnum::kNonBooleanKeyOrder);
166 }
167 vary_on_key_order = !key_order.member[0].item.GetBoolean();
168 }
169
170 // Populate `no_vary_params` or `vary_by_default` based on the "params" key.
171 if (dict.contains(kParams)) {
172 const auto& params = dict.at(kParams);
173 if (params.member_is_inner_list) {
174 auto keys = ParseStringList(params.member);
175 if (!keys.has_value()) {
176 return base::unexpected(ParseErrorEnum::kParamsNotStringList);
177 }
178 no_vary_params = std::move(*keys);
179 } else if (params.member[0].item.is_boolean()) {
180 vary_by_default = !params.member[0].item.GetBoolean();
181 } else {
182 return base::unexpected(ParseErrorEnum::kParamsNotStringList);
183 }
184 }
185
186 // Populate `vary_params` based on the "except" key.
187 // This should be present only if "params" was true
188 // (i.e., params don't vary by default).
189 if (dict.contains(kExcept)) {
190 const auto& excepted_params = dict.at(kExcept);
191 if (vary_by_default) {
192 return base::unexpected(ParseErrorEnum::kExceptWithoutTrueParams);
193 }
194 if (!excepted_params.member_is_inner_list) {
195 return base::unexpected(ParseErrorEnum::kExceptNotStringList);
196 }
197 auto keys = ParseStringList(excepted_params.member);
198 if (!keys.has_value()) {
199 return base::unexpected(ParseErrorEnum::kExceptNotStringList);
200 }
201 vary_params = std::move(*keys);
202 }
203
204 // "params" controls both `vary_by_default` and `no_vary_params`. Check to
205 // make sure that when "params" is a boolean, `no_vary_params` is empty.
206 if (!vary_by_default)
207 DCHECK(no_vary_params.empty());
208
209 if (no_vary_params.empty() && vary_params.empty() && vary_by_default &&
210 vary_on_key_order) {
211 // If header is present but it's value is equivalent to only default values
212 // then it is the same as if there were no header present.
213 return base::unexpected(ParseErrorEnum::kDefaultValue);
214 }
215
216 HttpNoVarySearchData no_vary_search;
217 no_vary_search.no_vary_params_ = std::move(no_vary_params);
218 no_vary_search.vary_params_ = std::move(vary_params);
219 no_vary_search.vary_on_key_order_ = vary_on_key_order;
220 no_vary_search.vary_by_default_ = vary_by_default;
221
222 return base::ok(no_vary_search);
223 }
224
225 } // namespace net
226