123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- // Copyright 2015 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 "url/origin.h"
- #include <stdint.h>
- #include <algorithm>
- #include <ostream>
- #include <string>
- #include <tuple>
- #include <utility>
- #include "base/base64.h"
- #include "base/check.h"
- #include "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 "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.
- DCHECK(!tuple.IsValid() || url.IsStandard() ||
- 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(
- base::StringPiece scheme,
- 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(
- base::StringPiece precursor_scheme,
- 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 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 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 {
- 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 (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.
- 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(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)
- 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 = 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())
- base::StrAppend(&out, {" anonymous]"});
- else
- base::StrAppend(&out, {" derived from ", tuple_.Serialize(), "]"});
- return out;
- }
- Origin::Origin(SchemeHostPort tuple) : tuple_(std::move(tuple)) {
- DCHECK(!opaque());
- 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)) {
- DCHECK(opaque());
- // |precursor| is retained, but not accessible via scheme()/host()/port().
- DCHECK_EQ("", scheme());
- DCHECK_EQ("", host());
- 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;
- 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);
- }
- 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 base::Base64Encode(data);
- }
- // static
- absl::optional<Origin> Origin::Deserialize(const std::string& value) {
- std::string data;
- if (!base::Base64Decode(value, &data))
- return absl::nullopt;
- base::Pickle pickle(reinterpret_cast<char*>(&data[0]), data.size());
- 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;
- Origin::Nonce nonce;
- if (nonce_high != 0 && nonce_low != 0) {
- // The serialized nonce wasn't empty, so copy it here.
- nonce = Origin::Nonce(
- base::UnguessableToken::Deserialize(nonce_high, nonce_low));
- }
- 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 base::UnguessableToken& token) : token_(token) {
- CHECK(!token_.is_empty());
- }
- const 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_ = base::UnguessableToken::Create();
- return token_;
- }
- const 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_ = base::UnguessableToken(); // Reset |other|.
- }
- Origin::Nonce& Origin::Nonce::operator=(Origin::Nonce&& other) noexcept {
- token_ = other.token_;
- other.token_ = 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(
- 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
|