Files
AskaEth 996201c4ed fix(httpclient): 信封 message 默认不进错误,渠道上游自由文本一并收口
信封路径(非 2xx + 信封体、2xx + code!=0)此前把上游 message 经 RedactText
拼进 error,而上游会在 message 里回显被拒的请求正文 —— 她的原话因此进了错误
与调用方日志(M49 探针实测:code 1: invalid content: 她刚才说的那句话 ...)。

- envelope message 改为**默认不带**(新 opt-in WithEnvelopeMessage(),与
  trace.UpstreamError 的 message 同一口径);code 与上游 status 保留,
  errors.Is 的 ErrAPI / trace.ErrUpstream 链不变。
- opt-in 下统一走单一清洗 upstreamText:压平空白 + trace.RedactTextLimited,
  上限 40 rune(比 trace 的 128 短:错误里那句话是提示,不是摘要)。
- plugins/channels 同族出口一并收口(扫过全部,命中 4 处):
  telegram description、qq_obv11 msg(错误 + logEchoFailure 日志)、
  weixin_ilink CDN x-error-message;qq 的 status 字段改走 trace.ErrorToken
  令牌守卫。被剥掉的一律由 status / error_code / retcode 承担分类。

每条新增/改写用例都做过变异验证(把字段拼回去即变红)。
2026-10-26 19:28:15 +08:00

49 lines
2.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package httpclient
// 本文件是**渠道侧上游自由文本的唯一清洗入口**(M50)。它管的是同一件事在四个出口上
// 的形态:KOOK 一类 `{code,message}` 信封的 `message`、Telegram Bot API 的
// `description`、OneBot v11 信封的 `msg` / `wording`、iLink CDN 的 `x-error-message`
// 头。四者都是上游给的**人话**,都可能把被拒的请求内容回显出来 —— 而请求内容里就有
// 她的原话。
//
// 🔴 契约(改本文件前先读):
// - 上层口径是 **默认不带**:这些字段默认不进 error、不进日志(见 client.go 的
// WithEnvelopeMessage 与各渠道调用点的注释)。本文件的函数**只在调用方已经显式
// 选择保留**时才被调用 —— 它是「保留时的上限」,不是「可以带」的许可。
// - 清洗只有一份:`trace.RedactTextLimited`(凭据形状打码 + rune 截断)。本包**不**
// 另写一套正则或截断,另写一套必然漂移,而漂移的方向总是「其中一条出口开始原样
// 吐凭据」。
// - 压平空白也是必需的一步:一条诊断只许占一行,否则上游可以用换行伪造日志行
// (log injection)。
import (
"strings"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
)
// maxUpstreamTextRunes 是上游自由文本允许保留的 rune 上限:**40**。
//
// 为什么比 `trace.RedactText` 的 80 更短:那一档服务的是**摘要素材**(出图、工具结果
// 摘要),读者只看这一条;本文件的产物进的是**错误链与日志**,同一条信息还会被调用方
// 再落一次,长度预算没有理由按摘要给。而 40 rune 已足够装下渠道侧真正有用的那类说明
// (`invalid token`、`参数错误`、`缺少 code`、`FLOOD_WAIT_30`),又短到装不下一句她
// 说的话 —— 上游回显 `invalid content: <正文>` 时,前半句就吃掉大半预算。
const maxUpstreamTextRunes = 40
// upstreamText 把一段上游自由文本收敛成可以出镜的短诊断:压平空白,再交给
// `trace.RedactTextLimited` 打码凭据形状并截到 maxUpstreamTextRunes(超出的带省略号)。
//
// 空串进 ⇒ 空串出;调用方据此判断「这一段有没有东西可显示」。
//
// 🔴 它**不**决定该不该带(默认不带)。把它的结果无条件拼进 error 仍然是把她的原话
// 送上日志 —— 调用点必须自己持有那个显式的开关(本包只有 WithEnvelopeMessage 一处)。
// 渠道插件当前**不带**任何自由文本,因此没有第二个调用点;将来若真要开,也要回到
// 这一条(`trace` 的唯一清洗 + 40 rune),不要在插件里另写一套截断。
func upstreamText(s string) string {
if s == "" {
return ""
}
return trace.RedactTextLimited(strings.Join(strings.Fields(s), " "), maxUpstreamTextRunes)
}