blob: d518ddc72f879f84714f15a94a2a54a53b493f53 [file] [log] [blame]
// Copyright 2019 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/site_for_cookies.h"
#include <utility>
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/cookies/cookie_util.h"
namespace net {
SiteForCookies::SiteForCookies() = default;
SiteForCookies::SiteForCookies(const SchemefulSite& site)
: site_(site), schemefully_same_(!site.opaque()) {
site_.ConvertWebSocketToHttp();
}
SiteForCookies::SiteForCookies(const SiteForCookies& other) = default;
SiteForCookies::SiteForCookies(SiteForCookies&& other) = default;
SiteForCookies::~SiteForCookies() = default;
SiteForCookies& SiteForCookies::operator=(const SiteForCookies& other) =
default;
SiteForCookies& SiteForCookies::operator=(SiteForCookies&& site_for_cookies) =
default;
// static
bool SiteForCookies::FromWire(const SchemefulSite& site,
bool schemefully_same,
SiteForCookies* out) {
SiteForCookies candidate(site);
if (site != candidate.site_)
return false;
candidate.schemefully_same_ = schemefully_same;
*out = std::move(candidate);
return true;
}
// static
SiteForCookies SiteForCookies::FromOrigin(const url::Origin& origin) {
return SiteForCookies(SchemefulSite(origin));
}
// static
SiteForCookies SiteForCookies::FromUrl(const GURL& url) {
return SiteForCookies::FromOrigin(url::Origin::Create(url));
}
std::string SiteForCookies::ToDebugString() const {
std::string same_scheme_string = schemefully_same_ ? "true" : "false";
return base::StrCat({"SiteForCookies: {site=", site_.Serialize(),
"; schemefully_same=", same_scheme_string, "}"});
}
bool SiteForCookies::IsFirstParty(const GURL& url) const {
return IsFirstPartyWithSchemefulMode(
url, cookie_util::IsSchemefulSameSiteEnabled());
}
bool SiteForCookies::IsFirstPartyWithSchemefulMode(
const GURL& url,
bool compute_schemefully) const {
if (compute_schemefully)
return IsSchemefullyFirstParty(url);
return IsSchemelesslyFirstParty(url);
}
bool SiteForCookies::IsEquivalent(const SiteForCookies& other) const {
if (IsNull() || other.IsNull()) {
// We need to check if `other.IsNull()` explicitly in order to catch if
// `other.schemefully_same_` is false when "Schemeful Same-Site" is enabled.
return IsNull() && other.IsNull();
}
// In the case where the site has no registrable domain or host, the scheme
// cannot be ws(s) or http(s), so equality of sites implies actual equality of
// schemes (not just modulo ws-http and wss-https compatibility).
if (cookie_util::IsSchemefulSameSiteEnabled() ||
!site_.has_registrable_domain_or_host()) {
return site_ == other.site_;
}
return site_.SchemelesslyEqual(other.site_);
}
bool SiteForCookies::CompareWithFrameTreeSiteAndRevise(
const SchemefulSite& other) {
// Two opaque SFC are considered equivalent.
if (site_.opaque() && other.opaque())
return true;
// But if only one is opaque we should return false.
if (site_.opaque())
return false;
// Nullify `this` if the `other` is opaque
if (other.opaque()) {
site_ = SchemefulSite();
return false;
}
bool nullify = site_.has_registrable_domain_or_host()
? !site_.SchemelesslyEqual(other)
: site_ != other;
if (nullify) {
// We should only nullify this SFC if the registrable domains (or the entire
// site for cases without an RD) don't match. We *should not* nullify if
// only the schemes mismatch (unless there is no RD) because cookies may be
// processed with LEGACY semantics which only use the RDs. Eventually, when
// schemeful same-site can no longer be disabled, we can revisit this.
site_ = SchemefulSite();
return false;
}
MarkIfCrossScheme(other);
return true;
}
bool SiteForCookies::CompareWithFrameTreeOriginAndRevise(
const url::Origin& other) {
return CompareWithFrameTreeSiteAndRevise(SchemefulSite(other));
}
GURL SiteForCookies::RepresentativeUrl() const {
if (IsNull())
return GURL();
// Cannot use url::Origin::GetURL() because it loses the hostname for file:
// scheme origins.
GURL result(base::StrCat({scheme(), "://", registrable_domain(), "/"}));
DCHECK(result.is_valid());
return result;
}
bool SiteForCookies::IsNull() const {
if (cookie_util::IsSchemefulSameSiteEnabled())
return site_.opaque() || !schemefully_same_;
return site_.opaque();
}
bool SiteForCookies::IsSchemefullyFirstParty(const GURL& url) const {
// Can't use IsNull() as we want the same behavior regardless of
// SchemefulSameSite feature status.
if (site_.opaque() || !schemefully_same_ || !url.is_valid())
return false;
SchemefulSite other_site(url);
other_site.ConvertWebSocketToHttp();
return site_ == other_site;
}
bool SiteForCookies::IsSchemelesslyFirstParty(const GURL& url) const {
// Can't use IsNull() as we want the same behavior regardless of
// SchemefulSameSite feature status.
if (site_.opaque() || !url.is_valid())
return false;
// We don't need to bother changing WebSocket schemes to http, because if
// there is no registrable domain or host, the scheme cannot be ws(s) or
// http(s), and the latter comparison is schemeless anyway.
SchemefulSite other_site(url);
if (!site_.has_registrable_domain_or_host())
return site_ == other_site;
return site_.SchemelesslyEqual(other_site);
}
void SiteForCookies::MarkIfCrossScheme(const SchemefulSite& other) {
// If `this` is IsNull() then `this` doesn't match anything which means that
// the scheme check is pointless. Also exit early if schemefully_same_ is
// already false.
if (IsNull() || !schemefully_same_)
return;
// Mark if `other` is opaque. Opaque origins shouldn't match.
if (other.opaque()) {
schemefully_same_ = false;
return;
}
// Conversion to http/https should have occurred during construction.
DCHECK_NE(url::kWsScheme, scheme());
DCHECK_NE(url::kWssScheme, scheme());
// If the schemes are equal, modulo ws-http and wss-https, don't mark.
if (scheme() == other.site_as_origin_.scheme() ||
(scheme() == url::kHttpsScheme &&
other.site_as_origin_.scheme() == url::kWssScheme) ||
(scheme() == url::kHttpScheme &&
other.site_as_origin_.scheme() == url::kWsScheme)) {
return;
}
// Mark that the two are cross-scheme to each other.
schemefully_same_ = false;
}
bool operator<(const SiteForCookies& lhs, const SiteForCookies& rhs) {
// Similar to IsEquivalent(), if they're both null then they're equivalent
// and therefore `lhs` is not < `rhs`.
if (lhs.IsNull() && rhs.IsNull())
return false;
// If only `lhs` is null then it's always < `rhs`.
if (lhs.IsNull())
return true;
// If only `rhs` is null then `lhs` is not < `rhs`.
if (rhs.IsNull())
return false;
// Otherwise neither are null and we need to compare the `site_`s.
return lhs.site_ < rhs.site_;
}
} // namespace net