from __future__ import annotations import os from pathlib import Path from yuxi.utils.logging_config import logger DEFAULT_IMESSAGE_ATTACHMENT_ROOTS = [ "/Users/*/Library/Messages/Attachments", ] def resolve_attachment_roots( configured_roots: list[str] | None = None, ) -> list[str]: if configured_roots: return configured_roots expanded: list[str] = [] for pattern in DEFAULT_IMESSAGE_ATTACHMENT_ROOTS: import glob as _glob_module matches = _glob_module.glob(pattern) if matches: expanded.extend(matches) return expanded or DEFAULT_IMESSAGE_ATTACHMENT_ROOTS def validate_attachment_path( file_path: str, allowed_roots: list[str] | None = None, ) -> bool: """校验附件路径是否在允许的根路径内。 阻止路径遍历攻击(如 ../../etc/passwd)。 """ if not file_path: return False real_path = os.path.realpath(file_path) if os.path.exists(file_path) else os.path.abspath(file_path) roots = allowed_roots or [] if not roots: return True for root in roots: root_real = os.path.realpath(root) if os.path.exists(root) else os.path.abspath(root) try: Path(real_path).relative_to(root_real) return True except ValueError: continue logger.warning(f"[iMessage/Security] Attachment path rejected: {file_path} not in allowed roots") return False def validate_remote_attachment_url( url: str, allowed_roots: list[str] | None = None, server_url: str = "", ) -> bool: """校验远程附件 URL 是否安全。""" if not url: return False if allowed_roots: for root in allowed_roots: if url.startswith(root): return True return False if server_url and url.startswith(server_url): return True return False def sanitize_attachment_filename(filename: str) -> str: """净化附件文件名,移除路径分隔符。""" if not filename: return "attachment" basename = os.path.basename(filename) safe = "".join(c for c in basename if c.isalnum() or c in "._-() ") return safe or "attachment"