| // 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 <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 "base/strings/string_piece.h" | |
| #include "base/unguessable_token.h" | |
| #include "polyfills/third_party/perfetto/include/perfetto/tracing/traced_value.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 | |
| absl::optional<Origin> Origin::UnsafelyCreateTupleOriginWithoutNormalization( | |
| gurl_base::StringPiece scheme, | |
| gurl_base::StringPiece host, | |
| uint16_t port) { | |
| SchemeHostPort tuple(std::string(scheme), std::string(host), port, | |
| SchemeHostPort::CHECK_CANONICALIZATION); | |
| if (!tuple.IsValid()) | |
| return absl::nullopt; | |
| return Origin(std::move(tuple)); | |
| } | |
| // static | |
| absl::optional<Origin> Origin::UnsafelyCreateOpaqueOriginWithoutNormalization( | |
| gurl_base::StringPiece precursor_scheme, | |
| gurl_base::StringPiece 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 absl::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(gurl_base::StringPiece 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()); | |
| } | |
| absl::optional<std::string> Origin::SerializeWithNonce() const { | |
| return SerializeWithNonceImpl(); | |
| } | |
| absl::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. | |
| absl::optional<std::string> Origin::SerializeWithNonceImpl() const { | |
| if (!opaque() && !tuple_.IsValid()) | |
| return absl::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 | |
| absl::optional<Origin> Origin::Deserialize(const std::string& value) { | |
| std::string data; | |
| if (!gurl_base::Base64Decode(value, &data)) | |
| return absl::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 absl::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 absl::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 absl::nullopt; // Something went horribly wrong. | |
| return origin; | |
| } | |
| uint64_t nonce_high = 0; | |
| if (!reader.ReadUInt64(&nonce_high)) | |
| return absl::nullopt; | |
| uint64_t nonce_low = 0; | |
| if (!reader.ReadUInt64(&nonce_low)) | |
| return absl::nullopt; | |
| absl::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()); | |
| } | |
| 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 |