commit 0c48149c2a4d4603f066c26c1b54f5e4fe554454
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Thu Jul 20 17:04:44 2017 -0400
fixup! Bug 19273: Avoid JavaScript patching of the external app helper dialog.
When handling an external URI or downloading a file, invoke Torbutton's
external app blocker component (which will present a download warning
dialog unless the user has checked the "Automatically download files from
now on" box).
For e10s compatibility, avoid using a modal dialog and instead use a
callback interface (nsIHelperAppWarningLauncher) to allow Torbutton to
indicate the user's desire to cancel or continue each request.
---
.../exthandler/nsExternalHelperAppService.cpp | 162 ++++++++++++++++++---
uriloader/exthandler/nsExternalHelperAppService.h | 3 +
.../exthandler/nsIExternalHelperAppService.idl | 47 ++++++
3 files changed, 195 insertions(+), 17 deletions(-)
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp
index 51a7ee0f6ab6..df08905304fb 100644
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -132,6 +132,9 @@ static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
"browser.helperApps.neverAsk.openFile";
+static const char WARNING_DIALOG_CONTRACT_ID[] =
+ "@torproject.org/torbutton-extAppBlocker;1";
+
// Helper functions for Content-Disposition headers
/**
@@ -594,6 +597,107 @@ static const nsDefaultMimeTypeEntry nonDecodableExtensions[] = {
{ APPLICATION_GZIP, "svgz" }
};
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// begin nsExternalLoadURIHandler class definition and implementation
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+class nsExternalLoadURIHandler final : public nsIHelperAppWarningLauncher
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHELPERAPPWARNINGLAUNCHER
+
+ nsExternalLoadURIHandler(nsIInterfaceRequestor * aWindowContext,
+ nsIURI *aURI,
+ nsIHandlerInfo *aHandlerInfo);
+
+protected:
+ ~nsExternalLoadURIHandler();
+
+ nsCOMPtr<nsIInterfaceRequestor> mWindowContext;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIHandlerInfo> mHandlerInfo;
+ nsCOMPtr<nsIHelperAppWarningDialog> mWarningDialog;
+};
+
+NS_IMPL_ADDREF(nsExternalLoadURIHandler)
+NS_IMPL_RELEASE(nsExternalLoadURIHandler)
+
+NS_INTERFACE_MAP_BEGIN(nsExternalLoadURIHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHelperAppWarningLauncher)
+ NS_INTERFACE_MAP_ENTRY(nsIHelperAppWarningLauncher)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsExternalLoadURIHandler::nsExternalLoadURIHandler(
+ nsIInterfaceRequestor *aWindowContext,
+ nsIURI *aURI,
+ nsIHandlerInfo *aHandlerInfo)
+: mWindowContext(aWindowContext)
+, mURI(aURI)
+, mHandlerInfo(aHandlerInfo)
+{
+ nsresult rv = NS_OK;
+ mWarningDialog = do_CreateInstance(WARNING_DIALOG_CONTRACT_ID, &rv);
+ if (NS_SUCCEEDED(rv) && mWarningDialog) {
+ // This will create a reference cycle (the dialog holds a reference to us
+ // as nsIHelperAppWarningLauncher), which will be broken in ContinueRequest
+ // or CancelRequest.
+ rv = mWarningDialog->MaybeShow(this, aWindowContext);
+ }
+
+ if (NS_FAILED(rv)) {
+ // If for some reason we could not open the download warning prompt,
+ // continue with the request.
+ ContinueRequest();
+ }
+}
+
+nsExternalLoadURIHandler::~nsExternalLoadURIHandler()
+{
+}
+
+NS_IMETHODIMP nsExternalLoadURIHandler::ContinueRequest()
+{
+ MOZ_ASSERT(mURI);
+ MOZ_ASSERT(mHandlerInfo);
+
+ // Break our reference cycle with the download warning dialog (set up in
+ // LoadURI).
+ mWarningDialog = nullptr;
+
+ nsHandlerInfoAction preferredAction;
+ mHandlerInfo->GetPreferredAction(&preferredAction);
+ bool alwaysAsk = true;
+ mHandlerInfo->GetAlwaysAskBeforeHandling(&alwaysAsk);
+
+ // if we are not supposed to ask, and the preferred action is to use
+ // a helper app or the system default, we just launch the URI.
+ if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp ||
+ preferredAction == nsIHandlerInfo::useSystemDefault))
+ return mHandlerInfo->LaunchWithURI(mURI, mWindowContext);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIContentDispatchChooser> chooser =
+ do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return chooser->Ask(mHandlerInfo, mWindowContext, mURI,
+ nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
+}
+
+NS_IMETHODIMP nsExternalLoadURIHandler::CancelRequest(nsresult aReason)
+{
+ NS_ENSURE_ARG(NS_FAILED(aReason));
+
+ // Break our reference cycle with the download warning dialog (set up in
+ // LoadURI).
+ mWarningDialog = nullptr;
+
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// nsExternalHelperAppService definition and implementation
+//////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMPL_ISUPPORTS(
nsExternalHelperAppService,
nsIExternalHelperAppService,
@@ -1014,23 +1118,13 @@ nsExternalHelperAppService::LoadURI(nsIURI *aURI,
rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
NS_ENSURE_SUCCESS(rv, rv);
- nsHandlerInfoAction preferredAction;
- handler->GetPreferredAction(&preferredAction);
- bool alwaysAsk = true;
- handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
+ RefPtr<nsExternalLoadURIHandler> h =
+ new nsExternalLoadURIHandler(aWindowContext, uri, handler);
+ if (!h) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
- // if we are not supposed to ask, and the preferred action is to use
- // a helper app or the system default, we just launch the URI.
- if (!alwaysAsk && (preferredAction == nsIHandlerInfo::useHelperApp ||
- preferredAction == nsIHandlerInfo::useSystemDefault))
- return handler->LaunchWithURI(uri, aWindowContext);
-
- nsCOMPtr<nsIContentDispatchChooser> chooser =
- do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
- NS_ENSURE_SUCCESS(rv, rv);
-
- return chooser->Ask(handler, aWindowContext, uri,
- nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
+ return NS_OK;
}
NS_IMETHODIMP nsExternalHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
@@ -1198,6 +1292,7 @@ NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
+ NS_INTERFACE_MAP_ENTRY(nsIHelperAppWarningLauncher)
NS_INTERFACE_MAP_ENTRY(nsICancelable)
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
@@ -1671,6 +1766,29 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISuppo
httpInternal->SetChannelIsForDownload(true);
}
+ mWarningDialog = do_CreateInstance(WARNING_DIALOG_CONTRACT_ID, &rv);
+ if (NS_SUCCEEDED(rv) && mWarningDialog) {
+ // This will create a reference cycle (the dialog holds a reference to us
+ // as nsIHelperAppWarningLauncher), which will be broken in ContinueRequest
+ // or CancelRequest.
+ rv = mWarningDialog->MaybeShow(this, GetDialogParent());
+ }
+
+ if (NS_FAILED(rv)) {
+ // If for some reason we could not open the download warning prompt,
+ // continue with the request.
+ ContinueRequest();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::ContinueRequest()
+{
+ // Break our reference cycle with the download warning dialog (set up in
+ // OnStartRequest)
+ mWarningDialog = nullptr;
+
// now that the temp file is set up, find out if we need to invoke a dialog
// asking the user what they want us to do with this content...
@@ -1731,6 +1849,7 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISuppo
action = nsIMIMEInfo::saveToDisk;
}
+ nsresult rv = NS_OK;
if (alwaysAsk)
{
// Display the dialog
@@ -1786,6 +1905,15 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISuppo
return NS_OK;
}
+NS_IMETHODIMP nsExternalAppHandler::CancelRequest(nsresult aReason)
+{
+ // Break our reference cycle with the download warning dialog (set up in
+ // OnStartRequest)
+ mWarningDialog = nullptr;
+
+ return Cancel(aReason);
+}
+
// Convert error info into proper message text and send OnStatusChange
// notification to the dialog progress listener or nsITransfer implementation.
void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path)
@@ -2477,7 +2605,7 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason)
}
// Break our reference cycle with the helper app dialog (set up in
- // OnStartRequest)
+ // ContinueRequest)
mDialog = nullptr;
mRequest = nullptr;
diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h
index ceec66661dd4..3146407dcbe5 100644
--- a/uriloader/exthandler/nsExternalHelperAppService.h
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -210,6 +210,7 @@ private:
*/
class nsExternalAppHandler final : public nsIStreamListener,
public nsIHelperAppLauncher,
+ public nsIHelperAppWarningLauncher,
public nsITimerCallback,
public nsIBackgroundFileSaverObserver
{
@@ -218,6 +219,7 @@ public:
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSIHELPERAPPLAUNCHER
+ NS_DECL_NSIHELPERAPPWARNINGLAUNCHER
NS_DECL_NSICANCELABLE
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSIBACKGROUNDFILESAVEROBSERVER
@@ -478,6 +480,7 @@ protected:
nsCOMPtr<nsIChannel> mOriginalChannel; /**< in the case of a redirect, this will be the pre-redirect channel. */
nsCOMPtr<nsIHelperAppLauncherDialog> mDialog;
+ nsCOMPtr<nsIHelperAppWarningDialog> mWarningDialog;
/**
* Keep request alive in case when helper non-modal dialog shown.
diff --git a/uriloader/exthandler/nsIExternalHelperAppService.idl b/uriloader/exthandler/nsIExternalHelperAppService.idl
index bfdfff5ceaa8..546252f04ba3 100644
--- a/uriloader/exthandler/nsIExternalHelperAppService.idl
+++ b/uriloader/exthandler/nsIExternalHelperAppService.idl
@@ -152,3 +152,50 @@ interface nsIHelperAppLauncher : nsICancelable
*/
readonly attribute int64_t contentLength;
};
+
+/**
+ * nsIHelperAppWarningLauncher is implemented by two classes:
+ * nsExternalLoadURIHandler
+ * nsExternalAppHandler
+ */
+[scriptable, uuid(cffd508b-4aaf-43ad-99c6-671d35cbc558)]
+interface nsIHelperAppWarningLauncher : nsISupports
+{
+ /**
+ * Callback invoked by the external app warning dialog to continue the
+ * request.
+ * NOTE: This will release the reference to the nsIHelperAppWarningDialog.
+ */
+ void continueRequest();
+
+ /**
+ * Callback invoked by the external app warning dialog to cancel the request.
+ * NOTE: This will release the reference to the nsIHelperAppWarningDialog.
+ *
+ * @param aReason
+ * Pass a failure code to indicate the reason why this operation is
+ * being canceled. It is an error to pass a success code.
+ */
+ void cancelRequest(in nsresult aReason);
+};
+
+/**
+ * nsIHelperAppWarningDialog is implemented by Torbutton's external app
+ * blocker (src/components/external-app-blocker.js).
+ */
+[scriptable, uuid(f4899a3f-0df3-42cc-9db8-bdf599e5a208)]
+interface nsIHelperAppWarningDialog : nsISupports
+{
+ /**
+ * Possibly show a launch warning dialog (it will not be shown if the user
+ * has chosen to not see the warning again).
+ *
+ * @param aLauncher
+ * A nsIHelperAppWarningLauncher to be invoked after the user confirms
+ * or cancels the download.
+ * @param aWindowContext
+ * The window associated with the download.
+ */
+ void maybeShow(in nsIHelperAppWarningLauncher aLauncher,
+ in nsISupports aWindowContext);
+};