Skip to content

validate inputs to is_hidden #429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 36 additions & 20 deletions jupyter_core/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ def exists(path: str) -> bool:
return True


def is_file_hidden_win(abs_path: str, stat_res: Optional[Any] = None) -> bool:
def is_file_hidden_win(abs_path: str | Path, stat_res: Optional[Any] = None) -> bool:
"""Is a file hidden?

This only checks the file itself; it should be called in combination with
Expand All @@ -449,7 +449,8 @@ def is_file_hidden_win(abs_path: str, stat_res: Optional[Any] = None) -> bool:
The result of calling stat() on abs_path. If not passed, this function
will call stat() internally.
"""
if Path(abs_path).name.startswith("."):
abs_path = Path(abs_path)
if abs_path.name.startswith("."):
return True

if stat_res is None:
Expand Down Expand Up @@ -478,7 +479,7 @@ def is_file_hidden_win(abs_path: str, stat_res: Optional[Any] = None) -> bool:
return False


def is_file_hidden_posix(abs_path: str, stat_res: Optional[Any] = None) -> bool:
def is_file_hidden_posix(abs_path: str | Path, stat_res: Optional[Any] = None) -> bool:
"""Is a file hidden?

This only checks the file itself; it should be called in combination with
Expand All @@ -494,12 +495,13 @@ def is_file_hidden_posix(abs_path: str, stat_res: Optional[Any] = None) -> bool:
The result of calling stat() on abs_path. If not passed, this function
will call stat() internally.
"""
if Path(abs_path).name.startswith("."):
abs_path = Path(abs_path)
if abs_path.name.startswith("."):
return True

if stat_res is None or stat.S_ISLNK(stat_res.st_mode):
try:
stat_res = Path(abs_path).stat()
stat_res = abs_path.stat()
except OSError as e:
if e.errno == errno.ENOENT:
return False
Expand All @@ -524,7 +526,7 @@ def is_file_hidden_posix(abs_path: str, stat_res: Optional[Any] = None) -> bool:
is_file_hidden = is_file_hidden_posix


def is_hidden(abs_path: str, abs_root: str = "") -> bool:
def is_hidden(abs_path: str | Path, abs_root: str | Path = "") -> bool:
"""Is a file hidden or contained in a hidden directory?

This will start with the rightmost path element and work backwards to the
Expand All @@ -538,42 +540,56 @@ def is_hidden(abs_path: str, abs_root: str = "") -> bool:

Parameters
----------
abs_path : unicode
abs_path : str or Path
The absolute path to check for hidden directories.
abs_root : unicode
abs_root : str or Path
The absolute path of the root directory in which hidden directories
should be checked for.
"""
abs_path = os.path.normpath(abs_path)
abs_root = os.path.normpath(abs_root)
abs_path = Path(os.path.normpath(abs_path))
if abs_root:
abs_root = Path(os.path.normpath(abs_root))
else:
abs_root = list(abs_path.parents)[-1]

if abs_path == abs_root:
# root itself is never hidden
return False

# check that arguments are valid
if not abs_path.is_absolute():
_msg = f"{abs_path=} is not absolute. abs_path must be absolute."
raise ValueError(_msg)
if not abs_root.is_absolute():
_msg = f"{abs_root=} is not absolute. abs_root must be absolute."
raise ValueError(_msg)
if not abs_path.is_relative_to(abs_root):
_msg = (
f"{abs_path=} is not a subdirectory of {abs_root=}. abs_path must be within abs_root."
)
raise ValueError(_msg)

if is_file_hidden(abs_path):
return True

if not abs_root:
abs_root = abs_path.split(os.sep, 1)[0] + os.sep
inside_root = abs_path[len(abs_root) :]
if any(part.startswith(".") for part in Path(inside_root).parts):
relative_path = abs_path.relative_to(abs_root)
if any(part.startswith(".") for part in relative_path.parts):
return True

# check UF_HIDDEN on any location up to root.
# is_file_hidden() already checked the file, so start from its parent dir
path = str(Path(abs_path).parent)
while path and path.startswith(abs_root) and path != abs_root:
if not Path(path).exists():
path = str(Path(path).parent)
for parent in abs_path.parents:
if not parent.exists():
continue
if parent == abs_root:
break
try:
# may fail on Windows junctions
st = os.lstat(path)
st = parent.lstat()
except OSError:
return True
if getattr(st, "st_flags", 0) & UF_HIDDEN:
return True
path = str(Path(path).parent)

return False

Expand Down
13 changes: 13 additions & 0 deletions tests/test_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,19 @@ def test_is_hidden():
assert is_hidden(subdir78, root)


@pytest.mark.parametrize(
("abs_path", "abs_root"),
[
("relative.py", "/absolute"),
("/absolute/path.py", "relative"),
("/absolute/path.py", "/absolute/not/parent"),
],
)
def test_is_hidden_invalid(abs_path, abs_root):
with pytest.raises(ValueError, match="abs"):
is_hidden(abs_path, abs_root)


@pytest.mark.skipif(
not (
sys.platform == "win32"
Expand Down
Loading