• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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