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 <optional>
#include <ostream>
#include <string>
#include <vector>

#include "net/http/structured_headers.h"
#include "services/network/public/mojom/attribution.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/fuzztest/src/fuzztest/fuzztest.h"

namespace network {

namespace {
using GreaseContext =
    ::network::AttributionReportingHeaderGreaseOptions::GreaseContext;

using ::network::mojom::AttributionSupport;
}  // namespace

bool operator==(const AttributionReportingHeaderGreaseOptions& a,
                const AttributionReportingHeaderGreaseOptions& b) {
  const auto tie = [](const AttributionReportingHeaderGreaseOptions& o) {
    return std::make_tuple(o.reverse, o.swap_greases, o.context1, o.context2,
                           o.use_front1, o.use_front2);
  };
  return tie(a) == tie(b);
}

std::ostream& operator<<(std::ostream& out, GreaseContext context) {
  switch (context) {
    case GreaseContext::kNone:
      return out << "kNone";
    case GreaseContext::kKey:
      return out << "kKey";
    case GreaseContext::kValue:
      return out << "kValue";
    case GreaseContext::kParamName:
      return out << "kParamName";
  }
}

std::ostream& operator<<(std::ostream& out,
                         const AttributionReportingHeaderGreaseOptions& o) {
  return out << o.reverse << ", " << o.swap_greases << ", " << o.context1
             << ", " << o.context2 << ", " << o.use_front1 << ", "
             << o.use_front2;
}

namespace {

using ::network::mojom::AttributionReportingEligibility;

TEST(AttributionRequestHeadersTest, GreaseOptionsFromBits) {
  const struct {
    uint8_t bits;
    AttributionReportingHeaderGreaseOptions expected;
  } kTestCases[] = {
      {
          0b00000000,
          {},
      },
      {
          0b00000001,
          {.reverse = true},
      },
      {
          0b00000010,
          {.swap_greases = true},
      },
      {
          0b00000100,
          {.context1 = GreaseContext::kKey},
      },
      {
          0b00001000,
          {.context1 = GreaseContext::kValue},
      },
      {
          0b00001100,
          {.context1 = GreaseContext::kParamName},
      },
      {
          0b00010000,
          {.context2 = GreaseContext::kKey},
      },
      {
          0b00100000,
          {.context2 = GreaseContext::kValue},
      },
      {
          0b00110000,
          {.context2 = GreaseContext::kParamName},
      },
      {
          0b01000000,
          {.use_front1 = true},
      },
      {
          0b10000000,
          {.use_front2 = true},
      },
  };

  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(
        test_case.expected,
        AttributionReportingHeaderGreaseOptions::FromBits(test_case.bits));
  }
}

TEST(AttributionRequestHeadersTest, NoGrease) {
  const struct {
    AttributionReportingEligibility eligibility;
    const char* expected;
  } kTestCases[] = {
      {AttributionReportingEligibility::kEmpty, ""},
      {AttributionReportingEligibility::kEventSource, "event-source"},
      {AttributionReportingEligibility::kNavigationSource, "navigation-source"},
      {AttributionReportingEligibility::kTrigger, "trigger"},
      {AttributionReportingEligibility::kEventSourceOrTrigger,
       "event-source, trigger"},
  };

  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(
        test_case.expected,
        SerializeAttributionReportingEligibleHeader(
            test_case.eligibility, AttributionReportingHeaderGreaseOptions()));
  }
}

TEST(AttributionRequestHeadersTest, Greases) {
  const struct {
    AttributionReportingEligibility eligibility;
    AttributionReportingHeaderGreaseOptions options;
    const char* expected;
  } kTestCases[] = {
      // reverse with vectors of varying lengths
      {
          AttributionReportingEligibility::kEmpty,
          {.reverse = true},
          "",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.reverse = true},
          "event-source",
      },
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {.reverse = true},
          "trigger, event-source",
      },
      // grease 1
      {
          AttributionReportingEligibility::kEmpty,
          {.context1 = GreaseContext::kKey},
          "not-event-source",
      },
      {
          AttributionReportingEligibility::kEmpty,
          {.context1 = GreaseContext::kValue},
          "",
      },
      {
          AttributionReportingEligibility::kEmpty,
          {.context1 = GreaseContext::kParamName},
          "",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context1 = GreaseContext::kKey},
          "event-source, not-trigger",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context1 = GreaseContext::kValue},
          "event-source=trigger",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context1 = GreaseContext::kParamName},
          "event-source;trigger",
      },
      {
          AttributionReportingEligibility::kNavigationSource,
          {.context1 = GreaseContext::kKey},
          "navigation-source, not-event-source",
      },
      {
          AttributionReportingEligibility::kTrigger,
          {.context1 = GreaseContext::kKey},
          "trigger, not-navigation-source",
      },
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {.context1 = GreaseContext::kKey},
          "event-source, trigger, not-navigation-source",
      },
      // grease 2
      {
          AttributionReportingEligibility::kEmpty,
          {.context2 = GreaseContext::kKey},
          "not-trigger",
      },
      {
          AttributionReportingEligibility::kEmpty,
          {.context2 = GreaseContext::kValue},
          "",
      },
      {
          AttributionReportingEligibility::kEmpty,
          {.context2 = GreaseContext::kParamName},
          "",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context2 = GreaseContext::kKey},
          "event-source, not-navigation-source",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context2 = GreaseContext::kValue},
          "event-source=navigation-source",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context2 = GreaseContext::kParamName},
          "event-source;navigation-source",
      },
      {
          AttributionReportingEligibility::kNavigationSource,
          {.context2 = GreaseContext::kKey},
          "navigation-source, not-trigger",
      },
      {
          AttributionReportingEligibility::kTrigger,
          {.context2 = GreaseContext::kKey},
          "trigger, not-event-source",
      },
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {.context2 = GreaseContext::kKey},
          "event-source, trigger",
      },
      // swap greases
      {
          AttributionReportingEligibility::kEmpty,
          {
              .swap_greases = true,
              .context1 = GreaseContext::kKey,
          },
          "not-trigger",
      },
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {
              .swap_greases = true,
              .context1 = GreaseContext::kKey,
          },
          "event-source, trigger",
      },
      // use_front
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {
              .context1 = GreaseContext::kValue,
              .use_front1 = false,
          },
          "event-source, trigger=navigation-source",
      },
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {
              .context1 = GreaseContext::kValue,
              .use_front1 = true,
          },
          "event-source=navigation-source, trigger",
      },
  };

  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(test_case.expected,
              SerializeAttributionReportingEligibleHeader(test_case.eligibility,
                                                          test_case.options));
  }
}

TEST(AttributionRequestHeadersTest, GetAttributionSupportHeader) {
  const struct {
    mojom::AttributionSupport attribution_support;
    std::vector<std::string> required_keys;
    std::vector<std::string> prohibited_keys;
  } kTestCases[] = {
      {mojom::AttributionSupport::kWeb, {"web"}, {"os"}},
      {mojom::AttributionSupport::kWebAndOs, {"os", "web"}, {}},
      {mojom::AttributionSupport::kOs, {"os"}, {"web"}},
      {mojom::AttributionSupport::kNone, {}, {"os", "web"}},
  };

  for (const auto& test_case : kTestCases) {
    SCOPED_TRACE(test_case.attribution_support);

    std::string actual =
        GetAttributionSupportHeader(test_case.attribution_support,
                                    AttributionReportingHeaderGreaseOptions());

    auto dict = net::structured_headers::ParseDictionary(actual);
    EXPECT_TRUE(dict.has_value());

    for (const auto& key : test_case.required_keys) {
      EXPECT_TRUE(dict->contains(key)) << key;
    }

    for (const auto& key : test_case.prohibited_keys) {
      EXPECT_FALSE(dict->contains(key)) << key;
    }
  }
}

TEST(AttributionRequestHeadersTest, Greases_Support) {
  const struct {
    AttributionSupport support;
    AttributionReportingHeaderGreaseOptions options;
    const char* expected;
  } kTestCases[] = {
      // reverse with vectors of varying lengths
      {
          AttributionSupport::kNone,
          {.reverse = true},
          "",
      },
      {
          AttributionSupport::kWeb,
          {.reverse = true},
          "web",
      },
      {
          AttributionSupport::kWebAndOs,
          {.reverse = true},
          "web, os",
      },
      // grease 1
      {
          AttributionSupport::kNone,
          {.context1 = GreaseContext::kKey},
          "not-os",
      },
      {
          AttributionSupport::kWeb,
          {.context1 = GreaseContext::kKey},
          "web, not-os",
      },
      {
          AttributionSupport::kWeb,
          {.context1 = GreaseContext::kValue},
          "web=os",
      },
      {
          AttributionSupport::kWeb,
          {.context1 = GreaseContext::kParamName},
          "web;os",
      },
      {
          AttributionSupport::kOs,
          {.context1 = GreaseContext::kKey},
          "os, not-web",
      },
      {
          AttributionSupport::kOs,
          {.context1 = GreaseContext::kValue},
          "os=web",
      },
      {
          AttributionSupport::kOs,
          {.context1 = GreaseContext::kParamName},
          "os;web",
      },
      {
          AttributionSupport::kWebAndOs,
          {.context1 = GreaseContext::kKey},
          "os, web",
      },
      {
          AttributionSupport::kWebAndOs,
          {.context1 = GreaseContext::kValue},
          "os, web",
      },
      {
          AttributionSupport::kWebAndOs,
          {.context1 = GreaseContext::kParamName},
          "os, web",
      },
  };

  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(test_case.expected, GetAttributionSupportHeader(
                                      test_case.support, test_case.options));
  }
}

TEST(AttributionRequestHeadersTest, AdAuctionRegistrationEligibleHeader) {
  const struct {
    AttributionReportingEligibility eligibility;
    const char* expected;
  } kTestCases[] = {
      {AttributionReportingEligibility::kEmpty, ""},
      {AttributionReportingEligibility::kEventSource, "view"},
      {AttributionReportingEligibility::kNavigationSource, "click"},
      {AttributionReportingEligibility::kTrigger, ""},
      {AttributionReportingEligibility::kEventSourceOrTrigger, "view"},
  };

  for (const auto& test_case : kTestCases) {
    EXPECT_EQ(
        test_case.expected,
        SerializeAdAuctionRegistrationEligibleHeader(
            test_case.eligibility, AttributionReportingHeaderGreaseOptions()));
  }
}

TEST(AttributionRequestHeadersTest, Greases_AdAuctionRegistrationEligible) {
  const struct {
    AttributionReportingEligibility eligibility;
    AttributionReportingHeaderGreaseOptions options;
    const char* expected;
  } kTestCases[] = {
      // reverse with vectors of varying lengths
      {
          AttributionReportingEligibility::kEmpty,
          {.reverse = true},
          "",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.reverse = true},
          "view",
      },
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {.reverse = true},
          "view",
      },
      // grease 1
      {
          AttributionReportingEligibility::kEmpty,
          {.context1 = GreaseContext::kKey},
          "not-view",
      },
      {
          AttributionReportingEligibility::kEmpty,
          {.context1 = GreaseContext::kValue},
          "",
      },
      {
          AttributionReportingEligibility::kEmpty,
          {.context1 = GreaseContext::kParamName},
          "",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context1 = GreaseContext::kKey},
          "view, not-click",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context1 = GreaseContext::kValue},
          "view=click",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context1 = GreaseContext::kParamName},
          "view;click",
      },
      {
          AttributionReportingEligibility::kNavigationSource,
          {.context1 = GreaseContext::kKey},
          "click, not-view",
      },
      {
          AttributionReportingEligibility::kTrigger,
          {.context1 = GreaseContext::kKey},
          "not-view",
      },
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {.context1 = GreaseContext::kKey},
          "view, not-click",
      },
      // grease 2
      {
          AttributionReportingEligibility::kEmpty,
          {.context2 = GreaseContext::kKey},
          "not-click",
      },
      {
          AttributionReportingEligibility::kEmpty,
          {.context2 = GreaseContext::kValue},
          "",
      },
      {
          AttributionReportingEligibility::kEmpty,
          {.context2 = GreaseContext::kParamName},
          "",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context2 = GreaseContext::kKey},
          "view, not-view",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context2 = GreaseContext::kValue},
          "view=view",
      },
      {
          AttributionReportingEligibility::kEventSource,
          {.context2 = GreaseContext::kParamName},
          "view;view",
      },
      {
          AttributionReportingEligibility::kNavigationSource,
          {.context2 = GreaseContext::kKey},
          "click, not-click",
      },
      {
          AttributionReportingEligibility::kTrigger,
          {.context2 = GreaseContext::kKey},
          "not-click",
      },
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {.context2 = GreaseContext::kKey},
          "view, not-view",
      },
      // swap greases
      {
          AttributionReportingEligibility::kEmpty,
          {
              .swap_greases = true,
              .context1 = GreaseContext::kKey,
          },
          "not-click",
      },
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {
              .swap_greases = true,
              .context1 = GreaseContext::kKey,
          },
          "view, not-view",
      },
      // use_front
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {
              .context1 = GreaseContext::kValue,
              .use_front1 = false,
          },
          "view=click",
      },
      {
          AttributionReportingEligibility::kEventSourceOrTrigger,
          {
              .context1 = GreaseContext::kValue,
              .use_front1 = true,
          },
          "view=click",
      },
  };

  int i = 0;
  for (const auto& test_case : kTestCases) {
    SCOPED_TRACE(i++);
    EXPECT_EQ(test_case.expected,
              SerializeAdAuctionRegistrationEligibleHeader(
                  test_case.eligibility, test_case.options));
  }
}

// Ensure that any support and any grease bits combine to produce a valid
// structured header dictionary.
void IsSupportValid(const mojom::AttributionSupport support,
                    const uint8_t grease_bits) {
  const auto grease_options =
      AttributionReportingHeaderGreaseOptions::FromBits(grease_bits);

  const std::string actual =
      GetAttributionSupportHeader(support, grease_options);

  auto dict = net::structured_headers::ParseDictionary(actual);
  EXPECT_TRUE(dict.has_value());
}

FUZZ_TEST(GetAttributionSupportHeader, IsSupportValid)
    .WithDomains(fuzztest::ElementOf<mojom::AttributionSupport>({
                     mojom::AttributionSupport::kWeb,
                     mojom::AttributionSupport::kWebAndOs,
                     mojom::AttributionSupport::kOs,
                     mojom::AttributionSupport::kNone,
                 }),
                 fuzztest::Arbitrary<uint8_t>());

// Ensure that any eligibility and any grease bits combine to produce a valid
// structured header dictionary.
void IsEligibleValid(const mojom::AttributionReportingEligibility eligibility,
                     const uint8_t grease_bits) {
  const auto grease_options =
      AttributionReportingHeaderGreaseOptions::FromBits(grease_bits);

  const std::string actual =
      SerializeAttributionReportingEligibleHeader(eligibility, grease_options);

  auto dict = net::structured_headers::ParseDictionary(actual);
  EXPECT_TRUE(dict.has_value());
}

FUZZ_TEST(SerializeAttributionReportingEligibleHeader, IsEligibleValid)
    .WithDomains(
        fuzztest::ElementOf<mojom::AttributionReportingEligibility>({
            mojom::AttributionReportingEligibility::kEmpty,
            mojom::AttributionReportingEligibility::kEventSource,
            mojom::AttributionReportingEligibility::kNavigationSource,
            mojom::AttributionReportingEligibility::kTrigger,
            mojom::AttributionReportingEligibility::kEventSourceOrTrigger,
        }),
        fuzztest::Arbitrary<uint8_t>());

}  // namespace
}  // namespace network