Bug 1469889 - Allow blacklisting search engines based on URL. r?florian draft
authorMichael Kaply <mozilla@kaply.com>
Mon, 02 Jul 2018 10:53:01 -0500
changeset 813673 67a1eff2f64abfe76584f6133d99f27315dd1bf5
parent 812548 a009b5249a4b78a889fdc5ffcf55ad51715cc686
push id114963
push usermozilla@kaply.com
push dateTue, 03 Jul 2018 17:13:09 +0000
reviewersflorian
bugs1469889
milestone63.0a1
Bug 1469889 - Allow blacklisting search engines based on URL. r?florian MozReview-Commit-ID: IGL6JJGcyXa
toolkit/components/search/nsSearchService.js
toolkit/components/search/tests/xpcshell/data/search_blacklist.json
toolkit/components/search/tests/xpcshell/test_blacklist.js
toolkit/components/search/tests/xpcshell/test_json_cache_blacklist.js
toolkit/components/search/tests/xpcshell/xpcshell.ini
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -2867,34 +2867,38 @@ SearchService.prototype = {
     let buildID = Services.appinfo.platformBuildID;
     let rebuildCache = !cache.engines ||
                        cache.version != CACHE_VERSION ||
                        cache.locale != getLocale() ||
                        cache.buildID != buildID ||
                        cache.visibleDefaultEngines.length != this._visibleDefaultEngines.length ||
                        this._visibleDefaultEngines.some(notInCacheVisibleEngines);
 
-    if (rebuildCache) {
-      LOG("_loadEngines: Absent or outdated cache. Loading engines from disk.");
-      distDirs.forEach(this._loadEnginesFromDir, this);
-
-      this._loadFromChromeURLs(chromeURIs);
-
-      LOG("_loadEngines: load user-installed engines from the obsolete cache");
-      this._loadEnginesFromCache(cache, true);
-
-      this._loadEnginesMetadataFromCache(cache);
-      this._buildCache();
-      return;
+    if (!rebuildCache) {
+      LOG("_loadEngines: loading from cache directories");
+      this._loadEnginesFromCache(cache);
+      if (Object.keys(this._engines).length) {
+        LOG("_loadEngines: done using existing cache");
+        return;
+      }
+      LOG("_loadEngines: No valid engines found in cache. Loading engines from disk.");
     }
 
-    LOG("_loadEngines: loading from cache directories");
-    this._loadEnginesFromCache(cache);
-
-    LOG("_loadEngines: done");
+    LOG("_loadEngines: Absent or outdated cache. Loading engines from disk.");
+    distDirs.forEach(this._loadEnginesFromDir, this);
+
+    this._loadFromChromeURLs(chromeURIs);
+
+    LOG("_loadEngines: load user-installed engines from the obsolete cache");
+    this._loadEnginesFromCache(cache, true);
+
+    this._loadEnginesMetadataFromCache(cache);
+    this._buildCache();
+
+    LOG("_loadEngines: done using rebuilt cache");
   },
 
   /**
    * Loads engines asynchronously.
    *
    * @returns {Promise} A promise, resolved successfully if loading data
    * succeeds.
    */
@@ -2937,39 +2941,43 @@ SearchService.prototype = {
     let buildID = Services.appinfo.platformBuildID;
     let rebuildCache = !cache.engines ||
                        cache.version != CACHE_VERSION ||
                        cache.locale != getLocale() ||
                        cache.buildID != buildID ||
                        cache.visibleDefaultEngines.length != this._visibleDefaultEngines.length ||
                        this._visibleDefaultEngines.some(notInCacheVisibleEngines);
 
-    if (rebuildCache) {
-      LOG("_asyncLoadEngines: Absent or outdated cache. Loading engines from disk.");
-      for (let loadDir of distDirs) {
-        let enginesFromDir =
-          await checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
-        enginesFromDir.forEach(this._addEngineToStore, this);
+    if (!rebuildCache) {
+      LOG("_asyncLoadEngines: loading from cache directories");
+      this._loadEnginesFromCache(cache);
+      if (Object.keys(this._engines).length) {
+        LOG("_asyncLoadEngines: done using existing cache");
+        return;
       }
-      let enginesFromURLs =
-        await checkForSyncCompletion(this._asyncLoadFromChromeURLs(chromeURIs));
-      enginesFromURLs.forEach(this._addEngineToStore, this);
-
-      LOG("_asyncLoadEngines: loading user-installed engines from the obsolete cache");
-      this._loadEnginesFromCache(cache, true);
-
-      this._loadEnginesMetadataFromCache(cache);
-      this._buildCache();
-      return;
+      LOG("_asyncLoadEngines: No valid engines found in cache. Loading engines from disk.");
+    }
+
+    LOG("_asyncLoadEngines: Absent or outdated cache. Loading engines from disk.");
+    for (let loadDir of distDirs) {
+      let enginesFromDir =
+        await checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
+      enginesFromDir.forEach(this._addEngineToStore, this);
     }
-
-    LOG("_asyncLoadEngines: loading from cache directories");
-    this._loadEnginesFromCache(cache);
-
-    LOG("_asyncLoadEngines: done");
+    let enginesFromURLs =
+      await checkForSyncCompletion(this._asyncLoadFromChromeURLs(chromeURIs));
+    enginesFromURLs.forEach(this._addEngineToStore, this);
+
+    LOG("_asyncLoadEngines: loading user-installed engines from the obsolete cache");
+    this._loadEnginesFromCache(cache, true);
+
+    this._loadEnginesMetadataFromCache(cache);
+    this._buildCache();
+
+    LOG("_asyncLoadEngines: done using rebuilt cache");
   },
 
   _asyncReInit() {
     LOG("_asyncReInit");
     // Start by clearing the initialized state, so we don't abort early.
     gInitialized = false;
 
     (async () => {
@@ -3102,17 +3110,27 @@ SearchService.prototype = {
         LOG("batchTask: Invalidating engine cache");
         this._buildCache();
       };
       this._batchTask = new DeferredTask(task, CACHE_INVALIDATION_DELAY);
     }
     return this._batchTask;
   },
 
+  _blackList: [
+    "blacklist=true",
+  ],
+
   _addEngineToStore: function SRCH_SVC_addEngineToStore(aEngine) {
+    let url = aEngine._getURLOfType("text/html").getSubmission("dummy", aEngine).uri.spec;
+    if (this._blackList.some(code => url.includes(code))) {
+      LOG("_addEngineToStore: Ignoring blacklisted engine");
+      return;
+    }
+
     LOG("_addEngineToStore: Adding engine: \"" + aEngine.name + "\"");
 
     // See if there is an existing engine with the same name. However, if this
     // engine is updating another engine, it's allowed to have the same name.
     var hasSameNameAsUpdate = (aEngine._engineToUpdate &&
                                aEngine.name == aEngine._engineToUpdate.name);
     if (aEngine.name in this._engines && !hasSameNameAsUpdate) {
       LOG("_addEngineToStore: Duplicate engine found, aborting!");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/data/search_blacklist.json
@@ -0,0 +1,90 @@
+{
+  "version": 1,
+  "buildID": "20121106",
+  "locale": "en-US",
+  "metaData": {},
+  "engines": [
+    {
+      "_name": "Test search engine",
+      "_shortName": "test-search-engine",
+      "description": "A test search engine (based on Google search)",
+      "extensionID": "test-addon-id@mozilla.org",
+      "__searchForm": "http://www.google.com/",
+      "_iconURL": "data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA",
+      "_metaData": {},
+      "_urls": [
+        {
+          "template": "http://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}&q={searchTerms}",
+          "rels": [
+          ],
+          "type": "application/x-suggestions+json",
+          "params": [
+          ]
+        },
+        {
+          "template": "http://www.google.com/search",
+          "resultDomain": "google.com",
+          "rels": [
+          ],
+          "params": [
+            {
+              "name": "q",
+              "value": "{searchTerms}"
+            },
+            {
+              "name": "ie",
+              "value": "utf-8"
+            },
+            {
+              "name": "oe",
+              "value": "utf-8"
+            },
+            {
+              "name": "aq",
+              "value": "t"
+            },
+            {
+              "name": "blacklist",
+              "value": "true"
+            },
+            {
+              "name": "channel",
+              "value": "fflb",
+              "purpose": "keyword"
+            },
+            {
+              "name": "channel",
+              "value": "rcs",
+              "purpose": "contextmenu"
+            }
+          ]
+        },
+        {
+          "template": "http://www.google.com/search",
+          "resultDomain": "purpose.google.com",
+          "rels": [
+          ],
+          "type": "application/x-moz-default-purpose",
+          "params": [
+            {
+              "name": "q",
+              "value": "{searchTerms}"
+            },
+            {
+              "name": "channel",
+              "value": "fflb",
+              "purpose": "keyword"
+            },
+            {
+              "name": "channel",
+              "value": "rcs",
+              "purpose": "contextmenu"
+            }
+          ]
+        }
+      ],
+      "queryCharset": "UTF-8",
+      "_readOnly": false
+    }
+  ]
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_blacklist.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const kSearchEngineID = "blacklist_test_engine";
+const kSearchEngineURL = "http://example.com/?search={searchTerms}&blacklist=true";
+
+add_task(async function test_blacklistEngine() {
+  Assert.ok(!Services.search.isInitialized);
+
+  await asyncInit();
+
+  Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get",
+                                       kSearchEngineURL);
+
+  // A blacklisted engine shouldn't be available at all
+  let engine = Services.search.getEngineByName(kSearchEngineID);
+  Assert.equal(engine, null, "Engine should not exist");
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_json_cache_blacklist.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test initializing from the search cache.
+ */
+
+"use strict";
+
+var cacheTemplate, appPluginsPath, profPlugins;
+
+/**
+ * Test reading from search.json.mozlz4
+ */
+function run_test() {
+  let cacheTemplateFile = do_get_file("data/search_blacklist.json");
+  cacheTemplate = readJSONFile(cacheTemplateFile);
+  cacheTemplate.buildID = getAppInfo().platformBuildID;
+
+  let engineFile = gProfD.clone();
+  engineFile.append("searchplugins");
+  engineFile.append("test-search-engine.xml");
+  engineFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+  // Copy the test engine to the test profile.
+  let engineTemplateFile = do_get_file("data/engine.xml");
+  engineTemplateFile.copyTo(engineFile.parent, "test-search-engine.xml");
+
+  // The list of visibleDefaultEngines needs to match or the cache will be ignored.
+  cacheTemplate.visibleDefaultEngines = getDefaultEngineList(false);
+
+  run_next_test();
+}
+
+add_test(function prepare_test_data() {
+  promiseSaveCacheData(cacheTemplate).then(run_next_test);
+});
+
+/**
+ * Start the search service and confirm the cache was reset
+ */
+add_test(function test_cache_rest() {
+  info("init search service");
+
+  Services.search.init(function initComplete(aResult) {
+    info("init'd search service");
+    Assert.ok(Components.isSuccessCode(aResult));
+
+    let engines = Services.search.getEngines({});
+
+    // Engine list will have been reset to the default,
+    // Not the one engine in the cache.
+    // It should have more than one engine.
+    Assert.ok(engines.length > 1);
+
+    removeCacheFile();
+    run_next_test();
+  });
+});
--- a/toolkit/components/search/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/search/tests/xpcshell/xpcshell.ini
@@ -26,25 +26,28 @@ support-files =
   data/list.json
   data/search.json
   data/searchSuggestions.sjs
   data/searchTest.jar
 
 [test_nocache.js]
 [test_645970.js]
 [test_big_icon.js]
+[test_blacklist.js]
 [test_bug930456.js]
 [test_bug930456_child.js]
 [test_engine_set_alias.js]
 [test_hasEngineWithURL.js]
 [test_identifiers.js]
 [test_invalid_engine_from_dir.js]
 [test_init_async_multiple.js]
 [test_init_async_multiple_then_sync.js]
 [test_json_cache.js]
+[test_json_cache_blacklist.js]
+support-files = data/search_blacklist.json
 [test_list_json_locale.js]
 [test_list_json_searchdefault.js]
 [test_list_json_searchdefault_distro.js]
 [test_list_json_searchorder.js]
 [test_location.js]
 [test_location_error.js]
 [test_location_malformed_json.js]
 [test_location_partner.js]