mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bug #990669: os.path.realpath() will resolve symlinks before normalizing the
path, as normalizing the path may alter the meaning of the path if it contains symlinks. Also add tests for infinite symlink loops and parent symlinks that need to be resolved.
This commit is contained in:
parent
f9a098efe1
commit
4ec40648a5
3 changed files with 109 additions and 10 deletions
|
@ -400,9 +400,11 @@ def abspath(path):
|
||||||
def realpath(filename):
|
def realpath(filename):
|
||||||
"""Return the canonical path of the specified filename, eliminating any
|
"""Return the canonical path of the specified filename, eliminating any
|
||||||
symbolic links encountered in the path."""
|
symbolic links encountered in the path."""
|
||||||
filename = abspath(filename)
|
if isabs(filename):
|
||||||
|
bits = ['/'] + filename.split('/')[1:]
|
||||||
bits = ['/'] + filename.split('/')[1:]
|
else:
|
||||||
|
bits = filename.split('/')
|
||||||
|
|
||||||
for i in range(2, len(bits)+1):
|
for i in range(2, len(bits)+1):
|
||||||
component = join(*bits[0:i])
|
component = join(*bits[0:i])
|
||||||
# Resolve symbolic links.
|
# Resolve symbolic links.
|
||||||
|
@ -410,13 +412,13 @@ symbolic links encountered in the path."""
|
||||||
resolved = _resolve_link(component)
|
resolved = _resolve_link(component)
|
||||||
if resolved is None:
|
if resolved is None:
|
||||||
# Infinite loop -- return original component + rest of the path
|
# Infinite loop -- return original component + rest of the path
|
||||||
return join(*([component] + bits[i:]))
|
return abspath(join(*([component] + bits[i:])))
|
||||||
else:
|
else:
|
||||||
newpath = join(*([resolved] + bits[i:]))
|
newpath = join(*([resolved] + bits[i:]))
|
||||||
return realpath(newpath)
|
return realpath(newpath)
|
||||||
|
|
||||||
return filename
|
|
||||||
|
|
||||||
|
return abspath(filename)
|
||||||
|
|
||||||
|
|
||||||
def _resolve_link(path):
|
def _resolve_link(path):
|
||||||
"""Internal helper function. Takes a path and follows symlinks
|
"""Internal helper function. Takes a path and follows symlinks
|
||||||
|
|
|
@ -2,6 +2,12 @@ import unittest
|
||||||
from test import test_support
|
from test import test_support
|
||||||
|
|
||||||
import posixpath, os
|
import posixpath, os
|
||||||
|
from posixpath import realpath, abspath, join, dirname, basename
|
||||||
|
|
||||||
|
# An absolute path to a temporary filename for testing. We can't rely on TESTFN
|
||||||
|
# being an absolute path, so we need this.
|
||||||
|
|
||||||
|
ABSTFN = abspath(test_support.TESTFN)
|
||||||
|
|
||||||
class PosixPathTest(unittest.TestCase):
|
class PosixPathTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -389,9 +395,96 @@ class PosixPathTest(unittest.TestCase):
|
||||||
self.assertRaises(TypeError, posixpath.abspath)
|
self.assertRaises(TypeError, posixpath.abspath)
|
||||||
|
|
||||||
def test_realpath(self):
|
def test_realpath(self):
|
||||||
self.assert_("foo" in posixpath.realpath("foo"))
|
self.assert_("foo" in realpath("foo"))
|
||||||
|
|
||||||
self.assertRaises(TypeError, posixpath.realpath)
|
self.assertRaises(TypeError, posixpath.realpath)
|
||||||
|
|
||||||
|
if hasattr(os, "symlink"):
|
||||||
|
def test_realpath_basic(self):
|
||||||
|
# Basic operation.
|
||||||
|
try:
|
||||||
|
os.symlink(ABSTFN+"1", ABSTFN)
|
||||||
|
self.assertEqual(realpath(ABSTFN), ABSTFN+"1")
|
||||||
|
finally:
|
||||||
|
self.safe_remove(ABSTFN)
|
||||||
|
|
||||||
|
def test_realpath_symlink_loops(self):
|
||||||
|
# Bug #930024, return the path unchanged if we get into an infinite
|
||||||
|
# symlink loop.
|
||||||
|
try:
|
||||||
|
old_path = abspath('.')
|
||||||
|
os.symlink(ABSTFN, ABSTFN)
|
||||||
|
self.assertEqual(realpath(ABSTFN), ABSTFN)
|
||||||
|
|
||||||
|
os.symlink(ABSTFN+"1", ABSTFN+"2")
|
||||||
|
os.symlink(ABSTFN+"2", ABSTFN+"1")
|
||||||
|
self.assertEqual(realpath(ABSTFN+"1"), ABSTFN+"1")
|
||||||
|
self.assertEqual(realpath(ABSTFN+"2"), ABSTFN+"2")
|
||||||
|
|
||||||
|
# Test using relative path as well.
|
||||||
|
os.chdir(dirname(ABSTFN))
|
||||||
|
self.assertEqual(realpath(basename(ABSTFN)), ABSTFN)
|
||||||
|
finally:
|
||||||
|
os.chdir(old_path)
|
||||||
|
self.safe_remove(ABSTFN)
|
||||||
|
self.safe_remove(ABSTFN+"1")
|
||||||
|
self.safe_remove(ABSTFN+"2")
|
||||||
|
|
||||||
|
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
|
||||||
|
# /usr/doc with 'doc' being a symlink to /usr/share/doc. We call
|
||||||
|
# realpath("a"). This should return /usr/share/doc/a/.
|
||||||
|
try:
|
||||||
|
old_path = abspath('.')
|
||||||
|
os.mkdir(ABSTFN)
|
||||||
|
os.mkdir(ABSTFN + "/y")
|
||||||
|
os.symlink(ABSTFN + "/y", ABSTFN + "/k")
|
||||||
|
|
||||||
|
os.chdir(ABSTFN + "/k")
|
||||||
|
self.assertEqual(realpath("a"), ABSTFN + "/y/a")
|
||||||
|
finally:
|
||||||
|
os.chdir(old_path)
|
||||||
|
self.safe_remove(ABSTFN + "/k")
|
||||||
|
self.safe_rmdir(ABSTFN + "/y")
|
||||||
|
self.safe_rmdir(ABSTFN)
|
||||||
|
|
||||||
|
def test_realpath_resolve_before_normalizing(self):
|
||||||
|
# Bug #990669: Symbolic links should be resolved before we
|
||||||
|
# normalize the path. E.g.: if we have directories 'a', 'k' and 'y'
|
||||||
|
# in the following hierarchy:
|
||||||
|
# a/k/y
|
||||||
|
#
|
||||||
|
# and a symbolic link 'link-y' pointing to 'y' in directory 'a',
|
||||||
|
# then realpath("link-y/..") should return 'k', not 'a'.
|
||||||
|
try:
|
||||||
|
old_path = abspath('.')
|
||||||
|
os.mkdir(ABSTFN)
|
||||||
|
os.mkdir(ABSTFN + "/k")
|
||||||
|
os.mkdir(ABSTFN + "/k/y")
|
||||||
|
os.symlink(ABSTFN + "/k/y", ABSTFN + "/link-y")
|
||||||
|
|
||||||
|
# Absolute path.
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/link-y/.."), ABSTFN + "/k")
|
||||||
|
# Relative path.
|
||||||
|
os.chdir(dirname(ABSTFN))
|
||||||
|
self.assertEqual(realpath(basename(ABSTFN) + "/link-y/.."), ABSTFN + "/k")
|
||||||
|
finally:
|
||||||
|
os.chdir(old_path)
|
||||||
|
self.safe_remove(ABSTFN + "/link-y")
|
||||||
|
self.safe_rmdir(ABSTFN + "/k/y")
|
||||||
|
self.safe_rmdir(ABSTFN + "/k")
|
||||||
|
self.safe_rmdir(ABSTFN)
|
||||||
|
|
||||||
|
# Convenience functions for removing temporary files.
|
||||||
|
def pass_os_error(self, func, filename):
|
||||||
|
try: func(filename)
|
||||||
|
except OSError: pass
|
||||||
|
|
||||||
|
def safe_remove(self, filename):
|
||||||
|
self.pass_os_error(os.remove, filename)
|
||||||
|
|
||||||
|
def safe_rmdir(self, dirname):
|
||||||
|
self.pass_os_error(os.rmdir, dirname)
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
test_support.run_unittest(PosixPathTest)
|
test_support.run_unittest(PosixPathTest)
|
||||||
|
|
|
@ -41,6 +41,10 @@ Extension modules
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- bug #990669: os.path.realpath() will resolve symlinks before normalizing the
|
||||||
|
path, as normalizing the path may alter the meaning of the path if it
|
||||||
|
contains symlinks.
|
||||||
|
|
||||||
- bug #851123: shutil.copyfile will raise an exception when trying to copy a
|
- bug #851123: shutil.copyfile will raise an exception when trying to copy a
|
||||||
file onto a link to itself. Thanks Gregory Ball.
|
file onto a link to itself. Thanks Gregory Ball.
|
||||||
|
|
||||||
|
@ -77,7 +81,7 @@ C API
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
- bug 990669: os.path.normpath may alter the meaning of a path if it contains
|
- bug #990669: os.path.normpath may alter the meaning of a path if it contains
|
||||||
symbolic links. This has been documented in a comment since 1992, but is now in
|
symbolic links. This has been documented in a comment since 1992, but is now in
|
||||||
the library reference as well.
|
the library reference as well.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue