#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 {
constexpr int kTopicMaxLength = 3;
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.");
}
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;
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)}}));
}
if (num_versions_in_epochs == 0) {
num_versions_in_epochs = 1;
}
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);
int max_length =
max_number_of_epochs * kTopicMaxLength +
max_number_of_epochs -
num_versions_in_epochs +
num_versions_in_epochs * 5 +
num_versions_in_epochs *
kVersionMaxLength +
(num_versions_in_epochs - 1) * 2;
if (header_list.size() == 0) {
max_length += 2;
}
int padding_needed =
header_list.size() > 0
? max_length -
net::structured_headers::SerializeList(header_list)->length()
: max_length;
if (padding_needed < 0) {
padding_needed = 0;
}
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;
}
if (!request_initiator_frame.GetPage().IsPrimary()) {
return;
}
std::vector<blink::mojom::EpochTopicPtr> topics;
GetContentClient()->browser()->HandleTopicsWebApi(
caller_origin, request_initiator_frame.GetMainFrame(), caller_source,
false,
true, topics);
}
}