"""Unit tests for archive extraction — path traversal protection, flattening, permissions.""" import io import os import platform import stat import tarfile import zipfile import pytest from cloakbrowser.download import ( _extract_tar, _extract_zip, _flatten_single_subdir, _is_executable, _make_executable, ) # --------------------------------------------------------------------------- # tar.gz extraction # --------------------------------------------------------------------------- def _create_tar_gz(tmp_path, members: dict[str, bytes]) -> "Path": """Create a tar.gz with given {name: content} members.""" archive = tmp_path / "test.tar.gz" with tarfile.open(archive, "w:gz") as tar: for name, content in members.items(): info = tarfile.TarInfo(name=name) info.size = len(content) tar.addfile(info, io.BytesIO(content)) return archive class TestExtractTar: def test_basic(self, tmp_path): archive = _create_tar_gz(tmp_path, {"chrome": b"binary", "lib/libfoo.so": b"lib"}) dest = tmp_path / "out" dest.mkdir() _extract_tar(archive, dest) assert (dest / "chrome").read_bytes() == b"binary" assert (dest / "lib" / "libfoo.so").read_bytes() == b"lib" def test_path_traversal_blocked(self, tmp_path): archive = tmp_path / "evil.tar.gz" with tarfile.open(archive, "w:gz") as tar: info = tarfile.TarInfo(name="../../../etc/passwd") info.size = 4 tar.addfile(info, io.BytesIO(b"evil")) dest = tmp_path / "out" dest.mkdir() with pytest.raises(RuntimeError, match="path traversal"): _extract_tar(archive, dest) def test_suspicious_symlink_skipped(self, tmp_path): """Symlinks with absolute targets are skipped (logged as warning).""" archive = tmp_path / "symlink.tar.gz" with tarfile.open(archive, "w:gz") as tar: # Normal file info = tarfile.TarInfo(name="chrome") info.size = 6 tar.addfile(info, io.BytesIO(b"binary")) # Suspicious symlink sym = tarfile.TarInfo(name="evil_link") sym.type = tarfile.SYMTYPE sym.linkname = "/etc/passwd" tar.addfile(sym) dest = tmp_path / "out" dest.mkdir() _extract_tar(archive, dest) # Normal file extracted assert (dest / "chrome").exists() # Suspicious symlink was skipped assert not (dest / "evil_link").exists() # --------------------------------------------------------------------------- # zip extraction # --------------------------------------------------------------------------- def _create_zip(tmp_path, members: dict[str, bytes]) -> "Path": """Create a zip with given {name: content} members.""" archive = tmp_path / "test.zip" with zipfile.ZipFile(archive, "w") as zf: for name, content in members.items(): zf.writestr(name, content) return archive class TestExtractZip: def test_basic(self, tmp_path): archive = _create_zip(tmp_path, {"chrome.exe": b"binary", "lib/foo.dll": b"lib"}) dest = tmp_path / "out" dest.mkdir() _extract_zip(archive, dest) assert (dest / "chrome.exe").read_bytes() == b"binary" assert (dest / "lib" / "foo.dll").read_bytes() == b"lib" def test_path_traversal_blocked(self, tmp_path): archive = tmp_path / "evil.zip" with zipfile.ZipFile(archive, "w") as zf: zf.writestr("../../../etc/passwd", "evil") dest = tmp_path / "out" dest.mkdir() with pytest.raises(RuntimeError, match="path traversal"): _extract_zip(archive, dest) # --------------------------------------------------------------------------- # Directory flattening # --------------------------------------------------------------------------- class TestFlatten: def test_single_subdir_flattened(self, tmp_path): """Single subdir contents moved up.""" dest = tmp_path / "out" dest.mkdir() subdir = dest / "fingerprint-chromium-custom-v14" subdir.mkdir() (subdir / "chrome").write_bytes(b"binary") (subdir / "lib").mkdir() _flatten_single_subdir(dest) assert (dest / "chrome").read_bytes() == b"binary" assert (dest / "lib").is_dir() assert not subdir.exists() def test_app_bundle_preserved(self, tmp_path): """.app directory NOT flattened (macOS bundle).""" dest = tmp_path / "out" dest.mkdir() app = dest / "Chromium.app" app.mkdir() (app / "Contents").mkdir() (app / "Contents" / "MacOS").mkdir() (app / "Contents" / "MacOS" / "Chromium").write_bytes(b"binary") _flatten_single_subdir(dest) # .app bundle kept intact assert app.is_dir() assert (app / "Contents" / "MacOS" / "Chromium").exists() def test_noop_multiple_entries(self, tmp_path): """Multiple entries at top level — no flattening.""" dest = tmp_path / "out" dest.mkdir() (dest / "chrome").write_bytes(b"binary") (dest / "lib").mkdir() _flatten_single_subdir(dest) # Nothing moved assert (dest / "chrome").exists() assert (dest / "lib").is_dir() # --------------------------------------------------------------------------- # Permissions # --------------------------------------------------------------------------- class TestPermissions: @pytest.mark.skipif(platform.system() == "Windows", reason="chmod not applicable on Windows") def test_make_executable(self, tmp_path): binary = tmp_path / "chrome" binary.write_bytes(b"binary") binary.chmod(0o644) assert not _is_executable(binary) _make_executable(binary) assert _is_executable(binary) def test_is_executable_true(self, tmp_path): binary = tmp_path / "chrome" binary.write_bytes(b"binary") binary.chmod(0o755) assert _is_executable(binary) def test_is_executable_false(self, tmp_path): binary = tmp_path / "chrome" binary.write_bytes(b"binary") binary.chmod(0o644) assert not _is_executable(binary)