aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/posixpath.py
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2024-11-13 22:59:32 +0000
committerGitHub <noreply@github.com>2024-11-13 22:59:32 +0000
commitfd4b5453df74e249987553b12c14ad75fafa4991 (patch)
treebffc6a81cdd6a05b097468aedf6b9206944d61eb /Lib/posixpath.py
parentc695e37a3f95c225ee08d1e882d23fa200b5ec34 (diff)
downloadcpython-fd4b5453df74e249987553b12c14ad75fafa4991.tar.gz
cpython-fd4b5453df74e249987553b12c14ad75fafa4991.zip
GH-118289: Fix handling of non-directories in `posixpath.realpath()` (#120127)
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.
Diffstat (limited to 'Lib/posixpath.py')
-rw-r--r--Lib/posixpath.py18
1 files changed, 14 insertions, 4 deletions
diff --git a/Lib/posixpath.py b/Lib/posixpath.py
index fccca4e066b..db72ded8826 100644
--- a/Lib/posixpath.py
+++ b/Lib/posixpath.py
@@ -412,6 +412,10 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
# very fast way of spelling list(reversed(...)).
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.
# Note: getcwd() returns a normalized and symlink-free path.
path = sep if filename.startswith(sep) else getcwd()
@@ -426,12 +430,13 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
# by *maxlinks*, this is used instead of *seen* to detect symlink loops.
link_count = 0
- while rest:
+ while part_count:
name = rest.pop()
if name is None:
# resolved symlink target
seen[rest.pop()] = path
continue
+ part_count -= 1
if not name or name == curdir:
# current dir
continue
@@ -444,8 +449,11 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
else:
newpath = path + sep + name
try:
- st = lstat(newpath)
- if not stat.S_ISLNK(st.st_mode):
+ st_mode = lstat(newpath).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
continue
elif maxlinks is not None:
@@ -487,7 +495,9 @@ def _realpath(filename, strict=False, sep=sep, curdir=curdir, pardir=pardir,
rest.append(newpath)
rest.append(None)
# 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