diff --git a/lib/utils/io.py b/lib/utils/io.py index bb34b11e21bbf2c70c32cbc5609c405a8f120d86..bb0a1237c5a5e95999c042f818027369eaaace2c 100644 --- a/lib/utils/io.py +++ b/lib/utils/io.py @@ -647,15 +647,28 @@ def IsNormAbsPath(path): def IsBelowDir(root, other_path): """Check whether a path is below a root dir. - This works around the nasty byte-byte comparisation of commonprefix. + This works around the nasty byte-byte comparison of commonprefix. """ if not (os.path.isabs(root) and os.path.isabs(other_path)): raise ValueError("Provided paths '%s' and '%s' are not absolute" % (root, other_path)) - prepared_root = "%s%s" % (os.path.normpath(root), os.sep) - return os.path.commonprefix([prepared_root, - os.path.normpath(other_path)]) == prepared_root + + norm_other = os.path.normpath(other_path) + + if norm_other == os.sep: + # The root directory can never be below another path + return False + + norm_root = os.path.normpath(root) + + if norm_root == os.sep: + # This is the root directory, no need to add another slash + prepared_root = norm_root + else: + prepared_root = "%s%s" % (norm_root, os.sep) + + return os.path.commonprefix([prepared_root, norm_other]) == prepared_root def PathJoin(*args): diff --git a/test/ganeti.utils.io_unittest.py b/test/ganeti.utils.io_unittest.py index 458ecabf143aae8da500cc642bb188f14d3541e1..cffea24de1f9414ee5661edc145b17193169ba3e 100755 --- a/test/ganeti.utils.io_unittest.py +++ b/test/ganeti.utils.io_unittest.py @@ -630,10 +630,10 @@ class TestIsNormAbsPath(unittest.TestCase): def _pathTestHelper(self, path, result): if result: self.assert_(utils.IsNormAbsPath(path), - "Path %s should result absolute and normalized" % path) + msg="Path %s should result absolute and normalized" % path) else: self.assertFalse(utils.IsNormAbsPath(path), - "Path %s should not result absolute and normalized" % path) + msg="Path %s should not result absolute and normalized" % path) def testBase(self): self._pathTestHelper("/etc", True) @@ -642,6 +642,17 @@ class TestIsNormAbsPath(unittest.TestCase): self._pathTestHelper("/etc/../root", False) self._pathTestHelper("/etc/", False) + def testSlashes(self): + # Root directory + self._pathTestHelper("/", True) + + # POSIX' "implementation-defined" double slashes + self._pathTestHelper("//", True) + + # Three and more slashes count as one, so the path is not normalized + for i in range(3, 10): + self._pathTestHelper("/" * i, False) + class TestIsBelowDir(unittest.TestCase): """Testing case for IsBelowDir""" @@ -673,6 +684,25 @@ class TestIsBelowDir(unittest.TestCase): self.assertRaises(ValueError, utils.IsBelowDir, "/a/b/c", "d") self.assertRaises(ValueError, utils.IsBelowDir, "a/b/c", "/d") self.assertRaises(ValueError, utils.IsBelowDir, "a/b/c", "d") + self.assertRaises(ValueError, utils.IsBelowDir, "", "/") + self.assertRaises(ValueError, utils.IsBelowDir, "/", "") + + def testRoot(self): + self.assertFalse(utils.IsBelowDir("/", "/")) + + for i in ["/a", "/tmp", "/tmp/foo/bar", "/tmp/"]: + self.assertTrue(utils.IsBelowDir("/", i)) + + def testSlashes(self): + # In POSIX a double slash is "implementation-defined". + self.assertFalse(utils.IsBelowDir("//", "//")) + self.assertFalse(utils.IsBelowDir("//", "/tmp")) + self.assertTrue(utils.IsBelowDir("//tmp", "//tmp/x")) + + # Three (or more) slashes count as one + self.assertFalse(utils.IsBelowDir("/", "///")) + self.assertTrue(utils.IsBelowDir("/", "///tmp")) + self.assertTrue(utils.IsBelowDir("/tmp", "///tmp/a/b")) class TestPathJoin(unittest.TestCase):