Bug 896927 - Handle the executable warning prompt. r=enn

This commit is contained in:
Paolo Amadini 2013-08-16 21:48:01 +02:00
Родитель 52608ac5e3
Коммит 73620d69d7
3 изменённых файлов: 214 добавлений и 108 удалений

Просмотреть файл

@ -53,6 +53,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
"resource://gre/modules/PluralForm.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
"resource://gre/modules/DownloadUIHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
"resource://gre/modules/DownloadUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
@ -68,27 +70,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "DownloadsLogger",
const nsIDM = Ci.nsIDownloadManager;
const kDownloadsStringBundleUrl =
"chrome://browser/locale/downloads/downloads.properties";
const kPrefBdmScanWhenDone = "browser.download.manager.scanWhenDone";
const kPrefBdmAlertOnExeOpen = "browser.download.manager.alertOnEXEOpen";
const kDownloadsStringsRequiringFormatting = {
sizeWithUnits: true,
shortTimeLeftSeconds: true,
shortTimeLeftMinutes: true,
shortTimeLeftHours: true,
shortTimeLeftDays: true,
statusSeparator: true,
statusSeparatorBeforeNumber: true,
fileExecutableSecurityWarning: true
};
const kDownloadsStringsRequiringPluralForm = {
otherDownloads2: true
};
XPCOMUtils.defineLazyGetter(this, "DownloadsLocalFileCtor", function () {
return Components.Constructor("@mozilla.org/file/local;1",
"nsILocalFile", "initWithPath");
@ -163,41 +147,6 @@ this.DownloadsCommon = {
}
this.error.apply(this, aMessageArgs);
},
/**
* Returns an object whose keys are the string names from the downloads string
* bundle, and whose values are either the translated strings or functions
* returning formatted strings.
*/
get strings()
{
let strings = {};
let sb = Services.strings.createBundle(kDownloadsStringBundleUrl);
let enumerator = sb.getSimpleEnumeration();
while (enumerator.hasMoreElements()) {
let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
let stringName = string.key;
if (stringName in kDownloadsStringsRequiringFormatting) {
strings[stringName] = function () {
// Convert "arguments" to a real array before calling into XPCOM.
return sb.formatStringFromName(stringName,
Array.slice(arguments, 0),
arguments.length);
};
} else if (stringName in kDownloadsStringsRequiringPluralForm) {
strings[stringName] = function (aCount) {
// Convert "arguments" to a real array before calling into XPCOM.
let formattedString = sb.formatStringFromName(stringName,
Array.slice(arguments, 0),
arguments.length);
return PluralForm.get(aCount, formattedString);
};
} else {
strings[stringName] = string.value;
}
}
delete this.strings;
return this.strings = strings;
},
/**
* Generates a very short string representing the given time left.
@ -480,65 +429,44 @@ this.DownloadsCommon = {
if (!(aOwnerWindow instanceof Ci.nsIDOMWindow))
throw new Error("aOwnerWindow must be a dom-window object");
// Confirm opening executable files if required.
let promiseShouldLaunch;
if (aFile.isExecutable()) {
let showAlert = true;
try {
showAlert = Services.prefs.getBoolPref(kPrefBdmAlertOnExeOpen);
} catch (ex) { }
// On Vista and above, we rely on native security prompting for
// downloaded content unless it's disabled.
if (DownloadsCommon.isWinVistaOrHigher) {
try {
if (Services.prefs.getBoolPref(kPrefBdmScanWhenDone)) {
showAlert = false;
}
} catch (ex) { }
}
if (showAlert) {
let name = aFile.leafName;
let message =
DownloadsCommon.strings.fileExecutableSecurityWarning(name, name);
let title =
DownloadsCommon.strings.fileExecutableSecurityWarningTitle;
let dontAsk =
DownloadsCommon.strings.fileExecutableSecurityWarningDontAsk;
let checkbox = { value: false };
let open = Services.prompt.confirmCheck(aOwnerWindow, title, message,
dontAsk, checkbox);
if (!open) {
return;
}
Services.prefs.setBoolPref(kPrefBdmAlertOnExeOpen,
!checkbox.value);
}
// We get a prompter for the provided window here, even though anchoring
// to the most recently active window should work as well.
promiseShouldLaunch =
DownloadUIHelper.getPrompter(aOwnerWindow)
.confirmLaunchExecutable(aFile.path);
} else {
promiseShouldLaunch = Promise.resolve(true);
}
// Actually open the file.
try {
if (aMimeInfo && aMimeInfo.preferredAction == aMimeInfo.useHelperApp) {
aMimeInfo.launchWithFile(aFile);
promiseShouldLaunch.then(shouldLaunch => {
if (!shouldLaunch) {
return;
}
}
catch(ex) { }
// If either we don't have the mime info, or the preferred action failed,
// attempt to launch the file directly.
try {
aFile.launch();
}
catch(ex) {
// If launch fails, try sending it through the system's external "file:"
// URL handler.
Cc["@mozilla.org/uriloader/external-protocol-service;1"]
.getService(Ci.nsIExternalProtocolService)
.loadUrl(NetUtil.newURI(aFile));
}
// Actually open the file.
try {
if (aMimeInfo && aMimeInfo.preferredAction == aMimeInfo.useHelperApp) {
aMimeInfo.launchWithFile(aFile);
return;
}
}
catch(ex) { }
// If either we don't have the mime info, or the preferred action failed,
// attempt to launch the file directly.
try {
aFile.launch();
}
catch(ex) {
// If launch fails, try sending it through the system's external "file:"
// URL handler.
Cc["@mozilla.org/uriloader/external-protocol-service;1"]
.getService(Ci.nsIExternalProtocolService)
.loadUrl(NetUtil.newURI(aFile));
}
}).then(null, Cu.reportError);
},
/**
@ -574,6 +502,15 @@ this.DownloadsCommon = {
}
};
/**
* Returns an object whose keys are the string names from the downloads string
* bundle, and whose values are either the translated strings or functions
* returning formatted strings.
*/
XPCOMUtils.defineLazyGetter(DownloadsCommon, "strings", function () {
return DownloadUIHelper.strings;
});
/**
* Returns true if we are executing on Windows Vista or a later version.
*/

Просмотреть файл

@ -29,6 +29,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "DownloadStore",
"resource://gre/modules/DownloadStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadImport",
"resource://gre/modules/DownloadImport.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
"resource://gre/modules/DownloadUIHelper.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
@ -61,6 +63,7 @@ XPCOMUtils.defineLazyGetter(this, "gParentalControlsService", function() {
return null;
});
// This will be replaced by "DownloadUIHelper.strings" (see bug 905123).
XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
return Services.strings.
createBundle("chrome://mozapps/locale/downloads/downloads.properties");
@ -414,6 +417,24 @@ 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.
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,
// but also because the most recently active window is the right choice
// in basically all cases.
let shouldLaunch = yield DownloadUIHelper.getPrompter()
.confirmLaunchExecutable(file.path);
if (!shouldLaunch) {
return;
}
}
// In case of a double extension, like ".tar.gz", we only
// consider the last one, because the MIME service cannot
// handle multiple extensions.
@ -560,7 +581,11 @@ this.DownloadIntegration = {
*/
_createDownloadsDirectory: function DI_createDownloadsDirectory(aName) {
let directory = this._getDirectory(aName);
directory.append(gStringBundle.GetStringFromName("downloadsFolder"));
// We read the name of the directory from the list of translated strings
// that is kept by the UI helper module, even if this string is not strictly
// displayed in the user interface.
directory.append(DownloadUIHelper.strings.downloadsFolder);
// Create the Downloads folder and ignore if it already exists.
return OS.File.makeDir(directory.path, { ignoreExisting: true }).

Просмотреть файл

@ -24,6 +24,31 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
const kStringBundleUrl =
"chrome://browser/locale/downloads/downloads.properties";
const kStringsRequiringFormatting = {
sizeWithUnits: true,
shortTimeLeftSeconds: true,
shortTimeLeftMinutes: true,
shortTimeLeftHours: true,
shortTimeLeftDays: true,
statusSeparator: true,
statusSeparatorBeforeNumber: true,
fileExecutableSecurityWarning: true,
};
const kStringsRequiringPluralForm = {
otherDownloads2: true,
};
////////////////////////////////////////////////////////////////////////////////
//// DownloadUIHelper
@ -31,4 +56,123 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
* Provides functions to handle status and messages in the user interface.
*/
this.DownloadUIHelper = {
/**
* Returns an object that can be used to display prompts related to downloads.
*
* The prompts may be either anchored to a specified window, or anchored to
* the most recently active window, for example if the prompt is displayed in
* response to global notifications that are not associated with any window.
*
* @param aParent
* If specified, should reference the nsIDOMWindow to which the prompts
* should be attached. If omitted, the prompts will be attached to the
* most recently active window.
*
* @return A DownloadPrompter object.
*/
getPrompter: function (aParent)
{
return new DownloadPrompter(aParent || null);
},
};
/**
* Returns an object whose keys are the string names from the downloads string
* bundle, and whose values are either the translated strings or functions
* returning formatted strings.
*/
XPCOMUtils.defineLazyGetter(DownloadUIHelper, "strings", function () {
let strings = {};
let sb = Services.strings.createBundle(kStringBundleUrl);
let enumerator = sb.getSimpleEnumeration();
while (enumerator.hasMoreElements()) {
let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
let stringName = string.key;
if (stringName in kStringsRequiringFormatting) {
strings[stringName] = function () {
// Convert "arguments" to a real array before calling into XPCOM.
return sb.formatStringFromName(stringName,
Array.slice(arguments, 0),
arguments.length);
};
} else if (stringName in kStringsRequiringPluralForm) {
strings[stringName] = function (aCount) {
// Convert "arguments" to a real array before calling into XPCOM.
let formattedString = sb.formatStringFromName(stringName,
Array.slice(arguments, 0),
arguments.length);
return PluralForm.get(aCount, formattedString);
};
} else {
strings[stringName] = string.value;
}
}
return strings;
});
////////////////////////////////////////////////////////////////////////////////
//// DownloadPrompter
/**
* Allows displaying prompts related to downloads.
*
* @param aParent
* The nsIDOMWindow to which prompts should be attached, or null to
* attach prompts to the most recently active window.
*/
function DownloadPrompter(aParent)
{
this._prompter = Services.ww.getNewPrompter(aParent);
}
DownloadPrompter.prototype = {
/**
* nsIPrompt instance for displaying messages.
*/
_prompter: null,
/**
* Displays a warning message box that informs that the specified file is
* executable, and asks whether the user wants to launch it. The user is
* given the option of disabling future instances of this warning.
*
* @param aPath
* String containing the full path to the file to be opened.
*
* @return {Promise}
* @resolves Boolean indicating whether the launch operation can continue.
* @rejects JavaScript exception.
*/
confirmLaunchExecutable: function (aPath)
{
const kPrefAlertOnEXEOpen = "browser.download.manager.alertOnEXEOpen";
try {
try {
if (!Services.prefs.getBoolPref(kPrefAlertOnEXEOpen)) {
return Promise.resolve(true);
}
} catch (ex) {
// If the preference does not exist, continue with the prompt.
}
let leafName = OS.Path.basename(aPath);
let s = DownloadUIHelper.strings;
let checkState = { value: false };
let shouldLaunch = this._prompter.confirmCheck(
s.fileExecutableSecurityWarningTitle,
s.fileExecutableSecurityWarning(leafName, leafName),
s.fileExecutableSecurityWarningDontAsk,
checkState);
if (shouldLaunch) {
Services.prefs.setBoolPref(kPrefAlertOnEXEOpen, !checkState.value);
}
return Promise.resolve(shouldLaunch);
} catch (ex) {
return Promise.reject(ex);
}
},
};