diff --git a/lib/utils.py b/lib/utils.py index 6877dc704d6a44f389b2e166df47839bc6a846cb..393f4f4f396058596dcd9a5fff723be03f74e324 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -1936,6 +1936,36 @@ def IsNormAbsPath(path): return os.path.normpath(path) == path and os.path.isabs(path) +def PathJoin(*args): + """Safe-join a list of path components. + + Requirements: + - the first argument must be an absolute path + - no component in the path must have backtracking (e.g. /../), + since we check for normalization at the end + + @param args: the path components to be joined + @raise ValueError: for invalid paths + + """ + # ensure we're having at least one path passed in + assert args + # ensure the first component is an absolute and normalized path name + root = args[0] + if not IsNormAbsPath(root): + raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0])) + result = os.path.join(*args) + # ensure that the whole path is normalized + if not IsNormAbsPath(result): + raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args)) + # check that we're still under the original prefix + prefix = os.path.commonprefix([root, result]) + if prefix != root: + raise ValueError("Error: path joining resulted in different prefix" + " (%s != %s)" % (prefix, root)) + return result + + def TailFile(fname, lines=20): """Return the last lines from a file. diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py index 4d19f859985419a5ef19c7beacc20fa18348c26e..550bf25034ea68885d0cb9c543c5f7ee93e8062a 100755 --- a/test/ganeti.utils_unittest.py +++ b/test/ganeti.utils_unittest.py @@ -47,7 +47,7 @@ from ganeti.utils import IsProcessAlive, RunCmd, \ ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles, \ SetEtcHostsEntry, RemoveEtcHostsEntry, FirstFree, OwnIpAddress, \ TailFile, ForceDictType, SafeEncode, IsNormAbsPath, FormatTime, \ - UnescapeAndSplit, RunParts + UnescapeAndSplit, RunParts, PathJoin from ganeti.errors import LockError, UnitParseError, GenericError, \ ProgrammerError @@ -1260,5 +1260,22 @@ class TestUnescapeAndSplit(unittest.TestCase): self.failUnlessEqual(UnescapeAndSplit(sep.join(a), sep=sep), b) +class TestPathJoin(unittest.TestCase): + """Testing case for PathJoin""" + + def testBasicItems(self): + mlist = ["/a", "b", "c"] + self.failUnlessEqual(PathJoin(*mlist), "/".join(mlist)) + + def testNonAbsPrefix(self): + self.failUnlessRaises(ValueError, PathJoin, "a", "b") + + def testBackTrack(self): + self.failUnlessRaises(ValueError, PathJoin, "/a", "b/../c") + + def testMultiAbs(self): + self.failUnlessRaises(ValueError, PathJoin, "/a", "/b") + + if __name__ == '__main__': testutils.GanetiTestProgram()