| // 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 |