mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-15 05:07:15 +00:00
Clean release snapshot
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
"""Unit tests for ``lib/safepath.py::safe_join``.
|
||||
|
||||
The three archive extractors and the filename-bound HTTP routes all rely on
|
||||
this helper for containment. Pin the contract here so any regression surfaces
|
||||
at the helper layer before it can sneak into a call site."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from safepath import safe_join
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def root(tmp_path):
|
||||
return tmp_path
|
||||
|
||||
|
||||
# ── Rejected inputs ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name",
|
||||
[
|
||||
"", # empty
|
||||
"..", # bare dotdot
|
||||
"../escaped", # one-level traversal
|
||||
"../" * 6 + "tmp/pwn", # deep traversal
|
||||
"subdir/../../escaped", # mid-path traversal
|
||||
"/etc/passwd", # absolute POSIX
|
||||
"..\\windows", # backslash-traversal (Windows-style)
|
||||
"\x00null", # NUL byte → ValueError on resolve
|
||||
],
|
||||
)
|
||||
def test_safe_join_rejects(root, name):
|
||||
assert safe_join(root, name) is None
|
||||
|
||||
|
||||
# ── Accepted inputs ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name",
|
||||
[
|
||||
"file.txt",
|
||||
"subdir/file.txt",
|
||||
"deep/nested/dir/file.txt",
|
||||
"a/./b", # current-dir segment, contained
|
||||
"%2e%2e/x", # literal percent-encoding (not decoded)
|
||||
],
|
||||
)
|
||||
def test_safe_join_accepts(root, name):
|
||||
result = safe_join(root, name)
|
||||
assert result is not None
|
||||
assert result.is_relative_to(root.resolve())
|
||||
|
||||
|
||||
def test_safe_join_dot_resolves_to_root(root):
|
||||
"""A bare ``.`` is contained (equals root). Caller is responsible for
|
||||
declining to write a file at the root path."""
|
||||
result = safe_join(root, ".")
|
||||
assert result == root.resolve()
|
||||
|
||||
|
||||
def test_safe_join_returns_resolved_path(root):
|
||||
"""Result is fully resolved — caller can use ``is_relative_to`` directly
|
||||
without re-resolving."""
|
||||
(root / "sub").mkdir()
|
||||
result = safe_join(root, "sub/file.txt")
|
||||
assert result == (root / "sub" / "file.txt").resolve()
|
||||
|
||||
|
||||
def _symlink_or_skip(link: Path, target: Path) -> None:
|
||||
"""Create ``link`` pointing at ``target``, or skip the test on platforms
|
||||
where symlinks aren't allowed (Windows without dev-mode, restricted
|
||||
container, non-symlink-capable filesystem).
|
||||
|
||||
Passes ``target_is_directory=target.is_dir()`` so directory symlinks work
|
||||
on Windows hosts that *do* allow symlinks — otherwise this helper would
|
||||
skip unnecessarily on Windows even when the underlying capability is
|
||||
present.
|
||||
"""
|
||||
try:
|
||||
link.symlink_to(target, target_is_directory=target.is_dir())
|
||||
except (OSError, NotImplementedError) as e:
|
||||
pytest.skip(f"symlinks not available on this platform: {e}")
|
||||
|
||||
|
||||
def test_safe_join_symlinked_root_still_contains(tmp_path):
|
||||
"""Containment uses realpath on both sides so a symlinked root doesn't
|
||||
break the check."""
|
||||
real = tmp_path / "real"
|
||||
real.mkdir()
|
||||
link = tmp_path / "link"
|
||||
_symlink_or_skip(link, real)
|
||||
result = safe_join(link, "file.txt")
|
||||
assert result is not None
|
||||
assert result == (real / "file.txt").resolve()
|
||||
|
||||
|
||||
def test_safe_join_traversal_through_symlinked_root(tmp_path):
|
||||
"""A ``..`` from a symlinked root must still resolve outside the real
|
||||
root (not just the symlink path) and be rejected."""
|
||||
real = tmp_path / "real"
|
||||
real.mkdir()
|
||||
link = tmp_path / "link"
|
||||
_symlink_or_skip(link, real)
|
||||
assert safe_join(link, "../escaped") is None
|
||||
Reference in New Issue
Block a user