mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-28 16:26:10 +03:00
481 lines
16 KiB
C++
481 lines
16 KiB
C++
|
// 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 "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
|