diff --git a/CHANGES.md b/CHANGES.md index 1e75fb5856..f29834a3f7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,6 +29,8 @@ +- Fix symlink handling, properly catch and ignore symlinks that point outside of root + (#4161) - Fix cache mtime logic that resulted in false positive cache hits (#4128) ### Packaging diff --git a/src/black/__init__.py b/src/black/__init__.py index 735ba713b8..e3cbaab5f1 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -49,6 +49,7 @@ find_user_pyproject_toml, gen_python_files, get_gitignore, + get_root_relative_path, normalize_path_maybe_ignore, parse_pyproject_toml, path_is_excluded, @@ -700,7 +701,10 @@ def get_sources( # Compare the logic here to the logic in `gen_python_files`. if is_stdin or path.is_file(): - root_relative_path = path.absolute().relative_to(root).as_posix() + root_relative_path = get_root_relative_path(path, root, report) + + if root_relative_path is None: + continue root_relative_path = "/" + root_relative_path diff --git a/src/black/files.py b/src/black/files.py index 858303ca1a..65951efdbe 100644 --- a/src/black/files.py +++ b/src/black/files.py @@ -259,14 +259,7 @@ def normalize_path_maybe_ignore( try: abspath = path if path.is_absolute() else Path.cwd() / path normalized_path = abspath.resolve() - try: - root_relative_path = normalized_path.relative_to(root).as_posix() - except ValueError: - if report: - report.path_ignored( - path, f"is a symbolic link that points outside {root}" - ) - return None + root_relative_path = get_root_relative_path(normalized_path, root, report) except OSError as e: if report: @@ -276,6 +269,21 @@ def normalize_path_maybe_ignore( return root_relative_path +def get_root_relative_path( + path: Path, + root: Path, + report: Optional[Report] = None, +) -> Optional[str]: + """Returns the file path relative to the 'root' directory""" + try: + root_relative_path = path.absolute().relative_to(root).as_posix() + except ValueError: + if report: + report.path_ignored(path, f"is a symbolic link that points outside {root}") + return None + return root_relative_path + + def _path_is_ignored( root_relative_path: str, root: Path, diff --git a/tests/test_black.py b/tests/test_black.py index 0af5fd2a1f..2b5fab5d28 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -2592,6 +2592,20 @@ def test_symlinks(self) -> None: outside_root_symlink.resolve.assert_called_once() ignored_symlink.resolve.assert_not_called() + def test_get_sources_with_stdin_symlink_outside_root( + self, + ) -> None: + path = THIS_DIR / "data" / "include_exclude_tests" + stdin_filename = str(path / "b/exclude/a.py") + outside_root_symlink = Path("/target_directory/a.py") + with patch("pathlib.Path.resolve", return_value=outside_root_symlink): + assert_collected_sources( + root=Path("target_directory/"), + src=["-"], + expected=[], + stdin_filename=stdin_filename, + ) + @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None)) def test_get_sources_with_stdin(self) -> None: src = ["-"]