summaryrefslogtreecommitdiffstats
path: root/services/common/blocklist-updater.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/common/blocklist-updater.js')
-rw-r--r--services/common/blocklist-updater.js117
1 files changed, 117 insertions, 0 deletions
diff --git a/services/common/blocklist-updater.js b/services/common/blocklist-updater.js
new file mode 100644
index 000000000..3b39b9552
--- /dev/null
+++ b/services/common/blocklist-updater.js
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["checkVersions", "addTestBlocklistClient"];
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.importGlobalProperties(['fetch']);
+const BlocklistClients = Cu.import("resource://services-common/blocklist-clients.js", {});
+
+const PREF_SETTINGS_SERVER = "services.settings.server";
+const PREF_BLOCKLIST_CHANGES_PATH = "services.blocklist.changes.path";
+const PREF_BLOCKLIST_BUCKET = "services.blocklist.bucket";
+const PREF_BLOCKLIST_LAST_UPDATE = "services.blocklist.last_update_seconds";
+const PREF_BLOCKLIST_LAST_ETAG = "services.blocklist.last_etag";
+const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
+
+
+const gBlocklistClients = {
+ [BlocklistClients.OneCRLBlocklistClient.collectionName]: BlocklistClients.OneCRLBlocklistClient,
+ [BlocklistClients.AddonBlocklistClient.collectionName]: BlocklistClients.AddonBlocklistClient,
+ [BlocklistClients.GfxBlocklistClient.collectionName]: BlocklistClients.GfxBlocklistClient,
+ [BlocklistClients.PluginBlocklistClient.collectionName]: BlocklistClients.PluginBlocklistClient
+};
+
+// Add a blocklist client for testing purposes. Do not use for any other purpose
+this.addTestBlocklistClient = (name, client) => { gBlocklistClients[name] = client; }
+
+// This is called by the ping mechanism.
+// returns a promise that rejects if something goes wrong
+this.checkVersions = function() {
+ return Task.spawn(function* syncClients() {
+ // Fetch a versionInfo object that looks like:
+ // {"data":[
+ // {
+ // "host":"kinto-ota.dev.mozaws.net",
+ // "last_modified":1450717104423,
+ // "bucket":"blocklists",
+ // "collection":"certificates"
+ // }]}
+ // Right now, we only use the collection name and the last modified info
+ let kintoBase = Services.prefs.getCharPref(PREF_SETTINGS_SERVER);
+ let changesEndpoint = kintoBase + Services.prefs.getCharPref(PREF_BLOCKLIST_CHANGES_PATH);
+ let blocklistsBucket = Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET);
+
+ // Use ETag to obtain a `304 Not modified` when no change occurred.
+ const headers = {};
+ if (Services.prefs.prefHasUserValue(PREF_BLOCKLIST_LAST_ETAG)) {
+ const lastEtag = Services.prefs.getCharPref(PREF_BLOCKLIST_LAST_ETAG);
+ if (lastEtag) {
+ headers["If-None-Match"] = lastEtag;
+ }
+ }
+
+ let response = yield fetch(changesEndpoint, {headers});
+
+ let versionInfo;
+ // No changes since last time. Go on with empty list of changes.
+ if (response.status == 304) {
+ versionInfo = {data: []};
+ } else {
+ versionInfo = yield response.json();
+ }
+
+ // If the server is failing, the JSON response might not contain the
+ // expected data (e.g. error response - Bug 1259145)
+ if (!versionInfo.hasOwnProperty("data")) {
+ throw new Error("Polling for changes failed.");
+ }
+
+ // Record new update time and the difference between local and server time
+ let serverTimeMillis = Date.parse(response.headers.get("Date"));
+
+ // negative clockDifference means local time is behind server time
+ // by the absolute of that value in seconds (positive means it's ahead)
+ let clockDifference = Math.floor((Date.now() - serverTimeMillis) / 1000);
+ Services.prefs.setIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, clockDifference);
+ Services.prefs.setIntPref(PREF_BLOCKLIST_LAST_UPDATE, serverTimeMillis / 1000);
+
+ let firstError;
+ for (let collectionInfo of versionInfo.data) {
+ // Skip changes that don't concern configured blocklist bucket.
+ if (collectionInfo.bucket != blocklistsBucket) {
+ continue;
+ }
+
+ let collection = collectionInfo.collection;
+ let client = gBlocklistClients[collection];
+ if (client && client.maybeSync) {
+ let lastModified = 0;
+ if (collectionInfo.last_modified) {
+ lastModified = collectionInfo.last_modified;
+ }
+ try {
+ yield client.maybeSync(lastModified, serverTimeMillis);
+ } catch (e) {
+ if (!firstError) {
+ firstError = e;
+ }
+ }
+ }
+ }
+ if (firstError) {
+ // cause the promise to reject by throwing the first observed error
+ throw firstError;
+ }
+
+ // Save current Etag for next poll.
+ if (response.headers.has("ETag")) {
+ const currentEtag = response.headers.get("ETag");
+ Services.prefs.setCharPref(PREF_BLOCKLIST_LAST_ETAG, currentEtag);
+ }
+ });
+};