michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: Components.utils.import("resource://gre/modules/AddonManager.jsm"); michael@0: michael@0: const DOWNLOAD_STARTED = 0; michael@0: const DOWNLOAD_FINISHED = 1; michael@0: const INSTALL_STARTED = 2; michael@0: const INSTALL_FINISHED = 3; michael@0: const INSTALLS_COMPLETE = 4; michael@0: michael@0: function getLocalizedError(key) michael@0: { michael@0: return document.getElementById("xpinstallStrings").getString(key); michael@0: } michael@0: michael@0: function binaryToHex(input) michael@0: { michael@0: return [('0' + input.charCodeAt(i).toString(16)).slice(-2) michael@0: for (i in input)].join(''); michael@0: } michael@0: michael@0: function verifyHash(aFile, aHash) michael@0: { michael@0: try { michael@0: var [, method, hash] = /^([A-Za-z0-9]+):(.*)$/.exec(aHash); michael@0: michael@0: var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. michael@0: createInstance(Components.interfaces.nsIFileInputStream); michael@0: fis.init(aFile, -1, -1, 0); michael@0: michael@0: var hasher = Components.classes['@mozilla.org/security/hash;1']. michael@0: createInstance(Components.interfaces.nsICryptoHash); michael@0: hasher.initWithString(method); michael@0: hasher.updateFromStream(fis, -1); michael@0: dlhash = binaryToHex(hasher.finish(false)); michael@0: return dlhash == hash; michael@0: } michael@0: catch (e) { michael@0: Components.utils.reportError(e); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: function InstallerObserver(aPlugin) michael@0: { michael@0: this._plugin = aPlugin; michael@0: this._init(); michael@0: } michael@0: michael@0: InstallerObserver.prototype = { michael@0: _init: function() michael@0: { michael@0: try { michael@0: var ios = Components.classes["@mozilla.org/network/io-service;1"]. michael@0: getService(Components.interfaces.nsIIOService); michael@0: var uri = ios.newURI(this._plugin.InstallerLocation, null, null); michael@0: uri.QueryInterface(Components.interfaces.nsIURL); michael@0: michael@0: // Use a local filename appropriate for the OS michael@0: var leafName = uri.fileName; michael@0: var os = Components.classes["@mozilla.org/xre/app-info;1"] michael@0: .getService(Components.interfaces.nsIXULRuntime) michael@0: .OS; michael@0: if (os == "WINNT" && leafName.indexOf(".") < 0) michael@0: leafName += ".exe"; michael@0: michael@0: var dirs = Components.classes["@mozilla.org/file/directory_service;1"]. michael@0: getService(Components.interfaces.nsIProperties); michael@0: michael@0: var resultFile = dirs.get("TmpD", Components.interfaces.nsIFile); michael@0: resultFile.append(leafName); michael@0: resultFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, michael@0: 0770); michael@0: michael@0: var channel = ios.newChannelFromURI(uri); michael@0: this._downloader = michael@0: Components.classes["@mozilla.org/network/downloader;1"]. michael@0: createInstance(Components.interfaces.nsIDownloader); michael@0: this._downloader.init(this, resultFile); michael@0: channel.notificationCallbacks = this; michael@0: michael@0: this._fireNotification(DOWNLOAD_STARTED, null); michael@0: michael@0: channel.asyncOpen(this._downloader, null); michael@0: } michael@0: catch (e) { michael@0: Components.utils.reportError(e); michael@0: this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-228")); michael@0: if (resultFile && resultFile.exists()) michael@0: resultfile.remove(false); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Inform the gPluginInstaller about what's going on. michael@0: */ michael@0: _fireNotification: function(aStatus, aErrorMsg) michael@0: { michael@0: gPluginInstaller.pluginInstallationProgress(this._plugin.pid, michael@0: aStatus, aErrorMsg); michael@0: michael@0: if (aStatus == INSTALL_FINISHED) { michael@0: --PluginInstallService._installersPending; michael@0: PluginInstallService._fireFinishedNotification(); michael@0: } michael@0: }, michael@0: michael@0: QueryInterface: function(iid) michael@0: { michael@0: if (iid.equals(Components.interfaces.nsISupports) || michael@0: iid.equals(Components.interfaces.nsIInterfaceRequestor) || michael@0: iid.equals(Components.interfaces.nsIDownloadObserver) || michael@0: iid.equals(Components.interfaces.nsIProgressEventSink)) michael@0: return this; michael@0: michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: michael@0: getInterface: function(iid) michael@0: { michael@0: if (iid.equals(Components.interfaces.nsIProgressEventSink)) michael@0: return this; michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: onDownloadComplete: function(downloader, request, ctxt, status, result) michael@0: { michael@0: if (!Components.isSuccessCode(status)) { michael@0: // xpinstall error 228 is "Download Error" michael@0: this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-228")); michael@0: result.remove(false); michael@0: return; michael@0: } michael@0: michael@0: this._fireNotification(DOWNLOAD_FINISHED); michael@0: michael@0: if (this._plugin.InstallerHash && michael@0: !verifyHash(result, this._plugin.InstallerHash)) { michael@0: // xpinstall error 261 is "Invalid file hash..." michael@0: this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-261")); michael@0: result.remove(false); michael@0: return; michael@0: } michael@0: michael@0: this._fireNotification(INSTALL_STARTED); michael@0: michael@0: result.QueryInterface(Components.interfaces.nsILocalFile); michael@0: try { michael@0: // Make sure the file is executable michael@0: result.permissions = 0770; michael@0: var process = Components.classes["@mozilla.org/process/util;1"] michael@0: .createInstance(Components.interfaces.nsIProcess); michael@0: process.init(result); michael@0: var self = this; michael@0: process.runAsync([], 0, { michael@0: observe: function(subject, topic, data) { michael@0: if (topic != "process-finished") { michael@0: Components.utils.reportError("Failed to launch installer"); michael@0: self._fireNotification(INSTALL_FINISHED, michael@0: getLocalizedError("error-207")); michael@0: } michael@0: else if (process.exitValue != 0) { michael@0: Components.utils.reportError("Installer returned exit code " + process.exitValue); michael@0: self._fireNotification(INSTALL_FINISHED, michael@0: getLocalizedError("error-203")); michael@0: } michael@0: else { michael@0: self._fireNotification(INSTALL_FINISHED, null); michael@0: } michael@0: result.remove(false); michael@0: } michael@0: }); michael@0: } michael@0: catch (e) { michael@0: Components.utils.reportError(e); michael@0: this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-207")); michael@0: result.remove(false); michael@0: } michael@0: }, michael@0: michael@0: onProgress: function(aRequest, aContext, aProgress, aProgressMax) michael@0: { michael@0: gPluginInstaller.pluginInstallationProgressMeter(this._plugin.pid, michael@0: aProgress, michael@0: aProgressMax); michael@0: }, michael@0: michael@0: onStatus: function(aRequest, aContext, aStatus, aStatusArg) michael@0: { michael@0: /* pass */ michael@0: } michael@0: }; michael@0: michael@0: var PluginInstallService = { michael@0: michael@0: /** michael@0: * Start installation of installers and XPI plugins. michael@0: * @param aInstallerPlugins An array of objects which should have the michael@0: * properties "pid", "InstallerLocation", michael@0: * and "InstallerHash" michael@0: * @param aXPIPlugins An array of objects which should have the michael@0: * properties "pid", "XPILocation", michael@0: * and "XPIHash" michael@0: */ michael@0: startPluginInstallation: function (aInstallerPlugins, michael@0: aXPIPlugins) michael@0: { michael@0: this._xpiPlugins = aXPIPlugins; michael@0: this._xpisPending = aXPIPlugins.length; michael@0: michael@0: aXPIPlugins.forEach(function(plugin) { michael@0: AddonManager.getInstallForURL(plugin.XPILocation, function(install) { michael@0: install.addListener(PluginInstallService); michael@0: install.install(); michael@0: }, "application/x-xpinstall", plugin.XPIHash); michael@0: }); michael@0: michael@0: // InstallerObserver may finish immediately so we must initialise the michael@0: // installers after setting the number of installers and xpis pending michael@0: this._installersPending = aInstallerPlugins.length; michael@0: this._installerPlugins = [new InstallerObserver(plugin) michael@0: for each (plugin in aInstallerPlugins)]; michael@0: }, michael@0: michael@0: _fireFinishedNotification: function() michael@0: { michael@0: if (this._installersPending == 0 && this._xpisPending == 0) michael@0: gPluginInstaller.pluginInstallationProgress(null, INSTALLS_COMPLETE, null); michael@0: }, michael@0: michael@0: getPidForInstall: function(install) { michael@0: for (let i = 0; i < this._xpiPlugins.length; i++) { michael@0: if (install.sourceURI.spec == this._xpiPlugins[i].XPILocation) michael@0: return this._xpiPlugins[i].pid; michael@0: } michael@0: return -1; michael@0: }, michael@0: michael@0: // InstallListener interface michael@0: onDownloadStarted: function(install) { michael@0: var pid = this.getPidForInstall(install); michael@0: gPluginInstaller.pluginInstallationProgress(pid, DOWNLOAD_STARTED, null); michael@0: }, michael@0: michael@0: onDownloadProgress: function(install) { michael@0: var pid = this.getPidForInstall(install); michael@0: gPluginInstaller.pluginInstallationProgressMeter(pid, install.progress, michael@0: install.maxProgress); michael@0: }, michael@0: michael@0: onDownloadEnded: function(install) { michael@0: var pid = this.getPidForInstall(install); michael@0: gPluginInstaller.pluginInstallationProgress(pid, DOWNLOAD_FINISHED, null); michael@0: }, michael@0: michael@0: onDownloadFailed: function(install) { michael@0: var pid = this.getPidForInstall(install); michael@0: switch (install.error) { michael@0: case AddonManager.ERROR_NETWORK_FAILURE: michael@0: var errorMsg = getLocalizedError("error-228"); michael@0: break; michael@0: case AddonManager.ERROR_INCORRECT_HASH: michael@0: var errorMsg = getLocalizedError("error-261"); michael@0: break; michael@0: case AddonManager.ERROR_CORRUPT_FILE: michael@0: var errorMsg = getLocalizedError("error-207"); michael@0: break; michael@0: } michael@0: gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, errorMsg); michael@0: michael@0: this._xpisPending--; michael@0: this._fireFinishedNotification(); michael@0: }, michael@0: michael@0: onInstallStarted: function(install) { michael@0: var pid = this.getPidForInstall(install); michael@0: gPluginInstaller.pluginInstallationProgress(pid, INSTALL_STARTED, null); michael@0: }, michael@0: michael@0: onInstallEnded: function(install, addon) { michael@0: var pid = this.getPidForInstall(install); michael@0: gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, null); michael@0: michael@0: this._xpisPending--; michael@0: this._fireFinishedNotification(); michael@0: }, michael@0: michael@0: onInstallFailed: function(install) { michael@0: var pid = this.getPidForInstall(install); michael@0: gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, michael@0: getLocalizedError("error-203")); michael@0: michael@0: this._xpisPending--; michael@0: this._fireFinishedNotification(); michael@0: } michael@0: }