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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/browsing_topics/header_util.h"
#include "base/strings/strcat.h"
#include "components/browsing_topics/common/common_types.h"
#include "components/browsing_topics/common/semantic_tree.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/page.h"
#include "content/public/common/content_client.h"
#include "net/http/http_request_headers.h"
#include "net/http/structured_headers.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
namespace content {
namespace {
// The max number of digits in a topic. As new taxonomies are introduced and old
// topics are expired, the expectation is this value will gradually grow.
constexpr int kTopicMaxLength = 3;
// The number of characters in a version string, e.g., chrome.1:1:10. This will
// grow as versions require more digits.
constexpr int kVersionMaxLength = 13;
static_assert(browsing_topics::ConfigVersion::kMaxValue < 10,
"Topics config version should not exceed 1 digit, or "
"`kVersionMaxLength` should be updated accordingly.");
static_assert(blink::features::kBrowsingTopicsTaxonomyVersionDefault < 10,
"Topics taxonomy version should not exceed 1 digit, or "
"`kVersionMaxLength` should be updated accordingly.");
static_assert(browsing_topics::SemanticTree::kNumTopics < 1000,
"Total number of topics (i.e. max topic ID) should not exceed 3 "
"digits, or `kTopicMaxLength` should be updated accordingly.");
} // namespace
const char kBrowsingTopicsRequestHeaderKey[] = "Sec-Browsing-Topics";
std::string DeriveTopicsHeaderValue(
const std::vector<blink::mojom::EpochTopicPtr>& topics,
int num_versions_in_epochs) {
net::structured_headers::List header_list;
std::optional<std::string> last_version;
std::vector<net::structured_headers::ParameterizedItem> cur_topics;
// Build up the header without the padding parameter.
for (auto& topic : topics) {
bool new_version =
(!last_version.has_value() || last_version.value() != topic->version);
if (new_version) {
if (cur_topics.size() > 0) {
CHECK(last_version.has_value());
header_list.push_back(net::structured_headers::ParameterizedMember(
cur_topics,
{{"v",
net::structured_headers::Item(
*last_version, net::structured_headers::Item::kTokenType)}}));
cur_topics.clear();
}
last_version = topic->version;
}
cur_topics.push_back(net::structured_headers::ParameterizedItem(
net::structured_headers::Item(static_cast<int64_t>(topic->topic)), {}));
}
if (cur_topics.size() > 0) {
CHECK(last_version.has_value());
header_list.push_back(net::structured_headers::ParameterizedMember(
cur_topics, {{"v", net::structured_headers::Item(
*last_version,
net::structured_headers::Item::kTokenType)}}));
}
// The header is now complete, except for padding. We want the header to be of
// fixed size for the given number of versions in the list, so we add padding
// to make that happen.
// When adding padding, we'll always have at least 1 version.
if (num_versions_in_epochs == 0) {
num_versions_in_epochs = 1;
}
// The number of topics that should be in the padded response.
int max_number_of_epochs =
blink::features::kBrowsingTopicsNumberOfEpochsToExpose.Get();
CHECK_LE(num_versions_in_epochs, max_number_of_epochs);
CHECK_GT(max_number_of_epochs, 0);
// The padded length of the header given the number of versions.
// Example header: Sec-Browsing-Topics: (100 200);v=chrome.1:1:2,
// (300);v=chrome.1:1:4, ();p=P00
int max_length =
max_number_of_epochs * kTopicMaxLength + // length of three topics
max_number_of_epochs -
num_versions_in_epochs + // spaces between topics in a list
num_versions_in_epochs * 5 + // '();v='
num_versions_in_epochs *
kVersionMaxLength + // max length of the versions
(num_versions_in_epochs - 1) * 2; // the ', ' between topic lists
// Add the bytes for the ", " between the last list and the padding list in
// the event that there are no topics.
if (header_list.size() == 0) {
max_length += 2;
}
// How many bytes of padding do we need to add?
int padding_needed =
header_list.size() > 0
? max_length -
net::structured_headers::SerializeList(header_list)->length()
: max_length;
// The padding should generally be >= 0. It can be negative in certain
// circumstances and we need to handle that here. It can be negative if a new
// version is rolled out via finch (e.g., model or taxonomy) that uses an
// extra digit in its number but the binary hasn't been updated to handle the
// extra digit yet. It could also happen if there is a race between getting
// topics and getting the number of distinct topic versions. We clamp to 0 to
// prevent breakage in these rare circumstances.
if (padding_needed < 0) {
padding_needed = 0;
}
// Add the padding list at the end.
header_list.push_back(net::structured_headers::ParameterizedMember(
std::vector<net::structured_headers::ParameterizedItem>(),
{{"p", net::structured_headers::Item(
base::StrCat({"P", std::string(padding_needed, '0')}),
net::structured_headers::Item::kTokenType)}}));
std::optional<std::string> serialized_header =
net::structured_headers::SerializeList(header_list);
CHECK(serialized_header);
return *serialized_header;
}
void HandleTopicsEligibleResponse(
const network::mojom::ParsedHeadersPtr& parsed_headers,
const url::Origin& caller_origin,
RenderFrameHost& request_initiator_frame,
browsing_topics::ApiCallerSource caller_source) {
DCHECK(caller_source == browsing_topics::ApiCallerSource::kFetch ||
caller_source == browsing_topics::ApiCallerSource::kIframeAttribute ||
caller_source == browsing_topics::ApiCallerSource::kImgAttribute);
if (!parsed_headers || !parsed_headers->observe_browsing_topics) {
return;
}
// Check the page's IsPrimary() status again in case it has changed since the
// request time.
if (!request_initiator_frame.GetPage().IsPrimary()) {
return;
}
// Store the observation.
std::vector<blink::mojom::EpochTopicPtr> topics;
GetContentClient()->browser()->HandleTopicsWebApi(
caller_origin, request_initiator_frame.GetMainFrame(), caller_source,
/*get_topics=*/false,
/*observe=*/true, topics);
}
} // namespace content
|