1. 新增poll命令支持创建、关闭、列出投票 2. 新增审计日志记录功能 3. 优化远程附件URL安全校验逻辑 4. 修复表格匹配正则,支持包含竖线的表格分隔行 5. 新增媒体AI处理能力,支持图片描述和音频转录 6. 完善配置校验和错误处理 7. 重构文本发送逻辑,增加重试机制 8. 新增投票投票处理逻辑,支持数字快捷投票
92 lines
2.4 KiB
Python
92 lines
2.4 KiB
Python
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 是否安全。"""
|
||
from urllib.parse import urlparse
|
||
|
||
if not url:
|
||
return False
|
||
|
||
parsed_url = urlparse(url)
|
||
url_host = parsed_url.hostname or ""
|
||
|
||
if allowed_roots:
|
||
for root in allowed_roots:
|
||
root_parsed = urlparse(root)
|
||
root_host = root_parsed.hostname or ""
|
||
if url_host == root_host and 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"
|