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