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