summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm')
-rw-r--r--toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm335
1 files changed, 87 insertions, 248 deletions
diff --git a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
index 63c16737c..939e2e269 100644
--- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
@@ -22,6 +22,9 @@ const PREFIX_ITEM = "urn:mozilla:item:";
const PREFIX_EXTENSION = "urn:mozilla:extension:";
const PREFIX_THEME = "urn:mozilla:theme:";
const TOOLKIT_ID = "toolkit@mozilla.org"
+#ifdef MOZ_PHOENIX_EXTENSIONS
+const FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"
+#endif
const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
const PREF_UPDATE_REQUIREBUILTINCERTS = "extensions.update.requireBuiltInCerts";
@@ -31,16 +34,11 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
- "resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
"resource://gre/modules/addons/AddonRepository.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
- "resource://gre/modules/ServiceRequest.jsm");
-
// Shared code for suppressing bad cert dialogs.
-XPCOMUtils.defineLazyGetter(this, "CertUtils", function() {
+XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() {
let certUtils = {};
Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
return certUtils;
@@ -82,7 +80,7 @@ RDFSerializer.prototype = {
* @return a string with all characters invalid in XML character data
* converted to entity references.
*/
- escapeEntities: function(aString) {
+ escapeEntities: function RDFS_escapeEntities(aString) {
aString = aString.replace(/&/g, "&");
aString = aString.replace(/</g, "&lt;");
aString = aString.replace(/>/g, "&gt;");
@@ -100,7 +98,8 @@ RDFSerializer.prototype = {
* The current level of indent for pretty-printing
* @return a string containing the serialized elements.
*/
- serializeContainerItems: function(aDs, aContainer, aIndent) {
+ serializeContainerItems: function RDFS_serializeContainerItems(aDs, aContainer,
+ aIndent) {
var result = "";
var items = aContainer.GetElements();
while (items.hasMoreElements()) {
@@ -126,7 +125,9 @@ RDFSerializer.prototype = {
* @return a string containing the serialized properties.
* @throws if the resource contains a property that cannot be serialized
*/
- serializeResourceProperties: function(aDs, aResource, aIndent) {
+ serializeResourceProperties: function RDFS_serializeResourceProperties(aDs,
+ aResource,
+ aIndent) {
var result = "";
var items = [];
var arcs = aDs.ArcLabelsOut(aResource);
@@ -180,7 +181,7 @@ RDFSerializer.prototype = {
* @return a string containing the serialized resource.
* @throws if the RDF data contains multiple references to the same resource.
*/
- serializeResource: function(aDs, aResource, aIndent) {
+ serializeResource: function RDFS_serializeResource(aDs, aResource, aIndent) {
if (this.resources.indexOf(aResource) != -1 ) {
// We cannot output multiple references to the same resource.
throw Components.Exception("Cannot serialize multiple references to " + aResource.Value);
@@ -220,48 +221,6 @@ RDFSerializer.prototype = {
}
/**
- * Sanitizes the update URL in an update item, as returned by
- * parseRDFManifest and parseJSONManifest. Ensures that:
- *
- * - The URL is secure, or secured by a strong enough hash.
- * - The security principal of the update manifest has permission to
- * load the URL.
- *
- * @param aUpdate
- * The update item to sanitize.
- * @param aRequest
- * The XMLHttpRequest used to load the manifest.
- * @param aHashPattern
- * The regular expression used to validate the update hash.
- * @param aHashString
- * The human-readable string specifying which hash functions
- * are accepted.
- */
-function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) {
- if (aUpdate.updateURL) {
- let scriptSecurity = Services.scriptSecurityManager;
- let principal = scriptSecurity.getChannelURIPrincipal(aRequest.channel);
- try {
- // This logs an error on failure, so no need to log it a second time
- scriptSecurity.checkLoadURIStrWithPrincipal(principal, aUpdate.updateURL,
- scriptSecurity.DISALLOW_SCRIPT);
- } catch (e) {
- delete aUpdate.updateURL;
- return;
- }
-
- if (AddonManager.checkUpdateSecurity &&
- !aUpdate.updateURL.startsWith("https:") &&
- !aHashPattern.test(aUpdate.updateHash)) {
- logger.warn(`Update link ${aUpdate.updateURL} is not secure and is not verified ` +
- `by a strong enough hash (needs to be ${aHashString}).`);
- delete aUpdate.updateURL;
- delete aUpdate.updateHash;
- }
- }
-}
-
-/**
* Parses an RDF style update manifest into an array of update objects.
*
* @param aId
@@ -270,17 +229,10 @@ function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) {
* An optional update key for the add-on
* @param aRequest
* The XMLHttpRequest that has retrieved the update manifest
- * @param aManifestData
- * The pre-parsed manifest, as a bare XML DOM document
* @return an array of update objects
* @throws if the update manifest is invalid in any way
*/
-function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) {
- if (aManifestData.documentElement.namespaceURI != PREFIX_NS_RDF) {
- throw Components.Exception("Update manifest had an unrecognised namespace: " +
- aManifestData.documentElement.namespaceURI);
- }
-
+function parseRDFManifest(aId, aUpdateKey, aRequest) {
function EM_R(aProp) {
return gRDF.GetResource(PREFIX_NS_EM + aProp);
}
@@ -323,13 +275,9 @@ function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) {
let extensionRes = gRDF.GetResource(PREFIX_EXTENSION + aId);
let themeRes = gRDF.GetResource(PREFIX_THEME + aId);
let itemRes = gRDF.GetResource(PREFIX_ITEM + aId);
- let addonRes;
- if (ds.ArcLabelsOut(extensionRes).hasMoreElements())
- addonRes = extensionRes;
- else if (ds.ArcLabelsOut(themeRes).hasMoreElements())
- addonRes = themeRes;
- else
- addonRes = itemRes;
+ let addonRes = ds.ArcLabelsOut(extensionRes).hasMoreElements() ? extensionRes
+ : ds.ArcLabelsOut(themeRes).hasMoreElements() ? themeRes
+ : itemRes;
// If we have an update key then the update manifest must be signed
if (aUpdateKey) {
@@ -421,134 +369,16 @@ function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) {
targetApplications: [appEntry]
};
- // The JSON update protocol requires an SHA-2 hash. RDF still
- // supports SHA-1, for compatibility reasons.
- sanitizeUpdateURL(result, aRequest, /^sha/, "sha1 or stronger");
-
- results.push(result);
- }
- }
- return results;
-}
-
-/**
- * Parses an JSON update manifest into an array of update objects.
- *
- * @param aId
- * The ID of the add-on being checked for updates
- * @param aUpdateKey
- * An optional update key for the add-on
- * @param aRequest
- * The XMLHttpRequest that has retrieved the update manifest
- * @param aManifestData
- * The pre-parsed manifest, as a JSON object tree
- * @return an array of update objects
- * @throws if the update manifest is invalid in any way
- */
-function parseJSONManifest(aId, aUpdateKey, aRequest, aManifestData) {
- if (aUpdateKey)
- throw Components.Exception("Update keys are not supported for JSON update manifests");
-
- let TYPE_CHECK = {
- "array": val => Array.isArray(val),
- "object": val => val && typeof val == "object" && !Array.isArray(val),
- };
-
- function getProperty(aObj, aProperty, aType, aDefault = undefined) {
- if (!(aProperty in aObj))
- return aDefault;
-
- let value = aObj[aProperty];
-
- let matchesType = aType in TYPE_CHECK ? TYPE_CHECK[aType](value) : typeof value == aType;
- if (!matchesType)
- throw Components.Exception(`Update manifest property '${aProperty}' has incorrect type (expected ${aType})`);
-
- return value;
- }
-
- function getRequiredProperty(aObj, aProperty, aType) {
- let value = getProperty(aObj, aProperty, aType);
- if (value === undefined)
- throw Components.Exception(`Update manifest is missing a required ${aProperty} property.`);
- return value;
- }
-
- let manifest = aManifestData;
-
- if (!TYPE_CHECK["object"](manifest))
- throw Components.Exception("Root element of update manifest must be a JSON object literal");
-
- // The set of add-ons this manifest has updates for
- let addons = getRequiredProperty(manifest, "addons", "object");
-
- // The entry for this particular add-on
- let addon = getProperty(addons, aId, "object");
-
- // A missing entry doesn't count as a failure, just as no avialable update
- // information
- if (!addon) {
- logger.warn("Update manifest did not contain an entry for " + aId);
- return [];
- }
-
- // The list of available updates
- let updates = getProperty(addon, "updates", "array", []);
-
- let results = [];
-
- for (let update of updates) {
- let version = getRequiredProperty(update, "version", "string");
-
- logger.debug(`Found an update entry for ${aId} version ${version}`);
-
- let applications = getProperty(update, "applications", "object",
- { gecko: {} });
-
- // "gecko" is currently the only supported application entry. If
- // it's missing, skip this update.
- if (!("gecko" in applications)) {
- logger.debug("gecko not in application entry, skipping update of ${addon}")
- continue;
- }
-
- let app = getProperty(applications, "gecko", "object");
-
- let appEntry = {
- id: TOOLKIT_ID,
- minVersion: getProperty(app, "strict_min_version", "string",
- AddonManagerPrivate.webExtensionsMinPlatformVersion),
- maxVersion: "*",
- };
-
- let result = {
- id: aId,
- version: version,
- multiprocessCompatible: getProperty(update, "multiprocess_compatible", "boolean", true),
- updateURL: getProperty(update, "update_link", "string"),
- updateHash: getProperty(update, "update_hash", "string"),
- updateInfoURL: getProperty(update, "update_info_url", "string"),
- strictCompatibility: false,
- targetApplications: [appEntry],
- };
-
- if ("strict_max_version" in app) {
- if ("advisory_max_version" in app) {
- logger.warn("Ignoring 'advisory_max_version' update manifest property for " +
- aId + " property since 'strict_max_version' also present");
+ if (result.updateURL && AddonManager.checkUpdateSecurity &&
+ result.updateURL.substring(0, 6) != "https:" &&
+ (!result.updateHash || result.updateHash.substring(0, 3) != "sha")) {
+ logger.warn("updateLink " + result.updateURL + " is not secure and is not verified" +
+ " by a strong enough hash (needs to be sha1 or stronger).");
+ delete result.updateURL;
+ delete result.updateHash;
}
-
- appEntry.maxVersion = getProperty(app, "strict_max_version", "string");
- result.strictCompatibility = appEntry.maxVersion != "*";
- } else if ("advisory_max_version" in app) {
- appEntry.maxVersion = getProperty(app, "advisory_max_version", "string");
+ results.push(result);
}
-
- // The JSON update protocol requires an SHA-2 hash. RDF still
- // supports SHA-1, for compatibility reasons.
- sanitizeUpdateURL(result, aRequest, /^sha(256|512):/, "sha256 or sha512");
-
- results.push(result);
}
return results;
}
@@ -581,18 +411,20 @@ function UpdateParser(aId, aUpdateKey, aUrl, aObserver) {
logger.debug("Requesting " + aUrl);
try {
- this.request = new ServiceRequest();
+ this.request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsIXMLHttpRequest);
this.request.open("GET", this.url, true);
this.request.channel.notificationCallbacks = new CertUtils.BadCertHandler(!requireBuiltIn);
this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
// Prevent the request from writing to cache.
this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
- this.request.overrideMimeType("text/plain");
+ this.request.overrideMimeType("text/xml");
this.request.setRequestHeader("Moz-XPI-Update", "1", true);
this.request.timeout = TIMEOUT;
- this.request.addEventListener("load", () => this.onLoad(), false);
- this.request.addEventListener("error", () => this.onError(), false);
- this.request.addEventListener("timeout", () => this.onTimeout(), false);
+ var self = this;
+ this.request.addEventListener("load", function loadEventListener(event) { self.onLoad() }, false);
+ this.request.addEventListener("error", function errorEventListener(event) { self.onError() }, false);
+ this.request.addEventListener("timeout", function timeoutEventListener(event) { self.onTimeout() }, false);
this.request.send(null);
}
catch (e) {
@@ -610,7 +442,7 @@ UpdateParser.prototype = {
/**
* Called when the manifest has been successfully loaded.
*/
- onLoad: function() {
+ onLoad: function UP_onLoad() {
let request = this.request;
this.request = null;
this._doneAt = new Error("place holder");
@@ -626,7 +458,6 @@ UpdateParser.prototype = {
CertUtils.checkCert(request.channel, !requireBuiltIn);
}
catch (e) {
- logger.warn("Request failed: " + this.url + " - " + e);
this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
return;
}
@@ -645,52 +476,41 @@ UpdateParser.prototype = {
return;
}
- // Detect the manifest type by first attempting to parse it as
- // JSON, and falling back to parsing it as XML if that fails.
- let parser;
- try {
- try {
- let json = JSON.parse(request.responseText);
-
- parser = () => parseJSONManifest(this.id, this.updateKey, request, json);
- } catch (e) {
- if (!(e instanceof SyntaxError))
- throw e;
- let domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
- let xml = domParser.parseFromString(request.responseText, "text/xml");
-
- if (xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR)
- throw new Error("Update manifest was not valid XML or JSON");
-
- parser = () => parseRDFManifest(this.id, this.updateKey, request, xml);
- }
- } catch (e) {
- logger.warn("onUpdateCheckComplete failed to determine manifest type");
- this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT);
- return;
- }
-
- let results;
- try {
- results = parser();
- }
- catch (e) {
- logger.warn("onUpdateCheckComplete failed to parse update manifest", e);
+ let xml = request.responseXML;
+ if (!xml || xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR) {
+ logger.warn("Update manifest was not valid XML");
this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
return;
}
- if ("onUpdateCheckComplete" in this.observer) {
+ // We currently only know about RDF update manifests
+ if (xml.documentElement.namespaceURI == PREFIX_NS_RDF) {
+ let results = null;
+
try {
- this.observer.onUpdateCheckComplete(results);
+ results = parseRDFManifest(this.id, this.updateKey, request);
}
catch (e) {
- logger.warn("onUpdateCheckComplete notification failed", e);
+ logger.warn("onUpdateCheckComplete failed to parse RDF manifest", e);
+ this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
+ return;
}
+ if ("onUpdateCheckComplete" in this.observer) {
+ try {
+ this.observer.onUpdateCheckComplete(results);
+ }
+ catch (e) {
+ logger.warn("onUpdateCheckComplete notification failed", e);
+ }
+ }
+ else {
+ logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker"));
+ }
+ return;
}
- else {
- logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker"));
- }
+
+ logger.warn("Update manifest had an unrecognised namespace: " + xml.documentElement.namespaceURI);
+ this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT);
},
/**
@@ -706,7 +526,7 @@ UpdateParser.prototype = {
/**
* Called when the manifest failed to load.
*/
- onError: function() {
+ onError: function UP_onError() {
if (!Components.isSuccessCode(this.request.status)) {
logger.warn("Request failed: " + this.url + " - " + this.request.status);
}
@@ -735,7 +555,7 @@ UpdateParser.prototype = {
/**
* Helper method to notify the observer that an error occured.
*/
- notifyError: function(aStatus) {
+ notifyError: function UP_notifyError(aStatus) {
if ("onUpdateCheckError" in this.observer) {
try {
this.observer.onUpdateCheckError(aStatus);
@@ -749,7 +569,7 @@ UpdateParser.prototype = {
/**
* Called to cancel an in-progress update check.
*/
- cancel: function() {
+ cancel: function UP_cancel() {
if (!this.request) {
logger.error("Trying to cancel already-complete request", this._doneAt);
return;
@@ -799,6 +619,12 @@ function matchesVersions(aUpdate, aAppVersion, aPlatformVersion,
return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) &&
(aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0));
}
+#ifdef MOZ_PHOENIX_EXTENSIONS
+ if (app.id == FIREFOX_ID) {
+ return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) &&
+ (aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0));
+ }
+#endif
if (app.id == TOOLKIT_ID) {
result = (Services.vc.compare(aPlatformVersion, app.minVersion) >= 0) &&
(aIgnoreMaxVersion || (Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0));
@@ -843,9 +669,12 @@ this.AddonUpdateChecker = {
* Ignore strictCompatibility when testing if an update matches. Optional.
* @return an update object if one matches or null if not
*/
- getCompatibilityUpdate: function(aUpdates, aVersion, aIgnoreCompatibility,
- aAppVersion, aPlatformVersion,
- aIgnoreMaxVersion, aIgnoreStrictCompat) {
+ getCompatibilityUpdate: function AUC_getCompatibilityUpdate(aUpdates, aVersion,
+ aIgnoreCompatibility,
+ aAppVersion,
+ aPlatformVersion,
+ aIgnoreMaxVersion,
+ aIgnoreStrictCompat) {
if (!aAppVersion)
aAppVersion = Services.appinfo.version;
if (!aPlatformVersion)
@@ -856,7 +685,12 @@ this.AddonUpdateChecker = {
if (aIgnoreCompatibility) {
for (let targetApp of update.targetApplications) {
let id = targetApp.id;
+#ifdef MOZ_PHOENIX_EXTENSIONS
+ if (id == Services.appinfo.ID || id == FIREFOX_ID ||
+ id == TOOLKIT_ID)
+#else
if (id == Services.appinfo.ID || id == TOOLKIT_ID)
+#endif
return update;
}
}
@@ -886,9 +720,12 @@ this.AddonUpdateChecker = {
* Array of AddonCompatibilityOverride to take into account. Optional.
* @return an update object if one matches or null if not
*/
- getNewestCompatibleUpdate: function(aUpdates, aAppVersion, aPlatformVersion,
- aIgnoreMaxVersion, aIgnoreStrictCompat,
- aCompatOverrides) {
+ getNewestCompatibleUpdate: function AUC_getNewestCompatibleUpdate(aUpdates,
+ aAppVersion,
+ aPlatformVersion,
+ aIgnoreMaxVersion,
+ aIgnoreStrictCompat,
+ aCompatOverrides) {
if (!aAppVersion)
aAppVersion = Services.appinfo.version;
if (!aPlatformVersion)
@@ -928,7 +765,9 @@ this.AddonUpdateChecker = {
* @return UpdateParser so that the caller can use UpdateParser.cancel() to shut
* down in-progress update requests
*/
- checkForUpdates: function(aId, aUpdateKey, aUrl, aObserver) {
- return new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
+ checkForUpdates: function AUC_checkForUpdates(aId, aUpdateKey, aUrl, aObserver) {
+ // Exclude default theme
+ if (aId != "{972ce4c6-7e08-4474-a285-3208198ce6fd}")
+ return new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
}
};