| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "url/origin.h" |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <ostream> |
| #include <string> |
| #include <string_view> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/base64.h" |
| #include "polyfills/base/check.h" |
| #include "polyfills/base/check_op.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/span.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/pickle.h" |
| #include "base/strings/strcat.h" |
| #include "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.h" |
| #include "polyfills/base/trace_event/memory_usage_estimator.h" |
| #include "base/unguessable_token.h" |
| #include "url/gurl.h" |
| #include "url/scheme_host_port.h" |
| #include "url/url_constants.h" |
| #include "url/url_util.h" |
| |
| namespace url { |
| |
| Origin::Origin() : nonce_(Nonce()) {} |
| |
| Origin Origin::Create(const GURL& url) { |
| if (!url.is_valid()) |
| return Origin(); |
| |
| SchemeHostPort tuple; |
| |
| if (url.SchemeIsFileSystem()) { |
| tuple = SchemeHostPort(*url.inner_url()); |
| } else if (url.SchemeIsBlob()) { |
| // If we're dealing with a 'blob:' URL, https://url.spec.whatwg.org/#origin |
| // defines the origin as the origin of the URL which results from parsing |
| // the "path", which boils down to everything after the scheme. GURL's |
| // 'GetContent()' gives us exactly that. |
| tuple = SchemeHostPort(GURL(url.GetContent())); |
| } else { |
| tuple = SchemeHostPort(url); |
| |
| // It's SchemeHostPort's responsibility to filter out unrecognized schemes; |
| // sanity check that this is happening. |
| GURL_DCHECK(!tuple.IsValid() || url.IsStandard() || |
| gurl_base::Contains(GetLocalSchemes(), url.scheme_piece()) || |
| AllowNonStandardSchemesForAndroidWebView()); |
| } |
| |
| if (!tuple.IsValid()) |
| return Origin(); |
| return Origin(std::move(tuple)); |
| } |
| |
| Origin Origin::Resolve(const GURL& url, const Origin& base_origin) { |
| if (url.SchemeIs(kAboutScheme) || url.is_empty()) |
| return base_origin; |
| Origin result = Origin::Create(url); |
| if (!result.opaque()) |
| return result; |
| return base_origin.DeriveNewOpaqueOrigin(); |
| } |
| |
| Origin::Origin(const Origin&) = default; |
| Origin& Origin::operator=(const Origin&) = default; |
| Origin::Origin(Origin&&) noexcept = default; |
| Origin& Origin::operator=(Origin&&) noexcept = default; |
| Origin::~Origin() = default; |
| |
| // static |
| std::optional<Origin> Origin::UnsafelyCreateTupleOriginWithoutNormalization( |
| std::string_view scheme, |
| std::string_view host, |
| uint16_t port) { |
| SchemeHostPort tuple(std::string(scheme), std::string(host), port, |
| SchemeHostPort::CHECK_CANONICALIZATION); |
| if (!tuple.IsValid()) |
| return std::nullopt; |
| return Origin(std::move(tuple)); |
| } |
| |
| // static |
| std::optional<Origin> Origin::UnsafelyCreateOpaqueOriginWithoutNormalization( |
| std::string_view precursor_scheme, |
| std::string_view precursor_host, |
| uint16_t precursor_port, |
| const Origin::Nonce& nonce) { |
| SchemeHostPort precursor(std::string(precursor_scheme), |
| std::string(precursor_host), precursor_port, |
| SchemeHostPort::CHECK_CANONICALIZATION); |
| // For opaque origins, it is okay for the SchemeHostPort to be invalid; |
| // however, this should only arise when the arguments indicate the |
| // canonical representation of the invalid SchemeHostPort. |
| if (!precursor.IsValid() && |
| !(precursor_scheme.empty() && precursor_host.empty() && |
| precursor_port == 0)) { |
| return std::nullopt; |
| } |
| return Origin(std::move(nonce), std::move(precursor)); |
| } |
| |
| // static |
| Origin Origin::CreateFromNormalizedTuple(std::string scheme, |
| std::string host, |
| uint16_t port) { |
| SchemeHostPort tuple(std::move(scheme), std::move(host), port, |
| SchemeHostPort::ALREADY_CANONICALIZED); |
| if (!tuple.IsValid()) |
| return Origin(); |
| return Origin(std::move(tuple)); |
| } |
| |
| // static |
| Origin Origin::CreateOpaqueFromNormalizedPrecursorTuple( |
| std::string precursor_scheme, |
| std::string precursor_host, |
| uint16_t precursor_port, |
| const Origin::Nonce& nonce) { |
| SchemeHostPort precursor(std::move(precursor_scheme), |
| std::move(precursor_host), precursor_port, |
| SchemeHostPort::ALREADY_CANONICALIZED); |
| // For opaque origins, it is okay for the SchemeHostPort to be invalid. |
| return Origin(std::move(nonce), std::move(precursor)); |
| } |
| |
| std::string Origin::Serialize() const { |
| if (opaque()) |
| return "null"; |
| |
| if (scheme() == kFileScheme) |
| return "file://"; |
| |
| return tuple_.Serialize(); |
| } |
| |
| GURL Origin::GetURL() const { |
| if (opaque()) |
| return GURL(); |
| |
| if (scheme() == kFileScheme) |
| return GURL("file:///"); |
| |
| return tuple_.GetURL(); |
| } |
| |
| const gurl_base::UnguessableToken* Origin::GetNonceForSerialization() const { |
| return nonce_ ? &nonce_->token() : nullptr; |
| } |
| |
| bool Origin::IsSameOriginWith(const Origin& other) const { |
| // scheme/host/port must match, even for opaque origins where |tuple_| holds |
| // the precursor origin. |
| return std::tie(tuple_, nonce_) == std::tie(other.tuple_, other.nonce_); |
| } |
| |
| bool Origin::IsSameOriginWith(const GURL& url) const { |
| if (opaque()) |
| return false; |
| |
| // The `url::Origin::Create` call here preserves how IsSameOriginWith was used |
| // historically, even though in some scenarios it is not clearly correct: |
| // - Origin of about:blank and about:srcdoc cannot be correctly |
| // computed/recovered. |
| // - Ideally passing an invalid `url` would be a caller error (e.g. a GURL_DCHECK). |
| // - The caller intent is not always clear wrt handling the outer-vs-inner |
| // origins/URLs in blob: and filesystem: schemes. |
| return IsSameOriginWith(url::Origin::Create(url)); |
| } |
| |
| bool Origin::CanBeDerivedFrom(const GURL& url) const { |
| GURL_DCHECK(url.is_valid()); |
| |
| // For "no access" schemes, blink's SecurityOrigin will always create an |
| // opaque unique one. However, about: scheme is also registered as such but |
| // does not behave this way, therefore exclude it from this check. |
| if (gurl_base::Contains(url::GetNoAccessSchemes(), url.scheme()) && |
| !url.SchemeIs(kAboutScheme)) { |
| // If |this| is not opaque, definitely return false as the expectation |
| // is for opaque origin. |
| if (!opaque()) |
| return false; |
| |
| // And if it is unique opaque origin, it definitely is fine. But if there |
| // is a precursor stored, we should fall through to compare the tuples. |
| if (!tuple_.IsValid()) |
| return true; |
| } |
| |
| SchemeHostPort url_tuple; |
| |
| // Optimization for the common, success case: Scheme/Host/Port match on the |
| // precursor, and the URL is standard. Opaqueness does not matter as a tuple |
| // origin can always create an opaque tuple origin. |
| if (url.IsStandard()) { |
| // Note: if extra copies of the scheme and host are undesirable, this check |
| // can be implemented using StringPiece comparisons, but it has to account |
| // explicitly checks on port numbers. |
| if (url.SchemeIsFileSystem()) { |
| url_tuple = SchemeHostPort(*url.inner_url()); |
| } else { |
| url_tuple = SchemeHostPort(url); |
| } |
| return url_tuple == tuple_; |
| |
| // Blob URLs still contain an inner origin, however it is not accessible |
| // through inner_url(), therefore it requires specific case to handle it. |
| } else if (url.SchemeIsBlob()) { |
| // If |this| doesn't contain any precursor information, it is an unique |
| // opaque origin. It is valid case, as any browser-initiated navigation |
| // to about:blank or data: URL will result in a document with such |
| // origin and it is valid for it to create blob: URLs. |
| if (!tuple_.IsValid()) |
| return true; |
| |
| url_tuple = SchemeHostPort(GURL(url.GetContent())); |
| return url_tuple == tuple_; |
| } |
| |
| // At this point, the URL has non-standard scheme. |
| GURL_DCHECK(!url.IsStandard()); |
| |
| // All about: URLs (about:blank, about:srcdoc) inherit their origin from |
| // the context which navigated them, which means that they can be in any |
| // type of origin. |
| if (url.SchemeIs(kAboutScheme)) |
| return true; |
| |
| // All data: URLs commit in opaque origins, therefore |this| must be opaque |
| // if |url| has data: scheme. |
| if (url.SchemeIs(kDataScheme)) |
| return opaque(); |
| |
| // If |this| does not have valid precursor tuple, it is unique opaque origin, |
| // which is what we expect non-standard schemes to get. |
| if (!tuple_.IsValid()) |
| return true; |
| |
| // However, when there is precursor present, the schemes must match. |
| return url.scheme() == tuple_.scheme(); |
| } |
| |
| bool Origin::DomainIs(std::string_view canonical_domain) const { |
| return !opaque() && url::DomainIs(tuple_.host(), canonical_domain); |
| } |
| |
| bool Origin::operator<(const Origin& other) const { |
| return std::tie(tuple_, nonce_) < std::tie(other.tuple_, other.nonce_); |
| } |
| |
| Origin Origin::DeriveNewOpaqueOrigin() const { |
| return Origin(Nonce(), tuple_); |
| } |
| |
| std::string Origin::GetDebugString(bool include_nonce) const { |
| // Handle non-opaque origins first, as they are simpler. |
| if (!opaque()) { |
| std::string out = Serialize(); |
| if (scheme() == kFileScheme) |
| gurl_base::StrAppend(&out, {" [internally: ", tuple_.Serialize(), "]"}); |
| return out; |
| } |
| |
| // For opaque origins, log the nonce and precursor as well. Without this, |
| // EXPECT_EQ failures between opaque origins are nearly impossible to |
| // understand. |
| std::string out = gurl_base::StrCat({Serialize(), " [internally:"}); |
| if (include_nonce) { |
| out += " ("; |
| if (nonce_->raw_token().is_empty()) |
| out += "nonce TBD"; |
| else |
| out += nonce_->raw_token().ToString(); |
| out += ")"; |
| } |
| if (!tuple_.IsValid()) |
| gurl_base::StrAppend(&out, {" anonymous]"}); |
| else |
| gurl_base::StrAppend(&out, {" derived from ", tuple_.Serialize(), "]"}); |
| return out; |
| } |
| |
| Origin::Origin(SchemeHostPort tuple) : tuple_(std::move(tuple)) { |
| GURL_DCHECK(!opaque()); |
| GURL_DCHECK(tuple_.IsValid()); |
| } |
| |
| // Constructs an opaque origin derived from |precursor|. |
| Origin::Origin(const Nonce& nonce, SchemeHostPort precursor) |
| : tuple_(std::move(precursor)), nonce_(std::move(nonce)) { |
| GURL_DCHECK(opaque()); |
| // |precursor| is retained, but not accessible via scheme()/host()/port(). |
| GURL_DCHECK_EQ("", scheme()); |
| GURL_DCHECK_EQ("", host()); |
| GURL_DCHECK_EQ(0U, port()); |
| } |
| |
| std::optional<std::string> Origin::SerializeWithNonce() const { |
| return SerializeWithNonceImpl(); |
| } |
| |
| std::optional<std::string> Origin::SerializeWithNonceAndInitIfNeeded() { |
| GetNonceForSerialization(); |
| return SerializeWithNonceImpl(); |
| } |
| |
| // The pickle is saved in the following format, in order: |
| // string - tuple_.GetURL().spec(). |
| // uint64_t (if opaque) - high bits of nonce if opaque. 0 if not initialized. |
| // uint64_t (if opaque) - low bits of nonce if opaque. 0 if not initialized. |
| std::optional<std::string> Origin::SerializeWithNonceImpl() const { |
| if (!opaque() && !tuple_.IsValid()) |
| return std::nullopt; |
| |
| gurl_base::Pickle pickle; |
| pickle.WriteString(tuple_.Serialize()); |
| if (opaque() && !nonce_->raw_token().is_empty()) { |
| pickle.WriteUInt64(nonce_->token().GetHighForSerialization()); |
| pickle.WriteUInt64(nonce_->token().GetLowForSerialization()); |
| } else if (opaque()) { |
| // Nonce hasn't been initialized. |
| pickle.WriteUInt64(0); |
| pickle.WriteUInt64(0); |
| } |
| |
| gurl_base::span<const uint8_t> data(static_cast<const uint8_t*>(pickle.data()), |
| pickle.size()); |
| // Base64 encode the data to make it nicer to play with. |
| return gurl_base::Base64Encode(data); |
| } |
| |
| // static |
| std::optional<Origin> Origin::Deserialize(const std::string& value) { |
| std::string data; |
| if (!gurl_base::Base64Decode(value, &data)) |
| return std::nullopt; |
| gurl_base::Pickle pickle(reinterpret_cast<char*>(&data[0]), data.size()); |
| gurl_base::PickleIterator reader(pickle); |
| |
| std::string pickled_url; |
| if (!reader.ReadString(&pickled_url)) |
| return std::nullopt; |
| GURL url(pickled_url); |
| |
| // If only a tuple was serialized, then this origin is not opaque. For opaque |
| // origins, we expect two uint64's to be left in the pickle. |
| bool is_opaque = !reader.ReachedEnd(); |
| |
| // Opaque origins without a tuple are ok. |
| if (!is_opaque && !url.is_valid()) |
| return std::nullopt; |
| SchemeHostPort tuple(url); |
| |
| // Possible successful early return if the pickled Origin was not opaque. |
| if (!is_opaque) { |
| Origin origin(tuple); |
| if (origin.opaque()) |
| return std::nullopt; // Something went horribly wrong. |
| return origin; |
| } |
| |
| uint64_t nonce_high = 0; |
| if (!reader.ReadUInt64(&nonce_high)) |
| return std::nullopt; |
| |
| uint64_t nonce_low = 0; |
| if (!reader.ReadUInt64(&nonce_low)) |
| return std::nullopt; |
| |
| std::optional<gurl_base::UnguessableToken> nonce_token = |
| gurl_base::UnguessableToken::Deserialize(nonce_high, nonce_low); |
| |
| Origin::Nonce nonce; |
| if (nonce_token.has_value()) { |
| // The serialized nonce wasn't empty, so copy it here. |
| nonce = Origin::Nonce(nonce_token.value()); |
| } |
| Origin origin; |
| origin.nonce_ = std::move(nonce); |
| origin.tuple_ = tuple; |
| return origin; |
| } |
| |
| void Origin::WriteIntoTrace(perfetto::TracedValue context) const { |
| std::move(context).WriteString(GetDebugString()); |
| } |
| |
| size_t Origin::EstimateMemoryUsage() const { |
| return gurl_base::trace_event::EstimateMemoryUsage(tuple_); |
| } |
| |
| std::ostream& operator<<(std::ostream& out, const url::Origin& origin) { |
| out << origin.GetDebugString(); |
| return out; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, const url::Origin::Nonce& nonce) { |
| // Subtle: don't let logging trigger lazy-generation of the token value. |
| if (nonce.raw_token().is_empty()) |
| return (out << "(nonce TBD)"); |
| else |
| return (out << nonce.raw_token()); |
| } |
| |
| bool IsSameOriginWith(const GURL& a, const GURL& b) { |
| return Origin::Create(a).IsSameOriginWith(Origin::Create(b)); |
| } |
| |
| Origin::Nonce::Nonce() = default; |
| Origin::Nonce::Nonce(const gurl_base::UnguessableToken& token) : token_(token) { |
| GURL_CHECK(!token_.is_empty()); |
| } |
| |
| const gurl_base::UnguessableToken& Origin::Nonce::token() const { |
| // Inspecting the value of a nonce triggers lazy-generation. |
| // TODO(dcheng): UnguessableToken::is_empty should go away -- what sentinel |
| // value to use instead? |
| if (token_.is_empty()) |
| token_ = gurl_base::UnguessableToken::Create(); |
| return token_; |
| } |
| |
| const gurl_base::UnguessableToken& Origin::Nonce::raw_token() const { |
| return token_; |
| } |
| |
| // Copying a Nonce triggers lazy-generation of the token. |
| Origin::Nonce::Nonce(const Origin::Nonce& other) : token_(other.token()) {} |
| |
| Origin::Nonce& Origin::Nonce::operator=(const Origin::Nonce& other) { |
| // Copying a Nonce triggers lazy-generation of the token. |
| token_ = other.token(); |
| return *this; |
| } |
| |
| // Moving a nonce does NOT trigger lazy-generation of the token. |
| Origin::Nonce::Nonce(Origin::Nonce&& other) noexcept : token_(other.token_) { |
| other.token_ = gurl_base::UnguessableToken(); // Reset |other|. |
| } |
| |
| Origin::Nonce& Origin::Nonce::operator=(Origin::Nonce&& other) noexcept { |
| token_ = other.token_; |
| other.token_ = gurl_base::UnguessableToken(); // Reset |other|. |
| return *this; |
| } |
| |
| bool Origin::Nonce::operator<(const Origin::Nonce& other) const { |
| // When comparing, lazy-generation is required of both tokens, so that an |
| // ordering is established. |
| return token() < other.token(); |
| } |
| |
| bool Origin::Nonce::operator==(const Origin::Nonce& other) const { |
| // Equality testing doesn't actually require that the tokens be generated. |
| // If the tokens are both zero, equality only holds if they're the same |
| // object. |
| return (other.token_ == token_) && !(token_.is_empty() && (&other != this)); |
| } |
| |
| bool Origin::Nonce::operator!=(const Origin::Nonce& other) const { |
| return !(*this == other); |
| } |
| |
| namespace debug { |
| |
| ScopedOriginCrashKey::ScopedOriginCrashKey( |
| gurl_base::debug::CrashKeyString* crash_key, |
| const url::Origin* value) |
| : scoped_string_value_( |
| crash_key, |
| value ? value->GetDebugString(false /* include_nonce */) |
| : "nullptr") {} |
| |
| ScopedOriginCrashKey::~ScopedOriginCrashKey() = default; |
| |
| } // namespace debug |
| |
| } // namespace url |