diff options
author | Barney Gale <barney.gale@gmail.com> | 2024-11-13 22:59:32 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-13 22:59:32 +0000 |
commit | fd4b5453df74e249987553b12c14ad75fafa4991 (patch) | |
tree | bffc6a81cdd6a05b097468aedf6b9206944d61eb /Lib/posixpath.py | |
parent | c695e37a3f95c225ee08d1e882d23fa200b5ec34 (diff) | |
download | cpython-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.py | 18 |
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 |