/* -*- 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 LocationBase::CheckURL( nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { RefPtr bc(GetBrowsingContext()); if (NS_WARN_IF(!bc)) { aRv.Throw(NS_ERROR_NOT_AVAILABLE); return nullptr; } nsCOMPtr triggeringPrincipal; nsCOMPtr sourceURI; ReferrerPolicy referrerPolicy = ReferrerPolicy::_empty; nsCOMPtr 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(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 incumbent = do_QueryInterface(mozilla::dom::GetIncumbentGlobal()); nsCOMPtr doc = incumbent ? incumbent->GetDoc() : nullptr; // Create load info RefPtr loadState = new nsDocShellLoadState(aURI); if (!doc) { // No document; just use our subject principal as the triggering principal. loadState->SetTriggeringPrincipal(&aSubjectPrincipal); return loadState.forget(); } nsCOMPtr docOriginalURI, docCurrentURI, principalURI; docOriginalURI = doc->GetOriginalURI(); docCurrentURI = doc->GetDocumentURI(); nsCOMPtr 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 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 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 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 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 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