- Page.navigate 的 errorText 是**协议枚举**面(net error 码):先剥 `net::` 命名空间再交给 trace.ErrorToken,像枚举就原样保留、散文一律丢弃只留字节数; - Runtime.evaluate 的 exceptionDetails.text 是**页面自由文本**面:压平空白 (防日志行伪造)+ trace.RedactTextLimited 按 200 rune 截断(档位理由见 errtext.go:80 会截断真实异常消息,4096 等于允许页面塞 4KB 正文); - 两类守卫不互换(口径见 trace/errortoken.go 的按字段类别判定),清洗实现 仍只有 trace 那一份,本片只做「选哪一道 + 补形状前提」。 测试:出口 A/B 各一条经 RenderText 的真实路径断言(含 URL/凭据形状的假文本), 外加 net::ERR_* 正向保留、上限截断/换行压平、netErrorCode 形状表。三条泄露断言 已验证「退回原样直吐即红」。
99 lines
5.1 KiB
Go
99 lines
5.1 KiB
Go
package browser
|
||
|
||
// 本文件是**页面侧自由文本出口**的收敛点:CDP 有两个字段会把页面/对端说的话原样
|
||
// 交给我们,它们出镜(进 error ⇒ 日志 + 模型可见视图)之前必须在这里过一道。
|
||
//
|
||
// 两个字段**不是同一类**,因此守卫也不同(口径见 internal/runtime/trace/errortoken.go
|
||
// 的「按字段类别判定」):
|
||
//
|
||
// - `Page.navigate` 的 `errorText` 是**协议枚举**:值域是 Chrome 的 net error 码
|
||
// (`net::ERR_NAME_NOT_RESOLVED` 之类),短、标识符形状 ⇒ 用 trace.ErrorToken 守:
|
||
// 像枚举就保留,散文一律丢弃;
|
||
// - `Runtime.evaluate` 的 `exceptionDetails.text` 是**页面自由文本**:页面的 JS 可以
|
||
// throw 任何东西 —— 包括它读到的 DOM 正文、地址栏里带 token 的 URL。⇒ 默认不带
|
||
// 全文,按「带上限地带」处理:压平空白 + trace.RedactTextLimited 截断。
|
||
//
|
||
// 🔴 两类守卫**不能互换**:拿 ErrorToken 去「清洗」自由文本是错的口径(它只会把整段
|
||
// 话删掉,不是清洗);拿 RedactTextLimited 去守枚举字段也不对(它不判形状,挡不住
|
||
// 散文)。清洗实现仍然只有一份,本文件只做「选哪一道 + 补形状前提」。
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
|
||
)
|
||
|
||
// maxExceptionTextRunes 是 `exceptionDetails.text` 允许出镜的 rune 上限:**200**。
|
||
//
|
||
// 为什么不复用既有的两个档位:
|
||
// - **80**(`trace.RedactText` 的摘要档)会把真实的异常消息拦腰截断 ——
|
||
// `TypeError: Cannot read properties of undefined (reading 'addEventListener')`
|
||
// 已经 73 rune,再带上半句调用点就过 80,而调用点是唯一的定位线索;
|
||
// - **4096**(网关日志页 `maxLogMsgRunes` 那一档)是给「运维要读全文」的日志行用的;
|
||
// 这个字段是**页面自己说的话**,4096 等于允许页面把 4KB 正文塞进一条错误串 ——
|
||
// 而这条错误串同时进日志与模型可见视图。
|
||
//
|
||
// 200 容得下「异常消息 + 一段调用点」,又把页面可控文本压在「一句话」量级。超限时
|
||
// trace.RedactTextLimited 会补 `…`,因此截断本身是看得见的,不会被误读成全文。
|
||
const maxExceptionTextRunes = 200
|
||
|
||
// netErrorNamespace 是 Chrome net error 码的命名空间前缀(`net::ERR_...`)。
|
||
//
|
||
// 为什么要先剥掉它再交给 ErrorToken:ErrorToken 的字符集是 `[A-Za-z0-9_.-]`,**不含
|
||
// `:`**,整串直接送进去会把合法的网络错误码一起判掉 —— 而「合法网络错误码形态保留」
|
||
// 是这条出口的硬要求(上层与工具都在断言它,见 render_test ③)。这个前缀是**协议
|
||
// 常量**,不是从对端抄来的文本,因此剥它是安全的。
|
||
const netErrorNamespace = "net::"
|
||
|
||
// netErrorCodePrefix 是 net error 枚举的名字前缀(`ERR_`):所有真实取值都以它开头。
|
||
// 于是这一条额外把「恰好是单个令牌的自由文本」(`hunter2` 之类)也挡掉 —— 形状判定
|
||
// 仍由 trace.ErrorToken 做,本文件只补这一个前提,不复制第二份守卫实现。
|
||
const netErrorCodePrefix = "ERR_"
|
||
|
||
// netErrorCode 只放行「像 net error 枚举」的 errorText:`[net::]ERR_<标识符>`。
|
||
// 其余(散文、被回显的 URL、带凭据的文本)返回空串,由调用方按「已隐去」处理。
|
||
func netErrorCode(raw string) string {
|
||
text := strings.TrimSpace(raw)
|
||
namespace := ""
|
||
if rest, ok := strings.CutPrefix(text, netErrorNamespace); ok {
|
||
namespace, text = netErrorNamespace, rest
|
||
}
|
||
if !strings.HasPrefix(text, netErrorCodePrefix) {
|
||
return ""
|
||
}
|
||
token := trace.ErrorToken(text)
|
||
if token == "" {
|
||
return ""
|
||
}
|
||
return namespace + token
|
||
}
|
||
|
||
// navigateCause 把 `Page.navigate` 的 errorText 变成可出镜的原因子句。
|
||
// 调用前提:raw 非空(navigate 只在 errorText 非空时判定为失败)。
|
||
//
|
||
// 不是枚举时一个字符的自由文本都不带,只留**字节数** —— 够判断「对端说了句话」,
|
||
// 不带那句话(与 cdpError.Error 同一口径)。
|
||
func navigateCause(raw string) string {
|
||
if code := netErrorCode(raw); code != "" {
|
||
return code
|
||
}
|
||
return fmt.Sprintf("网络错误文本已隐去(%d 字节)", len(raw))
|
||
}
|
||
|
||
// pageExceptionText 是 `exceptionDetails.text` 出镜前的唯一处理:压平空白(一个换行
|
||
// 就能在按行聚合的日志里伪造出「另一条记录」,与 sdklog / judgeReason 同一做法)+
|
||
// 凭据形状打码 + 截断(清洗实现只有一份:trace.RedactTextLimited)。
|
||
func pageExceptionText(raw string) string {
|
||
return trace.RedactTextLimited(strings.Join(strings.Fields(raw), " "), maxExceptionTextRunes)
|
||
}
|
||
|
||
// evaluateCause 把 `Runtime.evaluate` 的 exceptionDetails.text 变成可出镜的原因子句。
|
||
func evaluateCause(raw string) string {
|
||
if text := pageExceptionText(raw); text != "" {
|
||
return text
|
||
}
|
||
// 压平之后什么都不剩(对端只给了空白):如实说「没给文本」,不假装异常无因。
|
||
return "页面异常(对端未给文本)"
|
||
}
|