136 lines
4.3 KiB
Python
136 lines
4.3 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import os
|
||
|
|
import tempfile
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from yuxi.channels.adapters.imessage.media_security import (
|
||
|
|
resolve_attachment_roots,
|
||
|
|
sanitize_attachment_filename,
|
||
|
|
validate_attachment_path,
|
||
|
|
validate_remote_attachment_url,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestResolveAttachmentRoots:
|
||
|
|
def test_configured_roots_returned(self):
|
||
|
|
roots = ["/custom/path"]
|
||
|
|
assert resolve_attachment_roots(roots) == ["/custom/path"]
|
||
|
|
|
||
|
|
def test_empty_list_returns_configured_empty(self):
|
||
|
|
result = resolve_attachment_roots([])
|
||
|
|
assert len(result) > 0
|
||
|
|
|
||
|
|
def test_default_roots_with_wildcard(self):
|
||
|
|
roots = resolve_attachment_roots()
|
||
|
|
assert len(roots) > 0
|
||
|
|
assert any("Messages" in r for r in roots)
|
||
|
|
|
||
|
|
|
||
|
|
class TestValidateAttachmentPath:
|
||
|
|
def test_empty_path(self):
|
||
|
|
assert validate_attachment_path("") is False
|
||
|
|
|
||
|
|
def test_path_in_allowed_root(self):
|
||
|
|
with tempfile.TemporaryDirectory() as tmp:
|
||
|
|
file_path = os.path.join(tmp, "test.jpg")
|
||
|
|
with open(file_path, "w") as f:
|
||
|
|
f.write("content")
|
||
|
|
assert validate_attachment_path(file_path, [tmp]) is True
|
||
|
|
|
||
|
|
def test_path_not_in_allowed_root(self):
|
||
|
|
roots = ["/allowed/path"]
|
||
|
|
assert validate_attachment_path("/other/path/file.jpg", roots) is False
|
||
|
|
|
||
|
|
def test_path_with_traversal_rejected(self):
|
||
|
|
roots = ["/allowed"]
|
||
|
|
assert validate_attachment_path("/allowed/../not_allowed/file.jpg", roots) is False
|
||
|
|
|
||
|
|
def test_no_allowed_roots_means_all_allowed(self):
|
||
|
|
assert validate_attachment_path("/any/path/file.jpg", []) is True
|
||
|
|
|
||
|
|
def test_file_does_not_exist(self):
|
||
|
|
roots = ["/allowed"]
|
||
|
|
path = os.path.join("/allowed", "nonexistent.jpg")
|
||
|
|
assert validate_attachment_path(path, roots) is True
|
||
|
|
|
||
|
|
|
||
|
|
class TestValidateRemoteAttachmentURL:
|
||
|
|
def test_empty_url(self):
|
||
|
|
assert validate_remote_attachment_url("") is False
|
||
|
|
|
||
|
|
def test_url_in_allowed_roots(self):
|
||
|
|
assert validate_remote_attachment_url(
|
||
|
|
"https://cdn.example.com/media/img.jpg",
|
||
|
|
["https://cdn.example.com/media"],
|
||
|
|
) is True
|
||
|
|
|
||
|
|
def test_url_not_in_allowed_roots(self):
|
||
|
|
assert validate_remote_attachment_url(
|
||
|
|
"https://evil.com/img.jpg",
|
||
|
|
["https://cdn.example.com"],
|
||
|
|
) is False
|
||
|
|
|
||
|
|
def test_url_matches_server_url(self):
|
||
|
|
assert (
|
||
|
|
validate_remote_attachment_url(
|
||
|
|
"http://localhost:1234/media/img.jpg",
|
||
|
|
server_url="http://localhost:1234",
|
||
|
|
)
|
||
|
|
is True
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_url_does_not_match_server_url(self):
|
||
|
|
assert (
|
||
|
|
validate_remote_attachment_url(
|
||
|
|
"http://evil.com/img.jpg",
|
||
|
|
server_url="http://localhost:1234",
|
||
|
|
)
|
||
|
|
is False
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_no_roots_no_server_url_returns_false(self):
|
||
|
|
assert validate_remote_attachment_url("http://example.com/img.jpg") is False
|
||
|
|
|
||
|
|
def test_partial_match_fails(self):
|
||
|
|
assert (
|
||
|
|
validate_remote_attachment_url(
|
||
|
|
"https://cdn.example.com.fake/media/img.jpg",
|
||
|
|
["https://cdn.example.com"],
|
||
|
|
)
|
||
|
|
is False
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestSanitizeAttachmentFilename:
|
||
|
|
def test_normal_filename(self):
|
||
|
|
assert sanitize_attachment_filename("test.jpg") == "test.jpg"
|
||
|
|
|
||
|
|
def test_path_traversal_removed(self):
|
||
|
|
result = sanitize_attachment_filename("../../../etc/passwd")
|
||
|
|
assert "/" not in result
|
||
|
|
assert result == "passwd"
|
||
|
|
|
||
|
|
def test_special_characters_removed(self):
|
||
|
|
result = sanitize_attachment_filename("file;*name?.jpg")
|
||
|
|
assert ";" not in result
|
||
|
|
assert "*" not in result
|
||
|
|
assert "?" not in result
|
||
|
|
|
||
|
|
def test_empty_filename(self):
|
||
|
|
assert sanitize_attachment_filename("") == "attachment"
|
||
|
|
|
||
|
|
def test_safe_chars_preserved(self):
|
||
|
|
result = sanitize_attachment_filename("My File (1)_v2.0.txt")
|
||
|
|
assert result == "My File (1)_v2.0.txt"
|
||
|
|
|
||
|
|
def test_windows_path(self):
|
||
|
|
result = sanitize_attachment_filename("C:\\Users\\test\\file.jpg")
|
||
|
|
assert ":" not in result
|
||
|
|
assert "\\" not in result
|
||
|
|
assert result == "file.jpg"
|
||
|
|
|
||
|
|
def test_result_never_empty(self):
|
||
|
|
result = sanitize_attachment_filename("!@#$%")
|
||
|
|
assert result == "attachment"
|