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