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