diff options
author | Barney Gale <barney.gale@gmail.com> | 2024-08-23 19:03:11 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-23 19:03:11 (GMT) |
commit | d7ae4dc5c14bc014ca0c056dab54c86ba8f395cb (patch) | |
tree | 3efe697566e955e5825c726586942c0d8a30e633 /Lib/pathlib | |
parent | bf1b5d323bdb6b1609c6f4b31dcaed621e5d0e2f (diff) | |
download | cpython-d7ae4dc5c14bc014ca0c056dab54c86ba8f395cb.zip cpython-d7ae4dc5c14bc014ca0c056dab54c86ba8f395cb.tar.gz cpython-d7ae4dc5c14bc014ca0c056dab54c86ba8f395cb.tar.bz2 |
GH-73991: Disallow copying directory into itself via `pathlib.Path.copy()` (#122924)
Diffstat (limited to 'Lib/pathlib')
-rw-r--r-- | Lib/pathlib/_abc.py | 43 |
1 files changed, 37 insertions, 6 deletions
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 720756c..9943ea4 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -14,6 +14,7 @@ resemble pathlib's PurePath and Path respectively. import functools import operator import posixpath +from errno import EINVAL from glob import _GlobberBase, _no_recurse_symlinks from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO from pathlib._os import copyfileobj @@ -564,14 +565,38 @@ class PathBase(PurePathBase): return (st.st_ino == other_st.st_ino and st.st_dev == other_st.st_dev) - def _samefile_safe(self, other_path): + def _ensure_different_file(self, other_path): """ - Like samefile(), but returns False rather than raising OSError. + Raise OSError(EINVAL) if both paths refer to the same file. """ try: - return self.samefile(other_path) + if not self.samefile(other_path): + return except (OSError, ValueError): - return False + return + err = OSError(EINVAL, "Source and target are the same file") + err.filename = str(self) + err.filename2 = str(other_path) + raise err + + def _ensure_distinct_path(self, other_path): + """ + Raise OSError(EINVAL) if the other path is within this path. + """ + # Note: there is no straightforward, foolproof algorithm to determine + # if one directory is within another (a particularly perverse example + # would be a single network share mounted in one location via NFS, and + # in another location via CIFS), so we simply checks whether the + # other path is lexically equal to, or within, this path. + if self == other_path: + err = OSError(EINVAL, "Source and target are the same path") + elif self in other_path.parents: + err = OSError(EINVAL, "Source path is a parent of target path") + else: + return + err.filename = str(self) + err.filename2 = str(other_path) + raise err def open(self, mode='r', buffering=-1, encoding=None, errors=None, newline=None): @@ -826,8 +851,7 @@ class PathBase(PurePathBase): """ Copy the contents of this file to the given target. """ - if self._samefile_safe(target): - raise OSError(f"{self!r} and {target!r} are the same file") + self._ensure_different_file(target) with self.open('rb') as source_f: try: with target.open('wb') as target_f: @@ -847,6 +871,13 @@ class PathBase(PurePathBase): """ if not isinstance(target, PathBase): target = self.with_segments(target) + try: + self._ensure_distinct_path(target) + except OSError as err: + if on_error is None: + raise + on_error(err) + return stack = [(self, target)] while stack: src, dst = stack.pop() |