diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 25e1cc31577c..76200fd55f9a 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -147,6 +147,13 @@ pref("browser.helperApps.alwaysAsk.force", false); pref("browser.helperApps.neverAsk.saveToDisk", ""); pref("browser.helperApps.neverAsk.openFile", ""); +#ifdef XP_WIN +// By default, security zone information is stored in the Alternate Data Stream +// of downloaded executable files on Windows. This preference allows disabling +// this feature, and thus the associated system-level execution prompts. +pref("browser.download.saveZoneInformation", true); +#endif + // xxxbsmedberg: where should prefs for the toolkit go? pref("browser.chrome.toolbar_tips", true); // 0 = Pictures Only, 1 = Text Only, 2 = Pictures and Text diff --git a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm index 524d0589c1fe..b61daa853e86 100644 --- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm +++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm @@ -66,6 +66,14 @@ XPCOMUtils.defineLazyGetter(this, "gParentalControlsService", function() { return null; }); +/** + * ArrayBufferView representing the bytes to be written to the "Zone.Identifier" + * Alternate Data Stream to mark a file as coming from the Internet zone. + */ +XPCOMUtils.defineLazyGetter(this, "gInternetZoneIdentifier", function() { + return new TextEncoder().encode("[ZoneTransfer]\r\nZoneId=3\r\n"); +}); + const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer", "initWithCallback"); @@ -389,15 +397,46 @@ this.DownloadIntegration = { * @rejects JavaScript exception if any of the operations failed. */ downloadDone: function(aDownload) { - try { + return Task.spawn(function () { +#ifdef XP_WIN + // On Windows, we mark any executable file saved to the NTFS file system + // as coming from the Internet security zone. We do this by writing to + // the "Zone.Identifier" Alternate Data Stream directly, because the Save + // method of the IAttachmentExecute interface would trigger operations + // that may cause the application to hang, or other performance issues. + // The stream created in this way is forward-compatible with all the + // current and future versions of Windows. + if (Services.prefs.getBoolPref("browser.download.saveZoneInformation")) { + let file = new FileUtils.File(aDownload.target.path); + if (file.isExecutable()) { + try { + let streamPath = aDownload.target.path + ":Zone.Identifier"; + let stream = yield OS.File.open(streamPath, { create: true }); + try { + yield stream.write(gInternetZoneIdentifier); + } finally { + yield stream.close(); + } + } catch (ex) { + // If writing to the stream fails, we ignore the error and continue. + // The Windows API error 123 (ERROR_INVALID_NAME) is expected to + // occur when working on a file system that does not support + // Alternate Data Streams, like FAT32, thus we don't report this + // specific error. + if (!(ex instanceof OS.File.Error) || ex.winLastError != 123) { + Cu.reportError(ex); + } + } + } + } +#endif + gDownloadPlatform.downloadDone(NetUtil.newURI(aDownload.source.url), new FileUtils.File(aDownload.target.path), - aDownload.contentType, aDownload.source.isPrivate); + aDownload.contentType, + aDownload.source.isPrivate); this.downloadDoneCalled = true; - return Promise.resolve(); - } catch(ex) { - return Promise.reject(ex); - } + }.bind(this)); }, /** @@ -432,12 +471,14 @@ this.DownloadIntegration = { let deferred = Task.spawn(function DI_launchDownload_task() { let file = new FileUtils.File(aDownload.target.path); - // Ask for confirmation if the file is executable. We do this here, - // instead of letting the caller handle the prompt separately in the user - // interface layer, for two reasons. The first is because of its security - // nature, so that add-ons cannot forget to do this check. The second is - // that the system-level security prompt, if enabled, would be displayed - // at launch time in any case. +#ifndef XP_WIN + // Ask for confirmation if the file is executable, except on Windows where + // the operating system will show the prompt based on the security zone. + // We do this here, instead of letting the caller handle the prompt + // separately in the user interface layer, for two reasons. The first is + // because of its security nature, so that add-ons cannot forget to do + // this check. The second is that the system-level security prompt would + // be displayed at launch time in any case. if (file.isExecutable() && !this.dontOpenFileAndFolder) { // We don't anchor the prompt to a specific window intentionally, not // only because this is the same behavior as the system-level prompt, @@ -449,6 +490,7 @@ this.DownloadIntegration = { return; } } +#endif // In case of a double extension, like ".tar.gz", we only // consider the last one, because the MIME service cannot