blob: 334bd67044d578189aee1a7d47a9ddc3f2612e91 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/first_party_sets/first_party_sets.h"
#include <initializer_list>
#include <set>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_split.h"
#include "net/base/schemeful_site.h"
#include "net/cookies/cookie_constants.h"
#include "services/network/first_party_sets/first_party_set_parser.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace network {
namespace {
absl::optional<
std::pair<net::SchemefulSite, base::flat_set<net::SchemefulSite>>>
CanonicalizeSet(const std::vector<std::string>& origins) {
if (origins.empty())
return absl::nullopt;
const absl::optional<net::SchemefulSite> maybe_owner =
FirstPartySetParser::CanonicalizeRegisteredDomain(origins[0],
true /* emit_errors */);
if (!maybe_owner.has_value()) {
LOG(ERROR) << "First-Party Set owner is not valid; aborting.";
return absl::nullopt;
}
const net::SchemefulSite& owner = *maybe_owner;
base::flat_set<net::SchemefulSite> members;
for (auto it = origins.begin() + 1; it != origins.end(); ++it) {
const absl::optional<net::SchemefulSite> maybe_member =
FirstPartySetParser::CanonicalizeRegisteredDomain(
*it, true /* emit_errors */);
if (maybe_member.has_value() && maybe_member != owner)
members.emplace(std::move(*maybe_member));
}
if (members.empty()) {
LOG(ERROR) << "No valid First-Party Set members were specified; aborting.";
return absl::nullopt;
}
return absl::make_optional(
std::make_pair(std::move(owner), std::move(members)));
}
} // namespace
FirstPartySets::FirstPartySets() = default;
FirstPartySets::~FirstPartySets() = default;
void FirstPartySets::SetManuallySpecifiedSet(const std::string& flag_value) {
manually_specified_set_ = CanonicalizeSet(base::SplitString(
flag_value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY));
ApplyManuallySpecifiedSet();
}
base::flat_map<net::SchemefulSite, net::SchemefulSite>*
FirstPartySets::ParseAndSet(base::StringPiece raw_sets) {
sets_ = FirstPartySetParser::ParseSetsFromComponentUpdater(raw_sets);
ApplyManuallySpecifiedSet();
return &sets_;
}
bool FirstPartySets::IsContextSamePartyWithSite(
const net::SchemefulSite& site,
const absl::optional<net::SchemefulSite>& top_frame_site,
const std::set<net::SchemefulSite>& party_context) const {
const auto it = sets_.find(site);
if (it == sets_.end())
return false;
const net::SchemefulSite& site_owner = it->second;
const auto is_owned_by_site_owner =
[this, &site_owner](const net::SchemefulSite& context_site) {
const auto context_owner = sets_.find(context_site);
return context_owner != sets_.end() &&
context_owner->second == site_owner;
};
if (top_frame_site && !is_owned_by_site_owner(*top_frame_site))
return false;
return base::ranges::all_of(party_context, is_owned_by_site_owner);
}
net::FirstPartySetsContextType FirstPartySets::ComputeContextType(
const net::SchemefulSite& site,
const absl::optional<net::SchemefulSite>& top_frame_site,
const std::set<net::SchemefulSite>& party_context) const {
const auto owner_or_site =
[this](const net::SchemefulSite& site) -> const net::SchemefulSite& {
const auto it = sets_.find(site);
return it == sets_.end() ? site : it->second;
};
const net::SchemefulSite& site_owner = owner_or_site(site);
// Note: the `party_context` consists of the intermediate frames (for frame
// requests) or intermediate frames and current frame for subresource
// requests.
const bool is_homogeneous = base::ranges::all_of(
party_context, [&](const net::SchemefulSite& middle_site) {
return owner_or_site(middle_site) == site_owner;
});
if (!top_frame_site.has_value()) {
return is_homogeneous
? net::FirstPartySetsContextType::kTopFrameIgnoredHomogeneous
: net::FirstPartySetsContextType::kTopFrameIgnoredMixed;
}
if (owner_or_site(*top_frame_site) != site_owner)
return net::FirstPartySetsContextType::kTopResourceMismatch;
return is_homogeneous
? net::FirstPartySetsContextType::kHomogeneous
: net::FirstPartySetsContextType::kTopResourceMatchMixed;
}
bool FirstPartySets::IsInNontrivialFirstPartySet(
const net::SchemefulSite& site) const {
return base::Contains(sets_, site);
}
base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>>
FirstPartySets::Sets() const {
base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> sets;
for (const auto& pair : sets_) {
const net::SchemefulSite& member = pair.first;
const net::SchemefulSite& owner = pair.second;
auto set = sets.find(owner);
if (set == sets.end()) {
sets.emplace(owner, std::initializer_list<net::SchemefulSite>{member});
} else {
set->second.insert(member);
}
}
return sets;
}
void FirstPartySets::ApplyManuallySpecifiedSet() {
if (!manually_specified_set_)
return;
const net::SchemefulSite& manual_owner = manually_specified_set_->first;
const base::flat_set<net::SchemefulSite>& manual_members =
manually_specified_set_->second;
const auto was_manually_provided =
[&manual_members, &manual_owner](const net::SchemefulSite& site) {
return site == manual_owner || manual_members.contains(site);
};
// Erase the intersection between the manually-specified set and the
// CU-supplied set, and any members whose owner was in the intersection.
sets_.erase(base::ranges::remove_if(sets_,
[&was_manually_provided](const auto& p) {
return was_manually_provided(p.first) ||
was_manually_provided(p.second);
}),
sets_.end());
// Now remove singleton sets. We already removed any sites that were part
// of the intersection, or whose owner was part of the intersection. This
// leaves sites that *are* owners, which no longer have any (other)
// members.
std::set<net::SchemefulSite> owners_with_members;
for (const auto& it : sets_) {
if (it.first != it.second)
owners_with_members.insert(it.second);
}
sets_.erase(base::ranges::remove_if(
sets_,
[&owners_with_members](const auto& p) {
return p.first == p.second &&
!base::Contains(owners_with_members, p.first);
}),
sets_.end());
// Next, we must add the manually-added set to the parsed value.
for (const net::SchemefulSite& member : manual_members) {
sets_.emplace(member, manual_owner);
}
sets_.emplace(manual_owner, manual_owner);
}
} // namespace network