1 // Copyright 2024 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_cookie_indices.h"
6
7 #include <functional>
8
9 #include "base/containers/span.h"
10 #include "base/pickle.h"
11 #include "base/ranges/algorithm.h"
12 #include "crypto/sha2.h"
13 #include "net/cookies/parsed_cookie.h"
14 #include "net/http/http_response_headers.h"
15 #include "net/http/structured_headers.h"
16
17 namespace net {
18
19 namespace {
20 constexpr std::string_view kCookieIndicesHeader = "Cookie-Indices";
21 } // namespace
22
ParseCookieIndices(const HttpResponseHeaders & headers)23 std::optional<std::vector<std::string>> ParseCookieIndices(
24 const HttpResponseHeaders& headers) {
25 std::optional<std::string> normalized_header =
26 headers.GetNormalizedHeader(kCookieIndicesHeader);
27 if (!normalized_header) {
28 return std::nullopt;
29 }
30
31 std::optional<structured_headers::List> list =
32 structured_headers::ParseList(*normalized_header);
33 if (!list.has_value()) {
34 return std::nullopt;
35 }
36
37 std::vector<std::string> cookie_names;
38 cookie_names.reserve(list->size());
39 for (const structured_headers::ParameterizedMember& member : *list) {
40 if (member.member_is_inner_list) {
41 // Inner list not permitted here.
42 return std::nullopt;
43 }
44
45 const structured_headers::ParameterizedItem& item = member.member[0];
46 if (!item.item.is_string()) {
47 // Non-string items are not permitted here.
48 return std::nullopt;
49 }
50
51 // There are basically three sets of requirements that are interesting here.
52 //
53 // 1. Cookie names Chromium considers valid, given by:
54 // cookie-name = *cookie-name-octet
55 // cookie-name-octet = %x20-3A / %x3C / %x3E-7E / %x80-FF
56 // ; octets excluding CTLs, ";", and "="
57 // See |ParsedCookie::IsValidCookieName|.
58 //
59 // 2. Cookie names RFC 6265 considers valid, given by:
60 // cookie-name = token
61 // token = 1*<any CHAR except CTLs or separators>
62 // separators = "(" | ")" | "<" | ">" | "@"
63 // | "," | ";" | ":" | "\" | <">
64 // | "/" | "[" | "]" | "?" | "="
65 // | "{" | "}" | SP | HT
66 // CHAR = <any US-ASCII character (octets 0 - 127)>
67 // CTL = <any US-ASCII control character
68 // (octets 0 - 31) and DEL (127)>
69 //
70 // 3. Valid RFC 8941 structured field strings, whose values are given by:
71 // string-value = *( %x20-7E )
72 //
73 // While all RFC 6265 valid cookie names are valid structured field strings,
74 // Chromium accepts cookies whose names can nonetheless not be spelled here.
75 // For example, cookie names outside 7-bit ASCII cannot be specified.
76 //
77 // Nor is every structured field string a valid cookie name, since it may
78 // contain a ";" or "=" character (or several other characters excluded by
79 // RFC 6265 in addition to Chromium). In the interest of interoperability,
80 // those are expressly rejected.
81 const std::string& name = item.item.GetString();
82 if (name.find_first_of("()<>@,;:\\\"/[]?={} \t") != std::string::npos) {
83 // This is one of those structured field strings that is not a valid
84 // cookie name according to RFC 6265.
85 // TODO(crbug.com/328628231): Watch mnot/I-D#346 to see if a different
86 // behavior is agreed on.
87 continue;
88 }
89 CHECK(ParsedCookie::IsValidCookieName(name))
90 << "invalid cookie name \"" << name << "\"";
91 cookie_names.push_back(name);
92 }
93 return cookie_names;
94 }
95
HashCookieIndices(base::span<const std::string> cookie_indices,base::span<const std::pair<std::string,std::string>> cookies)96 CookieIndicesHash HashCookieIndices(
97 base::span<const std::string> cookie_indices,
98 base::span<const std::pair<std::string, std::string>> cookies) {
99 CHECK(base::ranges::adjacent_find(cookie_indices, std::greater_equal<>()) ==
100 cookie_indices.end())
101 << "cookie indices must be sorted and unique";
102
103 std::vector<std::pair<std::string_view, std::string_view>> cookies_sorted(
104 cookies.begin(), cookies.end());
105 base::ranges::sort(cookies_sorted);
106
107 base::Pickle pickle;
108 auto cookies_it = cookies_sorted.begin();
109 for (const std::string& cookie_name : cookie_indices) {
110 while (cookies_it != cookies_sorted.end() &&
111 cookies_it->first < cookie_name) {
112 ++cookies_it;
113 }
114 while (cookies_it != cookies_sorted.end() &&
115 cookies_it->first == cookie_name) {
116 pickle.WriteBool(true);
117 pickle.WriteString(cookies_it->second);
118 ++cookies_it;
119 }
120 pickle.WriteBool(false);
121 }
122 return crypto::SHA256Hash(pickle.payload_bytes());
123 }
124
125 } // namespace net
126