diff options
Diffstat (limited to '')
-rw-r--r-- | dom/base/LocationBase.cpp | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/dom/base/LocationBase.cpp b/dom/base/LocationBase.cpp new file mode 100644 index 0000000000..fd48c54c7e --- /dev/null +++ b/dom/base/LocationBase.cpp @@ -0,0 +1,274 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/LocationBase.h" +#include "nsIScriptSecurityManager.h" +#include "nsIScriptContext.h" +#include "nsDocShellLoadState.h" +#include "nsIWebNavigation.h" +#include "nsNetUtil.h" +#include "nsCOMPtr.h" +#include "nsError.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/dom/WindowContext.h" + +namespace mozilla::dom { + +already_AddRefed<nsDocShellLoadState> LocationBase::CheckURL( + nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { + RefPtr<BrowsingContext> bc(GetBrowsingContext()); + if (NS_WARN_IF(!bc)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + nsCOMPtr<nsIURI> sourceURI; + ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty; + nsCOMPtr<nsIReferrerInfo> referrerInfo; + + // Get security manager. + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + if (NS_WARN_IF(!ssm)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + // Check to see if URI is allowed. We're not going to worry about a + // window ID here because it's not 100% clear which window's id we + // would want, and we're throwing a content-visible exception + // anyway. + nsresult rv = ssm->CheckLoadURIWithPrincipal( + &aSubjectPrincipal, aURI, nsIScriptSecurityManager::STANDARD, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + nsAutoCString spec; + aURI->GetSpec(spec); + aRv.ThrowTypeError<MSG_URL_NOT_LOADABLE>(spec); + return nullptr; + } + + // Make the load's referrer reflect changes to the document's URI caused by + // push/replaceState, if possible. First, get the document corresponding to + // fp. If the document's original URI (i.e. its URI before + // push/replaceState) matches the principal's URI, use the document's + // current URI as the referrer. If they don't match, use the principal's + // URI. + // + // The triggering principal for this load should be the principal of the + // incumbent document (which matches where the referrer information is + // coming from) when there is an incumbent document, and the subject + // principal otherwise. Note that the URI in the triggering principal + // may not match the referrer URI in various cases, notably including + // the cases when the incumbent document's document URI was modified + // after the document was loaded. + + nsCOMPtr<nsPIDOMWindowInner> incumbent = + do_QueryInterface(mozilla::dom::GetIncumbentGlobal()); + nsCOMPtr<Document> doc = incumbent ? incumbent->GetDoc() : nullptr; + + // Create load info + RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI); + + if (!doc) { + // No document; just use our subject principal as the triggering principal. + loadState->SetTriggeringPrincipal(&aSubjectPrincipal); + return loadState.forget(); + } + + nsCOMPtr<nsIURI> docOriginalURI, docCurrentURI, principalURI; + docOriginalURI = doc->GetOriginalURI(); + docCurrentURI = doc->GetDocumentURI(); + nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal(); + + triggeringPrincipal = doc->NodePrincipal(); + referrerPolicy = doc->GetReferrerPolicy(); + + bool urisEqual = false; + if (docOriginalURI && docCurrentURI && principal) { + principal->EqualsURI(docOriginalURI, &urisEqual); + } + if (urisEqual) { + referrerInfo = new ReferrerInfo(docCurrentURI, referrerPolicy); + } else { + principal->CreateReferrerInfo(referrerPolicy, getter_AddRefs(referrerInfo)); + } + loadState->SetTriggeringPrincipal(triggeringPrincipal); + loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags()); + loadState->SetCsp(doc->GetCsp()); + if (referrerInfo) { + loadState->SetReferrerInfo(referrerInfo); + } + loadState->SetHasValidUserGestureActivation( + doc->HasValidTransientUserGestureActivation()); + + return loadState.forget(); +} + +void LocationBase::SetURI(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, + ErrorResult& aRv, bool aReplace) { + RefPtr<BrowsingContext> bc = GetBrowsingContext(); + if (!bc || bc->IsDiscarded()) { + return; + } + + CallerType callerType = aSubjectPrincipal.IsSystemPrincipal() + ? CallerType::System + : CallerType::NonSystem; + + nsresult rv = bc->CheckLocationChangeRateLimit(callerType); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return; + } + + RefPtr<nsDocShellLoadState> loadState = + CheckURL(aURI, aSubjectPrincipal, aRv); + if (aRv.Failed()) { + return; + } + + if (aReplace) { + loadState->SetLoadType(LOAD_STOP_CONTENT_AND_REPLACE); + } else { + loadState->SetLoadType(LOAD_STOP_CONTENT); + } + + // Get the incumbent script's browsing context to set as source. + nsCOMPtr<nsPIDOMWindowInner> sourceWindow = + nsContentUtils::IncumbentInnerWindow(); + if (sourceWindow) { + WindowContext* context = sourceWindow->GetWindowContext(); + loadState->SetSourceBrowsingContext(sourceWindow->GetBrowsingContext()); + loadState->SetHasValidUserGestureActivation( + context && context->HasValidTransientUserGestureActivation()); + } + + loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE); + loadState->SetFirstParty(true); + + rv = bc->LoadURI(loadState); + if (NS_WARN_IF(NS_FAILED(rv))) { + if (rv == NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI && + net::SchemeIsJavascript(loadState->URI())) { + // Per spec[1], attempting to load a javascript: URI into a cross-origin + // BrowsingContext is a no-op, and should not raise an exception. + // Technically, Location setters run with exceptions enabled should only + // throw an exception[2] when the caller is not allowed to navigate[3] the + // target browsing context due to sandboxing flags or not being + // closely-related enough, though in practice we currently throw for other + // reasons as well. + // + // [1]: + // https://html.spec.whatwg.org/multipage/browsing-the-web.html#javascript-protocol + // [2]: + // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate + // [3]: + // https://html.spec.whatwg.org/multipage/browsers.html#allowed-to-navigate + return; + } + aRv.Throw(rv); + return; + } + + Document* doc = bc->GetDocument(); + if (doc && nsContentUtils::IsExternalProtocol(aURI)) { + doc->EnsureNotEnteringAndExitFullscreen(); + } +} + +void LocationBase::SetHref(const nsAString& aHref, + nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { + DoSetHref(aHref, aSubjectPrincipal, false, aRv); +} + +void LocationBase::DoSetHref(const nsAString& aHref, + nsIPrincipal& aSubjectPrincipal, bool aReplace, + ErrorResult& aRv) { + // Get the source of the caller + nsCOMPtr<nsIURI> base = GetSourceBaseURL(); + SetHrefWithBase(aHref, base, aSubjectPrincipal, aReplace, aRv); +} + +void LocationBase::SetHrefWithBase(const nsAString& aHref, nsIURI* aBase, + nsIPrincipal& aSubjectPrincipal, + bool aReplace, ErrorResult& aRv) { + nsresult result; + nsCOMPtr<nsIURI> newUri; + + if (Document* doc = GetEntryDocument()) { + result = NS_NewURI(getter_AddRefs(newUri), aHref, + doc->GetDocumentCharacterSet(), aBase); + } else { + result = NS_NewURI(getter_AddRefs(newUri), aHref, nullptr, aBase); + } + + if (newUri) { + /* Check with the scriptContext if it is currently processing a script tag. + * If so, this must be a <script> tag with a location.href in it. + * we want to do a replace load, in such a situation. + * In other cases, for example if a event handler or a JS timer + * had a location.href in it, we want to do a normal load, + * so that the new url will be appended to Session History. + * This solution is tricky. Hopefully it isn't going to bite + * anywhere else. This is part of solution for bug # 39938, 72197 + */ + bool inScriptTag = false; + nsIScriptContext* scriptContext = nullptr; + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(GetEntryGlobal()); + if (win) { + scriptContext = nsGlobalWindowInner::Cast(win)->GetContextInternal(); + } + + if (scriptContext) { + if (scriptContext->GetProcessingScriptTag()) { + // Now check to make sure that the script is running in our window, + // since we only want to replace if the location is set by a + // <script> tag in the same window. See bug 178729. + nsCOMPtr<nsIDocShell> docShell(GetDocShell()); + nsCOMPtr<nsIScriptGlobalObject> ourGlobal = + docShell ? docShell->GetScriptGlobalObject() : nullptr; + inScriptTag = (ourGlobal == scriptContext->GetGlobalObject()); + } + } + + SetURI(newUri, aSubjectPrincipal, aRv, aReplace || inScriptTag); + return; + } + + aRv.Throw(result); +} + +void LocationBase::Replace(const nsAString& aUrl, + nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { + DoSetHref(aUrl, aSubjectPrincipal, true, aRv); +} + +nsIURI* LocationBase::GetSourceBaseURL() { + Document* doc = GetEntryDocument(); + + // If there's no entry document, we either have no Script Entry Point or one + // that isn't a DOM Window. This doesn't generally happen with the DOM, but + // can sometimes happen with extension code in certain IPC configurations. If + // this happens, try falling back on the current document associated with the + // docshell. If that fails, just return null and hope that the caller passed + // an absolute URI. + if (!doc) { + if (nsCOMPtr<nsIDocShell> docShell = GetDocShell()) { + nsCOMPtr<nsPIDOMWindowOuter> docShellWin = + do_QueryInterface(docShell->GetScriptGlobalObject()); + if (docShellWin) { + doc = docShellWin->GetDoc(); + } + } + } + return doc ? doc->GetBaseURI() : nullptr; +} + +} // namespace mozilla::dom |