/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=4 sw=2 et 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 "nsCOMPtr.h" #include "jsapi.h" #include "js/Wrapper.h" #include "nsCRT.h" #include "nsError.h" #include "nsString.h" #include "nsGlobalWindowInner.h" #include "nsReadableUtils.h" #include "nsJSProtocolHandler.h" #include "nsStringStream.h" #include "nsNetUtil.h" #include "nsIClassInfoImpl.h" #include "nsIStreamListener.h" #include "nsIURI.h" #include "nsIScriptContext.h" #include "nsIScriptGlobalObject.h" #include "nsIPrincipal.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsPIDOMWindow.h" #include "nsEscape.h" #include "nsIWebNavigation.h" #include "nsIDocShell.h" #include "nsIContentViewer.h" #include "nsContentUtils.h" #include "nsJSUtils.h" #include "nsThreadUtils.h" #include "nsIScriptChannel.h" #include "mozilla/dom/Document.h" #include "nsIObjectInputStream.h" #include "nsIObjectOutputStream.h" #include "nsIWritablePropertyBag2.h" #include "nsIContentSecurityPolicy.h" #include "nsSandboxFlags.h" #include "nsTextToSubURI.h" #include "mozilla/BasePrincipal.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/dom/AutoEntryScript.h" #include "mozilla/dom/DOMSecurityMonitor.h" #include "mozilla/dom/JSExecutionContext.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/PopupBlocker.h" #include "nsContentSecurityManager.h" #include "mozilla/LoadInfo.h" #include "mozilla/Maybe.h" #include "mozilla/TextUtils.h" #include "mozilla/ipc/URIUtils.h" using mozilla::IsAscii; using mozilla::dom::AutoEntryScript; using mozilla::dom::JSExecutionContext; static NS_DEFINE_CID(kJSURICID, NS_JSURI_CID); class nsJSThunk : public nsIInputStream { public: nsJSThunk(); NS_DECL_THREADSAFE_ISUPPORTS NS_FORWARD_SAFE_NSIINPUTSTREAM(mInnerStream) nsresult Init(nsIURI* uri); nsresult EvaluateScript( nsIChannel* aChannel, mozilla::dom::PopupBlocker::PopupControlState aPopupState, uint32_t aExecutionPolicy, nsPIDOMWindowInner* aOriginalInnerWindow); protected: virtual ~nsJSThunk(); nsCOMPtr mInnerStream; nsCString mScript; nsCString mURL; }; // // nsISupports implementation... // NS_IMPL_ISUPPORTS(nsJSThunk, nsIInputStream) nsJSThunk::nsJSThunk() = default; nsJSThunk::~nsJSThunk() = default; nsresult nsJSThunk::Init(nsIURI* uri) { NS_ENSURE_ARG_POINTER(uri); // Get the script string to evaluate... nsresult rv = uri->GetPathQueryRef(mScript); if (NS_FAILED(rv)) return rv; // Get the url. rv = uri->GetSpec(mURL); if (NS_FAILED(rv)) return rv; return NS_OK; } static bool IsISO88591(const nsString& aString) { for (nsString::const_char_iterator c = aString.BeginReading(), c_end = aString.EndReading(); c < c_end; ++c) { if (*c > 255) return false; } return true; } static nsIScriptGlobalObject* GetGlobalObject(nsIChannel* aChannel) { // Get the global object owner from the channel nsCOMPtr docShell; NS_QueryNotificationCallbacks(aChannel, docShell); if (!docShell) { NS_WARNING("Unable to get a docShell from the channel!"); return nullptr; } // So far so good: get the script global from its docshell nsIScriptGlobalObject* global = docShell->GetScriptGlobalObject(); NS_ASSERTION(global, "Unable to get an nsIScriptGlobalObject from the " "docShell!"); return global; } static bool AllowedByCSP(nsIContentSecurityPolicy* aCSP, const nsACString& aJavaScriptURL) { if (!aCSP) { return true; } // https://w3c.github.io/webappsec-csp/#should-block-navigation-request // Step 3. If result is "Allowed", and if navigation request’s current URL’s // scheme is javascript: // // Step 3.1.1.2 If directive’s inline check returns "Allowed" when executed // upon null, "navigation" and navigation request’s current URL, skip to the // next directive. // // This means /type/ is "navigation" and /source string/ is the // "navigation request’s current URL". // // Per // https://w3c.github.io/webappsec-csp/#effective-directive-for-inline-check // type "navigation" maps to the effective directive script-src-elem. bool allowsInlineScript = true; nsresult rv = aCSP->GetAllowsInline(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE, true, // aHasUnsafeHash u""_ns, // aNonce true, // aParserCreated nullptr, // aElement, nullptr, // nsICSPEventListener NS_ConvertASCIItoUTF16(aJavaScriptURL), // aContent 0, // aLineNumber 0, // aColumnNumber &allowsInlineScript); return (NS_SUCCEEDED(rv) && allowsInlineScript); } nsresult nsJSThunk::EvaluateScript( nsIChannel* aChannel, mozilla::dom::PopupBlocker::PopupControlState aPopupState, uint32_t aExecutionPolicy, nsPIDOMWindowInner* aOriginalInnerWindow) { if (aExecutionPolicy == nsIScriptChannel::NO_EXECUTION) { // Nothing to do here. return NS_ERROR_DOM_RETVAL_UNDEFINED; } NS_ENSURE_ARG_POINTER(aChannel); MOZ_ASSERT(aOriginalInnerWindow, "We should not have gotten here if this was null!"); // Set the channel's resultPrincipalURI to the active document's URI. This // corresponds to treating that URI as the URI of our channel's response. In // the spec we're supposed to use the URL of the active document, but since // we bail out of here if the inner window has changed, and GetDocumentURI() // on the inner window returns the URL of the active document if the inner // window is current, this is equivalent to the spec behavior. nsCOMPtr docURI = aOriginalInnerWindow->GetDocumentURI(); if (!docURI) { // We're not going to be able to have a sane URL, so just don't run the // script at all. return NS_ERROR_DOM_RETVAL_UNDEFINED; } nsCOMPtr loadInfo = aChannel->LoadInfo(); loadInfo->SetResultPrincipalURI(docURI); #ifdef DEBUG DOMSecurityMonitor::AuditUseOfJavaScriptURI(aChannel); #endif // Get principal of code for execution nsCOMPtr owner; aChannel->GetOwner(getter_AddRefs(owner)); nsCOMPtr principal = do_QueryInterface(owner); if (!principal) { if (loadInfo->GetForceInheritPrincipal()) { principal = loadInfo->FindPrincipalToInherit(aChannel); } else { // No execution without a principal! NS_ASSERTION(!owner, "Non-principal owner?"); NS_WARNING("No principal to execute JS with"); return NS_ERROR_DOM_RETVAL_UNDEFINED; } } nsresult rv; // CSP check: javascript: URIs are disabled unless "inline" scripts // are allowed by both the CSP of the thing that started the load // (which is the CSPToInherit of the loadinfo) and the CSP of the // target document. The target document check is performed below, // once we have determined the target document. nsCOMPtr csp = loadInfo->GetCspToInherit(); if (!AllowedByCSP(csp, mURL)) { return NS_ERROR_DOM_RETVAL_UNDEFINED; } // Get the global object we should be running on. nsIScriptGlobalObject* global = GetGlobalObject(aChannel); if (!global) { return NS_ERROR_FAILURE; } // Make sure we still have the same inner window as we used to. nsCOMPtr win = do_QueryInterface(global); nsPIDOMWindowInner* innerWin = win->GetCurrentInnerWindow(); if (innerWin != aOriginalInnerWindow) { return NS_ERROR_UNEXPECTED; } mozilla::dom::Document* targetDoc = innerWin->GetExtantDoc(); // Sandboxed document check: javascript: URI execution is disabled in a // sandboxed document unless 'allow-scripts' was specified. if ((targetDoc && !targetDoc->IsScriptEnabled()) || (loadInfo->GetTriggeringSandboxFlags() & SANDBOXED_SCRIPTS)) { if (nsCOMPtr obs = mozilla::services::GetObserverService()) { obs->NotifyWhenScriptSafe(ToSupports(innerWin), "javascript-uri-blocked-by-sandbox"); } return NS_ERROR_DOM_RETVAL_UNDEFINED; } if (targetDoc) { // Perform a Security check against the CSP of the document we are // running against. javascript: URIs are disabled unless "inline" // scripts are allowed. We only do that if targetDoc->NodePrincipal() // subsumes loadInfo->TriggeringPrincipal(). If it doesn't, then // someone more-privileged (our UI or an extension) started the // load and hence the load should not be subject to the target // document's CSP. // // The "more privileged" assumption is safe, because if the triggering // principal does not subsume targetDoc->NodePrincipal() we won't run the // script at all. More precisely, we test that "principal" subsumes the // target's principal, but "principal" should never be higher-privilege // than the triggering principal here: it's either the triggering // principal, or the principal of the document we started the load // against if the triggering principal is system. if (targetDoc->NodePrincipal()->Subsumes(loadInfo->TriggeringPrincipal())) { nsCOMPtr targetCSP = targetDoc->GetCsp(); if (!AllowedByCSP(targetCSP, mURL)) { return NS_ERROR_DOM_RETVAL_UNDEFINED; } } } // Push our popup control state AutoPopupStatePusher popupStatePusher(aPopupState); nsCOMPtr innerGlobal = do_QueryInterface(innerWin); // So far so good: get the script context from its owner. nsCOMPtr scriptContext = global->GetContext(); if (!scriptContext) return NS_ERROR_FAILURE; // New script entry point required, due to the "Create a script" step of // http://www.whatwg.org/specs/web-apps/current-work/#javascript-protocol mozilla::nsAutoMicroTask mt; AutoEntryScript aes(innerGlobal, "javascript: URI", true); JSContext* cx = aes.cx(); JS::Rooted globalJSObject(cx, innerGlobal->GetGlobalJSObject()); NS_ENSURE_TRUE(globalJSObject, NS_ERROR_UNEXPECTED); //-- Don't execute unless the script principal subsumes the // principal of the context. nsIPrincipal* objectPrincipal = nsContentUtils::ObjectPrincipal(globalJSObject); bool subsumes; rv = principal->Subsumes(objectPrincipal, &subsumes); if (NS_FAILED(rv)) return rv; if (!subsumes) { return NS_ERROR_DOM_RETVAL_UNDEFINED; } // Fail if someone tries to execute in a global with system principal. if (objectPrincipal->IsSystemPrincipal()) { return NS_ERROR_DOM_SECURITY_ERR; } nsAutoCString script(mScript); // Unescape the script NS_UnescapeURL(script); JS::Rooted v(cx, JS::UndefinedValue()); // Finally, we have everything needed to evaluate the expression. JS::CompileOptions options(cx); options.setFileAndLine(mURL.get(), 1); options.setIntroductionType("javascriptURL"); { JSExecutionContext exec(cx, globalJSObject, options); exec.SetCoerceToString(true); exec.Compile(NS_ConvertUTF8toUTF16(script)); rv = exec.ExecScript(&v); } js::AssertSameCompartment(cx, v); if (NS_FAILED(rv) || !(v.isString() || v.isUndefined())) { return NS_ERROR_MALFORMED_URI; } if (v.isUndefined()) { return NS_ERROR_DOM_RETVAL_UNDEFINED; } MOZ_ASSERT(rv != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW, "How did we get a non-undefined return value?"); nsAutoJSString result; if (!result.init(cx, v)) { return NS_ERROR_OUT_OF_MEMORY; } char* bytes; uint32_t bytesLen; constexpr auto isoCharset = "windows-1252"_ns; constexpr auto utf8Charset = "UTF-8"_ns; const nsLiteralCString* charset; if (IsISO88591(result)) { // For compatibility, if the result is ISO-8859-1, we use // windows-1252, so that people can compatibly create images // using javascript: URLs. bytes = ToNewCString(result, mozilla::fallible); bytesLen = result.Length(); charset = &isoCharset; } else { bytes = ToNewUTF8String(result, &bytesLen); charset = &utf8Charset; } aChannel->SetContentCharset(*charset); if (bytes) { rv = NS_NewByteInputStream(getter_AddRefs(mInnerStream), mozilla::Span(bytes, bytesLen), NS_ASSIGNMENT_ADOPT); } else { rv = NS_ERROR_OUT_OF_MEMORY; } return rv; } //////////////////////////////////////////////////////////////////////////////// class nsJSChannel : public nsIChannel, public nsIStreamListener, public nsIScriptChannel, public nsIPropertyBag2 { public: nsJSChannel(); NS_DECL_ISUPPORTS NS_DECL_NSIREQUEST NS_DECL_NSICHANNEL NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER NS_DECL_NSISCRIPTCHANNEL NS_FORWARD_SAFE_NSIPROPERTYBAG(mPropertyBag) NS_FORWARD_SAFE_NSIPROPERTYBAG2(mPropertyBag) nsresult Init(nsIURI* aURI, nsILoadInfo* aLoadInfo); // Actually evaluate the script. void EvaluateScript(); protected: virtual ~nsJSChannel(); nsresult StopAll(); void NotifyListener(); void CleanupStrongRefs(); nsCOMPtr mStreamChannel; nsCOMPtr mPropertyBag; nsCOMPtr mListener; // Our final listener nsCOMPtr mOriginalInnerWindow; // The inner window our // load started against. // If we blocked onload on a document in AsyncOpen, this is the document we // did it on. RefPtr mDocumentOnloadBlockedOn; nsresult mStatus; // Our status nsLoadFlags mLoadFlags; nsLoadFlags mActualLoadFlags; // See AsyncOpen RefPtr mIOThunk; mozilla::dom::PopupBlocker::PopupControlState mPopupState; uint32_t mExecutionPolicy; bool mIsAsync; bool mIsActive; bool mOpenedStreamChannel; }; nsJSChannel::nsJSChannel() : mStatus(NS_OK), mLoadFlags(LOAD_NORMAL), mActualLoadFlags(LOAD_NORMAL), mPopupState(mozilla::dom::PopupBlocker::openOverridden), mExecutionPolicy(NO_EXECUTION), mIsAsync(true), mIsActive(false), mOpenedStreamChannel(false) {} nsJSChannel::~nsJSChannel() = default; nsresult nsJSChannel::StopAll() { nsresult rv = NS_ERROR_UNEXPECTED; nsCOMPtr webNav; NS_QueryNotificationCallbacks(mStreamChannel, webNav); NS_ASSERTION(webNav, "Can't get nsIWebNavigation from channel!"); if (webNav) { rv = webNav->Stop(nsIWebNavigation::STOP_ALL); } return rv; } nsresult nsJSChannel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) { RefPtr jsURI; nsresult rv = aURI->QueryInterface(kJSURICID, getter_AddRefs(jsURI)); NS_ENSURE_SUCCESS(rv, rv); // Create the nsIStreamIO layer used by the nsIStreamIOChannel. mIOThunk = new nsJSThunk(); // Create a stock input stream channel... // Remember, until AsyncOpen is called, the script will not be evaluated // and the underlying Input Stream will not be created... nsCOMPtr channel; RefPtr thunk = mIOThunk; rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI, thunk.forget(), "text/html"_ns, ""_ns, aLoadInfo); NS_ENSURE_SUCCESS(rv, rv); rv = mIOThunk->Init(aURI); if (NS_SUCCEEDED(rv)) { mStreamChannel = channel; mPropertyBag = do_QueryInterface(channel); nsCOMPtr writableBag = do_QueryInterface(channel); if (writableBag && jsURI->GetBaseURI()) { writableBag->SetPropertyAsInterface(u"baseURI"_ns, jsURI->GetBaseURI()); } } return rv; } NS_IMETHODIMP nsJSChannel::GetIsDocument(bool* aIsDocument) { return NS_GetIsDocumentChannel(this, aIsDocument); } // // nsISupports implementation... // NS_IMPL_ISUPPORTS(nsJSChannel, nsIChannel, nsIRequest, nsIRequestObserver, nsIStreamListener, nsIScriptChannel, nsIPropertyBag, nsIPropertyBag2) // // nsIRequest implementation... // NS_IMETHODIMP nsJSChannel::GetName(nsACString& aResult) { return mStreamChannel->GetName(aResult); } NS_IMETHODIMP nsJSChannel::IsPending(bool* aResult) { *aResult = mIsActive; return NS_OK; } NS_IMETHODIMP nsJSChannel::GetStatus(nsresult* aResult) { if (NS_SUCCEEDED(mStatus) && mOpenedStreamChannel) { return mStreamChannel->GetStatus(aResult); } *aResult = mStatus; return NS_OK; } NS_IMETHODIMP nsJSChannel::SetCanceledReason(const nsACString& aReason) { return SetCanceledReasonImpl(aReason); } NS_IMETHODIMP nsJSChannel::GetCanceledReason(nsACString& aReason) { return GetCanceledReasonImpl(aReason); } NS_IMETHODIMP nsJSChannel::CancelWithReason(nsresult aStatus, const nsACString& aReason) { return CancelWithReasonImpl(aStatus, aReason); } NS_IMETHODIMP nsJSChannel::Cancel(nsresult aStatus) { mStatus = aStatus; if (mOpenedStreamChannel) { mStreamChannel->Cancel(aStatus); } return NS_OK; } NS_IMETHODIMP nsJSChannel::GetCanceled(bool* aCanceled) { nsresult status = NS_ERROR_FAILURE; GetStatus(&status); *aCanceled = NS_FAILED(status); return NS_OK; } NS_IMETHODIMP nsJSChannel::Suspend() { return mStreamChannel->Suspend(); } NS_IMETHODIMP nsJSChannel::Resume() { return mStreamChannel->Resume(); } // // nsIChannel implementation // NS_IMETHODIMP nsJSChannel::GetOriginalURI(nsIURI** aURI) { return mStreamChannel->GetOriginalURI(aURI); } NS_IMETHODIMP nsJSChannel::SetOriginalURI(nsIURI* aURI) { return mStreamChannel->SetOriginalURI(aURI); } NS_IMETHODIMP nsJSChannel::GetURI(nsIURI** aURI) { return mStreamChannel->GetURI(aURI); } NS_IMETHODIMP nsJSChannel::Open(nsIInputStream** aStream) { nsCOMPtr listener; nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); NS_ENSURE_SUCCESS(rv, rv); rv = mIOThunk->EvaluateScript(mStreamChannel, mPopupState, mExecutionPolicy, mOriginalInnerWindow); NS_ENSURE_SUCCESS(rv, rv); return mStreamChannel->Open(aStream); } NS_IMETHODIMP nsJSChannel::AsyncOpen(nsIStreamListener* aListener) { NS_ENSURE_ARG(aListener); nsCOMPtr listener = aListener; nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); NS_ENSURE_SUCCESS(rv, rv); #ifdef DEBUG { nsCOMPtr loadInfo = nsIChannel::LoadInfo(); MOZ_ASSERT(!loadInfo || loadInfo->GetSecurityMode() == 0 || loadInfo->GetInitialSecurityCheckDone(), "security flags in loadInfo but asyncOpen() not called"); } #endif // First make sure that we have a usable inner window; we'll want to make // sure that we execute against that inner and no other. nsIScriptGlobalObject* global = GetGlobalObject(this); if (!global) { return NS_ERROR_NOT_AVAILABLE; } nsCOMPtr win(do_QueryInterface(global)); NS_ASSERTION(win, "Our global is not a window??"); // Make sure we create a new inner window if one doesn't already exist (see // bug 306630). mOriginalInnerWindow = win->EnsureInnerWindow(); if (!mOriginalInnerWindow) { return NS_ERROR_NOT_AVAILABLE; } mListener = aListener; mIsActive = true; // Temporarily set the LOAD_BACKGROUND flag to suppress load group observer // notifications (and hence nsIWebProgressListener notifications) from // being dispatched. This is required since we suppress LOAD_DOCUMENT_URI, // which means that the DocLoader would not generate document start and // stop notifications (see bug 257875). mActualLoadFlags = mLoadFlags; mLoadFlags |= LOAD_BACKGROUND; // Add the javascript channel to its loadgroup so that we know if // network loads were canceled or not... nsCOMPtr loadGroup; mStreamChannel->GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) { rv = loadGroup->AddRequest(this, nullptr); if (NS_FAILED(rv)) { mIsActive = false; CleanupStrongRefs(); return rv; } } mDocumentOnloadBlockedOn = mOriginalInnerWindow->GetExtantDoc(); if (mDocumentOnloadBlockedOn) { // If we're a document channel, we need to actually block onload on our // _parent_ document. This is because we don't actually set our // LOAD_DOCUMENT_URI flag, so a docloader we're loading in as the // document channel will claim to not be busy, and our parent's onload // could fire too early. nsLoadFlags loadFlags; mStreamChannel->GetLoadFlags(&loadFlags); if (loadFlags & LOAD_DOCUMENT_URI) { mDocumentOnloadBlockedOn = mDocumentOnloadBlockedOn->GetInProcessParentDocument(); } } if (mDocumentOnloadBlockedOn) { mDocumentOnloadBlockedOn->BlockOnload(); } mPopupState = mozilla::dom::PopupBlocker::GetPopupControlState(); void (nsJSChannel::*method)(); const char* name; if (mIsAsync) { // post an event to do the rest method = &nsJSChannel::EvaluateScript; name = "nsJSChannel::EvaluateScript"; } else { EvaluateScript(); if (mOpenedStreamChannel) { // That will handle notifying things return NS_OK; } NS_ASSERTION(NS_FAILED(mStatus), "We should have failed _somehow_"); // mStatus is going to be NS_ERROR_DOM_RETVAL_UNDEFINED if we didn't // have any content resulting from the execution and NS_BINDING_ABORTED // if something we did causes our own load to be stopped. Return // success in those cases, and error out in all others. if (mStatus != NS_ERROR_DOM_RETVAL_UNDEFINED && mStatus != NS_BINDING_ABORTED) { // Note that calling EvaluateScript() handled removing us from the // loadgroup and marking us as not active anymore. CleanupStrongRefs(); return mStatus; } // We're returning success from asyncOpen(), but we didn't open a // stream channel. We'll have to notify ourselves, but make sure to do // it asynchronously. method = &nsJSChannel::NotifyListener; name = "nsJSChannel::NotifyListener"; } nsCOMPtr runnable = mozilla::NewRunnableMethod(name, this, method); nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(mOriginalInnerWindow); rv = window->Dispatch(mozilla::TaskCategory::Other, runnable.forget()); if (NS_FAILED(rv)) { loadGroup->RemoveRequest(this, nullptr, rv); mIsActive = false; CleanupStrongRefs(); } return rv; } void nsJSChannel::EvaluateScript() { // Synchronously execute the script... // mIsActive is used to indicate the the request is 'busy' during the // the script evaluation phase. This means that IsPending() will // indicate the the request is busy while the script is executing... // Note that we want to be in the loadgroup and pending while we evaluate // the script, so that we find out if the loadgroup gets canceled by the // script execution (in which case we shouldn't pump out data even if the // script returns it). if (NS_SUCCEEDED(mStatus)) { nsresult rv = mIOThunk->EvaluateScript( mStreamChannel, mPopupState, mExecutionPolicy, mOriginalInnerWindow); // Note that evaluation may have canceled us, so recheck mStatus again if (NS_FAILED(rv) && NS_SUCCEEDED(mStatus)) { mStatus = rv; } } // Remove the javascript channel from its loadgroup... nsCOMPtr loadGroup; mStreamChannel->GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) { loadGroup->RemoveRequest(this, nullptr, mStatus); } // Reset load flags to their original value... mLoadFlags = mActualLoadFlags; // We're no longer active, it's now up to the stream channel to do // the loading, if needed. mIsActive = false; if (NS_FAILED(mStatus)) { if (mIsAsync) { NotifyListener(); } return; } // EvaluateScript() succeeded, and we were not canceled, that // means there's data to parse as a result of evaluating the // script. // Get the stream channels load flags (!= mLoadFlags). nsLoadFlags loadFlags; mStreamChannel->GetLoadFlags(&loadFlags); uint32_t disposition; if (NS_FAILED(mStreamChannel->GetContentDisposition(&disposition))) disposition = nsIChannel::DISPOSITION_INLINE; if (loadFlags & LOAD_DOCUMENT_URI && disposition != nsIChannel::DISPOSITION_ATTACHMENT) { // We're loaded as the document channel and not expecting to download // the result. If we go on, we'll blow away the current document. Make // sure that's ok. If so, stop all pending network loads. nsCOMPtr docShell; NS_QueryNotificationCallbacks(mStreamChannel, docShell); if (docShell) { nsCOMPtr cv; docShell->GetContentViewer(getter_AddRefs(cv)); if (cv) { bool okToUnload; if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && !okToUnload) { // The user didn't want to unload the current // page, translate this into an undefined // return from the javascript: URL... mStatus = NS_ERROR_DOM_RETVAL_UNDEFINED; } } } if (NS_SUCCEEDED(mStatus)) { mStatus = StopAll(); } } if (NS_FAILED(mStatus)) { if (mIsAsync) { NotifyListener(); } return; } mStatus = mStreamChannel->AsyncOpen(this); if (NS_SUCCEEDED(mStatus)) { // mStreamChannel will call OnStartRequest and OnStopRequest on // us, so we'll be sure to call them on our listener. mOpenedStreamChannel = true; // Now readd ourselves to the loadgroup so we can receive // cancellation notifications. mIsActive = true; if (loadGroup) { mStatus = loadGroup->AddRequest(this, nullptr); // If AddRequest failed, that's OK. The key is to make sure we get // cancelled if needed, and that call just canceled us if it // failed. We'll still get notified by the stream channel when it // finishes. } } else if (mIsAsync) { NotifyListener(); } } void nsJSChannel::NotifyListener() { mListener->OnStartRequest(this); mListener->OnStopRequest(this, mStatus); CleanupStrongRefs(); } void nsJSChannel::CleanupStrongRefs() { mListener = nullptr; mOriginalInnerWindow = nullptr; if (mDocumentOnloadBlockedOn) { mDocumentOnloadBlockedOn->UnblockOnload(false); mDocumentOnloadBlockedOn = nullptr; } } NS_IMETHODIMP nsJSChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) { *aLoadFlags = mLoadFlags; return NS_OK; } NS_IMETHODIMP nsJSChannel::SetLoadFlags(nsLoadFlags aLoadFlags) { // Figure out whether the LOAD_BACKGROUND bit in aLoadFlags is // actually right. bool bogusLoadBackground = false; if (mIsActive && !(mActualLoadFlags & LOAD_BACKGROUND) && (aLoadFlags & LOAD_BACKGROUND)) { // We're getting a LOAD_BACKGROUND, but it's probably just our own fake // flag being mirrored to us. The one exception is if our loadgroup is // LOAD_BACKGROUND. bool loadGroupIsBackground = false; nsCOMPtr loadGroup; mStreamChannel->GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) { nsLoadFlags loadGroupFlags; loadGroup->GetLoadFlags(&loadGroupFlags); loadGroupIsBackground = ((loadGroupFlags & LOAD_BACKGROUND) != 0); } bogusLoadBackground = !loadGroupIsBackground; } // Since the javascript channel is never the actual channel that // any data is loaded through, don't ever set the // LOAD_DOCUMENT_URI flag on it, since that could lead to two // 'document channels' in the loadgroup if a javascript: URL is // loaded while a document is being loaded in the same window. // XXXbz this, and a whole lot of other hackery, could go away if we'd just // cancel the current document load on javascript: load start like IE does. mLoadFlags = aLoadFlags & ~LOAD_DOCUMENT_URI; if (bogusLoadBackground) { aLoadFlags = aLoadFlags & ~LOAD_BACKGROUND; } mActualLoadFlags = aLoadFlags; // ... but the underlying stream channel should get this bit, if // set, since that'll be the real document channel if the // javascript: URL generated data. return mStreamChannel->SetLoadFlags(aLoadFlags); } NS_IMETHODIMP nsJSChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { return GetTRRModeImpl(aTRRMode); } NS_IMETHODIMP nsJSChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) { return SetTRRModeImpl(aTRRMode); } NS_IMETHODIMP nsJSChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) { return mStreamChannel->GetLoadGroup(aLoadGroup); } NS_IMETHODIMP nsJSChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) { if (aLoadGroup) { bool streamPending; nsresult rv = mStreamChannel->IsPending(&streamPending); NS_ENSURE_SUCCESS(rv, rv); if (streamPending) { nsCOMPtr curLoadGroup; mStreamChannel->GetLoadGroup(getter_AddRefs(curLoadGroup)); if (aLoadGroup != curLoadGroup) { // Move the stream channel to our new loadgroup. Make sure to // add it before removing it, so that we don't trigger onload // by accident. aLoadGroup->AddRequest(mStreamChannel, nullptr); if (curLoadGroup) { curLoadGroup->RemoveRequest(mStreamChannel, nullptr, NS_BINDING_RETARGETED); } } } } return mStreamChannel->SetLoadGroup(aLoadGroup); } NS_IMETHODIMP nsJSChannel::GetOwner(nsISupports** aOwner) { return mStreamChannel->GetOwner(aOwner); } NS_IMETHODIMP nsJSChannel::SetOwner(nsISupports* aOwner) { return mStreamChannel->SetOwner(aOwner); } NS_IMETHODIMP nsJSChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) { return mStreamChannel->GetLoadInfo(aLoadInfo); } NS_IMETHODIMP nsJSChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) { MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null"); return mStreamChannel->SetLoadInfo(aLoadInfo); } NS_IMETHODIMP nsJSChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) { return mStreamChannel->GetNotificationCallbacks(aCallbacks); } NS_IMETHODIMP nsJSChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) { return mStreamChannel->SetNotificationCallbacks(aCallbacks); } NS_IMETHODIMP nsJSChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) { return mStreamChannel->GetSecurityInfo(aSecurityInfo); } NS_IMETHODIMP nsJSChannel::GetContentType(nsACString& aContentType) { return mStreamChannel->GetContentType(aContentType); } NS_IMETHODIMP nsJSChannel::SetContentType(const nsACString& aContentType) { return mStreamChannel->SetContentType(aContentType); } NS_IMETHODIMP nsJSChannel::GetContentCharset(nsACString& aContentCharset) { return mStreamChannel->GetContentCharset(aContentCharset); } NS_IMETHODIMP nsJSChannel::SetContentCharset(const nsACString& aContentCharset) { return mStreamChannel->SetContentCharset(aContentCharset); } NS_IMETHODIMP nsJSChannel::GetContentDisposition(uint32_t* aContentDisposition) { return mStreamChannel->GetContentDisposition(aContentDisposition); } NS_IMETHODIMP nsJSChannel::SetContentDisposition(uint32_t aContentDisposition) { return mStreamChannel->SetContentDisposition(aContentDisposition); } NS_IMETHODIMP nsJSChannel::GetContentDispositionFilename( nsAString& aContentDispositionFilename) { return mStreamChannel->GetContentDispositionFilename( aContentDispositionFilename); } NS_IMETHODIMP nsJSChannel::SetContentDispositionFilename( const nsAString& aContentDispositionFilename) { return mStreamChannel->SetContentDispositionFilename( aContentDispositionFilename); } NS_IMETHODIMP nsJSChannel::GetContentDispositionHeader( nsACString& aContentDispositionHeader) { return mStreamChannel->GetContentDispositionHeader(aContentDispositionHeader); } NS_IMETHODIMP nsJSChannel::GetContentLength(int64_t* aContentLength) { return mStreamChannel->GetContentLength(aContentLength); } NS_IMETHODIMP nsJSChannel::SetContentLength(int64_t aContentLength) { return mStreamChannel->SetContentLength(aContentLength); } NS_IMETHODIMP nsJSChannel::OnStartRequest(nsIRequest* aRequest) { NS_ENSURE_TRUE(aRequest == mStreamChannel, NS_ERROR_UNEXPECTED); return mListener->OnStartRequest(this); } NS_IMETHODIMP nsJSChannel::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream, uint64_t aOffset, uint32_t aCount) { NS_ENSURE_TRUE(aRequest == mStreamChannel, NS_ERROR_UNEXPECTED); return mListener->OnDataAvailable(this, aInputStream, aOffset, aCount); } NS_IMETHODIMP nsJSChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { NS_ENSURE_TRUE(aRequest == mStreamChannel, NS_ERROR_UNEXPECTED); nsCOMPtr listener = mListener; CleanupStrongRefs(); // Make sure aStatus matches what GetStatus() returns if (NS_FAILED(mStatus)) { aStatus = mStatus; } nsresult rv = listener->OnStopRequest(this, aStatus); nsCOMPtr loadGroup; mStreamChannel->GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) { loadGroup->RemoveRequest(this, nullptr, mStatus); } mIsActive = false; return rv; } NS_IMETHODIMP nsJSChannel::SetExecutionPolicy(uint32_t aPolicy) { NS_ENSURE_ARG(aPolicy <= EXECUTE_NORMAL); mExecutionPolicy = aPolicy; return NS_OK; } NS_IMETHODIMP nsJSChannel::GetExecutionPolicy(uint32_t* aPolicy) { *aPolicy = mExecutionPolicy; return NS_OK; } NS_IMETHODIMP nsJSChannel::SetExecuteAsync(bool aIsAsync) { if (!mIsActive) { mIsAsync = aIsAsync; } // else ignore this call NS_WARNING_ASSERTION(!mIsActive, "Calling SetExecuteAsync on active channel?"); return NS_OK; } NS_IMETHODIMP nsJSChannel::GetExecuteAsync(bool* aIsAsync) { *aIsAsync = mIsAsync; return NS_OK; } bool nsJSChannel::GetIsDocumentLoad() { // Our LOAD_DOCUMENT_URI flag, if any, lives on our stream channel. nsLoadFlags flags; mStreamChannel->GetLoadFlags(&flags); return flags & LOAD_DOCUMENT_URI; } //////////////////////////////////////////////////////////////////////////////// nsJSProtocolHandler::nsJSProtocolHandler() = default; nsJSProtocolHandler::~nsJSProtocolHandler() = default; NS_IMPL_ISUPPORTS(nsJSProtocolHandler, nsIProtocolHandler) /* static */ nsresult nsJSProtocolHandler::EnsureUTF8Spec( const nsCString& aSpec, const char* aCharset, nsACString& aUTF8Spec) { aUTF8Spec.Truncate(); nsAutoString uStr; nsresult rv = nsTextToSubURI::UnEscapeNonAsciiURI( nsDependentCString(aCharset), aSpec, uStr); NS_ENSURE_SUCCESS(rv, rv); if (!IsAscii(uStr)) { rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(uStr), esc_AlwaysCopy | esc_OnlyNonASCII, aUTF8Spec, mozilla::fallible); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // nsIProtocolHandler methods: NS_IMETHODIMP nsJSProtocolHandler::GetScheme(nsACString& result) { result = "javascript"; return NS_OK; } /* static */ nsresult nsJSProtocolHandler::CreateNewURI(const nsACString& aSpec, const char* aCharset, nsIURI* aBaseURI, nsIURI** result) { nsresult rv = NS_OK; // javascript: URLs (currently) have no additional structure beyond that // provided by standard URLs, so there is no "outer" object given to // CreateInstance. NS_MutateURI mutator(new nsJSURI::Mutator()); nsCOMPtr base(aBaseURI); mutator.Apply(&nsIJSURIMutator::SetBase, base); if (!aCharset || !nsCRT::strcasecmp("UTF-8", aCharset)) { mutator.SetSpec(aSpec); } else { nsAutoCString utf8Spec; rv = EnsureUTF8Spec(PromiseFlatCString(aSpec), aCharset, utf8Spec); if (NS_FAILED(rv)) { return rv; } if (utf8Spec.IsEmpty()) { mutator.SetSpec(aSpec); } else { mutator.SetSpec(utf8Spec); } } nsCOMPtr url; rv = mutator.Finalize(url); if (NS_FAILED(rv)) { return rv; } url.forget(result); return rv; } NS_IMETHODIMP nsJSProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, nsIChannel** result) { nsresult rv; NS_ENSURE_ARG_POINTER(uri); RefPtr channel = new nsJSChannel(); if (!channel) { return NS_ERROR_OUT_OF_MEMORY; } rv = channel->Init(uri, aLoadInfo); NS_ENSURE_SUCCESS(rv, rv); if (NS_SUCCEEDED(rv)) { channel.forget(result); } return rv; } NS_IMETHODIMP nsJSProtocolHandler::AllowPort(int32_t port, const char* scheme, bool* _retval) { // don't override anything. *_retval = false; return NS_OK; } //////////////////////////////////////////////////////////// // nsJSURI implementation static NS_DEFINE_CID(kThisSimpleURIImplementationCID, NS_THIS_SIMPLEURI_IMPLEMENTATION_CID); NS_IMPL_ADDREF_INHERITED(nsJSURI, mozilla::net::nsSimpleURI) NS_IMPL_RELEASE_INHERITED(nsJSURI, mozilla::net::nsSimpleURI) NS_IMPL_CLASSINFO(nsJSURI, nullptr, nsIClassInfo::THREADSAFE, NS_JSURI_CID); // Empty CI getter. We only need nsIClassInfo for Serialization NS_IMPL_CI_INTERFACE_GETTER0(nsJSURI) NS_INTERFACE_MAP_BEGIN(nsJSURI) if (aIID.Equals(kJSURICID)) foundInterface = static_cast(this); else if (aIID.Equals(kThisSimpleURIImplementationCID)) { // Need to return explicitly here, because if we just set foundInterface // to null the NS_INTERFACE_MAP_END_INHERITING will end up calling into // nsSimplURI::QueryInterface and finding something for this CID. *aInstancePtr = nullptr; return NS_NOINTERFACE; } else NS_IMPL_QUERY_CLASSINFO(nsJSURI) NS_INTERFACE_MAP_END_INHERITING(mozilla::net::nsSimpleURI) // nsISerializable methods: NS_IMETHODIMP nsJSURI::Read(nsIObjectInputStream* aStream) { MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead"); return NS_ERROR_NOT_IMPLEMENTED; } nsresult nsJSURI::ReadPrivate(nsIObjectInputStream* aStream) { nsresult rv = mozilla::net::nsSimpleURI::ReadPrivate(aStream); if (NS_FAILED(rv)) return rv; bool haveBase; rv = aStream->ReadBoolean(&haveBase); if (NS_FAILED(rv)) return rv; if (haveBase) { nsCOMPtr supports; rv = aStream->ReadObject(true, getter_AddRefs(supports)); if (NS_FAILED(rv)) return rv; mBaseURI = do_QueryInterface(supports); } return NS_OK; } NS_IMETHODIMP nsJSURI::Write(nsIObjectOutputStream* aStream) { nsresult rv = mozilla::net::nsSimpleURI::Write(aStream); if (NS_FAILED(rv)) return rv; rv = aStream->WriteBoolean(mBaseURI != nullptr); if (NS_FAILED(rv)) return rv; if (mBaseURI) { rv = aStream->WriteObject(mBaseURI, true); if (NS_FAILED(rv)) return rv; } return NS_OK; } NS_IMETHODIMP_(void) nsJSURI::Serialize(mozilla::ipc::URIParams& aParams) { using namespace mozilla::ipc; JSURIParams jsParams; URIParams simpleParams; mozilla::net::nsSimpleURI::Serialize(simpleParams); jsParams.simpleParams() = simpleParams; if (mBaseURI) { SerializeURI(mBaseURI, jsParams.baseURI()); } else { jsParams.baseURI() = mozilla::Nothing(); } aParams = jsParams; } bool nsJSURI::Deserialize(const mozilla::ipc::URIParams& aParams) { using namespace mozilla::ipc; if (aParams.type() != URIParams::TJSURIParams) { NS_ERROR("Received unknown parameters from the other process!"); return false; } const JSURIParams& jsParams = aParams.get_JSURIParams(); mozilla::net::nsSimpleURI::Deserialize(jsParams.simpleParams()); if (jsParams.baseURI().isSome()) { mBaseURI = DeserializeURI(jsParams.baseURI().ref()); } else { mBaseURI = nullptr; } return true; } // nsSimpleURI methods: /* virtual */ mozilla::net::nsSimpleURI* nsJSURI::StartClone( mozilla::net::nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef) { nsJSURI* url = new nsJSURI(mBaseURI); SetRefOnClone(url, refHandlingMode, newRef); return url; } // Queries this list of interfaces. If none match, it queries mURI. NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsJSURI::Mutator, nsIURISetters, nsIURIMutator, nsISerializable, nsIJSURIMutator) NS_IMETHODIMP nsJSURI::Mutate(nsIURIMutator** aMutator) { RefPtr mutator = new nsJSURI::Mutator(); nsresult rv = mutator->InitFromURI(this); if (NS_FAILED(rv)) { return rv; } mutator.forget(aMutator); return NS_OK; } /* virtual */ nsresult nsJSURI::EqualsInternal( nsIURI* aOther, mozilla::net::nsSimpleURI::RefHandlingEnum aRefHandlingMode, bool* aResult) { NS_ENSURE_ARG_POINTER(aOther); MOZ_ASSERT(aResult, "null pointer for outparam"); RefPtr otherJSURI; nsresult rv = aOther->QueryInterface(kJSURICID, getter_AddRefs(otherJSURI)); if (NS_FAILED(rv)) { *aResult = false; // aOther is not a nsJSURI --> not equal. return NS_OK; } // Compare the member data that our base class knows about. if (!mozilla::net::nsSimpleURI::EqualsInternal(otherJSURI, aRefHandlingMode)) { *aResult = false; return NS_OK; } // Compare the piece of additional member data that we add to base class. nsIURI* otherBaseURI = otherJSURI->GetBaseURI(); if (mBaseURI) { // (As noted in StartClone, we always honor refs on mBaseURI) return mBaseURI->Equals(otherBaseURI, aResult); } *aResult = !otherBaseURI; return NS_OK; }