// Copyright 2019 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 "net/cookies/site_for_cookies.h" #include #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