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