summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm')
-rw-r--r--toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm467
1 files changed, 0 insertions, 467 deletions
diff --git a/toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm b/toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm
deleted file mode 100644
index f98dd2a94..000000000
--- a/toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm
+++ /dev/null
@@ -1,467 +0,0 @@
-/* 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/. */
-
-"use strict";
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-const LOCAL_EME_SOURCES = [{
- "id": "gmp-eme-adobe",
- "src": "chrome://global/content/gmp-sources/eme-adobe.json"
-}, {
- "id": "gmp-gmpopenh264",
- "src": "chrome://global/content/gmp-sources/openh264.json"
-}, {
- "id": "gmp-widevinecdm",
- "src": "chrome://global/content/gmp-sources/widevinecdm.json"
-}];
-
-this.EXPORTED_SYMBOLS = [ "ProductAddonChecker" ];
-
-Cu.importGlobalProperties(["XMLHttpRequest"]);
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/CertUtils.jsm");
-/* globals checkCert, BadCertHandler*/
-Cu.import("resource://gre/modules/FileUtils.jsm");
-Cu.import("resource://gre/modules/NetUtil.jsm");
-Cu.import("resource://gre/modules/osfile.jsm");
-
-/* globals GMPPrefs */
-XPCOMUtils.defineLazyModuleGetter(this, "GMPPrefs",
- "resource://gre/modules/GMPUtils.jsm");
-
-/* globals OS */
-
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
- "resource://gre/modules/UpdateUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
- "resource://gre/modules/ServiceRequest.jsm");
-
-// This exists so that tests can override the XHR behaviour for downloading
-// the addon update XML file.
-var CreateXHR = function() {
- return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
- createInstance(Ci.nsISupports);
-}
-
-var logger = Log.repository.getLogger("addons.productaddons");
-
-/**
- * Number of milliseconds after which we need to cancel `downloadXML`.
- *
- * Bug 1087674 suggests that the XHR we use in `downloadXML` may
- * never terminate in presence of network nuisances (e.g. strange
- * antivirus behavior). This timeout is a defensive measure to ensure
- * that we fail cleanly in such case.
- */
-const TIMEOUT_DELAY_MS = 20000;
-// Chunk size for the incremental downloader
-const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
-// Incremental downloader interval
-const DOWNLOAD_INTERVAL = 0;
-// How much of a file to read into memory at a time for hashing
-const HASH_CHUNK_SIZE = 8192;
-
-/**
- * Gets the status of an XMLHttpRequest either directly or from its underlying
- * channel.
- *
- * @param request
- * The XMLHttpRequest.
- * @return an integer status value.
- */
-function getRequestStatus(request) {
- let status = null;
- try {
- status = request.status;
- }
- catch (e) {
- }
-
- if (status != null) {
- return status;
- }
-
- return request.channel.QueryInterface(Ci.nsIRequest).status;
-}
-
-/**
- * Downloads an XML document from a URL optionally testing the SSL certificate
- * for certain attributes.
- *
- * @param url
- * The url to download from.
- * @param allowNonBuiltIn
- * Whether to trust SSL certificates without a built-in CA issuer.
- * @param allowedCerts
- * The list of certificate attributes to match the SSL certificate
- * against or null to skip checks.
- * @return a promise that resolves to the DOM document downloaded or rejects
- * with a JS exception in case of error.
- */
-function downloadXML(url, allowNonBuiltIn = false, allowedCerts = null) {
- return new Promise((resolve, reject) => {
- let request = CreateXHR();
- // This is here to let unit test code override XHR
- if (request.wrappedJSObject) {
- request = request.wrappedJSObject;
- }
- request.open("GET", url, true);
- request.channel.notificationCallbacks = new BadCertHandler(allowNonBuiltIn);
- // Prevent the request from reading from the cache.
- request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
- // Prevent the request from writing to the cache.
- request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
- // Use conservative TLS settings. See bug 1325501.
- // TODO move to ServiceRequest.
- if (request.channel instanceof Ci.nsIHttpChannelInternal) {
- request.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
- }
- request.timeout = TIMEOUT_DELAY_MS;
-
- request.overrideMimeType("text/xml");
- // The Cache-Control header is only interpreted by proxies and the
- // final destination. It does not help if a resource is already
- // cached locally.
- request.setRequestHeader("Cache-Control", "no-cache");
- // HTTP/1.0 servers might not implement Cache-Control and
- // might only implement Pragma: no-cache
- request.setRequestHeader("Pragma", "no-cache");
-
- let fail = (event) => {
- let request = event.target;
- let status = getRequestStatus(request);
- let message = "Failed downloading XML, status: " + status + ", reason: " + event.type;
- logger.warn(message);
- let ex = new Error(message);
- ex.status = status;
- reject(ex);
- };
-
- let success = (event) => {
- logger.info("Completed downloading document");
- let request = event.target;
-
- try {
- checkCert(request.channel, allowNonBuiltIn, allowedCerts);
- } catch (ex) {
- logger.error("Request failed certificate checks: " + ex);
- ex.status = getRequestStatus(request);
- reject(ex);
- return;
- }
-
- resolve(request.responseXML);
- };
-
- request.addEventListener("error", fail, false);
- request.addEventListener("abort", fail, false);
- request.addEventListener("timeout", fail, false);
- request.addEventListener("load", success, false);
-
- logger.info("sending request to: " + url);
- request.send(null);
- });
-}
-
-function downloadJSON(uri) {
- logger.info("fetching config from: " + uri);
- return new Promise((resolve, reject) => {
- let xmlHttp = new ServiceRequest({mozAnon: true});
-
- xmlHttp.onload = function(aResponse) {
- resolve(JSON.parse(this.responseText));
- };
-
- xmlHttp.onerror = function(e) {
- reject("Fetching " + uri + " results in error code: " + e.target.status);
- };
-
- xmlHttp.open("GET", uri);
- xmlHttp.overrideMimeType("application/json");
- xmlHttp.send();
- });
-}
-
-
-/**
- * Parses a list of add-ons from a DOM document.
- *
- * @param document
- * The DOM document to parse.
- * @return null if there is no <addons> element otherwise an object containing
- * an array of the addons listed and a field notifying whether the
- * fallback was used.
- */
-function parseXML(document) {
- // Check that the root element is correct
- if (document.documentElement.localName != "updates") {
- throw new Error("got node name: " + document.documentElement.localName +
- ", expected: updates");
- }
-
- // Check if there are any addons elements in the updates element
- let addons = document.querySelector("updates:root > addons");
- if (!addons) {
- return null;
- }
-
- let results = [];
- let addonList = document.querySelectorAll("updates:root > addons > addon");
- for (let addonElement of addonList) {
- let addon = {};
-
- for (let name of ["id", "URL", "hashFunction", "hashValue", "version", "size"]) {
- if (addonElement.hasAttribute(name)) {
- addon[name] = addonElement.getAttribute(name);
- }
- }
- addon.size = Number(addon.size) || undefined;
-
- results.push(addon);
- }
-
- return {
- usedFallback: false,
- gmpAddons: results
- };
-}
-
-/**
- * If downloading from the network fails (AUS server is down),
- * load the sources from local build configuration.
- */
-function downloadLocalConfig() {
-
- if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
- logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
- return Promise.resolve({usedFallback: true, gmpAddons: []});
- }
-
- return Promise.all(LOCAL_EME_SOURCES.map(conf => {
- return downloadJSON(conf.src).then(addons => {
-
- let platforms = addons.vendors[conf.id].platforms;
- let target = Services.appinfo.OS + "_" + UpdateUtils.ABI;
- let details = null;
-
- while (!details) {
- if (!(target in platforms)) {
- // There was no matching platform so return false, this addon
- // will be filtered from the results below
- logger.info("no details found for: " + target);
- return false;
- }
- // Field either has the details of the binary or is an alias
- // to another build target key that does
- if (platforms[target].alias) {
- target = platforms[target].alias;
- } else {
- details = platforms[target];
- }
- }
-
- logger.info("found plugin: " + conf.id);
- return {
- "id": conf.id,
- "URL": details.fileUrl,
- "hashFunction": addons.hashFunction,
- "hashValue": details.hashValue,
- "version": addons.vendors[conf.id].version,
- "size": details.filesize
- };
- });
- })).then(addons => {
-
- // Some filters may not match this platform so
- // filter those out
- addons = addons.filter(x => x !== false);
-
- return {
- usedFallback: true,
- gmpAddons: addons
- };
- });
-}
-
-/**
- * Downloads file from a URL using XHR.
- *
- * @param url
- * The url to download from.
- * @return a promise that resolves to the path of a temporary file or rejects
- * with a JS exception in case of error.
- */
-function downloadFile(url) {
- return new Promise((resolve, reject) => {
- let xhr = new XMLHttpRequest();
- xhr.onload = function(response) {
- logger.info("downloadXHR File download. status=" + xhr.status);
- if (xhr.status != 200 && xhr.status != 206) {
- reject(Components.Exception("File download failed", xhr.status));
- return;
- }
- Task.spawn(function* () {
- let f = yield OS.File.openUnique(OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon"));
- let path = f.path;
- logger.info(`Downloaded file will be saved to ${path}`);
- yield f.file.close();
- yield OS.File.writeAtomic(path, new Uint8Array(xhr.response));
- return path;
- }).then(resolve, reject);
- };
-
- let fail = (event) => {
- let request = event.target;
- let status = getRequestStatus(request);
- let message = "Failed downloading via XHR, status: " + status + ", reason: " + event.type;
- logger.warn(message);
- let ex = new Error(message);
- ex.status = status;
- reject(ex);
- };
- xhr.addEventListener("error", fail);
- xhr.addEventListener("abort", fail);
-
- xhr.responseType = "arraybuffer";
- try {
- xhr.open("GET", url);
- // Use conservative TLS settings. See bug 1325501.
- // TODO move to ServiceRequest.
- if (xhr.channel instanceof Ci.nsIHttpChannelInternal) {
- xhr.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true;
- }
- xhr.send(null);
- } catch (ex) {
- reject(ex);
- }
- });
-}
-
-/**
- * Convert a string containing binary values to hex.
- */
-function binaryToHex(input) {
- let result = "";
- for (let i = 0; i < input.length; ++i) {
- let hex = input.charCodeAt(i).toString(16);
- if (hex.length == 1) {
- hex = "0" + hex;
- }
- result += hex;
- }
- return result;
-}
-
-/**
- * Calculates the hash of a file.
- *
- * @param hashFunction
- * The type of hash function to use, must be supported by nsICryptoHash.
- * @param path
- * The path of the file to hash.
- * @return a promise that resolves to hash of the file or rejects with a JS
- * exception in case of error.
- */
-var computeHash = Task.async(function*(hashFunction, path) {
- let file = yield OS.File.open(path, { existing: true, read: true });
- try {
- let hasher = Cc["@mozilla.org/security/hash;1"].
- createInstance(Ci.nsICryptoHash);
- hasher.initWithString(hashFunction);
-
- let bytes;
- do {
- bytes = yield file.read(HASH_CHUNK_SIZE);
- hasher.update(bytes, bytes.length);
- } while (bytes.length == HASH_CHUNK_SIZE);
-
- return binaryToHex(hasher.finish(false));
- }
- finally {
- yield file.close();
- }
-});
-
-/**
- * Verifies that a downloaded file matches what was expected.
- *
- * @param properties
- * The properties to check, `size` and `hashFunction` with `hashValue`
- * are supported. Any properties missing won't be checked.
- * @param path
- * The path of the file to check.
- * @return a promise that resolves if the file matched or rejects with a JS
- * exception in case of error.
- */
-var verifyFile = Task.async(function*(properties, path) {
- if (properties.size !== undefined) {
- let stat = yield OS.File.stat(path);
- if (stat.size != properties.size) {
- throw new Error("Downloaded file was " + stat.size + " bytes but expected " + properties.size + " bytes.");
- }
- }
-
- if (properties.hashFunction !== undefined) {
- let expectedDigest = properties.hashValue.toLowerCase();
- let digest = yield computeHash(properties.hashFunction, path);
- if (digest != expectedDigest) {
- throw new Error("Hash was `" + digest + "` but expected `" + expectedDigest + "`.");
- }
- }
-});
-
-const ProductAddonChecker = {
- /**
- * Downloads a list of add-ons from a URL optionally testing the SSL
- * certificate for certain attributes.
- *
- * @param url
- * The url to download from.
- * @param allowNonBuiltIn
- * Whether to trust SSL certificates without a built-in CA issuer.
- * @param allowedCerts
- * The list of certificate attributes to match the SSL certificate
- * against or null to skip checks.
- * @return a promise that resolves to an object containing the list of add-ons
- * and whether the local fallback was used, or rejects with a JS
- * exception in case of error.
- */
- getProductAddonList: function(url, allowNonBuiltIn = false, allowedCerts = null) {
- if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
- logger.info("Updates are disabled via media.gmp-manager.updateEnabled");
- return Promise.resolve({usedFallback: true, gmpAddons: []});
- }
-
- return downloadXML(url, allowNonBuiltIn, allowedCerts)
- .then(parseXML)
- .catch(downloadLocalConfig);
- },
-
- /**
- * Downloads an add-on to a local file and checks that it matches the expected
- * file. The caller is responsible for deleting the temporary file returned.
- *
- * @param addon
- * The addon to download.
- * @return a promise that resolves to the temporary file downloaded or rejects
- * with a JS exception in case of error.
- */
- downloadAddon: Task.async(function*(addon) {
- let path = yield downloadFile(addon.URL);
- try {
- yield verifyFile(addon, path);
- return path;
- }
- catch (e) {
- yield OS.File.remove(path);
- throw e;
- }
- })
-}