• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "url/origin.h"
6 
7 #include <stdint.h>
8 
9 #include <algorithm>
10 #include <ostream>
11 #include <string>
12 #include <string_view>
13 #include <tuple>
14 #include <utility>
15 
16 #include "base/base64.h"
17 #include "base/check.h"
18 #include "base/check_op.h"
19 #include "base/containers/contains.h"
20 #include "base/containers/span.h"
21 #include "base/debug/crash_logging.h"
22 #include "base/pickle.h"
23 #include "base/strings/strcat.h"
24 #include "base/trace_event/base_tracing.h"
25 #include "base/trace_event/memory_usage_estimator.h"
26 #include "base/unguessable_token.h"
27 #include "url/gurl.h"
28 #include "url/scheme_host_port.h"
29 #include "url/url_constants.h"
30 #include "url/url_util.h"
31 
32 namespace url {
33 
Origin()34 Origin::Origin() : nonce_(Nonce()) {}
35 
Create(const GURL & url)36 Origin Origin::Create(const GURL& url) {
37   if (!url.is_valid())
38     return Origin();
39 
40   SchemeHostPort tuple;
41 
42   if (url.SchemeIsFileSystem()) {
43     tuple = SchemeHostPort(*url.inner_url());
44   } else if (url.SchemeIsBlob()) {
45     // If we're dealing with a 'blob:' URL, https://url.spec.whatwg.org/#origin
46     // defines the origin as the origin of the URL which results from parsing
47     // the "path", which boils down to everything after the scheme. GURL's
48     // 'GetContent()' gives us exactly that.
49     tuple = SchemeHostPort(GURL(url.GetContent()));
50   } else {
51     tuple = SchemeHostPort(url);
52 
53     // It's SchemeHostPort's responsibility to filter out unrecognized schemes;
54     // sanity check that this is happening.
55     DCHECK(!tuple.IsValid() || url.IsStandard() ||
56            base::Contains(GetLocalSchemes(), url.scheme_piece()) ||
57            AllowNonStandardSchemesForAndroidWebView());
58   }
59 
60   if (!tuple.IsValid())
61     return Origin();
62   return Origin(std::move(tuple));
63 }
64 
Resolve(const GURL & url,const Origin & base_origin)65 Origin Origin::Resolve(const GURL& url, const Origin& base_origin) {
66   if (url.SchemeIs(kAboutScheme) || url.is_empty())
67     return base_origin;
68   Origin result = Origin::Create(url);
69   if (!result.opaque())
70     return result;
71   return base_origin.DeriveNewOpaqueOrigin();
72 }
73 
74 Origin::Origin(const Origin&) = default;
75 Origin& Origin::operator=(const Origin&) = default;
76 Origin::Origin(Origin&&) noexcept = default;
77 Origin& Origin::operator=(Origin&&) noexcept = default;
78 Origin::~Origin() = default;
79 
80 // static
UnsafelyCreateTupleOriginWithoutNormalization(std::string_view scheme,std::string_view host,uint16_t port)81 std::optional<Origin> Origin::UnsafelyCreateTupleOriginWithoutNormalization(
82     std::string_view scheme,
83     std::string_view host,
84     uint16_t port) {
85   SchemeHostPort tuple(std::string(scheme), std::string(host), port,
86                        SchemeHostPort::CHECK_CANONICALIZATION);
87   if (!tuple.IsValid())
88     return std::nullopt;
89   return Origin(std::move(tuple));
90 }
91 
92 // static
UnsafelyCreateOpaqueOriginWithoutNormalization(std::string_view precursor_scheme,std::string_view precursor_host,uint16_t precursor_port,const Origin::Nonce & nonce)93 std::optional<Origin> Origin::UnsafelyCreateOpaqueOriginWithoutNormalization(
94     std::string_view precursor_scheme,
95     std::string_view precursor_host,
96     uint16_t precursor_port,
97     const Origin::Nonce& nonce) {
98   SchemeHostPort precursor(std::string(precursor_scheme),
99                            std::string(precursor_host), precursor_port,
100                            SchemeHostPort::CHECK_CANONICALIZATION);
101   // For opaque origins, it is okay for the SchemeHostPort to be invalid;
102   // however, this should only arise when the arguments indicate the
103   // canonical representation of the invalid SchemeHostPort.
104   if (!precursor.IsValid() &&
105       !(precursor_scheme.empty() && precursor_host.empty() &&
106         precursor_port == 0)) {
107     return std::nullopt;
108   }
109   return Origin(std::move(nonce), std::move(precursor));
110 }
111 
112 // static
CreateFromNormalizedTuple(std::string scheme,std::string host,uint16_t port)113 Origin Origin::CreateFromNormalizedTuple(std::string scheme,
114                                          std::string host,
115                                          uint16_t port) {
116   SchemeHostPort tuple(std::move(scheme), std::move(host), port,
117                        SchemeHostPort::ALREADY_CANONICALIZED);
118   if (!tuple.IsValid())
119     return Origin();
120   return Origin(std::move(tuple));
121 }
122 
123 // static
CreateOpaqueFromNormalizedPrecursorTuple(std::string precursor_scheme,std::string precursor_host,uint16_t precursor_port,const Origin::Nonce & nonce)124 Origin Origin::CreateOpaqueFromNormalizedPrecursorTuple(
125     std::string precursor_scheme,
126     std::string precursor_host,
127     uint16_t precursor_port,
128     const Origin::Nonce& nonce) {
129   SchemeHostPort precursor(std::move(precursor_scheme),
130                            std::move(precursor_host), precursor_port,
131                            SchemeHostPort::ALREADY_CANONICALIZED);
132   // For opaque origins, it is okay for the SchemeHostPort to be invalid.
133   return Origin(std::move(nonce), std::move(precursor));
134 }
135 
Serialize() const136 std::string Origin::Serialize() const {
137   if (opaque())
138     return "null";
139 
140   if (scheme() == kFileScheme)
141     return "file://";
142 
143   return tuple_.Serialize();
144 }
145 
GetURL() const146 GURL Origin::GetURL() const {
147   if (opaque())
148     return GURL();
149 
150   if (scheme() == kFileScheme)
151     return GURL("file:///");
152 
153   return tuple_.GetURL();
154 }
155 
GetNonceForSerialization() const156 const base::UnguessableToken* Origin::GetNonceForSerialization() const {
157   return nonce_ ? &nonce_->token() : nullptr;
158 }
159 
IsSameOriginWith(const Origin & other) const160 bool Origin::IsSameOriginWith(const Origin& other) const {
161   // scheme/host/port must match, even for opaque origins where |tuple_| holds
162   // the precursor origin.
163   return std::tie(tuple_, nonce_) == std::tie(other.tuple_, other.nonce_);
164 }
165 
IsSameOriginWith(const GURL & url) const166 bool Origin::IsSameOriginWith(const GURL& url) const {
167   if (opaque())
168     return false;
169 
170   // The `url::Origin::Create` call here preserves how IsSameOriginWith was used
171   // historically, even though in some scenarios it is not clearly correct:
172   // - Origin of about:blank and about:srcdoc cannot be correctly
173   //   computed/recovered.
174   // - Ideally passing an invalid `url` would be a caller error (e.g. a DCHECK).
175   // - The caller intent is not always clear wrt handling the outer-vs-inner
176   //   origins/URLs in blob: and filesystem: schemes.
177   return IsSameOriginWith(url::Origin::Create(url));
178 }
179 
CanBeDerivedFrom(const GURL & url) const180 bool Origin::CanBeDerivedFrom(const GURL& url) const {
181   DCHECK(url.is_valid());
182 
183   // For "no access" schemes, blink's SecurityOrigin will always create an
184   // opaque unique one. However, about: scheme is also registered as such but
185   // does not behave this way, therefore exclude it from this check.
186   if (base::Contains(url::GetNoAccessSchemes(), url.scheme()) &&
187       !url.SchemeIs(kAboutScheme)) {
188     // If |this| is not opaque, definitely return false as the expectation
189     // is for opaque origin.
190     if (!opaque())
191       return false;
192 
193     // And if it is unique opaque origin, it definitely is fine. But if there
194     // is a precursor stored, we should fall through to compare the tuples.
195     if (!tuple_.IsValid())
196       return true;
197   }
198 
199   SchemeHostPort url_tuple;
200 
201   // Optimization for the common, success case: Scheme/Host/Port match on the
202   // precursor, and the URL is standard. Opaqueness does not matter as a tuple
203   // origin can always create an opaque tuple origin.
204   if (url.IsStandard()) {
205     // Note: if extra copies of the scheme and host are undesirable, this check
206     // can be implemented using StringPiece comparisons, but it has to account
207     // explicitly checks on port numbers.
208     if (url.SchemeIsFileSystem()) {
209       url_tuple = SchemeHostPort(*url.inner_url());
210     } else {
211       url_tuple = SchemeHostPort(url);
212     }
213     return url_tuple == tuple_;
214 
215     // Blob URLs still contain an inner origin, however it is not accessible
216     // through inner_url(), therefore it requires specific case to handle it.
217   } else if (url.SchemeIsBlob()) {
218     // If |this| doesn't contain any precursor information, it is an unique
219     // opaque origin. It is valid case, as any browser-initiated navigation
220     // to about:blank or data: URL will result in a document with such
221     // origin and it is valid for it to create blob: URLs.
222     if (!tuple_.IsValid())
223       return true;
224 
225     url_tuple = SchemeHostPort(GURL(url.GetContent()));
226     return url_tuple == tuple_;
227   }
228 
229   // At this point, the URL has non-standard scheme.
230   DCHECK(!url.IsStandard());
231 
232   // All about: URLs (about:blank, about:srcdoc) inherit their origin from
233   // the context which navigated them, which means that they can be in any
234   // type of origin.
235   if (url.SchemeIs(kAboutScheme))
236     return true;
237 
238   // All data: URLs commit in opaque origins, therefore |this| must be opaque
239   // if |url| has data: scheme.
240   if (url.SchemeIs(kDataScheme))
241     return opaque();
242 
243   // If |this| does not have valid precursor tuple, it is unique opaque origin,
244   // which is what we expect non-standard schemes to get.
245   if (!tuple_.IsValid())
246     return true;
247 
248   // However, when there is precursor present, the schemes must match.
249   return url.scheme() == tuple_.scheme();
250 }
251 
DomainIs(std::string_view canonical_domain) const252 bool Origin::DomainIs(std::string_view canonical_domain) const {
253   return !opaque() && url::DomainIs(tuple_.host(), canonical_domain);
254 }
255 
operator <(const Origin & other) const256 bool Origin::operator<(const Origin& other) const {
257   return std::tie(tuple_, nonce_) < std::tie(other.tuple_, other.nonce_);
258 }
259 
DeriveNewOpaqueOrigin() const260 Origin Origin::DeriveNewOpaqueOrigin() const {
261   return Origin(Nonce(), tuple_);
262 }
263 
GetNonceForTesting() const264 const base::UnguessableToken* Origin::GetNonceForTesting() const {
265   return GetNonceForSerialization();
266 }
267 
GetDebugString(bool include_nonce) const268 std::string Origin::GetDebugString(bool include_nonce) const {
269   // Handle non-opaque origins first, as they are simpler.
270   if (!opaque()) {
271     std::string out = Serialize();
272     if (scheme() == kFileScheme)
273       base::StrAppend(&out, {" [internally: ", tuple_.Serialize(), "]"});
274     return out;
275   }
276 
277   // For opaque origins, log the nonce and precursor as well. Without this,
278   // EXPECT_EQ failures between opaque origins are nearly impossible to
279   // understand.
280   std::string out = base::StrCat({Serialize(), " [internally:"});
281   if (include_nonce) {
282     out += " (";
283     if (nonce_->raw_token().is_empty())
284       out += "nonce TBD";
285     else
286       out += nonce_->raw_token().ToString();
287     out += ")";
288   }
289   if (!tuple_.IsValid())
290     base::StrAppend(&out, {" anonymous]"});
291   else
292     base::StrAppend(&out, {" derived from ", tuple_.Serialize(), "]"});
293   return out;
294 }
295 
Origin(SchemeHostPort tuple)296 Origin::Origin(SchemeHostPort tuple) : tuple_(std::move(tuple)) {
297   DCHECK(!opaque());
298   DCHECK(tuple_.IsValid());
299 }
300 
301 // Constructs an opaque origin derived from |precursor|.
Origin(const Nonce & nonce,SchemeHostPort precursor)302 Origin::Origin(const Nonce& nonce, SchemeHostPort precursor)
303     : tuple_(std::move(precursor)), nonce_(std::move(nonce)) {
304   DCHECK(opaque());
305   // |precursor| is retained, but not accessible via scheme()/host()/port().
306   DCHECK_EQ("", scheme());
307   DCHECK_EQ("", host());
308   DCHECK_EQ(0U, port());
309 }
310 
SerializeWithNonce() const311 std::optional<std::string> Origin::SerializeWithNonce() const {
312   return SerializeWithNonceImpl();
313 }
314 
SerializeWithNonceAndInitIfNeeded()315 std::optional<std::string> Origin::SerializeWithNonceAndInitIfNeeded() {
316   GetNonceForSerialization();
317   return SerializeWithNonceImpl();
318 }
319 
320 // The pickle is saved in the following format, in order:
321 // string - tuple_.GetURL().spec().
322 // uint64_t (if opaque) - high bits of nonce if opaque. 0 if not initialized.
323 // uint64_t (if opaque) - low bits of nonce if opaque. 0 if not initialized.
SerializeWithNonceImpl() const324 std::optional<std::string> Origin::SerializeWithNonceImpl() const {
325   if (!opaque() && !tuple_.IsValid())
326     return std::nullopt;
327 
328   base::Pickle pickle;
329   pickle.WriteString(tuple_.Serialize());
330   if (opaque() && !nonce_->raw_token().is_empty()) {
331     pickle.WriteUInt64(nonce_->token().GetHighForSerialization());
332     pickle.WriteUInt64(nonce_->token().GetLowForSerialization());
333   } else if (opaque()) {
334     // Nonce hasn't been initialized.
335     pickle.WriteUInt64(0);
336     pickle.WriteUInt64(0);
337   }
338 
339   base::span<const uint8_t> data(static_cast<const uint8_t*>(pickle.data()),
340                                  pickle.size());
341   // Base64 encode the data to make it nicer to play with.
342   return base::Base64Encode(data);
343 }
344 
345 // static
Deserialize(const std::string & value)346 std::optional<Origin> Origin::Deserialize(const std::string& value) {
347   std::string data;
348   if (!base::Base64Decode(value, &data))
349     return std::nullopt;
350   base::Pickle pickle(reinterpret_cast<char*>(&data[0]), data.size());
351   base::PickleIterator reader(pickle);
352 
353   std::string pickled_url;
354   if (!reader.ReadString(&pickled_url))
355     return std::nullopt;
356   GURL url(pickled_url);
357 
358   // If only a tuple was serialized, then this origin is not opaque. For opaque
359   // origins, we expect two uint64's to be left in the pickle.
360   bool is_opaque = !reader.ReachedEnd();
361 
362   // Opaque origins without a tuple are ok.
363   if (!is_opaque && !url.is_valid())
364     return std::nullopt;
365   SchemeHostPort tuple(url);
366 
367   // Possible successful early return if the pickled Origin was not opaque.
368   if (!is_opaque) {
369     Origin origin(tuple);
370     if (origin.opaque())
371       return std::nullopt;  // Something went horribly wrong.
372     return origin;
373   }
374 
375   uint64_t nonce_high = 0;
376   if (!reader.ReadUInt64(&nonce_high))
377     return std::nullopt;
378 
379   uint64_t nonce_low = 0;
380   if (!reader.ReadUInt64(&nonce_low))
381     return std::nullopt;
382 
383   std::optional<base::UnguessableToken> nonce_token =
384       base::UnguessableToken::Deserialize(nonce_high, nonce_low);
385 
386   Origin::Nonce nonce;
387   if (nonce_token.has_value()) {
388     // The serialized nonce wasn't empty, so copy it here.
389     nonce = Origin::Nonce(nonce_token.value());
390   }
391   Origin origin;
392   origin.nonce_ = std::move(nonce);
393   origin.tuple_ = tuple;
394   return origin;
395 }
396 
WriteIntoTrace(perfetto::TracedValue context) const397 void Origin::WriteIntoTrace(perfetto::TracedValue context) const {
398   std::move(context).WriteString(GetDebugString());
399 }
400 
EstimateMemoryUsage() const401 size_t Origin::EstimateMemoryUsage() const {
402   return base::trace_event::EstimateMemoryUsage(tuple_);
403 }
404 
operator <<(std::ostream & out,const url::Origin & origin)405 std::ostream& operator<<(std::ostream& out, const url::Origin& origin) {
406   out << origin.GetDebugString();
407   return out;
408 }
409 
operator <<(std::ostream & out,const url::Origin::Nonce & nonce)410 std::ostream& operator<<(std::ostream& out, const url::Origin::Nonce& nonce) {
411   // Subtle: don't let logging trigger lazy-generation of the token value.
412   if (nonce.raw_token().is_empty())
413     return (out << "(nonce TBD)");
414   else
415     return (out << nonce.raw_token());
416 }
417 
IsSameOriginWith(const GURL & a,const GURL & b)418 bool IsSameOriginWith(const GURL& a, const GURL& b) {
419   return Origin::Create(a).IsSameOriginWith(Origin::Create(b));
420 }
421 
422 Origin::Nonce::Nonce() = default;
Nonce(const base::UnguessableToken & token)423 Origin::Nonce::Nonce(const base::UnguessableToken& token) : token_(token) {
424   CHECK(!token_.is_empty());
425 }
426 
token() const427 const base::UnguessableToken& Origin::Nonce::token() const {
428   // Inspecting the value of a nonce triggers lazy-generation.
429   // TODO(dcheng): UnguessableToken::is_empty should go away -- what sentinel
430   // value to use instead?
431   if (token_.is_empty())
432     token_ = base::UnguessableToken::Create();
433   return token_;
434 }
435 
raw_token() const436 const base::UnguessableToken& Origin::Nonce::raw_token() const {
437   return token_;
438 }
439 
440 // Copying a Nonce triggers lazy-generation of the token.
Nonce(const Origin::Nonce & other)441 Origin::Nonce::Nonce(const Origin::Nonce& other) : token_(other.token()) {}
442 
operator =(const Origin::Nonce & other)443 Origin::Nonce& Origin::Nonce::operator=(const Origin::Nonce& other) {
444   // Copying a Nonce triggers lazy-generation of the token.
445   token_ = other.token();
446   return *this;
447 }
448 
449 // Moving a nonce does NOT trigger lazy-generation of the token.
Nonce(Origin::Nonce && other)450 Origin::Nonce::Nonce(Origin::Nonce&& other) noexcept : token_(other.token_) {
451   other.token_ = base::UnguessableToken();  // Reset |other|.
452 }
453 
operator =(Origin::Nonce && other)454 Origin::Nonce& Origin::Nonce::operator=(Origin::Nonce&& other) noexcept {
455   token_ = other.token_;
456   other.token_ = base::UnguessableToken();  // Reset |other|.
457   return *this;
458 }
459 
operator <(const Origin::Nonce & other) const460 bool Origin::Nonce::operator<(const Origin::Nonce& other) const {
461   // When comparing, lazy-generation is required of both tokens, so that an
462   // ordering is established.
463   return token() < other.token();
464 }
465 
operator ==(const Origin::Nonce & other) const466 bool Origin::Nonce::operator==(const Origin::Nonce& other) const {
467   // Equality testing doesn't actually require that the tokens be generated.
468   // If the tokens are both zero, equality only holds if they're the same
469   // object.
470   return (other.token_ == token_) && !(token_.is_empty() && (&other != this));
471 }
472 
operator !=(const Origin::Nonce & other) const473 bool Origin::Nonce::operator!=(const Origin::Nonce& other) const {
474   return !(*this == other);
475 }
476 
477 namespace debug {
478 
ScopedOriginCrashKey(base::debug::CrashKeyString * crash_key,const url::Origin * value)479 ScopedOriginCrashKey::ScopedOriginCrashKey(
480     base::debug::CrashKeyString* crash_key,
481     const url::Origin* value)
482     : scoped_string_value_(
483           crash_key,
484           value ? value->GetDebugString(false /* include_nonce */)
485                 : "nullptr") {}
486 
487 ScopedOriginCrashKey::~ScopedOriginCrashKey() = default;
488 
489 }  // namespace debug
490 
491 }  // namespace url
492