blob: a1e7b443604e5dcdb4a2f3bdcbe6cae8dcab2676 [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 <algorithm>
#include <initializer_list>
#include <string_view>
#include <tuple>
#include <utility>
#include "base/containers/enum_set.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "url/gurl.h"
namespace net {
using ExclusionReason = CookieInclusionStatus::ExclusionReason;
using WarningReason = CookieInclusionStatus::WarningReason;
CookieInclusionStatus::CookieInclusionStatus() = default;
CookieInclusionStatus::CookieInclusionStatus(
const CookieInclusionStatus& other) = default;
CookieInclusionStatus& CookieInclusionStatus::operator=(
const CookieInclusionStatus& other) = default;
bool CookieInclusionStatus::operator==(
const CookieInclusionStatus& other) const = default;
bool CookieInclusionStatus::IsInclude() const {
return exclusion_reasons_.empty();
}
bool CookieInclusionStatus::HasExclusionReason(ExclusionReason reason) const {
return exclusion_reasons_.Has(reason);
}
bool CookieInclusionStatus::HasOnlyExclusionReason(
ExclusionReason reason) const {
return exclusion_reasons_.Has(reason) && exclusion_reasons_.size() == 1;
}
void CookieInclusionStatus::AddExclusionReason(ExclusionReason reason) {
exclusion_reasons_.Put(reason);
// 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_.Remove(reason);
}
void CookieInclusionStatus::RemoveExclusionReasons(
ExclusionReasonBitset reasons) {
exclusion_reasons_ = ExclusionReasonsWithout(reasons);
}
void CookieInclusionStatus::MaybeSetExemptionReason(ExemptionReason reason) {
if (IsInclude() && exemption_reason_ == ExemptionReason::kNone) {
exemption_reason_ = reason;
}
}
CookieInclusionStatus::ExclusionReasonBitset
CookieInclusionStatus::ExclusionReasonsWithout(
ExclusionReasonBitset reasons) const {
CookieInclusionStatus::ExclusionReasonBitset result(exclusion_reasons_);
result.RemoveAll(reasons);
return result;
}
void CookieInclusionStatus::MaybeClearSameSiteWarning() {
if (!ExclusionReasonsWithout(
{
ExclusionReason::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
ExclusionReason::EXCLUDE_SAMESITE_NONE_INSECURE,
})
.empty()) {
RemoveWarningReason(
WarningReason::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT);
RemoveWarningReason(WarningReason::WARN_SAMESITE_NONE_INSECURE);
RemoveWarningReason(
WarningReason::WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE);
}
if (!ShouldRecordDowngradeMetrics()) {
RemoveWarningReason(
WarningReason::WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE);
RemoveWarningReason(
WarningReason::WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE);
RemoveWarningReason(
WarningReason::WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE);
RemoveWarningReason(
WarningReason::WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE);
RemoveWarningReason(WarningReason::WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE);
RemoveWarningReason(
WarningReason::WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION);
}
}
void CookieInclusionStatus::MaybeClearThirdPartyPhaseoutReason() {
if (!IsInclude()) {
RemoveWarningReason(WarningReason::WARN_THIRD_PARTY_PHASEOUT);
}
if (!ExclusionReasonsWithout(
{ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT,
ExclusionReason::
EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET})
.empty()) {
RemoveExclusionReasons(
{ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT,
ExclusionReason::EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET});
}
}
bool CookieInclusionStatus::ShouldRecordDowngradeMetrics() const {
return ExclusionReasonsWithout(
{
ExclusionReason::EXCLUDE_SAMESITE_STRICT,
ExclusionReason::EXCLUDE_SAMESITE_LAX,
ExclusionReason::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
})
.empty();
}
bool CookieInclusionStatus::ShouldWarn() const {
return !warning_reasons_.empty();
}
bool CookieInclusionStatus::HasWarningReason(WarningReason reason) const {
return warning_reasons_.Has(reason);
}
bool CookieInclusionStatus::HasSchemefulDowngradeWarning(
WarningReason* reason) const {
if (!ShouldWarn())
return false;
const WarningReason kDowngradeWarnings[] = {
WarningReason::WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE,
WarningReason::WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE,
WarningReason::WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE,
WarningReason::WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE,
WarningReason::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_.Put(reason);
}
void CookieInclusionStatus::RemoveWarningReason(WarningReason reason) {
warning_reasons_.Remove(reason);
}
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::MAX_WARNING_REASON;
// 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[] = {
{ExclusionReason::EXCLUDE_UNKNOWN_ERROR, "EXCLUDE_UNKNOWN_ERROR"},
{ExclusionReason::EXCLUDE_HTTP_ONLY, "EXCLUDE_HTTP_ONLY"},
{ExclusionReason::EXCLUDE_SECURE_ONLY, "EXCLUDE_SECURE_ONLY"},
{ExclusionReason::EXCLUDE_DOMAIN_MISMATCH, "EXCLUDE_DOMAIN_MISMATCH"},
{ExclusionReason::EXCLUDE_NOT_ON_PATH, "EXCLUDE_NOT_ON_PATH"},
{ExclusionReason::EXCLUDE_SAMESITE_STRICT, "EXCLUDE_SAMESITE_STRICT"},
{ExclusionReason::EXCLUDE_SAMESITE_LAX, "EXCLUDE_SAMESITE_LAX"},
{ExclusionReason::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX,
"EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX"},
{ExclusionReason::EXCLUDE_SAMESITE_NONE_INSECURE,
"EXCLUDE_SAMESITE_NONE_INSECURE"},
{ExclusionReason::EXCLUDE_USER_PREFERENCES, "EXCLUDE_USER_PREFERENCES"},
{ExclusionReason::EXCLUDE_FAILURE_TO_STORE, "EXCLUDE_FAILURE_TO_STORE"},
{ExclusionReason::EXCLUDE_NONCOOKIEABLE_SCHEME,
"EXCLUDE_NONCOOKIEABLE_SCHEME"},
{ExclusionReason::EXCLUDE_OVERWRITE_SECURE, "EXCLUDE_OVERWRITE_SECURE"},
{ExclusionReason::EXCLUDE_OVERWRITE_HTTP_ONLY,
"EXCLUDE_OVERWRITE_HTTP_ONLY"},
{ExclusionReason::EXCLUDE_INVALID_DOMAIN, "EXCLUDE_INVALID_DOMAIN"},
{ExclusionReason::EXCLUDE_INVALID_PREFIX, "EXCLUDE_INVALID_PREFIX"},
{ExclusionReason::EXCLUDE_INVALID_PARTITIONED,
"EXCLUDE_INVALID_PARTITIONED"},
{ExclusionReason::EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE,
"EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE"},
{ExclusionReason::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE,
"EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"},
{ExclusionReason::EXCLUDE_DOMAIN_NON_ASCII, "EXCLUDE_DOMAIN_NON_ASCII"},
{ExclusionReason::EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET,
"EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET"},
{ExclusionReason::EXCLUDE_PORT_MISMATCH, "EXCLUDE_PORT_MISMATCH"},
{ExclusionReason::EXCLUDE_SCHEME_MISMATCH, "EXCLUDE_SCHEME_MISMATCH"},
{ExclusionReason::EXCLUDE_SHADOWING_DOMAIN, "EXCLUDE_SHADOWING_DOMAIN"},
{ExclusionReason::EXCLUDE_DISALLOWED_CHARACTER,
"EXCLUDE_DISALLOWED_CHARACTER"},
{ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT,
"EXCLUDE_THIRD_PARTY_PHASEOUT"},
{ExclusionReason::EXCLUDE_NO_COOKIE_CONTENT, "EXCLUDE_NO_COOKIE_CONTENT"},
{ExclusionReason::EXCLUDE_ANONYMOUS_CONTEXT, "EXCLUDE_ANONYMOUS_CONTEXT"},
};
static_assert(
std::size(exclusion_reasons) == ExclusionReasonBitset::kValueCount,
"Please ensure all ExclusionReason variants are enumerated in "
"GetDebugString");
static_assert(std::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[] = {
{WarningReason::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT,
"WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT"},
{WarningReason::WARN_SAMESITE_NONE_INSECURE,
"WARN_SAMESITE_NONE_INSECURE"},
{WarningReason::WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE,
"WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE"},
{WarningReason::WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE,
"WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE"},
{WarningReason::WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE,
"WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE"},
{WarningReason::WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE,
"WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE"},
{WarningReason::WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE,
"WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE"},
{WarningReason::WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE,
"WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE"},
{WarningReason::WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC,
"WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC"},
{WarningReason::WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION,
"WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION"},
{WarningReason::WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE,
"WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"},
{WarningReason::WARN_DOMAIN_NON_ASCII, "WARN_DOMAIN_NON_ASCII"},
{WarningReason::WARN_PORT_MISMATCH, "WARN_PORT_MISMATCH"},
{WarningReason::WARN_SCHEME_MISMATCH, "WARN_SCHEME_MISMATCH"},
{WarningReason::WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME,
"WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME"},
{WarningReason::WARN_SHADOWING_DOMAIN, "WARN_SHADOWING_DOMAIN"},
{WarningReason::WARN_THIRD_PARTY_PHASEOUT, "WARN_THIRD_PARTY_PHASEOUT"},
};
static_assert(std::size(warning_reasons) == WarningReasonBitset::kValueCount,
"Please ensure all WarningReason variants are enumerated in "
"GetDebugString");
static_assert(std::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
std::string_view reason;
switch (exemption_reason()) {
case ExemptionReason::kNone:
reason = "NO_EXEMPTION";
break;
case ExemptionReason::kUserSetting:
reason = "ExemptionUserSetting";
break;
case ExemptionReason::k3PCDMetadata:
reason = "Exemption3PCDMetadata";
break;
case ExemptionReason::k3PCDDeprecationTrial:
reason = "Exemption3PCDDeprecationTrial";
break;
case ExemptionReason::kTopLevel3PCDDeprecationTrial:
reason = "ExemptionTopLevel3PCDDeprecationTrial";
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::kScheme:
reason = "ExemptionScheme";
break;
case ExemptionReason::kSameSiteNoneCookiesInSandbox:
reason = "ExemptionSameSiteNoneCookiesInSandbox";
break;
}
base::StrAppend(&out, {reason});
return out;
}
bool CookieInclusionStatus::HasExactlyExclusionReasonsForTesting(
ExclusionReasonBitset reasons) const {
CookieInclusionStatus expected = MakeFromReasonsForTesting(reasons);
return expected.exclusion_reasons_ == exclusion_reasons_;
}
bool CookieInclusionStatus::HasExactlyWarningReasonsForTesting(
WarningReasonBitset reasons) const {
CookieInclusionStatus expected = MakeFromReasonsForTesting({}, reasons);
return expected.warning_reasons_ == warning_reasons_;
}
CookieInclusionStatus CookieInclusionStatus::MakeFromReasonsForTesting(
ExclusionReasonBitset exclusions,
WarningReasonBitset warnings,
ExemptionReason exemption) {
CookieInclusionStatus status;
for (ExclusionReason reason : exclusions) {
status.AddExclusionReason(reason);
}
for (WarningReason warning : warnings) {
status.AddWarningReason(warning);
}
status.MaybeSetExemptionReason(exemption);
for (auto reason : exclusions) {
CHECK(status.HasExclusionReason(reason))
<< "Exemption " << static_cast<int>(reason) << " could not be applied";
}
CHECK_EQ(status.exclusion_reasons_.size(), exclusions.size());
for (auto reason : warnings) {
CHECK(status.HasWarningReason(reason))
<< "Warning " << static_cast<int>(reason) << " could not be applied";
}
CHECK_EQ(status.warning_reasons_.size(), warnings.size());
CHECK_EQ(status.exemption_reason(), exemption)
<< "Exemption " << static_cast<int>(exemption) << " could not be applied";
return status;
}
std::optional<CookieInclusionStatus> CookieInclusionStatus::MakeFromComponents(
ExclusionReasonBitset exclusions,
WarningReasonBitset warnings,
ExemptionReason exemption) {
CookieInclusionStatus status;
for (ExclusionReason reason : exclusions) {
status.AddExclusionReason(reason);
}
for (WarningReason warning : warnings) {
status.AddWarningReason(warning);
}
status.MaybeSetExemptionReason(exemption);
if (status.exclusion_reasons() != exclusions ||
status.warning_reasons() != warnings ||
status.exemption_reason() != exemption) {
return std::nullopt;
}
return status;
}
bool CookieInclusionStatus::ExcludedByUserPreferencesOrTPCD() const {
if (HasOnlyExclusionReason(ExclusionReason::EXCLUDE_USER_PREFERENCES) ||
HasOnlyExclusionReason(ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT)) {
return true;
}
return exclusion_reasons_ ==
ExclusionReasonBitset(
{ExclusionReason::EXCLUDE_THIRD_PARTY_PHASEOUT,
ExclusionReason::
EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET});
}
} // namespace net