mirror of
https://github.com/python/cpython.git
synced 2025-07-23 19:25:40 +00:00
[3.13] GH-118289: Fix handling of non-directories in posixpath.realpath()
(GH-120127) (#126815)
In strict mode, raise `NotADirectoryError` if we encounter a non-directory
while we still have path parts left to process.
We use a `part_count` variable rather than `len(rest)` because the `rest`
stack also contains markers for unresolved symlinks.
(cherry picked from commit fd4b5453df
)
This commit is contained in:
parent
ad1b23bf29
commit
b66728da05
3 changed files with 76 additions and 4 deletions
|
@ -22,6 +22,7 @@ defpath = '/bin:/usr/bin'
|
||||||
altsep = None
|
altsep = None
|
||||||
devnull = '/dev/null'
|
devnull = '/dev/null'
|
||||||
|
|
||||||
|
import errno
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import stat
|
import stat
|
||||||
|
@ -408,6 +409,10 @@ symbolic links encountered in the path."""
|
||||||
# very fast way of spelling list(reversed(...)).
|
# very fast way of spelling list(reversed(...)).
|
||||||
rest = filename.split(sep)[::-1]
|
rest = filename.split(sep)[::-1]
|
||||||
|
|
||||||
|
# Number of unprocessed parts in 'rest'. This can differ from len(rest)
|
||||||
|
# later, because 'rest' might contain markers for unresolved symlinks.
|
||||||
|
part_count = len(rest)
|
||||||
|
|
||||||
# The resolved path, which is absolute throughout this function.
|
# The resolved path, which is absolute throughout this function.
|
||||||
# Note: getcwd() returns a normalized and symlink-free path.
|
# Note: getcwd() returns a normalized and symlink-free path.
|
||||||
path = sep if filename.startswith(sep) else getcwd()
|
path = sep if filename.startswith(sep) else getcwd()
|
||||||
|
@ -418,12 +423,13 @@ symbolic links encountered in the path."""
|
||||||
# the same links.
|
# the same links.
|
||||||
seen = {}
|
seen = {}
|
||||||
|
|
||||||
while rest:
|
while part_count:
|
||||||
name = rest.pop()
|
name = rest.pop()
|
||||||
if name is None:
|
if name is None:
|
||||||
# resolved symlink target
|
# resolved symlink target
|
||||||
seen[rest.pop()] = path
|
seen[rest.pop()] = path
|
||||||
continue
|
continue
|
||||||
|
part_count -= 1
|
||||||
if not name or name == curdir:
|
if not name or name == curdir:
|
||||||
# current dir
|
# current dir
|
||||||
continue
|
continue
|
||||||
|
@ -436,8 +442,11 @@ symbolic links encountered in the path."""
|
||||||
else:
|
else:
|
||||||
newpath = path + sep + name
|
newpath = path + sep + name
|
||||||
try:
|
try:
|
||||||
st = os.lstat(newpath)
|
st_mode = os.lstat(newpath).st_mode
|
||||||
if not stat.S_ISLNK(st.st_mode):
|
if not stat.S_ISLNK(st_mode):
|
||||||
|
if strict and part_count and not stat.S_ISDIR(st_mode):
|
||||||
|
raise OSError(errno.ENOTDIR, os.strerror(errno.ENOTDIR),
|
||||||
|
newpath)
|
||||||
path = newpath
|
path = newpath
|
||||||
continue
|
continue
|
||||||
if newpath in seen:
|
if newpath in seen:
|
||||||
|
@ -469,7 +478,9 @@ symbolic links encountered in the path."""
|
||||||
rest.append(newpath)
|
rest.append(newpath)
|
||||||
rest.append(None)
|
rest.append(None)
|
||||||
# Push the unresolved symlink target parts onto the stack.
|
# Push the unresolved symlink target parts onto the stack.
|
||||||
rest.extend(target.split(sep)[::-1])
|
target_parts = target.split(sep)[::-1]
|
||||||
|
rest.extend(target_parts)
|
||||||
|
part_count += len(target_parts)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
|
@ -695,6 +695,65 @@ class PosixPathTest(unittest.TestCase):
|
||||||
os.chmod(ABSTFN, 0o755, follow_symlinks=False)
|
os.chmod(ABSTFN, 0o755, follow_symlinks=False)
|
||||||
os.unlink(ABSTFN)
|
os.unlink(ABSTFN)
|
||||||
|
|
||||||
|
@skip_if_ABSTFN_contains_backslash
|
||||||
|
def test_realpath_nonterminal_file(self):
|
||||||
|
try:
|
||||||
|
with open(ABSTFN, 'w') as f:
|
||||||
|
f.write('test_posixpath wuz ere')
|
||||||
|
self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN)
|
||||||
|
self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN)
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/", strict=False), ABSTFN)
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/", strict=True)
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/.", strict=False), ABSTFN)
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.", strict=True)
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/..", strict=False), dirname(ABSTFN))
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..", strict=True)
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/subdir", strict=False), ABSTFN + "/subdir")
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir", strict=True)
|
||||||
|
finally:
|
||||||
|
os_helper.unlink(ABSTFN)
|
||||||
|
|
||||||
|
@os_helper.skip_unless_symlink
|
||||||
|
@skip_if_ABSTFN_contains_backslash
|
||||||
|
def test_realpath_nonterminal_symlink_to_file(self):
|
||||||
|
try:
|
||||||
|
with open(ABSTFN + "1", 'w') as f:
|
||||||
|
f.write('test_posixpath wuz ere')
|
||||||
|
os.symlink(ABSTFN + "1", ABSTFN)
|
||||||
|
self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN + "1")
|
||||||
|
self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN + "1")
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/", strict=False), ABSTFN + "1")
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/", strict=True)
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/.", strict=False), ABSTFN + "1")
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.", strict=True)
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/..", strict=False), dirname(ABSTFN))
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..", strict=True)
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/subdir", strict=False), ABSTFN + "1/subdir")
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir", strict=True)
|
||||||
|
finally:
|
||||||
|
os_helper.unlink(ABSTFN)
|
||||||
|
|
||||||
|
@os_helper.skip_unless_symlink
|
||||||
|
@skip_if_ABSTFN_contains_backslash
|
||||||
|
def test_realpath_nonterminal_symlink_to_symlinks_to_file(self):
|
||||||
|
try:
|
||||||
|
with open(ABSTFN + "2", 'w') as f:
|
||||||
|
f.write('test_posixpath wuz ere')
|
||||||
|
os.symlink(ABSTFN + "2", ABSTFN + "1")
|
||||||
|
os.symlink(ABSTFN + "1", ABSTFN)
|
||||||
|
self.assertEqual(realpath(ABSTFN, strict=False), ABSTFN + "2")
|
||||||
|
self.assertEqual(realpath(ABSTFN, strict=True), ABSTFN + "2")
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/", strict=False), ABSTFN + "2")
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/", strict=True)
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/.", strict=False), ABSTFN + "2")
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/.", strict=True)
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/..", strict=False), dirname(ABSTFN))
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/..", strict=True)
|
||||||
|
self.assertEqual(realpath(ABSTFN + "/subdir", strict=False), ABSTFN + "2/subdir")
|
||||||
|
self.assertRaises(NotADirectoryError, realpath, ABSTFN + "/subdir", strict=True)
|
||||||
|
finally:
|
||||||
|
os_helper.unlink(ABSTFN)
|
||||||
|
|
||||||
def test_relpath(self):
|
def test_relpath(self):
|
||||||
(real_getcwd, os.getcwd) = (os.getcwd, lambda: r"/home/user/bar")
|
(real_getcwd, os.getcwd) = (os.getcwd, lambda: r"/home/user/bar")
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
:func:`!posixpath.realpath` now raises :exc:`NotADirectoryError` when *strict*
|
||||||
|
mode is enabled and a non-directory path with a trailing slash is supplied.
|
Loading…
Add table
Add a link
Reference in a new issue