Bug 396701 - clicking on .exe file shows wrong download dialog. r=gavin, sr=biesi, b-ff3=beltzner, a=blockingM9
authoredward.lee@engineering.uiuc.edu
Thu, 01 Nov 2007 14:18:55 -0700
changeset 7284 fd33f58491b0b120d676888ae74fa2f777de2b8f
parent 7283 b8d9d76edbebc7bcc6e4de902ddda8f823c410b2
child 7285 432a0772f888d377612eb3a8ca5d76441ffe2400
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherderautoland@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgavin, biesi, blockingM9
bugs396701
milestone1.9a9pre
Bug 396701 - clicking on .exe file shows wrong download dialog. r=gavin, sr=biesi, b-ff3=beltzner, a=blockingM9
toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
uriloader/exthandler/nsExternalHelperAppService.cpp
uriloader/exthandler/nsExternalHelperAppService.h
uriloader/exthandler/nsIExternalHelperAppService.idl
--- a/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
+++ b/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
@@ -415,17 +415,17 @@ nsUnknownContentTypeDialog.prototype = {
       var iconString = "moz-icon://" + fname + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
       this.dialogElement("contentTypeImage").setAttribute("src", iconString);
 
       // if always-save and is-executable and no-handler
       // then set up simple ui
       var mimeType = this.mLauncher.MIMEInfo.MIMEType;
       var shouldntRememberChoice = (mimeType == "application/octet-stream" || 
                                     mimeType == "application/x-msdownload" ||
-                                    this.mLauncher.targetFile.isExecutable());
+                                    this.mLauncher.targetFileIsExecutable);
       if (shouldntRememberChoice && !this.openWithDefaultOK()) {
         // hide featured choice 
         this.mDialog.document.getElementById("normalBox").collapsed = "true";
         // show basic choice 
         this.mDialog.document.getElementById("basicBox").collapsed = "false";
         // change button labels
         this.mDialog.document.documentElement.getButton("accept").label = this.dialogElement("strings").getString("unknownAccept.label");
         this.mDialog.document.documentElement.getButton("cancel").label = this.dialogElement("strings").getString("unknownCancel.label");
@@ -590,32 +590,27 @@ nsUnknownContentTypeDialog.prototype = {
       if (this._delayExpired) {
         var script = "document.documentElement.getButton('accept').disabled = false";
         this.mDialog.setTimeout(script, 250);
       }
     },
 
     // Returns true if opening the default application makes sense.
     openWithDefaultOK: function() {
-        var result;
-
         // The checking is different on Windows...
 #ifdef XP_WIN
         // Windows presents some special cases.
         // We need to prevent use of "system default" when the file is
         // executable (so the user doesn't launch nasty programs downloaded
         // from the web), and, enable use of "system default" if it isn't
         // executable (because we will prompt the user for the default app
         // in that case).
         
-        // Need to get temporary file and check for executable-ness.
-        var tmpFile = this.mLauncher.targetFile;
-        
         //  Default is Ok if the file isn't executable (and vice-versa).
-        return !tmpFile.isExecutable();
+        return !this.mLauncher.targetFileIsExecutable;
 #else
             // On other platforms, default is Ok if there is a default app.
             // Note that nsIMIMEInfo providers need to ensure that this holds true
             // on each platform.
         return this.mLauncher.MIMEInfo.hasDefaultHandler;
 #endif
     },
     
@@ -647,17 +642,17 @@ nsUnknownContentTypeDialog.prototype = {
     // initAppAndSaveToDiskValues:
     initAppAndSaveToDiskValues: function() {
       var modeGroup = this.dialogElement("mode");
 
       // We don't let users open .exe files or random binary data directly 
       // from the browser at the moment because of security concerns. 
       var openWithDefaultOK = this.openWithDefaultOK();
       var mimeType = this.mLauncher.MIMEInfo.MIMEType;
-      if (this.mLauncher.targetFile.isExecutable() || (
+      if (this.mLauncher.targetFileIsExecutable || (
           (mimeType == "application/octet-stream" ||
            mimeType == "application/x-msdownload") && 
            !openWithDefaultOK)) {
         this.dialogElement("open").disabled = true;
         var openHandler = this.dialogElement("openHandler");
         openHandler.disabled = true;
         openHandler.selectedItem = null;
         modeGroup.selectedItem = this.dialogElement("save");
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -1025,16 +1025,27 @@ NS_IMETHODIMP nsExternalAppHandler::GetT
     *aTarget = mFinalFileDestination;
   else
     *aTarget = mTempFile;
 
   NS_IF_ADDREF(*aTarget);
   return NS_OK;
 }
 
+NS_IMETHODIMP nsExternalAppHandler::GetTargetFileIsExecutable(PRBool *aExec)
+{
+  // Use the real target if it's been set
+  if (mFinalFileDestination)
+    return mFinalFileDestination->IsExecutable(aExec);
+
+  // Otherwise, use the stored executable-ness of the temporary
+  *aExec = mTempFileIsExecutable;
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsExternalAppHandler::GetTimeDownloadStarted(PRTime* aTime)
 {
   *aTime = mTimeDownloadStarted;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsExternalAppHandler::CloseProgressWindow()
 {
@@ -1168,24 +1179,47 @@ nsresult nsExternalAppHandler::SetUpTemp
   nsCAutoString ext;
   mMimeInfo->GetPrimaryExtension(ext);
   if (!ext.IsEmpty()) {
     if (ext.First() != '.')
       tempLeafName.Append('.');
     tempLeafName.Append(ext);
   }
 
+#ifdef XP_WIN
+  // On windows, we need to temporarily create a dummy file with the correct
+  // file extension to determine the executable-ness, so do this before adding
+  // the extra .part extension.
+  nsCOMPtr<nsIFile> dummyFile;
+  rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dummyFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Set the file name without .part
+  dummyFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
+  dummyFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+
+  // Store executable-ness then delete
+  dummyFile->IsExecutable(&mTempFileIsExecutable);
+  dummyFile->Remove(PR_FALSE);
+#endif
+
   // Add an additional .part to prevent the OS from running this file in the
   // default application.
   tempLeafName.Append(NS_LITERAL_CSTRING(".part"));
 
   mTempFile->Append(NS_ConvertUTF8toUTF16(tempLeafName));
   // make this file unique!!!
   mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
 
+#ifndef XP_WIN
+  // On other platforms, the file permission bits are used, so we can just call
+  // IsExecutable
+  mTempFile->IsExecutable(&mTempFileIsExecutable);
+#endif
+
 #ifdef XP_MACOSX
   // Now that the file exists set Mac type if the file has no extension
   // and we can determine a type.
   if (ext.IsEmpty() && mMimeInfo) {
     nsCOMPtr<nsILocalFileMac> macfile = do_QueryInterface(mTempFile);
     if (macfile) {
       PRUint32 type;
       mMimeInfo->GetMacType(&type);
--- a/uriloader/exthandler/nsExternalHelperAppService.h
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -315,16 +315,21 @@ protected:
 
   /**
    * One of the REASON_ constants from nsIHelperAppLauncherDialog. Indicates the
    * reason the dialog was shown (unknown content type, server requested it,
    * etc).
    */
   PRUint32 mReason;
 
+  /**
+   * Track the executable-ness of the temporary file.
+   */
+  PRBool mTempFileIsExecutable;
+
   PRTime mTimeDownloadStarted;
   nsInt64 mContentLength;
   nsInt64 mProgress; /**< Number of bytes received (for sending progress notifications). */
 
   /**
    * When we are told to save the temp file to disk (in a more permament
    * location) before we are done writing the content to a temp file, then
    * we need to remember the final destination until we are ready to use it.
--- a/uriloader/exthandler/nsIExternalHelperAppService.idl
+++ b/uriloader/exthandler/nsIExternalHelperAppService.idl
@@ -96,17 +96,17 @@ interface nsPIExternalAppLauncher : nsIS
 
 /**
  * A helper app launcher is a small object created to handle the launching
  * of an external application.
  *
  * Note that cancelling the load via the nsICancelable interface will release
  * the reference to the launcher dialog.
  */
-[scriptable, uuid(99a0882d-2ff9-4659-9952-9ac531ba5592)]
+[scriptable, uuid(cc75c21a-0a79-4f68-90e1-563253d0c555)]
 interface nsIHelperAppLauncher : nsICancelable
 {
   /**
    * The mime info object associated with the content type this helper app
    * launcher is currently attempting to load
    */
   readonly attribute nsIMIMEInfo MIMEInfo;
 
@@ -148,13 +148,19 @@ interface nsIHelperAppLauncher : nsICanc
    * so we can release any local state...
    */
   void closeProgressWindow();
 
   /**
    * The file we are saving to
    */
   readonly attribute nsIFile targetFile;
+
+  /**
+   * The executable-ness of the target file
+   */
+  readonly attribute boolean targetFileIsExecutable;
+
   /**
    * Time when the download started
    */
   readonly attribute PRTime timeDownloadStarted;
 };