michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsLocation.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellLoadInfo.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsCDefaultURIFixup.h" michael@0: #include "nsIURIFixup.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIJARURI.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsEscape.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsError.h" michael@0: #include "nsDOMClassInfoID.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsITextToSubURI.h" michael@0: #include "nsJSUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: #include "nsNullPrincipal.h" michael@0: #include "ScriptSettings.h" michael@0: michael@0: static nsresult michael@0: GetDocumentCharacterSetForURI(const nsAString& aHref, nsACString& aCharset) michael@0: { michael@0: aCharset.Truncate(); michael@0: michael@0: JSContext *cx = nsContentUtils::GetCurrentJSContext(); michael@0: if (cx) { michael@0: nsCOMPtr window = michael@0: do_QueryInterface(nsJSUtils::GetDynamicScriptGlobal(cx)); michael@0: NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); michael@0: michael@0: if (nsIDocument* doc = window->GetDoc()) { michael@0: aCharset = doc->GetDocumentCharacterSet(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsLocation::nsLocation(nsIDocShell *aDocShell) michael@0: { michael@0: mDocShell = do_GetWeakReference(aDocShell); michael@0: nsCOMPtr outer = do_GetInterface(aDocShell); michael@0: mOuter = do_GetWeakReference(outer); michael@0: } michael@0: michael@0: nsLocation::~nsLocation() michael@0: { michael@0: } michael@0: michael@0: DOMCI_DATA(Location, nsLocation) michael@0: michael@0: // QueryInterface implementation for nsLocation michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsLocation) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMLocation) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Location) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsLocation) michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsLocation) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsLocation) michael@0: michael@0: void michael@0: nsLocation::SetDocShell(nsIDocShell *aDocShell) michael@0: { michael@0: mDocShell = do_GetWeakReference(aDocShell); michael@0: } michael@0: michael@0: nsIDocShell * michael@0: nsLocation::GetDocShell() michael@0: { michael@0: nsCOMPtr docshell(do_QueryReferent(mDocShell)); michael@0: return docshell; michael@0: } michael@0: michael@0: nsresult michael@0: nsLocation::CheckURL(nsIURI* aURI, nsIDocShellLoadInfo** aLoadInfo) michael@0: { michael@0: *aLoadInfo = nullptr; michael@0: michael@0: nsCOMPtr docShell(do_QueryReferent(mDocShell)); michael@0: NS_ENSURE_TRUE(docShell, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsCOMPtr owner; michael@0: nsCOMPtr sourceURI; michael@0: michael@0: if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) { michael@0: // No cx means that there's no JS running, or at least no JS that michael@0: // was run through code that properly pushed a context onto the michael@0: // context stack (as all code that runs JS off of web pages michael@0: // does). We won't bother with security checks in this case, but michael@0: // we need to create the loadinfo etc. michael@0: michael@0: // Get security manager. michael@0: nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); michael@0: NS_ENSURE_STATE(ssm); michael@0: michael@0: // Check to see if URI is allowed. michael@0: nsresult rv = ssm->CheckLoadURIFromScript(cx, aURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Make the load's referrer reflect changes to the document's URI caused by michael@0: // push/replaceState, if possible. First, get the document corresponding to michael@0: // fp. If the document's original URI (i.e. its URI before michael@0: // push/replaceState) matches the principal's URI, use the document's michael@0: // current URI as the referrer. If they don't match, use the principal's michael@0: // URI. michael@0: michael@0: nsCOMPtr doc; michael@0: nsCOMPtr docOriginalURI, docCurrentURI, principalURI; michael@0: nsCOMPtr incumbent = michael@0: do_QueryInterface(mozilla::dom::GetIncumbentGlobal()); michael@0: if (incumbent) { michael@0: doc = incumbent->GetDoc(); michael@0: } michael@0: if (doc) { michael@0: docOriginalURI = doc->GetOriginalURI(); michael@0: docCurrentURI = doc->GetDocumentURI(); michael@0: rv = doc->NodePrincipal()->GetURI(getter_AddRefs(principalURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: bool urisEqual = false; michael@0: if (docOriginalURI && docCurrentURI && principalURI) { michael@0: principalURI->Equals(docOriginalURI, &urisEqual); michael@0: } michael@0: michael@0: if (urisEqual) { michael@0: sourceURI = docCurrentURI; michael@0: } michael@0: else { michael@0: // Use principalURI as long as it is not an nsNullPrincipalURI. michael@0: // We could add a method such as GetReferrerURI to principals to make this michael@0: // cleaner, but given that we need to start using Source Browsing Context michael@0: // for referrer (see Bug 960639) this may be wasted effort at this stage. michael@0: if (principalURI) { michael@0: bool isNullPrincipalScheme; michael@0: rv = principalURI->SchemeIs(NS_NULLPRINCIPAL_SCHEME, michael@0: &isNullPrincipalScheme); michael@0: if (NS_SUCCEEDED(rv) && !isNullPrincipalScheme) { michael@0: sourceURI = principalURI; michael@0: } michael@0: } michael@0: } michael@0: michael@0: owner = do_QueryInterface(ssm->GetCxSubjectPrincipal(cx)); michael@0: } michael@0: michael@0: // Create load info michael@0: nsCOMPtr loadInfo; michael@0: docShell->CreateLoadInfo(getter_AddRefs(loadInfo)); michael@0: NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE); michael@0: michael@0: loadInfo->SetOwner(owner); michael@0: michael@0: if (sourceURI) { michael@0: loadInfo->SetReferrer(sourceURI); michael@0: } michael@0: michael@0: loadInfo.swap(*aLoadInfo); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsLocation::GetURI(nsIURI** aURI, bool aGetInnermostURI) michael@0: { michael@0: *aURI = nullptr; michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr docShell(do_QueryReferent(mDocShell)); michael@0: nsCOMPtr webNav(do_QueryInterface(docShell, &rv)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr uri; michael@0: rv = webNav->GetCurrentURI(getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // It is valid for docshell to return a null URI. Don't try to fixup michael@0: // if this happens. michael@0: if (!uri) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aGetInnermostURI) { michael@0: nsCOMPtr jarURI(do_QueryInterface(uri)); michael@0: while (jarURI) { michael@0: jarURI->GetJARFile(getter_AddRefs(uri)); michael@0: jarURI = do_QueryInterface(uri); michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(uri, "nsJARURI screwed up?"); michael@0: michael@0: nsCOMPtr urifixup(do_GetService(NS_URIFIXUP_CONTRACTID, &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return urifixup->CreateExposableURI(uri, aURI); michael@0: } michael@0: michael@0: nsresult michael@0: nsLocation::GetWritableURI(nsIURI** aURI) michael@0: { michael@0: *aURI = nullptr; michael@0: michael@0: nsCOMPtr uri; michael@0: michael@0: nsresult rv = GetURI(getter_AddRefs(uri)); michael@0: if (NS_FAILED(rv) || !uri) { michael@0: return rv; michael@0: } michael@0: michael@0: return uri->Clone(aURI); michael@0: } michael@0: michael@0: nsresult michael@0: nsLocation::SetURI(nsIURI* aURI, bool aReplace) michael@0: { michael@0: nsCOMPtr docShell(do_QueryReferent(mDocShell)); michael@0: if (docShell) { michael@0: nsCOMPtr loadInfo; michael@0: nsCOMPtr webNav(do_QueryInterface(docShell)); michael@0: michael@0: if(NS_FAILED(CheckURL(aURI, getter_AddRefs(loadInfo)))) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (aReplace) { michael@0: loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContentAndReplace); michael@0: } else { michael@0: loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContent); michael@0: } michael@0: michael@0: // Get the incumbent script's browsing context to set as source. michael@0: nsCOMPtr sourceWindow = michael@0: do_QueryInterface(mozilla::dom::GetIncumbentGlobal()); michael@0: if (sourceWindow) { michael@0: loadInfo->SetSourceDocShell(sourceWindow->GetDocShell()); michael@0: } michael@0: michael@0: return docShell->LoadURI(aURI, loadInfo, michael@0: nsIWebNavigation::LOAD_FLAGS_NONE, true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocation::GetHash(nsAString& aHash) michael@0: { michael@0: if (!CallerSubsumes()) michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: michael@0: aHash.SetLength(0); michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult rv = GetURI(getter_AddRefs(uri)); michael@0: if (NS_FAILED(rv) || !uri) { michael@0: return rv; michael@0: } michael@0: michael@0: nsAutoCString ref; michael@0: nsAutoString unicodeRef; michael@0: michael@0: rv = uri->GetRef(ref); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr textToSubURI( michael@0: do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv)); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsAutoCString charset; michael@0: uri->GetOriginCharset(charset); michael@0: michael@0: rv = textToSubURI->UnEscapeURIForUI(charset, ref, unicodeRef); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // Oh, well. No intl here! michael@0: NS_UnescapeURL(ref); michael@0: CopyASCIItoUTF16(ref, unicodeRef); michael@0: rv = NS_OK; michael@0: } michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv) && !unicodeRef.IsEmpty()) { michael@0: aHash.Assign(char16_t('#')); michael@0: aHash.Append(unicodeRef); michael@0: } michael@0: michael@0: if (aHash == mCachedHash) { michael@0: // Work around ShareThis stupidly polling location.hash every michael@0: // 5ms all the time by handing out the same exact string buffer michael@0: // we handed out last time. michael@0: aHash = mCachedHash; michael@0: } else { michael@0: mCachedHash = aHash; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocation::SetHash(const nsAString& aHash) michael@0: { michael@0: nsCOMPtr uri; michael@0: nsresult rv = GetWritableURI(getter_AddRefs(uri)); michael@0: if (NS_FAILED(rv) || !uri) { michael@0: return rv; michael@0: } michael@0: michael@0: NS_ConvertUTF16toUTF8 hash(aHash); michael@0: if (hash.IsEmpty() || hash.First() != char16_t('#')) { michael@0: hash.Insert(char16_t('#'), 0); michael@0: } michael@0: rv = uri->SetRef(hash); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: return SetURI(uri); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocation::GetHost(nsAString& aHost) michael@0: { michael@0: if (!CallerSubsumes()) michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: michael@0: aHost.Truncate(); michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult result; michael@0: michael@0: result = GetURI(getter_AddRefs(uri), true); michael@0: michael@0: if (uri) { michael@0: nsAutoCString hostport; michael@0: michael@0: result = uri->GetHostPort(hostport); michael@0: michael@0: if (NS_SUCCEEDED(result)) { michael@0: AppendUTF8toUTF16(hostport, aHost); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocation::SetHost(const nsAString& aHost) michael@0: { michael@0: if (!CallerSubsumes()) michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult rv = GetWritableURI(getter_AddRefs(uri)); michael@0: if (NS_WARN_IF(NS_FAILED(rv) || !uri)) { michael@0: return rv; michael@0: } michael@0: michael@0: rv = uri->SetHostPort(NS_ConvertUTF16toUTF8(aHost)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: return SetURI(uri); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocation::GetHostname(nsAString& aHostname) michael@0: { michael@0: if (!CallerSubsumes()) michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: michael@0: aHostname.Truncate(); michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult result; michael@0: michael@0: result = GetURI(getter_AddRefs(uri), true); michael@0: michael@0: if (uri) { michael@0: nsAutoCString host; michael@0: michael@0: result = uri->GetHost(host); michael@0: michael@0: if (NS_SUCCEEDED(result)) { michael@0: AppendUTF8toUTF16(host, aHostname); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocation::SetHostname(const nsAString& aHostname) michael@0: { michael@0: if (!CallerSubsumes()) michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult rv = GetWritableURI(getter_AddRefs(uri)); michael@0: if (NS_WARN_IF(NS_FAILED(rv) || !uri)) { michael@0: return rv; michael@0: } michael@0: michael@0: rv = uri->SetHost(NS_ConvertUTF16toUTF8(aHostname)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: michael@0: return SetURI(uri); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocation::GetHref(nsAString& aHref) michael@0: { michael@0: if (!CallerSubsumes()) michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: michael@0: aHref.Truncate(); michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult result; michael@0: michael@0: result = GetURI(getter_AddRefs(uri)); michael@0: michael@0: if (uri) { michael@0: nsAutoCString uriString; michael@0: michael@0: result = uri->GetSpec(uriString); michael@0: michael@0: if (NS_SUCCEEDED(result)) { michael@0: AppendUTF8toUTF16(uriString, aHref); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsLocation::SetHref(const nsAString& aHref) michael@0: { michael@0: nsAutoString oldHref; michael@0: nsresult rv = NS_OK; michael@0: michael@0: JSContext *cx = nsContentUtils::GetCurrentJSContext(); michael@0: if (cx) { michael@0: rv = SetHrefWithContext(cx, aHref, false); michael@0: } else { michael@0: rv = GetHref(oldHref); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr oldUri; michael@0: michael@0: rv = NS_NewURI(getter_AddRefs(oldUri), oldHref); michael@0: michael@0: if (oldUri) { michael@0: rv = SetHrefWithBase(aHref, oldUri, false); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsLocation::SetHrefWithContext(JSContext* cx, const nsAString& aHref, michael@0: bool aReplace) michael@0: { michael@0: nsCOMPtr base; michael@0: michael@0: // Get the source of the caller michael@0: nsresult result = GetSourceBaseURL(cx, getter_AddRefs(base)); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: return result; michael@0: } michael@0: michael@0: return SetHrefWithBase(aHref, base, aReplace); michael@0: } michael@0: michael@0: nsresult michael@0: nsLocation::SetHrefWithBase(const nsAString& aHref, nsIURI* aBase, michael@0: bool aReplace) michael@0: { michael@0: nsresult result; michael@0: nsCOMPtr newUri; michael@0: michael@0: nsCOMPtr docShell(do_QueryReferent(mDocShell)); michael@0: michael@0: nsAutoCString docCharset; michael@0: if (NS_SUCCEEDED(GetDocumentCharacterSetForURI(aHref, docCharset))) michael@0: result = NS_NewURI(getter_AddRefs(newUri), aHref, docCharset.get(), aBase); michael@0: else michael@0: result = NS_NewURI(getter_AddRefs(newUri), aHref, nullptr, aBase); michael@0: michael@0: if (newUri) { michael@0: /* Check with the scriptContext if it is currently processing a script tag. michael@0: * If so, this must be a