Index: Lib/posixpath.py =================================================================== --- Lib/posixpath.py (revisione 75449) +++ Lib/posixpath.py (copia locale) @@ -344,47 +344,63 @@ def realpath(filename): """Return the canonical path of the specified filename, eliminating any -symbolic links encountered in the path.""" + symbolic links encountered in the path.""" + # we first split the path in tokens if isabs(filename): - bits = ['/'] + filename.split('/')[1:] + nodes = ['/'] + filename.split('/')[1:] else: - bits = [''] + filename.split('/') + nodes = [''] + filename.split('/') - for i in range(2, len(bits)+1): - component = join(*bits[0:i]) - # Resolve symbolic links. + tokens_path = [] + # then we iterate on every token in the path. + # if token1 is a link, read it and add it to the tokens_path + # if token1+token2 is a link, read it and add to the tokens_path + # and so on until we reach the end of the loop + # Assuming test/one/that_dir points to ../two (test/two) + # Example: ['', 'test', 'one', 'that_dir', '..'] + # then: ['test/one'] + # then: ['test/one/that_dir'] <-- this is a symlink + # then: ['test/two/'] + + for index, node in enumerate(nodes): + tokens_path.append(node) + component = join(*tokens_path) if islink(component): - resolved = _resolve_link(component) - if resolved is None: - # Infinite loop -- return original component + rest of the path - return abspath(join(*([component] + bits[i:]))) + resolved_link = _resolve_link(component) + tokens_path = [resolved_link] + if resolved_link is None: + return abspath(component) else: - newpath = join(*([resolved] + bits[i:])) - return realpath(newpath) - + newpath = realpath(join(*(tokens_path + nodes[index+1:]))) + return newpath return abspath(filename) - -def _resolve_link(path): - """Internal helper function. Takes a path and follows symlinks - until we either arrive at something that isn't a symlink, or +def _follow_link(link): + """Internal helper function. Takes a path and follow it until the + we either arrive at something that isn't a symlink, or encounter a path we've seen before (meaning that there's a loop). """ - paths_seen = set() - while islink(path): - if path in paths_seen: - # Already seen this path, so we must have a symlink loop + read_links = [] + while islink(link): + # already seen this path. we're in a symlink loop + if link in read_links: return None - paths_seen.add(path) - # Resolve where the link points to - resolved = os.readlink(path) - if not isabs(resolved): - dir = dirname(path) - path = normpath(join(dir, resolved)) - else: - path = normpath(resolved) - return path + link = os.readlink(link) + read_links.append(link) + return link +def _resolve_link(path): + """Internal helper function. Takes a path, follows the symlinks + and returns a normalized path between the parent directory and the + path computed following the link. + """ + link = _follow_link(path) + if link is None: + return None + dir_ = dirname(path) + path_ = normpath(join(dir_, link)) + return path_ + supports_unicode_filenames = False def relpath(path, start=curdir): Index: Lib/test/test_posixpath.py =================================================================== --- Lib/test/test_posixpath.py (revisione 75449) +++ Lib/test/test_posixpath.py (copia locale) @@ -423,6 +423,33 @@ test_support.unlink(ABSTFN+"1") test_support.unlink(ABSTFN+"2") + def test_realpath_symlink_cycles_with_inner_loops(self): + #Bug: http://bugs.python.org/issue6975 + #Given two paths: one/ and two/ in the root 'test_dirs' + #and given a symlink in two/, called "this_dir" + #pointing to ../two + #if we create a symlink in one/ pointing to + #../two/this_dir/this_dir/ + #realpath('test_dirs/one/that_dir') should return + #'/test_dirs/two', not /test_dirs/two/two + + try: + old_path = abspath('.') + os.mkdir(ABSTFN) + os.mkdir(ABSTFN + '/one') + os.mkdir(ABSTFN + '/two') + os.symlink('../two', ABSTFN + '/two/this_dir') + os.symlink('../two/this_dir/this_dir/this_dir/this_dir', + ABSTFN + '/one/that_dir') + self.assertEqual(realpath(ABSTFN + '/one/that_dir'), + ABSTFN + '/two') + finally: + test_support.unlink(ABSTFN + '/two/this_dir') + test_support.unlink(ABSTFN + '/one/that_dir') + safe_rmdir(ABSTFN + '/one') + safe_rmdir(ABSTFN + '/two') + safe_rmdir(ABSTFN) + def test_realpath_resolve_parents(self): # We also need to resolve any symlinks in the parents of a relative # path passed to realpath. E.g.: current working directory is