html.escapestring 不处理下划线,因其仅转义 html 特殊字符(如 &、),而下划线 _ 是安全字符;若前端用 markdown 解析器(如 marked),需手动将 _ 替换为 _ 防 xss。

为什么 html.EscapeString 不能直接处理 Markdown 或富文本中的下划线
因为 html.EscapeString 只做纯字符映射:它把 变成 <code><,& 变成 &,其余一概不动。下划线 _ 在 HTML 中本就是安全字符,不触发解析,所以它完全不会碰——你传进去一个带 _italic_ 的字符串,出来还是 _italic_,后续被前端解析成斜体,就等于没防住 XSS。
手动转义下划线前,先确认它是否真需要转义
关键看你的渲染链路:如果后端输出后,前端用的是原生 innerHTML 直接插入,且中间**没有 Markdown 解析器或富文本库介入**,那 _ 其实不用动;但只要前端用了 marked、remark、quill 或任何会把 _text_ 当格式标记的库,这个下划线就得提前“废掉”——否则攻击者写 _<script>alert(1)</script>_ 就可能执行。
- 只对最终渲染路径中会被解释为格式符号的字符做预处理,不是所有非字母数字都要逃
- 常见需干预的符号:下划线
_、星号*、反引号`、方括号[和圆括号((取决于目标解析器) - 优先查你前端用的解析器文档,看它支持哪些 inline delimiter,再针对性处理
用 strings.ReplaceAll 插入 HTML 实体是最轻量的做法
别写正则、别引入新依赖。Golang 标准库的 strings.ReplaceAll 足够快又确定——只要你知道要“废掉”的符号和它的 HTML 实体形式。比如把 _ 换成 _(即 Unicode U+005F),浏览器显示为下划线,但 Markdown 解析器认不出它是 delimiter。
func escapeUnderscore(s string) string {
return strings.ReplaceAll(s, "_", "_")
}
- 必须在
html.EscapeString**之后**调用,否则&会被二次编码成_ - 实体选
_而不是_:更短,兼容性无差别,且避免十六进制混淆 - 如果还要处理
*,同样用strings.ReplaceAll(s, "*", "*"),注意*的码点是 42
注意模板渲染顺序和上下文隔离
最容易翻车的地方是:你在 Go 模板里混用了 {{.Content | html}} 和手写的 escapeUnderscore。Go 模板的 html 函数本质就是调 html.EscapeString,但它**不接受已转义过的字符串再次过滤**——如果你先手动转了 _ 成 _,再进模板的 | html,& 会被转成 &,结果变成 _,前端就显示字面量 _ 而不是下划线。
立即学习“go语言免费学习笔记(深入)”;
- 要么全用模板函数,要么全手动处理,不要交叉
- 如果必须混合,确保手动步骤在模板执行前完成,并关闭该字段的自动转义:
{{.EscapedContent}}(不加| html) - 永远用
html.UnescapeString测试你的输出:如果解出来还是_,说明你没转成功;如果解出来是_,说明你多转了一层
真正麻烦的从来不是怎么转,而是搞清哪个环节在哪个时刻把哪个字符当什么用。多一次 fmt.Printf("%q", s) 查原始字节,比猜三小时强。











