diff options
Diffstat (limited to 'toolkit/modules/tests/xpcshell/test_GMPInstallManager.js')
-rw-r--r-- | toolkit/modules/tests/xpcshell/test_GMPInstallManager.js | 1030 |
1 files changed, 1030 insertions, 0 deletions
diff --git a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js new file mode 100644 index 0000000000..b39b19f0ae --- /dev/null +++ b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js @@ -0,0 +1,1030 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +var Cm = Components.manager; +const URL_HOST = "http://localhost"; +const PR_USEC_PER_MSEC = 1000; + +var GMPScope = ChromeUtils.import( + "resource://gre/modules/GMPInstallManager.jsm", + null +); +var GMPInstallManager = GMPScope.GMPInstallManager; + +const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); +const { FileUtils } = ChromeUtils.import( + "resource://gre/modules/FileUtils.jsm" +); +const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); +const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); +const { Preferences } = ChromeUtils.import( + "resource://gre/modules/Preferences.jsm" +); +const { UpdateUtils } = ChromeUtils.import( + "resource://gre/modules/UpdateUtils.jsm" +); +const GMPUtils = ChromeUtils.import("resource://gre/modules/GMPUtils.jsm"); + +var ProductAddonCheckerScope = ChromeUtils.import( + "resource://gre/modules/addons/ProductAddonChecker.jsm", + null +); + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +Services.prefs.setBoolPref("media.gmp-manager.updateEnabled", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); + Services.prefs.clearUserPref("media.gmp-manager.updateEnabled"); +}); + +do_get_profile(); + +function run_test() { + Preferences.set("media.gmp.log.dump", true); + Preferences.set("media.gmp.log.level", 0); + run_next_test(); +} + +/** + * Tests that the helper used for preferences works correctly + */ +add_task(async function test_prefs() { + let addon1 = "addon1", + addon2 = "addon2"; + + GMPScope.GMPPrefs.setString( + GMPScope.GMPPrefs.KEY_URL, + "http://not-really-used" + ); + GMPScope.GMPPrefs.setString( + GMPScope.GMPPrefs.KEY_URL_OVERRIDE, + "http://not-really-used-2" + ); + GMPScope.GMPPrefs.setInt(GMPScope.GMPPrefs.KEY_PLUGIN_LAST_UPDATE, 1, addon1); + GMPScope.GMPPrefs.setString( + GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, + "2", + addon1 + ); + GMPScope.GMPPrefs.setInt(GMPScope.GMPPrefs.KEY_PLUGIN_LAST_UPDATE, 3, addon2); + GMPScope.GMPPrefs.setInt(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, 4, addon2); + GMPScope.GMPPrefs.setBool( + GMPScope.GMPPrefs.KEY_PLUGIN_AUTOUPDATE, + false, + addon2 + ); + GMPScope.GMPPrefs.setBool(GMPScope.GMPPrefs.KEY_CERT_CHECKATTRS, true); + + Assert.equal( + GMPScope.GMPPrefs.getString(GMPScope.GMPPrefs.KEY_URL), + "http://not-really-used" + ); + Assert.equal( + GMPScope.GMPPrefs.getString(GMPScope.GMPPrefs.KEY_URL_OVERRIDE), + "http://not-really-used-2" + ); + Assert.equal( + GMPScope.GMPPrefs.getInt( + GMPScope.GMPPrefs.KEY_PLUGIN_LAST_UPDATE, + "", + addon1 + ), + 1 + ); + Assert.equal( + GMPScope.GMPPrefs.getString( + GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, + "", + addon1 + ), + "2" + ); + Assert.equal( + GMPScope.GMPPrefs.getInt( + GMPScope.GMPPrefs.KEY_PLUGIN_LAST_UPDATE, + "", + addon2 + ), + 3 + ); + Assert.equal( + GMPScope.GMPPrefs.getInt(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, "", addon2), + 4 + ); + Assert.equal( + GMPScope.GMPPrefs.getBool( + GMPScope.GMPPrefs.KEY_PLUGIN_AUTOUPDATE, + undefined, + addon2 + ), + false + ); + Assert.ok(GMPScope.GMPPrefs.getBool(GMPScope.GMPPrefs.KEY_CERT_CHECKATTRS)); + GMPScope.GMPPrefs.setBool( + GMPScope.GMPPrefs.KEY_PLUGIN_AUTOUPDATE, + true, + addon2 + ); +}); + +/** + * Tests that an uninit without a check works fine + */ +add_task(async function test_checkForAddons_uninitWithoutCheck() { + let installManager = new GMPInstallManager(); + installManager.uninit(); +}); + +/** + * Tests that an uninit without an install works fine + */ +add_test(function test_checkForAddons_uninitWithoutInstall() { + overrideXHR(200, ""); + let installManager = new GMPInstallManager(); + let promise = installManager.checkForAddons(); + promise.then(res => { + Assert.ok(res.usedFallback); + installManager.uninit(); + run_next_test(); + }); +}); + +/** + * Tests that no response returned rejects + */ +add_test(function test_checkForAddons_noResponse() { + overrideXHR(200, ""); + let installManager = new GMPInstallManager(); + let promise = installManager.checkForAddons(); + promise.then(res => { + Assert.ok(res.usedFallback); + installManager.uninit(); + run_next_test(); + }); +}); + +/** + * Tests that no addons element returned resolves with no addons + */ +add_task(async function test_checkForAddons_noAddonsElement() { + overrideXHR(200, "<updates></updates>"); + let installManager = new GMPInstallManager(); + let res = await installManager.checkForAddons(); + Assert.equal(res.addons.length, 0); + installManager.uninit(); +}); + +/** + * Tests that empty addons element returned resolves with no addons + */ +add_task(async function test_checkForAddons_emptyAddonsElement() { + overrideXHR(200, "<updates><addons/></updates>"); + let installManager = new GMPInstallManager(); + let res = await installManager.checkForAddons(); + Assert.equal(res.addons.length, 0); + installManager.uninit(); +}); + +/** + * Tests that a response with the wrong root element rejects + */ +add_test(function test_checkForAddons_wrongResponseXML() { + overrideXHR(200, "<digits_of_pi>3.141592653589793....</digits_of_pi>"); + let installManager = new GMPInstallManager(); + let promise = installManager.checkForAddons(); + promise.then(res => { + Assert.ok(res.usedFallback); + installManager.uninit(); + run_next_test(); + }); +}); + +/** + * Tests that a 404 error works as expected + */ +add_test(function test_checkForAddons_404Error() { + overrideXHR(404, ""); + let installManager = new GMPInstallManager(); + let promise = installManager.checkForAddons(); + promise.then(res => { + Assert.ok(res.usedFallback); + installManager.uninit(); + run_next_test(); + }); +}); + +/** + * Tests that a xhr abort() works as expected + */ +add_test(function test_checkForAddons_abort() { + let overriddenXhr = overrideXHR(200, "", { dropRequest: true }); + let installManager = new GMPInstallManager(); + let promise = installManager.checkForAddons(); + + // Since the XHR is created in checkForAddons asynchronously, + // we need to delay aborting till the XHR is running. + // Since checkForAddons returns a Promise already only after + // the abort is triggered, we can't use that, and instead + // we'll use a fake timer. + setTimeout(() => { + overriddenXhr.abort(); + }, 100); + + promise.then(res => { + Assert.ok(res.usedFallback); + installManager.uninit(); + run_next_test(); + }); +}); + +/** + * Tests that a defensive timeout works as expected + */ +add_test(function test_checkForAddons_timeout() { + overrideXHR(200, "", { dropRequest: true, timeout: true }); + let installManager = new GMPInstallManager(); + let promise = installManager.checkForAddons(); + promise.then(res => { + Assert.ok(res.usedFallback); + installManager.uninit(); + run_next_test(); + }); +}); + +/** + * Tests that we throw correctly in case of ssl certification error. + */ +add_test(function test_checkForAddons_bad_ssl() { + // + // Add random stuff that cause CertUtil to require https. + // + let PREF_KEY_URL_OVERRIDE_BACKUP = Preferences.get( + GMPScope.GMPPrefs.KEY_URL_OVERRIDE, + "" + ); + Preferences.reset(GMPScope.GMPPrefs.KEY_URL_OVERRIDE); + + let CERTS_BRANCH_DOT_ONE = GMPScope.GMPPrefs.KEY_CERTS_BRANCH + ".1"; + let PREF_CERTS_BRANCH_DOT_ONE_BACKUP = Preferences.get( + CERTS_BRANCH_DOT_ONE, + "" + ); + Services.prefs.setCharPref(CERTS_BRANCH_DOT_ONE, "funky value"); + + overrideXHR(200, ""); + let installManager = new GMPInstallManager(); + let promise = installManager.checkForAddons(); + promise.then(res => { + Assert.ok(res.usedFallback); + installManager.uninit(); + if (PREF_KEY_URL_OVERRIDE_BACKUP) { + Preferences.set( + GMPScope.GMPPrefs.KEY_URL_OVERRIDE, + PREF_KEY_URL_OVERRIDE_BACKUP + ); + } + if (PREF_CERTS_BRANCH_DOT_ONE_BACKUP) { + Preferences.set(CERTS_BRANCH_DOT_ONE, PREF_CERTS_BRANCH_DOT_ONE_BACKUP); + } + run_next_test(); + }); +}); + +/** + * Tests that gettinga a funky non XML response works as expected + */ +add_test(function test_checkForAddons_notXML() { + overrideXHR(200, "3.141592653589793...."); + let installManager = new GMPInstallManager(); + let promise = installManager.checkForAddons(); + + promise.then(res => { + Assert.ok(res.usedFallback); + installManager.uninit(); + run_next_test(); + }); +}); + +/** + * Tests that getting a response with a single addon works as expected + */ +add_task(async function test_checkForAddons_singleAddon() { + let responseXML = + '<?xml version="1.0"?>' + + "<updates>" + + " <addons>" + + ' <addon id="gmp-gmpopenh264"' + + ' URL="http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip"' + + ' hashFunction="sha256"' + + ' hashValue="1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' version="1.1"/>' + + " </addons>" + + "</updates>"; + overrideXHR(200, responseXML); + let installManager = new GMPInstallManager(); + let res = await installManager.checkForAddons(); + Assert.equal(res.addons.length, 1); + let gmpAddon = res.addons[0]; + Assert.equal(gmpAddon.id, "gmp-gmpopenh264"); + Assert.equal(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip"); + Assert.equal(gmpAddon.hashFunction, "sha256"); + Assert.equal( + gmpAddon.hashValue, + "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee" + ); + Assert.equal(gmpAddon.version, "1.1"); + Assert.ok(gmpAddon.isValid); + Assert.ok(!gmpAddon.isInstalled); + installManager.uninit(); +}); + +/** + * Tests that getting a response with a single addon with the optional size + * attribute parses as expected. + */ +add_task(async function test_checkForAddons_singleAddonWithSize() { + let responseXML = + '<?xml version="1.0"?>' + + "<updates>" + + " <addons>" + + ' <addon id="openh264-plugin-no-at-symbol"' + + ' URL="http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip"' + + ' hashFunction="sha256"' + + ' size="42"' + + ' hashValue="1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' version="1.1"/>' + + " </addons>" + + "</updates>"; + overrideXHR(200, responseXML); + let installManager = new GMPInstallManager(); + let res = await installManager.checkForAddons(); + Assert.equal(res.addons.length, 1); + let gmpAddon = res.addons[0]; + Assert.equal(gmpAddon.id, "openh264-plugin-no-at-symbol"); + Assert.equal(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip"); + Assert.equal(gmpAddon.hashFunction, "sha256"); + Assert.equal( + gmpAddon.hashValue, + "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee" + ); + Assert.equal(gmpAddon.size, 42); + Assert.equal(gmpAddon.version, "1.1"); + Assert.ok(gmpAddon.isValid); + Assert.ok(!gmpAddon.isInstalled); + installManager.uninit(); +}); + +/** + * Tests that checking for multiple addons work correctly. + * Also tests that invalid addons work correctly. + */ +add_task( + async function test_checkForAddons_multipleAddonNoUpdatesSomeInvalid() { + let responseXML = + '<?xml version="1.0"?>' + + "<updates>" + + " <addons>" + + // valid openh264 + ' <addon id="gmp-gmpopenh264"' + + ' URL="http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip"' + + ' hashFunction="sha256"' + + ' hashValue="1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' version="1.1"/>' + + // valid not openh264 + ' <addon id="NOT-gmp-gmpopenh264"' + + ' URL="http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip"' + + ' hashFunction="sha512"' + + ' hashValue="141592656f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' version="9.1"/>' + + // noid + ' <addon notid="NOT-gmp-gmpopenh264"' + + ' URL="http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip"' + + ' hashFunction="sha512"' + + ' hashValue="141592656f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' version="9.1"/>' + + // no URL + ' <addon id="NOT-gmp-gmpopenh264"' + + ' notURL="http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip"' + + ' hashFunction="sha512"' + + ' hashValue="141592656f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' version="9.1"/>' + + // no hash function + ' <addon id="NOT-gmp-gmpopenh264"' + + ' URL="http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip"' + + ' nothashFunction="sha512"' + + ' hashValue="141592656f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' version="9.1"/>' + + // no hash function + ' <addon id="NOT-gmp-gmpopenh264"' + + ' URL="http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip"' + + ' hashFunction="sha512"' + + ' nothashValue="141592656f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' version="9.1"/>' + + // not version + ' <addon id="NOT-gmp-gmpopenh264"' + + ' URL="http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip"' + + ' hashFunction="sha512"' + + ' hashValue="141592656f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' notversion="9.1"/>' + + " </addons>" + + "</updates>"; + overrideXHR(200, responseXML); + let installManager = new GMPInstallManager(); + let res = await installManager.checkForAddons(); + Assert.equal(res.addons.length, 7); + let gmpAddon = res.addons[0]; + Assert.equal(gmpAddon.id, "gmp-gmpopenh264"); + Assert.equal(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip"); + Assert.equal(gmpAddon.hashFunction, "sha256"); + Assert.equal( + gmpAddon.hashValue, + "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee" + ); + Assert.equal(gmpAddon.version, "1.1"); + Assert.ok(gmpAddon.isValid); + Assert.ok(!gmpAddon.isInstalled); + + gmpAddon = res.addons[1]; + Assert.equal(gmpAddon.id, "NOT-gmp-gmpopenh264"); + Assert.equal( + gmpAddon.URL, + "http://127.0.0.1:8011/NOT-gmp-gmpopenh264-1.1.zip" + ); + Assert.equal(gmpAddon.hashFunction, "sha512"); + Assert.equal( + gmpAddon.hashValue, + "141592656f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee" + ); + Assert.equal(gmpAddon.version, "9.1"); + Assert.ok(gmpAddon.isValid); + Assert.ok(!gmpAddon.isInstalled); + + for (let i = 2; i < res.addons.length; i++) { + Assert.ok(!res.addons[i].isValid); + Assert.ok(!res.addons[i].isInstalled); + } + installManager.uninit(); + } +); + +/** + * Tests that checking for addons when there are also updates available + * works as expected. + */ +add_task(async function test_checkForAddons_updatesWithAddons() { + let responseXML = + '<?xml version="1.0"?>' + + " <updates>" + + ' <update type="minor" displayVersion="33.0a1" appVersion="33.0a1" platformVersion="33.0a1" buildID="20140628030201">' + + ' <patch type="complete" URL="http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/2014/06/2014-06-28-03-02-01-mozilla-central/firefox-33.0a1.en-US.mac.complete.mar" hashFunction="sha512" hashValue="f3f90d71dff03ae81def80e64bba3e4569da99c9e15269f731c2b167c4fc30b3aed9f5fee81c19614120230ca333e73a5e7def1b8e45d03135b2069c26736219" size="85249896"/>' + + " </update>" + + " <addons>" + + ' <addon id="gmp-gmpopenh264"' + + ' URL="http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip"' + + ' hashFunction="sha256"' + + ' hashValue="1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' version="1.1"/>' + + " </addons>" + + "</updates>"; + overrideXHR(200, responseXML); + let installManager = new GMPInstallManager(); + let res = await installManager.checkForAddons(); + Assert.equal(res.addons.length, 1); + let gmpAddon = res.addons[0]; + Assert.equal(gmpAddon.id, "gmp-gmpopenh264"); + Assert.equal(gmpAddon.URL, "http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip"); + Assert.equal(gmpAddon.hashFunction, "sha256"); + Assert.equal( + gmpAddon.hashValue, + "1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee" + ); + Assert.equal(gmpAddon.version, "1.1"); + Assert.ok(gmpAddon.isValid); + Assert.ok(!gmpAddon.isInstalled); + installManager.uninit(); +}); + +/** + * Tests that installing found addons works as expected + */ +async function test_checkForAddons_installAddon( + id, + includeSize, + wantInstallReject +) { + info( + "Running installAddon for id: " + + id + + ", includeSize: " + + includeSize + + " and wantInstallReject: " + + wantInstallReject + ); + let httpServer = new HttpServer(); + let dir = FileUtils.getDir("TmpD", [], true); + httpServer.registerDirectory("/", dir); + httpServer.start(-1); + let testserverPort = httpServer.identity.primaryPort; + let zipFileName = "test_" + id + "_GMP.zip"; + + let zipURL = URL_HOST + ":" + testserverPort + "/" + zipFileName; + info("zipURL: " + zipURL); + + let data = "e~=0.5772156649"; + let zipFile = createNewZipFile(zipFileName, data); + let hashFunc = "sha256"; + let expectedDigest = await ProductAddonCheckerScope.computeHash( + hashFunc, + zipFile.path + ); + let fileSize = zipFile.fileSize; + if (wantInstallReject) { + fileSize = 1; + } + + let responseXML = + '<?xml version="1.0"?>' + + "<updates>" + + " <addons>" + + ' <addon id="' + + id + + '-gmp-gmpopenh264"' + + ' URL="' + + zipURL + + '"' + + ' hashFunction="' + + hashFunc + + '"' + + ' hashValue="' + + expectedDigest + + '"' + + (includeSize ? ' size="' + fileSize + '"' : "") + + ' version="1.1"/>' + + " </addons>" + + "</updates>"; + + overrideXHR(200, responseXML); + let installManager = new GMPInstallManager(); + let res = await installManager.checkForAddons(); + Assert.equal(res.addons.length, 1); + let gmpAddon = res.addons[0]; + Assert.ok(!gmpAddon.isInstalled); + + try { + let extractedPaths = await installManager.installAddon(gmpAddon); + if (wantInstallReject) { + Assert.ok(false); // installAddon() should have thrown. + } + Assert.equal(extractedPaths.length, 1); + let extractedPath = extractedPaths[0]; + + info("Extracted path: " + extractedPath); + + let extractedFile = Cc["@mozilla.org/file/local;1"].createInstance( + Ci.nsIFile + ); + extractedFile.initWithPath(extractedPath); + Assert.ok(extractedFile.exists()); + let readData = readStringFromFile(extractedFile); + Assert.equal(readData, data); + + // Make sure the prefs are set correctly + Assert.ok( + !!GMPScope.GMPPrefs.getInt( + GMPScope.GMPPrefs.KEY_PLUGIN_LAST_UPDATE, + "", + gmpAddon.id + ) + ); + Assert.equal( + GMPScope.GMPPrefs.getString( + GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, + "", + gmpAddon.id + ), + "1.1" + ); + Assert.equal( + GMPScope.GMPPrefs.getString( + GMPScope.GMPPrefs.KEY_PLUGIN_ABI, + "", + gmpAddon.id + ), + UpdateUtils.ABI + ); + // Make sure it reports as being installed + Assert.ok(gmpAddon.isInstalled); + + // Cleanup + extractedFile.parent.remove(true); + zipFile.remove(false); + httpServer.stop(function() {}); + installManager.uninit(); + } catch (ex) { + zipFile.remove(false); + if (!wantInstallReject) { + do_throw("install update should not reject " + ex.message); + } + } +} + +add_task(test_checkForAddons_installAddon.bind(null, "1", true, false)); +add_task(test_checkForAddons_installAddon.bind(null, "2", false, false)); +add_task(test_checkForAddons_installAddon.bind(null, "3", true, true)); + +/** + * Tests simpleCheckAndInstall when autoupdate is disabled for a GMP + */ +add_task(async function test_simpleCheckAndInstall_autoUpdateDisabled() { + GMPScope.GMPPrefs.setBool( + GMPScope.GMPPrefs.KEY_PLUGIN_AUTOUPDATE, + false, + GMPUtils.OPEN_H264_ID + ); + let responseXML = + '<?xml version="1.0"?>' + + "<updates>" + + " <addons>" + + // valid openh264 + ' <addon id="gmp-gmpopenh264"' + + ' URL="http://127.0.0.1:8011/gmp-gmpopenh264-1.1.zip"' + + ' hashFunction="sha256"' + + ' hashValue="1118b90d6f645eefc2b99af17bae396636ace1e33d079c88de715177584e2aee"' + + ' version="1.1"/>' + + " </addons>" + + "</updates>"; + + overrideXHR(200, responseXML); + let installManager = new GMPInstallManager(); + let result = await installManager.simpleCheckAndInstall(); + Assert.equal(result.status, "nothing-new-to-install"); + Preferences.reset(GMPScope.GMPPrefs.KEY_UPDATE_LAST_CHECK); + GMPScope.GMPPrefs.setBool( + GMPScope.GMPPrefs.KEY_PLUGIN_AUTOUPDATE, + true, + GMPUtils.OPEN_H264_ID + ); +}); + +/** + * Tests simpleCheckAndInstall nothing to install + */ +add_task(async function test_simpleCheckAndInstall_nothingToInstall() { + let responseXML = '<?xml version="1.0"?><updates></updates>'; + + overrideXHR(200, responseXML); + let installManager = new GMPInstallManager(); + let result = await installManager.simpleCheckAndInstall(); + Assert.equal(result.status, "nothing-new-to-install"); +}); + +/** + * Tests simpleCheckAndInstall too frequent + */ +add_task(async function test_simpleCheckAndInstall_tooFrequent() { + let responseXML = '<?xml version="1.0"?><updates></updates>'; + + overrideXHR(200, responseXML); + let installManager = new GMPInstallManager(); + let result = await installManager.simpleCheckAndInstall(); + Assert.equal(result.status, "too-frequent-no-check"); +}); + +/** + * Tests that installing addons when there is no server works as expected + */ +add_test(function test_installAddon_noServer() { + let zipFileName = "test_GMP.zip"; + let zipURL = URL_HOST + ":0/" + zipFileName; + + let responseXML = + '<?xml version="1.0"?>' + + "<updates>" + + " <addons>" + + ' <addon id="gmp-gmpopenh264"' + + ' URL="' + + zipURL + + '"' + + ' hashFunction="sha256"' + + ' hashValue="11221cbda000347b054028b527a60e578f919cb10f322ef8077d3491c6fcb474"' + + ' version="1.1"/>' + + " </addons>" + + "</updates>"; + + overrideXHR(200, responseXML); + let installManager = new GMPInstallManager(); + let checkPromise = installManager.checkForAddons(); + checkPromise.then( + res => { + Assert.equal(res.addons.length, 1); + let gmpAddon = res.addons[0]; + + GMPInstallManager.overrideLeaveDownloadedZip = true; + let installPromise = installManager.installAddon(gmpAddon); + installPromise.then( + extractedPaths => { + do_throw("No server for install should reject"); + }, + err => { + Assert.ok(!!err); + installManager.uninit(); + run_next_test(); + } + ); + }, + () => { + do_throw("check should not reject for install no server"); + } + ); +}); + +/*** + * Tests GMPExtractor (an internal component of GMPInstallManager) to ensure + * it handles paths with certain characters. + */ + +add_task(async function test_GMPExtractor_paths() { + let GMPExtractor = GMPScope.GMPExtractor; + registerCleanupFunction(function() { + // Must stop holding on to the zip file using the JAR cache: + let zipFile = new FileUtils.File( + OS.Path.join(tempDir.path, "dummy_gmp.zip") + ); + Services.obs.notifyObservers(zipFile, "flush-cache-entry"); + extractedDir.remove(/* recursive */ true); + tempDir.remove(/* recursive */ true); + }); + // Create a dir with the following in the name + // - # -- this is used to delimit URI fragments and tests that + // we escape any internal URIs appropriately. + // - 猫 -- ensure we handle non-ascii characters appropriately. + let tempDirName = "TmpDir#猫"; + let tempDir = FileUtils.getDir("TmpD", [tempDirName], true); + let zipPath = OS.Path.join(tempDir.path, "dummy_gmp.zip"); + await OS.File.copy("zips/dummy_gmp.zip", zipPath, { noOverwrite: false }); + // The path inside the profile dir we'll extract to. Make sure we handle + // the characters there too. + let relativeExtractPath = "extracted#猫"; + let extractor = new GMPExtractor(zipPath, relativeExtractPath); + let extractedPaths = await extractor.install(); + // extractedPaths should contain the files extracted. In this case we + // should have a single file extracted to our profile dir -- the zip + // contains two files, but one should be skipped by the extraction logic. + Assert.equal(extractedPaths.length, 1, "One file should be extracted"); + Assert.ok( + extractedPaths[0].includes("dummy_file.txt"), + "dummy_file.txt should be on extracted path" + ); + Assert.ok( + !extractedPaths[0].includes("verified_contents.json"), + "verified_contents.json should not be on extracted path" + ); + let extractedDir = FileUtils.getDir("ProfD", [relativeExtractPath], false); + Assert.ok( + extractedDir.exists(), + "Extraction should have created a directory" + ); + let extractedFile = FileUtils.getDir( + "ProfD", + [relativeExtractPath, "dummy_file.txt"], + false + ); + Assert.ok( + extractedFile.exists(), + "Extraction should have created dummy_file.txt" + ); + let unextractedFile = FileUtils.getDir( + "ProfD", + [relativeExtractPath, "verified_contents.json"], + false + ); + Assert.ok( + !unextractedFile.exists(), + "Extraction should not have created verified_contents.json" + ); +}); + +/** + * Returns the read stream into a string + */ +function readStringFromInputStream(inputStream) { + let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(inputStream); + let text = sis.read(sis.available()); + sis.close(); + return text; +} + +/** + * Reads a string of text from a file. + * This function only works with ASCII text. + */ +function readStringFromFile(file) { + if (!file.exists()) { + info("readStringFromFile - file doesn't exist: " + file.path); + return null; + } + let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fis.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); + return readStringFromInputStream(fis); +} + +/** + * Bare bones XMLHttpRequest implementation for testing onprogress, onerror, + * and onload nsIDomEventListener handleEvent. + */ +function makeHandler(aVal) { + if (typeof aVal == "function") { + return { handleEvent: aVal }; + } + return aVal; +} +/** + * Constructs a mock xhr which is used for testing different aspects + * of responses. + */ +function xhr(inputStatus, inputResponse, options) { + this.inputStatus = inputStatus; + this.inputResponse = inputResponse; + this.status = 0; + this.responseXML = null; + this._aborted = false; + this._onabort = null; + this._onprogress = null; + this._onerror = null; + this._onload = null; + this._onloadend = null; + this._ontimeout = null; + this._url = null; + this._method = null; + this._timeout = 0; + this._notified = false; + this._options = options || {}; +} +xhr.prototype = { + overrideMimeType(aMimetype) {}, + setRequestHeader(aHeader, aValue) {}, + status: null, + channel: { set notificationCallbacks(aVal) {} }, + open(aMethod, aUrl) { + this.channel.originalURI = Services.io.newURI(aUrl); + this._method = aMethod; + this._url = aUrl; + }, + abort() { + this._dropRequest = true; + this._notify(["abort", "loadend"]); + }, + responseXML: null, + responseText: null, + send(aBody) { + executeSoon(() => { + try { + if (this._options.dropRequest) { + if (this._timeout > 0 && this._options.timeout) { + this._notify(["timeout", "loadend"]); + } + return; + } + this.status = this.inputStatus; + this.responseText = this.inputResponse; + try { + let parser = new DOMParser(); + this.responseXML = parser.parseFromString( + this.inputResponse, + "application/xml" + ); + } catch (e) { + this.responseXML = null; + } + if (this.inputStatus === 200) { + this._notify(["load", "loadend"]); + } else { + this._notify(["error", "loadend"]); + } + } catch (ex) { + do_throw(ex); + } + }); + }, + set onabort(aValue) { + this._onabort = makeHandler(aValue); + }, + get onabort() { + return this._onabort; + }, + set onprogress(aValue) { + this._onprogress = makeHandler(aValue); + }, + get onprogress() { + return this._onprogress; + }, + set onerror(aValue) { + this._onerror = makeHandler(aValue); + }, + get onerror() { + return this._onerror; + }, + set onload(aValue) { + this._onload = makeHandler(aValue); + }, + get onload() { + return this._onload; + }, + set onloadend(aValue) { + this._onloadend = makeHandler(aValue); + }, + get onloadend() { + return this._onloadend; + }, + set ontimeout(aValue) { + this._ontimeout = makeHandler(aValue); + }, + get ontimeout() { + return this._ontimeout; + }, + set timeout(aValue) { + this._timeout = aValue; + }, + _notify(events) { + if (this._notified) { + return; + } + this._notified = true; + for (let item of events) { + let k = "on" + item; + if (this[k]) { + info("Notifying " + item); + let e = { + target: this, + type: item, + }; + this[k](e); + } else { + info("Notifying " + item + ", but there are no listeners"); + } + } + }, + addEventListener(aEvent, aValue, aCapturing) { + // eslint-disable-next-line no-eval + eval("this._on" + aEvent + " = aValue"); + }, + get wrappedJSObject() { + return this; + }, +}; + +/** + * Helper used to overrideXHR requests (no matter to what URL) with the + * specified status and response. + * @param status The status you want to get back when an XHR request is made + * @param response The response you want to get back when an XHR request is made + */ +function overrideXHR(status, response, options) { + overrideXHR.myxhr = new xhr(status, response, options); + ProductAddonCheckerScope.CreateXHR = function() { + return overrideXHR.myxhr; + }; + return overrideXHR.myxhr; +} + +/** + * Creates a new zip file containing a file with the specified data + * @param zipName The name of the zip file + * @param data The data to go inside the zip for the filename entry1.info + */ +function createNewZipFile(zipName, data) { + // Create a zip file which will be used for extracting + let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + stream.setData(data, data.length); + let zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance( + Ci.nsIZipWriter + ); + let zipFile = FileUtils.getFile("TmpD", [zipName]); + if (zipFile.exists()) { + zipFile.remove(false); + } + // From prio.h + const PR_RDWR = 0x04; + const PR_CREATE_FILE = 0x08; + const PR_TRUNCATE = 0x20; + zipWriter.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + zipWriter.addEntryStream( + "entry1.info", + Date.now() * PR_USEC_PER_MSEC, + Ci.nsIZipWriter.COMPRESSION_BEST, + stream, + false + ); + zipWriter.close(); + stream.close(); + info("zip file created on disk at: " + zipFile.path); + return zipFile; +} |