зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1192924: Split out add-on update.xml parsing code from GMP modules. r=spohl
The system add-on update checks will use the same update.xml format as GMP so this splits out the code for parsing and downloading files into a standalone module that both can reuse. --HG-- extra : commitid : 31m1WDO3PCP extra : rebase_source : f018d36b94460942b217e9a6bb4ec146309f9a55 extra : histedit_source : 15e2e92984ee8747b59d0278dab12f6872a17223
This commit is contained in:
Родитель
fb7653707b
Коммит
b3e382b93e
|
@ -8,10 +8,6 @@ this.EXPORTED_SYMBOLS = [];
|
|||
|
||||
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu, manager: Cm} =
|
||||
Components;
|
||||
// Chunk size for the incremental downloader
|
||||
const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
|
||||
// Incremental downloader interval
|
||||
const DOWNLOAD_INTERVAL = 0;
|
||||
// 1 day default
|
||||
const DEFAULT_SECONDS_BETWEEN_CHECKS = 60 * 60 * 24;
|
||||
|
||||
|
@ -31,6 +27,7 @@ Cu.import("resource://gre/modules/Log.jsm");
|
|||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/GMPUtils.jsm");
|
||||
Cu.import("resource://gre/modules/addons/ProductAddonChecker.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["GMPInstallManager", "GMPExtractor", "GMPDownloader",
|
||||
"GMPAddon"];
|
||||
|
@ -45,16 +42,6 @@ XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() {
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
|
||||
/**
|
||||
* Number of milliseconds after which we need to cancel `checkForAddons`.
|
||||
*
|
||||
* Bug 1087674 suggests that the XHR we use in `checkForAddons` may
|
||||
* never terminate in presence of network nuisances (e.g. strange
|
||||
* antivirus behavior). This timeout is a defensive measure to ensure
|
||||
* that we fail cleanly in such case.
|
||||
*/
|
||||
const CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS = 20000;
|
||||
|
||||
function getScopedLogger(prefix) {
|
||||
// `PARENT_LOGGER_ID.` being passed here effectively links this logger
|
||||
// to the parentLogger.
|
||||
|
@ -108,38 +95,27 @@ GMPInstallManager.prototype = {
|
|||
this._deferred = Promise.defer();
|
||||
let url = this._getURL();
|
||||
|
||||
this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
||||
createInstance(Ci.nsISupports);
|
||||
// This is here to let unit test code override XHR
|
||||
if (this._request.wrappedJSObject) {
|
||||
this._request = this._request.wrappedJSObject;
|
||||
let allowNonBuiltIn = true;
|
||||
let certs = null;
|
||||
if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE)) {
|
||||
allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN, true);
|
||||
if (GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true)) {
|
||||
certs = gCertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH);
|
||||
}
|
||||
}
|
||||
this._request.open("GET", url, true);
|
||||
let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true);
|
||||
this._request.channel.notificationCallbacks =
|
||||
new gCertUtils.BadCertHandler(allowNonBuiltIn);
|
||||
// Prevent the request from reading from the cache.
|
||||
this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
|
||||
// Prevent the request from writing to the cache.
|
||||
this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
||||
|
||||
this._request.overrideMimeType("text/xml");
|
||||
// The Cache-Control header is only interpreted by proxies and the
|
||||
// final destination. It does not help if a resource is already
|
||||
// cached locally.
|
||||
this._request.setRequestHeader("Cache-Control", "no-cache");
|
||||
// HTTP/1.0 servers might not implement Cache-Control and
|
||||
// might only implement Pragma: no-cache
|
||||
this._request.setRequestHeader("Pragma", "no-cache");
|
||||
|
||||
this._request.timeout = CHECK_FOR_ADDONS_TIMEOUT_DELAY_MS;
|
||||
this._request.addEventListener("error", event => this.onFailXML("onErrorXML", event), false);
|
||||
this._request.addEventListener("abort", event => this.onFailXML("onAbortXML", event), false);
|
||||
this._request.addEventListener("timeout", event => this.onFailXML("onTimeoutXML", event), false);
|
||||
this._request.addEventListener("load", event => this.onLoadXML(event), false);
|
||||
|
||||
log.info("sending request to: " + url);
|
||||
this._request.send(null);
|
||||
ProductAddonChecker.getProductAddonList(url, allowNonBuiltIn, certs).then((addons) => {
|
||||
if (!addons) {
|
||||
this._deferred.resolve([]);
|
||||
}
|
||||
else {
|
||||
this._deferred.resolve([for (a of addons) new GMPAddon(a)]);
|
||||
}
|
||||
delete this._deferred;
|
||||
}, (ex) => {
|
||||
this._deferred.reject(ex);
|
||||
delete this._deferred;
|
||||
});
|
||||
|
||||
return this._deferred.promise;
|
||||
},
|
||||
|
@ -341,132 +317,6 @@ GMPInstallManager.prototype = {
|
|||
* This is useful for tests.
|
||||
*/
|
||||
overrideLeaveDownloadedZip: false,
|
||||
|
||||
/**
|
||||
* The XMLHttpRequest succeeded and the document was loaded.
|
||||
* @param event The nsIDOMEvent for the load
|
||||
*/
|
||||
onLoadXML: function(event) {
|
||||
let log = getScopedLogger("GMPInstallManager.onLoadXML");
|
||||
try {
|
||||
log.info("request completed downloading document");
|
||||
let certs = null;
|
||||
if (!Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE) &&
|
||||
GMPPrefs.get(GMPPrefs.KEY_CERT_CHECKATTRS, true)) {
|
||||
certs = gCertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH);
|
||||
}
|
||||
|
||||
let allowNonBuiltIn = !GMPPrefs.get(GMPPrefs.KEY_CERT_REQUIREBUILTIN,
|
||||
true);
|
||||
log.info("allowNonBuiltIn: " + allowNonBuiltIn);
|
||||
|
||||
gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
|
||||
|
||||
this.parseResponseXML();
|
||||
} catch (ex) {
|
||||
log.error("could not load xml: " + ex);
|
||||
this._deferred.reject({
|
||||
target: event.target,
|
||||
status: this._getChannelStatus(event.target),
|
||||
message: "" + ex,
|
||||
});
|
||||
delete this._deferred;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the status code for the XMLHttpRequest
|
||||
*/
|
||||
_getChannelStatus: function(request) {
|
||||
let log = getScopedLogger("GMPInstallManager._getChannelStatus");
|
||||
let status = null;
|
||||
try {
|
||||
status = request.status;
|
||||
log.info("request.status is: " + request.status);
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
|
||||
if (status == null) {
|
||||
status = request.channel.QueryInterface(Ci.nsIRequest).status;
|
||||
}
|
||||
return status;
|
||||
},
|
||||
|
||||
/**
|
||||
* There was an error of some kind during the XMLHttpRequest. This
|
||||
* error may have been caused by external factors (e.g. network
|
||||
* issues) or internally (by a timeout).
|
||||
*
|
||||
* @param event The nsIDOMEvent for the error
|
||||
*/
|
||||
onFailXML: function(failure, event) {
|
||||
let log = getScopedLogger("GMPInstallManager.onFailXML " + failure);
|
||||
let request = event.target;
|
||||
let status = this._getChannelStatus(request);
|
||||
let message = "request.status: " + status + " (" + event.type + ")";
|
||||
log.warn(message);
|
||||
this._deferred.reject({
|
||||
target: request,
|
||||
status: status,
|
||||
message: message
|
||||
});
|
||||
delete this._deferred;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an array of GMPAddon objects discovered by the update check.
|
||||
* Or returns an empty array if there were any problems with parsing.
|
||||
* If there's an error, it will be logged if logging is enabled.
|
||||
*/
|
||||
parseResponseXML: function() {
|
||||
try {
|
||||
let log = getScopedLogger("GMPInstallManager.parseResponseXML");
|
||||
let updatesElement = this._request.responseXML.documentElement;
|
||||
if (!updatesElement) {
|
||||
let message = "empty updates document";
|
||||
log.warn(message);
|
||||
this._deferred.reject({
|
||||
target: this._request,
|
||||
message: message
|
||||
});
|
||||
delete this._deferred;
|
||||
return;
|
||||
}
|
||||
|
||||
if (updatesElement.nodeName != "updates") {
|
||||
let message = "got node name: " + updatesElement.nodeName +
|
||||
", expected: updates";
|
||||
log.warn(message);
|
||||
this._deferred.reject({
|
||||
target: this._request,
|
||||
message: message
|
||||
});
|
||||
delete this._deferred;
|
||||
return;
|
||||
}
|
||||
|
||||
const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
|
||||
let gmpResults = [];
|
||||
for (let i = 0; i < updatesElement.childNodes.length; ++i) {
|
||||
let updatesChildElement = updatesElement.childNodes.item(i);
|
||||
if (updatesChildElement.nodeType != ELEMENT_NODE) {
|
||||
continue;
|
||||
}
|
||||
if (updatesChildElement.localName == "addons") {
|
||||
gmpResults = GMPAddon.parseGMPAddonsNode(updatesChildElement);
|
||||
}
|
||||
}
|
||||
this._deferred.resolve(gmpResults);
|
||||
delete this._deferred;
|
||||
} catch (e) {
|
||||
this._deferred.reject({
|
||||
target: this._request,
|
||||
message: e
|
||||
});
|
||||
delete this._deferred;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -474,49 +324,16 @@ GMPInstallManager.prototype = {
|
|||
* GMPAddon objects are returns from GMPInstallManager.checkForAddons
|
||||
* GMPAddon objects can also be used in calls to GMPInstallManager.installAddon
|
||||
*
|
||||
* @param gmpAddon The AUS response XML's DOM element `addon`
|
||||
* @param addon The ProductAddonChecker `addon` object
|
||||
*/
|
||||
function GMPAddon(gmpAddon) {
|
||||
function GMPAddon(addon) {
|
||||
let log = getScopedLogger("GMPAddon.constructor");
|
||||
gmpAddon.QueryInterface(Ci.nsIDOMElement);
|
||||
["id", "URL", "hashFunction",
|
||||
"hashValue", "version", "size"].forEach(name => {
|
||||
if (gmpAddon.hasAttribute(name)) {
|
||||
this[name] = gmpAddon.getAttribute(name);
|
||||
}
|
||||
});
|
||||
this.size = Number(this.size) || undefined;
|
||||
for (let name of Object.keys(addon)) {
|
||||
this[name] = addon[name];
|
||||
}
|
||||
log.info ("Created new addon: " + this.toString());
|
||||
}
|
||||
/**
|
||||
* Parses an XML GMP addons node from AUS into an array
|
||||
* @param addonsElement An nsIDOMElement compatible node with XML from AUS
|
||||
* @return An array of GMPAddon results
|
||||
*/
|
||||
GMPAddon.parseGMPAddonsNode = function(addonsElement) {
|
||||
let log = getScopedLogger("GMPAddon.parseGMPAddonsNode");
|
||||
let gmpResults = [];
|
||||
if (addonsElement.localName !== "addons") {
|
||||
return;
|
||||
}
|
||||
|
||||
addonsElement.QueryInterface(Ci.nsIDOMElement);
|
||||
let addonCount = addonsElement.childNodes.length;
|
||||
for (let i = 0; i < addonCount; ++i) {
|
||||
let addonElement = addonsElement.childNodes.item(i);
|
||||
if (addonElement.localName !== "addon") {
|
||||
continue;
|
||||
}
|
||||
addonElement.QueryInterface(Ci.nsIDOMElement);
|
||||
try {
|
||||
gmpResults.push(new GMPAddon(addonElement));
|
||||
} catch (e) {
|
||||
log.warn("invalid addon: " + e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return gmpResults;
|
||||
};
|
||||
GMPAddon.prototype = {
|
||||
/**
|
||||
* Returns a string representation of the addon
|
||||
|
@ -647,38 +464,7 @@ function GMPDownloader(gmpAddon)
|
|||
{
|
||||
this._gmpAddon = gmpAddon;
|
||||
}
|
||||
/**
|
||||
* Computes the file hash of fileToHash with the specified hash function
|
||||
* @param hashFunctionName A hash function name such as sha512
|
||||
* @param fileToHash An nsIFile to hash
|
||||
* @return a promise which resolve to a digest in binary hex format
|
||||
*/
|
||||
GMPDownloader.computeHash = function(hashFunctionName, fileToHash) {
|
||||
let log = getScopedLogger("GMPDownloader.computeHash");
|
||||
let digest;
|
||||
let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Ci.nsIFileInputStream);
|
||||
fileStream.init(fileToHash, FileUtils.MODE_RDONLY,
|
||||
FileUtils.PERMS_FILE, 0);
|
||||
try {
|
||||
let hash = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
let hashFunction =
|
||||
Ci.nsICryptoHash[hashFunctionName.toUpperCase()];
|
||||
if (!hashFunction) {
|
||||
log.error("could not get hash function");
|
||||
return Promise.reject();
|
||||
}
|
||||
hash.init(hashFunction);
|
||||
hash.updateFromStream(fileStream, -1);
|
||||
digest = binaryToHex(hash.finish(false));
|
||||
} catch (e) {
|
||||
log.warn("failed to compute hash: " + e);
|
||||
digest = "";
|
||||
}
|
||||
fileStream.close();
|
||||
return Promise.resolve(digest);
|
||||
},
|
||||
|
||||
GMPDownloader.prototype = {
|
||||
/**
|
||||
* Starts the download process for an addon.
|
||||
|
@ -686,9 +472,10 @@ GMPDownloader.prototype = {
|
|||
* See GMPInstallManager.installAddon for resolve/rejected info
|
||||
*/
|
||||
start: function() {
|
||||
let log = getScopedLogger("GMPDownloader.start");
|
||||
this._deferred = Promise.defer();
|
||||
if (!this._gmpAddon.isValid) {
|
||||
let log = getScopedLogger("GMPDownloader");
|
||||
let gmpAddon = this._gmpAddon;
|
||||
|
||||
if (!gmpAddon.isValid) {
|
||||
log.info("gmpAddon is not valid, will not continue");
|
||||
return Promise.reject({
|
||||
target: this,
|
||||
|
@ -697,55 +484,14 @@ GMPDownloader.prototype = {
|
|||
});
|
||||
}
|
||||
|
||||
let uri = Services.io.newURI(this._gmpAddon.URL, null, null);
|
||||
this._request = Cc["@mozilla.org/network/incremental-download;1"].
|
||||
createInstance(Ci.nsIIncrementalDownload);
|
||||
let gmpFile = FileUtils.getFile("TmpD", [this._gmpAddon.id + ".zip"]);
|
||||
if (gmpFile.exists()) {
|
||||
gmpFile.remove(false);
|
||||
}
|
||||
|
||||
log.info("downloading from " + uri.spec + " to " + gmpFile.path);
|
||||
this._request.init(uri, gmpFile, DOWNLOAD_CHUNK_BYTES_SIZE,
|
||||
DOWNLOAD_INTERVAL);
|
||||
this._request.start(this, null);
|
||||
return this._deferred.promise;
|
||||
},
|
||||
// For nsIRequestObserver
|
||||
onStartRequest: function(request, context) {
|
||||
},
|
||||
// For nsIRequestObserver
|
||||
// Called when the GMP addon zip file is downloaded
|
||||
onStopRequest: function(request, context, status) {
|
||||
let log = getScopedLogger("GMPDownloader.onStopRequest");
|
||||
log.info("onStopRequest called");
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
log.info("status failed: " + status);
|
||||
this._deferred.reject({
|
||||
target: this,
|
||||
status: status,
|
||||
type: "downloaderr"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let promise = this._verifyDownload();
|
||||
promise.then(() => {
|
||||
log.info("GMP file is ready to unzip");
|
||||
let destination = this._request.destination;
|
||||
|
||||
let zipPath = destination.path;
|
||||
let gmpAddon = this._gmpAddon;
|
||||
let installToDirPath = Cc["@mozilla.org/file/local;1"].
|
||||
createInstance(Ci.nsIFile);
|
||||
return ProductAddonChecker.downloadAddon(gmpAddon).then((zipPath) => {
|
||||
let path = OS.Path.join(OS.Constants.Path.profileDir,
|
||||
gmpAddon.id,
|
||||
gmpAddon.version);
|
||||
installToDirPath.initWithPath(path);
|
||||
log.info("install to directory path: " + installToDirPath.path);
|
||||
let gmpInstaller = new GMPExtractor(zipPath, installToDirPath.path);
|
||||
log.info("install to directory path: " + path);
|
||||
let gmpInstaller = new GMPExtractor(zipPath, path);
|
||||
let installPromise = gmpInstaller.install();
|
||||
installPromise.then(extractedPaths => {
|
||||
return installPromise.then(extractedPaths => {
|
||||
// Success, set the prefs
|
||||
let now = Math.round(Date.now() / 1000);
|
||||
GMPPrefs.set(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id);
|
||||
|
@ -761,73 +507,8 @@ GMPDownloader.prototype = {
|
|||
// if you need to set other prefs etc. do it before this.
|
||||
GMPPrefs.set(GMPPrefs.KEY_PLUGIN_VERSION, gmpAddon.version,
|
||||
gmpAddon.id);
|
||||
this._deferred.resolve(extractedPaths);
|
||||
}, err => {
|
||||
this._deferred.reject(err);
|
||||
});
|
||||
}, err => {
|
||||
log.warn("verifyDownload check failed");
|
||||
this._deferred.reject({
|
||||
target: this,
|
||||
status: 200,
|
||||
type: "verifyerr"
|
||||
return extractedPaths;
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Verifies that the downloaded zip file's hash matches the GMPAddon hash.
|
||||
* @return a promise which resolves if the download verifies
|
||||
*/
|
||||
_verifyDownload: function() {
|
||||
let verifyDownloadDeferred = Promise.defer();
|
||||
let log = getScopedLogger("GMPDownloader._verifyDownload");
|
||||
log.info("_verifyDownload called");
|
||||
if (!this._request) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
let destination = this._request.destination;
|
||||
log.info("for path: " + destination.path);
|
||||
|
||||
// Ensure that the file size matches the expected file size.
|
||||
if (this._gmpAddon.size !== undefined &&
|
||||
destination.fileSize != this._gmpAddon.size) {
|
||||
log.warn("Downloader:_verifyDownload downloaded size " +
|
||||
destination.fileSize + " != expected size " +
|
||||
this._gmpAddon.size + ".");
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
let promise = GMPDownloader.computeHash(this._gmpAddon.hashFunction, destination);
|
||||
promise.then(digest => {
|
||||
let expectedDigest = this._gmpAddon.hashValue.toLowerCase();
|
||||
if (digest !== expectedDigest) {
|
||||
log.warn("hashes do not match! Got: `" +
|
||||
digest + "`, expected: `" + expectedDigest + "`");
|
||||
this._deferred.reject();
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("hashes match!");
|
||||
verifyDownloadDeferred.resolve();
|
||||
}, err => {
|
||||
verifyDownloadDeferred.reject();
|
||||
});
|
||||
return verifyDownloadDeferred.promise;
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver])
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a string containing binary values to hex.
|
||||
*/
|
||||
function binaryToHex(input) {
|
||||
let result = "";
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
let hex = input.charCodeAt(i).toString(16);
|
||||
if (hex.length == 1)
|
||||
hex = "0" + hex;
|
||||
result += hex;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ Cu.import("resource://gre/modules/Promise.jsm");
|
|||
Cu.import("resource://gre/modules/Preferences.jsm")
|
||||
Cu.import("resource://gre/modules/UpdateUtils.jsm");
|
||||
|
||||
let { computeHash } = Cu.import("resource://gre/modules/addons/ProductAddonChecker.jsm");
|
||||
|
||||
do_get_profile();
|
||||
|
||||
function run_test() {Cu.import("resource://gre/modules/Preferences.jsm")
|
||||
|
@ -431,7 +433,7 @@ function* test_checkForAddons_installAddon(id, includeSize, wantInstallReject) {
|
|||
let data = "e~=0.5772156649";
|
||||
let zipFile = createNewZipFile(zipFileName, data);
|
||||
let hashFunc = "sha256";
|
||||
let expectedDigest = yield GMPDownloader.computeHash(hashFunc, zipFile);
|
||||
let expectedDigest = yield computeHash(hashFunc, zipFile.path);
|
||||
let fileSize = zipFile.fileSize;
|
||||
if (wantInstallReject) {
|
||||
fileSize = 1;
|
||||
|
@ -457,7 +459,6 @@ function* test_checkForAddons_installAddon(id, includeSize, wantInstallReject) {
|
|||
let gmpAddon = gmpAddons[0];
|
||||
do_check_false(gmpAddon.isInstalled);
|
||||
|
||||
GMPInstallManager.overrideLeaveDownloadedZip = true;
|
||||
try {
|
||||
let extractedPaths = yield installManager.installAddon(gmpAddon);
|
||||
if (wantInstallReject) {
|
||||
|
@ -475,14 +476,6 @@ function* test_checkForAddons_installAddon(id, includeSize, wantInstallReject) {
|
|||
let readData = readStringFromFile(extractedFile);
|
||||
do_check_eq(readData, data);
|
||||
|
||||
// Check that the downloaded zip matches the offered zip exactly
|
||||
let downloadedGMPFile = FileUtils.getFile("TmpD",
|
||||
[gmpAddon.id + ".zip"]);
|
||||
do_check_true(downloadedGMPFile.exists());
|
||||
let downloadedBytes = getBinaryFileData(downloadedGMPFile);
|
||||
let sourceBytes = getBinaryFileData(zipFile);
|
||||
do_check_true(compareBinaryData(downloadedBytes, sourceBytes));
|
||||
|
||||
// Make sure the prefs are set correctly
|
||||
do_check_true(!!GMPScope.GMPPrefs.get(
|
||||
GMPScope.GMPPrefs.KEY_PLUGIN_LAST_UPDATE, "", gmpAddon.id));
|
||||
|
@ -499,16 +492,9 @@ function* test_checkForAddons_installAddon(id, includeSize, wantInstallReject) {
|
|||
extractedFile.parent.remove(true);
|
||||
zipFile.remove(false);
|
||||
httpServer.stop(function() {});
|
||||
do_print("Removing downloaded GMP file: " + downloadedGMPFile.path);
|
||||
downloadedGMPFile.remove(false);
|
||||
installManager.uninit();
|
||||
} catch(ex) {
|
||||
zipFile.remove(false);
|
||||
let downloadedGMPFile = FileUtils.getFile("TmpD",
|
||||
[gmpAddon.id + ".zip"]);
|
||||
do_print("Removing downloaded GMP file from exception handler: " +
|
||||
downloadedGMPFile.path);
|
||||
downloadedGMPFile.remove(false);
|
||||
if (!wantInstallReject) {
|
||||
do_throw("install update should not reject");
|
||||
}
|
||||
|
@ -799,45 +785,6 @@ function overrideXHR(status, response, options) {
|
|||
return overrideXHR.myxhr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares binary data of 2 arrays and returns true if they are the same
|
||||
*
|
||||
* @param arr1 The first array to compare
|
||||
* @param arr2 The second array to compare
|
||||
*/
|
||||
function compareBinaryData(arr1, arr2) {
|
||||
do_check_eq(arr1.length, arr2.length);
|
||||
for (let i = 0; i < arr1.length; i++) {
|
||||
if (arr1[i] != arr2[i]) {
|
||||
do_print("Data differs at index " + i +
|
||||
", arr1: " + arr1[i] + ", arr2: " + arr2[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a file's data and returns it
|
||||
*
|
||||
* @param file The file to read the data from
|
||||
* @return array of bytes for the data in the file.
|
||||
*/
|
||||
function getBinaryFileData(file) {
|
||||
let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Ci.nsIFileInputStream);
|
||||
// Open as RD_ONLY with default permissions.
|
||||
fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
|
||||
|
||||
// Check the returned size versus the expected size.
|
||||
let stream = Cc["@mozilla.org/binaryinputstream;1"].
|
||||
createInstance(Ci.nsIBinaryInputStream);
|
||||
stream.setInputStream(fileStream);
|
||||
let bytes = stream.readByteArray(stream.available());
|
||||
fileStream.close();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new zip file containing a file with the specified data
|
||||
* @param zipName The name of the zip file
|
||||
|
|
|
@ -0,0 +1,322 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = [ "ProductAddonChecker" ];
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/CertUtils.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
let logger = Log.repository.getLogger("addons.productaddons");
|
||||
|
||||
/**
|
||||
* Number of milliseconds after which we need to cancel `downloadXML`.
|
||||
*
|
||||
* Bug 1087674 suggests that the XHR we use in `downloadXML` may
|
||||
* never terminate in presence of network nuisances (e.g. strange
|
||||
* antivirus behavior). This timeout is a defensive measure to ensure
|
||||
* that we fail cleanly in such case.
|
||||
*/
|
||||
const TIMEOUT_DELAY_MS = 20000;
|
||||
// Chunk size for the incremental downloader
|
||||
const DOWNLOAD_CHUNK_BYTES_SIZE = 300000;
|
||||
// Incremental downloader interval
|
||||
const DOWNLOAD_INTERVAL = 0;
|
||||
// How much of a file to read into memory at a time for hashing
|
||||
const HASH_CHUNK_SIZE = 8192;
|
||||
|
||||
/**
|
||||
* Gets the status of an XMLHttpRequest either directly or from its underlying
|
||||
* channel.
|
||||
*
|
||||
* @param request
|
||||
* The XMLHttpRequest.
|
||||
* @return an integer status value.
|
||||
*/
|
||||
function getRequestStatus(request) {
|
||||
let status = null;
|
||||
try {
|
||||
status = request.status;
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
|
||||
if (status != null) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return request.channel.QueryInterface(Ci.nsIRequest).status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads an XML document from a URL optionally testing the SSL certificate
|
||||
* for certain attributes.
|
||||
*
|
||||
* @param url
|
||||
* The url to download from.
|
||||
* @param allowNonBuiltIn
|
||||
* Whether to trust SSL certificates without a built-in CA issuer.
|
||||
* @param allowedCerts
|
||||
* The list of certificate attributes to match the SSL certificate
|
||||
* against or null to skip checks.
|
||||
* @return a promise that resolves to the DOM document downloaded or rejects
|
||||
* with a JS exception in case of error.
|
||||
*/
|
||||
function downloadXML(url, allowNonBuiltIn = false, allowedCerts = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
||||
createInstance(Ci.nsISupports);
|
||||
// This is here to let unit test code override XHR
|
||||
if (request.wrappedJSObject) {
|
||||
request = request.wrappedJSObject;
|
||||
}
|
||||
request.open("GET", url, true);
|
||||
request.channel.notificationCallbacks = new BadCertHandler(allowNonBuiltIn);
|
||||
// Prevent the request from reading from the cache.
|
||||
request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
|
||||
// Prevent the request from writing to the cache.
|
||||
request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
||||
request.timeout = TIMEOUT_DELAY_MS;
|
||||
|
||||
request.overrideMimeType("text/xml");
|
||||
// The Cache-Control header is only interpreted by proxies and the
|
||||
// final destination. It does not help if a resource is already
|
||||
// cached locally.
|
||||
request.setRequestHeader("Cache-Control", "no-cache");
|
||||
// HTTP/1.0 servers might not implement Cache-Control and
|
||||
// might only implement Pragma: no-cache
|
||||
request.setRequestHeader("Pragma", "no-cache");
|
||||
|
||||
let fail = (event) => {
|
||||
let request = event.target;
|
||||
let status = getRequestStatus(request);
|
||||
let message = "Failed downloading XML, status: " + status + ", reason: " + event.type;
|
||||
logger.warn(message);
|
||||
let ex = new Error(message);
|
||||
ex.status = status;
|
||||
reject(ex);
|
||||
};
|
||||
|
||||
let success = (event) => {
|
||||
logger.info("Completed downloading document");
|
||||
let request = event.target;
|
||||
|
||||
try {
|
||||
checkCert(request.channel, allowNonBuiltIn, allowedCerts);
|
||||
} catch (ex) {
|
||||
logger.error("Request failed certificate checks: " + ex);
|
||||
ex.status = getRequestStatus(request);
|
||||
reject(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(request.responseXML);
|
||||
};
|
||||
|
||||
request.addEventListener("error", fail, false);
|
||||
request.addEventListener("abort", fail, false);
|
||||
request.addEventListener("timeout", fail, false);
|
||||
request.addEventListener("load", success, false);
|
||||
|
||||
logger.info("sending request to: " + url);
|
||||
request.send(null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a list of add-ons from a DOM document.
|
||||
*
|
||||
* @param document
|
||||
* The DOM document to parse.
|
||||
* @return null if there is no <addons> element otherwise an array of the addons
|
||||
* listed.
|
||||
*/
|
||||
function parseXML(document) {
|
||||
// Check that the root element is correct
|
||||
if (document.documentElement.localName != "updates") {
|
||||
throw new Error("got node name: " + document.documentElement.localName +
|
||||
", expected: updates");
|
||||
}
|
||||
|
||||
// Check if there are any addons elements in the updates element
|
||||
let addons = document.querySelector("updates:root > addons");
|
||||
if (!addons) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let results = [];
|
||||
let addonList = document.querySelectorAll("updates:root > addons > addon");
|
||||
for (let addonElement of addonList) {
|
||||
let addon = {};
|
||||
|
||||
for (let name of ["id", "URL", "hashFunction", "hashValue", "version", "size"]) {
|
||||
if (addonElement.hasAttribute(name)) {
|
||||
addon[name] = addonElement.getAttribute(name);
|
||||
}
|
||||
}
|
||||
addon.size = Number(addon.size) || undefined;
|
||||
|
||||
results.push(addon);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads file from a URL using the incremental file downloader.
|
||||
*
|
||||
* @param url
|
||||
* The url to download from.
|
||||
* @return a promise that resolves to the path of a temporary file or rejects
|
||||
* with a JS exception in case of error.
|
||||
*/
|
||||
function downloadFile(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let observer = {
|
||||
onStartRequest: function() {},
|
||||
|
||||
onStopRequest: function(request, context, status) {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
logger.warn("File download failed: 0x" + status.toString(16));
|
||||
tmpFile.remove(true);
|
||||
reject(Components.Exception("File download failed", status));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(tmpFile.path);
|
||||
}
|
||||
};
|
||||
|
||||
let uri = NetUtil.newURI(url);
|
||||
let request = Cc["@mozilla.org/network/incremental-download;1"].
|
||||
createInstance(Ci.nsIIncrementalDownload);
|
||||
let tmpFile = FileUtils.getFile("TmpD", ["tmpaddon"]);
|
||||
tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
|
||||
|
||||
logger.info("Downloading from " + uri.spec + " to " + tmpFile.path);
|
||||
request.init(uri, tmpFile, DOWNLOAD_CHUNK_BYTES_SIZE, DOWNLOAD_INTERVAL);
|
||||
request.start(observer, null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string containing binary values to hex.
|
||||
*/
|
||||
function binaryToHex(input) {
|
||||
let result = "";
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
let hex = input.charCodeAt(i).toString(16);
|
||||
if (hex.length == 1) {
|
||||
hex = "0" + hex;
|
||||
}
|
||||
result += hex;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the hash of a file.
|
||||
*
|
||||
* @param hashFunction
|
||||
* The type of hash function to use, must be supported by nsICryptoHash.
|
||||
* @param path
|
||||
* The path of the file to hash.
|
||||
* @return a promise that resolves to hash of the file or rejects with a JS
|
||||
* exception in case of error.
|
||||
*/
|
||||
let computeHash = Task.async(function*(hashFunction, path) {
|
||||
let file = yield OS.File.open(path, { existing: true, read: true });
|
||||
try {
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
hasher.initWithString(hashFunction);
|
||||
|
||||
let bytes;
|
||||
do {
|
||||
bytes = yield file.read(HASH_CHUNK_SIZE);
|
||||
hasher.update(bytes, bytes.length);
|
||||
} while (bytes.length == HASH_CHUNK_SIZE);
|
||||
|
||||
return binaryToHex(hasher.finish(false));
|
||||
}
|
||||
finally {
|
||||
yield file.close();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies that a downloaded file matches what was expected.
|
||||
*
|
||||
* @param properties
|
||||
* The properties to check, `size` and `hashFunction` with `hashValue`
|
||||
* are supported. Any properties missing won't be checked.
|
||||
* @param path
|
||||
* The path of the file to check.
|
||||
* @return a promise that resolves if the file matched or rejects with a JS
|
||||
* exception in case of error.
|
||||
*/
|
||||
let verifyFile = Task.async(function*(properties, path) {
|
||||
if (properties.size !== undefined) {
|
||||
let stat = yield OS.File.stat(path);
|
||||
if (stat.size != properties.size) {
|
||||
throw new Error("Downloaded file was " + stat.size + " bytes but expected " + properties.size + " bytes.");
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.hashFunction !== undefined) {
|
||||
let expectedDigest = properties.hashValue.toLowerCase();
|
||||
let digest = yield computeHash(properties.hashFunction, path);
|
||||
if (digest != expectedDigest) {
|
||||
throw new Error("Hash was `" + digest + "` but expected `" + expectedDigest + "`.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const ProductAddonChecker = {
|
||||
/**
|
||||
* Downloads a list of add-ons from a URL optionally testing the SSL
|
||||
* certificate for certain attributes.
|
||||
*
|
||||
* @param url
|
||||
* The url to download from.
|
||||
* @param allowNonBuiltIn
|
||||
* Whether to trust SSL certificates without a built-in CA issuer.
|
||||
* @param allowedCerts
|
||||
* The list of certificate attributes to match the SSL certificate
|
||||
* against or null to skip checks.
|
||||
* @return a promise that resolves to the list of add-ons or rejects with a JS
|
||||
* exception in case of error.
|
||||
*/
|
||||
getProductAddonList: function(url, allowNonBuiltIn = false, allowedCerts = null) {
|
||||
return downloadXML(url, allowNonBuiltIn, allowedCerts).then(parseXML);
|
||||
},
|
||||
|
||||
/**
|
||||
* Downloads an add-on to a local file and checks that it matches the expected
|
||||
* file. The caller is responsible for deleting the temporary file returned.
|
||||
*
|
||||
* @param addon
|
||||
* The addon to download.
|
||||
* @return a promise that resolves to the temporary file downloaded or rejects
|
||||
* with a JS exception in case of error.
|
||||
*/
|
||||
downloadAddon: Task.async(function*(addon) {
|
||||
let path = yield downloadFile(addon.URL);
|
||||
try {
|
||||
yield verifyFile(addon, path);
|
||||
return path;
|
||||
}
|
||||
catch (e) {
|
||||
yield OS.File.remove(path);
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
}
|
|
@ -12,6 +12,7 @@ EXTRA_JS_MODULES.addons += [
|
|||
'Content.js',
|
||||
'GMPProvider.jsm',
|
||||
'LightweightThemeImageOptimizer.jsm',
|
||||
'ProductAddonChecker.jsm',
|
||||
'SpellCheckDictionaryBootstrap.js',
|
||||
'WebExtensionBootstrap.js',
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Not an xml file!
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<foobar></barfoo>
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<test></test>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<updates>
|
||||
<addons></addons>
|
||||
</updates>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<updates>
|
||||
<addons>
|
||||
<addon id="test1" URL="http://example.com/test1.xpi"/>
|
||||
<addon id="test2" URL="http://example.com/test2.xpi" hashFunction="md5" hashValue="djhfgsjdhf"/>
|
||||
<addon id="test3" URL="http://example.com/test3.xpi" version="1.0" size="45"/>
|
||||
<addon id="test4"/>
|
||||
<addon URL="http://example.com/test5.xpi"/>
|
||||
</addons>
|
||||
</updates>
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<updates></updates>
|
Двоичные данные
toolkit/mozapps/extensions/test/xpcshell/data/productaddons/unsigned.xpi
Normal file
Двоичные данные
toolkit/mozapps/extensions/test/xpcshell/data/productaddons/unsigned.xpi
Normal file
Двоичный файл не отображается.
|
@ -0,0 +1,264 @@
|
|||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/addons/ProductAddonChecker.jsm");
|
||||
Components.utils.import("resource://testing-common/httpd.js");
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
const LocalFile = new Components.Constructor("@mozilla.org/file/local;1", AM_Ci.nsIFile, "initWithPath");
|
||||
|
||||
let testserver = new HttpServer();
|
||||
testserver.registerDirectory("/data/", do_get_file("data/productaddons"));
|
||||
testserver.start();
|
||||
let root = testserver.identity.primaryScheme + "://" +
|
||||
testserver.identity.primaryHost + ":" +
|
||||
testserver.identity.primaryPort + "/data/"
|
||||
|
||||
/**
|
||||
* Compares binary data of 2 arrays and returns true if they are the same
|
||||
*
|
||||
* @param arr1 The first array to compare
|
||||
* @param arr2 The second array to compare
|
||||
*/
|
||||
function compareBinaryData(arr1, arr2) {
|
||||
do_check_eq(arr1.length, arr2.length);
|
||||
for (let i = 0; i < arr1.length; i++) {
|
||||
if (arr1[i] != arr2[i]) {
|
||||
do_print("Data differs at index " + i +
|
||||
", arr1: " + arr1[i] + ", arr2: " + arr2[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a file's data and returns it
|
||||
*
|
||||
* @param file The file to read the data from
|
||||
* @return array of bytes for the data in the file.
|
||||
*/
|
||||
function getBinaryFileData(file) {
|
||||
let fileStream = AM_Cc["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(AM_Ci.nsIFileInputStream);
|
||||
// Open as RD_ONLY with default permissions.
|
||||
fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
|
||||
|
||||
let stream = AM_Cc["@mozilla.org/binaryinputstream;1"].
|
||||
createInstance(AM_Ci.nsIBinaryInputStream);
|
||||
stream.setInputStream(fileStream);
|
||||
let bytes = stream.readByteArray(stream.available());
|
||||
fileStream.close();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares binary data of 2 files and returns true if they are the same
|
||||
*
|
||||
* @param file1 The first file to compare
|
||||
* @param file2 The second file to compare
|
||||
*/
|
||||
function compareFiles(file1, file2) {
|
||||
return compareBinaryData(getBinaryFileData(file1), getBinaryFileData(file2));
|
||||
}
|
||||
|
||||
add_task(function* test_404() {
|
||||
try {
|
||||
let addons = yield ProductAddonChecker.getProductAddonList(root + "404.xml");
|
||||
do_throw("Should not have returned anything");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true(true, "Expected to throw for a missing update file");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_not_xml() {
|
||||
try {
|
||||
let addons = yield ProductAddonChecker.getProductAddonList(root + "bad.txt");
|
||||
do_throw("Should not have returned anything");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true(true, "Expected to throw for a non XML result");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_invalid_xml() {
|
||||
try {
|
||||
let addons = yield ProductAddonChecker.getProductAddonList(root + "bad.xml");
|
||||
do_throw("Should not have returned anything");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true(true, "Expected to throw for invalid XML");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_wrong_xml() {
|
||||
try {
|
||||
let addons = yield ProductAddonChecker.getProductAddonList(root + "bad2.xml");
|
||||
do_throw("Should not have returned anything");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true(true, "Expected to throw for a missing <updates> tag");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_missing() {
|
||||
let addons = yield ProductAddonChecker.getProductAddonList(root + "missing.xml");
|
||||
do_check_eq(addons, null);
|
||||
});
|
||||
|
||||
add_task(function* test_empty() {
|
||||
let addons = yield ProductAddonChecker.getProductAddonList(root + "empty.xml");
|
||||
do_check_true(Array.isArray(addons));
|
||||
do_check_eq(addons.length, 0);
|
||||
});
|
||||
|
||||
add_task(function* test_good_xml() {
|
||||
let addons = yield ProductAddonChecker.getProductAddonList(root + "good.xml");
|
||||
do_check_true(Array.isArray(addons));
|
||||
|
||||
// There are three valid entries in the XML
|
||||
do_check_eq(addons.length, 5);
|
||||
|
||||
let addon = addons[0];
|
||||
do_check_eq(addon.id, "test1");
|
||||
do_check_eq(addon.URL, "http://example.com/test1.xpi");
|
||||
do_check_eq(addon.hashFunction, undefined);
|
||||
do_check_eq(addon.hashValue, undefined);
|
||||
do_check_eq(addon.version, undefined);
|
||||
do_check_eq(addon.size, undefined);
|
||||
|
||||
addon = addons[1];
|
||||
do_check_eq(addon.id, "test2");
|
||||
do_check_eq(addon.URL, "http://example.com/test2.xpi");
|
||||
do_check_eq(addon.hashFunction, "md5");
|
||||
do_check_eq(addon.hashValue, "djhfgsjdhf");
|
||||
do_check_eq(addon.version, undefined);
|
||||
do_check_eq(addon.size, undefined);
|
||||
|
||||
addon = addons[2];
|
||||
do_check_eq(addon.id, "test3");
|
||||
do_check_eq(addon.URL, "http://example.com/test3.xpi");
|
||||
do_check_eq(addon.hashFunction, undefined);
|
||||
do_check_eq(addon.hashValue, undefined);
|
||||
do_check_eq(addon.version, "1.0");
|
||||
do_check_eq(addon.size, 45);
|
||||
|
||||
addon = addons[3];
|
||||
do_check_eq(addon.id, "test4");
|
||||
do_check_eq(addon.URL, undefined);
|
||||
do_check_eq(addon.hashFunction, undefined);
|
||||
do_check_eq(addon.hashValue, undefined);
|
||||
do_check_eq(addon.version, undefined);
|
||||
do_check_eq(addon.size, undefined);
|
||||
|
||||
addon = addons[4];
|
||||
do_check_eq(addon.id, undefined);
|
||||
do_check_eq(addon.URL, "http://example.com/test5.xpi");
|
||||
do_check_eq(addon.hashFunction, undefined);
|
||||
do_check_eq(addon.hashValue, undefined);
|
||||
do_check_eq(addon.version, undefined);
|
||||
do_check_eq(addon.size, undefined);
|
||||
});
|
||||
|
||||
add_task(function* test_download_nourl() {
|
||||
try {
|
||||
let path = yield ProductAddonChecker.downloadAddon({});
|
||||
|
||||
yield OS.File.remove(path);
|
||||
do_throw("Should not have downloaded a file with a missing url");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true(true, "Should have thrown when downloading a file with a missing url.");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_download_missing() {
|
||||
try {
|
||||
let path = yield ProductAddonChecker.downloadAddon({
|
||||
URL: root + "nofile.xpi",
|
||||
});
|
||||
|
||||
yield OS.File.remove(path);
|
||||
do_throw("Should not have downloaded a missing file");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true(true, "Should have thrown when downloading a missing file.");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_download_noverify() {
|
||||
let path = yield ProductAddonChecker.downloadAddon({
|
||||
URL: root + "unsigned.xpi",
|
||||
});
|
||||
|
||||
let stat = yield OS.File.stat(path);
|
||||
do_check_false(stat.isDir);
|
||||
do_check_eq(stat.size, 452)
|
||||
|
||||
do_check_true(compareFiles(do_get_file("data/productaddons/unsigned.xpi"), new LocalFile(path)));
|
||||
|
||||
yield OS.File.remove(path);
|
||||
});
|
||||
|
||||
add_task(function* test_download_badsize() {
|
||||
try {
|
||||
let path = yield ProductAddonChecker.downloadAddon({
|
||||
URL: root + "unsigned.xpi",
|
||||
size: 400,
|
||||
});
|
||||
|
||||
yield OS.File.remove(path);
|
||||
do_throw("Should not have downloaded a file with a bad size");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true(true, "Should have thrown when downloading a file with a bad size.");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_download_badhashfn() {
|
||||
try {
|
||||
let path = yield ProductAddonChecker.downloadAddon({
|
||||
URL: root + "unsigned.xpi",
|
||||
hashFunction: "sha2567",
|
||||
hashValue: "9b9abf7ddfc1a6d7ffc7e0247481dcc202363e4445ad3494fb22036f1698c7f3",
|
||||
});
|
||||
|
||||
yield OS.File.remove(path);
|
||||
do_throw("Should not have downloaded a file with a bad hash function");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true(true, "Should have thrown when downloading a file with a bad hash function.");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_download_badhash() {
|
||||
try {
|
||||
let path = yield ProductAddonChecker.downloadAddon({
|
||||
URL: root + "unsigned.xpi",
|
||||
hashFunction: "sha256",
|
||||
hashValue: "8b9abf7ddfc1a6d7ffc7e0247481dcc202363e4445ad3494fb22036f1698c7f3",
|
||||
});
|
||||
|
||||
yield OS.File.remove(path);
|
||||
do_throw("Should not have downloaded a file with a bad hash");
|
||||
}
|
||||
catch (e) {
|
||||
do_check_true(true, "Should have thrown when downloading a file with a bad hash.");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_download_works() {
|
||||
let path = yield ProductAddonChecker.downloadAddon({
|
||||
URL: root + "unsigned.xpi",
|
||||
size: 452,
|
||||
hashFunction: "sha256",
|
||||
hashValue: "9b9abf7ddfc1a6d7ffc7e0247481dcc202363e4445ad3494fb22036f1698c7f3",
|
||||
});
|
||||
|
||||
let stat = yield OS.File.stat(path);
|
||||
do_check_false(stat.isDir);
|
||||
|
||||
do_check_true(compareFiles(do_get_file("data/productaddons/unsigned.xpi"), new LocalFile(path)));
|
||||
|
||||
yield OS.File.remove(path);
|
||||
});
|
|
@ -23,6 +23,7 @@ skip-if = appname != "firefox"
|
|||
[test_provider_shutdown.js]
|
||||
[test_provider_unsafe_access_shutdown.js]
|
||||
[test_provider_unsafe_access_startup.js]
|
||||
[test_ProductAddonChecker.js]
|
||||
[test_shutdown.js]
|
||||
[test_system_reset.js]
|
||||
[test_XPIcancel.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче