
在 Go 的 html/template 包中,<title> 元素的内容被归类为 RCDATA(Raw Text with Escapable Characters),这是 HTML 规范定义的一类特殊上下文。与普通文本不同,RCDATA 中的 <、& 会被严格转义(防止 XSS),但 html/template 同时也会对 '(单引号)和 "(双引号)进行实体化处理(如 ' → '),这一行为是硬编码在模板引擎内部的,且优先级高于 template.HTML 类型的标记——也就是说,即使你显式将值包装为 template.HTML("Hello World'"),它依然会被二次转义。
这是设计使然:html/template 的核心目标是默认安全,而非完全可控的字符串输出。其源码中 html.go 的 escapeText 函数明确对 contentTypeHTML 上下文执行 ' 和 " 的转义,无法通过用户侧 API 关闭。
✅ 正确解决方案:改用 text/template 并手动转义敏感字符
package main
import (
"strings"
"text/template"
"os"
)
const tmpl = `<html>
<head>
<title>{{.Title}}</title>
</head>
</html>`
// safeHTML 将输入字符串中可能引发 XSS 的字符转义(仅需转义 <, >, &, ")
func safeHTML(s string) string {
s = strings.ReplaceAll(s, "&", "&")
s = strings.ReplaceAll(s, "<", "<")
s = strings.ReplaceAll(s, ">", ">")
s = strings.ReplaceAll(s, `"`, """)
return s
}
func main() {
t := template.Must(template.New("ex").Funcs(template.FuncMap{
"safe": safeHTML,
}).Parse(tmpl))
v := map[string]interface{}{
"Title": "Hello World'", // 原始字符串,不包装 template.HTML
}
t.Execute(os.Stdout, v)
}⚠️ 注意事项:
立即学习“前端免费学习笔记(深入)”;
- 不要试图用 template.HTML + text/template 组合——text/template 不识别该类型,会直接调用 .String() 或 fmt.Sprint,失去语义;
- 若模板中还需渲染其他 HTML 片段(如动态 <script> 内容),请确保仅对可信内容跳过转义,并始终对用户输入调用 safeHTML;
- 对于完整 HTML 页面生成,更推荐保持 html/template,并通过调整结构规避 RCDATA 限制(例如:将标题内容通过 JavaScript 注入或使用 <meta> + CSS 替代 <title>)。
总之,html/template 的 ' 转义不是 bug,而是安全模型的一部分。当业务逻辑明确要求保留原始单引号且可确保上下文无 XSS 风险时,切换至 text/template 并精细化控制转义,才是清晰、可靠、符合 Go 工程实践的选择。











