910e62b5创建于 1月15日历史提交
// 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 "base/types/cxx23_to_underlying.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(base::to_underlying(GreaseContext::kNone) == 0);
  static_assert(base::to_underlying(GreaseContext::kKey) == 1);
  static_assert(base::to_underlying(GreaseContext::kValue) == 2);
  static_assert(base::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