From 81d928b36b90127572e64a5d9b4e40fa9a669ea3 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Wed, 28 Dec 2011 23:36:15 +1300 Subject: [PATCH] Bug 685155 - Add-on Manager should treat symbolic links the same as proxy files. r=dtownsend --- toolkit/mozapps/extensions/XPIProvider.jsm | 49 ++-- .../extensions/test/xpcshell/head_addons.js | 2 +- .../extensions/test/xpcshell/test_proxies.js | 248 ++++++++++++++++++ .../extensions/test/xpcshell/xpcshell.ini | 1 + 4 files changed, 279 insertions(+), 21 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/test_proxies.js diff --git a/toolkit/mozapps/extensions/XPIProvider.jsm b/toolkit/mozapps/extensions/XPIProvider.jsm index 0288121449d1..f95925c3afda 100644 --- a/toolkit/mozapps/extensions/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/XPIProvider.jsm @@ -294,7 +294,7 @@ SafeInstallOperation.prototype = { _installDirEntry: function(aDirEntry, aTargetDirectory, aCopy) { try { - if (aDirEntry.isDirectory()) + if (aDirEntry.isDirectory() && !aDirEntry.isSymlink()) this._installDirectory(aDirEntry, aTargetDirectory, aCopy); else this._installFile(aDirEntry, aTargetDirectory, aCopy); @@ -354,7 +354,7 @@ SafeInstallOperation.prototype = { rollback: function() { while (this._installedFiles.length > 0) { let move = this._installedFiles.pop(); - if (move.newFile.isDirectory()) { + if (move.newFile.isDirectory() && !move.newFile.isSymlink()) { let oldDir = move.oldFile.parent.clone(); oldDir.append(move.oldFile.leafName); oldDir.create(Ci.nsILocalFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); @@ -1276,7 +1276,7 @@ function recursiveRemove(aFile) { return; } catch (e) { - if (!aFile.isDirectory()) { + if (!aFile.isDirectory() || aFile.isSymlink()) { ERROR("Failed to remove file " + aFile.path, e); throw e; } @@ -7855,24 +7855,33 @@ DirectoryInstallLocation.prototype = { * @return a nsILocalFile object representing the linked directory. */ _readDirectoryFromFile: function DirInstallLocation__readDirectoryFromFile(aFile) { - let fis = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); - fis.init(aFile, -1, -1, false); - let line = { value: "" }; - if (fis instanceof Ci.nsILineInputStream) - fis.readLine(line); - fis.close(); - if (line.value) { - let linkedDirectory = Cc["@mozilla.org/file/local;1"]. - createInstance(Ci.nsILocalFile); + if (aFile.isSymlink()) { + var linkedDirectory = aFile.clone(); + linkedDirectory.normalize(); + } + else { + let fis = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + fis.init(aFile, -1, -1, false); + let line = { value: "" }; + if (fis instanceof Ci.nsILineInputStream) + fis.readLine(line); + fis.close(); - try { - linkedDirectory.initWithPath(line.value); - } - catch (e) { - linkedDirectory.setRelativeDescriptor(aFile.parent, line.value); - } + if (line.value) { + linkedDirectory = Cc["@mozilla.org/file/local;1"]. + createInstance(Ci.nsILocalFile); + try { + linkedDirectory.initWithPath(line.value); + } + catch (e) { + linkedDirectory.setRelativeDescriptor(aFile.parent, line.value); + } + } + } + + if (linkedDirectory) { if (!linkedDirectory.exists()) { WARN("File pointer " + aFile.path + " points to " + linkedDirectory.path + " which does not exist"); @@ -7922,7 +7931,7 @@ DirectoryInstallLocation.prototype = { continue; } - if (entry.isFile() && !directLoad) { + if (!directLoad && (entry.isFile() || entry.isSymlink())) { let newEntry = this._readDirectoryFromFile(entry); if (!newEntry) { LOG("Deleting stale pointer file " + entry.path); diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 937c567fd2c0..fba7cc026faf 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -518,7 +518,7 @@ function createInstallRDF(aData) { rdf += '\n'; ["id", "version", "type", "internalName", "updateURL", "updateKey", - "optionsURL", "optionsType", "aboutURL", "iconURL", "icon64URL", + "optionsURL", "optionsType", "aboutURL", "iconURL", "icon64URL", "unpack", "skinnable", "bootstrap", "strictCompatibility"].forEach(function(aProp) { if (aProp in aData) rdf += "" + escapeXML(aData[aProp]) + "\n"; diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_proxies.js b/toolkit/mozapps/extensions/test/xpcshell/test_proxies.js new file mode 100644 index 000000000000..2bdd8fcd1734 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_proxies.js @@ -0,0 +1,248 @@ +/* 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" + } +]; + +var METADATA = { + version: "2.0", + targetApplications: [{ + id: "xpcshell@tests.mozilla.org", + minVersion: "2", + maxVersion: "2" + }] +} + +const ios = AM_Cc["@mozilla.org/network/io-service;1"].getService(AM_Ci.nsIIOService); + +const LocalFile = Components.Constructor("@mozilla.org/file/local;1", + "nsILocalFile", "initWithPath"); +const Process = Components.Constructor("@mozilla.org/process/util;1", + "nsIProcess", "init"); + +const gHaveSymlinks = !("nsIWindowsRegKey" in AM_Ci); +if (gHaveSymlinks) + var gLink = findExecutable("ln"); + + +// Unfortunately, GRE provides no way to create or manipulate symlinks +// directly. Fallback to the ln(1) executable. +function createSymlink(aSource, aDest) { + if (aSource instanceof AM_Ci.nsIFile) + aSource = aSource.path; + + let process = Process(gLink); + process.run(true, ["-s", aSource, aDest.path], 3); + do_check_eq(process.exitValue, 0, Components.stack.caller); + return process.exitValue; +} + +function findExecutable(aName) { + if (environment.PATH) { + for each (let path in environment.PATH.split(":")) { + try { + let file = LocalFile(path); + file.append(aName); + + if (file.exists() && file.isFile() && file.isExecutable()) + return file; + } + catch (e) {} + } + } + return null; +} + +function writeFile(aData, aFile) { + if (!aFile.parent.exists()) + aFile.parent.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755); + + var fos = AM_Cc["@mozilla.org/network/file-output-stream;1"]. + createInstance(AM_Ci.nsIFileOutputStream); + fos.init(aFile, + FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE, + FileUtils.PERMS_FILE, 0); + fos.write(aData, aData.length); + fos.close(); +} + +function checkAddonsExist() { + for each (let addon in ADDONS) { + let file = addon.directory.clone(); + file.append("install.rdf"); + do_check_true(file.exists(), Components.stack.caller); + } +} + + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + + +function run_test() { + createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2"); + + // Make sure we can create symlinks on platforms that support them. + do_check_true(!gHaveSymlinks || gLink != null); + + add_test(run_proxy_tests); + + if (gHaveSymlinks) + add_test(run_symlink_tests); + + run_next_test(); +} + +function run_proxy_tests() { + + for each (let addon in ADDONS) { + addon.directory = gTmpD.clone(); + addon.directory.append(addon.id); + + addon.proxyFile = profileDir.clone(); + addon.proxyFile.append(addon.dirId || addon.id); + + METADATA.id = addon.id; + METADATA.name = addon.id; + writeInstallRDFToDir(METADATA, addon.directory); + + if (addon.type == "proxy") { + writeFile(addon.directory.path, addon.proxyFile) + } + else if (addon.type == "symlink" && gHaveSymlinks) { + createSymlink(addon.directory, addon.proxyFile) + } + } + + startupManager(); + + // Check that all add-ons original sources still exist after invalid + // add-ons have been removed at startup. + checkAddonsExist(); + + AddonManager.getAddonsByIDs(ADDONS.map(function(addon) addon.id), + function(addons) { + try { + for (let [i, addon] in Iterator(addons)) { + // 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" && !gHaveSymlinks); + do_check_eq(addon == null, + ADDONS[i].dirId != null + || ADDONS[i].type == "symlink" && !gHaveSymlinks); + + if (addon != null) { + // Check that proxied add-ons do not have upgrade permissions. + do_check_eq(addon.permissions & AddonManager.PERM_CAN_UPGRADE, 0); + + // Check that getResourceURI points to the right place. + do_check_eq(ios.newFileURI(ADDONS[i].directory).spec, + addon.getResourceURI().spec); + + let file = ADDONS[i].directory.clone(); + file.append("install.rdf"); + do_check_eq(ios.newFileURI(file).spec, + addon.getResourceURI("install.rdf").spec); + + addon.uninstall(); + } + } + + // Check that original sources still exist after explicit uninstall. + restartManager(); + checkAddonsExist(); + + shutdownManager(); + + // Check that all of the proxy files have been removed and remove + // the original targets. + for each (let addon in ADDONS) { + do_check_false(addon.proxyFile.exists()); + addon.directory.remove(true); + } + } + catch (e) { + do_throw(e); + } + + run_next_test(); + }); +} + +function run_symlink_tests() { + // Check that symlinks are not followed out of a directory tree + // when deleting an add-on. + + METADATA.id = "unpacked@test.mozilla.org"; + METADATA.name = METADATA.id; + METADATA.unpack = "true"; + + let tempDirectory = gTmpD.clone(); + tempDirectory.append(METADATA.id); + + let tempFile = tempDirectory.clone(); + tempFile.append("test.txt"); + tempFile.create(AM_Ci.nsIFile.NORMAL_FILE_TYPE, 0644); + + let addonDirectory = profileDir.clone(); + addonDirectory.append(METADATA.id); + + let symlink = addonDirectory.clone(); + symlink.append(tempDirectory.leafname); + createSymlink(tempDirectory, symlink); + + // Make sure that the symlink was created properly. + let file = symlink.clone(); + file.append(tempFile.leafName); + file.normalize(); + do_check_true(file.equals(tempFile)); + + writeInstallRDFToDir(METADATA, addonDirectory); + + startupManager(); + + AddonManager.getAddonByID(METADATA.id, + function(addon) { + do_check_neq(addon, null); + + addon.uninstall(); + + restartManager(); + shutdownManager(); + + // Check that the install directory is gone. + do_check_false(addonDirectory.exists()); + + // Check that the temp file is not gone. + do_check_true(tempFile.exists()); + + tempDirectory.remove(true); + + run_next_test(); + }); +} + diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index ac7f6d480bcb..ef0f044d9f46 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -183,6 +183,7 @@ skip-if = os == "android" [test_plugins.js] # Bug 676992: test consistently fails on Android fail-if = os == "android" +[test_proxies.js] [test_registry.js] [test_safemode.js] [test_startup.js]