michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * The AddonUpdateChecker is responsible for retrieving the update information michael@0: * from an add-on's remote update manifest. michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: this.EXPORTED_SYMBOLS = [ "AddonUpdateChecker" ]; michael@0: michael@0: const TIMEOUT = 60 * 1000; michael@0: const PREFIX_NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; michael@0: const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; michael@0: const PREFIX_ITEM = "urn:mozilla:item:"; michael@0: const PREFIX_EXTENSION = "urn:mozilla:extension:"; michael@0: const PREFIX_THEME = "urn:mozilla:theme:"; michael@0: const TOOLKIT_ID = "toolkit@mozilla.org" michael@0: const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" michael@0: michael@0: const PREF_UPDATE_REQUIREBUILTINCERTS = "extensions.update.requireBuiltInCerts"; michael@0: michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", michael@0: "resource://gre/modules/AddonManager.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", michael@0: "resource://gre/modules/addons/AddonRepository.jsm"); michael@0: michael@0: // Shared code for suppressing bad cert dialogs. michael@0: XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() { michael@0: let certUtils = {}; michael@0: Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils); michael@0: return certUtils; michael@0: }); michael@0: michael@0: var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. michael@0: getService(Ci.nsIRDFService); michael@0: michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: const LOGGER_ID = "addons.update-checker"; michael@0: michael@0: // Create a new logger for use by the Addons Update Checker michael@0: // (Requires AddonManager.jsm) michael@0: let logger = Log.repository.getLogger(LOGGER_ID); michael@0: michael@0: /** michael@0: * A serialisation method for RDF data that produces an identical string michael@0: * for matching RDF graphs. michael@0: * The serialisation is not complete, only assertions stemming from a given michael@0: * resource are included, multiple references to the same resource are not michael@0: * permitted, and the RDF prolog and epilog are not included. michael@0: * RDF Blob and Date literals are not supported. michael@0: */ michael@0: function RDFSerializer() { michael@0: this.cUtils = Cc["@mozilla.org/rdf/container-utils;1"]. michael@0: getService(Ci.nsIRDFContainerUtils); michael@0: this.resources = []; michael@0: } michael@0: michael@0: RDFSerializer.prototype = { michael@0: INDENT: " ", // The indent used for pretty-printing michael@0: resources: null, // Array of the resources that have been found michael@0: michael@0: /** michael@0: * Escapes characters from a string that should not appear in XML. michael@0: * michael@0: * @param aString michael@0: * The string to be escaped michael@0: * @return a string with all characters invalid in XML character data michael@0: * converted to entity references. michael@0: */ michael@0: escapeEntities: function RDFS_escapeEntities(aString) { michael@0: aString = aString.replace(/&/g, "&"); michael@0: aString = aString.replace(//g, ">"); michael@0: return aString.replace(/"/g, """); michael@0: }, michael@0: michael@0: /** michael@0: * Serializes all the elements of an RDF container. michael@0: * michael@0: * @param aDs michael@0: * The RDF datasource michael@0: * @param aContainer michael@0: * The RDF container to output the child elements of michael@0: * @param aIndent michael@0: * The current level of indent for pretty-printing michael@0: * @return a string containing the serialized elements. michael@0: */ michael@0: serializeContainerItems: function RDFS_serializeContainerItems(aDs, aContainer, michael@0: aIndent) { michael@0: var result = ""; michael@0: var items = aContainer.GetElements(); michael@0: while (items.hasMoreElements()) { michael@0: var item = items.getNext().QueryInterface(Ci.nsIRDFResource); michael@0: result += aIndent + "\n" michael@0: result += this.serializeResource(aDs, item, aIndent + this.INDENT); michael@0: result += aIndent + "\n" michael@0: } michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Serializes all em:* (see EM_NS) properties of an RDF resource except for michael@0: * the em:signature property. As this serialization is to be compared against michael@0: * the manifest signature it cannot contain the em:signature property itself. michael@0: * michael@0: * @param aDs michael@0: * The RDF datasource michael@0: * @param aResource michael@0: * The RDF resource that contains the properties to serialize michael@0: * @param aIndent michael@0: * The current level of indent for pretty-printing michael@0: * @return a string containing the serialized properties. michael@0: * @throws if the resource contains a property that cannot be serialized michael@0: */ michael@0: serializeResourceProperties: function RDFS_serializeResourceProperties(aDs, michael@0: aResource, michael@0: aIndent) { michael@0: var result = ""; michael@0: var items = []; michael@0: var arcs = aDs.ArcLabelsOut(aResource); michael@0: while (arcs.hasMoreElements()) { michael@0: var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource); michael@0: if (arc.ValueUTF8.substring(0, PREFIX_NS_EM.length) != PREFIX_NS_EM) michael@0: continue; michael@0: var prop = arc.ValueUTF8.substring(PREFIX_NS_EM.length); michael@0: if (prop == "signature") michael@0: continue; michael@0: michael@0: var targets = aDs.GetTargets(aResource, arc, true); michael@0: while (targets.hasMoreElements()) { michael@0: var target = targets.getNext(); michael@0: if (target instanceof Ci.nsIRDFResource) { michael@0: var item = aIndent + "\n"; michael@0: item += this.serializeResource(aDs, target, aIndent + this.INDENT); michael@0: item += aIndent + "\n"; michael@0: items.push(item); michael@0: } michael@0: else if (target instanceof Ci.nsIRDFLiteral) { michael@0: items.push(aIndent + "" + michael@0: this.escapeEntities(target.Value) + "\n"); michael@0: } michael@0: else if (target instanceof Ci.nsIRDFInt) { michael@0: items.push(aIndent + "" + michael@0: target.Value + "\n"); michael@0: } michael@0: else { michael@0: throw Components.Exception("Cannot serialize unknown literal type"); michael@0: } michael@0: } michael@0: } michael@0: items.sort(); michael@0: result += items.join(""); michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Recursively serializes an RDF resource and all resources it links to. michael@0: * This will only output EM_NS properties and will ignore any em:signature michael@0: * property. michael@0: * michael@0: * @param aDs michael@0: * The RDF datasource michael@0: * @param aResource michael@0: * The RDF resource to serialize michael@0: * @param aIndent michael@0: * The current level of indent for pretty-printing. If undefined no michael@0: * indent will be added michael@0: * @return a string containing the serialized resource. michael@0: * @throws if the RDF data contains multiple references to the same resource. michael@0: */ michael@0: serializeResource: function RDFS_serializeResource(aDs, aResource, aIndent) { michael@0: if (this.resources.indexOf(aResource) != -1 ) { michael@0: // We cannot output multiple references to the same resource. michael@0: throw Components.Exception("Cannot serialize multiple references to " + aResource.Value); michael@0: } michael@0: if (aIndent === undefined) michael@0: aIndent = ""; michael@0: michael@0: this.resources.push(aResource); michael@0: var container = null; michael@0: var type = "Description"; michael@0: if (this.cUtils.IsSeq(aDs, aResource)) { michael@0: type = "Seq"; michael@0: container = this.cUtils.MakeSeq(aDs, aResource); michael@0: } michael@0: else if (this.cUtils.IsAlt(aDs, aResource)) { michael@0: type = "Alt"; michael@0: container = this.cUtils.MakeAlt(aDs, aResource); michael@0: } michael@0: else if (this.cUtils.IsBag(aDs, aResource)) { michael@0: type = "Bag"; michael@0: container = this.cUtils.MakeBag(aDs, aResource); michael@0: } michael@0: michael@0: var result = aIndent + "\n"; michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Parses an RDF style update manifest into an array of update objects. michael@0: * michael@0: * @param aId michael@0: * The ID of the add-on being checked for updates michael@0: * @param aUpdateKey michael@0: * An optional update key for the add-on michael@0: * @param aRequest michael@0: * The XMLHttpRequest that has retrieved the update manifest michael@0: * @return an array of update objects michael@0: * @throws if the update manifest is invalid in any way michael@0: */ michael@0: function parseRDFManifest(aId, aUpdateKey, aRequest) { michael@0: function EM_R(aProp) { michael@0: return gRDF.GetResource(PREFIX_NS_EM + aProp); michael@0: } michael@0: michael@0: function getValue(aLiteral) { michael@0: if (aLiteral instanceof Ci.nsIRDFLiteral) michael@0: return aLiteral.Value; michael@0: if (aLiteral instanceof Ci.nsIRDFResource) michael@0: return aLiteral.Value; michael@0: if (aLiteral instanceof Ci.nsIRDFInt) michael@0: return aLiteral.Value; michael@0: return null; michael@0: } michael@0: michael@0: function getProperty(aDs, aSource, aProperty) { michael@0: return getValue(aDs.GetTarget(aSource, EM_R(aProperty), true)); michael@0: } michael@0: michael@0: function getRequiredProperty(aDs, aSource, aProperty) { michael@0: let value = getProperty(aDs, aSource, aProperty); michael@0: if (!value) michael@0: throw Components.Exception("Update manifest is missing a required " + aProperty + " property."); michael@0: return value; michael@0: } michael@0: michael@0: let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"]. michael@0: createInstance(Ci.nsIRDFXMLParser); michael@0: let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]. michael@0: createInstance(Ci.nsIRDFDataSource); michael@0: rdfParser.parseString(ds, aRequest.channel.URI, aRequest.responseText); michael@0: michael@0: // Differentiating between add-on types is deprecated michael@0: let extensionRes = gRDF.GetResource(PREFIX_EXTENSION + aId); michael@0: let themeRes = gRDF.GetResource(PREFIX_THEME + aId); michael@0: let itemRes = gRDF.GetResource(PREFIX_ITEM + aId); michael@0: let addonRes = ds.ArcLabelsOut(extensionRes).hasMoreElements() ? extensionRes michael@0: : ds.ArcLabelsOut(themeRes).hasMoreElements() ? themeRes michael@0: : itemRes; michael@0: michael@0: // If we have an update key then the update manifest must be signed michael@0: if (aUpdateKey) { michael@0: let signature = getProperty(ds, addonRes, "signature"); michael@0: if (!signature) michael@0: throw Components.Exception("Update manifest for " + aId + " does not contain a required signature"); michael@0: let serializer = new RDFSerializer(); michael@0: let updateString = null; michael@0: michael@0: try { michael@0: updateString = serializer.serializeResource(ds, addonRes); michael@0: } michael@0: catch (e) { michael@0: throw Components.Exception("Failed to generate signed string for " + aId + ". Serializer threw " + e, michael@0: e.result); michael@0: } michael@0: michael@0: let result = false; michael@0: michael@0: try { michael@0: let verifier = Cc["@mozilla.org/security/datasignatureverifier;1"]. michael@0: getService(Ci.nsIDataSignatureVerifier); michael@0: result = verifier.verifyData(updateString, signature, aUpdateKey); michael@0: } michael@0: catch (e) { michael@0: throw Components.Exception("The signature or updateKey for " + aId + " is malformed." + michael@0: "Verifier threw " + e, e.result); michael@0: } michael@0: michael@0: if (!result) michael@0: throw Components.Exception("The signature for " + aId + " was not created by the add-on's updateKey"); michael@0: } michael@0: michael@0: let updates = ds.GetTarget(addonRes, EM_R("updates"), true); michael@0: michael@0: // A missing updates property doesn't count as a failure, just as no avialable michael@0: // update information michael@0: if (!updates) { michael@0: logger.warn("Update manifest for " + aId + " did not contain an updates property"); michael@0: return []; michael@0: } michael@0: michael@0: if (!(updates instanceof Ci.nsIRDFResource)) michael@0: throw Components.Exception("Missing updates property for " + addonRes.Value); michael@0: michael@0: let cu = Cc["@mozilla.org/rdf/container-utils;1"]. michael@0: getService(Ci.nsIRDFContainerUtils); michael@0: if (!cu.IsContainer(ds, updates)) michael@0: throw Components.Exception("Updates property was not an RDF container"); michael@0: michael@0: let results = []; michael@0: let ctr = Cc["@mozilla.org/rdf/container;1"]. michael@0: createInstance(Ci.nsIRDFContainer); michael@0: ctr.Init(ds, updates); michael@0: let items = ctr.GetElements(); michael@0: while (items.hasMoreElements()) { michael@0: let item = items.getNext().QueryInterface(Ci.nsIRDFResource); michael@0: let version = getProperty(ds, item, "version"); michael@0: if (!version) { michael@0: logger.warn("Update manifest is missing a required version property."); michael@0: continue; michael@0: } michael@0: michael@0: logger.debug("Found an update entry for " + aId + " version " + version); michael@0: michael@0: let targetApps = ds.GetTargets(item, EM_R("targetApplication"), true); michael@0: while (targetApps.hasMoreElements()) { michael@0: let targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource); michael@0: michael@0: let appEntry = {}; michael@0: try { michael@0: appEntry.id = getRequiredProperty(ds, targetApp, "id"); michael@0: appEntry.minVersion = getRequiredProperty(ds, targetApp, "minVersion"); michael@0: appEntry.maxVersion = getRequiredProperty(ds, targetApp, "maxVersion"); michael@0: } michael@0: catch (e) { michael@0: logger.warn(e); michael@0: continue; michael@0: } michael@0: michael@0: let result = { michael@0: id: aId, michael@0: version: version, michael@0: updateURL: getProperty(ds, targetApp, "updateLink"), michael@0: updateHash: getProperty(ds, targetApp, "updateHash"), michael@0: updateInfoURL: getProperty(ds, targetApp, "updateInfoURL"), michael@0: strictCompatibility: getProperty(ds, targetApp, "strictCompatibility") == "true", michael@0: targetApplications: [appEntry] michael@0: }; michael@0: michael@0: if (result.updateURL && AddonManager.checkUpdateSecurity && michael@0: result.updateURL.substring(0, 6) != "https:" && michael@0: (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) { michael@0: logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" + michael@0: " by a strong enough hash (needs to be sha1 or stronger)."); michael@0: delete result.updateURL; michael@0: delete result.updateHash; michael@0: } michael@0: results.push(result); michael@0: } michael@0: } michael@0: return results; michael@0: } michael@0: michael@0: /** michael@0: * Starts downloading an update manifest and then passes it to an appropriate michael@0: * parser to convert to an array of update objects michael@0: * michael@0: * @param aId michael@0: * The ID of the add-on being checked for updates michael@0: * @param aUpdateKey michael@0: * An optional update key for the add-on michael@0: * @param aUrl michael@0: * The URL of the update manifest michael@0: * @param aObserver michael@0: * An observer to pass results to michael@0: */ michael@0: function UpdateParser(aId, aUpdateKey, aUrl, aObserver) { michael@0: this.id = aId; michael@0: this.updateKey = aUpdateKey; michael@0: this.observer = aObserver; michael@0: this.url = aUrl; michael@0: michael@0: let requireBuiltIn = true; michael@0: try { michael@0: requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS); michael@0: } michael@0: catch (e) { michael@0: } michael@0: michael@0: logger.debug("Requesting " + aUrl); michael@0: try { michael@0: this.request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. michael@0: createInstance(Ci.nsIXMLHttpRequest); michael@0: this.request.open("GET", this.url, true); michael@0: this.request.channel.notificationCallbacks = new CertUtils.BadCertHandler(!requireBuiltIn); michael@0: this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; michael@0: // Prevent the request from writing to cache. michael@0: this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; michael@0: this.request.overrideMimeType("text/xml"); michael@0: this.request.timeout = TIMEOUT; michael@0: var self = this; michael@0: this.request.addEventListener("load", function loadEventListener(event) { self.onLoad() }, false); michael@0: this.request.addEventListener("error", function errorEventListener(event) { self.onError() }, false); michael@0: this.request.addEventListener("timeout", function timeoutEventListener(event) { self.onTimeout() }, false); michael@0: this.request.send(null); michael@0: } michael@0: catch (e) { michael@0: logger.error("Failed to request update manifest", e); michael@0: } michael@0: } michael@0: michael@0: UpdateParser.prototype = { michael@0: id: null, michael@0: updateKey: null, michael@0: observer: null, michael@0: request: null, michael@0: url: null, michael@0: michael@0: /** michael@0: * Called when the manifest has been successfully loaded. michael@0: */ michael@0: onLoad: function UP_onLoad() { michael@0: let request = this.request; michael@0: this.request = null; michael@0: michael@0: let requireBuiltIn = true; michael@0: try { michael@0: requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS); michael@0: } michael@0: catch (e) { michael@0: } michael@0: michael@0: try { michael@0: CertUtils.checkCert(request.channel, !requireBuiltIn); michael@0: } michael@0: catch (e) { michael@0: this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR); michael@0: return; michael@0: } michael@0: michael@0: if (!Components.isSuccessCode(request.status)) { michael@0: logger.warn("Request failed: " + this.url + " - " + request.status); michael@0: this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR); michael@0: return; michael@0: } michael@0: michael@0: let channel = request.channel; michael@0: if (channel instanceof Ci.nsIHttpChannel && !channel.requestSucceeded) { michael@0: logger.warn("Request failed: " + this.url + " - " + channel.responseStatus + michael@0: ": " + channel.responseStatusText); michael@0: this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR); michael@0: return; michael@0: } michael@0: michael@0: let xml = request.responseXML; michael@0: if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) { michael@0: logger.warn("Update manifest was not valid XML"); michael@0: this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); michael@0: return; michael@0: } michael@0: michael@0: // We currently only know about RDF update manifests michael@0: if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) { michael@0: let results = null; michael@0: michael@0: try { michael@0: results = parseRDFManifest(this.id, this.updateKey, request); michael@0: } michael@0: catch (e) { michael@0: logger.warn(e); michael@0: this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); michael@0: return; michael@0: } michael@0: if ("onUpdateCheckComplete" in this.observer) { michael@0: try { michael@0: this.observer.onUpdateCheckComplete(results); michael@0: } michael@0: catch (e) { michael@0: logger.warn("onUpdateCheckComplete notification failed", e); michael@0: } michael@0: } michael@0: return; michael@0: } michael@0: michael@0: logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); michael@0: this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the request times out michael@0: */ michael@0: onTimeout: function() { michael@0: this.request = null; michael@0: logger.warn("Request for " + this.url + " timed out"); michael@0: this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT); michael@0: }, michael@0: michael@0: /** michael@0: * Called when the manifest failed to load. michael@0: */ michael@0: onError: function UP_onError() { michael@0: if (!Components.isSuccessCode(this.request.status)) { michael@0: logger.warn("Request failed: " + this.url + " - " + this.request.status); michael@0: } michael@0: else if (this.request.channel instanceof Ci.nsIHttpChannel) { michael@0: try { michael@0: if (this.request.channel.requestSucceeded) { michael@0: logger.warn("Request failed: " + this.url + " - " + michael@0: this.request.channel.responseStatus + ": " + michael@0: this.request.channel.responseStatusText); michael@0: } michael@0: } michael@0: catch (e) { michael@0: logger.warn("HTTP Request failed for an unknown reason"); michael@0: } michael@0: } michael@0: else { michael@0: logger.warn("Request failed for an unknown reason"); michael@0: } michael@0: michael@0: this.request = null; michael@0: michael@0: this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR); michael@0: }, michael@0: michael@0: /** michael@0: * Helper method to notify the observer that an error occured. michael@0: */ michael@0: notifyError: function UP_notifyError(aStatus) { michael@0: if ("onUpdateCheckError" in this.observer) { michael@0: try { michael@0: this.observer.onUpdateCheckError(aStatus); michael@0: } michael@0: catch (e) { michael@0: logger.warn("onUpdateCheckError notification failed", e); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Called to cancel an in-progress update check. michael@0: */ michael@0: cancel: function UP_cancel() { michael@0: this.request.abort(); michael@0: this.request = null; michael@0: this.notifyError(AddonUpdateChecker.ERROR_CANCELLED); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Tests if an update matches a version of the application or platform michael@0: * michael@0: * @param aUpdate michael@0: * The available update michael@0: * @param aAppVersion michael@0: * The application version to use michael@0: * @param aPlatformVersion michael@0: * The platform version to use michael@0: * @param aIgnoreMaxVersion michael@0: * Ignore maxVersion when testing if an update matches. Optional. michael@0: * @param aIgnoreStrictCompat michael@0: * Ignore strictCompatibility when testing if an update matches. Optional. michael@0: * @param aCompatOverrides michael@0: * AddonCompatibilityOverride objects to match against. Optional. michael@0: * @return true if the update is compatible with the application/platform michael@0: */ michael@0: function matchesVersions(aUpdate, aAppVersion, aPlatformVersion, michael@0: aIgnoreMaxVersion, aIgnoreStrictCompat, michael@0: aCompatOverrides) { michael@0: if (aCompatOverrides) { michael@0: let override = AddonRepository.findMatchingCompatOverride(aUpdate.version, michael@0: aCompatOverrides, michael@0: aAppVersion, michael@0: aPlatformVersion); michael@0: if (override && override.type == "incompatible") michael@0: return false; michael@0: } michael@0: michael@0: if (aUpdate.strictCompatibility && !aIgnoreStrictCompat) michael@0: aIgnoreMaxVersion = false; michael@0: michael@0: let result = false; michael@0: for (let app of aUpdate.targetApplications) { michael@0: if (app.id == Services.appinfo.ID) { michael@0: return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) && michael@0: (aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0)); michael@0: } michael@0: if (app.id == TOOLKIT_ID) { michael@0: result = (Services.vc.compare(aPlatformVersion, app.minVersion) >= 0) && michael@0: (aIgnoreMaxVersion || (Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0)); michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: this.AddonUpdateChecker = { michael@0: // These must be kept in sync with AddonManager michael@0: // The update check timed out michael@0: ERROR_TIMEOUT: -1, michael@0: // There was an error while downloading the update information. michael@0: ERROR_DOWNLOAD_ERROR: -2, michael@0: // The update information was malformed in some way. michael@0: ERROR_PARSE_ERROR: -3, michael@0: // The update information was not in any known format. michael@0: ERROR_UNKNOWN_FORMAT: -4, michael@0: // The update information was not correctly signed or there was an SSL error. michael@0: ERROR_SECURITY_ERROR: -5, michael@0: // The update was cancelled michael@0: ERROR_CANCELLED: -6, michael@0: michael@0: /** michael@0: * Retrieves the best matching compatibility update for the application from michael@0: * a list of available update objects. michael@0: * michael@0: * @param aUpdates michael@0: * An array of update objects michael@0: * @param aVersion michael@0: * The version of the add-on to get new compatibility information for michael@0: * @param aIgnoreCompatibility michael@0: * An optional parameter to get the first compatibility update that michael@0: * is compatible with any version of the application or toolkit michael@0: * @param aAppVersion michael@0: * The version of the application or null to use the current version michael@0: * @param aPlatformVersion michael@0: * The version of the platform or null to use the current version michael@0: * @param aIgnoreMaxVersion michael@0: * Ignore maxVersion when testing if an update matches. Optional. michael@0: * @param aIgnoreStrictCompat michael@0: * Ignore strictCompatibility when testing if an update matches. Optional. michael@0: * @return an update object if one matches or null if not michael@0: */ michael@0: getCompatibilityUpdate: function AUC_getCompatibilityUpdate(aUpdates, aVersion, michael@0: aIgnoreCompatibility, michael@0: aAppVersion, michael@0: aPlatformVersion, michael@0: aIgnoreMaxVersion, michael@0: aIgnoreStrictCompat) { michael@0: if (!aAppVersion) michael@0: aAppVersion = Services.appinfo.version; michael@0: if (!aPlatformVersion) michael@0: aPlatformVersion = Services.appinfo.platformVersion; michael@0: michael@0: for (let update of aUpdates) { michael@0: if (Services.vc.compare(update.version, aVersion) == 0) { michael@0: if (aIgnoreCompatibility) { michael@0: for (let targetApp of update.targetApplications) { michael@0: let id = targetApp.id; michael@0: if (id == Services.appinfo.ID || id == TOOLKIT_ID) michael@0: return update; michael@0: } michael@0: } michael@0: else if (matchesVersions(update, aAppVersion, aPlatformVersion, michael@0: aIgnoreMaxVersion, aIgnoreStrictCompat)) { michael@0: return update; michael@0: } michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Returns the newest available update from a list of update objects. michael@0: * michael@0: * @param aUpdates michael@0: * An array of update objects michael@0: * @param aAppVersion michael@0: * The version of the application or null to use the current version michael@0: * @param aPlatformVersion michael@0: * The version of the platform or null to use the current version michael@0: * @param aIgnoreMaxVersion michael@0: * When determining compatible updates, ignore maxVersion. Optional. michael@0: * @param aIgnoreStrictCompat michael@0: * When determining compatible updates, ignore strictCompatibility. Optional. michael@0: * @param aCompatOverrides michael@0: * Array of AddonCompatibilityOverride to take into account. Optional. michael@0: * @return an update object if one matches or null if not michael@0: */ michael@0: getNewestCompatibleUpdate: function AUC_getNewestCompatibleUpdate(aUpdates, michael@0: aAppVersion, michael@0: aPlatformVersion, michael@0: aIgnoreMaxVersion, michael@0: aIgnoreStrictCompat, michael@0: aCompatOverrides) { michael@0: if (!aAppVersion) michael@0: aAppVersion = Services.appinfo.version; michael@0: if (!aPlatformVersion) michael@0: aPlatformVersion = Services.appinfo.platformVersion; michael@0: michael@0: let blocklist = Cc["@mozilla.org/extensions/blocklist;1"]. michael@0: getService(Ci.nsIBlocklistService); michael@0: michael@0: let newest = null; michael@0: for (let update of aUpdates) { michael@0: if (!update.updateURL) michael@0: continue; michael@0: let state = blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion); michael@0: if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) michael@0: continue; michael@0: if ((newest == null || (Services.vc.compare(newest.version, update.version) < 0)) && michael@0: matchesVersions(update, aAppVersion, aPlatformVersion, michael@0: aIgnoreMaxVersion, aIgnoreStrictCompat, michael@0: aCompatOverrides)) { michael@0: newest = update; michael@0: } michael@0: } michael@0: return newest; michael@0: }, michael@0: michael@0: /** michael@0: * Starts an update check. michael@0: * michael@0: * @param aId michael@0: * The ID of the add-on being checked for updates michael@0: * @param aUpdateKey michael@0: * An optional update key for the add-on michael@0: * @param aUrl michael@0: * The URL of the add-on's update manifest michael@0: * @param aObserver michael@0: * An observer to notify of results michael@0: * @return UpdateParser so that the caller can use UpdateParser.cancel() to shut michael@0: * down in-progress update requests michael@0: */ michael@0: checkForUpdates: function AUC_checkForUpdates(aId, aUpdateKey, aUrl, michael@0: aObserver) { michael@0: return new UpdateParser(aId, aUpdateKey, aUrl, aObserver); michael@0: } michael@0: };