toolkit/mozapps/extensions/nsBlocklistService.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:5c7fed3ce8b8
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]);

mercurial