xref: /aosp_15_r20/external/cronet/net/http/http_no_vary_search_data.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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