fix(dingtalk): cap remote download time and keep URL query out of temp file names

Co-authored-by: cowagent <cow@cowagent.ai>
This commit is contained in:
zhayujieandcowagent committed 2026-09-29 22:29:04 +08:00
1 parent 0198418729
commit 353eb7b95a
3 files changed
+27 -5

No files matched your search

+17 -4
View File
@@ -10,6 +10,8 @@ import json
import logging
import os
import time
from urllib.parse import unquote, urlparse
import requests
import dingtalk_stream
@@ -21,6 +23,7 @@ from dingtalk_stream.card_replier import CardReplier
from bridge.context import Context, ContextType
from bridge.reply import Reply, ReplyType
from channel.chat_channel import ChatChannel
from channel.chat_message import safe_filename
from common import state_dir
from common.media_download import MAX_FILE_BYTES, MediaTooLargeError, download_to_file
from channel.dingtalk.dingtalk_message import DingTalkMessage
@@ -34,6 +37,8 @@ from common.singleton import singleton
from common.time_check import time_checker
from config import conf
_MAX_REMOTE_FILE_SECONDS = 300
def _markdown_preview_title(markdown: str, limit: int = 30) -> str:
"""Plain-text title for a webhook markdown message.
@@ -447,17 +452,25 @@ class DingTalkChanel(ChatChannel, dingtalk_stream.ChatbotHandler):
if file_path.startswith("http://") or file_path.startswith("https://"):
try:
import uuid
file_name = os.path.basename(file_path) or f"media_{uuid.uuid4()}"
# Query strings may carry tokens and characters Windows rejects
# in file names; keep only the sanitized last path segment.
file_name = (
safe_filename(unquote(os.path.basename(urlparse(file_path).path)))
or f"media_{uuid.uuid4()}"
)
temp_file = os.path.join(str(state_dir.tmp_dir()), file_name)
try:
download_to_file(file_path, temp_file, MAX_FILE_BYTES, timeout=(5, 60))
download_to_file(
file_path, temp_file, MAX_FILE_BYTES,
timeout=(5, 60), max_seconds=_MAX_REMOTE_FILE_SECONDS,
)
except MediaTooLargeError:
logger.error(f"[DingTalk] Downloaded file exceeds size limit: {file_path}")
logger.error("[DingTalk] Remote file exceeds size limit, skipped upload")
return None
file_path = temp_file
logger.info(f"[DingTalk] Downloaded file to {file_path}")
except Exception as e:
logger.error(f"[DingTalk] Error downloading file: {e}")
logger.error(f"[DingTalk] Error downloading file: {type(e).__name__}")
return None
if not os.path.exists(file_path):
+1 -1
View File
@@ -109,7 +109,7 @@ Thanks @liuns-yang (#3191)
本版本对外部资源访问和本地文件写入做了系统性加固:
- **下载限制**:通道收发媒体、终端图片、视觉识别、关键词文件等外部下载统一限制文件大小和总耗时,避免超大文件或慢速连接拖垮服务;视觉识别下载图片时对每次重定向重新校验地址。Thanks @rudycelekli (#3255, #3256, #3257, #3264, #3266, #3267, #3268, #3269, #3270, #3271, #3272)、@c020627 (#3219, #3225, #3252, #3300)
- **下载限制**:通道收发媒体、终端图片、视觉识别、关键词文件等外部下载统一限制文件大小和总耗时,避免超大文件或慢速连接拖垮服务;视觉识别下载图片时对每次重定向重新校验地址。Thanks @rudycelekli (#3255, #3256, #3257, #3264, #3266, #3267, #3268, #3269, #3270, #3271, #3272)、@c020627 (#3219, #3225, #3252, #3300, #3322)
- **请求超时**:模型、语音、翻译等外部请求全部设置超时,上游无响应时不再卡住对话。Thanks @c020627 (#3217, #3220, #3240, #3254, #3275)
- **配置安全写入**:`config.json`、插件配置、模型列表、定时任务、技能配置、凭证文件、`MEMORY.md` 等改为原子写入,写入中途失败不会损坏原文件;定时任务文件损坏时自动从备份恢复。Thanks @c020627 (#3223, #3239, #3241, #3242, #3259, #3273, #3274, #3276, #3279, #3280, #3281)、@rudycelekli (#3260)
- **临时文件管理**:通道接收的文件名限制在临时目录内并限制长度(Thanks @Lesereingrape #3247, #3248、@c020627 #3212, #3301);下载的媒体、回复附件和语音合成文件统一存放在对应 Agent 的临时目录(Thanks @c020627 #3198, #3207, #3208, #3213)。
@@ -79,6 +79,15 @@ def test_http_url_over_size_limit_rejected(monkeypatch, tmp_path):
assert _channel().upload_media("https://evil.example/huge.bin", "file") is None
def test_http_url_query_string_stays_out_of_the_file_name(monkeypatch, tmp_path):
get = FakeResp(content=b"<media data>")
post = FakeResp(json_data={"errcode": 0, "media_id": "mid-3"})
_stub(monkeypatch, get, post, tmp_path)
url = "https://cdn.example/dir/%E6%8A%A5%E5%91%8A.pdf?token=secret&x=a/b"
assert _channel().upload_media(url, "file") == "mid-3"
assert [p.name for p in tmp_path.iterdir()] == ["报告.pdf"]
def test_local_file_url_still_uploaded(monkeypatch, tmp_path):
local = tmp_path / "clip.mp4"
local.write_bytes(b"<media data>")