diff options
Diffstat (limited to 'toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm')
-rw-r--r-- | toolkit/mozapps/webextensions/internal/ProductAddonChecker.jsm | 467 |
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; - } - }) -} |