blob: e8f33f72073183c55744c6fd77138269bb6683eb [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/cookies/cookie_inclusion_status.h"
#include <initializer_list>
#include <string_view>
#include <tuple>
#include <utility>
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "url/gurl.h"
namespace net {
CookieInclusionStatus::CookieInclusionStatus() = default;
CookieInclusionStatus::CookieInclusionStatus(ExclusionReason reason) {
exclusion_reasons_[reason] = true;
}
CookieInclusionStatus::CookieInclusionStatus(ExclusionReason reason,
WarningReason warning) {
exclusion_reasons_[reason] = true;
warning_reasons_[warning] = true;
}
CookieInclusionStatus::CookieInclusionStatus(WarningReason warning) {
warning_reasons_[warning] = true;
}
CookieInclusionStatus::CookieInclusionStatus(
std::vector<ExclusionReason> exclusions,
std::vector<WarningReason> warnings,
ExemptionReason exemption) {
for (ExclusionReason reason : exclusions) {
exclusion_reasons_[reason] = true;
}
for (WarningReason warning : warnings) {
warning_reasons_[warning] = true;
}
exemption_reason_ = exemption;
}
CookieInclusionStatus::CookieInclusionStatus(
const CookieInclusionStatus& other) = default;
CookieInclusionStatus& CookieInclusionStatus::operator=(
const CookieInclusionStatus& other) = default;
bool CookieInclusionStatus::operator==(
const CookieInclusionStatus& other) const {
return exclusion_reasons_ == other.exclusion_reasons_ &&
warning_reasons_ == other.warning_reasons_ &&
exemption_reason_ == other.exemption_reason_;
}
bool CookieInclusionStatus::operator!=(
const CookieInclusionStatus& other) const {
return !operator==(other);
}
bool CookieInclusionStatus::operator<(
const CookieInclusionStatus& other) const {
static_assert(NUM_EXCLUSION_REASONS <= sizeof(unsigned long) * CHAR_BIT,
"use .ullong() instead");
static_assert(NUM_WARNING_REASONS <= sizeof(unsigned long) * CHAR_BIT,
"use .ullong() instead");
return std::make_tuple(exclusion_reasons_.to_ulong(),
warning_reasons_.to_ulong(), exemption_reason_) <
std::make_tuple(other.exclusion_reasons_.to_ulong(),
other.warning_reasons_.to_ulong(),
other.exemption_reason_);
}
bool CookieInclusionStatus::IsInclude() const {
return exclusion_reasons_.none();
}
bool CookieInclusionStatus::HasExclusionReason(ExclusionReason reason) const {
return exclusion_reasons_[reason];
}
bool CookieInclusionStatus::HasOnlyExclusionReason(
ExclusionReason reason) const {
return exclusion_reasons_[reason] && exclusion_reasons_.count() == 1;
}
void CookieInclusionStatus::AddExclusionReason(ExclusionReason reason) {
exclusion_reasons_[reason] = true;
// If the cookie would be excluded for reasons other than the new SameSite
// rules, don't bother warning about it.
MaybeClearSameSiteWarning();
// If the cookie would be excluded for reasons unrelated to 3pcd, don't bother
// warning about 3pcd.
MaybeClearThirdPartyPhaseoutReason();
// If the cookie would have been excluded, clear the exemption reason.
exemption_reason_ = ExemptionReason::kNone;
}
void CookieInclusionStatus::RemoveExclusionReason(ExclusionReason reason) {
exclusion_reasons_[reason] = false;
}
void CookieInclusionStatus::RemoveExclusionReasons(
const std::vector<ExclusionReason>& reasons) {
exclusion_reasons_ = ExclusionReasonsWithout(reasons);
}
void CookieInclusionStatus::MaybeSetExemptionReason(ExemptionReason reason) {
if (IsInclude() && exemption_reason_ == ExemptionReason::kNone) {
exemption_reason_ = reason;
}
}
CookieInclusionStatus::ExclusionReasonBitset
CookieInclusionStatus::ExclusionReasonsWithout(
const std::vector<ExclusionReason>& reasons) const {
CookieInclusionStatus::ExclusionReasonBitset result(exclusion_reasons_);
for (const ExclusionReason reason : reasons) {
result[reason] = false;
}
return result;
}
void CookieInclusionStatus::MaybeClearSameSiteWarning() {
if (ExclusionReasonsWithout({
EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
EXCLUDE_SAMESITE_NONE_INSECURE,
}) != 0u) {
RemoveWarningReason(WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT);
RemoveWarningReason(WARN_SAMESITE_NONE_INSECURE);
RemoveWarningReason(WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE);
}
if (!ShouldRecordDowngradeMetrics()) {
RemoveWarningReason(WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE);
RemoveWarningReason(WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE);
RemoveWarningReason(WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE);
RemoveWarningReason(WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE);
RemoveWarningReason(WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE);
RemoveWarningReason(WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION);
}
}
void CookieInclusionStatus::MaybeClearThirdPartyPhaseoutReason() {
if (!IsInclude()) {
RemoveWarningReason(WARN_THIRD_PARTY_PHASEOUT);
}
if (ExclusionReasonsWithout(
{EXCLUDE_THIRD_PARTY_PHASEOUT,
EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET}) != 0u) {
RemoveExclusionReason(EXCLUDE_THIRD_PARTY_PHASEOUT);
RemoveExclusionReason(EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET);
}
}
bool CookieInclusionStatus::ShouldRecordDowngradeMetrics() const {
return ExclusionReasonsWithout({
EXCLUDE_SAMESITE_STRICT,
EXCLUDE_SAMESITE_LAX,
EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
}) == 0u;
}
bool CookieInclusionStatus::ShouldWarn() const {
return warning_reasons_.any();
}
bool CookieInclusionStatus::HasWarningReason(WarningReason reason) const {
return warning_reasons_[reason];
}
bool CookieInclusionStatus::HasSchemefulDowngradeWarning(
CookieInclusionStatus::WarningReason* reason) const {
if (!ShouldWarn())
return false;
const CookieInclusionStatus::WarningReason kDowngradeWarnings[] = {
WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE,
WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE,
WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE,
WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE,
WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE,
};
for (auto warning : kDowngradeWarnings) {
if (!HasWarningReason(warning))
continue;
if (reason)
*reason = warning;
return true;
}
return false;
}
void CookieInclusionStatus::AddWarningReason(WarningReason reason) {
warning_reasons_[reason] = true;
}
void CookieInclusionStatus::RemoveWarningReason(WarningReason reason) {
warning_reasons_[reason] = false;
}
CookieInclusionStatus::ContextDowngradeMetricValues
CookieInclusionStatus::GetBreakingDowngradeMetricsEnumValue(
const GURL& url) const {
bool url_is_secure = url.SchemeIsCryptographic();
// Start the |reason| as something other than the downgrade warnings.
WarningReason reason = WarningReason::NUM_WARNING_REASONS;
// Don't bother checking the return value because the default switch case
// will handle if no reason was found.
HasSchemefulDowngradeWarning(&reason);
switch (reason) {
case WarningReason::WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE:
return url_is_secure
? ContextDowngradeMetricValues::kStrictLaxStrictSecure
: ContextDowngradeMetricValues::kStrictLaxStrictInsecure;
case WarningReason::WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE:
return url_is_secure
? ContextDowngradeMetricValues::kStrictCrossStrictSecure
: ContextDowngradeMetricValues::kStrictCrossStrictInsecure;
case WarningReason::WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE:
return url_is_secure
? ContextDowngradeMetricValues::kStrictCrossLaxSecure
: ContextDowngradeMetricValues::kStrictCrossLaxInsecure;
case WarningReason::WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE:
return url_is_secure
? ContextDowngradeMetricValues::kLaxCrossStrictSecure
: ContextDowngradeMetricValues::kLaxCrossStrictInsecure;
case WarningReason::WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE:
return url_is_secure ? ContextDowngradeMetricValues::kLaxCrossLaxSecure
: ContextDowngradeMetricValues::kLaxCrossLaxInsecure;
default:
return url_is_secure ? ContextDowngradeMetricValues::kNoDowngradeSecure
: ContextDowngradeMetricValues::kNoDowngradeInsecure;
}
}
std::string CookieInclusionStatus::GetDebugString() const {
std::string out;
if (IsInclude())
base::StrAppend(&out, {"INCLUDE, "});
constexpr std::pair<ExclusionReason, const char*> exclusion_reasons[] = {
{EXCLUDE_UNKNOWN_ERROR, "EXCLUDE_UNKNOWN_ERROR"},
{EXCLUDE_HTTP_ONLY, "EXCLUDE_HTTP_ONLY"},
{EXCLUDE_SECURE_ONLY, "EXCLUDE_SECURE_ONLY"},
{EXCLUDE_DOMAIN_MISMATCH, "EXCLUDE_DOMAIN_MISMATCH"},
{EXCLUDE_NOT_ON_PATH, "EXCLUDE_NOT_ON_PATH"},
{EXCLUDE_SAMESITE_STRICT, "EXCLUDE_SAMESITE_STRICT"},
{EXCLUDE_SAMESITE_LAX, "EXCLUDE_SAMESITE_LAX"},
{EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
"EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX"},
{EXCLUDE_SAMESITE_NONE_INSECURE, "EXCLUDE_SAMESITE_NONE_INSECURE"},
{EXCLUDE_USER_PREFERENCES, "EXCLUDE_USER_PREFERENCES"},
{EXCLUDE_FAILURE_TO_STORE, "EXCLUDE_FAILURE_TO_STORE"},
{EXCLUDE_NONCOOKIEABLE_SCHEME, "EXCLUDE_NONCOOKIEABLE_SCHEME"},
{EXCLUDE_OVERWRITE_SECURE, "EXCLUDE_OVERWRITE_SECURE"},
{EXCLUDE_OVERWRITE_HTTP_ONLY, "EXCLUDE_OVERWRITE_HTTP_ONLY"},
{EXCLUDE_INVALID_DOMAIN, "EXCLUDE_INVALID_DOMAIN"},
{EXCLUDE_INVALID_PREFIX, "EXCLUDE_INVALID_PREFIX"},
{EXCLUDE_INVALID_PARTITIONED, "EXCLUDE_INVALID_PARTITIONED"},
{EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE,
"EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE"},
{EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE,
"EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"},
{EXCLUDE_DOMAIN_NON_ASCII, "EXCLUDE_DOMAIN_NON_ASCII"},
{EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET,
"EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET"},
{EXCLUDE_PORT_MISMATCH, "EXCLUDE_PORT_MISMATCH"},
{EXCLUDE_SCHEME_MISMATCH, "EXCLUDE_SCHEME_MISMATCH"},
{EXCLUDE_SHADOWING_DOMAIN, "EXCLUDE_SHADOWING_DOMAIN"},
{EXCLUDE_DISALLOWED_CHARACTER, "EXCLUDE_DISALLOWED_CHARACTER"},
{EXCLUDE_THIRD_PARTY_PHASEOUT, "EXCLUDE_THIRD_PARTY_PHASEOUT"},
{EXCLUDE_NO_COOKIE_CONTENT, "EXCLUDE_NO_COOKIE_CONTENT"},
};
static_assert(
std::size(exclusion_reasons) == ExclusionReason::NUM_EXCLUSION_REASONS,
"Please ensure all ExclusionReason variants are enumerated in "
"GetDebugString");
static_assert(base::ranges::is_sorted(exclusion_reasons),
"Please keep the ExclusionReason variants sorted in numerical "
"order in GetDebugString");
for (const auto& reason : exclusion_reasons) {
if (HasExclusionReason(reason.first))
base::StrAppend(&out, {reason.second, ", "});
}
// Add warning
if (!ShouldWarn()) {
base::StrAppend(&out, {"DO_NOT_WARN, "});
}
constexpr std::pair<WarningReason, const char*> warning_reasons[] = {
{WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT,
"WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT"},
{WARN_SAMESITE_NONE_INSECURE, "WARN_SAMESITE_NONE_INSECURE"},
{WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE,
"WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE"},
{WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE,
"WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE"},
{WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE,
"WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE"},
{WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE,
"WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE"},
{WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE,
"WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE"},
{WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE,
"WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE"},
{WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC,
"WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC"},
{WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION,
"WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION"},
{WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE,
"WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"},
{WARN_DOMAIN_NON_ASCII, "WARN_DOMAIN_NON_ASCII"},
{WARN_PORT_MISMATCH, "WARN_PORT_MISMATCH"},
{WARN_SCHEME_MISMATCH, "WARN_SCHEME_MISMATCH"},
{WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME,
"WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME"},
{WARN_SHADOWING_DOMAIN, "WARN_SHADOWING_DOMAIN"},
{WARN_THIRD_PARTY_PHASEOUT, "WARN_THIRD_PARTY_PHASEOUT"},
};
static_assert(
std::size(warning_reasons) == WarningReason::NUM_WARNING_REASONS,
"Please ensure all WarningReason variants are enumerated in "
"GetDebugString");
static_assert(base::ranges::is_sorted(warning_reasons),
"Please keep the WarningReason variants sorted in numerical "
"order in GetDebugString");
for (const auto& reason : warning_reasons) {
if (HasWarningReason(reason.first))
base::StrAppend(&out, {reason.second, ", "});
}
// Add exemption reason
if (exemption_reason() == CookieInclusionStatus::ExemptionReason::kNone) {
base::StrAppend(&out, {"NO_EXEMPTION"});
return out;
}
std::string_view reason;
switch (exemption_reason()) {
case ExemptionReason::kUserSetting:
reason = "ExemptionUserSetting";
break;
case ExemptionReason::k3PCDMetadata:
reason = "Exemption3PCDMetadata";
break;
case ExemptionReason::k3PCDDeprecationTrial:
reason = "Exemption3PCDDeprecationTrial";
break;
case ExemptionReason::k3PCDHeuristics:
reason = "Exemption3PCDHeuristics";
break;
case ExemptionReason::kEnterprisePolicy:
reason = "ExemptionEnterprisePolicy";
break;
case ExemptionReason::kStorageAccess:
reason = "ExemptionStorageAccess";
break;
case ExemptionReason::kTopLevelStorageAccess:
reason = "ExemptionTopLevelStorageAccess";
break;
case ExemptionReason::kCorsOptIn:
reason = "ExemptionCorsOptIn";
break;
case ExemptionReason::kNone:
NOTREACHED_NORETURN();
};
base::StrAppend(&out, {reason});
return out;
}
bool CookieInclusionStatus::HasExactlyExclusionReasonsForTesting(
std::vector<CookieInclusionStatus::ExclusionReason> reasons) const {
CookieInclusionStatus expected = MakeFromReasonsForTesting(reasons);
return expected.exclusion_reasons_ == exclusion_reasons_;
}
bool CookieInclusionStatus::HasExactlyWarningReasonsForTesting(
std::vector<WarningReason> reasons) const {
CookieInclusionStatus expected = MakeFromReasonsForTesting({}, reasons);
return expected.warning_reasons_ == warning_reasons_;
}
// static
bool CookieInclusionStatus::ValidateExclusionAndWarningFromWire(
uint32_t exclusion_reasons,
uint32_t warning_reasons) {
uint32_t exclusion_mask =
static_cast<uint32_t>(~0ul << ExclusionReason::NUM_EXCLUSION_REASONS);
uint32_t warning_mask =
static_cast<uint32_t>(~0ul << WarningReason::NUM_WARNING_REASONS);
return (exclusion_reasons & exclusion_mask) == 0 &&
(warning_reasons & warning_mask) == 0;
}
CookieInclusionStatus CookieInclusionStatus::MakeFromReasonsForTesting(
std::vector<ExclusionReason> exclusions,
std::vector<WarningReason> warnings,
ExemptionReason exemption,
bool use_literal) {
CookieInclusionStatus literal_status(exclusions, warnings, exemption);
if (use_literal) {
return literal_status;
}
CookieInclusionStatus status;
for (ExclusionReason reason : exclusions) {
status.AddExclusionReason(reason);
}
for (WarningReason warning : warnings) {
status.AddWarningReason(warning);
}
status.MaybeSetExemptionReason(exemption);
CHECK_EQ(status, literal_status);
return status;
}
bool CookieInclusionStatus::ExcludedByUserPreferencesOrTPCD() const {
if (HasOnlyExclusionReason(ExclusionReason::EXCLUDE_USER_PREFERENCES) ||
HasOnlyExclusionReason(ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT)) {
return true;
}
return exclusion_reasons_.count() == 2 &&
exclusion_reasons_[ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT] &&
exclusion_reasons_
[ExclusionReason::
EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET];
}
} // namespace net