/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // Tests the semantics of extension proxy files and symlinks var ADDONS = [ { id: "proxy1@tests.mozilla.org", dirId: "proxy1@tests.mozilla.com", type: "proxy", }, { id: "proxy2@tests.mozilla.org", type: "proxy", }, { id: "symlink1@tests.mozilla.org", dirId: "symlink1@tests.mozilla.com", type: "symlink", }, { id: "symlink2@tests.mozilla.org", type: "symlink", }, ]; const gHaveSymlinks = AppConstants.platform != "win"; function createSymlink(aSource, aDest) { if (aSource instanceof Ci.nsIFile) { aSource = aSource.path; } if (aDest instanceof Ci.nsIFile) { aDest = aDest.path; } const ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); ln.initWithPath("/bin/ln"); const lnProcess = Cc["@mozilla.org/process/util;1"].createInstance( Ci.nsIProcess ); lnProcess.init(ln); const args = ["-s", aSource, aDest]; lnProcess.run(true, args, args.length); Assert.equal(lnProcess.exitValue, 0); } async function promiseWriteFile(aFile, aData) { if (!(await IOUtils.exists(aFile.parent.path))) { await IOUtils.makeDirectory(aFile.parent.path); } return IOUtils.writeUTF8(aFile.path, aData); } function checkAddonsExist() { for (let addon of ADDONS) { let file = addon.directory.clone(); file.append("manifest.json"); Assert.ok(file.exists()); } } const profileDir = gProfD.clone(); profileDir.append("extensions"); function run_test() { createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2"); // Unpacked extensions are never signed, so this can only run with // signature checks disabled. Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false); add_task(run_proxy_tests); if (gHaveSymlinks) { add_task(run_symlink_tests); } run_next_test(); } async function run_proxy_tests() { if (!gHaveSymlinks) { ADDONS = ADDONS.filter(a => a.type != "symlink"); } for (let addon of ADDONS) { addon.directory = gTmpD.clone(); addon.directory.append(addon.id); addon.proxyFile = profileDir.clone(); addon.proxyFile.append(addon.dirId || addon.id); let files = ExtensionTestCommon.generateFiles({ manifest: { name: addon.id, browser_specific_settings: { gecko: { id: addon.id } }, }, }); let path = PathUtils.join(gTmpD.path, addon.id); await AddonTestUtils.promiseWriteFilesToDir(path, files); if (addon.type == "proxy") { await promiseWriteFile(addon.proxyFile, addon.directory.path); } else if (addon.type == "symlink") { await createSymlink(addon.directory, addon.proxyFile); } } await promiseStartupManager(); // Check that all add-ons original sources still exist after invalid // add-ons have been removed at startup. checkAddonsExist(); let addons = await AddonManager.getAddonsByIDs(ADDONS.map(addon => addon.id)); try { for (let [i, addon] of addons.entries()) { // Ensure that valid proxied add-ons were installed properly on // platforms that support the installation method. print( ADDONS[i].id, ADDONS[i].dirId, ADDONS[i].dirId != null, ADDONS[i].type == "symlink" ); Assert.equal(addon == null, ADDONS[i].dirId != null); if (addon != null) { let fixURL = url => { if (AppConstants.platform == "macosx") { return url.replace(RegExp(`^file:///private/`), "file:///"); } return url; }; // Check that proxied add-ons do not have upgrade permissions. Assert.equal(addon.permissions & AddonManager.PERM_CAN_UPGRADE, 0); // Check that getResourceURI points to the right place. Assert.equal( Services.io.newFileURI(ADDONS[i].directory).spec, fixURL(addon.getResourceURI().spec), `Base resource URL resolves as expected` ); let file = ADDONS[i].directory.clone(); file.append("manifest.json"); Assert.equal( Services.io.newFileURI(file).spec, fixURL(addon.getResourceURI("manifest.json").spec), `Resource URLs resolve as expected` ); await addon.uninstall(); } } // Check that original sources still exist after explicit uninstall. await promiseRestartManager(); checkAddonsExist(); await promiseShutdownManager(); // Check that all of the proxy files have been removed and remove // the original targets. for (let addon of ADDONS) { equal( addon.proxyFile.exists(), addon.dirId != null, `Proxy file ${addon.proxyFile.path} should exist?` ); addon.directory.remove(true); try { addon.proxyFile.remove(false); } catch (e) {} } } catch (e) { do_throw(e); } } // Check that symlinks are not followed out of a directory tree // when deleting an add-on. async function run_symlink_tests() { const ID = "unpacked@test.mozilla.org"; let tempDirectory = gTmpD.clone(); tempDirectory.append(ID); let tempFile = tempDirectory.clone(); tempFile.append("test.txt"); tempFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); let addonDirectory = profileDir.clone(); addonDirectory.append(ID); let files = ExtensionTestCommon.generateFiles({ manifest: { browser_specific_settings: { gecko: { id: ID } } }, }); await AddonTestUtils.promiseWriteFilesToDir(addonDirectory.path, files); let symlink = addonDirectory.clone(); symlink.append(tempDirectory.leafName); await createSymlink(tempDirectory, symlink); // Make sure that the symlink was created properly. let file = symlink.clone(); file.append(tempFile.leafName); file.normalize(); Assert.equal(file.path.replace(/^\/private\//, "/"), tempFile.path); await promiseStartupManager(); let addon = await AddonManager.getAddonByID(ID); Assert.notEqual(addon, null); await addon.uninstall(); await promiseRestartManager(); await promiseShutdownManager(); // Check that the install directory is gone. Assert.ok(!addonDirectory.exists()); // Check that the temp file is not gone. Assert.ok(tempFile.exists()); tempDirectory.remove(true); }