Bug 639524: Use the application shipped blocklist when it is newer then the profile version. r=Unfocused. a=sylvestre
authorDave Townsend <dtownsend@oxymoronical.com>
Thu, 17 Apr 2014 09:17:29 -0700
changeset 192916 66d3dcc2bd4709f2e2f7cdf89385134a4dbd17cd
parent 192915 6bba6a68039f89cd15035b476105f18dabb3e8d7
child 192917 eaa1f0c148eca5f315e8716ede7c10e1a0d22a39
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersUnfocused, sylvestre
bugs639524
milestone30.0a2
Bug 639524: Use the application shipped blocklist when it is newer then the profile version. r=Unfocused. a=sylvestre
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/ancient.xml
toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/new.xml
toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/old.xml
toolkit/mozapps/extensions/test/xpcshell/test_overrideblocklist.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -40,16 +40,22 @@ const PREF_EM_CERT_CHECKATTRIBUTES    = 
 const PREF_EM_HOTFIX_CERTS            = "extensions.hotfix.certs.";
 const PREF_MATCH_OS_LOCALE            = "intl.locale.matchOS";
 const PREF_SELECTED_LOCALE            = "general.useragent.locale";
 const UNKNOWN_XPCOM_ABI               = "unknownABI";
 
 const UPDATE_REQUEST_VERSION          = 2;
 const CATEGORY_UPDATE_PARAMS          = "extension-update-params";
 
+const XMLURI_BLOCKLIST                = "http://www.mozilla.org/2006/addons-blocklist";
+
+const KEY_PROFILEDIR                  = "ProfD";
+const KEY_APPDIR                      = "XCurProcD";
+const FILE_BLOCKLIST                  = "blocklist.xml";
+
 const BRANCH_REGEXP                   = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
 const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility";
 #ifdef MOZ_COMPATIBILITY_NIGHTLY
 var PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly";
 #else
 var PREF_EM_CHECK_COMPATIBILITY;
 #endif
 
@@ -60,16 +66,18 @@ const VALID_TYPES_REGEXP = /^[\w\-]+$/;
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                   "resource://gre/modules/addons/AddonRepository.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() {
   let certUtils = {};
   Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
   return certUtils;
 });
 
 
@@ -516,16 +524,98 @@ var AddonManagerInternal = {
       return this.getPropertyNames();
     }
   }),
 
   recordTimestamp: function AMI_recordTimestamp(name, value) {
     this.TelemetryTimestamps.add(name, value);
   },
 
+  validateBlocklist: function AMI_validateBlocklist() {
+    let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
+
+    // If there is no application shipped blocklist then there is nothing to do
+    if (!appBlocklist.exists())
+      return;
+
+    let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
+
+    // If there is no blocklist in the profile then copy the application shipped
+    // one there
+    if (!profileBlocklist.exists()) {
+      try {
+        appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
+      }
+      catch (e) {
+        logger.warn("Failed to copy the application shipped blocklist to the profile", e);
+      }
+      return;
+    }
+
+    let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
+                     createInstance(Ci.nsIFileInputStream);
+    try {
+      let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
+                    createInstance(Ci.nsIConverterInputStream);
+      fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+      cstream.init(fileStream, "UTF-8", 0, 0);
+
+      let data = "";
+      let str = {};
+      let read = 0;
+      do {
+        read = cstream.readString(0xffffffff, str);
+        data += str.value;
+      } while (read != 0);
+
+      let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+                   createInstance(Ci.nsIDOMParser);
+      var doc = parser.parseFromString(data, "text/xml");
+    }
+    catch (e) {
+      logger.warn("Application shipped blocklist could not be loaded", e);
+      return;
+    }
+    finally {
+      try {
+        fileStream.close();
+      }
+      catch (e) {
+        logger.warn("Unable to close blocklist file stream", e);
+      }
+    }
+
+    // If the namespace is incorrect then ignore the application shipped
+    // blocklist
+    if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
+      logger.warn("Application shipped blocklist has an unexpected namespace (" +
+                  doc.documentElement.namespaceURI + ")");
+      return;
+    }
+
+    // If there is no lastupdate information then ignore the application shipped
+    // blocklist
+    if (!doc.documentElement.hasAttribute("lastupdate"))
+      return;
+
+    // If the application shipped blocklist is older than the profile blocklist
+    // then do nothing
+    if (doc.documentElement.getAttribute("lastupdate") <=
+        profileBlocklist.lastModifiedTime)
+      return;
+
+    // Otherwise copy the application shipped blocklist to the profile
+    try {
+      appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
+    }
+    catch (e) {
+      logger.warn("Failed to copy the application shipped blocklist to the profile", e);
+    }
+  },
+
   /**
    * Initializes the AddonManager, loading any known providers and initializing
    * them.
    */
   startup: function AMI_startup() {
     try {
       if (gStarted)
         return;
@@ -554,16 +644,17 @@ var AddonManagerInternal = {
       if (appChanged !== false) {
         logger.debug("Application has been upgraded");
         Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
                                    Services.appinfo.version);
         Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
                                    Services.appinfo.platformVersion);
         Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION,
                                   (appChanged === undefined ? 0 : -1));
+        this.validateBlocklist();
       }
 
 #ifndef MOZ_COMPATIBILITY_NIGHTLY
       PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." +
                                     Services.appinfo.version.replace(BRANCH_REGEXP, "$1");
 #endif
 
       try {
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/ancient.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+  <emItems>
+    <emItem  blockID="i454" id="ancient@tests.mozilla.org">
+      <versionRange  minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+  </emItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/new.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1396046918000">
+  <emItems>
+    <emItem  blockID="i454" id="new@tests.mozilla.org">
+      <versionRange  minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+  </emItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_overrideblocklist/old.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1296046918000">
+  <emItems>
+    <emItem  blockID="i454" id="old@tests.mozilla.org">
+      <versionRange  minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
+  </emItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_overrideblocklist.js
@@ -0,0 +1,200 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const KEY_PROFILEDIR                  = "ProfD";
+const KEY_APPDIR                      = "XCurProcD";
+const FILE_BLOCKLIST                  = "blocklist.xml";
+
+const PREF_BLOCKLIST_ENABLED          = "extensions.blocklist.enabled";
+
+const OLD = do_get_file("data/test_overrideblocklist/old.xml");
+const NEW = do_get_file("data/test_overrideblocklist/new.xml");
+const ANCIENT = do_get_file("data/test_overrideblocklist/ancient.xml");
+const OLD_TSTAMP = 1296046918000;
+const NEW_TSTAMP = 1396046918000;
+
+const gAppDir = FileUtils.getFile(KEY_APPDIR, []);
+
+let oldAddon = {
+  id: "old@tests.mozilla.org",
+  version: 1
+}
+let newAddon = {
+  id: "new@tests.mozilla.org",
+  version: 1
+}
+let ancientAddon = {
+  id: "ancient@tests.mozilla.org",
+  version: 1
+}
+let invalidAddon = {
+  id: "invalid@tests.mozilla.org",
+  version: 1
+}
+
+function incrementAppVersion() {
+  gAppInfo.version = "" + (parseInt(gAppInfo.version) + 1);
+}
+
+function clearBlocklists() {
+  let blocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
+  if (blocklist.exists())
+    blocklist.remove(true);
+
+  blocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
+  if (blocklist.exists())
+    blocklist.remove(true);
+}
+
+function reloadBlocklist() {
+  Services.prefs.setBoolPref(PREF_BLOCKLIST_ENABLED, false);
+  Services.prefs.setBoolPref(PREF_BLOCKLIST_ENABLED, true);
+}
+
+function copyToApp(file) {
+  file.clone().copyTo(gAppDir, FILE_BLOCKLIST);
+}
+
+function copyToProfile(file, tstamp) {
+  file = file.clone();
+  file.copyTo(gProfD, FILE_BLOCKLIST);
+  file = gProfD.clone();
+  file.append(FILE_BLOCKLIST);
+  file.lastModifiedTime = tstamp;
+}
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+
+  let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
+  if (appBlocklist.exists()) {
+    try {
+      appBlocklist.moveTo(gAppDir, "blocklist.old");
+    }
+    catch (e) {
+      todo(false, "Aborting test due to unmovable blocklist file: " + e);
+      return;
+    }
+    do_register_cleanup(function() {
+      clearBlocklists();
+      appBlocklist.moveTo(gAppDir, FILE_BLOCKLIST);
+    });
+  }
+
+  run_next_test();
+}
+
+// On first run whataver is in the app dir should get copied to the profile
+add_test(function test_copy() {
+  clearBlocklists();
+  copyToApp(OLD);
+
+  incrementAppVersion();
+  startupManager();
+
+  reloadBlocklist();
+  let blocklist = AM_Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService(AM_Ci.nsIBlocklistService);
+  do_check_false(blocklist.isAddonBlocklisted(invalidAddon));
+  do_check_false(blocklist.isAddonBlocklisted(ancientAddon));
+  do_check_true(blocklist.isAddonBlocklisted(oldAddon));
+  do_check_false(blocklist.isAddonBlocklisted(newAddon));
+
+  shutdownManager();
+
+  run_next_test();
+});
+
+// An ancient blocklist should be ignored
+add_test(function test_ancient() {
+  clearBlocklists();
+  copyToApp(ANCIENT);
+  copyToProfile(OLD, OLD_TSTAMP);
+
+  incrementAppVersion();
+  startupManager();
+
+  reloadBlocklist();
+  let blocklist = AM_Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService(AM_Ci.nsIBlocklistService);
+  do_check_false(blocklist.isAddonBlocklisted(invalidAddon));
+  do_check_false(blocklist.isAddonBlocklisted(ancientAddon));
+  do_check_true(blocklist.isAddonBlocklisted(oldAddon));
+  do_check_false(blocklist.isAddonBlocklisted(newAddon));
+
+  shutdownManager();
+
+  run_next_test();
+});
+
+// A new blocklist should override an old blocklist
+add_test(function test_override() {
+  clearBlocklists();
+  copyToApp(NEW);
+  copyToProfile(OLD, OLD_TSTAMP);
+
+  incrementAppVersion();
+  startupManager();
+
+  reloadBlocklist();
+  let blocklist = AM_Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService(AM_Ci.nsIBlocklistService);
+  do_check_false(blocklist.isAddonBlocklisted(invalidAddon));
+  do_check_false(blocklist.isAddonBlocklisted(ancientAddon));
+  do_check_false(blocklist.isAddonBlocklisted(oldAddon));
+  do_check_true(blocklist.isAddonBlocklisted(newAddon));
+
+  shutdownManager();
+
+  run_next_test();
+});
+
+// An old blocklist shouldn't override a new blocklist
+add_test(function test_retain() {
+  clearBlocklists();
+  copyToApp(OLD);
+  copyToProfile(NEW, NEW_TSTAMP);
+
+  incrementAppVersion();
+  startupManager();
+
+  reloadBlocklist();
+  let blocklist = AM_Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService(AM_Ci.nsIBlocklistService);
+  do_check_false(blocklist.isAddonBlocklisted(invalidAddon));
+  do_check_false(blocklist.isAddonBlocklisted(ancientAddon));
+  do_check_false(blocklist.isAddonBlocklisted(oldAddon));
+  do_check_true(blocklist.isAddonBlocklisted(newAddon));
+
+  shutdownManager();
+
+  run_next_test();
+});
+
+// A missing blocklist in the profile should still load an app-shipped blocklist
+add_test(function test_missing() {
+  clearBlocklists();
+  copyToApp(OLD);
+  copyToProfile(NEW, NEW_TSTAMP);
+
+  incrementAppVersion();
+  startupManager();
+  shutdownManager();
+
+  let blocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
+  blocklist.remove(true);
+  startupManager(false);
+
+  reloadBlocklist();
+  let blocklist = AM_Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService(AM_Ci.nsIBlocklistService);
+  do_check_false(blocklist.isAddonBlocklisted(invalidAddon));
+  do_check_false(blocklist.isAddonBlocklisted(ancientAddon));
+  do_check_true(blocklist.isAddonBlocklisted(oldAddon));
+  do_check_false(blocklist.isAddonBlocklisted(newAddon));
+
+  shutdownManager();
+
+  run_next_test();
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -255,8 +255,10 @@ run-sequentially = Uses hardcoded ports 
 [test_upgrade.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 run-sequentially = Uses global XCurProcD dir.
 [test_upgrade_strictcompat.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 run-sequentially = Uses global XCurProcD dir.
+[test_overrideblocklist.js]
+run-sequentially = Uses global XCurProcD dir.