html/template自动转义以防XSS,text/template不转义仅作纯文本输出;需用template.HTML包装可信HTML字符串,并注意上下文转义规则及子模板中类型丢失问题。

为什么 html/template 会自动转义,而 text/template 不会
因为 html/template 的设计目标就是防 XSS,默认把所有 .Name、.Content 这类数据插值都当成不可信的 HTML 片段处理。它内部调用 html.EscapeString() 做转义;text/template 没这层逻辑,纯文本输出,所以 <script> 会原样吐出来——这在渲染 HTML 页面时极其危险。
常见错误现象:html/template 把你传进去的合法 HTML(比如富文本编辑器存的 <p>Hello</p>)也转义成
Hello
,页面显示为源码而非渲染效果。- 使用场景:用户提交的富文本、后台 CMS 内容、带标签的评论
- 安全边界:只有你**完全确认该字符串来自可信来源且已做过内容清洗**,才考虑绕过转义
- 不要用
text/template替代来“解决”转义问题——它根本不校验 HTML 结构,容易拼出不合法或可被利用的标签
如何安全地插入已知合法的 HTML 字符串
用 template.HTML 类型包装字符串,告诉 html/template:“这段我负责,别转义”。这不是后门,是明确的契约。
示例:
立即学习“go语言免费学习笔记(深入)”;
type Page struct {
Content template.HTML // 注意类型不是 string
}
t := template.Must(template.New("").Parse(`<div>{{.Content}}</div>`))
t.Execute(w, Page{Content: template.HTML(`<p>Hello <strong>world</strong></p>`)})
- 必须显式转换:
template.HTML(myStr),不能靠类型断言或反射绕过 - 如果
myStr来自用户输入且未过滤,直接包成template.HTML= 开放 XSS 入口 - 推荐前置过滤:用
bluemonday或golang.org/x/net/html做白名单清洗,再转template.HTML
html/template 中哪些操作不会触发自动转义
只有通过 {{.Field}} 插值进 HTML 上下文时才转义。其他情况有明确例外规则:
-
{{printf "%s" .RawHTML}}—— 仍会转义,printf不改变上下文语义 -
{{.Field | safeHTML}}—— 这个函数存在,但它是html/template自带的,等价于template.HTML包装,且只在 HTML 上下文中生效 -
<script>{{.JSData}}</script>—— 进入 JS 上下文后,转义逻辑切换为 JS 转义(如引号、反斜杠),不是 HTML 转义 -
<a href="{{.URL}}">—— 进入属性上下文,会做 URL 编码(空格→%20),不是 HTML 实体编码
关键点:转义行为取决于**插入位置的上下文**,不是模板引擎“粗暴统一处理”。这也是为什么不能靠正则替换 回 <code>< —— 你不知道当前上下文要什么编码。
常见踩坑:嵌套模板 + template.HTML 丢失类型
当用 {{define "content"}}{{.Body}}{{end}} 定义子模板,再通过 {{template "content" .}} 调用时,如果 .Body 是 template.HTML 类型,它会在传参过程中被隐式转成 string,导致再次转义。
- 原因:
template包对传入的interface{}值不做类型保留,只取底层值 - 解法:在子模板里重新声明接收类型,或改用
funcmap注册一个透传函数 - 更稳做法:避免跨模板传递原始 HTML,改为在主模板中完成
template.HTML包装,子模板只接收已包装好的值
这个细节容易被忽略——看着代码没变,只是拆了模板,结果线上富文本突然全变成源码了。











