Commit 98380467 authored by Kathleen Brade's avatar Kathleen Brade Committed by Matthew Finkel
Browse files

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.

Other bugs fixed:
 Bug 21766: Crash with e10s enabled while trying to download a file
 Bug 21886: Download is stalled in non-e10s mode
 Bug 22471: Downloading files via the PDF viewer download button is broken
 Bug 22472: Fix FTP downloads when external helper app dialog is shown
 Bug 22610: Avoid crashes when canceling external helper app downloads
 Bug 22618: Downloading pdf file via file:/// is stalling
parent 80ce46b2
Loading
Loading
Loading
Loading
+167 −35
Original line number Original line Diff line number Diff line
@@ -132,6 +132,9 @@ static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
    "browser.helperApps.neverAsk.openFile";
    "browser.helperApps.neverAsk.openFile";


static const char WARNING_DIALOG_CONTRACT_ID[] =
    "@torproject.org/torbutton-extAppBlocker;1";

// Helper functions for Content-Disposition headers
// Helper functions for Content-Disposition headers


/**
/**
@@ -388,6 +391,22 @@ static nsresult GetDownloadDirectory(nsIFile** _directory,
  return NS_OK;
  return NS_OK;
}
}


static already_AddRefed<nsIInterfaceRequestor> GetDialogParentAux(
    BrowsingContext* aBrowsingContext, nsIInterfaceRequestor* aWindowContext) {
  nsCOMPtr<nsIInterfaceRequestor> dialogParent = aWindowContext;

  if (!dialogParent && aBrowsingContext) {
    dialogParent = do_QueryInterface(aBrowsingContext->GetDOMWindow());
  }
  if (!dialogParent && aBrowsingContext && XRE_IsParentProcess()) {
    RefPtr<Element> element = aBrowsingContext->Top()->GetEmbedderElement();
    if (element) {
      dialogParent = do_QueryInterface(element->OwnerDoc()->GetWindow());
    }
  }
  return dialogParent.forget();
}

/**
/**
 * Structure for storing extension->type mappings.
 * Structure for storing extension->type mappings.
 * @see defaultMimeEntries
 * @see defaultMimeEntries
@@ -544,6 +563,111 @@ static const nsDefaultMimeTypeEntry nonDecodableExtensions[] = {
    {APPLICATION_COMPRESS, "z"},
    {APPLICATION_COMPRESS, "z"},
    {APPLICATION_GZIP, "svgz"}};
    {APPLICATION_GZIP, "svgz"}};


//////////////////////////////////////////////////////////////////////////////////////////////////////
// begin nsExternalLoadURIHandler class definition and implementation
//////////////////////////////////////////////////////////////////////////////////////////////////////
class nsExternalLoadURIHandler final : public nsIHelperAppWarningLauncher {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIHELPERAPPWARNINGLAUNCHER

  nsExternalLoadURIHandler(nsIHandlerInfo* aHandlerInfo, nsIURI* aURI,
                           nsIPrincipal* aTriggeringPrincipal,
                           BrowsingContext* aBrowsingContext);

 protected:
  ~nsExternalLoadURIHandler();

  nsCOMPtr<nsIHandlerInfo> mHandlerInfo;
  nsCOMPtr<nsIURI> mURI;
  nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
  RefPtr<BrowsingContext> mBrowsingContext;
  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

nsExternalLoadURIHandler::nsExternalLoadURIHandler(
    nsIHandlerInfo* aHandlerInfo, nsIURI* aURI,
    nsIPrincipal* aTriggeringPrincipal, BrowsingContext* aBrowsingContext)
    : mHandlerInfo(aHandlerInfo),
      mURI(aURI),
      mTriggeringPrincipal(aTriggeringPrincipal),
      mBrowsingContext(aBrowsingContext)

{
  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.
    nsCOMPtr<nsIInterfaceRequestor> dialogParent =
        GetDialogParentAux(aBrowsingContext, nullptr);
    rv = mWarningDialog->MaybeShow(this, dialogParent);
  }

  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);

  nsresult rv = NS_OK;
  // 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)) {
    rv = mHandlerInfo->LaunchWithURI(mURI, mBrowsingContext);
    // We are not supposed to ask, but when file not found the user most likely
    // uninstalled the application which handles the uri so we will continue
    // by application chooser dialog.
    if (rv != NS_ERROR_FILE_NOT_FOUND) {
      return rv;
    }
  }

  nsCOMPtr<nsIContentDispatchChooser> chooser =
      do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return chooser->Ask(mHandlerInfo, mURI, mTriggeringPrincipal,
                      mBrowsingContext,
                      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;
}

static StaticRefPtr<nsExternalHelperAppService> sExtHelperAppSvcSingleton;
static StaticRefPtr<nsExternalHelperAppService> sExtHelperAppSvcSingleton;


/**
/**
@@ -570,6 +694,9 @@ nsExternalHelperAppService::GetSingleton() {
  return do_AddRef(sExtHelperAppSvcSingleton);
  return do_AddRef(sExtHelperAppSvcSingleton);
}
}


//////////////////////////////////////////////////////////////////////////////////////////////////////
// nsExternalHelperAppService definition and implementation
//////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMPL_ISUPPORTS(nsExternalHelperAppService, nsIExternalHelperAppService,
NS_IMPL_ISUPPORTS(nsExternalHelperAppService, nsIExternalHelperAppService,
                  nsPIExternalAppLauncher, nsIExternalProtocolService,
                  nsPIExternalAppLauncher, nsIExternalProtocolService,
                  nsIMIMEService, nsIObserver, nsISupportsWeakReference)
                  nsIMIMEService, nsIObserver, nsISupportsWeakReference)
@@ -1003,30 +1130,13 @@ nsExternalHelperAppService::LoadURI(nsIURI* aURI,
  rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
  rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
  NS_ENSURE_SUCCESS(rv, rv);
  NS_ENSURE_SUCCESS(rv, rv);


  nsHandlerInfoAction preferredAction;
  RefPtr<nsExternalLoadURIHandler> h = new nsExternalLoadURIHandler(
  handler->GetPreferredAction(&preferredAction);
      handler, uri, aTriggeringPrincipal, aBrowsingContext);
  bool alwaysAsk = true;
  if (!h) {
  handler->GetAlwaysAskBeforeHandling(&alwaysAsk);
    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)) {
    rv = handler->LaunchWithURI(uri, aBrowsingContext);
    // We are not supposed to ask, but when file not found the user most likely
    // uninstalled the application which handles the uri so we will continue
    // by application chooser dialog.
    if (rv != NS_ERROR_FILE_NOT_FOUND) {
      return rv;
    }
  }
  }


  nsCOMPtr<nsIContentDispatchChooser> chooser =
  return NS_OK;
      do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return chooser->Ask(handler, uri, aTriggeringPrincipal, aBrowsingContext,
                      nsIContentDispatchChooser::REASON_CANNOT_HANDLE);
}
}


//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1171,6 +1281,7 @@ NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
  NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
  NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
  NS_INTERFACE_MAP_ENTRY(nsIHelperAppWarningLauncher)
  NS_INTERFACE_MAP_ENTRY(nsICancelable)
  NS_INTERFACE_MAP_ENTRY(nsICancelable)
  NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
  NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
  NS_INTERFACE_MAP_ENTRY(nsINamed)
  NS_INTERFACE_MAP_ENTRY(nsINamed)
@@ -1532,18 +1643,7 @@ void nsExternalAppHandler::MaybeApplyDecodingForExtension(


already_AddRefed<nsIInterfaceRequestor>
already_AddRefed<nsIInterfaceRequestor>
nsExternalAppHandler::GetDialogParent() {
nsExternalAppHandler::GetDialogParent() {
  nsCOMPtr<nsIInterfaceRequestor> dialogParent = mWindowContext;
  return GetDialogParentAux(mBrowsingContext, mWindowContext);

  if (!dialogParent && mBrowsingContext) {
    dialogParent = do_QueryInterface(mBrowsingContext->GetDOMWindow());
  }
  if (!dialogParent && mBrowsingContext && XRE_IsParentProcess()) {
    RefPtr<Element> element = mBrowsingContext->Top()->GetEmbedderElement();
    if (element) {
      dialogParent = do_QueryInterface(element->OwnerDoc()->GetWindow());
    }
  }
  return dialogParent.forget();
}
}


NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
@@ -1651,6 +1751,29 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }
  }


  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.
    nsCOMPtr<nsIInterfaceRequestor> dialogParent = GetDialogParent();
    rv = mWarningDialog->MaybeShow(this, dialogParent);
  }

  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
  // 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...
  // asking the user what they want us to do with this content...


@@ -1736,6 +1859,7 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
    action = nsIMIMEInfo::saveToDisk;
    action = nsIMIMEInfo::saveToDisk;
  }
  }


  nsresult rv = NS_OK;
  if (alwaysAsk) {
  if (alwaysAsk) {
    // Display the dialog
    // Display the dialog
    mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
    mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
@@ -1793,6 +1917,14 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
  return NS_OK;
  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
// Convert error info into proper message text and send OnStatusChange
// notification to the dialog progress listener or nsITransfer implementation.
// notification to the dialog progress listener or nsITransfer implementation.
void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv,
void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv,
@@ -2456,7 +2588,7 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) {
  }
  }


  // Break our reference cycle with the helper app dialog (set up in
  // Break our reference cycle with the helper app dialog (set up in
  // OnStartRequest)
  // ContinueRequest)
  mDialog = nullptr;
  mDialog = nullptr;


  mRequest = nullptr;
  mRequest = nullptr;
+3 −0
Original line number Original line Diff line number Diff line
@@ -203,6 +203,7 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService,
 */
 */
class nsExternalAppHandler final : public nsIStreamListener,
class nsExternalAppHandler final : public nsIStreamListener,
                                   public nsIHelperAppLauncher,
                                   public nsIHelperAppLauncher,
                                   public nsIHelperAppWarningLauncher,
                                   public nsIBackgroundFileSaverObserver,
                                   public nsIBackgroundFileSaverObserver,
                                   public nsINamed {
                                   public nsINamed {
 public:
 public:
@@ -210,6 +211,7 @@ class nsExternalAppHandler final : public nsIStreamListener,
  NS_DECL_NSISTREAMLISTENER
  NS_DECL_NSISTREAMLISTENER
  NS_DECL_NSIREQUESTOBSERVER
  NS_DECL_NSIREQUESTOBSERVER
  NS_DECL_NSIHELPERAPPLAUNCHER
  NS_DECL_NSIHELPERAPPLAUNCHER
  NS_DECL_NSIHELPERAPPWARNINGLAUNCHER
  NS_DECL_NSICANCELABLE
  NS_DECL_NSICANCELABLE
  NS_DECL_NSIBACKGROUNDFILESAVEROBSERVER
  NS_DECL_NSIBACKGROUNDFILESAVEROBSERVER
  NS_DECL_NSINAMED
  NS_DECL_NSINAMED
@@ -460,6 +462,7 @@ class nsExternalAppHandler final : public nsIStreamListener,
  nsCOMPtr<nsITransfer> mTransfer;
  nsCOMPtr<nsITransfer> mTransfer;


  nsCOMPtr<nsIHelperAppLauncherDialog> mDialog;
  nsCOMPtr<nsIHelperAppLauncherDialog> mDialog;
  nsCOMPtr<nsIHelperAppWarningDialog> mWarningDialog;


  /**
  /**


+47 −0
Original line number Original line Diff line number Diff line
@@ -182,3 +182,50 @@ interface nsIHelperAppLauncher : nsICancelable
   */
   */
  readonly attribute uint64_t browsingContextId;
  readonly attribute uint64_t browsingContextId;
};
};

/**
  * 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);
};