Location.cpp (17237B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "Location.h" 8 9 #include "ReferrerInfo.h" 10 #include "mozilla/BasePrincipal.h" 11 #include "mozilla/Components.h" 12 #include "mozilla/Likely.h" 13 #include "mozilla/NullPrincipal.h" 14 #include "mozilla/ServoStyleConsts.h" 15 #include "mozilla/StaticPrefs_dom.h" 16 #include "mozilla/dom/Document.h" 17 #include "mozilla/dom/DocumentInlines.h" 18 #include "mozilla/dom/FragmentDirective.h" 19 #include "mozilla/dom/LocationBinding.h" 20 #include "mozilla/dom/ScriptSettings.h" 21 #include "nsCOMPtr.h" 22 #include "nsContentUtils.h" 23 #include "nsCycleCollectionParticipant.h" 24 #include "nsDocShell.h" 25 #include "nsDocShellLoadState.h" 26 #include "nsError.h" 27 #include "nsEscape.h" 28 #include "nsGlobalWindowOuter.h" 29 #include "nsIJARURI.h" 30 #include "nsIOService.h" 31 #include "nsIScriptContext.h" 32 #include "nsIScriptObjectPrincipal.h" 33 #include "nsIURIMutator.h" 34 #include "nsIURL.h" 35 #include "nsIWebNavigation.h" 36 #include "nsJSUtils.h" 37 #include "nsNetUtil.h" 38 #include "nsPIDOMWindowInlines.h" 39 #include "nsPresContext.h" 40 #include "nsReadableUtils.h" 41 42 namespace mozilla::dom { 43 44 nsTArray<nsString> ProduceAncestorOriginsList( 45 const nsTArray<nsCOMPtr<nsIPrincipal>>& aPrincipals) { 46 nsTArray<nsString> result; 47 48 for (const auto& principal : aPrincipals) { 49 nsString origin; 50 if (principal == nullptr) { 51 origin.AssignLiteral(u"null"); 52 } else { 53 nsAutoCString originNoSuffix; 54 if (NS_WARN_IF(NS_FAILED(principal->GetOriginNoSuffix(originNoSuffix)))) { 55 origin.AssignLiteral(u"null"); 56 } else { 57 CopyUTF8toUTF16(originNoSuffix, origin); 58 } 59 } 60 result.AppendElement(std::move(origin)); 61 } 62 63 return result; 64 } 65 66 Location::Location(nsPIDOMWindowInner* aWindow) 67 : mCachedHash(VoidCString()), mInnerWindow(aWindow) { 68 BrowsingContext* bc = GetBrowsingContext(); 69 if (bc) { 70 bc->LocationCreated(this); 71 } 72 } 73 74 Location::~Location() { 75 if (isInList()) { 76 remove(); 77 } 78 } 79 80 // QueryInterface implementation for Location 81 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Location) 82 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 83 NS_INTERFACE_MAP_ENTRY(nsISupports) 84 NS_INTERFACE_MAP_END 85 86 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Location, mInnerWindow) 87 88 NS_IMPL_CYCLE_COLLECTING_ADDREF(Location) 89 NS_IMPL_CYCLE_COLLECTING_RELEASE(Location) 90 91 BrowsingContext* Location::GetBrowsingContext() { 92 return mInnerWindow ? mInnerWindow->GetBrowsingContext() : nullptr; 93 } 94 95 nsIDocShell* Location::GetDocShell() { 96 if (BrowsingContext* bc = GetBrowsingContext()) { 97 return bc->GetDocShell(); 98 } 99 return nullptr; 100 } 101 102 nsresult Location::GetURI(nsIURI** aURI, bool aGetInnermostURI) { 103 *aURI = nullptr; 104 105 nsIDocShell* docShell = GetDocShell(); 106 if (!docShell) { 107 return NS_OK; 108 } 109 110 nsIWebNavigation* webNav = nsDocShell::Cast(docShell); 111 112 nsCOMPtr<nsIURI> uri; 113 nsresult rv = webNav->GetCurrentURI(getter_AddRefs(uri)); 114 NS_ENSURE_SUCCESS(rv, rv); 115 116 // It is valid for docshell to return a null URI. Don't try to fixup 117 // if this happens. 118 if (!uri) { 119 return NS_OK; 120 } 121 122 if (aGetInnermostURI) { 123 nsCOMPtr<nsIJARURI> jarURI(do_QueryInterface(uri)); 124 while (jarURI) { 125 jarURI->GetJARFile(getter_AddRefs(uri)); 126 jarURI = do_QueryInterface(uri); 127 } 128 } 129 130 NS_ASSERTION(uri, "nsJARURI screwed up?"); 131 132 // Remove the fragment directive from the url hash. 133 FragmentDirective::ParseAndRemoveFragmentDirectiveFromFragment(uri); 134 nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(uri); 135 exposableURI.forget(aURI); 136 return NS_OK; 137 } 138 139 void Location::GetHash(nsACString& aHash, nsIPrincipal& aSubjectPrincipal, 140 ErrorResult& aRv) { 141 if (!CallerSubsumes(&aSubjectPrincipal)) { 142 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 143 return; 144 } 145 146 if (!mCachedHash.IsVoid()) { 147 aHash = mCachedHash; 148 return; 149 } 150 151 aHash.SetLength(0); 152 153 nsCOMPtr<nsIURI> uri; 154 aRv = GetURI(getter_AddRefs(uri)); 155 if (NS_WARN_IF(aRv.Failed()) || !uri) { 156 return; 157 } 158 159 nsAutoCString ref; 160 161 aRv = uri->GetRef(ref); 162 if (NS_WARN_IF(aRv.Failed())) { 163 return; 164 } 165 166 if (!ref.IsEmpty()) { 167 aHash.SetCapacity(ref.Length() + 1); 168 aHash.Assign('#'); 169 aHash.Append(ref); 170 } 171 172 mCachedHash = aHash; 173 } 174 175 void Location::SetHash(const nsACString& aHash, nsIPrincipal& aSubjectPrincipal, 176 ErrorResult& aRv) { 177 if (!CallerSubsumes(&aSubjectPrincipal)) { 178 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 179 return; 180 } 181 182 nsCOMPtr<nsIURI> uri; 183 aRv = GetURI(getter_AddRefs(uri)); 184 if (NS_WARN_IF(aRv.Failed()) || !uri) { 185 return; 186 } 187 188 if (aHash.IsEmpty() || aHash.First() != '#') { 189 aRv = NS_MutateURI(uri).SetRef("#"_ns + aHash).Finalize(uri); 190 } else { 191 aRv = NS_MutateURI(uri).SetRef(aHash).Finalize(uri); 192 } 193 if (NS_WARN_IF(aRv.Failed()) || !uri) { 194 return; 195 } 196 197 Navigate(uri, aSubjectPrincipal, aRv); 198 } 199 200 // https://html.spec.whatwg.org/#dom-location-ancestororigins 201 RefPtr<DOMStringList> Location::GetAncestorOrigins( 202 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 203 Document* doc = mInnerWindow->GetExtantDoc(); 204 // Step 1. If this's relevant Document is null, then return an empty list. 205 if (!doc || !doc->IsActive()) { 206 return MakeRefPtr<DOMStringList>(); 207 } 208 209 // Step 2. If this's relevant Document's origin is not same origin-domain with 210 // the entry settings object's origin, then throw a "SecurityError" 211 // DOMException. 212 if (!CallerSubsumes(&aSubjectPrincipal)) { 213 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 214 return nullptr; 215 } 216 217 // Step 3. Otherwise, return this's ancestor origins list. 218 return doc->AncestorOrigins(); 219 } 220 221 void Location::GetHost(nsACString& aHost, nsIPrincipal& aSubjectPrincipal, 222 ErrorResult& aRv) { 223 if (!CallerSubsumes(&aSubjectPrincipal)) { 224 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 225 return; 226 } 227 228 aHost.Truncate(); 229 230 nsCOMPtr<nsIURI> uri; 231 (void)GetURI(getter_AddRefs(uri), true); 232 233 if (uri) { 234 (void)uri->GetHostPort(aHost); 235 } 236 } 237 238 void Location::SetHost(const nsACString& aHost, nsIPrincipal& aSubjectPrincipal, 239 ErrorResult& aRv) { 240 if (!CallerSubsumes(&aSubjectPrincipal)) { 241 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 242 return; 243 } 244 245 nsCOMPtr<nsIURI> uri; 246 aRv = GetURI(getter_AddRefs(uri)); 247 if (NS_WARN_IF(aRv.Failed()) || !uri) { 248 return; 249 } 250 251 aRv = NS_MutateURI(uri).SetHostPort(aHost).Finalize(uri); 252 if (NS_WARN_IF(aRv.Failed())) { 253 return; 254 } 255 256 Navigate(uri, aSubjectPrincipal, aRv); 257 } 258 259 void Location::GetHostname(nsACString& aHostname, 260 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 261 if (!CallerSubsumes(&aSubjectPrincipal)) { 262 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 263 return; 264 } 265 266 aHostname.Truncate(); 267 268 nsCOMPtr<nsIURI> uri; 269 GetURI(getter_AddRefs(uri), true); 270 if (uri) { 271 nsContentUtils::GetHostOrIPv6WithBrackets(uri, aHostname); 272 } 273 } 274 275 void Location::SetHostname(const nsACString& aHostname, 276 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 277 if (!CallerSubsumes(&aSubjectPrincipal)) { 278 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 279 return; 280 } 281 282 nsCOMPtr<nsIURI> uri; 283 aRv = GetURI(getter_AddRefs(uri)); 284 if (NS_WARN_IF(aRv.Failed()) || !uri) { 285 return; 286 } 287 288 aRv = NS_MutateURI(uri).SetHost(aHostname).Finalize(uri); 289 if (NS_WARN_IF(aRv.Failed())) { 290 return; 291 } 292 293 Navigate(uri, aSubjectPrincipal, aRv); 294 } 295 296 nsresult Location::GetHref(nsACString& aHref) { 297 aHref.Truncate(); 298 299 nsCOMPtr<nsIURI> uri; 300 nsresult rv = GetURI(getter_AddRefs(uri)); 301 if (NS_WARN_IF(NS_FAILED(rv)) || !uri) { 302 return rv; 303 } 304 305 rv = uri->GetSpec(aHref); 306 if (NS_WARN_IF(NS_FAILED(rv))) { 307 return rv; 308 } 309 return NS_OK; 310 } 311 312 void Location::GetOrigin(nsACString& aOrigin, nsIPrincipal& aSubjectPrincipal, 313 ErrorResult& aRv) { 314 if (!CallerSubsumes(&aSubjectPrincipal)) { 315 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 316 return; 317 } 318 319 aOrigin.Truncate(); 320 321 nsCOMPtr<nsIURI> uri; 322 aRv = GetURI(getter_AddRefs(uri), true); 323 if (NS_WARN_IF(aRv.Failed()) || !uri) { 324 return; 325 } 326 327 aRv = nsContentUtils::GetWebExposedOriginSerialization(uri, aOrigin); 328 if (NS_WARN_IF(aRv.Failed())) { 329 aOrigin.Truncate(); 330 return; 331 } 332 } 333 334 void Location::GetPathname(nsACString& aPathname, 335 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 336 if (!CallerSubsumes(&aSubjectPrincipal)) { 337 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 338 return; 339 } 340 341 aPathname.Truncate(); 342 343 nsCOMPtr<nsIURI> uri; 344 aRv = GetURI(getter_AddRefs(uri)); 345 if (NS_WARN_IF(aRv.Failed()) || !uri) { 346 return; 347 } 348 349 aRv = uri->GetFilePath(aPathname); 350 if (NS_WARN_IF(aRv.Failed())) { 351 return; 352 } 353 } 354 355 void Location::SetPathname(const nsACString& aPathname, 356 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 357 if (!CallerSubsumes(&aSubjectPrincipal)) { 358 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 359 return; 360 } 361 362 nsCOMPtr<nsIURI> uri; 363 aRv = GetURI(getter_AddRefs(uri)); 364 if (NS_WARN_IF(aRv.Failed()) || !uri) { 365 return; 366 } 367 368 nsresult rv = NS_MutateURI(uri).SetFilePath(aPathname).Finalize(uri); 369 if (NS_FAILED(rv)) { 370 return; 371 } 372 373 Navigate(uri, aSubjectPrincipal, aRv); 374 } 375 376 void Location::GetPort(nsACString& aPort, nsIPrincipal& aSubjectPrincipal, 377 ErrorResult& aRv) { 378 if (!CallerSubsumes(&aSubjectPrincipal)) { 379 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 380 return; 381 } 382 383 aPort.SetLength(0); 384 385 nsCOMPtr<nsIURI> uri; 386 aRv = GetURI(getter_AddRefs(uri), true); 387 if (NS_WARN_IF(aRv.Failed()) || !uri) { 388 return; 389 } 390 391 int32_t port; 392 nsresult result = uri->GetPort(&port); 393 394 // Don't propagate this exception to caller 395 if (NS_SUCCEEDED(result) && -1 != port) { 396 aPort.AppendInt(port); 397 } 398 } 399 400 void Location::SetPort(const nsACString& aPort, nsIPrincipal& aSubjectPrincipal, 401 ErrorResult& aRv) { 402 if (!CallerSubsumes(&aSubjectPrincipal)) { 403 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 404 return; 405 } 406 407 nsCOMPtr<nsIURI> uri; 408 aRv = GetURI(getter_AddRefs(uri)); 409 if (NS_WARN_IF(aRv.Failed() || !uri)) { 410 return; 411 } 412 413 // perhaps use nsReadingIterators at some point? 414 nsAutoCString portStr(aPort); 415 const char* buf = portStr.get(); 416 int32_t port = -1; 417 418 if (!portStr.IsEmpty() && buf) { 419 // Sadly, ToInteger() on nsACString does not have the right semantics. 420 if (*buf == ':') { 421 port = atol(buf + 1); 422 } else { 423 port = atol(buf); 424 } 425 } 426 427 aRv = NS_MutateURI(uri).SetPort(port).Finalize(uri); 428 if (NS_WARN_IF(aRv.Failed())) { 429 return; 430 } 431 432 Navigate(uri, aSubjectPrincipal, aRv); 433 } 434 435 void Location::GetProtocol(nsACString& aProtocol, 436 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 437 if (!CallerSubsumes(&aSubjectPrincipal)) { 438 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 439 return; 440 } 441 442 aProtocol.SetLength(0); 443 444 nsCOMPtr<nsIURI> uri; 445 aRv = GetURI(getter_AddRefs(uri)); 446 if (NS_WARN_IF(aRv.Failed()) || !uri) { 447 return; 448 } 449 450 aRv = uri->GetScheme(aProtocol); 451 if (NS_WARN_IF(aRv.Failed())) { 452 return; 453 } 454 455 aProtocol.Append(':'); 456 } 457 458 void Location::SetProtocol(const nsACString& aProtocol, 459 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 460 if (!CallerSubsumes(&aSubjectPrincipal)) { 461 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 462 return; 463 } 464 465 nsCOMPtr<nsIURI> uri; 466 aRv = GetURI(getter_AddRefs(uri)); 467 if (NS_WARN_IF(aRv.Failed()) || !uri) { 468 return; 469 } 470 471 nsACString::const_iterator start, end; 472 aProtocol.BeginReading(start); 473 aProtocol.EndReading(end); 474 nsACString::const_iterator iter(start); 475 (void)FindCharInReadable(':', iter, end); 476 477 nsresult rv = 478 NS_MutateURI(uri).SetScheme(Substring(start, iter)).Finalize(uri); 479 if (NS_WARN_IF(NS_FAILED(rv))) { 480 // Oh, I wish nsStandardURL returned NS_ERROR_MALFORMED_URI for _all_ the 481 // malformed cases, not just some of them! 482 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); 483 return; 484 } 485 486 nsAutoCString newSpec; 487 aRv = uri->GetSpec(newSpec); 488 if (NS_WARN_IF(aRv.Failed())) { 489 return; 490 } 491 // We may want a new URI class for the new URI, so recreate it: 492 rv = NS_NewURI(getter_AddRefs(uri), newSpec); 493 if (NS_FAILED(rv)) { 494 if (rv == NS_ERROR_MALFORMED_URI) { 495 rv = NS_ERROR_DOM_SYNTAX_ERR; 496 } 497 498 aRv.Throw(rv); 499 return; 500 } 501 502 if (!net::SchemeIsHttpOrHttps(uri)) { 503 // No-op, per spec. 504 return; 505 } 506 507 Navigate(uri, aSubjectPrincipal, aRv); 508 } 509 510 void Location::GetSearch(nsACString& aSearch, nsIPrincipal& aSubjectPrincipal, 511 ErrorResult& aRv) { 512 if (!CallerSubsumes(&aSubjectPrincipal)) { 513 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 514 return; 515 } 516 517 aSearch.SetLength(0); 518 519 nsCOMPtr<nsIURI> uri; 520 nsresult result = NS_OK; 521 522 result = GetURI(getter_AddRefs(uri)); 523 524 nsCOMPtr<nsIURL> url(do_QueryInterface(uri)); 525 526 if (url) { 527 nsAutoCString search; 528 529 result = url->GetQuery(search); 530 531 if (NS_SUCCEEDED(result) && !search.IsEmpty()) { 532 aSearch.SetCapacity(search.Length() + 1); 533 aSearch.Assign('?'); 534 aSearch.Append(search); 535 } 536 } 537 } 538 539 void Location::SetSearch(const nsACString& aSearch, 540 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 541 if (!CallerSubsumes(&aSubjectPrincipal)) { 542 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 543 return; 544 } 545 546 nsCOMPtr<nsIURI> uri; 547 aRv = GetURI(getter_AddRefs(uri)); 548 nsCOMPtr<nsIURL> url(do_QueryInterface(uri)); 549 if (NS_WARN_IF(aRv.Failed()) || !url) { 550 return; 551 } 552 553 aRv = NS_MutateURI(uri).SetQuery(aSearch).Finalize(uri); 554 if (NS_WARN_IF(aRv.Failed())) { 555 return; 556 } 557 558 Navigate(uri, aSubjectPrincipal, aRv); 559 } 560 561 void Location::Reload(JSContext* aCx, bool aForceget, 562 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { 563 if (!CallerSubsumes(&aSubjectPrincipal)) { 564 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 565 return; 566 } 567 568 RefPtr<nsDocShell> docShell(nsDocShell::Cast(GetDocShell())); 569 if (!docShell) { 570 return aRv.Throw(NS_ERROR_FAILURE); 571 } 572 573 RefPtr<BrowsingContext> bc = GetBrowsingContext(); 574 if (!bc || bc->IsDiscarded()) { 575 return; 576 } 577 578 CallerType callerType = aSubjectPrincipal.IsSystemPrincipal() 579 ? CallerType::System 580 : CallerType::NonSystem; 581 582 nsresult rv = bc->CheckNavigationRateLimit(callerType); 583 if (NS_FAILED(rv)) { 584 aRv.Throw(rv); 585 return; 586 } 587 588 uint32_t reloadFlags = nsIWebNavigation::LOAD_FLAGS_NONE; 589 590 if (aForceget) { 591 reloadFlags = nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE | 592 nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY; 593 } 594 595 UserNavigationInvolvement userInvolvement = 596 callerType == CallerType::System ? UserNavigationInvolvement::BrowserUI 597 : UserNavigationInvolvement::None; 598 599 rv = docShell->ReloadNavigable(Some(WrapNotNull(aCx)), reloadFlags, nullptr, 600 userInvolvement); 601 if (NS_FAILED(rv) && rv != NS_BINDING_ABORTED) { 602 // NS_BINDING_ABORTED is returned when we attempt to reload a POST result 603 // and the user says no at the "do you want to reload?" prompt. Don't 604 // propagate this one back to callers. 605 return aRv.Throw(rv); 606 } 607 } 608 609 void Location::Assign(const nsACString& aUrl, nsIPrincipal& aSubjectPrincipal, 610 ErrorResult& aRv) { 611 if (!CallerSubsumes(&aSubjectPrincipal)) { 612 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 613 return; 614 } 615 616 DoSetHref(aUrl, aSubjectPrincipal, false, aRv); 617 } 618 619 bool Location::CallerSubsumes(nsIPrincipal* aSubjectPrincipal) { 620 MOZ_ASSERT(aSubjectPrincipal); 621 622 BrowsingContext* bc = GetBrowsingContext(); 623 if (MOZ_UNLIKELY(!bc) || MOZ_UNLIKELY(bc->IsDiscarded())) { 624 // Per spec, operations on a Location object with a discarded BC are no-ops, 625 // not security errors, so we need to return true from the access check and 626 // let the caller do its own discarded docShell check. 627 return true; 628 } 629 if (MOZ_UNLIKELY(!bc->IsInProcess())) { 630 return false; 631 } 632 633 // Get the principal associated with the location object. Note that this is 634 // the principal of the page which will actually be navigated, not the 635 // principal of the Location object itself. This is why we need this check 636 // even though we only allow limited cross-origin access to Location objects 637 // in general. 638 nsPIDOMWindowOuter* outer = bc->GetDOMWindow(); 639 MOZ_DIAGNOSTIC_ASSERT(outer); 640 if (MOZ_UNLIKELY(!outer)) return false; 641 642 nsIScriptObjectPrincipal* sop = nsGlobalWindowOuter::Cast(outer); 643 bool subsumes = false; 644 nsresult rv = aSubjectPrincipal->SubsumesConsideringDomain( 645 sop->GetPrincipal(), &subsumes); 646 NS_ENSURE_SUCCESS(rv, false); 647 return subsumes; 648 } 649 650 JSObject* Location::WrapObject(JSContext* aCx, 651 JS::Handle<JSObject*> aGivenProto) { 652 return Location_Binding::Wrap(aCx, this, aGivenProto); 653 } 654 655 void Location::ClearCachedValues() { mCachedHash = VoidCString(); } 656 657 } // namespace mozilla::dom