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