1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/plugins/content/pluginInstallerService.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,299 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +Components.utils.import("resource://gre/modules/AddonManager.jsm"); 1.9 + 1.10 +const DOWNLOAD_STARTED = 0; 1.11 +const DOWNLOAD_FINISHED = 1; 1.12 +const INSTALL_STARTED = 2; 1.13 +const INSTALL_FINISHED = 3; 1.14 +const INSTALLS_COMPLETE = 4; 1.15 + 1.16 +function getLocalizedError(key) 1.17 +{ 1.18 + return document.getElementById("xpinstallStrings").getString(key); 1.19 +} 1.20 + 1.21 +function binaryToHex(input) 1.22 +{ 1.23 + return [('0' + input.charCodeAt(i).toString(16)).slice(-2) 1.24 + for (i in input)].join(''); 1.25 +} 1.26 + 1.27 +function verifyHash(aFile, aHash) 1.28 +{ 1.29 + try { 1.30 + var [, method, hash] = /^([A-Za-z0-9]+):(.*)$/.exec(aHash); 1.31 + 1.32 + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. 1.33 + createInstance(Components.interfaces.nsIFileInputStream); 1.34 + fis.init(aFile, -1, -1, 0); 1.35 + 1.36 + var hasher = Components.classes['@mozilla.org/security/hash;1']. 1.37 + createInstance(Components.interfaces.nsICryptoHash); 1.38 + hasher.initWithString(method); 1.39 + hasher.updateFromStream(fis, -1); 1.40 + dlhash = binaryToHex(hasher.finish(false)); 1.41 + return dlhash == hash; 1.42 + } 1.43 + catch (e) { 1.44 + Components.utils.reportError(e); 1.45 + return false; 1.46 + } 1.47 +} 1.48 + 1.49 +function InstallerObserver(aPlugin) 1.50 +{ 1.51 + this._plugin = aPlugin; 1.52 + this._init(); 1.53 +} 1.54 + 1.55 +InstallerObserver.prototype = { 1.56 + _init: function() 1.57 + { 1.58 + try { 1.59 + var ios = Components.classes["@mozilla.org/network/io-service;1"]. 1.60 + getService(Components.interfaces.nsIIOService); 1.61 + var uri = ios.newURI(this._plugin.InstallerLocation, null, null); 1.62 + uri.QueryInterface(Components.interfaces.nsIURL); 1.63 + 1.64 + // Use a local filename appropriate for the OS 1.65 + var leafName = uri.fileName; 1.66 + var os = Components.classes["@mozilla.org/xre/app-info;1"] 1.67 + .getService(Components.interfaces.nsIXULRuntime) 1.68 + .OS; 1.69 + if (os == "WINNT" && leafName.indexOf(".") < 0) 1.70 + leafName += ".exe"; 1.71 + 1.72 + var dirs = Components.classes["@mozilla.org/file/directory_service;1"]. 1.73 + getService(Components.interfaces.nsIProperties); 1.74 + 1.75 + var resultFile = dirs.get("TmpD", Components.interfaces.nsIFile); 1.76 + resultFile.append(leafName); 1.77 + resultFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 1.78 + 0770); 1.79 + 1.80 + var channel = ios.newChannelFromURI(uri); 1.81 + this._downloader = 1.82 + Components.classes["@mozilla.org/network/downloader;1"]. 1.83 + createInstance(Components.interfaces.nsIDownloader); 1.84 + this._downloader.init(this, resultFile); 1.85 + channel.notificationCallbacks = this; 1.86 + 1.87 + this._fireNotification(DOWNLOAD_STARTED, null); 1.88 + 1.89 + channel.asyncOpen(this._downloader, null); 1.90 + } 1.91 + catch (e) { 1.92 + Components.utils.reportError(e); 1.93 + this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-228")); 1.94 + if (resultFile && resultFile.exists()) 1.95 + resultfile.remove(false); 1.96 + } 1.97 + }, 1.98 + 1.99 + /** 1.100 + * Inform the gPluginInstaller about what's going on. 1.101 + */ 1.102 + _fireNotification: function(aStatus, aErrorMsg) 1.103 + { 1.104 + gPluginInstaller.pluginInstallationProgress(this._plugin.pid, 1.105 + aStatus, aErrorMsg); 1.106 + 1.107 + if (aStatus == INSTALL_FINISHED) { 1.108 + --PluginInstallService._installersPending; 1.109 + PluginInstallService._fireFinishedNotification(); 1.110 + } 1.111 + }, 1.112 + 1.113 + QueryInterface: function(iid) 1.114 + { 1.115 + if (iid.equals(Components.interfaces.nsISupports) || 1.116 + iid.equals(Components.interfaces.nsIInterfaceRequestor) || 1.117 + iid.equals(Components.interfaces.nsIDownloadObserver) || 1.118 + iid.equals(Components.interfaces.nsIProgressEventSink)) 1.119 + return this; 1.120 + 1.121 + throw Components.results.NS_ERROR_NO_INTERFACE; 1.122 + }, 1.123 + 1.124 + getInterface: function(iid) 1.125 + { 1.126 + if (iid.equals(Components.interfaces.nsIProgressEventSink)) 1.127 + return this; 1.128 + 1.129 + return null; 1.130 + }, 1.131 + 1.132 + onDownloadComplete: function(downloader, request, ctxt, status, result) 1.133 + { 1.134 + if (!Components.isSuccessCode(status)) { 1.135 + // xpinstall error 228 is "Download Error" 1.136 + this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-228")); 1.137 + result.remove(false); 1.138 + return; 1.139 + } 1.140 + 1.141 + this._fireNotification(DOWNLOAD_FINISHED); 1.142 + 1.143 + if (this._plugin.InstallerHash && 1.144 + !verifyHash(result, this._plugin.InstallerHash)) { 1.145 + // xpinstall error 261 is "Invalid file hash..." 1.146 + this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-261")); 1.147 + result.remove(false); 1.148 + return; 1.149 + } 1.150 + 1.151 + this._fireNotification(INSTALL_STARTED); 1.152 + 1.153 + result.QueryInterface(Components.interfaces.nsILocalFile); 1.154 + try { 1.155 + // Make sure the file is executable 1.156 + result.permissions = 0770; 1.157 + var process = Components.classes["@mozilla.org/process/util;1"] 1.158 + .createInstance(Components.interfaces.nsIProcess); 1.159 + process.init(result); 1.160 + var self = this; 1.161 + process.runAsync([], 0, { 1.162 + observe: function(subject, topic, data) { 1.163 + if (topic != "process-finished") { 1.164 + Components.utils.reportError("Failed to launch installer"); 1.165 + self._fireNotification(INSTALL_FINISHED, 1.166 + getLocalizedError("error-207")); 1.167 + } 1.168 + else if (process.exitValue != 0) { 1.169 + Components.utils.reportError("Installer returned exit code " + process.exitValue); 1.170 + self._fireNotification(INSTALL_FINISHED, 1.171 + getLocalizedError("error-203")); 1.172 + } 1.173 + else { 1.174 + self._fireNotification(INSTALL_FINISHED, null); 1.175 + } 1.176 + result.remove(false); 1.177 + } 1.178 + }); 1.179 + } 1.180 + catch (e) { 1.181 + Components.utils.reportError(e); 1.182 + this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-207")); 1.183 + result.remove(false); 1.184 + } 1.185 + }, 1.186 + 1.187 + onProgress: function(aRequest, aContext, aProgress, aProgressMax) 1.188 + { 1.189 + gPluginInstaller.pluginInstallationProgressMeter(this._plugin.pid, 1.190 + aProgress, 1.191 + aProgressMax); 1.192 + }, 1.193 + 1.194 + onStatus: function(aRequest, aContext, aStatus, aStatusArg) 1.195 + { 1.196 + /* pass */ 1.197 + } 1.198 +}; 1.199 + 1.200 +var PluginInstallService = { 1.201 + 1.202 + /** 1.203 + * Start installation of installers and XPI plugins. 1.204 + * @param aInstallerPlugins An array of objects which should have the 1.205 + * properties "pid", "InstallerLocation", 1.206 + * and "InstallerHash" 1.207 + * @param aXPIPlugins An array of objects which should have the 1.208 + * properties "pid", "XPILocation", 1.209 + * and "XPIHash" 1.210 + */ 1.211 + startPluginInstallation: function (aInstallerPlugins, 1.212 + aXPIPlugins) 1.213 + { 1.214 + this._xpiPlugins = aXPIPlugins; 1.215 + this._xpisPending = aXPIPlugins.length; 1.216 + 1.217 + aXPIPlugins.forEach(function(plugin) { 1.218 + AddonManager.getInstallForURL(plugin.XPILocation, function(install) { 1.219 + install.addListener(PluginInstallService); 1.220 + install.install(); 1.221 + }, "application/x-xpinstall", plugin.XPIHash); 1.222 + }); 1.223 + 1.224 + // InstallerObserver may finish immediately so we must initialise the 1.225 + // installers after setting the number of installers and xpis pending 1.226 + this._installersPending = aInstallerPlugins.length; 1.227 + this._installerPlugins = [new InstallerObserver(plugin) 1.228 + for each (plugin in aInstallerPlugins)]; 1.229 + }, 1.230 + 1.231 + _fireFinishedNotification: function() 1.232 + { 1.233 + if (this._installersPending == 0 && this._xpisPending == 0) 1.234 + gPluginInstaller.pluginInstallationProgress(null, INSTALLS_COMPLETE, null); 1.235 + }, 1.236 + 1.237 + getPidForInstall: function(install) { 1.238 + for (let i = 0; i < this._xpiPlugins.length; i++) { 1.239 + if (install.sourceURI.spec == this._xpiPlugins[i].XPILocation) 1.240 + return this._xpiPlugins[i].pid; 1.241 + } 1.242 + return -1; 1.243 + }, 1.244 + 1.245 + // InstallListener interface 1.246 + onDownloadStarted: function(install) { 1.247 + var pid = this.getPidForInstall(install); 1.248 + gPluginInstaller.pluginInstallationProgress(pid, DOWNLOAD_STARTED, null); 1.249 + }, 1.250 + 1.251 + onDownloadProgress: function(install) { 1.252 + var pid = this.getPidForInstall(install); 1.253 + gPluginInstaller.pluginInstallationProgressMeter(pid, install.progress, 1.254 + install.maxProgress); 1.255 + }, 1.256 + 1.257 + onDownloadEnded: function(install) { 1.258 + var pid = this.getPidForInstall(install); 1.259 + gPluginInstaller.pluginInstallationProgress(pid, DOWNLOAD_FINISHED, null); 1.260 + }, 1.261 + 1.262 + onDownloadFailed: function(install) { 1.263 + var pid = this.getPidForInstall(install); 1.264 + switch (install.error) { 1.265 + case AddonManager.ERROR_NETWORK_FAILURE: 1.266 + var errorMsg = getLocalizedError("error-228"); 1.267 + break; 1.268 + case AddonManager.ERROR_INCORRECT_HASH: 1.269 + var errorMsg = getLocalizedError("error-261"); 1.270 + break; 1.271 + case AddonManager.ERROR_CORRUPT_FILE: 1.272 + var errorMsg = getLocalizedError("error-207"); 1.273 + break; 1.274 + } 1.275 + gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, errorMsg); 1.276 + 1.277 + this._xpisPending--; 1.278 + this._fireFinishedNotification(); 1.279 + }, 1.280 + 1.281 + onInstallStarted: function(install) { 1.282 + var pid = this.getPidForInstall(install); 1.283 + gPluginInstaller.pluginInstallationProgress(pid, INSTALL_STARTED, null); 1.284 + }, 1.285 + 1.286 + onInstallEnded: function(install, addon) { 1.287 + var pid = this.getPidForInstall(install); 1.288 + gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, null); 1.289 + 1.290 + this._xpisPending--; 1.291 + this._fireFinishedNotification(); 1.292 + }, 1.293 + 1.294 + onInstallFailed: function(install) { 1.295 + var pid = this.getPidForInstall(install); 1.296 + gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, 1.297 + getLocalizedError("error-203")); 1.298 + 1.299 + this._xpisPending--; 1.300 + this._fireFinishedNotification(); 1.301 + } 1.302 +}