/* -*- 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 "Location.h" #include "nsIScriptSecurityManager.h" #include "nsIScriptObjectPrincipal.h" #include "nsIScriptContext.h" #include "nsIDocShell.h" #include "nsDocShellLoadState.h" #include "nsIWebNavigation.h" #include "nsIURIFixup.h" #include "nsIURL.h" #include "nsIURIMutator.h" #include "nsIJARURI.h" #include "nsNetUtil.h" #include "nsCOMPtr.h" #include "nsEscape.h" #include "nsIDOMWindow.h" #include "mozilla/dom/Document.h" #include "nsIPresShell.h" #include "nsPresContext.h" #include "nsError.h" #include "nsReadableUtils.h" #include "nsITextToSubURI.h" #include "nsJSUtils.h" #include "nsContentUtils.h" #include "nsGlobalWindow.h" #include "mozilla/Likely.h" #include "nsCycleCollectionParticipant.h" #include "mozilla/Components.h" #include "mozilla/NullPrincipal.h" #include "mozilla/Unused.h" #include "mozilla/dom/LocationBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "ReferrerInfo.h" namespace mozilla { namespace dom { Location::Location(nsPIDOMWindowInner* aWindow, nsIDocShell* aDocShell) : mInnerWindow(aWindow) { // aDocShell can be null if it gets called after nsDocShell::Destory(). mDocShell = do_GetWeakReference(aDocShell); } Location::~Location() {} // QueryInterface implementation for Location NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Location) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Location, mInnerWindow) NS_IMPL_CYCLE_COLLECTING_ADDREF(Location) NS_IMPL_CYCLE_COLLECTING_RELEASE(Location) already_AddRefed Location::CheckURL( nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { nsCOMPtr docShell(do_QueryReferent(mDocShell)); if (NS_WARN_IF(!docShell)) { aRv.Throw(NS_ERROR_NOT_AVAILABLE); return nullptr; } nsCOMPtr triggeringPrincipal; nsCOMPtr sourceURI; net::ReferrerPolicy referrerPolicy = net::RP_Unset; // 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. nsresult rv = ssm->CheckLoadURIWithPrincipal( &aSubjectPrincipal, aURI, nsIScriptSecurityManager::STANDARD); if (NS_WARN_IF(NS_FAILED(rv))) { nsAutoCString spec; aURI->GetSpec(spec); aRv.ThrowTypeError(NS_ConvertUTF8toUTF16(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; if (doc) { nsCOMPtr docOriginalURI, docCurrentURI, principalURI; docOriginalURI = doc->GetOriginalURI(); docCurrentURI = doc->GetDocumentURI(); rv = doc->NodePrincipal()->GetURI(getter_AddRefs(principalURI)); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } triggeringPrincipal = doc->NodePrincipal(); referrerPolicy = doc->GetReferrerPolicy(); bool urisEqual = false; if (docOriginalURI && docCurrentURI && principalURI) { principalURI->Equals(docOriginalURI, &urisEqual); } if (urisEqual) { sourceURI = docCurrentURI; } else { // Use principalURI as long as it is not an NullPrincipalURI. We // could add a method such as GetReferrerURI to principals to make this // cleaner, but given that we need to start using Source Browsing // Context for referrer (see Bug 960639) this may be wasted effort at // this stage. if (principalURI) { bool isNullPrincipalScheme; rv = principalURI->SchemeIs(NS_NULLPRINCIPAL_SCHEME, &isNullPrincipalScheme); if (NS_SUCCEEDED(rv) && !isNullPrincipalScheme) { sourceURI = principalURI; } } } } else { // No document; just use our subject principal as the triggering principal. triggeringPrincipal = &aSubjectPrincipal; } // Create load info RefPtr loadState = new nsDocShellLoadState(aURI); loadState->SetTriggeringPrincipal(triggeringPrincipal); // Currently we query the CSP from the triggeringPrincipal, which is the // doc->NodePrincipal() in case there is a doc. In that case we can query // the CSP directly from the doc after Bug 965637. In case there is no doc, // then we also do not need to query the CSP, because only documents can have // a CSP attached. nsCOMPtr csp; triggeringPrincipal->GetCsp(getter_AddRefs(csp)); loadState->SetCsp(csp); if (sourceURI) { nsCOMPtr referrerInfo = new ReferrerInfo(sourceURI, referrerPolicy); loadState->SetReferrerInfo(referrerInfo); } return loadState.forget(); } nsresult Location::GetURI(nsIURI** aURI, bool aGetInnermostURI) { *aURI = nullptr; nsCOMPtr docShell(do_QueryReferent(mDocShell)); if (!docShell) { return NS_OK; } nsresult rv; nsCOMPtr webNav(do_QueryInterface(docShell, &rv)); if (NS_FAILED(rv)) { return rv; } nsCOMPtr uri; rv = webNav->GetCurrentURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); // It is valid for docshell to return a null URI. Don't try to fixup // if this happens. if (!uri) { return NS_OK; } if (aGetInnermostURI) { nsCOMPtr jarURI(do_QueryInterface(uri)); while (jarURI) { jarURI->GetJARFile(getter_AddRefs(uri)); jarURI = do_QueryInterface(uri); } } NS_ASSERTION(uri, "nsJARURI screwed up?"); nsCOMPtr urifixup(components::URIFixup::Service()); return urifixup->CreateExposableURI(uri, aURI); } void Location::SetURI(nsIURI* aURI, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv, bool aReplace) { nsCOMPtr docShell(do_QueryReferent(mDocShell)); if (docShell) { 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 = do_QueryInterface(mozilla::dom::GetIncumbentGlobal()); if (sourceWindow) { loadState->SetSourceDocShell(sourceWindow->GetDocShell()); } loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE); loadState->SetFirstParty(true); nsresult rv = docShell->LoadURI(loadState); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); } } } void Location::GetHash(nsAString& aHash, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (!CallerSubsumes(&aSubjectPrincipal)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } aHash.SetLength(0); nsCOMPtr uri; aRv = GetURI(getter_AddRefs(uri)); if (NS_WARN_IF(aRv.Failed()) || !uri) { return; } nsAutoCString ref; nsAutoString unicodeRef; aRv = uri->GetRef(ref); if (NS_WARN_IF(aRv.Failed())) { return; } if (!ref.IsEmpty()) { aHash.Assign(char16_t('#')); AppendUTF8toUTF16(ref, aHash); } if (aHash == mCachedHash) { // Work around ShareThis stupidly polling location.hash every // 5ms all the time by handing out the same exact string buffer // we handed out last time. aHash = mCachedHash; } else { mCachedHash = aHash; } } void Location::SetHash(const nsAString& aHash, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (!CallerSubsumes(&aSubjectPrincipal)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } NS_ConvertUTF16toUTF8 hash(aHash); if (hash.IsEmpty() || hash.First() != char16_t('#')) { hash.Insert(char16_t('#'), 0); } nsCOMPtr uri; aRv = GetURI(getter_AddRefs(uri)); if (NS_WARN_IF(aRv.Failed()) || !uri) { return; } aRv = NS_MutateURI(uri).SetRef(hash).Finalize(uri); if (NS_WARN_IF(aRv.Failed()) || !uri) { return; } SetURI(uri, aSubjectPrincipal, aRv); } void Location::GetHost(nsAString& aHost, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (!CallerSubsumes(&aSubjectPrincipal)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } aHost.Truncate(); nsCOMPtr uri; nsresult result; result = GetURI(getter_AddRefs(uri), true); if (uri) { nsAutoCString hostport; result = uri->GetHostPort(hostport); if (NS_SUCCEEDED(result)) { AppendUTF8toUTF16(hostport, aHost); } } } void Location::SetHost(const nsAString& aHost, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (!CallerSubsumes(&aSubjectPrincipal)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } nsCOMPtr uri; aRv = GetURI(getter_AddRefs(uri)); if (NS_WARN_IF(aRv.Failed()) || !uri) { return; } aRv = NS_MutateURI(uri).SetHostPort(NS_ConvertUTF16toUTF8(aHost)).Finalize(uri); if (NS_WARN_IF(aRv.Failed())) { return; } SetURI(uri, aSubjectPrincipal, aRv); } void Location::GetHostname(nsAString& aHostname, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (!CallerSubsumes(&aSubjectPrincipal)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } aHostname.Truncate(); nsCOMPtr uri; GetURI(getter_AddRefs(uri), true); if (uri) { nsContentUtils::GetHostOrIPv6WithBrackets(uri, aHostname); } } void Location::SetHostname(const nsAString& aHostname, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { if (!CallerSubsumes(&aSubjectPrincipal)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } nsCOMPtr uri; aRv = GetURI(getter_AddRefs(uri)); if (NS_WARN_IF(aRv.Failed()) || !uri) { return; } aRv = NS_MutateURI(uri).SetHost(NS_ConvertUTF16toUTF8(aHostname)).Finalize(uri); if (NS_WARN_IF(aRv.Failed())) { return; } SetURI(uri, aSubjectPrincipal, aRv); } nsresult Location::GetHref(nsAString& aHref) { aHref.Truncate(); nsCOMPtr uri; nsresult rv = GetURI(getter_AddRefs(uri)); if (NS_WARN_IF(NS_FAILED(rv)) || !uri) { return rv; } nsAutoCString uriString; rv = uri->GetSpec(uriString); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } AppendUTF8toUTF16(uriString, aHref); return NS_OK; } void Location::SetHref(const nsAString& aHref, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) { DoSetHref(aHref, aSubjectPrincipal, false, aRv); } void Location::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 Location::SetHrefWithBase(const nsAString& aHref, nsIURI* aBase, nsIPrincipal& aSubjectPrincipal, bool aReplace, ErrorResult& aRv) { nsresult result; nsCOMPtr newUri; nsCOMPtr docShell(do_QueryReferent(mDocShell)); 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