1 // Copyright 2019 The Chromium Authors. All rights reserved.
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 "third_party/blink/public/common/web_package/web_package_request_matcher.h"
6 
7 #include <algorithm>
8 #include <limits>
9 #include <memory>
10 #include <utility>
11 
12 #include "base/containers/span.h"
13 #include "base/numerics/checked_math.h"
14 #include "base/optional.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "net/base/mime_util.h"
18 #include "net/http/http_request_headers.h"
19 #include "net/http/http_util.h"
20 #include "net/http/structured_headers.h"
21 #include "third_party/blink/public/common/web_package/signed_exchange_consts.h"
22 
23 namespace blink {
24 
25 namespace {
26 
27 constexpr char kIdentity[] = "identity";
28 
29 class ContentNegotiationAlgorithm {
30  public:
31   virtual ~ContentNegotiationAlgorithm() = default;
32   // Returns items from |available_values| that satisfy the request, in
33   // preference order. Each subclass should implement the algorithm defined by
34   // content negotiation mechanism.
35   virtual std::vector<std::string> run(
36       base::span<const std::string> available_values,
37       base::Optional<std::string> request_header_value) = 0;
38 
39  protected:
40   struct WeightedValue {
41     std::string value;
42     double weight;
43 
operator <blink::__anoneb5d6ad00111::ContentNegotiationAlgorithm::WeightedValue44     bool operator<(const WeightedValue& other) const {
45       return weight > other.weight;  // Descending order
46     }
47   };
48 
49   // Parses an Accept (Section 5.3.2 of [RFC7231]), an Accept-Encoding (Section
50   // 5.3.3 of [RFC7231]), or an Accept-Language (Section 5.3.5 of [RFC7231]).
51   // Returns items sorted by descending order of their weight, omitting items
52   // with weight of 0.
ParseRequestHeaderValue(const base::Optional<std::string> & request_header_value)53   std::vector<WeightedValue> ParseRequestHeaderValue(
54       const base::Optional<std::string>& request_header_value) {
55     std::vector<WeightedValue> items;
56     if (!request_header_value)
57       return items;
58 
59     // Value can start with '*', so it cannot be parsed by
60     // net::structured_headers::ParseParameterisedList.
61     net::HttpUtil::ValuesIterator values(request_header_value->begin(),
62                                          request_header_value->end(), ',');
63     while (values.GetNext()) {
64       net::HttpUtil::NameValuePairsIterator name_value_pairs(
65           values.value_begin(), values.value_end(), ';',
66           net::HttpUtil::NameValuePairsIterator::Values::NOT_REQUIRED,
67           net::HttpUtil::NameValuePairsIterator::Quotes::STRICT_QUOTES);
68       if (!name_value_pairs.GetNext())
69         continue;
70       WeightedValue item;
71       item.value = name_value_pairs.name();
72       item.weight = 1.0;
73       while (name_value_pairs.GetNext()) {
74         if (base::LowerCaseEqualsASCII(name_value_pairs.name_piece(), "q")) {
75           if (auto value = GetQValue(name_value_pairs.value()))
76             item.weight = *value;
77         } else {
78           // Parameters except for "q" are included in the output.
79           item.value +=
80               ';' + name_value_pairs.name() + '=' + name_value_pairs.value();
81         }
82       }
83       if (item.weight != 0.0)
84         items.push_back(std::move(item));
85     }
86     std::stable_sort(items.begin(), items.end());
87     return items;
88   }
89 
90  private:
GetQValue(const std::string & str)91   base::Optional<double> GetQValue(const std::string& str) {
92     // TODO(ksakamoto): Validate the syntax per Section 5.3.1 of [RFC7231],
93     // by factoring out the logic in HttpUtil::ParseAcceptEncoding().
94     double val;
95     if (!base::StringToDouble(str, &val))
96       return base::nullopt;
97     if (val < 0.0 || val > 1.0)
98       return base::nullopt;
99     return val;
100   }
101 };
102 
103 // https://httpwg.org/http-extensions/draft-ietf-httpbis-variants.html#content-type
104 class ContentTypeNegotiation final : public ContentNegotiationAlgorithm {
run(base::span<const std::string> available_values,base::Optional<std::string> request_header_value)105   std::vector<std::string> run(
106       base::span<const std::string> available_values,
107       base::Optional<std::string> request_header_value) override {
108     // Step 1. Let preferred-available be an empty list. [spec text]
109     std::vector<std::string> preferred_available;
110 
111     // Step 2. Let preferred-types be a list of the types in the request-value
112     // (or the empty list if request-value is null), ordered by their weight,
113     // highest to lowest, as per Section 5.3.2 of [RFC7231] (omitting any coding
114     // with a weight of 0). If a type lacks an explicit weight, an
115     // implementation MAY assign one.
116     std::vector<WeightedValue> preferred_types =
117         ParseRequestHeaderValue(request_header_value);
118 
119     // Step 3. For each preferred-type in preferred-types: [spec text]
120     for (const WeightedValue& preferred_type : preferred_types) {
121       // 3.1. If any member of available-values matches preferred-type, using
122       // the media-range matching mechanism specified in Section 5.3.2 of
123       // [RFC7231] (which is case-insensitive), append those members of
124       // available-values to preferred-available (preserving the precedence
125       // order implied by the media ranges' specificity).
126       for (const std::string& available : available_values) {
127         if (net::MatchesMimeType(preferred_type.value, available))
128           preferred_available.push_back(available);
129       }
130     }
131 
132     // Step 4. If preferred-available is empty, append the first member of
133     // available-values to preferred-available. This makes the first
134     // available-value the default when none of the client's preferences are
135     // available. [spec text]
136     if (preferred_available.empty() && !available_values.empty())
137       preferred_available.push_back(available_values[0]);
138 
139     // Step 5. Return preferred-available. [spec text]
140     return preferred_available;
141   }
142 };
143 
144 // https://httpwg.org/http-extensions/draft-ietf-httpbis-variants.html#content-encoding
145 class AcceptEncodingNegotiation final : public ContentNegotiationAlgorithm {
run(base::span<const std::string> available_values,base::Optional<std::string> request_header_value)146   std::vector<std::string> run(
147       base::span<const std::string> available_values,
148       base::Optional<std::string> request_header_value) override {
149     // Step 1. Let preferred-available be an empty list. [spec text]
150     std::vector<std::string> preferred_available;
151 
152     // Step 2. Let preferred-codings be a list of the codings in the
153     // request-value (or the empty list if request-value is null), ordered by
154     // their weight, highest to lowest, as per Section 5.3.1 of [RFC7231]
155     // (omitting any coding with a weight of 0). If a coding lacks an explicit
156     // weight, an implementation MAY assign one. [spec text]
157     std::vector<WeightedValue> preferred_codings =
158         ParseRequestHeaderValue(request_header_value);
159 
160     // Step 3. If "identity" is not a member of preferred-codings, append
161     // "identity". [spec text]
162     if (!std::any_of(
163             preferred_codings.begin(), preferred_codings.end(),
164             [](const WeightedValue& p) { return p.value == kIdentity; })) {
165       preferred_codings.push_back({kIdentity, 0.0});
166     }
167 
168     // Step 4. Append "identity" to available-values. [spec text]
169     // Instead, we explicitly check "identity" in Step 5.1 below.
170 
171     // Step 5. For each preferred-coding in preferred-codings: [spec text]
172     for (const WeightedValue& preferred_coding : preferred_codings) {
173       // Step 5.1. If there is a case-insensitive, character-for-character match
174       // for preferred-coding in available-values, append that member of
175       // available-values to preferred-available. [spec text]
176       if (preferred_coding.value == kIdentity) {
177         preferred_available.push_back(kIdentity);
178         continue;
179       }
180       for (const std::string& available : available_values) {
181         if (base::EqualsCaseInsensitiveASCII(preferred_coding.value,
182                                              available)) {
183           preferred_available.push_back(available);
184           break;
185         }
186       }
187     }
188 
189     // Step 6. Return preferred-available. [spec text]
190     return preferred_available;
191   }
192 };
193 
194 // https://httpwg.org/http-extensions/draft-ietf-httpbis-variants.html#content-language
195 class AcceptLanguageNegotiation final : public ContentNegotiationAlgorithm {
196  public:
run(base::span<const std::string> available_values,base::Optional<std::string> request_header_value)197   std::vector<std::string> run(
198       base::span<const std::string> available_values,
199       base::Optional<std::string> request_header_value) override {
200     // Step 1. Let preferred-available be an empty list. [spec text]
201     std::vector<std::string> preferred_available;
202 
203     // Step 2. Let preferred-langs be a list of the language-ranges in the
204     // request-value (or the empty list if request-value is null), ordered by
205     // their weight, highest to lowest, as per Section 5.3.1 of [RFC7231]
206     // (omitting any language-range with a weight of 0). If a language-range
207     // lacks a weight, an implementation MAY assign one. [spec text]
208     std::vector<WeightedValue> preferred_langs =
209         ParseRequestHeaderValue(request_header_value);
210 
211     // Step 3. For each preferred-lang in preferred-langs: [spec text]
212     for (const WeightedValue& preferred_lang : preferred_langs) {
213       // Step 3.1. If any member of available-values matches preferred-lang,
214       // using either the Basic or Extended Filtering scheme defined in
215       // Section 3.3 of [RFC4647], append those members of available-values to
216       // preferred-available (preserving their order). [spec text]
217       AppendMatchedLanguages(available_values, preferred_lang.value,
218                              &preferred_available);
219     }
220 
221     // Step 4. If preferred-available is empty, append the first member of
222     // available-values to preferred-available. This makes the first
223     // available-value the default when none of the client's preferences are
224     // available. [spec text]
225     if (preferred_available.empty() && !available_values.empty())
226       preferred_available.push_back(available_values[0]);
227 
228     // Step 5. Return preferred-available. [spec text]
229     return preferred_available;
230   }
231 
232  private:
233   // Performs the Basic Filtering (Section 3.3.1 of [RFC4647]).
AppendMatchedLanguages(base::span<const std::string> available_values,const std::string & preferred_lang,std::vector<std::string> * output)234   void AppendMatchedLanguages(base::span<const std::string> available_values,
235                               const std::string& preferred_lang,
236                               std::vector<std::string>* output) {
237     if (preferred_lang == "*") {
238       std::copy(available_values.begin(), available_values.end(),
239                 std::back_inserter(*output));
240       return;
241     }
242 
243     const std::string prefix = preferred_lang + '-';
244     for (const std::string& available : available_values) {
245       if (base::EqualsCaseInsensitiveASCII(preferred_lang, available) ||
246           base::StartsWith(available, prefix,
247                            base::CompareCase::INSENSITIVE_ASCII)) {
248         output->push_back(available);
249       }
250     }
251   }
252 };
253 
GetContentNegotiationAlgorithm(const std::string & field_name)254 std::unique_ptr<ContentNegotiationAlgorithm> GetContentNegotiationAlgorithm(
255     const std::string& field_name) {
256   if (field_name == "accept")
257     return std::make_unique<ContentTypeNegotiation>();
258   if (field_name == "accept-encoding")
259     return std::make_unique<AcceptEncodingNegotiation>();
260   if (field_name == "accept-language")
261     return std::make_unique<AcceptLanguageNegotiation>();
262   return nullptr;
263 }
264 
265 // https://tools.ietf.org/id/draft-ietf-httpbis-variants-04.html#variants
266 base::Optional<std::vector<std::pair<std::string, std::vector<std::string>>>>
ParseVariants(const base::StringPiece & str)267 ParseVariants(const base::StringPiece& str) {
268   // Compatibility note: Draft 4 of Variants
269   // (https://tools.ietf.org/id/draft-ietf-httpbis-variants-04.html#variants)
270   // uses a custom format for the Variants-04 header, which this method attempts
271   // to parse as a Structured Headers list-of-lists. This means that quoted
272   // string values as well as unquoted tokens will be accepted by this parser,
273   // which is strictly more lenient than the actual draft spec. Draft 5
274   // (https://tools.ietf.org/id/draft-ietf-httpbis-variants-05.html#variants,
275   // which we don't actually support yet) uses a Structured-Headers-Draft-9
276   // list-of-lists syntax for the Variants-05 header, and explicitly allows both
277   // strings and tokens.
278   // TODO(iclelland): As of October 2019, the latest editor's draft of Variants
279   // (https://httpwg.org/http-extensions/draft-ietf-httpbis-variants.html#variants)
280   // specifies a Structured-Headers-Draft-13 dictionary for the Variants header.
281   // Once the specs are updated, also parse the new Variants dictionary header
282   // as well. The same data structure should be returned.
283   base::Optional<net::structured_headers::ListOfLists> parsed =
284       net::structured_headers::ParseListOfLists(str);
285   if (!parsed)
286     return base::nullopt;
287   std::vector<std::pair<std::string, std::vector<std::string>>> variants;
288   // Each inner-list in the Variants header field value is parsed into a
289   // variant-axis.  The first list-member of the inner-list is interpreted as
290   // the field-name, and the remaining list-members are the available-values.
291   // [spec text]
292   for (const auto& inner_list : *parsed) {
293     auto it = inner_list.begin();
294     // Any list-member that is a token is interpreted as a string containing the
295     // same characters.
296     // [spec text]
297     if (!it->is_string() && !it->is_token())
298       return base::nullopt;
299     std::string field_name = it->GetString();
300     std::vector<std::string> available_values;
301     available_values.reserve(inner_list.size() - 1);
302     for (++it; it != inner_list.end(); ++it) {
303       // Any list-member that is a token is interpreted as a string containing
304       // the same characters.
305       // [spec text]
306       if (!it->is_string() && !it->is_token())
307         return base::nullopt;
308       available_values.push_back(it->GetString());
309     }
310     variants.push_back(std::make_pair(field_name, available_values));
311   }
312   return variants;
313 }
314 
315 // https://tools.ietf.org/id/draft-ietf-httpbis-variants-04.html#variant-key
ParseVariantKey(const base::StringPiece & str,size_t num_variant_axes)316 base::Optional<std::vector<std::vector<std::string>>> ParseVariantKey(
317     const base::StringPiece& str,
318     size_t num_variant_axes) {
319   // Compatibility note: Draft 4 of Variants
320   // (https://tools.ietf.org/id/draft-ietf-httpbis-variants-04.html#variant-key)
321   // uses a custom format for the Variant-Key-04 header, which this method
322   // attempts to parse as a Structured Headers list-of-lists. This means that
323   // quoted string values as well as unquoted tokens will be accepted by this
324   // parser, which is strictly more lenient than the actual draft spec. Draft 5
325   // (https://tools.ietf.org/id/draft-ietf-httpbis-variants-05.html#variant-key,
326   // which we don't actually support yet) uses a Structured-Headers-Draft-9
327   // list-of-lists syntax for the Variant-Key-05 header, and explicitly allows
328   // both strings and tokens.
329   // TODO(iclelland): Once the specs are updated, also parse the new
330   // Variants-Key header as well. The same data structure should be returned.
331   base::Optional<net::structured_headers::ListOfLists> parsed =
332       net::structured_headers::ParseListOfLists(str);
333   if (!parsed)
334     return base::nullopt;
335   std::vector<std::vector<std::string>> variant_keys;
336   variant_keys.reserve(parsed->size());
337   // Each inner-list MUST have the same number of list-members as there are
338   // variant-axes in the representation's Variants header field. Additionally,
339   // every element of each inner-list must be a string. If not, the client MUST
340   // treat the representation as having no Variant-Key header field.
341   // [spec text]
342   for (const auto& inner_list : *parsed) {
343     std::vector<std::string> list_members;
344     list_members.reserve(inner_list.size());
345     if (inner_list.size() != num_variant_axes)
346       return base::nullopt;
347     for (const net::structured_headers::Item& item : inner_list) {
348       if (!item.is_string() && !item.is_token())
349         return base::nullopt;
350       list_members.push_back(item.GetString());
351     }
352     variant_keys.push_back(list_members);
353   }
354   return variant_keys;
355 }
356 
357 // Returns the index of matching entry in Possible Keys [1] which is the cross
358 // product of |sorted_variants|. If there is no matching entry returns nullopt.
359 // Example:
360 //   sorted_variants: [["image/webp","image/jpg"], ["en", "fr", "ja"]]
361 //   variant_key: ["image/jpg", "fr"]
362 //   Possible Keys list for this sorted_variants:
363 //     [["image/webp", "en"], ["image/webp", "fr"], ["image/webp", "ja"],
364 //      ["image/jpg", "en"], ["image/jpg", "fr"], ["image/jpg", "ja"]]
365 //   Result: 4
366 // [1] https://httpwg.org/http-extensions/draft-ietf-httpbis-variants.html#find
GetPossibleKeysIndex(const std::vector<std::vector<std::string>> & sorted_variants,const std::vector<std::string> & variant_key)367 base::Optional<size_t> GetPossibleKeysIndex(
368     const std::vector<std::vector<std::string>>& sorted_variants,
369     const std::vector<std::string>& variant_key) {
370   DCHECK_EQ(variant_key.size(), sorted_variants.size());
371   size_t index = 0;
372   for (size_t i = 0; i < sorted_variants.size(); ++i) {
373     auto found = std::find(sorted_variants[i].begin(), sorted_variants[i].end(),
374                            variant_key[i]);
375     if (found == sorted_variants[i].end())
376       return base::nullopt;
377 
378     index = index * sorted_variants[i].size() +
379             (found - sorted_variants[i].begin());
380   }
381   return index;
382 }
383 
384 }  // namespace
385 
WebPackageRequestMatcher(const net::HttpRequestHeaders & request_headers,const std::string & accept_langs)386 WebPackageRequestMatcher::WebPackageRequestMatcher(
387     const net::HttpRequestHeaders& request_headers,
388     const std::string& accept_langs)
389     : request_headers_(request_headers) {
390   request_headers_.SetHeaderIfMissing(
391       net::HttpRequestHeaders::kAcceptLanguage,
392       net::HttpUtil::GenerateAcceptLanguageHeader(
393           net::HttpUtil::ExpandLanguageList(accept_langs)));
394   // We accept only "mi-sha256-03" as the inner content encoding.
395   // TODO(ksakamoto): Revisit once
396   // https://github.com/WICG/webpackage/issues/390 is settled.
397   request_headers_.SetHeader(net::HttpRequestHeaders::kAcceptEncoding,
398                              "mi-sha256-03");
399 }
400 
MatchRequest(const HeaderMap & response_headers) const401 bool WebPackageRequestMatcher::MatchRequest(
402     const HeaderMap& response_headers) const {
403   return MatchRequest(request_headers_, response_headers);
404 }
405 
406 std::vector<std::string>::const_iterator
FindBestMatchingVariantKey(const std::string & variants,const std::vector<std::string> & variant_key_list) const407 WebPackageRequestMatcher::FindBestMatchingVariantKey(
408     const std::string& variants,
409     const std::vector<std::string>& variant_key_list) const {
410   return FindBestMatchingVariantKey(request_headers_, variants,
411                                     variant_key_list);
412 }
413 
FindBestMatchingIndex(const std::string & variants) const414 base::Optional<size_t> WebPackageRequestMatcher::FindBestMatchingIndex(
415     const std::string& variants) const {
416   return FindBestMatchingIndex(request_headers_, variants);
417 }
418 
419 // Implements "Cache Behaviour" [1] when "stored-responses" is a singleton list
420 // containing a response that has "Variants" header whose value is |variants|.
421 // [1] https://httpwg.org/http-extensions/draft-ietf-httpbis-variants.html#cache
CacheBehavior(const std::vector<std::pair<std::string,std::vector<std::string>>> & variants,const net::HttpRequestHeaders & request_headers)422 std::vector<std::vector<std::string>> WebPackageRequestMatcher::CacheBehavior(
423     const std::vector<std::pair<std::string, std::vector<std::string>>>&
424         variants,
425     const net::HttpRequestHeaders& request_headers) {
426   // Step 1. If stored-responses is empty, return an empty list. [spec text]
427   // The size of stored-responses is always 1.
428 
429   // Step 2. Order stored-responses by the "Date" header field, most recent to
430   // least recent. [spec text]
431   // This is no-op because stored-responses is a single-element list.
432 
433   // Step 3. Let sorted-variants be an empty list. [spec text]
434   std::vector<std::vector<std::string>> sorted_variants;
435 
436   // Step 4. If the freshest member of stored-responses (as per [RFC7234],
437   // Section 4.2) has one or more "Variants" header field(s) that successfully
438   // parse according to Section 2: [spec text]
439 
440   // Step 4.1. Select one member of stored-responses with a "Variants" header
441   // field-value(s) that successfully parses according to Section 2 and let
442   // variants-header be this parsed value. This SHOULD be the most recent
443   // response, but MAY be from an older one as long as it is still fresh.
444   // [spec text]
445   // |variants| is the parsed "Variants" header field value.
446 
447   // Step 4.2. For each variant-axis in variants-header: [spec text]
448   for (const auto& variant_axis : variants) {
449     // Step 4.2.1. If variant-axis' field-name corresponds to the request header
450     // field identified by a content negotiation mechanism that the
451     // implementation supports: [spec text]
452     std::string field_name = base::ToLowerASCII(variant_axis.first);
453     std::unique_ptr<ContentNegotiationAlgorithm> negotiation_algorithm =
454         GetContentNegotiationAlgorithm(field_name);
455     if (negotiation_algorithm) {
456       // Step 4.2.1.1. Let request-value be the field-value associated with
457       // field-name in incoming-request (after being combined as allowed by
458       // Section 3.2.2 of [RFC7230]), or null if field-name is not in
459       // incoming-request. [spec text]
460       base::Optional<std::string> request_value;
461       std::string header_value;
462       if (request_headers.GetHeader(field_name, &header_value))
463         request_value = header_value;
464       // Step 4.2.1.2. Let sorted-values be the result of running the algorithm
465       // defined by the content negotiation mechanism with request-value and
466       // variant-axis' available-values. [spec text]
467       std::vector<std::string> sorted_values =
468           negotiation_algorithm->run(variant_axis.second, request_value);
469 
470       // Step 4.2.1.3. Append sorted-values to sorted-variants. [spec text]
471       sorted_variants.push_back(std::move(sorted_values));
472     }
473   }
474   // At this point, sorted-variants will be a list of lists, each member of the
475   // top-level list corresponding to a variant-axis in the Variants header
476   // field-value, containing zero or more items indicating available-values
477   // that are acceptable to the client, in order of preference, greatest to
478   // least. [spec text]
479 
480   // Step 5. Return result of running Compute Possible Keys (Section 4.1) on
481   // sorted-variants, an empty list and an empty list. [spec text]
482   // Instead of computing the cross product of sorted_variants, this
483   // implementation just returns sorted_variants.
484   return sorted_variants;
485 }
486 
487 // Implements step 3- of
488 // https://wicg.github.io/webpackage/loading.html#request-matching
MatchRequest(const net::HttpRequestHeaders & request_headers,const HeaderMap & response_headers)489 bool WebPackageRequestMatcher::MatchRequest(
490     const net::HttpRequestHeaders& request_headers,
491     const HeaderMap& response_headers) {
492   auto variants_found =
493       response_headers.find(blink::kSignedExchangeVariantsHeader);
494   auto variant_key_found =
495       response_headers.find(blink::kSignedExchangeVariantKeyHeader);
496 
497   // Step 3. If storedExchange's response's header list contains:
498   // - Neither a `Variants` nor a `Variant-Key` header
499   //   Return "match". [spec text]
500   if (variants_found == response_headers.end() &&
501       variant_key_found == response_headers.end()) {
502     return true;
503   }
504   // - A `Variant-Key` header but no `Variants` header
505   //   Return "mismatch". [spec text]
506   if (variants_found == response_headers.end())
507     return false;
508   // - A `Variants` header but no `Variant-Key` header
509   //   Return "mismatch". [spec text]
510   if (variant_key_found == response_headers.end())
511     return false;
512   // - Both a `Variants` and a `Variant-Key` header
513   //   Proceed to the following steps. [spec text]
514 
515   // Step 4. If getting `Variants` from storedExchange's response's header list
516   // returns a value that fails to parse according to the instructions for the
517   // Variants Header Field, return "mismatch". [spec text]
518   auto parsed_variants = ParseVariants(variants_found->second);
519   if (!parsed_variants)
520     return false;
521 
522   // Step 5. Let acceptableVariantKeys be the result of running the Variants
523   // Cache Behavior on an incoming-request of browserRequest and
524   // stored-responses of a list containing storedExchange's response.
525   // [spec text]
526   std::vector<std::vector<std::string>> sorted_variants =
527       CacheBehavior(*parsed_variants, request_headers);
528 
529   // This happens when `Variant` has unknown field names. In such cases,
530   // this algorithm never returns "match", so we do an early return.
531   if (sorted_variants.size() != parsed_variants->size())
532     return false;
533 
534   // Step 6. Let variantKeys be the result of getting `Variant-Key` from
535   // storedExchange's response's header list, and parsing it into a list of
536   // lists as described in Variant-Key Header Field. [spec text]
537   auto parsed_variant_key =
538       ParseVariantKey(variant_key_found->second, parsed_variants->size());
539 
540   // Step 7. If parsing variantKeys failed, return "mismatch". [spec text]
541   if (!parsed_variant_key)
542     return false;
543 
544   // Step 8. If the intersection of acceptableVariantKeys and variantKeys is
545   // empty, return "mismatch". [spec text]
546   // Step 9. Return "match". [spec text]
547 
548   // AcceptableVariantKeys is the cross product of sorted_variants. Instead of
549   // computing AcceptableVariantKeys and taking the intersection of it and
550   // variantKeys, we check its equivalent, i.e.: Return "match" if there is a vk
551   // in variantKeys such that for all i, vk[i] is in sorted_variants[i].
552   for (const std::vector<std::string>& vk : *parsed_variant_key) {
553     DCHECK_EQ(vk.size(), sorted_variants.size());
554     size_t i = 0;
555     for (; i < sorted_variants.size(); ++i) {
556       auto found = std::find(sorted_variants[i].begin(),
557                              sorted_variants[i].end(), vk[i]);
558       if (found == sorted_variants[i].end())
559         break;
560     }
561     if (i == sorted_variants.size())
562       return true;
563   }
564   // Otherwise return "mismatch".
565   return false;
566 }
567 
568 // static
569 std::vector<std::string>::const_iterator
FindBestMatchingVariantKey(const net::HttpRequestHeaders & request_headers,const std::string & variants,const std::vector<std::string> & variant_keys_list)570 WebPackageRequestMatcher::FindBestMatchingVariantKey(
571     const net::HttpRequestHeaders& request_headers,
572     const std::string& variants,
573     const std::vector<std::string>& variant_keys_list) {
574   auto parsed_variants = ParseVariants(variants);
575   if (!parsed_variants)
576     return variant_keys_list.end();
577 
578   std::vector<std::vector<std::string>> sorted_variants =
579       CacheBehavior(*parsed_variants, request_headers);
580   // This happens when `Variant` has unknown field names. In such cases,
581   // this algorithm never returns "match", so we do an early return.
582   if (sorted_variants.size() != parsed_variants->size())
583     return variant_keys_list.end();
584 
585   // Check that the combination count of Possible Keys doesn't overflow.
586   // Currently we have only three ContentNegotiationAlgorithms, so it is
587   // impossible to overflow. But we check to protect for future extension.
588   size_t possible_keys_count = 1;
589   for (const auto& key : sorted_variants) {
590     if (!base::CheckMul(possible_keys_count, key.size())
591              .AssignIfValid(&possible_keys_count)) {
592       return variant_keys_list.end();
593     }
594   }
595 
596   size_t minimum_index = std::numeric_limits<size_t>::max();
597   auto found_variant_key = variant_keys_list.end();
598 
599   for (auto variant_keys_list_it = variant_keys_list.begin();
600        variant_keys_list_it < variant_keys_list.end(); ++variant_keys_list_it) {
601     auto parsed_variant_keys =
602         ParseVariantKey(*variant_keys_list_it, parsed_variants->size());
603     if (!parsed_variant_keys)
604       continue;
605     for (const std::vector<std::string>& variant_key : *parsed_variant_keys) {
606       auto maching_index = GetPossibleKeysIndex(sorted_variants, variant_key);
607       if (maching_index.has_value() && *maching_index < minimum_index) {
608         minimum_index = *maching_index;
609         found_variant_key = variant_keys_list_it;
610       }
611     }
612   }
613   return found_variant_key;
614 }
615 
616 // static
FindBestMatchingIndex(const net::HttpRequestHeaders & request_headers,const std::string & variants)617 base::Optional<size_t> WebPackageRequestMatcher::FindBestMatchingIndex(
618     const net::HttpRequestHeaders& request_headers,
619     const std::string& variants) {
620   auto parsed_variants = ParseVariants(variants);
621   if (!parsed_variants)
622     return base::nullopt;
623 
624   size_t best_match_index = 0;
625   for (const auto& variant_axis : *parsed_variants) {
626     const std::string field_name = base::ToLowerASCII(variant_axis.first);
627     std::unique_ptr<ContentNegotiationAlgorithm> negotiation_algorithm =
628         GetContentNegotiationAlgorithm(field_name);
629     if (!negotiation_algorithm)
630       return base::nullopt;
631     base::Optional<std::string> request_value;
632     std::string header_value;
633     if (request_headers.GetHeader(field_name, &header_value))
634       request_value = header_value;
635 
636     std::vector<std::string> sorted_values =
637         negotiation_algorithm->run(variant_axis.second, request_value);
638     if (sorted_values.empty())
639       return base::nullopt;
640     auto it = std::find(variant_axis.second.begin(), variant_axis.second.end(),
641                         sorted_values.front());
642     if (it == variant_axis.second.end())
643       return base::nullopt;
644     size_t best_value_index = it - variant_axis.second.begin();
645 
646     if (!base::CheckMul(best_match_index, variant_axis.second.size())
647              .AssignIfValid(&best_match_index)) {
648       return base::nullopt;
649     }
650     if (!base::CheckAdd(best_match_index, best_value_index)
651              .AssignIfValid(&best_match_index)) {
652       return base::nullopt;
653     }
654   }
655   return best_match_index;
656 }
657 
658 }  // namespace blink
659