Files
shelfmark/tests/core/test_custom_script_argv.py
splitsec2 61c7ad3c62 Merge commit from fork
* fix(torrent): check each redirect hop when fetching a torrent file

The torrent file fetch followed redirects by hand without looking at where a
hop pointed, so a download link could make the server request a loopback,
link-local or LAN address. Each hop (the initial URL and every redirect
Location) must now be http or https and resolve only to global addresses.
The configured Prowlarr/Newznab origin is exempt, as it usually lives on the
LAN. The API key still only goes to that origin.

* fix(socketio): only accept websocket handshakes from the app's own origin

The Socket.IO server allowed every origin. It now uses the same-origin check
python-engineio applies by default (Host, or X-Forwarded-Proto and
X-Forwarded-Host behind a proxy). SOCKETIO_ALLOWED_ORIGINS takes a comma
separated list of extra origins for proxies that change Host without
forwarding it.

* fix(custom-script): do not pass a file name that starts with a dash as the first argument

In relative path mode the script got the organized file's relative path as
its first argument, which many scripts read as an option when the name starts
with a dash. Such a name is now passed with a ./ prefix. Absolute mode is
unchanged.
2026-10-21 01:34:38 -04:00

63 lines
1.8 KiB
Python

"""The file name handed to a custom script must not be readable as an option."""
from __future__ import annotations
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from shelfmark.download.postprocess.custom_script import (
CustomScriptExecution,
run_custom_script,
)
def _execution(target_arg: str, mode: str) -> CustomScriptExecution:
destination = Path("/library")
return CustomScriptExecution(
script_path="/scripts/hook.sh",
target_arg=Path(target_arg),
target_abs=destination / target_arg,
destination=destination,
mode=mode,
phase="after_move",
payload_json=None,
)
def _argv_for(execution: CustomScriptExecution) -> list[str]:
with patch(
"shelfmark.download.postprocess.custom_script.run_blocking_io",
return_value=MagicMock(stdout=""),
) as run:
assert run_custom_script(execution, task_id="t1", status_callback=MagicMock())
return list(run.call_args.args[1])
@pytest.mark.parametrize(
("target", "expected"),
[
("-rf", "./-rf"),
("--help.epub", "./--help.epub"),
("-Author/book.epub", "./-Author/book.epub"),
("normal.epub", "normal.epub"),
("Author/-odd.epub", "Author/-odd.epub"),
],
)
def test_relative_mode_target_never_starts_with_a_dash(target, expected):
assert _argv_for(_execution(target, "relative")) == ["/scripts/hook.sh", expected]
def test_absolute_mode_is_unchanged():
execution = CustomScriptExecution(
script_path="/scripts/hook.sh",
target_arg=Path("/library/-rf"),
target_abs=Path("/library/-rf"),
destination=Path("/library"),
mode="absolute",
phase="after_move",
payload_json=None,
)
assert _argv_for(execution) == ["/scripts/hook.sh", "/library/-rf"]