1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,738 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/** 1.9 + * The AddonUpdateChecker is responsible for retrieving the update information 1.10 + * from an add-on's remote update manifest. 1.11 + */ 1.12 + 1.13 +"use strict"; 1.14 + 1.15 +const Cc = Components.classes; 1.16 +const Ci = Components.interfaces; 1.17 +const Cu = Components.utils; 1.18 + 1.19 +this.EXPORTED_SYMBOLS = [ "AddonUpdateChecker" ]; 1.20 + 1.21 +const TIMEOUT = 60 * 1000; 1.22 +const PREFIX_NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; 1.23 +const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; 1.24 +const PREFIX_ITEM = "urn:mozilla:item:"; 1.25 +const PREFIX_EXTENSION = "urn:mozilla:extension:"; 1.26 +const PREFIX_THEME = "urn:mozilla:theme:"; 1.27 +const TOOLKIT_ID = "toolkit@mozilla.org" 1.28 +const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" 1.29 + 1.30 +const PREF_UPDATE_REQUIREBUILTINCERTS = "extensions.update.requireBuiltInCerts"; 1.31 + 1.32 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.33 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 1.34 + 1.35 +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", 1.36 + "resource://gre/modules/AddonManager.jsm"); 1.37 +XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", 1.38 + "resource://gre/modules/addons/AddonRepository.jsm"); 1.39 + 1.40 +// Shared code for suppressing bad cert dialogs. 1.41 +XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() { 1.42 + let certUtils = {}; 1.43 + Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils); 1.44 + return certUtils; 1.45 +}); 1.46 + 1.47 +var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. 1.48 + getService(Ci.nsIRDFService); 1.49 + 1.50 +Cu.import("resource://gre/modules/Log.jsm"); 1.51 +const LOGGER_ID = "addons.update-checker"; 1.52 + 1.53 +// Create a new logger for use by the Addons Update Checker 1.54 +// (Requires AddonManager.jsm) 1.55 +let logger = Log.repository.getLogger(LOGGER_ID); 1.56 + 1.57 +/** 1.58 + * A serialisation method for RDF data that produces an identical string 1.59 + * for matching RDF graphs. 1.60 + * The serialisation is not complete, only assertions stemming from a given 1.61 + * resource are included, multiple references to the same resource are not 1.62 + * permitted, and the RDF prolog and epilog are not included. 1.63 + * RDF Blob and Date literals are not supported. 1.64 + */ 1.65 +function RDFSerializer() { 1.66 + this.cUtils = Cc["@mozilla.org/rdf/container-utils;1"]. 1.67 + getService(Ci.nsIRDFContainerUtils); 1.68 + this.resources = []; 1.69 +} 1.70 + 1.71 +RDFSerializer.prototype = { 1.72 + INDENT: " ", // The indent used for pretty-printing 1.73 + resources: null, // Array of the resources that have been found 1.74 + 1.75 + /** 1.76 + * Escapes characters from a string that should not appear in XML. 1.77 + * 1.78 + * @param aString 1.79 + * The string to be escaped 1.80 + * @return a string with all characters invalid in XML character data 1.81 + * converted to entity references. 1.82 + */ 1.83 + escapeEntities: function RDFS_escapeEntities(aString) { 1.84 + aString = aString.replace(/&/g, "&"); 1.85 + aString = aString.replace(/</g, "<"); 1.86 + aString = aString.replace(/>/g, ">"); 1.87 + return aString.replace(/"/g, """); 1.88 + }, 1.89 + 1.90 + /** 1.91 + * Serializes all the elements of an RDF container. 1.92 + * 1.93 + * @param aDs 1.94 + * The RDF datasource 1.95 + * @param aContainer 1.96 + * The RDF container to output the child elements of 1.97 + * @param aIndent 1.98 + * The current level of indent for pretty-printing 1.99 + * @return a string containing the serialized elements. 1.100 + */ 1.101 + serializeContainerItems: function RDFS_serializeContainerItems(aDs, aContainer, 1.102 + aIndent) { 1.103 + var result = ""; 1.104 + var items = aContainer.GetElements(); 1.105 + while (items.hasMoreElements()) { 1.106 + var item = items.getNext().QueryInterface(Ci.nsIRDFResource); 1.107 + result += aIndent + "<RDF:li>\n" 1.108 + result += this.serializeResource(aDs, item, aIndent + this.INDENT); 1.109 + result += aIndent + "</RDF:li>\n" 1.110 + } 1.111 + return result; 1.112 + }, 1.113 + 1.114 + /** 1.115 + * Serializes all em:* (see EM_NS) properties of an RDF resource except for 1.116 + * the em:signature property. As this serialization is to be compared against 1.117 + * the manifest signature it cannot contain the em:signature property itself. 1.118 + * 1.119 + * @param aDs 1.120 + * The RDF datasource 1.121 + * @param aResource 1.122 + * The RDF resource that contains the properties to serialize 1.123 + * @param aIndent 1.124 + * The current level of indent for pretty-printing 1.125 + * @return a string containing the serialized properties. 1.126 + * @throws if the resource contains a property that cannot be serialized 1.127 + */ 1.128 + serializeResourceProperties: function RDFS_serializeResourceProperties(aDs, 1.129 + aResource, 1.130 + aIndent) { 1.131 + var result = ""; 1.132 + var items = []; 1.133 + var arcs = aDs.ArcLabelsOut(aResource); 1.134 + while (arcs.hasMoreElements()) { 1.135 + var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource); 1.136 + if (arc.ValueUTF8.substring(0, PREFIX_NS_EM.length) != PREFIX_NS_EM) 1.137 + continue; 1.138 + var prop = arc.ValueUTF8.substring(PREFIX_NS_EM.length); 1.139 + if (prop == "signature") 1.140 + continue; 1.141 + 1.142 + var targets = aDs.GetTargets(aResource, arc, true); 1.143 + while (targets.hasMoreElements()) { 1.144 + var target = targets.getNext(); 1.145 + if (target instanceof Ci.nsIRDFResource) { 1.146 + var item = aIndent + "<em:" + prop + ">\n"; 1.147 + item += this.serializeResource(aDs, target, aIndent + this.INDENT); 1.148 + item += aIndent + "</em:" + prop + ">\n"; 1.149 + items.push(item); 1.150 + } 1.151 + else if (target instanceof Ci.nsIRDFLiteral) { 1.152 + items.push(aIndent + "<em:" + prop + ">" + 1.153 + this.escapeEntities(target.Value) + "</em:" + prop + ">\n"); 1.154 + } 1.155 + else if (target instanceof Ci.nsIRDFInt) { 1.156 + items.push(aIndent + "<em:" + prop + " NC:parseType=\"Integer\">" + 1.157 + target.Value + "</em:" + prop + ">\n"); 1.158 + } 1.159 + else { 1.160 + throw Components.Exception("Cannot serialize unknown literal type"); 1.161 + } 1.162 + } 1.163 + } 1.164 + items.sort(); 1.165 + result += items.join(""); 1.166 + return result; 1.167 + }, 1.168 + 1.169 + /** 1.170 + * Recursively serializes an RDF resource and all resources it links to. 1.171 + * This will only output EM_NS properties and will ignore any em:signature 1.172 + * property. 1.173 + * 1.174 + * @param aDs 1.175 + * The RDF datasource 1.176 + * @param aResource 1.177 + * The RDF resource to serialize 1.178 + * @param aIndent 1.179 + * The current level of indent for pretty-printing. If undefined no 1.180 + * indent will be added 1.181 + * @return a string containing the serialized resource. 1.182 + * @throws if the RDF data contains multiple references to the same resource. 1.183 + */ 1.184 + serializeResource: function RDFS_serializeResource(aDs, aResource, aIndent) { 1.185 + if (this.resources.indexOf(aResource) != -1 ) { 1.186 + // We cannot output multiple references to the same resource. 1.187 + throw Components.Exception("Cannot serialize multiple references to " + aResource.Value); 1.188 + } 1.189 + if (aIndent === undefined) 1.190 + aIndent = ""; 1.191 + 1.192 + this.resources.push(aResource); 1.193 + var container = null; 1.194 + var type = "Description"; 1.195 + if (this.cUtils.IsSeq(aDs, aResource)) { 1.196 + type = "Seq"; 1.197 + container = this.cUtils.MakeSeq(aDs, aResource); 1.198 + } 1.199 + else if (this.cUtils.IsAlt(aDs, aResource)) { 1.200 + type = "Alt"; 1.201 + container = this.cUtils.MakeAlt(aDs, aResource); 1.202 + } 1.203 + else if (this.cUtils.IsBag(aDs, aResource)) { 1.204 + type = "Bag"; 1.205 + container = this.cUtils.MakeBag(aDs, aResource); 1.206 + } 1.207 + 1.208 + var result = aIndent + "<RDF:" + type; 1.209 + if (!gRDF.IsAnonymousResource(aResource)) 1.210 + result += " about=\"" + this.escapeEntities(aResource.ValueUTF8) + "\""; 1.211 + result += ">\n"; 1.212 + 1.213 + if (container) 1.214 + result += this.serializeContainerItems(aDs, container, aIndent + this.INDENT); 1.215 + 1.216 + result += this.serializeResourceProperties(aDs, aResource, aIndent + this.INDENT); 1.217 + 1.218 + result += aIndent + "</RDF:" + type + ">\n"; 1.219 + return result; 1.220 + } 1.221 +} 1.222 + 1.223 +/** 1.224 + * Parses an RDF style update manifest into an array of update objects. 1.225 + * 1.226 + * @param aId 1.227 + * The ID of the add-on being checked for updates 1.228 + * @param aUpdateKey 1.229 + * An optional update key for the add-on 1.230 + * @param aRequest 1.231 + * The XMLHttpRequest that has retrieved the update manifest 1.232 + * @return an array of update objects 1.233 + * @throws if the update manifest is invalid in any way 1.234 + */ 1.235 +function parseRDFManifest(aId, aUpdateKey, aRequest) { 1.236 + function EM_R(aProp) { 1.237 + return gRDF.GetResource(PREFIX_NS_EM + aProp); 1.238 + } 1.239 + 1.240 + function getValue(aLiteral) { 1.241 + if (aLiteral instanceof Ci.nsIRDFLiteral) 1.242 + return aLiteral.Value; 1.243 + if (aLiteral instanceof Ci.nsIRDFResource) 1.244 + return aLiteral.Value; 1.245 + if (aLiteral instanceof Ci.nsIRDFInt) 1.246 + return aLiteral.Value; 1.247 + return null; 1.248 + } 1.249 + 1.250 + function getProperty(aDs, aSource, aProperty) { 1.251 + return getValue(aDs.GetTarget(aSource, EM_R(aProperty), true)); 1.252 + } 1.253 + 1.254 + function getRequiredProperty(aDs, aSource, aProperty) { 1.255 + let value = getProperty(aDs, aSource, aProperty); 1.256 + if (!value) 1.257 + throw Components.Exception("Update manifest is missing a required " + aProperty + " property."); 1.258 + return value; 1.259 + } 1.260 + 1.261 + let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"]. 1.262 + createInstance(Ci.nsIRDFXMLParser); 1.263 + let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]. 1.264 + createInstance(Ci.nsIRDFDataSource); 1.265 + rdfParser.parseString(ds, aRequest.channel.URI, aRequest.responseText); 1.266 + 1.267 + // Differentiating between add-on types is deprecated 1.268 + let extensionRes = gRDF.GetResource(PREFIX_EXTENSION + aId); 1.269 + let themeRes = gRDF.GetResource(PREFIX_THEME + aId); 1.270 + let itemRes = gRDF.GetResource(PREFIX_ITEM + aId); 1.271 + let addonRes = ds.ArcLabelsOut(extensionRes).hasMoreElements() ? extensionRes 1.272 + : ds.ArcLabelsOut(themeRes).hasMoreElements() ? themeRes 1.273 + : itemRes; 1.274 + 1.275 + // If we have an update key then the update manifest must be signed 1.276 + if (aUpdateKey) { 1.277 + let signature = getProperty(ds, addonRes, "signature"); 1.278 + if (!signature) 1.279 + throw Components.Exception("Update manifest for " + aId + " does not contain a required signature"); 1.280 + let serializer = new RDFSerializer(); 1.281 + let updateString = null; 1.282 + 1.283 + try { 1.284 + updateString = serializer.serializeResource(ds, addonRes); 1.285 + } 1.286 + catch (e) { 1.287 + throw Components.Exception("Failed to generate signed string for " + aId + ". Serializer threw " + e, 1.288 + e.result); 1.289 + } 1.290 + 1.291 + let result = false; 1.292 + 1.293 + try { 1.294 + let verifier = Cc["@mozilla.org/security/datasignatureverifier;1"]. 1.295 + getService(Ci.nsIDataSignatureVerifier); 1.296 + result = verifier.verifyData(updateString, signature, aUpdateKey); 1.297 + } 1.298 + catch (e) { 1.299 + throw Components.Exception("The signature or updateKey for " + aId + " is malformed." + 1.300 + "Verifier threw " + e, e.result); 1.301 + } 1.302 + 1.303 + if (!result) 1.304 + throw Components.Exception("The signature for " + aId + " was not created by the add-on's updateKey"); 1.305 + } 1.306 + 1.307 + let updates = ds.GetTarget(addonRes, EM_R("updates"), true); 1.308 + 1.309 + // A missing updates property doesn't count as a failure, just as no avialable 1.310 + // update information 1.311 + if (!updates) { 1.312 + logger.warn("Update manifest for " + aId + " did not contain an updates property"); 1.313 + return []; 1.314 + } 1.315 + 1.316 + if (!(updates instanceof Ci.nsIRDFResource)) 1.317 + throw Components.Exception("Missing updates property for " + addonRes.Value); 1.318 + 1.319 + let cu = Cc["@mozilla.org/rdf/container-utils;1"]. 1.320 + getService(Ci.nsIRDFContainerUtils); 1.321 + if (!cu.IsContainer(ds, updates)) 1.322 + throw Components.Exception("Updates property was not an RDF container"); 1.323 + 1.324 + let results = []; 1.325 + let ctr = Cc["@mozilla.org/rdf/container;1"]. 1.326 + createInstance(Ci.nsIRDFContainer); 1.327 + ctr.Init(ds, updates); 1.328 + let items = ctr.GetElements(); 1.329 + while (items.hasMoreElements()) { 1.330 + let item = items.getNext().QueryInterface(Ci.nsIRDFResource); 1.331 + let version = getProperty(ds, item, "version"); 1.332 + if (!version) { 1.333 + logger.warn("Update manifest is missing a required version property."); 1.334 + continue; 1.335 + } 1.336 + 1.337 + logger.debug("Found an update entry for " + aId + " version " + version); 1.338 + 1.339 + let targetApps = ds.GetTargets(item, EM_R("targetApplication"), true); 1.340 + while (targetApps.hasMoreElements()) { 1.341 + let targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource); 1.342 + 1.343 + let appEntry = {}; 1.344 + try { 1.345 + appEntry.id = getRequiredProperty(ds, targetApp, "id"); 1.346 + appEntry.minVersion = getRequiredProperty(ds, targetApp, "minVersion"); 1.347 + appEntry.maxVersion = getRequiredProperty(ds, targetApp, "maxVersion"); 1.348 + } 1.349 + catch (e) { 1.350 + logger.warn(e); 1.351 + continue; 1.352 + } 1.353 + 1.354 + let result = { 1.355 + id: aId, 1.356 + version: version, 1.357 + updateURL: getProperty(ds, targetApp, "updateLink"), 1.358 + updateHash: getProperty(ds, targetApp, "updateHash"), 1.359 + updateInfoURL: getProperty(ds, targetApp, "updateInfoURL"), 1.360 + strictCompatibility: getProperty(ds, targetApp, "strictCompatibility") == "true", 1.361 + targetApplications: [appEntry] 1.362 + }; 1.363 + 1.364 + if (result.updateURL && AddonManager.checkUpdateSecurity && 1.365 + result.updateURL.substring(0, 6) != "https:" && 1.366 + (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) { 1.367 + logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" + 1.368 + " by a strong enough hash (needs to be sha1 or stronger)."); 1.369 + delete result.updateURL; 1.370 + delete result.updateHash; 1.371 + } 1.372 + results.push(result); 1.373 + } 1.374 + } 1.375 + return results; 1.376 +} 1.377 + 1.378 +/** 1.379 + * Starts downloading an update manifest and then passes it to an appropriate 1.380 + * parser to convert to an array of update objects 1.381 + * 1.382 + * @param aId 1.383 + * The ID of the add-on being checked for updates 1.384 + * @param aUpdateKey 1.385 + * An optional update key for the add-on 1.386 + * @param aUrl 1.387 + * The URL of the update manifest 1.388 + * @param aObserver 1.389 + * An observer to pass results to 1.390 + */ 1.391 +function UpdateParser(aId, aUpdateKey, aUrl, aObserver) { 1.392 + this.id = aId; 1.393 + this.updateKey = aUpdateKey; 1.394 + this.observer = aObserver; 1.395 + this.url = aUrl; 1.396 + 1.397 + let requireBuiltIn = true; 1.398 + try { 1.399 + requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS); 1.400 + } 1.401 + catch (e) { 1.402 + } 1.403 + 1.404 + logger.debug("Requesting " + aUrl); 1.405 + try { 1.406 + this.request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. 1.407 + createInstance(Ci.nsIXMLHttpRequest); 1.408 + this.request.open("GET", this.url, true); 1.409 + this.request.channel.notificationCallbacks = new CertUtils.BadCertHandler(!requireBuiltIn); 1.410 + this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; 1.411 + // Prevent the request from writing to cache. 1.412 + this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; 1.413 + this.request.overrideMimeType("text/xml"); 1.414 + this.request.timeout = TIMEOUT; 1.415 + var self = this; 1.416 + this.request.addEventListener("load", function loadEventListener(event) { self.onLoad() }, false); 1.417 + this.request.addEventListener("error", function errorEventListener(event) { self.onError() }, false); 1.418 + this.request.addEventListener("timeout", function timeoutEventListener(event) { self.onTimeout() }, false); 1.419 + this.request.send(null); 1.420 + } 1.421 + catch (e) { 1.422 + logger.error("Failed to request update manifest", e); 1.423 + } 1.424 +} 1.425 + 1.426 +UpdateParser.prototype = { 1.427 + id: null, 1.428 + updateKey: null, 1.429 + observer: null, 1.430 + request: null, 1.431 + url: null, 1.432 + 1.433 + /** 1.434 + * Called when the manifest has been successfully loaded. 1.435 + */ 1.436 + onLoad: function UP_onLoad() { 1.437 + let request = this.request; 1.438 + this.request = null; 1.439 + 1.440 + let requireBuiltIn = true; 1.441 + try { 1.442 + requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS); 1.443 + } 1.444 + catch (e) { 1.445 + } 1.446 + 1.447 + try { 1.448 + CertUtils.checkCert(request.channel, !requireBuiltIn); 1.449 + } 1.450 + catch (e) { 1.451 + this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR); 1.452 + return; 1.453 + } 1.454 + 1.455 + if (!Components.isSuccessCode(request.status)) { 1.456 + logger.warn("Request failed: " + this.url + " - " + request.status); 1.457 + this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR); 1.458 + return; 1.459 + } 1.460 + 1.461 + let channel = request.channel; 1.462 + if (channel instanceof Ci.nsIHttpChannel && !channel.requestSucceeded) { 1.463 + logger.warn("Request failed: " + this.url + " - " + channel.responseStatus + 1.464 + ": " + channel.responseStatusText); 1.465 + this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR); 1.466 + return; 1.467 + } 1.468 + 1.469 + let xml = request.responseXML; 1.470 + if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) { 1.471 + logger.warn("Update manifest was not valid XML"); 1.472 + this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); 1.473 + return; 1.474 + } 1.475 + 1.476 + // We currently only know about RDF update manifests 1.477 + if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) { 1.478 + let results = null; 1.479 + 1.480 + try { 1.481 + results = parseRDFManifest(this.id, this.updateKey, request); 1.482 + } 1.483 + catch (e) { 1.484 + logger.warn(e); 1.485 + this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR); 1.486 + return; 1.487 + } 1.488 + if ("onUpdateCheckComplete" in this.observer) { 1.489 + try { 1.490 + this.observer.onUpdateCheckComplete(results); 1.491 + } 1.492 + catch (e) { 1.493 + logger.warn("onUpdateCheckComplete notification failed", e); 1.494 + } 1.495 + } 1.496 + return; 1.497 + } 1.498 + 1.499 + logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI); 1.500 + this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT); 1.501 + }, 1.502 + 1.503 + /** 1.504 + * Called when the request times out 1.505 + */ 1.506 + onTimeout: function() { 1.507 + this.request = null; 1.508 + logger.warn("Request for " + this.url + " timed out"); 1.509 + this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT); 1.510 + }, 1.511 + 1.512 + /** 1.513 + * Called when the manifest failed to load. 1.514 + */ 1.515 + onError: function UP_onError() { 1.516 + if (!Components.isSuccessCode(this.request.status)) { 1.517 + logger.warn("Request failed: " + this.url + " - " + this.request.status); 1.518 + } 1.519 + else if (this.request.channel instanceof Ci.nsIHttpChannel) { 1.520 + try { 1.521 + if (this.request.channel.requestSucceeded) { 1.522 + logger.warn("Request failed: " + this.url + " - " + 1.523 + this.request.channel.responseStatus + ": " + 1.524 + this.request.channel.responseStatusText); 1.525 + } 1.526 + } 1.527 + catch (e) { 1.528 + logger.warn("HTTP Request failed for an unknown reason"); 1.529 + } 1.530 + } 1.531 + else { 1.532 + logger.warn("Request failed for an unknown reason"); 1.533 + } 1.534 + 1.535 + this.request = null; 1.536 + 1.537 + this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR); 1.538 + }, 1.539 + 1.540 + /** 1.541 + * Helper method to notify the observer that an error occured. 1.542 + */ 1.543 + notifyError: function UP_notifyError(aStatus) { 1.544 + if ("onUpdateCheckError" in this.observer) { 1.545 + try { 1.546 + this.observer.onUpdateCheckError(aStatus); 1.547 + } 1.548 + catch (e) { 1.549 + logger.warn("onUpdateCheckError notification failed", e); 1.550 + } 1.551 + } 1.552 + }, 1.553 + 1.554 + /** 1.555 + * Called to cancel an in-progress update check. 1.556 + */ 1.557 + cancel: function UP_cancel() { 1.558 + this.request.abort(); 1.559 + this.request = null; 1.560 + this.notifyError(AddonUpdateChecker.ERROR_CANCELLED); 1.561 + } 1.562 +}; 1.563 + 1.564 +/** 1.565 + * Tests if an update matches a version of the application or platform 1.566 + * 1.567 + * @param aUpdate 1.568 + * The available update 1.569 + * @param aAppVersion 1.570 + * The application version to use 1.571 + * @param aPlatformVersion 1.572 + * The platform version to use 1.573 + * @param aIgnoreMaxVersion 1.574 + * Ignore maxVersion when testing if an update matches. Optional. 1.575 + * @param aIgnoreStrictCompat 1.576 + * Ignore strictCompatibility when testing if an update matches. Optional. 1.577 + * @param aCompatOverrides 1.578 + * AddonCompatibilityOverride objects to match against. Optional. 1.579 + * @return true if the update is compatible with the application/platform 1.580 + */ 1.581 +function matchesVersions(aUpdate, aAppVersion, aPlatformVersion, 1.582 + aIgnoreMaxVersion, aIgnoreStrictCompat, 1.583 + aCompatOverrides) { 1.584 + if (aCompatOverrides) { 1.585 + let override = AddonRepository.findMatchingCompatOverride(aUpdate.version, 1.586 + aCompatOverrides, 1.587 + aAppVersion, 1.588 + aPlatformVersion); 1.589 + if (override && override.type == "incompatible") 1.590 + return false; 1.591 + } 1.592 + 1.593 + if (aUpdate.strictCompatibility && !aIgnoreStrictCompat) 1.594 + aIgnoreMaxVersion = false; 1.595 + 1.596 + let result = false; 1.597 + for (let app of aUpdate.targetApplications) { 1.598 + if (app.id == Services.appinfo.ID) { 1.599 + return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) && 1.600 + (aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0)); 1.601 + } 1.602 + if (app.id == TOOLKIT_ID) { 1.603 + result = (Services.vc.compare(aPlatformVersion, app.minVersion) >= 0) && 1.604 + (aIgnoreMaxVersion || (Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0)); 1.605 + } 1.606 + } 1.607 + return result; 1.608 +} 1.609 + 1.610 +this.AddonUpdateChecker = { 1.611 + // These must be kept in sync with AddonManager 1.612 + // The update check timed out 1.613 + ERROR_TIMEOUT: -1, 1.614 + // There was an error while downloading the update information. 1.615 + ERROR_DOWNLOAD_ERROR: -2, 1.616 + // The update information was malformed in some way. 1.617 + ERROR_PARSE_ERROR: -3, 1.618 + // The update information was not in any known format. 1.619 + ERROR_UNKNOWN_FORMAT: -4, 1.620 + // The update information was not correctly signed or there was an SSL error. 1.621 + ERROR_SECURITY_ERROR: -5, 1.622 + // The update was cancelled 1.623 + ERROR_CANCELLED: -6, 1.624 + 1.625 + /** 1.626 + * Retrieves the best matching compatibility update for the application from 1.627 + * a list of available update objects. 1.628 + * 1.629 + * @param aUpdates 1.630 + * An array of update objects 1.631 + * @param aVersion 1.632 + * The version of the add-on to get new compatibility information for 1.633 + * @param aIgnoreCompatibility 1.634 + * An optional parameter to get the first compatibility update that 1.635 + * is compatible with any version of the application or toolkit 1.636 + * @param aAppVersion 1.637 + * The version of the application or null to use the current version 1.638 + * @param aPlatformVersion 1.639 + * The version of the platform or null to use the current version 1.640 + * @param aIgnoreMaxVersion 1.641 + * Ignore maxVersion when testing if an update matches. Optional. 1.642 + * @param aIgnoreStrictCompat 1.643 + * Ignore strictCompatibility when testing if an update matches. Optional. 1.644 + * @return an update object if one matches or null if not 1.645 + */ 1.646 + getCompatibilityUpdate: function AUC_getCompatibilityUpdate(aUpdates, aVersion, 1.647 + aIgnoreCompatibility, 1.648 + aAppVersion, 1.649 + aPlatformVersion, 1.650 + aIgnoreMaxVersion, 1.651 + aIgnoreStrictCompat) { 1.652 + if (!aAppVersion) 1.653 + aAppVersion = Services.appinfo.version; 1.654 + if (!aPlatformVersion) 1.655 + aPlatformVersion = Services.appinfo.platformVersion; 1.656 + 1.657 + for (let update of aUpdates) { 1.658 + if (Services.vc.compare(update.version, aVersion) == 0) { 1.659 + if (aIgnoreCompatibility) { 1.660 + for (let targetApp of update.targetApplications) { 1.661 + let id = targetApp.id; 1.662 + if (id == Services.appinfo.ID || id == TOOLKIT_ID) 1.663 + return update; 1.664 + } 1.665 + } 1.666 + else if (matchesVersions(update, aAppVersion, aPlatformVersion, 1.667 + aIgnoreMaxVersion, aIgnoreStrictCompat)) { 1.668 + return update; 1.669 + } 1.670 + } 1.671 + } 1.672 + return null; 1.673 + }, 1.674 + 1.675 + /** 1.676 + * Returns the newest available update from a list of update objects. 1.677 + * 1.678 + * @param aUpdates 1.679 + * An array of update objects 1.680 + * @param aAppVersion 1.681 + * The version of the application or null to use the current version 1.682 + * @param aPlatformVersion 1.683 + * The version of the platform or null to use the current version 1.684 + * @param aIgnoreMaxVersion 1.685 + * When determining compatible updates, ignore maxVersion. Optional. 1.686 + * @param aIgnoreStrictCompat 1.687 + * When determining compatible updates, ignore strictCompatibility. Optional. 1.688 + * @param aCompatOverrides 1.689 + * Array of AddonCompatibilityOverride to take into account. Optional. 1.690 + * @return an update object if one matches or null if not 1.691 + */ 1.692 + getNewestCompatibleUpdate: function AUC_getNewestCompatibleUpdate(aUpdates, 1.693 + aAppVersion, 1.694 + aPlatformVersion, 1.695 + aIgnoreMaxVersion, 1.696 + aIgnoreStrictCompat, 1.697 + aCompatOverrides) { 1.698 + if (!aAppVersion) 1.699 + aAppVersion = Services.appinfo.version; 1.700 + if (!aPlatformVersion) 1.701 + aPlatformVersion = Services.appinfo.platformVersion; 1.702 + 1.703 + let blocklist = Cc["@mozilla.org/extensions/blocklist;1"]. 1.704 + getService(Ci.nsIBlocklistService); 1.705 + 1.706 + let newest = null; 1.707 + for (let update of aUpdates) { 1.708 + if (!update.updateURL) 1.709 + continue; 1.710 + let state = blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion); 1.711 + if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) 1.712 + continue; 1.713 + if ((newest == null || (Services.vc.compare(newest.version, update.version) < 0)) && 1.714 + matchesVersions(update, aAppVersion, aPlatformVersion, 1.715 + aIgnoreMaxVersion, aIgnoreStrictCompat, 1.716 + aCompatOverrides)) { 1.717 + newest = update; 1.718 + } 1.719 + } 1.720 + return newest; 1.721 + }, 1.722 + 1.723 + /** 1.724 + * Starts an update check. 1.725 + * 1.726 + * @param aId 1.727 + * The ID of the add-on being checked for updates 1.728 + * @param aUpdateKey 1.729 + * An optional update key for the add-on 1.730 + * @param aUrl 1.731 + * The URL of the add-on's update manifest 1.732 + * @param aObserver 1.733 + * An observer to notify of results 1.734 + * @return UpdateParser so that the caller can use UpdateParser.cancel() to shut 1.735 + * down in-progress update requests 1.736 + */ 1.737 + checkForUpdates: function AUC_checkForUpdates(aId, aUpdateKey, aUrl, 1.738 + aObserver) { 1.739 + return new UpdateParser(aId, aUpdateKey, aUrl, aObserver); 1.740 + } 1.741 +};