| // 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/base/schemeful_site.h" |
| |
| #include <string_view> |
| |
| #include "base/check.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/trace_event/memory_usage_estimator.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "net/base/url_util.h" |
| #include "url/gurl.h" |
| #include "url/url_canon.h" |
| #include "url/url_constants.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| // When `a_is_site` is true, `a` is actually a SchemefulSite internal |
| // `site_as_origin_`. |
| bool IsSameSiteInternal(const url::Origin& a, |
| const url::Origin& b, |
| bool a_is_site) { |
| if (a.opaque() || b.opaque()) { |
| return a == b; |
| } |
| |
| if (a.scheme() != b.scheme()) { |
| return false; |
| } |
| |
| // The remaining code largely matches what `SameDomainOrHost()` would do, with |
| // one exception: we consider equal-but-empty hosts to be same-site. |
| |
| // Host equality covers two cases: |
| // 1. Non-network schemes where origins are passed through unchanged. |
| // 2. Network schemes where equal hosts will have equal sites (and site |
| // computation is idempotent in cases where `a` is already a site). |
| if (a.host() == b.host()) { |
| return true; |
| } |
| |
| // If host equality is false, then the only way the origins can be same site |
| // is if we have a standard scheme with a network host, which is the only case |
| // when SchemefulSite looks at the registerable domain. |
| if (!IsStandardSchemeWithNetworkHost(a.scheme())) { |
| return false; |
| } |
| |
| std::string_view b_site = GetDomainAndRegistryAsStringPiece( |
| b, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
| |
| // If either `a_site` or `b_site` is empty, their associated SchemefulSites |
| // will have origins passed through without modification, and the positive |
| // result would be covered in the host check above. |
| if (b_site.empty()) { |
| return false; |
| } |
| |
| // Avoid re-calculating the site for `a` if it has already been done. |
| std::string_view a_site = |
| a_is_site |
| ? a.host() |
| : GetDomainAndRegistryAsStringPiece( |
| a, |
| net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
| return a_site == b_site; |
| } |
| |
| } // namespace |
| |
| struct SchemefulSite::ObtainASiteResult { |
| // This is only set if the supplied origin differs from calculated one. |
| std::optional<url::Origin> origin; |
| bool used_registerable_domain; |
| }; |
| |
| // Return a tuple containing: |
| // * a new origin using the registerable domain of `origin` if possible and |
| // a port of 0; otherwise, the passed-in origin. |
| // * a bool indicating whether `origin` had a non-null registerable domain. |
| // (False if `origin` was opaque.) |
| // |
| // Follows steps specified in |
| // https://html.spec.whatwg.org/multipage/origin.html#obtain-a-site |
| SchemefulSite::ObtainASiteResult SchemefulSite::ObtainASite( |
| const url::Origin& origin) { |
| // 1. If origin is an opaque origin, then return origin. |
| if (origin.opaque()) { |
| return {std::nullopt, false /* used_registerable_domain */}; |
| } |
| |
| int port = url::DefaultPortForScheme(origin.scheme()); |
| |
| // Provide a default port of 0 for non-standard schemes. |
| if (port == url::PORT_UNSPECIFIED) { |
| port = 0; |
| } |
| |
| std::string_view registerable_domain; |
| |
| // Non-normative step. |
| // We only lookup the registerable domain for schemes with network hosts, this |
| // is non-normative. Other schemes for non-opaque origins do not |
| // meaningfully have a registerable domain for their host, so they are |
| // skipped. |
| if (IsStandardSchemeWithNetworkHost(origin.scheme())) { |
| registerable_domain = GetDomainAndRegistryAsStringPiece( |
| origin, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
| if (!registerable_domain.empty() && |
| registerable_domain.size() == origin.host().size() && |
| origin.port() == port) { |
| return {std::nullopt, /* used_registerable_domain */ true}; |
| } |
| } |
| |
| // If origin's host's registrable domain is null, then return (origin's |
| // scheme, origin's host). |
| // |
| // `GetDomainAndRegistry()` returns an empty string for IP literals and |
| // effective TLDs. |
| // |
| // Note that `registerable_domain` could still end up empty, since the |
| // `origin` might have a scheme that permits empty hostnames, such as "file". |
| bool used_registerable_domain = !registerable_domain.empty(); |
| if (!used_registerable_domain) |
| registerable_domain = origin.host(); |
| |
| return {url::Origin::CreateFromNormalizedTuple( |
| origin.scheme(), std::string(registerable_domain), port), |
| used_registerable_domain}; |
| } |
| |
| SchemefulSite::SchemefulSite(ObtainASiteResult result, |
| const url::Origin& origin) { |
| if (result.origin) { |
| site_as_origin_ = std::move(*(result.origin)); |
| } else { |
| site_as_origin_ = origin; |
| } |
| } |
| |
| SchemefulSite::SchemefulSite(const url::Origin& origin) |
| : SchemefulSite(ObtainASite(origin), origin) {} |
| |
| SchemefulSite::SchemefulSite(const GURL& url) |
| : SchemefulSite(url::Origin::Create(url)) {} |
| |
| SchemefulSite::SchemefulSite(const SchemefulSite& other) = default; |
| SchemefulSite::SchemefulSite(SchemefulSite&& other) noexcept = default; |
| |
| SchemefulSite& SchemefulSite::operator=(const SchemefulSite& other) = default; |
| SchemefulSite& SchemefulSite::operator=(SchemefulSite&& other) noexcept = |
| default; |
| |
| // static |
| bool SchemefulSite::IsSameSite(const url::Origin& a, const url::Origin& b) { |
| bool same_site = IsSameSiteInternal(a, b, /*a_is_site=*/false); |
| DCHECK_EQ(same_site, SchemefulSite(a) == SchemefulSite(b)); |
| return same_site; |
| } |
| |
| bool SchemefulSite::IsSameSiteWith(const url::Origin& other) const { |
| bool same_site = |
| IsSameSiteInternal(internal_value(), other, /*a_is_site=*/true); |
| DCHECK_EQ(same_site, *this == SchemefulSite(other)); |
| return same_site; |
| } |
| |
| // TODO(csharrison): Consider augmenting the below SameSite methods to avoid |
| // creating intermediate Origins. For now, we sacrifice some performance for |
| // simplicity as GURL --> Origin conversion can be quite subtle. |
| // We could likely fast-path simple cases (e.g. valid http/https GURLs). |
| |
| // static |
| bool SchemefulSite::IsSameSite(const GURL& a, const GURL& b) { |
| bool same_site = IsSameSiteInternal( |
| url::Origin::Create(a), url::Origin::Create(b), /*a_is_site=*/false); |
| DCHECK_EQ(same_site, SchemefulSite(a) == SchemefulSite(b)); |
| return same_site; |
| } |
| |
| bool SchemefulSite::IsSameSiteWith(const GURL& other) const { |
| bool same_site = IsSameSiteInternal( |
| internal_value(), url::Origin::Create(other), /*a_is_site=*/true); |
| DCHECK_EQ(same_site, *this == SchemefulSite(other)); |
| return same_site; |
| } |
| |
| // static |
| bool SchemefulSite::FromWire(const url::Origin& site_as_origin, |
| SchemefulSite* out) { |
| // The origin passed into this constructor may not match the |
| // `site_as_origin_` used as the internal representation of the schemeful |
| // site. However, a valid SchemefulSite's internal origin should result in a |
| // match if used to construct another SchemefulSite. Thus, if there is a |
| // mismatch here, we must indicate a failure. |
| SchemefulSite candidate(site_as_origin); |
| if (candidate.site_as_origin_ != site_as_origin) |
| return false; |
| |
| *out = std::move(candidate); |
| return true; |
| } |
| |
| std::optional<SchemefulSite> SchemefulSite::CreateIfHasRegisterableDomain( |
| const url::Origin& origin) { |
| ObtainASiteResult result = ObtainASite(origin); |
| if (!result.used_registerable_domain) { |
| return std::nullopt; |
| } |
| return SchemefulSite(std::move(result), origin); |
| } |
| |
| void SchemefulSite::ConvertWebSocketToHttp() { |
| if (site_as_origin_.scheme() == url::kWsScheme || |
| site_as_origin_.scheme() == url::kWssScheme) { |
| site_as_origin_ = url::Origin::Create( |
| ChangeWebSocketSchemeToHttpScheme(site_as_origin_.GetURL())); |
| } |
| } |
| |
| // static |
| SchemefulSite SchemefulSite::Deserialize(std::string_view value) { |
| return SchemefulSite(GURL(value)); |
| } |
| |
| std::string SchemefulSite::Serialize() const { |
| return site_as_origin_.Serialize(); |
| } |
| |
| std::string SchemefulSite::SerializeFileSiteWithHost() const { |
| DCHECK_EQ(url::kFileScheme, site_as_origin_.scheme()); |
| return site_as_origin_.GetTupleOrPrecursorTupleIfOpaque().Serialize(); |
| } |
| |
| std::string SchemefulSite::GetDebugString() const { |
| return site_as_origin_.GetDebugString(); |
| } |
| |
| GURL SchemefulSite::GetURL() const { |
| return site_as_origin_.GetURL(); |
| } |
| |
| const url::Origin& SchemefulSite::GetInternalOriginForTesting() const { |
| return site_as_origin_; |
| } |
| |
| size_t SchemefulSite::EstimateMemoryUsage() const { |
| return base::trace_event::EstimateMemoryUsage(site_as_origin_); |
| } |
| |
| // static |
| std::optional<SchemefulSite> SchemefulSite::DeserializeWithNonce( |
| base::PassKey<NetworkAnonymizationKey>, |
| std::string_view value) { |
| return DeserializeWithNonce(value); |
| } |
| |
| // static |
| std::optional<SchemefulSite> SchemefulSite::DeserializeWithNonce( |
| std::string_view value) { |
| std::optional<url::Origin> result = url::Origin::Deserialize(value); |
| if (!result) |
| return std::nullopt; |
| return SchemefulSite(result.value()); |
| } |
| |
| std::optional<std::string> SchemefulSite::SerializeWithNonce( |
| base::PassKey<NetworkAnonymizationKey>) { |
| return SerializeWithNonce(); |
| } |
| |
| std::optional<std::string> SchemefulSite::SerializeWithNonce() { |
| return site_as_origin_.SerializeWithNonceAndInitIfNeeded(); |
| } |
| |
| bool SchemefulSite::SchemelesslyEqual(const SchemefulSite& other) const { |
| return site_as_origin_.host() == other.site_as_origin_.host(); |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const SchemefulSite& ss) { |
| os << ss.Serialize(); |
| return os; |
| } |
| |
| } // namespace net |