toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 /**
michael@0 6 * The AddonUpdateChecker is responsible for retrieving the update information
michael@0 7 * from an add-on's remote update manifest.
michael@0 8 */
michael@0 9
michael@0 10 "use strict";
michael@0 11
michael@0 12 const Cc = Components.classes;
michael@0 13 const Ci = Components.interfaces;
michael@0 14 const Cu = Components.utils;
michael@0 15
michael@0 16 this.EXPORTED_SYMBOLS = [ "AddonUpdateChecker" ];
michael@0 17
michael@0 18 const TIMEOUT = 60 * 1000;
michael@0 19 const PREFIX_NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
michael@0 20 const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
michael@0 21 const PREFIX_ITEM = "urn:mozilla:item:";
michael@0 22 const PREFIX_EXTENSION = "urn:mozilla:extension:";
michael@0 23 const PREFIX_THEME = "urn:mozilla:theme:";
michael@0 24 const TOOLKIT_ID = "toolkit@mozilla.org"
michael@0 25 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
michael@0 26
michael@0 27 const PREF_UPDATE_REQUIREBUILTINCERTS = "extensions.update.requireBuiltInCerts";
michael@0 28
michael@0 29 Components.utils.import("resource://gre/modules/Services.jsm");
michael@0 30 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 31
michael@0 32 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
michael@0 33 "resource://gre/modules/AddonManager.jsm");
michael@0 34 XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
michael@0 35 "resource://gre/modules/addons/AddonRepository.jsm");
michael@0 36
michael@0 37 // Shared code for suppressing bad cert dialogs.
michael@0 38 XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() {
michael@0 39 let certUtils = {};
michael@0 40 Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
michael@0 41 return certUtils;
michael@0 42 });
michael@0 43
michael@0 44 var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
michael@0 45 getService(Ci.nsIRDFService);
michael@0 46
michael@0 47 Cu.import("resource://gre/modules/Log.jsm");
michael@0 48 const LOGGER_ID = "addons.update-checker";
michael@0 49
michael@0 50 // Create a new logger for use by the Addons Update Checker
michael@0 51 // (Requires AddonManager.jsm)
michael@0 52 let logger = Log.repository.getLogger(LOGGER_ID);
michael@0 53
michael@0 54 /**
michael@0 55 * A serialisation method for RDF data that produces an identical string
michael@0 56 * for matching RDF graphs.
michael@0 57 * The serialisation is not complete, only assertions stemming from a given
michael@0 58 * resource are included, multiple references to the same resource are not
michael@0 59 * permitted, and the RDF prolog and epilog are not included.
michael@0 60 * RDF Blob and Date literals are not supported.
michael@0 61 */
michael@0 62 function RDFSerializer() {
michael@0 63 this.cUtils = Cc["@mozilla.org/rdf/container-utils;1"].
michael@0 64 getService(Ci.nsIRDFContainerUtils);
michael@0 65 this.resources = [];
michael@0 66 }
michael@0 67
michael@0 68 RDFSerializer.prototype = {
michael@0 69 INDENT: " ", // The indent used for pretty-printing
michael@0 70 resources: null, // Array of the resources that have been found
michael@0 71
michael@0 72 /**
michael@0 73 * Escapes characters from a string that should not appear in XML.
michael@0 74 *
michael@0 75 * @param aString
michael@0 76 * The string to be escaped
michael@0 77 * @return a string with all characters invalid in XML character data
michael@0 78 * converted to entity references.
michael@0 79 */
michael@0 80 escapeEntities: function RDFS_escapeEntities(aString) {
michael@0 81 aString = aString.replace(/&/g, "&amp;");
michael@0 82 aString = aString.replace(/</g, "&lt;");
michael@0 83 aString = aString.replace(/>/g, "&gt;");
michael@0 84 return aString.replace(/"/g, "&quot;");
michael@0 85 },
michael@0 86
michael@0 87 /**
michael@0 88 * Serializes all the elements of an RDF container.
michael@0 89 *
michael@0 90 * @param aDs
michael@0 91 * The RDF datasource
michael@0 92 * @param aContainer
michael@0 93 * The RDF container to output the child elements of
michael@0 94 * @param aIndent
michael@0 95 * The current level of indent for pretty-printing
michael@0 96 * @return a string containing the serialized elements.
michael@0 97 */
michael@0 98 serializeContainerItems: function RDFS_serializeContainerItems(aDs, aContainer,
michael@0 99 aIndent) {
michael@0 100 var result = "";
michael@0 101 var items = aContainer.GetElements();
michael@0 102 while (items.hasMoreElements()) {
michael@0 103 var item = items.getNext().QueryInterface(Ci.nsIRDFResource);
michael@0 104 result += aIndent + "<RDF:li>\n"
michael@0 105 result += this.serializeResource(aDs, item, aIndent + this.INDENT);
michael@0 106 result += aIndent + "</RDF:li>\n"
michael@0 107 }
michael@0 108 return result;
michael@0 109 },
michael@0 110
michael@0 111 /**
michael@0 112 * Serializes all em:* (see EM_NS) properties of an RDF resource except for
michael@0 113 * the em:signature property. As this serialization is to be compared against
michael@0 114 * the manifest signature it cannot contain the em:signature property itself.
michael@0 115 *
michael@0 116 * @param aDs
michael@0 117 * The RDF datasource
michael@0 118 * @param aResource
michael@0 119 * The RDF resource that contains the properties to serialize
michael@0 120 * @param aIndent
michael@0 121 * The current level of indent for pretty-printing
michael@0 122 * @return a string containing the serialized properties.
michael@0 123 * @throws if the resource contains a property that cannot be serialized
michael@0 124 */
michael@0 125 serializeResourceProperties: function RDFS_serializeResourceProperties(aDs,
michael@0 126 aResource,
michael@0 127 aIndent) {
michael@0 128 var result = "";
michael@0 129 var items = [];
michael@0 130 var arcs = aDs.ArcLabelsOut(aResource);
michael@0 131 while (arcs.hasMoreElements()) {
michael@0 132 var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource);
michael@0 133 if (arc.ValueUTF8.substring(0, PREFIX_NS_EM.length) != PREFIX_NS_EM)
michael@0 134 continue;
michael@0 135 var prop = arc.ValueUTF8.substring(PREFIX_NS_EM.length);
michael@0 136 if (prop == "signature")
michael@0 137 continue;
michael@0 138
michael@0 139 var targets = aDs.GetTargets(aResource, arc, true);
michael@0 140 while (targets.hasMoreElements()) {
michael@0 141 var target = targets.getNext();
michael@0 142 if (target instanceof Ci.nsIRDFResource) {
michael@0 143 var item = aIndent + "<em:" + prop + ">\n";
michael@0 144 item += this.serializeResource(aDs, target, aIndent + this.INDENT);
michael@0 145 item += aIndent + "</em:" + prop + ">\n";
michael@0 146 items.push(item);
michael@0 147 }
michael@0 148 else if (target instanceof Ci.nsIRDFLiteral) {
michael@0 149 items.push(aIndent + "<em:" + prop + ">" +
michael@0 150 this.escapeEntities(target.Value) + "</em:" + prop + ">\n");
michael@0 151 }
michael@0 152 else if (target instanceof Ci.nsIRDFInt) {
michael@0 153 items.push(aIndent + "<em:" + prop + " NC:parseType=\"Integer\">" +
michael@0 154 target.Value + "</em:" + prop + ">\n");
michael@0 155 }
michael@0 156 else {
michael@0 157 throw Components.Exception("Cannot serialize unknown literal type");
michael@0 158 }
michael@0 159 }
michael@0 160 }
michael@0 161 items.sort();
michael@0 162 result += items.join("");
michael@0 163 return result;
michael@0 164 },
michael@0 165
michael@0 166 /**
michael@0 167 * Recursively serializes an RDF resource and all resources it links to.
michael@0 168 * This will only output EM_NS properties and will ignore any em:signature
michael@0 169 * property.
michael@0 170 *
michael@0 171 * @param aDs
michael@0 172 * The RDF datasource
michael@0 173 * @param aResource
michael@0 174 * The RDF resource to serialize
michael@0 175 * @param aIndent
michael@0 176 * The current level of indent for pretty-printing. If undefined no
michael@0 177 * indent will be added
michael@0 178 * @return a string containing the serialized resource.
michael@0 179 * @throws if the RDF data contains multiple references to the same resource.
michael@0 180 */
michael@0 181 serializeResource: function RDFS_serializeResource(aDs, aResource, aIndent) {
michael@0 182 if (this.resources.indexOf(aResource) != -1 ) {
michael@0 183 // We cannot output multiple references to the same resource.
michael@0 184 throw Components.Exception("Cannot serialize multiple references to " + aResource.Value);
michael@0 185 }
michael@0 186 if (aIndent === undefined)
michael@0 187 aIndent = "";
michael@0 188
michael@0 189 this.resources.push(aResource);
michael@0 190 var container = null;
michael@0 191 var type = "Description";
michael@0 192 if (this.cUtils.IsSeq(aDs, aResource)) {
michael@0 193 type = "Seq";
michael@0 194 container = this.cUtils.MakeSeq(aDs, aResource);
michael@0 195 }
michael@0 196 else if (this.cUtils.IsAlt(aDs, aResource)) {
michael@0 197 type = "Alt";
michael@0 198 container = this.cUtils.MakeAlt(aDs, aResource);
michael@0 199 }
michael@0 200 else if (this.cUtils.IsBag(aDs, aResource)) {
michael@0 201 type = "Bag";
michael@0 202 container = this.cUtils.MakeBag(aDs, aResource);
michael@0 203 }
michael@0 204
michael@0 205 var result = aIndent + "<RDF:" + type;
michael@0 206 if (!gRDF.IsAnonymousResource(aResource))
michael@0 207 result += " about=\"" + this.escapeEntities(aResource.ValueUTF8) + "\"";
michael@0 208 result += ">\n";
michael@0 209
michael@0 210 if (container)
michael@0 211 result += this.serializeContainerItems(aDs, container, aIndent + this.INDENT);
michael@0 212
michael@0 213 result += this.serializeResourceProperties(aDs, aResource, aIndent + this.INDENT);
michael@0 214
michael@0 215 result += aIndent + "</RDF:" + type + ">\n";
michael@0 216 return result;
michael@0 217 }
michael@0 218 }
michael@0 219
michael@0 220 /**
michael@0 221 * Parses an RDF style update manifest into an array of update objects.
michael@0 222 *
michael@0 223 * @param aId
michael@0 224 * The ID of the add-on being checked for updates
michael@0 225 * @param aUpdateKey
michael@0 226 * An optional update key for the add-on
michael@0 227 * @param aRequest
michael@0 228 * The XMLHttpRequest that has retrieved the update manifest
michael@0 229 * @return an array of update objects
michael@0 230 * @throws if the update manifest is invalid in any way
michael@0 231 */
michael@0 232 function parseRDFManifest(aId, aUpdateKey, aRequest) {
michael@0 233 function EM_R(aProp) {
michael@0 234 return gRDF.GetResource(PREFIX_NS_EM + aProp);
michael@0 235 }
michael@0 236
michael@0 237 function getValue(aLiteral) {
michael@0 238 if (aLiteral instanceof Ci.nsIRDFLiteral)
michael@0 239 return aLiteral.Value;
michael@0 240 if (aLiteral instanceof Ci.nsIRDFResource)
michael@0 241 return aLiteral.Value;
michael@0 242 if (aLiteral instanceof Ci.nsIRDFInt)
michael@0 243 return aLiteral.Value;
michael@0 244 return null;
michael@0 245 }
michael@0 246
michael@0 247 function getProperty(aDs, aSource, aProperty) {
michael@0 248 return getValue(aDs.GetTarget(aSource, EM_R(aProperty), true));
michael@0 249 }
michael@0 250
michael@0 251 function getRequiredProperty(aDs, aSource, aProperty) {
michael@0 252 let value = getProperty(aDs, aSource, aProperty);
michael@0 253 if (!value)
michael@0 254 throw Components.Exception("Update manifest is missing a required " + aProperty + " property.");
michael@0 255 return value;
michael@0 256 }
michael@0 257
michael@0 258 let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
michael@0 259 createInstance(Ci.nsIRDFXMLParser);
michael@0 260 let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
michael@0 261 createInstance(Ci.nsIRDFDataSource);
michael@0 262 rdfParser.parseString(ds, aRequest.channel.URI, aRequest.responseText);
michael@0 263
michael@0 264 // Differentiating between add-on types is deprecated
michael@0 265 let extensionRes = gRDF.GetResource(PREFIX_EXTENSION + aId);
michael@0 266 let themeRes = gRDF.GetResource(PREFIX_THEME + aId);
michael@0 267 let itemRes = gRDF.GetResource(PREFIX_ITEM + aId);
michael@0 268 let addonRes = ds.ArcLabelsOut(extensionRes).hasMoreElements() ? extensionRes
michael@0 269 : ds.ArcLabelsOut(themeRes).hasMoreElements() ? themeRes
michael@0 270 : itemRes;
michael@0 271
michael@0 272 // If we have an update key then the update manifest must be signed
michael@0 273 if (aUpdateKey) {
michael@0 274 let signature = getProperty(ds, addonRes, "signature");
michael@0 275 if (!signature)
michael@0 276 throw Components.Exception("Update manifest for " + aId + " does not contain a required signature");
michael@0 277 let serializer = new RDFSerializer();
michael@0 278 let updateString = null;
michael@0 279
michael@0 280 try {
michael@0 281 updateString = serializer.serializeResource(ds, addonRes);
michael@0 282 }
michael@0 283 catch (e) {
michael@0 284 throw Components.Exception("Failed to generate signed string for " + aId + ". Serializer threw " + e,
michael@0 285 e.result);
michael@0 286 }
michael@0 287
michael@0 288 let result = false;
michael@0 289
michael@0 290 try {
michael@0 291 let verifier = Cc["@mozilla.org/security/datasignatureverifier;1"].
michael@0 292 getService(Ci.nsIDataSignatureVerifier);
michael@0 293 result = verifier.verifyData(updateString, signature, aUpdateKey);
michael@0 294 }
michael@0 295 catch (e) {
michael@0 296 throw Components.Exception("The signature or updateKey for " + aId + " is malformed." +
michael@0 297 "Verifier threw " + e, e.result);
michael@0 298 }
michael@0 299
michael@0 300 if (!result)
michael@0 301 throw Components.Exception("The signature for " + aId + " was not created by the add-on's updateKey");
michael@0 302 }
michael@0 303
michael@0 304 let updates = ds.GetTarget(addonRes, EM_R("updates"), true);
michael@0 305
michael@0 306 // A missing updates property doesn't count as a failure, just as no avialable
michael@0 307 // update information
michael@0 308 if (!updates) {
michael@0 309 logger.warn("Update manifest for " + aId + " did not contain an updates property");
michael@0 310 return [];
michael@0 311 }
michael@0 312
michael@0 313 if (!(updates instanceof Ci.nsIRDFResource))
michael@0 314 throw Components.Exception("Missing updates property for " + addonRes.Value);
michael@0 315
michael@0 316 let cu = Cc["@mozilla.org/rdf/container-utils;1"].
michael@0 317 getService(Ci.nsIRDFContainerUtils);
michael@0 318 if (!cu.IsContainer(ds, updates))
michael@0 319 throw Components.Exception("Updates property was not an RDF container");
michael@0 320
michael@0 321 let results = [];
michael@0 322 let ctr = Cc["@mozilla.org/rdf/container;1"].
michael@0 323 createInstance(Ci.nsIRDFContainer);
michael@0 324 ctr.Init(ds, updates);
michael@0 325 let items = ctr.GetElements();
michael@0 326 while (items.hasMoreElements()) {
michael@0 327 let item = items.getNext().QueryInterface(Ci.nsIRDFResource);
michael@0 328 let version = getProperty(ds, item, "version");
michael@0 329 if (!version) {
michael@0 330 logger.warn("Update manifest is missing a required version property.");
michael@0 331 continue;
michael@0 332 }
michael@0 333
michael@0 334 logger.debug("Found an update entry for " + aId + " version " + version);
michael@0 335
michael@0 336 let targetApps = ds.GetTargets(item, EM_R("targetApplication"), true);
michael@0 337 while (targetApps.hasMoreElements()) {
michael@0 338 let targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource);
michael@0 339
michael@0 340 let appEntry = {};
michael@0 341 try {
michael@0 342 appEntry.id = getRequiredProperty(ds, targetApp, "id");
michael@0 343 appEntry.minVersion = getRequiredProperty(ds, targetApp, "minVersion");
michael@0 344 appEntry.maxVersion = getRequiredProperty(ds, targetApp, "maxVersion");
michael@0 345 }
michael@0 346 catch (e) {
michael@0 347 logger.warn(e);
michael@0 348 continue;
michael@0 349 }
michael@0 350
michael@0 351 let result = {
michael@0 352 id: aId,
michael@0 353 version: version,
michael@0 354 updateURL: getProperty(ds, targetApp, "updateLink"),
michael@0 355 updateHash: getProperty(ds, targetApp, "updateHash"),
michael@0 356 updateInfoURL: getProperty(ds, targetApp, "updateInfoURL"),
michael@0 357 strictCompatibility: getProperty(ds, targetApp, "strictCompatibility") == "true",
michael@0 358 targetApplications: [appEntry]
michael@0 359 };
michael@0 360
michael@0 361 if (result.updateURL && AddonManager.checkUpdateSecurity &&
michael@0 362 result.updateURL.substring(0, 6) != "https:" &&
michael@0 363 (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) {
michael@0 364 logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" +
michael@0 365 " by a strong enough hash (needs to be sha1 or stronger).");
michael@0 366 delete result.updateURL;
michael@0 367 delete result.updateHash;
michael@0 368 }
michael@0 369 results.push(result);
michael@0 370 }
michael@0 371 }
michael@0 372 return results;
michael@0 373 }
michael@0 374
michael@0 375 /**
michael@0 376 * Starts downloading an update manifest and then passes it to an appropriate
michael@0 377 * parser to convert to an array of update objects
michael@0 378 *
michael@0 379 * @param aId
michael@0 380 * The ID of the add-on being checked for updates
michael@0 381 * @param aUpdateKey
michael@0 382 * An optional update key for the add-on
michael@0 383 * @param aUrl
michael@0 384 * The URL of the update manifest
michael@0 385 * @param aObserver
michael@0 386 * An observer to pass results to
michael@0 387 */
michael@0 388 function UpdateParser(aId, aUpdateKey, aUrl, aObserver) {
michael@0 389 this.id = aId;
michael@0 390 this.updateKey = aUpdateKey;
michael@0 391 this.observer = aObserver;
michael@0 392 this.url = aUrl;
michael@0 393
michael@0 394 let requireBuiltIn = true;
michael@0 395 try {
michael@0 396 requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS);
michael@0 397 }
michael@0 398 catch (e) {
michael@0 399 }
michael@0 400
michael@0 401 logger.debug("Requesting " + aUrl);
michael@0 402 try {
michael@0 403 this.request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
michael@0 404 createInstance(Ci.nsIXMLHttpRequest);
michael@0 405 this.request.open("GET", this.url, true);
michael@0 406 this.request.channel.notificationCallbacks = new CertUtils.BadCertHandler(!requireBuiltIn);
michael@0 407 this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
michael@0 408 // Prevent the request from writing to cache.
michael@0 409 this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
michael@0 410 this.request.overrideMimeType("text/xml");
michael@0 411 this.request.timeout = TIMEOUT;
michael@0 412 var self = this;
michael@0 413 this.request.addEventListener("load", function loadEventListener(event) { self.onLoad() }, false);
michael@0 414 this.request.addEventListener("error", function errorEventListener(event) { self.onError() }, false);
michael@0 415 this.request.addEventListener("timeout", function timeoutEventListener(event) { self.onTimeout() }, false);
michael@0 416 this.request.send(null);
michael@0 417 }
michael@0 418 catch (e) {
michael@0 419 logger.error("Failed to request update manifest", e);
michael@0 420 }
michael@0 421 }
michael@0 422
michael@0 423 UpdateParser.prototype = {
michael@0 424 id: null,
michael@0 425 updateKey: null,
michael@0 426 observer: null,
michael@0 427 request: null,
michael@0 428 url: null,
michael@0 429
michael@0 430 /**
michael@0 431 * Called when the manifest has been successfully loaded.
michael@0 432 */
michael@0 433 onLoad: function UP_onLoad() {
michael@0 434 let request = this.request;
michael@0 435 this.request = null;
michael@0 436
michael@0 437 let requireBuiltIn = true;
michael@0 438 try {
michael@0 439 requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS);
michael@0 440 }
michael@0 441 catch (e) {
michael@0 442 }
michael@0 443
michael@0 444 try {
michael@0 445 CertUtils.checkCert(request.channel, !requireBuiltIn);
michael@0 446 }
michael@0 447 catch (e) {
michael@0 448 this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
michael@0 449 return;
michael@0 450 }
michael@0 451
michael@0 452 if (!Components.isSuccessCode(request.status)) {
michael@0 453 logger.warn("Request failed: " + this.url + " - " + request.status);
michael@0 454 this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
michael@0 455 return;
michael@0 456 }
michael@0 457
michael@0 458 let channel = request.channel;
michael@0 459 if (channel instanceof Ci.nsIHttpChannel && !channel.requestSucceeded) {
michael@0 460 logger.warn("Request failed: " + this.url + " - " + channel.responseStatus +
michael@0 461 ": " + channel.responseStatusText);
michael@0 462 this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
michael@0 463 return;
michael@0 464 }
michael@0 465
michael@0 466 let xml = request.responseXML;
michael@0 467 if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) {
michael@0 468 logger.warn("Update manifest was not valid XML");
michael@0 469 this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
michael@0 470 return;
michael@0 471 }
michael@0 472
michael@0 473 // We currently only know about RDF update manifests
michael@0 474 if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) {
michael@0 475 let results = null;
michael@0 476
michael@0 477 try {
michael@0 478 results = parseRDFManifest(this.id, this.updateKey, request);
michael@0 479 }
michael@0 480 catch (e) {
michael@0 481 logger.warn(e);
michael@0 482 this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
michael@0 483 return;
michael@0 484 }
michael@0 485 if ("onUpdateCheckComplete" in this.observer) {
michael@0 486 try {
michael@0 487 this.observer.onUpdateCheckComplete(results);
michael@0 488 }
michael@0 489 catch (e) {
michael@0 490 logger.warn("onUpdateCheckComplete notification failed", e);
michael@0 491 }
michael@0 492 }
michael@0 493 return;
michael@0 494 }
michael@0 495
michael@0 496 logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI);
michael@0 497 this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT);
michael@0 498 },
michael@0 499
michael@0 500 /**
michael@0 501 * Called when the request times out
michael@0 502 */
michael@0 503 onTimeout: function() {
michael@0 504 this.request = null;
michael@0 505 logger.warn("Request for " + this.url + " timed out");
michael@0 506 this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT);
michael@0 507 },
michael@0 508
michael@0 509 /**
michael@0 510 * Called when the manifest failed to load.
michael@0 511 */
michael@0 512 onError: function UP_onError() {
michael@0 513 if (!Components.isSuccessCode(this.request.status)) {
michael@0 514 logger.warn("Request failed: " + this.url + " - " + this.request.status);
michael@0 515 }
michael@0 516 else if (this.request.channel instanceof Ci.nsIHttpChannel) {
michael@0 517 try {
michael@0 518 if (this.request.channel.requestSucceeded) {
michael@0 519 logger.warn("Request failed: " + this.url + " - " +
michael@0 520 this.request.channel.responseStatus + ": " +
michael@0 521 this.request.channel.responseStatusText);
michael@0 522 }
michael@0 523 }
michael@0 524 catch (e) {
michael@0 525 logger.warn("HTTP Request failed for an unknown reason");
michael@0 526 }
michael@0 527 }
michael@0 528 else {
michael@0 529 logger.warn("Request failed for an unknown reason");
michael@0 530 }
michael@0 531
michael@0 532 this.request = null;
michael@0 533
michael@0 534 this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
michael@0 535 },
michael@0 536
michael@0 537 /**
michael@0 538 * Helper method to notify the observer that an error occured.
michael@0 539 */
michael@0 540 notifyError: function UP_notifyError(aStatus) {
michael@0 541 if ("onUpdateCheckError" in this.observer) {
michael@0 542 try {
michael@0 543 this.observer.onUpdateCheckError(aStatus);
michael@0 544 }
michael@0 545 catch (e) {
michael@0 546 logger.warn("onUpdateCheckError notification failed", e);
michael@0 547 }
michael@0 548 }
michael@0 549 },
michael@0 550
michael@0 551 /**
michael@0 552 * Called to cancel an in-progress update check.
michael@0 553 */
michael@0 554 cancel: function UP_cancel() {
michael@0 555 this.request.abort();
michael@0 556 this.request = null;
michael@0 557 this.notifyError(AddonUpdateChecker.ERROR_CANCELLED);
michael@0 558 }
michael@0 559 };
michael@0 560
michael@0 561 /**
michael@0 562 * Tests if an update matches a version of the application or platform
michael@0 563 *
michael@0 564 * @param aUpdate
michael@0 565 * The available update
michael@0 566 * @param aAppVersion
michael@0 567 * The application version to use
michael@0 568 * @param aPlatformVersion
michael@0 569 * The platform version to use
michael@0 570 * @param aIgnoreMaxVersion
michael@0 571 * Ignore maxVersion when testing if an update matches. Optional.
michael@0 572 * @param aIgnoreStrictCompat
michael@0 573 * Ignore strictCompatibility when testing if an update matches. Optional.
michael@0 574 * @param aCompatOverrides
michael@0 575 * AddonCompatibilityOverride objects to match against. Optional.
michael@0 576 * @return true if the update is compatible with the application/platform
michael@0 577 */
michael@0 578 function matchesVersions(aUpdate, aAppVersion, aPlatformVersion,
michael@0 579 aIgnoreMaxVersion, aIgnoreStrictCompat,
michael@0 580 aCompatOverrides) {
michael@0 581 if (aCompatOverrides) {
michael@0 582 let override = AddonRepository.findMatchingCompatOverride(aUpdate.version,
michael@0 583 aCompatOverrides,
michael@0 584 aAppVersion,
michael@0 585 aPlatformVersion);
michael@0 586 if (override && override.type == "incompatible")
michael@0 587 return false;
michael@0 588 }
michael@0 589
michael@0 590 if (aUpdate.strictCompatibility && !aIgnoreStrictCompat)
michael@0 591 aIgnoreMaxVersion = false;
michael@0 592
michael@0 593 let result = false;
michael@0 594 for (let app of aUpdate.targetApplications) {
michael@0 595 if (app.id == Services.appinfo.ID) {
michael@0 596 return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) &&
michael@0 597 (aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0));
michael@0 598 }
michael@0 599 if (app.id == TOOLKIT_ID) {
michael@0 600 result = (Services.vc.compare(aPlatformVersion, app.minVersion) >= 0) &&
michael@0 601 (aIgnoreMaxVersion || (Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0));
michael@0 602 }
michael@0 603 }
michael@0 604 return result;
michael@0 605 }
michael@0 606
michael@0 607 this.AddonUpdateChecker = {
michael@0 608 // These must be kept in sync with AddonManager
michael@0 609 // The update check timed out
michael@0 610 ERROR_TIMEOUT: -1,
michael@0 611 // There was an error while downloading the update information.
michael@0 612 ERROR_DOWNLOAD_ERROR: -2,
michael@0 613 // The update information was malformed in some way.
michael@0 614 ERROR_PARSE_ERROR: -3,
michael@0 615 // The update information was not in any known format.
michael@0 616 ERROR_UNKNOWN_FORMAT: -4,
michael@0 617 // The update information was not correctly signed or there was an SSL error.
michael@0 618 ERROR_SECURITY_ERROR: -5,
michael@0 619 // The update was cancelled
michael@0 620 ERROR_CANCELLED: -6,
michael@0 621
michael@0 622 /**
michael@0 623 * Retrieves the best matching compatibility update for the application from
michael@0 624 * a list of available update objects.
michael@0 625 *
michael@0 626 * @param aUpdates
michael@0 627 * An array of update objects
michael@0 628 * @param aVersion
michael@0 629 * The version of the add-on to get new compatibility information for
michael@0 630 * @param aIgnoreCompatibility
michael@0 631 * An optional parameter to get the first compatibility update that
michael@0 632 * is compatible with any version of the application or toolkit
michael@0 633 * @param aAppVersion
michael@0 634 * The version of the application or null to use the current version
michael@0 635 * @param aPlatformVersion
michael@0 636 * The version of the platform or null to use the current version
michael@0 637 * @param aIgnoreMaxVersion
michael@0 638 * Ignore maxVersion when testing if an update matches. Optional.
michael@0 639 * @param aIgnoreStrictCompat
michael@0 640 * Ignore strictCompatibility when testing if an update matches. Optional.
michael@0 641 * @return an update object if one matches or null if not
michael@0 642 */
michael@0 643 getCompatibilityUpdate: function AUC_getCompatibilityUpdate(aUpdates, aVersion,
michael@0 644 aIgnoreCompatibility,
michael@0 645 aAppVersion,
michael@0 646 aPlatformVersion,
michael@0 647 aIgnoreMaxVersion,
michael@0 648 aIgnoreStrictCompat) {
michael@0 649 if (!aAppVersion)
michael@0 650 aAppVersion = Services.appinfo.version;
michael@0 651 if (!aPlatformVersion)
michael@0 652 aPlatformVersion = Services.appinfo.platformVersion;
michael@0 653
michael@0 654 for (let update of aUpdates) {
michael@0 655 if (Services.vc.compare(update.version, aVersion) == 0) {
michael@0 656 if (aIgnoreCompatibility) {
michael@0 657 for (let targetApp of update.targetApplications) {
michael@0 658 let id = targetApp.id;
michael@0 659 if (id == Services.appinfo.ID || id == TOOLKIT_ID)
michael@0 660 return update;
michael@0 661 }
michael@0 662 }
michael@0 663 else if (matchesVersions(update, aAppVersion, aPlatformVersion,
michael@0 664 aIgnoreMaxVersion, aIgnoreStrictCompat)) {
michael@0 665 return update;
michael@0 666 }
michael@0 667 }
michael@0 668 }
michael@0 669 return null;
michael@0 670 },
michael@0 671
michael@0 672 /**
michael@0 673 * Returns the newest available update from a list of update objects.
michael@0 674 *
michael@0 675 * @param aUpdates
michael@0 676 * An array of update objects
michael@0 677 * @param aAppVersion
michael@0 678 * The version of the application or null to use the current version
michael@0 679 * @param aPlatformVersion
michael@0 680 * The version of the platform or null to use the current version
michael@0 681 * @param aIgnoreMaxVersion
michael@0 682 * When determining compatible updates, ignore maxVersion. Optional.
michael@0 683 * @param aIgnoreStrictCompat
michael@0 684 * When determining compatible updates, ignore strictCompatibility. Optional.
michael@0 685 * @param aCompatOverrides
michael@0 686 * Array of AddonCompatibilityOverride to take into account. Optional.
michael@0 687 * @return an update object if one matches or null if not
michael@0 688 */
michael@0 689 getNewestCompatibleUpdate: function AUC_getNewestCompatibleUpdate(aUpdates,
michael@0 690 aAppVersion,
michael@0 691 aPlatformVersion,
michael@0 692 aIgnoreMaxVersion,
michael@0 693 aIgnoreStrictCompat,
michael@0 694 aCompatOverrides) {
michael@0 695 if (!aAppVersion)
michael@0 696 aAppVersion = Services.appinfo.version;
michael@0 697 if (!aPlatformVersion)
michael@0 698 aPlatformVersion = Services.appinfo.platformVersion;
michael@0 699
michael@0 700 let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
michael@0 701 getService(Ci.nsIBlocklistService);
michael@0 702
michael@0 703 let newest = null;
michael@0 704 for (let update of aUpdates) {
michael@0 705 if (!update.updateURL)
michael@0 706 continue;
michael@0 707 let state = blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion);
michael@0 708 if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
michael@0 709 continue;
michael@0 710 if ((newest == null || (Services.vc.compare(newest.version, update.version) < 0)) &&
michael@0 711 matchesVersions(update, aAppVersion, aPlatformVersion,
michael@0 712 aIgnoreMaxVersion, aIgnoreStrictCompat,
michael@0 713 aCompatOverrides)) {
michael@0 714 newest = update;
michael@0 715 }
michael@0 716 }
michael@0 717 return newest;
michael@0 718 },
michael@0 719
michael@0 720 /**
michael@0 721 * Starts an update check.
michael@0 722 *
michael@0 723 * @param aId
michael@0 724 * The ID of the add-on being checked for updates
michael@0 725 * @param aUpdateKey
michael@0 726 * An optional update key for the add-on
michael@0 727 * @param aUrl
michael@0 728 * The URL of the add-on's update manifest
michael@0 729 * @param aObserver
michael@0 730 * An observer to notify of results
michael@0 731 * @return UpdateParser so that the caller can use UpdateParser.cancel() to shut
michael@0 732 * down in-progress update requests
michael@0 733 */
michael@0 734 checkForUpdates: function AUC_checkForUpdates(aId, aUpdateKey, aUrl,
michael@0 735 aObserver) {
michael@0 736 return new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
michael@0 737 }
michael@0 738 };

mercurial