diff options
Diffstat (limited to 'services/common/blocklist-updater.js')
-rw-r--r-- | services/common/blocklist-updater.js | 117 |
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 0000000000..3b39b95524 --- /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); + } + }); +}; |