- 新增 internal/channel/mediafetch:来源白名单(deny-by-default,支持 *.suffix) + 逐跳重定向校验 + 边读边限 + 超时;只回来源主机,绝不回完整 URL - 四渠道各自 media.go 接通入站内联:只从平台载荷给出的引用取回字节 (telegram file_id→getFile→file_path 形态校验;kook CDN;qq 文件段 url; weixin cdnURL + AES-ECB 解密) - 常量全部复用既有来源:documents.ExtAllowed / DefaultMaxBytes / MaxPerMessage - channelbridge 诚实提示文案改为对「渠道只给远程引用」与「代取失败」两种成因都成立 - 18 个测试函数(含四渠道「正文里的 URL ⇒ 0 次命中」红线反证、端到端进模型可见视图) - 变异验证:去掉尺寸上限 ⇒ 四渠道红(含修正后不再是假绿的 weixin 超量用例)
63 lines
3.2 KiB
Go
63 lines
3.2 KiB
Go
package qq_obv11
|
||
|
||
// 本文件承载 OneBot v11 的**入站附件字节内联**(切片 IM-1):文件类消息段(数组
|
||
// 形态的 `{type:"file",data:{...}}` 与 CQ 码形态的 `[CQ:file,...]`)里,实现方通常
|
||
// 会给出一个 `url`(离线文件/群文件的下载地址),字节就照它取回。
|
||
//
|
||
// 三条边界:
|
||
// - **只认平台系资产主机**:白名单 = 腾讯系 CDN(`*.qq.com`、`*.qq.com.cn`、
|
||
// `*.qpic.cn`,覆盖 `multimedia.nt.qq.com.cn` 与 `gchat.qpic.cn`)+ 部署自己
|
||
// 声明的 OneBot API 端点(本地实现常自己下发文件 URL)。消息段里的 `url` 是
|
||
// 对端上报的内容,白名单是硬门:不在名单里一个字节都不发出去;
|
||
// - **没有真 url 就不下载**:段里只有文件 id/文件名时(`url` 缺失,`file` 被当作
|
||
// 引用写进了 URL 字段),它连绝对 http(s) 地址都不是 ⇒ 白名单直接否决,绝不
|
||
// 拼凑地址去猜;
|
||
// - 尺寸/超时/预筛/日志口径全在 `internal/channel/mediafetch`(四个适配器共用)。
|
||
|
||
import (
|
||
"context"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/mediafetch"
|
||
)
|
||
|
||
// 腾讯系资产主机白名单:OneBot 实现上报的文件地址落在这些子树里(`.qq.com` 家族
|
||
// 覆盖 `multimedia.nt.qq.com.cn` 之外的多媒体域名,`.qpic.cn` 覆盖图片/文件 CDN)。
|
||
// 通配条目**不含**裸的 `qq.com` 本身,因此不会顺带放行 `qq.com` 主页这类无关主机。
|
||
const (
|
||
qqAssetOriginQQ = "https://*.qq.com"
|
||
qqAssetOriginQQCN = "https://*.qq.com.cn"
|
||
qqAssetOriginQpic = "https://*.qpic.cn"
|
||
)
|
||
|
||
// inlineInboundMedia 把受支持格式的文件附件取回并内联到消息上。失败不抛出:该附件
|
||
// 保持无字节,由 channelbridge 的入站摄取照既有口径给模型一行诚实提示。
|
||
func (a *Adapter) inlineInboundMedia(ctx context.Context, msg *adapter.UnifiedMessage) {
|
||
mediafetch.Inline(ctx, a.log, msg, qqFetcher{a: a})
|
||
}
|
||
|
||
// qqFetcher 实现 mediafetch.Fetcher:OneBot 上报的 `url` 就是资产地址。
|
||
type qqFetcher struct{ a *Adapter }
|
||
|
||
// Fetch 按来源白名单取回一份文件资产。下载**不复用**出站 HTTP API 的客户端:那个
|
||
// 客户端带着 OneBot access_token 头,绝不能把它发到 CDN 主机上,因此这里交给
|
||
// mediafetch 自建一个干净客户端。
|
||
func (f qqFetcher) Fetch(ctx context.Context, m adapter.Media) ([]byte, error) {
|
||
return mediafetch.Fetch(ctx, mediafetch.Request{
|
||
URL: m.URL,
|
||
Allow: f.a.assetAllowlist(),
|
||
Timeout: mediafetch.DefaultTimeout,
|
||
})
|
||
}
|
||
|
||
// assetAllowlist 给出 OneBot 文件资产的来源白名单:腾讯系 CDN + 已配置的 OneBot
|
||
// API 端点(`api_base_url`,本地部署常是 `http://127.0.0.1:5700` 这类地址 —— 它由
|
||
// 部署显式声明,可信度与其余 API 调用一致)。`api_base_url` 为空时只用腾讯系条目。
|
||
func (a *Adapter) assetAllowlist() mediafetch.Allowlist {
|
||
origins := []string{qqAssetOriginQQ, qqAssetOriginQQCN, qqAssetOriginQpic}
|
||
if origin, err := mediafetch.OriginOf(a.cfg.APIBaseURL); err == nil {
|
||
origins = append(origins, origin)
|
||
}
|
||
return mediafetch.NewAllowlist(origins...)
|
||
}
|