|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 Components.utils.import("resource://gre/modules/AddonManager.jsm"); |
|
6 |
|
7 const DOWNLOAD_STARTED = 0; |
|
8 const DOWNLOAD_FINISHED = 1; |
|
9 const INSTALL_STARTED = 2; |
|
10 const INSTALL_FINISHED = 3; |
|
11 const INSTALLS_COMPLETE = 4; |
|
12 |
|
13 function getLocalizedError(key) |
|
14 { |
|
15 return document.getElementById("xpinstallStrings").getString(key); |
|
16 } |
|
17 |
|
18 function binaryToHex(input) |
|
19 { |
|
20 return [('0' + input.charCodeAt(i).toString(16)).slice(-2) |
|
21 for (i in input)].join(''); |
|
22 } |
|
23 |
|
24 function verifyHash(aFile, aHash) |
|
25 { |
|
26 try { |
|
27 var [, method, hash] = /^([A-Za-z0-9]+):(.*)$/.exec(aHash); |
|
28 |
|
29 var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. |
|
30 createInstance(Components.interfaces.nsIFileInputStream); |
|
31 fis.init(aFile, -1, -1, 0); |
|
32 |
|
33 var hasher = Components.classes['@mozilla.org/security/hash;1']. |
|
34 createInstance(Components.interfaces.nsICryptoHash); |
|
35 hasher.initWithString(method); |
|
36 hasher.updateFromStream(fis, -1); |
|
37 dlhash = binaryToHex(hasher.finish(false)); |
|
38 return dlhash == hash; |
|
39 } |
|
40 catch (e) { |
|
41 Components.utils.reportError(e); |
|
42 return false; |
|
43 } |
|
44 } |
|
45 |
|
46 function InstallerObserver(aPlugin) |
|
47 { |
|
48 this._plugin = aPlugin; |
|
49 this._init(); |
|
50 } |
|
51 |
|
52 InstallerObserver.prototype = { |
|
53 _init: function() |
|
54 { |
|
55 try { |
|
56 var ios = Components.classes["@mozilla.org/network/io-service;1"]. |
|
57 getService(Components.interfaces.nsIIOService); |
|
58 var uri = ios.newURI(this._plugin.InstallerLocation, null, null); |
|
59 uri.QueryInterface(Components.interfaces.nsIURL); |
|
60 |
|
61 // Use a local filename appropriate for the OS |
|
62 var leafName = uri.fileName; |
|
63 var os = Components.classes["@mozilla.org/xre/app-info;1"] |
|
64 .getService(Components.interfaces.nsIXULRuntime) |
|
65 .OS; |
|
66 if (os == "WINNT" && leafName.indexOf(".") < 0) |
|
67 leafName += ".exe"; |
|
68 |
|
69 var dirs = Components.classes["@mozilla.org/file/directory_service;1"]. |
|
70 getService(Components.interfaces.nsIProperties); |
|
71 |
|
72 var resultFile = dirs.get("TmpD", Components.interfaces.nsIFile); |
|
73 resultFile.append(leafName); |
|
74 resultFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, |
|
75 0770); |
|
76 |
|
77 var channel = ios.newChannelFromURI(uri); |
|
78 this._downloader = |
|
79 Components.classes["@mozilla.org/network/downloader;1"]. |
|
80 createInstance(Components.interfaces.nsIDownloader); |
|
81 this._downloader.init(this, resultFile); |
|
82 channel.notificationCallbacks = this; |
|
83 |
|
84 this._fireNotification(DOWNLOAD_STARTED, null); |
|
85 |
|
86 channel.asyncOpen(this._downloader, null); |
|
87 } |
|
88 catch (e) { |
|
89 Components.utils.reportError(e); |
|
90 this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-228")); |
|
91 if (resultFile && resultFile.exists()) |
|
92 resultfile.remove(false); |
|
93 } |
|
94 }, |
|
95 |
|
96 /** |
|
97 * Inform the gPluginInstaller about what's going on. |
|
98 */ |
|
99 _fireNotification: function(aStatus, aErrorMsg) |
|
100 { |
|
101 gPluginInstaller.pluginInstallationProgress(this._plugin.pid, |
|
102 aStatus, aErrorMsg); |
|
103 |
|
104 if (aStatus == INSTALL_FINISHED) { |
|
105 --PluginInstallService._installersPending; |
|
106 PluginInstallService._fireFinishedNotification(); |
|
107 } |
|
108 }, |
|
109 |
|
110 QueryInterface: function(iid) |
|
111 { |
|
112 if (iid.equals(Components.interfaces.nsISupports) || |
|
113 iid.equals(Components.interfaces.nsIInterfaceRequestor) || |
|
114 iid.equals(Components.interfaces.nsIDownloadObserver) || |
|
115 iid.equals(Components.interfaces.nsIProgressEventSink)) |
|
116 return this; |
|
117 |
|
118 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
119 }, |
|
120 |
|
121 getInterface: function(iid) |
|
122 { |
|
123 if (iid.equals(Components.interfaces.nsIProgressEventSink)) |
|
124 return this; |
|
125 |
|
126 return null; |
|
127 }, |
|
128 |
|
129 onDownloadComplete: function(downloader, request, ctxt, status, result) |
|
130 { |
|
131 if (!Components.isSuccessCode(status)) { |
|
132 // xpinstall error 228 is "Download Error" |
|
133 this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-228")); |
|
134 result.remove(false); |
|
135 return; |
|
136 } |
|
137 |
|
138 this._fireNotification(DOWNLOAD_FINISHED); |
|
139 |
|
140 if (this._plugin.InstallerHash && |
|
141 !verifyHash(result, this._plugin.InstallerHash)) { |
|
142 // xpinstall error 261 is "Invalid file hash..." |
|
143 this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-261")); |
|
144 result.remove(false); |
|
145 return; |
|
146 } |
|
147 |
|
148 this._fireNotification(INSTALL_STARTED); |
|
149 |
|
150 result.QueryInterface(Components.interfaces.nsILocalFile); |
|
151 try { |
|
152 // Make sure the file is executable |
|
153 result.permissions = 0770; |
|
154 var process = Components.classes["@mozilla.org/process/util;1"] |
|
155 .createInstance(Components.interfaces.nsIProcess); |
|
156 process.init(result); |
|
157 var self = this; |
|
158 process.runAsync([], 0, { |
|
159 observe: function(subject, topic, data) { |
|
160 if (topic != "process-finished") { |
|
161 Components.utils.reportError("Failed to launch installer"); |
|
162 self._fireNotification(INSTALL_FINISHED, |
|
163 getLocalizedError("error-207")); |
|
164 } |
|
165 else if (process.exitValue != 0) { |
|
166 Components.utils.reportError("Installer returned exit code " + process.exitValue); |
|
167 self._fireNotification(INSTALL_FINISHED, |
|
168 getLocalizedError("error-203")); |
|
169 } |
|
170 else { |
|
171 self._fireNotification(INSTALL_FINISHED, null); |
|
172 } |
|
173 result.remove(false); |
|
174 } |
|
175 }); |
|
176 } |
|
177 catch (e) { |
|
178 Components.utils.reportError(e); |
|
179 this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-207")); |
|
180 result.remove(false); |
|
181 } |
|
182 }, |
|
183 |
|
184 onProgress: function(aRequest, aContext, aProgress, aProgressMax) |
|
185 { |
|
186 gPluginInstaller.pluginInstallationProgressMeter(this._plugin.pid, |
|
187 aProgress, |
|
188 aProgressMax); |
|
189 }, |
|
190 |
|
191 onStatus: function(aRequest, aContext, aStatus, aStatusArg) |
|
192 { |
|
193 /* pass */ |
|
194 } |
|
195 }; |
|
196 |
|
197 var PluginInstallService = { |
|
198 |
|
199 /** |
|
200 * Start installation of installers and XPI plugins. |
|
201 * @param aInstallerPlugins An array of objects which should have the |
|
202 * properties "pid", "InstallerLocation", |
|
203 * and "InstallerHash" |
|
204 * @param aXPIPlugins An array of objects which should have the |
|
205 * properties "pid", "XPILocation", |
|
206 * and "XPIHash" |
|
207 */ |
|
208 startPluginInstallation: function (aInstallerPlugins, |
|
209 aXPIPlugins) |
|
210 { |
|
211 this._xpiPlugins = aXPIPlugins; |
|
212 this._xpisPending = aXPIPlugins.length; |
|
213 |
|
214 aXPIPlugins.forEach(function(plugin) { |
|
215 AddonManager.getInstallForURL(plugin.XPILocation, function(install) { |
|
216 install.addListener(PluginInstallService); |
|
217 install.install(); |
|
218 }, "application/x-xpinstall", plugin.XPIHash); |
|
219 }); |
|
220 |
|
221 // InstallerObserver may finish immediately so we must initialise the |
|
222 // installers after setting the number of installers and xpis pending |
|
223 this._installersPending = aInstallerPlugins.length; |
|
224 this._installerPlugins = [new InstallerObserver(plugin) |
|
225 for each (plugin in aInstallerPlugins)]; |
|
226 }, |
|
227 |
|
228 _fireFinishedNotification: function() |
|
229 { |
|
230 if (this._installersPending == 0 && this._xpisPending == 0) |
|
231 gPluginInstaller.pluginInstallationProgress(null, INSTALLS_COMPLETE, null); |
|
232 }, |
|
233 |
|
234 getPidForInstall: function(install) { |
|
235 for (let i = 0; i < this._xpiPlugins.length; i++) { |
|
236 if (install.sourceURI.spec == this._xpiPlugins[i].XPILocation) |
|
237 return this._xpiPlugins[i].pid; |
|
238 } |
|
239 return -1; |
|
240 }, |
|
241 |
|
242 // InstallListener interface |
|
243 onDownloadStarted: function(install) { |
|
244 var pid = this.getPidForInstall(install); |
|
245 gPluginInstaller.pluginInstallationProgress(pid, DOWNLOAD_STARTED, null); |
|
246 }, |
|
247 |
|
248 onDownloadProgress: function(install) { |
|
249 var pid = this.getPidForInstall(install); |
|
250 gPluginInstaller.pluginInstallationProgressMeter(pid, install.progress, |
|
251 install.maxProgress); |
|
252 }, |
|
253 |
|
254 onDownloadEnded: function(install) { |
|
255 var pid = this.getPidForInstall(install); |
|
256 gPluginInstaller.pluginInstallationProgress(pid, DOWNLOAD_FINISHED, null); |
|
257 }, |
|
258 |
|
259 onDownloadFailed: function(install) { |
|
260 var pid = this.getPidForInstall(install); |
|
261 switch (install.error) { |
|
262 case AddonManager.ERROR_NETWORK_FAILURE: |
|
263 var errorMsg = getLocalizedError("error-228"); |
|
264 break; |
|
265 case AddonManager.ERROR_INCORRECT_HASH: |
|
266 var errorMsg = getLocalizedError("error-261"); |
|
267 break; |
|
268 case AddonManager.ERROR_CORRUPT_FILE: |
|
269 var errorMsg = getLocalizedError("error-207"); |
|
270 break; |
|
271 } |
|
272 gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, errorMsg); |
|
273 |
|
274 this._xpisPending--; |
|
275 this._fireFinishedNotification(); |
|
276 }, |
|
277 |
|
278 onInstallStarted: function(install) { |
|
279 var pid = this.getPidForInstall(install); |
|
280 gPluginInstaller.pluginInstallationProgress(pid, INSTALL_STARTED, null); |
|
281 }, |
|
282 |
|
283 onInstallEnded: function(install, addon) { |
|
284 var pid = this.getPidForInstall(install); |
|
285 gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, null); |
|
286 |
|
287 this._xpisPending--; |
|
288 this._fireFinishedNotification(); |
|
289 }, |
|
290 |
|
291 onInstallFailed: function(install) { |
|
292 var pid = this.getPidForInstall(install); |
|
293 gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, |
|
294 getLocalizedError("error-203")); |
|
295 |
|
296 this._xpisPending--; |
|
297 this._fireFinishedNotification(); |
|
298 } |
|
299 } |