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