|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 "use strict"; |
|
8 |
|
9 const Cc = Components.classes; |
|
10 const Ci = Components.interfaces; |
|
11 const Cr = Components.results; |
|
12 |
|
13 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
14 Components.utils.import("resource://gre/modules/AddonManager.jsm"); |
|
15 Components.utils.import("resource://gre/modules/Services.jsm"); |
|
16 |
|
17 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", |
|
18 "resource://gre/modules/FileUtils.jsm"); |
|
19 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", |
|
20 "resource://gre/modules/UpdateChannel.jsm"); |
|
21 XPCOMUtils.defineLazyModuleGetter(this, "OS", |
|
22 "resource://gre/modules/osfile.jsm"); |
|
23 XPCOMUtils.defineLazyModuleGetter(this, "Task", |
|
24 "resource://gre/modules/Task.jsm"); |
|
25 |
|
26 const TOOLKIT_ID = "toolkit@mozilla.org" |
|
27 const KEY_PROFILEDIR = "ProfD"; |
|
28 const KEY_APPDIR = "XCurProcD"; |
|
29 const FILE_BLOCKLIST = "blocklist.xml"; |
|
30 const PREF_BLOCKLIST_LASTUPDATETIME = "app.update.lastUpdateTime.blocklist-background-update-timer"; |
|
31 const PREF_BLOCKLIST_URL = "extensions.blocklist.url"; |
|
32 const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL"; |
|
33 const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled"; |
|
34 const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval"; |
|
35 const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level"; |
|
36 const PREF_BLOCKLIST_PINGCOUNTTOTAL = "extensions.blocklist.pingCountTotal"; |
|
37 const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; |
|
38 const PREF_BLOCKLIST_SUPPRESSUI = "extensions.blocklist.suppressUI"; |
|
39 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser"; |
|
40 const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale"; |
|
41 const PREF_APP_DISTRIBUTION = "distribution.id"; |
|
42 const PREF_APP_DISTRIBUTION_VERSION = "distribution.version"; |
|
43 const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled"; |
|
44 const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; |
|
45 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" |
|
46 const UNKNOWN_XPCOM_ABI = "unknownABI"; |
|
47 const URI_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul" |
|
48 const DEFAULT_SEVERITY = 3; |
|
49 const DEFAULT_LEVEL = 2; |
|
50 const MAX_BLOCK_LEVEL = 3; |
|
51 const SEVERITY_OUTDATED = 0; |
|
52 const VULNERABILITYSTATUS_NONE = 0; |
|
53 const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1; |
|
54 const VULNERABILITYSTATUS_NO_UPDATE = 2; |
|
55 |
|
56 const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"]; |
|
57 |
|
58 var gLoggingEnabled = null; |
|
59 var gBlocklistEnabled = true; |
|
60 var gBlocklistLevel = DEFAULT_LEVEL; |
|
61 |
|
62 XPCOMUtils.defineLazyServiceGetter(this, "gConsole", |
|
63 "@mozilla.org/consoleservice;1", |
|
64 "nsIConsoleService"); |
|
65 |
|
66 XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker", |
|
67 "@mozilla.org/xpcom/version-comparator;1", |
|
68 "nsIVersionComparator"); |
|
69 |
|
70 XPCOMUtils.defineLazyGetter(this, "gPref", function bls_gPref() { |
|
71 return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService). |
|
72 QueryInterface(Ci.nsIPrefBranch); |
|
73 }); |
|
74 |
|
75 XPCOMUtils.defineLazyGetter(this, "gApp", function bls_gApp() { |
|
76 return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo). |
|
77 QueryInterface(Ci.nsIXULRuntime); |
|
78 }); |
|
79 |
|
80 XPCOMUtils.defineLazyGetter(this, "gABI", function bls_gABI() { |
|
81 let abi = null; |
|
82 try { |
|
83 abi = gApp.XPCOMABI; |
|
84 } |
|
85 catch (e) { |
|
86 LOG("BlockList Global gABI: XPCOM ABI unknown."); |
|
87 } |
|
88 #ifdef XP_MACOSX |
|
89 // Mac universal build should report a different ABI than either macppc |
|
90 // or mactel. |
|
91 let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"]. |
|
92 getService(Ci.nsIMacUtils); |
|
93 |
|
94 if (macutils.isUniversalBinary) |
|
95 abi += "-u-" + macutils.architecturesInBinary; |
|
96 #endif |
|
97 return abi; |
|
98 }); |
|
99 |
|
100 XPCOMUtils.defineLazyGetter(this, "gOSVersion", function bls_gOSVersion() { |
|
101 let osVersion; |
|
102 let sysInfo = Cc["@mozilla.org/system-info;1"]. |
|
103 getService(Ci.nsIPropertyBag2); |
|
104 try { |
|
105 osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"); |
|
106 } |
|
107 catch (e) { |
|
108 LOG("BlockList Global gOSVersion: OS Version unknown."); |
|
109 } |
|
110 |
|
111 if (osVersion) { |
|
112 try { |
|
113 osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")"; |
|
114 } |
|
115 catch (e) { |
|
116 // Not all platforms have a secondary widget library, so an error is nothing to worry about. |
|
117 } |
|
118 osVersion = encodeURIComponent(osVersion); |
|
119 } |
|
120 return osVersion; |
|
121 }); |
|
122 |
|
123 // shared code for suppressing bad cert dialogs |
|
124 XPCOMUtils.defineLazyGetter(this, "gCertUtils", function bls_gCertUtils() { |
|
125 let temp = { }; |
|
126 Components.utils.import("resource://gre/modules/CertUtils.jsm", temp); |
|
127 return temp; |
|
128 }); |
|
129 |
|
130 function getObserverService() { |
|
131 return Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); |
|
132 } |
|
133 |
|
134 /** |
|
135 * Logs a string to the error console. |
|
136 * @param string |
|
137 * The string to write to the error console.. |
|
138 */ |
|
139 function LOG(string) { |
|
140 if (gLoggingEnabled) { |
|
141 dump("*** " + string + "\n"); |
|
142 gConsole.logStringMessage(string); |
|
143 } |
|
144 } |
|
145 |
|
146 /** |
|
147 * Gets a preference value, handling the case where there is no default. |
|
148 * @param func |
|
149 * The name of the preference function to call, on nsIPrefBranch |
|
150 * @param preference |
|
151 * The name of the preference |
|
152 * @param defaultValue |
|
153 * The default value to return in the event the preference has |
|
154 * no setting |
|
155 * @returns The value of the preference, or undefined if there was no |
|
156 * user or default value. |
|
157 */ |
|
158 function getPref(func, preference, defaultValue) { |
|
159 try { |
|
160 return gPref[func](preference); |
|
161 } |
|
162 catch (e) { |
|
163 } |
|
164 return defaultValue; |
|
165 } |
|
166 |
|
167 /** |
|
168 * Constructs a URI to a spec. |
|
169 * @param spec |
|
170 * The spec to construct a URI to |
|
171 * @returns The nsIURI constructed. |
|
172 */ |
|
173 function newURI(spec) { |
|
174 var ioServ = Cc["@mozilla.org/network/io-service;1"]. |
|
175 getService(Ci.nsIIOService); |
|
176 return ioServ.newURI(spec, null, null); |
|
177 } |
|
178 |
|
179 // Restarts the application checking in with observers first |
|
180 function restartApp() { |
|
181 // Notify all windows that an application quit has been requested. |
|
182 var os = Cc["@mozilla.org/observer-service;1"]. |
|
183 getService(Ci.nsIObserverService); |
|
184 var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]. |
|
185 createInstance(Ci.nsISupportsPRBool); |
|
186 os.notifyObservers(cancelQuit, "quit-application-requested", null); |
|
187 |
|
188 // Something aborted the quit process. |
|
189 if (cancelQuit.data) |
|
190 return; |
|
191 |
|
192 var as = Cc["@mozilla.org/toolkit/app-startup;1"]. |
|
193 getService(Ci.nsIAppStartup); |
|
194 as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit); |
|
195 } |
|
196 |
|
197 /** |
|
198 * Checks whether this blocklist element is valid for the current OS and ABI. |
|
199 * If the element has an "os" attribute then the current OS must appear in |
|
200 * its comma separated list for the element to be valid. Similarly for the |
|
201 * xpcomabi attribute. |
|
202 */ |
|
203 function matchesOSABI(blocklistElement) { |
|
204 if (blocklistElement.hasAttribute("os")) { |
|
205 var choices = blocklistElement.getAttribute("os").split(","); |
|
206 if (choices.length > 0 && choices.indexOf(gApp.OS) < 0) |
|
207 return false; |
|
208 } |
|
209 |
|
210 if (blocklistElement.hasAttribute("xpcomabi")) { |
|
211 choices = blocklistElement.getAttribute("xpcomabi").split(","); |
|
212 if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0) |
|
213 return false; |
|
214 } |
|
215 |
|
216 return true; |
|
217 } |
|
218 |
|
219 /** |
|
220 * Gets the current value of the locale. It's possible for this preference to |
|
221 * be localized, so we have to do a little extra work here. Similar code |
|
222 * exists in nsHttpHandler.cpp when building the UA string. |
|
223 */ |
|
224 function getLocale() { |
|
225 try { |
|
226 // Get the default branch |
|
227 var defaultPrefs = gPref.getDefaultBranch(null); |
|
228 return defaultPrefs.getComplexValue(PREF_GENERAL_USERAGENT_LOCALE, |
|
229 Ci.nsIPrefLocalizedString).data; |
|
230 } catch (e) {} |
|
231 |
|
232 return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE); |
|
233 } |
|
234 |
|
235 /* Get the distribution pref values, from defaults only */ |
|
236 function getDistributionPrefValue(aPrefName) { |
|
237 var prefValue = "default"; |
|
238 |
|
239 var defaults = gPref.getDefaultBranch(null); |
|
240 try { |
|
241 prefValue = defaults.getCharPref(aPrefName); |
|
242 } catch (e) { |
|
243 // use default when pref not found |
|
244 } |
|
245 |
|
246 return prefValue; |
|
247 } |
|
248 |
|
249 /** |
|
250 * Parse a string representation of a regular expression. Needed because we |
|
251 * use the /pattern/flags form (because it's detectable), which is only |
|
252 * supported as a literal in JS. |
|
253 * |
|
254 * @param aStr |
|
255 * String representation of regexp |
|
256 * @return RegExp instance |
|
257 */ |
|
258 function parseRegExp(aStr) { |
|
259 let lastSlash = aStr.lastIndexOf("/"); |
|
260 let pattern = aStr.slice(1, lastSlash); |
|
261 let flags = aStr.slice(lastSlash + 1); |
|
262 return new RegExp(pattern, flags); |
|
263 } |
|
264 |
|
265 /** |
|
266 * Manages the Blocklist. The Blocklist is a representation of the contents of |
|
267 * blocklist.xml and allows us to remotely disable / re-enable blocklisted |
|
268 * items managed by the Extension Manager with an item's appDisabled property. |
|
269 * It also blocklists plugins with data from blocklist.xml. |
|
270 */ |
|
271 |
|
272 function Blocklist() { |
|
273 let os = getObserverService(); |
|
274 os.addObserver(this, "xpcom-shutdown", false); |
|
275 gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); |
|
276 gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); |
|
277 gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), |
|
278 MAX_BLOCK_LEVEL); |
|
279 gPref.addObserver("extensions.blocklist.", this, false); |
|
280 gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false); |
|
281 } |
|
282 |
|
283 Blocklist.prototype = { |
|
284 /** |
|
285 * Extension ID -> array of Version Ranges |
|
286 * Each value in the version range array is a JS Object that has the |
|
287 * following properties: |
|
288 * "minVersion" The minimum version in a version range (default = 0) |
|
289 * "maxVersion" The maximum version in a version range (default = *) |
|
290 * "targetApps" Application ID -> array of Version Ranges |
|
291 * (default = current application ID) |
|
292 * Each value in the version range array is a JS Object that |
|
293 * has the following properties: |
|
294 * "minVersion" The minimum version in a version range |
|
295 * (default = 0) |
|
296 * "maxVersion" The maximum version in a version range |
|
297 * (default = *) |
|
298 */ |
|
299 _addonEntries: null, |
|
300 _pluginEntries: null, |
|
301 |
|
302 observe: function Blocklist_observe(aSubject, aTopic, aData) { |
|
303 switch (aTopic) { |
|
304 case "xpcom-shutdown": |
|
305 let os = getObserverService(); |
|
306 os.removeObserver(this, "xpcom-shutdown"); |
|
307 gPref.removeObserver("extensions.blocklist.", this); |
|
308 gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this); |
|
309 break; |
|
310 case "nsPref:changed": |
|
311 switch (aData) { |
|
312 case PREF_EM_LOGGING_ENABLED: |
|
313 gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); |
|
314 break; |
|
315 case PREF_BLOCKLIST_ENABLED: |
|
316 gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); |
|
317 this._loadBlocklist(); |
|
318 this._blocklistUpdated(null, null); |
|
319 break; |
|
320 case PREF_BLOCKLIST_LEVEL: |
|
321 gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), |
|
322 MAX_BLOCK_LEVEL); |
|
323 this._blocklistUpdated(null, null); |
|
324 break; |
|
325 } |
|
326 break; |
|
327 } |
|
328 }, |
|
329 |
|
330 /* See nsIBlocklistService */ |
|
331 isAddonBlocklisted: function Blocklist_isAddonBlocklisted(addon, appVersion, toolkitVersion) { |
|
332 return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) == |
|
333 Ci.nsIBlocklistService.STATE_BLOCKED; |
|
334 }, |
|
335 |
|
336 /* See nsIBlocklistService */ |
|
337 getAddonBlocklistState: function Blocklist_getAddonBlocklistState(addon, appVersion, toolkitVersion) { |
|
338 if (!this._addonEntries) |
|
339 this._loadBlocklist(); |
|
340 return this._getAddonBlocklistState(addon, this._addonEntries, |
|
341 appVersion, toolkitVersion); |
|
342 }, |
|
343 |
|
344 /** |
|
345 * Private version of getAddonBlocklistState that allows the caller to pass in |
|
346 * the add-on blocklist entries to compare against. |
|
347 * |
|
348 * @param id |
|
349 * The ID of the item to get the blocklist state for. |
|
350 * @param version |
|
351 * The version of the item to get the blocklist state for. |
|
352 * @param addonEntries |
|
353 * The add-on blocklist entries to compare against. |
|
354 * @param appVersion |
|
355 * The application version to compare to, will use the current |
|
356 * version if null. |
|
357 * @param toolkitVersion |
|
358 * The toolkit version to compare to, will use the current version if |
|
359 * null. |
|
360 * @returns The blocklist state for the item, one of the STATE constants as |
|
361 * defined in nsIBlocklistService. |
|
362 */ |
|
363 _getAddonBlocklistState: function Blocklist_getAddonBlocklistStateCall(addon, |
|
364 addonEntries, appVersion, toolkitVersion) { |
|
365 if (!gBlocklistEnabled) |
|
366 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; |
|
367 |
|
368 if (!appVersion) |
|
369 appVersion = gApp.version; |
|
370 if (!toolkitVersion) |
|
371 toolkitVersion = gApp.platformVersion; |
|
372 |
|
373 var blItem = this._findMatchingAddonEntry(addonEntries, addon); |
|
374 if (!blItem) |
|
375 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; |
|
376 |
|
377 for (let currentblItem of blItem.versions) { |
|
378 if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion)) |
|
379 return currentblItem.severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED : |
|
380 Ci.nsIBlocklistService.STATE_SOFTBLOCKED; |
|
381 } |
|
382 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; |
|
383 }, |
|
384 |
|
385 /** |
|
386 * Returns the set of prefs of the add-on stored in the blocklist file |
|
387 * (probably to revert them on disabling). |
|
388 * @param addon |
|
389 * The add-on whose to-be-reset prefs are to be found. |
|
390 */ |
|
391 _getAddonPrefs: function Blocklist_getAddonPrefs(addon) { |
|
392 let entry = this._findMatchingAddonEntry(this._addonEntries, addon); |
|
393 return entry.prefs.slice(0); |
|
394 }, |
|
395 |
|
396 _findMatchingAddonEntry: function Blocklist_findMatchingAddonEntry(aAddonEntries, |
|
397 aAddon) { |
|
398 if (!aAddon) |
|
399 return null; |
|
400 // Returns true if the params object passes the constraints set by entry. |
|
401 // (For every non-null property in entry, the same key must exist in |
|
402 // params and value must be the same) |
|
403 function checkEntry(entry, params) { |
|
404 for (let [key, value] of entry) { |
|
405 if (value === null || value === undefined) |
|
406 continue; |
|
407 if (params[key]) { |
|
408 if (value instanceof RegExp) { |
|
409 if (!value.test(params[key])) { |
|
410 return false; |
|
411 } |
|
412 } else if (value !== params[key]) { |
|
413 return false; |
|
414 } |
|
415 } else { |
|
416 return false; |
|
417 } |
|
418 } |
|
419 return true; |
|
420 } |
|
421 |
|
422 let params = {}; |
|
423 for (let filter of EXTENSION_BLOCK_FILTERS) { |
|
424 params[filter] = aAddon[filter]; |
|
425 } |
|
426 if (params.creator) |
|
427 params.creator = params.creator.name; |
|
428 for (let entry of aAddonEntries) { |
|
429 if (checkEntry(entry.attributes, params)) { |
|
430 return entry; |
|
431 } |
|
432 } |
|
433 return null; |
|
434 }, |
|
435 |
|
436 /* See nsIBlocklistService */ |
|
437 getAddonBlocklistURL: function Blocklist_getAddonBlocklistURL(addon, appVersion, toolkitVersion) { |
|
438 if (!gBlocklistEnabled) |
|
439 return ""; |
|
440 |
|
441 if (!this._addonEntries) |
|
442 this._loadBlocklist(); |
|
443 |
|
444 let blItem = this._findMatchingAddonEntry(this._addonEntries, addon); |
|
445 if (!blItem || !blItem.blockID) |
|
446 return null; |
|
447 |
|
448 return this._createBlocklistURL(blItem.blockID); |
|
449 }, |
|
450 |
|
451 _createBlocklistURL: function Blocklist_createBlocklistURL(id) { |
|
452 let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL); |
|
453 url = url.replace(/%blockID%/g, id); |
|
454 |
|
455 return url; |
|
456 }, |
|
457 |
|
458 notify: function Blocklist_notify(aTimer) { |
|
459 if (!gBlocklistEnabled) |
|
460 return; |
|
461 |
|
462 try { |
|
463 var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL); |
|
464 } |
|
465 catch (e) { |
|
466 LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" + |
|
467 " is missing!"); |
|
468 return; |
|
469 } |
|
470 |
|
471 var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0); |
|
472 var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1); |
|
473 var daysSinceLastPing = 0; |
|
474 if (pingCountVersion == 0) { |
|
475 daysSinceLastPing = "new"; |
|
476 } |
|
477 else { |
|
478 // Seconds in one day is used because nsIUpdateTimerManager stores the |
|
479 // last update time in seconds. |
|
480 let secondsInDay = 60 * 60 * 24; |
|
481 let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0); |
|
482 if (lastUpdateTime == 0) { |
|
483 daysSinceLastPing = "invalid"; |
|
484 } |
|
485 else { |
|
486 let now = Math.round(Date.now() / 1000); |
|
487 daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay); |
|
488 } |
|
489 |
|
490 if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") { |
|
491 pingCountVersion = pingCountTotal = "invalid"; |
|
492 } |
|
493 } |
|
494 |
|
495 if (pingCountVersion < 1) |
|
496 pingCountVersion = 1; |
|
497 if (pingCountTotal < 1) |
|
498 pingCountTotal = 1; |
|
499 |
|
500 dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID); |
|
501 dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version); |
|
502 dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name); |
|
503 dsURI = dsURI.replace(/%VERSION%/g, gApp.version); |
|
504 dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID); |
|
505 dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI); |
|
506 dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion); |
|
507 dsURI = dsURI.replace(/%LOCALE%/g, getLocale()); |
|
508 dsURI = dsURI.replace(/%CHANNEL%/g, UpdateChannel.get()); |
|
509 dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion); |
|
510 dsURI = dsURI.replace(/%DISTRIBUTION%/g, |
|
511 getDistributionPrefValue(PREF_APP_DISTRIBUTION)); |
|
512 dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g, |
|
513 getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION)); |
|
514 dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion); |
|
515 dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal); |
|
516 dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing); |
|
517 dsURI = dsURI.replace(/\+/g, "%2B"); |
|
518 |
|
519 // Under normal operations it will take around 5,883,516 years before the |
|
520 // preferences used to store pingCountVersion and pingCountTotal will rollover |
|
521 // so this code doesn't bother trying to do the "right thing" here. |
|
522 if (pingCountVersion != "invalid") { |
|
523 pingCountVersion++; |
|
524 if (pingCountVersion > 2147483647) { |
|
525 // Rollover to -1 if the value is greater than what is support by an |
|
526 // integer preference. The -1 indicates that the counter has been reset. |
|
527 pingCountVersion = -1; |
|
528 } |
|
529 gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion); |
|
530 } |
|
531 |
|
532 if (pingCountTotal != "invalid") { |
|
533 pingCountTotal++; |
|
534 if (pingCountTotal > 2147483647) { |
|
535 // Rollover to 1 if the value is greater than what is support by an |
|
536 // integer preference. |
|
537 pingCountTotal = -1; |
|
538 } |
|
539 gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal); |
|
540 } |
|
541 |
|
542 // Verify that the URI is valid |
|
543 try { |
|
544 var uri = newURI(dsURI); |
|
545 } |
|
546 catch (e) { |
|
547 LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" + |
|
548 "for: " + dsURI + ", error: " + e); |
|
549 return; |
|
550 } |
|
551 |
|
552 LOG("Blocklist::notify: Requesting " + uri.spec); |
|
553 var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. |
|
554 createInstance(Ci.nsIXMLHttpRequest); |
|
555 request.open("GET", uri.spec, true); |
|
556 request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(); |
|
557 request.overrideMimeType("text/xml"); |
|
558 request.setRequestHeader("Cache-Control", "no-cache"); |
|
559 request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest); |
|
560 |
|
561 var self = this; |
|
562 request.addEventListener("error", function errorEventListener(event) { |
|
563 self.onXMLError(event); }, false); |
|
564 request.addEventListener("load", function loadEventListener(event) { |
|
565 self.onXMLLoad(event); }, false); |
|
566 request.send(null); |
|
567 |
|
568 // When the blocklist loads we need to compare it to the current copy so |
|
569 // make sure we have loaded it. |
|
570 if (!this._addonEntries) |
|
571 this._loadBlocklist(); |
|
572 }, |
|
573 |
|
574 onXMLLoad: Task.async(function* (aEvent) { |
|
575 let request = aEvent.target; |
|
576 try { |
|
577 gCertUtils.checkCert(request.channel); |
|
578 } |
|
579 catch (e) { |
|
580 LOG("Blocklist::onXMLLoad: " + e); |
|
581 return; |
|
582 } |
|
583 let responseXML = request.responseXML; |
|
584 if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR || |
|
585 (request.status != 200 && request.status != 0)) { |
|
586 LOG("Blocklist::onXMLLoad: there was an error during load"); |
|
587 return; |
|
588 } |
|
589 |
|
590 var oldAddonEntries = this._addonEntries; |
|
591 var oldPluginEntries = this._pluginEntries; |
|
592 this._addonEntries = []; |
|
593 this._pluginEntries = []; |
|
594 |
|
595 this._loadBlocklistFromString(request.responseText); |
|
596 this._blocklistUpdated(oldAddonEntries, oldPluginEntries); |
|
597 |
|
598 try { |
|
599 let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST); |
|
600 yield OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"}); |
|
601 } catch (e) { |
|
602 LOG("Blocklist::onXMLLoad: " + e); |
|
603 } |
|
604 }), |
|
605 |
|
606 onXMLError: function Blocklist_onXMLError(aEvent) { |
|
607 try { |
|
608 var request = aEvent.target; |
|
609 // the following may throw (e.g. a local file or timeout) |
|
610 var status = request.status; |
|
611 } |
|
612 catch (e) { |
|
613 request = aEvent.target.channel.QueryInterface(Ci.nsIRequest); |
|
614 status = request.status; |
|
615 } |
|
616 var statusText = "nsIXMLHttpRequest channel unavailable"; |
|
617 // When status is 0 we don't have a valid channel. |
|
618 if (status != 0) { |
|
619 try { |
|
620 statusText = request.statusText; |
|
621 } catch (e) { |
|
622 } |
|
623 } |
|
624 LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" + |
|
625 statusText); |
|
626 }, |
|
627 |
|
628 /** |
|
629 * Finds the newest blocklist file from the application and the profile and |
|
630 * load it or does nothing if neither exist. |
|
631 */ |
|
632 _loadBlocklist: function Blocklist_loadBlocklist() { |
|
633 this._addonEntries = []; |
|
634 this._pluginEntries = []; |
|
635 var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); |
|
636 if (profFile.exists()) { |
|
637 this._loadBlocklistFromFile(profFile); |
|
638 return; |
|
639 } |
|
640 var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); |
|
641 if (appFile.exists()) { |
|
642 this._loadBlocklistFromFile(appFile); |
|
643 return; |
|
644 } |
|
645 LOG("Blocklist::_loadBlocklist: no XML File found"); |
|
646 }, |
|
647 |
|
648 /** |
|
649 # The blocklist XML file looks something like this: |
|
650 # |
|
651 # <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist"> |
|
652 # <emItems> |
|
653 # <emItem id="item_1@domain" blockID="i1"> |
|
654 # <prefs> |
|
655 # <pref>accessibility.accesskeycausesactivation</pref> |
|
656 # <pref>accessibility.blockautorefresh</pref> |
|
657 # </prefs> |
|
658 # <versionRange minVersion="1.0" maxVersion="2.0.*"> |
|
659 # <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"> |
|
660 # <versionRange minVersion="1.5" maxVersion="1.5.*"/> |
|
661 # <versionRange minVersion="1.7" maxVersion="1.7.*"/> |
|
662 # </targetApplication> |
|
663 # <targetApplication id="toolkit@mozilla.org"> |
|
664 # <versionRange minVersion="1.9" maxVersion="1.9.*"/> |
|
665 # </targetApplication> |
|
666 # </versionRange> |
|
667 # <versionRange minVersion="3.0" maxVersion="3.0.*"> |
|
668 # <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"> |
|
669 # <versionRange minVersion="1.5" maxVersion="1.5.*"/> |
|
670 # </targetApplication> |
|
671 # <targetApplication id="toolkit@mozilla.org"> |
|
672 # <versionRange minVersion="1.9" maxVersion="1.9.*"/> |
|
673 # </targetApplication> |
|
674 # </versionRange> |
|
675 # </emItem> |
|
676 # <emItem id="item_2@domain" blockID="i2"> |
|
677 # <versionRange minVersion="3.1" maxVersion="4.*"/> |
|
678 # </emItem> |
|
679 # <emItem id="item_3@domain"> |
|
680 # <versionRange> |
|
681 # <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"> |
|
682 # <versionRange minVersion="1.5" maxVersion="1.5.*"/> |
|
683 # </targetApplication> |
|
684 # </versionRange> |
|
685 # </emItem> |
|
686 # <emItem id="item_4@domain" blockID="i3"> |
|
687 # <versionRange> |
|
688 # <targetApplication> |
|
689 # <versionRange minVersion="1.5" maxVersion="1.5.*"/> |
|
690 # </targetApplication> |
|
691 # </versionRange> |
|
692 # <emItem id="/@badperson\.com$/"/> |
|
693 # </emItems> |
|
694 # <pluginItems> |
|
695 # <pluginItem blockID="i4"> |
|
696 # <!-- All match tags must match a plugin to blocklist a plugin --> |
|
697 # <match name="name" exp="some plugin"/> |
|
698 # <match name="description" exp="1[.]2[.]3"/> |
|
699 # </pluginItem> |
|
700 # </pluginItems> |
|
701 # </blocklist> |
|
702 */ |
|
703 |
|
704 _loadBlocklistFromFile: function Blocklist_loadBlocklistFromFile(file) { |
|
705 if (!gBlocklistEnabled) { |
|
706 LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled"); |
|
707 return; |
|
708 } |
|
709 |
|
710 if (!file.exists()) { |
|
711 LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path); |
|
712 return; |
|
713 } |
|
714 |
|
715 let text = ""; |
|
716 let fstream = null; |
|
717 let cstream = null; |
|
718 |
|
719 try { |
|
720 fstream = Components.classes["@mozilla.org/network/file-input-stream;1"] |
|
721 .createInstance(Components.interfaces.nsIFileInputStream); |
|
722 cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] |
|
723 .createInstance(Components.interfaces.nsIConverterInputStream); |
|
724 |
|
725 fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); |
|
726 cstream.init(fstream, "UTF-8", 0, 0); |
|
727 |
|
728 let (str = {}) { |
|
729 let read = 0; |
|
730 |
|
731 do { |
|
732 read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value |
|
733 text += str.value; |
|
734 } while (read != 0); |
|
735 } |
|
736 } catch (e) { |
|
737 LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e); |
|
738 } finally { |
|
739 cstream.close(); |
|
740 fstream.close(); |
|
741 } |
|
742 |
|
743 text && this._loadBlocklistFromString(text); |
|
744 }, |
|
745 |
|
746 _loadBlocklistFromString : function Blocklist_loadBlocklistFromString(text) { |
|
747 try { |
|
748 var parser = Cc["@mozilla.org/xmlextras/domparser;1"]. |
|
749 createInstance(Ci.nsIDOMParser); |
|
750 var doc = parser.parseFromString(text, "text/xml"); |
|
751 if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { |
|
752 LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " + |
|
753 "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" + |
|
754 "Received: " + doc.documentElement.namespaceURI); |
|
755 return; |
|
756 } |
|
757 |
|
758 var childNodes = doc.documentElement.childNodes; |
|
759 for (let element of childNodes) { |
|
760 if (!(element instanceof Ci.nsIDOMElement)) |
|
761 continue; |
|
762 switch (element.localName) { |
|
763 case "emItems": |
|
764 this._addonEntries = this._processItemNodes(element.childNodes, "em", |
|
765 this._handleEmItemNode); |
|
766 break; |
|
767 case "pluginItems": |
|
768 this._pluginEntries = this._processItemNodes(element.childNodes, "plugin", |
|
769 this._handlePluginItemNode); |
|
770 break; |
|
771 default: |
|
772 Services.obs.notifyObservers(element, |
|
773 "blocklist-data-" + element.localName, |
|
774 null); |
|
775 } |
|
776 } |
|
777 } |
|
778 catch (e) { |
|
779 LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e); |
|
780 return; |
|
781 } |
|
782 }, |
|
783 |
|
784 _processItemNodes: function Blocklist_processItemNodes(itemNodes, prefix, handler) { |
|
785 var result = []; |
|
786 var itemName = prefix + "Item"; |
|
787 for (var i = 0; i < itemNodes.length; ++i) { |
|
788 var blocklistElement = itemNodes.item(i); |
|
789 if (!(blocklistElement instanceof Ci.nsIDOMElement) || |
|
790 blocklistElement.localName != itemName) |
|
791 continue; |
|
792 |
|
793 handler(blocklistElement, result); |
|
794 } |
|
795 return result; |
|
796 }, |
|
797 |
|
798 _handleEmItemNode: function Blocklist_handleEmItemNode(blocklistElement, result) { |
|
799 if (!matchesOSABI(blocklistElement)) |
|
800 return; |
|
801 |
|
802 let blockEntry = { |
|
803 versions: [], |
|
804 prefs: [], |
|
805 blockID: null, |
|
806 attributes: new Map() |
|
807 // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes |
|
808 }; |
|
809 |
|
810 // Any filter starting with '/' is interpreted as a regex. So if an attribute |
|
811 // starts with a '/' it must be checked via a regex. |
|
812 function regExpCheck(attr) { |
|
813 return attr.startsWith("/") ? parseRegExp(attr) : attr; |
|
814 } |
|
815 |
|
816 for (let filter of EXTENSION_BLOCK_FILTERS) { |
|
817 let attr = blocklistElement.getAttribute(filter); |
|
818 if (attr) |
|
819 blockEntry.attributes.set(filter, regExpCheck(attr)); |
|
820 } |
|
821 |
|
822 var childNodes = blocklistElement.childNodes; |
|
823 |
|
824 for (let x = 0; x < childNodes.length; x++) { |
|
825 var childElement = childNodes.item(x); |
|
826 if (!(childElement instanceof Ci.nsIDOMElement)) |
|
827 continue; |
|
828 if (childElement.localName === "prefs") { |
|
829 let prefElements = childElement.childNodes; |
|
830 for (let i = 0; i < prefElements.length; i++) { |
|
831 let prefElement = prefElements.item(i); |
|
832 if (!(prefElement instanceof Ci.nsIDOMElement) || |
|
833 prefElement.localName !== "pref") |
|
834 continue; |
|
835 blockEntry.prefs.push(prefElement.textContent); |
|
836 } |
|
837 } |
|
838 else if (childElement.localName === "versionRange") |
|
839 blockEntry.versions.push(new BlocklistItemData(childElement)); |
|
840 } |
|
841 // if only the extension ID is specified block all versions of the |
|
842 // extension for the current application. |
|
843 if (blockEntry.versions.length == 0) |
|
844 blockEntry.versions.push(new BlocklistItemData(null)); |
|
845 |
|
846 blockEntry.blockID = blocklistElement.getAttribute("blockID"); |
|
847 |
|
848 result.push(blockEntry); |
|
849 }, |
|
850 |
|
851 _handlePluginItemNode: function Blocklist_handlePluginItemNode(blocklistElement, result) { |
|
852 if (!matchesOSABI(blocklistElement)) |
|
853 return; |
|
854 |
|
855 var matchNodes = blocklistElement.childNodes; |
|
856 var blockEntry = { |
|
857 matches: {}, |
|
858 versions: [], |
|
859 blockID: null, |
|
860 }; |
|
861 var hasMatch = false; |
|
862 for (var x = 0; x < matchNodes.length; ++x) { |
|
863 var matchElement = matchNodes.item(x); |
|
864 if (!(matchElement instanceof Ci.nsIDOMElement)) |
|
865 continue; |
|
866 if (matchElement.localName == "match") { |
|
867 var name = matchElement.getAttribute("name"); |
|
868 var exp = matchElement.getAttribute("exp"); |
|
869 try { |
|
870 blockEntry.matches[name] = new RegExp(exp, "m"); |
|
871 hasMatch = true; |
|
872 } catch (e) { |
|
873 // Ignore invalid regular expressions |
|
874 } |
|
875 } |
|
876 if (matchElement.localName == "versionRange") |
|
877 blockEntry.versions.push(new BlocklistItemData(matchElement)); |
|
878 } |
|
879 // Plugin entries require *something* to match to an actual plugin |
|
880 if (!hasMatch) |
|
881 return; |
|
882 // Add a default versionRange if there wasn't one specified |
|
883 if (blockEntry.versions.length == 0) |
|
884 blockEntry.versions.push(new BlocklistItemData(null)); |
|
885 |
|
886 blockEntry.blockID = blocklistElement.getAttribute("blockID"); |
|
887 |
|
888 result.push(blockEntry); |
|
889 }, |
|
890 |
|
891 /* See nsIBlocklistService */ |
|
892 getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin, |
|
893 appVersion, toolkitVersion) { |
|
894 if (!this._pluginEntries) |
|
895 this._loadBlocklist(); |
|
896 return this._getPluginBlocklistState(plugin, this._pluginEntries, |
|
897 appVersion, toolkitVersion); |
|
898 }, |
|
899 |
|
900 /** |
|
901 * Private version of getPluginBlocklistState that allows the caller to pass in |
|
902 * the plugin blocklist entries. |
|
903 * |
|
904 * @param plugin |
|
905 * The nsIPluginTag to get the blocklist state for. |
|
906 * @param pluginEntries |
|
907 * The plugin blocklist entries to compare against. |
|
908 * @param appVersion |
|
909 * The application version to compare to, will use the current |
|
910 * version if null. |
|
911 * @param toolkitVersion |
|
912 * The toolkit version to compare to, will use the current version if |
|
913 * null. |
|
914 * @returns The blocklist state for the item, one of the STATE constants as |
|
915 * defined in nsIBlocklistService. |
|
916 */ |
|
917 _getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin, |
|
918 pluginEntries, appVersion, toolkitVersion) { |
|
919 if (!gBlocklistEnabled) |
|
920 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; |
|
921 |
|
922 if (!appVersion) |
|
923 appVersion = gApp.version; |
|
924 if (!toolkitVersion) |
|
925 toolkitVersion = gApp.platformVersion; |
|
926 |
|
927 for each (var blockEntry in pluginEntries) { |
|
928 var matchFailed = false; |
|
929 for (var name in blockEntry.matches) { |
|
930 if (!(name in plugin) || |
|
931 typeof(plugin[name]) != "string" || |
|
932 !blockEntry.matches[name].test(plugin[name])) { |
|
933 matchFailed = true; |
|
934 break; |
|
935 } |
|
936 } |
|
937 |
|
938 if (matchFailed) |
|
939 continue; |
|
940 |
|
941 for (let blockEntryVersion of blockEntry.versions) { |
|
942 if (blockEntryVersion.includesItem(plugin.version, appVersion, |
|
943 toolkitVersion)) { |
|
944 if (blockEntryVersion.severity >= gBlocklistLevel) |
|
945 return Ci.nsIBlocklistService.STATE_BLOCKED; |
|
946 if (blockEntryVersion.severity == SEVERITY_OUTDATED) { |
|
947 let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus; |
|
948 if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE) |
|
949 return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE; |
|
950 if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE) |
|
951 return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE; |
|
952 return Ci.nsIBlocklistService.STATE_OUTDATED; |
|
953 } |
|
954 return Ci.nsIBlocklistService.STATE_SOFTBLOCKED; |
|
955 } |
|
956 } |
|
957 } |
|
958 |
|
959 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; |
|
960 }, |
|
961 |
|
962 /* See nsIBlocklistService */ |
|
963 getPluginBlocklistURL: function Blocklist_getPluginBlocklistURL(plugin) { |
|
964 if (!gBlocklistEnabled) |
|
965 return ""; |
|
966 |
|
967 if (!this._pluginEntries) |
|
968 this._loadBlocklist(); |
|
969 |
|
970 for each (let blockEntry in this._pluginEntries) { |
|
971 let matchFailed = false; |
|
972 for (let name in blockEntry.matches) { |
|
973 if (!(name in plugin) || |
|
974 typeof(plugin[name]) != "string" || |
|
975 !blockEntry.matches[name].test(plugin[name])) { |
|
976 matchFailed = true; |
|
977 break; |
|
978 } |
|
979 } |
|
980 |
|
981 if (!matchFailed) { |
|
982 if(!blockEntry.blockID) |
|
983 return null; |
|
984 else |
|
985 return this._createBlocklistURL(blockEntry.blockID); |
|
986 } |
|
987 } |
|
988 }, |
|
989 |
|
990 _blocklistUpdated: function Blocklist_blocklistUpdated(oldAddonEntries, oldPluginEntries) { |
|
991 var addonList = []; |
|
992 |
|
993 // A helper function that reverts the prefs passed to default values. |
|
994 function resetPrefs(prefs) { |
|
995 for (let pref of prefs) |
|
996 gPref.clearUserPref(pref); |
|
997 } |
|
998 var self = this; |
|
999 const types = ["extension", "theme", "locale", "dictionary", "service"]; |
|
1000 AddonManager.getAddonsByTypes(types, function blocklistUpdated_getAddonsByTypes(addons) { |
|
1001 |
|
1002 for (let addon of addons) { |
|
1003 let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED; |
|
1004 if (oldAddonEntries) |
|
1005 oldState = self._getAddonBlocklistState(addon, oldAddonEntries); |
|
1006 let state = self.getAddonBlocklistState(addon); |
|
1007 |
|
1008 LOG("Blocklist state for " + addon.id + " changed from " + |
|
1009 oldState + " to " + state); |
|
1010 |
|
1011 // We don't want to re-warn about add-ons |
|
1012 if (state == oldState) |
|
1013 continue; |
|
1014 |
|
1015 if (state === Ci.nsIBlocklistService.STATE_BLOCKED) { |
|
1016 // It's a hard block. We must reset certain preferences. |
|
1017 let prefs = self._getAddonPrefs(addon); |
|
1018 resetPrefs(prefs); |
|
1019 } |
|
1020 |
|
1021 // Ensure that softDisabled is false if the add-on is not soft blocked |
|
1022 if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED) |
|
1023 addon.softDisabled = false; |
|
1024 |
|
1025 // Don't warn about add-ons becoming unblocked. |
|
1026 if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED) |
|
1027 continue; |
|
1028 |
|
1029 // If an add-on has dropped from hard to soft blocked just mark it as |
|
1030 // soft disabled and don't warn about it. |
|
1031 if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && |
|
1032 oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { |
|
1033 addon.softDisabled = true; |
|
1034 continue; |
|
1035 } |
|
1036 |
|
1037 // If the add-on is already disabled for some reason then don't warn |
|
1038 // about it |
|
1039 if (!addon.isActive) |
|
1040 continue; |
|
1041 |
|
1042 addonList.push({ |
|
1043 name: addon.name, |
|
1044 version: addon.version, |
|
1045 icon: addon.iconURL, |
|
1046 disable: false, |
|
1047 blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, |
|
1048 item: addon, |
|
1049 url: self.getAddonBlocklistURL(addon), |
|
1050 }); |
|
1051 } |
|
1052 |
|
1053 AddonManagerPrivate.updateAddonAppDisabledStates(); |
|
1054 |
|
1055 var phs = Cc["@mozilla.org/plugin/host;1"]. |
|
1056 getService(Ci.nsIPluginHost); |
|
1057 var plugins = phs.getPluginTags(); |
|
1058 |
|
1059 for (let plugin of plugins) { |
|
1060 let oldState = -1; |
|
1061 if (oldPluginEntries) |
|
1062 oldState = self._getPluginBlocklistState(plugin, oldPluginEntries); |
|
1063 let state = self.getPluginBlocklistState(plugin); |
|
1064 LOG("Blocklist state for " + plugin.name + " changed from " + |
|
1065 oldState + " to " + state); |
|
1066 // We don't want to re-warn about items |
|
1067 if (state == oldState) |
|
1068 continue; |
|
1069 |
|
1070 if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { |
|
1071 if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) |
|
1072 plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED; |
|
1073 } |
|
1074 else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { |
|
1075 if (state == Ci.nsIBlocklistService.STATE_OUTDATED) { |
|
1076 gPref.setBoolPref(PREF_PLUGINS_NOTIFYUSER, true); |
|
1077 } |
|
1078 else if (state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE && |
|
1079 state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) { |
|
1080 addonList.push({ |
|
1081 name: plugin.name, |
|
1082 version: plugin.version, |
|
1083 icon: "chrome://mozapps/skin/plugins/pluginGeneric.png", |
|
1084 disable: false, |
|
1085 blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, |
|
1086 item: plugin, |
|
1087 url: self.getPluginBlocklistURL(plugin), |
|
1088 }); |
|
1089 } |
|
1090 } |
|
1091 } |
|
1092 |
|
1093 if (addonList.length == 0) { |
|
1094 Services.obs.notifyObservers(self, "blocklist-updated", ""); |
|
1095 return; |
|
1096 } |
|
1097 |
|
1098 if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) { |
|
1099 try { |
|
1100 let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"] |
|
1101 .getService(Ci.nsIBlocklistPrompt); |
|
1102 blockedPrompter.prompt(addonList); |
|
1103 } catch (e) { |
|
1104 LOG(e); |
|
1105 } |
|
1106 Services.obs.notifyObservers(self, "blocklist-updated", ""); |
|
1107 return; |
|
1108 } |
|
1109 |
|
1110 var args = { |
|
1111 restart: false, |
|
1112 list: addonList |
|
1113 }; |
|
1114 // This lets the dialog get the raw js object |
|
1115 args.wrappedJSObject = args; |
|
1116 |
|
1117 /* |
|
1118 Some tests run without UI, so the async code listens to a message |
|
1119 that can be sent programatically |
|
1120 */ |
|
1121 let applyBlocklistChanges = function blocklistUpdated_applyBlocklistChanges() { |
|
1122 for (let addon of addonList) { |
|
1123 if (!addon.disable) |
|
1124 continue; |
|
1125 |
|
1126 if (addon.item instanceof Ci.nsIPluginTag) |
|
1127 addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED; |
|
1128 else { |
|
1129 // This add-on is softblocked. |
|
1130 addon.item.softDisabled = true; |
|
1131 // We must revert certain prefs. |
|
1132 let prefs = self._getAddonPrefs(addon.item); |
|
1133 resetPrefs(prefs); |
|
1134 } |
|
1135 } |
|
1136 |
|
1137 if (args.restart) |
|
1138 restartApp(); |
|
1139 |
|
1140 Services.obs.notifyObservers(self, "blocklist-updated", ""); |
|
1141 Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed"); |
|
1142 } |
|
1143 |
|
1144 Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed", false); |
|
1145 |
|
1146 if (getPref("getBoolPref", PREF_BLOCKLIST_SUPPRESSUI, false)) { |
|
1147 applyBlocklistChanges(); |
|
1148 return; |
|
1149 } |
|
1150 |
|
1151 function blocklistUnloadHandler(event) { |
|
1152 if (event.target.location == URI_BLOCKLIST_DIALOG) { |
|
1153 applyBlocklistChanges(); |
|
1154 blocklistWindow.removeEventListener("unload", blocklistUnloadHandler); |
|
1155 } |
|
1156 } |
|
1157 |
|
1158 let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "", |
|
1159 "chrome,centerscreen,dialog,titlebar", args); |
|
1160 if (blocklistWindow) |
|
1161 blocklistWindow.addEventListener("unload", blocklistUnloadHandler, false); |
|
1162 }); |
|
1163 }, |
|
1164 |
|
1165 classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"), |
|
1166 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, |
|
1167 Ci.nsIBlocklistService, |
|
1168 Ci.nsITimerCallback]), |
|
1169 }; |
|
1170 |
|
1171 /** |
|
1172 * Helper for constructing a blocklist. |
|
1173 */ |
|
1174 function BlocklistItemData(versionRangeElement) { |
|
1175 var versionRange = this.getBlocklistVersionRange(versionRangeElement); |
|
1176 this.minVersion = versionRange.minVersion; |
|
1177 this.maxVersion = versionRange.maxVersion; |
|
1178 if (versionRangeElement && versionRangeElement.hasAttribute("severity")) |
|
1179 this.severity = versionRangeElement.getAttribute("severity"); |
|
1180 else |
|
1181 this.severity = DEFAULT_SEVERITY; |
|
1182 if (versionRangeElement && versionRangeElement.hasAttribute("vulnerabilitystatus")) { |
|
1183 this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus"); |
|
1184 } else { |
|
1185 this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE; |
|
1186 } |
|
1187 this.targetApps = { }; |
|
1188 var found = false; |
|
1189 |
|
1190 if (versionRangeElement) { |
|
1191 for (var i = 0; i < versionRangeElement.childNodes.length; ++i) { |
|
1192 var targetAppElement = versionRangeElement.childNodes.item(i); |
|
1193 if (!(targetAppElement instanceof Ci.nsIDOMElement) || |
|
1194 targetAppElement.localName != "targetApplication") |
|
1195 continue; |
|
1196 found = true; |
|
1197 // default to the current application if id is not provided. |
|
1198 var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID; |
|
1199 this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement); |
|
1200 } |
|
1201 } |
|
1202 // Default to all versions of the current application when no targetApplication |
|
1203 // elements were found |
|
1204 if (!found) |
|
1205 this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null); |
|
1206 } |
|
1207 |
|
1208 BlocklistItemData.prototype = { |
|
1209 /** |
|
1210 * Tests if a version of an item is included in the version range and target |
|
1211 * application information represented by this BlocklistItemData using the |
|
1212 * provided application and toolkit versions. |
|
1213 * @param version |
|
1214 * The version of the item being tested. |
|
1215 * @param appVersion |
|
1216 * The application version to test with. |
|
1217 * @param toolkitVersion |
|
1218 * The toolkit version to test with. |
|
1219 * @returns True if the version range covers the item version and application |
|
1220 * or toolkit version. |
|
1221 */ |
|
1222 includesItem: function BlocklistItemData_includesItem(version, appVersion, toolkitVersion) { |
|
1223 // Some platforms have no version for plugins, these don't match if there |
|
1224 // was a min/maxVersion provided |
|
1225 if (!version && (this.minVersion || this.maxVersion)) |
|
1226 return false; |
|
1227 |
|
1228 // Check if the item version matches |
|
1229 if (!this.matchesRange(version, this.minVersion, this.maxVersion)) |
|
1230 return false; |
|
1231 |
|
1232 // Check if the application version matches |
|
1233 if (this.matchesTargetRange(gApp.ID, appVersion)) |
|
1234 return true; |
|
1235 |
|
1236 // Check if the toolkit version matches |
|
1237 return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion); |
|
1238 }, |
|
1239 |
|
1240 /** |
|
1241 * Checks if a version is higher than or equal to the minVersion (if provided) |
|
1242 * and lower than or equal to the maxVersion (if provided). |
|
1243 * @param version |
|
1244 * The version to test. |
|
1245 * @param minVersion |
|
1246 * The minimum version. If null it is assumed that version is always |
|
1247 * larger. |
|
1248 * @param maxVersion |
|
1249 * The maximum version. If null it is assumed that version is always |
|
1250 * smaller. |
|
1251 */ |
|
1252 matchesRange: function BlocklistItemData_matchesRange(version, minVersion, maxVersion) { |
|
1253 if (minVersion && gVersionChecker.compare(version, minVersion) < 0) |
|
1254 return false; |
|
1255 if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0) |
|
1256 return false; |
|
1257 return true; |
|
1258 }, |
|
1259 |
|
1260 /** |
|
1261 * Tests if there is a matching range for the given target application id and |
|
1262 * version. |
|
1263 * @param appID |
|
1264 * The application ID to test for, may be for an application or toolkit |
|
1265 * @param appVersion |
|
1266 * The version of the application to test for. |
|
1267 * @returns True if this version range covers the application version given. |
|
1268 */ |
|
1269 matchesTargetRange: function BlocklistItemData_matchesTargetRange(appID, appVersion) { |
|
1270 var blTargetApp = this.targetApps[appID]; |
|
1271 if (!blTargetApp) |
|
1272 return false; |
|
1273 |
|
1274 for (let app of blTargetApp) { |
|
1275 if (this.matchesRange(appVersion, app.minVersion, app.maxVersion)) |
|
1276 return true; |
|
1277 } |
|
1278 |
|
1279 return false; |
|
1280 }, |
|
1281 |
|
1282 /** |
|
1283 * Retrieves a version range (e.g. minVersion and maxVersion) for a |
|
1284 * blocklist item's targetApplication element. |
|
1285 * @param targetAppElement |
|
1286 * A targetApplication blocklist element. |
|
1287 * @returns An array of JS objects with the following properties: |
|
1288 * "minVersion" The minimum version in a version range (default = null). |
|
1289 * "maxVersion" The maximum version in a version range (default = null). |
|
1290 */ |
|
1291 getBlocklistAppVersions: function BlocklistItemData_getBlocklistAppVersions(targetAppElement) { |
|
1292 var appVersions = [ ]; |
|
1293 |
|
1294 if (targetAppElement) { |
|
1295 for (var i = 0; i < targetAppElement.childNodes.length; ++i) { |
|
1296 var versionRangeElement = targetAppElement.childNodes.item(i); |
|
1297 if (!(versionRangeElement instanceof Ci.nsIDOMElement) || |
|
1298 versionRangeElement.localName != "versionRange") |
|
1299 continue; |
|
1300 appVersions.push(this.getBlocklistVersionRange(versionRangeElement)); |
|
1301 } |
|
1302 } |
|
1303 // return minVersion = null and maxVersion = null if no specific versionRange |
|
1304 // elements were found |
|
1305 if (appVersions.length == 0) |
|
1306 appVersions.push(this.getBlocklistVersionRange(null)); |
|
1307 return appVersions; |
|
1308 }, |
|
1309 |
|
1310 /** |
|
1311 * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist |
|
1312 * versionRange element. |
|
1313 * @param versionRangeElement |
|
1314 * The versionRange blocklist element. |
|
1315 * @returns A JS object with the following properties: |
|
1316 * "minVersion" The minimum version in a version range (default = null). |
|
1317 * "maxVersion" The maximum version in a version range (default = null). |
|
1318 */ |
|
1319 getBlocklistVersionRange: function BlocklistItemData_getBlocklistVersionRange(versionRangeElement) { |
|
1320 var minVersion = null; |
|
1321 var maxVersion = null; |
|
1322 if (!versionRangeElement) |
|
1323 return { minVersion: minVersion, maxVersion: maxVersion }; |
|
1324 |
|
1325 if (versionRangeElement.hasAttribute("minVersion")) |
|
1326 minVersion = versionRangeElement.getAttribute("minVersion"); |
|
1327 if (versionRangeElement.hasAttribute("maxVersion")) |
|
1328 maxVersion = versionRangeElement.getAttribute("maxVersion"); |
|
1329 |
|
1330 return { minVersion: minVersion, maxVersion: maxVersion }; |
|
1331 } |
|
1332 }; |
|
1333 |
|
1334 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]); |