blob: c4f75aefbcb89a961f0cfc75909caecc52dcb34d [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/attribution/request_headers_internal.h"
#include <stdint.h>
#include <algorithm>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "net/http/structured_headers.h"
#include "services/network/public/mojom/attribution.mojom.h"
namespace network {
namespace {
using GreaseContext =
::network::AttributionReportingHeaderGreaseOptions::GreaseContext;
using ::network::mojom::AttributionReportingEligibility;
void AddTrueValuedDictMember(
std::vector<net::structured_headers::DictionaryMember>& dict,
std::string key) {
dict.emplace_back(std::move(key),
net::structured_headers::ParameterizedMember(
net::structured_headers::Item(true),
net::structured_headers::Parameters()));
}
void ApplyGreaseContext(
std::vector<net::structured_headers::DictionaryMember>& dict,
GreaseContext context,
bool use_front,
const char* grease) {
if (!grease) {
return;
}
switch (context) {
case GreaseContext::kNone:
break;
case GreaseContext::kKey:
AddTrueValuedDictMember(dict, base::StrCat({"not-", grease}));
break;
case GreaseContext::kValue:
if (!dict.empty()) {
auto& [_, parameterized_member] =
use_front ? dict.front() : dict.back();
parameterized_member.member.front().item =
net::structured_headers::Item(
grease, net::structured_headers::Item::kTokenType);
}
break;
case GreaseContext::kParamName:
if (!dict.empty()) {
auto& [_, parameterized_member] =
use_front ? dict.front() : dict.back();
parameterized_member.params.emplace_back(
grease, net::structured_headers::Item(true));
}
break;
}
}
void ApplyGrease(std::vector<net::structured_headers::DictionaryMember>& dict,
const AttributionReportingHeaderGreaseOptions& options,
const char* grease1,
const char* grease2) {
// We "grease" the header with meaningless components to help ensure that
// recipients are using a structured header parser, not naive string
// operations on the serialized header, and to ensure that values and
// parameters are ignored. We carefully choose the greases themselves to
// minimize excessive bandwidth: each substring corresponding to a real key
// will appear at most once in the serialized header.
//
// https://wicg.github.io/attribution-reporting-api/#set-attribution-reporting-headers
// Allowing these to be swapped gives them equal chance of being used, since
// otherwise a key is more likely to exist for the second grease than the
// first.
if (options.swap_greases) {
std::swap(grease1, grease2);
}
ApplyGreaseContext(dict, options.context1, options.use_front1, grease1);
ApplyGreaseContext(dict, options.context2, options.use_front2, grease2);
if (dict.size() > 1 && options.reverse) {
// Dictionaries retain order during serialization, so reordering helps
// ensure that recipients do not depend on it.
std::ranges::reverse(dict);
}
}
} // namespace
// static
AttributionReportingHeaderGreaseOptions
AttributionReportingHeaderGreaseOptions::FromBits(uint8_t bits) {
AttributionReportingHeaderGreaseOptions options;
options.reverse = bits & 0b1;
bits >>= 1;
options.swap_greases = bits & 0b1;
bits >>= 1;
static_assert(std::to_underlying(GreaseContext::kNone) == 0);
static_assert(std::to_underlying(GreaseContext::kKey) == 1);
static_assert(std::to_underlying(GreaseContext::kValue) == 2);
static_assert(std::to_underlying(GreaseContext::kParamName) == 3);
options.context1 = static_cast<GreaseContext>(bits & 0b11);
bits >>= 2;
options.context2 = static_cast<GreaseContext>(bits & 0b11);
bits >>= 2;
options.use_front1 = bits & 0b1;
bits >>= 1;
options.use_front2 = bits & 0b1;
bits >>= 1;
return options;
}
std::string SerializeAttributionReportingEligibleHeader(
const AttributionReportingEligibility eligibility,
const AttributionReportingHeaderGreaseOptions& options) {
const char* const kEventSource = "event-source";
const char* const kNavigationSource = "navigation-source";
const char* const kTrigger = "trigger";
std::vector<net::structured_headers::DictionaryMember> eligibilities;
const char* grease1;
const char* grease2;
switch (eligibility) {
case AttributionReportingEligibility::kUnset:
NOTREACHED();
case AttributionReportingEligibility::kEmpty:
grease1 = kEventSource;
grease2 = kTrigger;
break;
case AttributionReportingEligibility::kEventSource:
AddTrueValuedDictMember(eligibilities, kEventSource);
grease1 = kTrigger;
grease2 = kNavigationSource;
break;
case AttributionReportingEligibility::kNavigationSource:
AddTrueValuedDictMember(eligibilities, kNavigationSource);
grease1 = kEventSource;
grease2 = kTrigger;
break;
case AttributionReportingEligibility::kTrigger:
AddTrueValuedDictMember(eligibilities, kTrigger);
grease1 = kNavigationSource;
grease2 = kEventSource;
break;
case AttributionReportingEligibility::kEventSourceOrTrigger:
AddTrueValuedDictMember(eligibilities, kEventSource);
AddTrueValuedDictMember(eligibilities, kTrigger);
grease1 = kNavigationSource;
grease2 = nullptr;
break;
}
ApplyGrease(eligibilities, options, grease1, grease2);
std::optional<std::string> eligible_header =
net::structured_headers::SerializeDictionary(
net::structured_headers::Dictionary(std::move(eligibilities)));
DCHECK(eligible_header.has_value());
return std::move(*eligible_header);
}
std::string GetAttributionSupportHeader(
mojom::AttributionSupport attribution_support,
const AttributionReportingHeaderGreaseOptions& options) {
std::vector<net::structured_headers::DictionaryMember> registrars;
const char* grease1;
const char* grease2;
switch (attribution_support) {
case mojom::AttributionSupport::kWeb:
AddTrueValuedDictMember(registrars, "web");
grease1 = "os";
grease2 = nullptr;
break;
case mojom::AttributionSupport::kOs:
AddTrueValuedDictMember(registrars, "os");
grease1 = "web";
grease2 = nullptr;
break;
case mojom::AttributionSupport::kWebAndOs:
AddTrueValuedDictMember(registrars, "os");
AddTrueValuedDictMember(registrars, "web");
grease1 = nullptr;
grease2 = nullptr;
break;
case mojom::AttributionSupport::kNone:
grease1 = "os";
grease2 = "web";
break;
case mojom::AttributionSupport::kUnset:
NOTREACHED();
}
ApplyGrease(registrars, options, grease1, grease2);
std::optional<std::string> support_header =
net::structured_headers::SerializeDictionary(
net::structured_headers::Dictionary(std::move(registrars)));
DCHECK(support_header.has_value());
return std::move(*support_header);
}
std::string SerializeAdAuctionRegistrationEligibleHeader(
mojom::AttributionReportingEligibility eligibility,
const AttributionReportingHeaderGreaseOptions& options) {
constexpr char kView[] = "view";
constexpr char kClick[] = "click";
std::vector<net::structured_headers::DictionaryMember> eligibilities;
const char* grease1;
const char* grease2;
switch (eligibility) {
case AttributionReportingEligibility::kUnset:
NOTREACHED();
case AttributionReportingEligibility::kEmpty:
grease1 = kView;
grease2 = kClick;
break;
case AttributionReportingEligibility::kEventSource:
AddTrueValuedDictMember(eligibilities, kView);
grease1 = kClick;
grease2 = kView;
break;
case AttributionReportingEligibility::kNavigationSource:
AddTrueValuedDictMember(eligibilities, kClick);
grease1 = kView;
grease2 = kClick;
break;
case AttributionReportingEligibility::kTrigger:
grease1 = kView;
grease2 = kClick;
break;
case AttributionReportingEligibility::kEventSourceOrTrigger:
AddTrueValuedDictMember(eligibilities, kView);
grease1 = kClick;
grease2 = kView;
break;
}
ApplyGrease(eligibilities, options, grease1, grease2);
std::optional<std::string> eligible_header =
net::structured_headers::SerializeDictionary(
net::structured_headers::Dictionary(std::move(eligibilities)));
DCHECK(eligible_header.has_value());
return std::move(*eligible_header);
}
} // namespace network