
本文详解Go 1.4–1.6版本中html/template包在嵌套脚本模板内多次使用相同结构体字段(如{{.domain}})时触发的双重转义Bug,解释其成因,并提供兼容性解决方案。
本文详解go 1.4–1.6版本中`html/template`包在嵌套脚本模板内多次使用相同结构体字段(如`{{.domain}}`)时触发的双重转义bug,解释其成因,并提供兼容性解决方案。
该问题并非 Gin 框架本身缺陷,而是源于 Go 标准库 html/template 在特定版本中存在的已知解析逻辑缺陷:当同一模板中多次出现完全相同的模板动作(如 {{.domain}})且位于 <script type="text/template"> 这类非标准 HTML 上下文内时,Go 模板引擎会错误地对后续出现的变量进行重复 HTML 转义,导致字符串被意外包裹双引号(例如 "meican.loc"),破坏前端 JavaScript 模板的原始语义。
此行为已在 Go 官方 Issue 和 CL 中确认为 Bug,并于 Go 1.6 部分修复、Go 1.7 彻底解决(见 CL 14335 和 CL 14336)。但若项目仍需兼容 Go ≤1.6 环境,可采用以下可靠规避方案:
✅ 推荐解决方案:使用 template.JS 类型显式声明非转义内容
修改后端数据传递方式,将需原样插入的字符串显式标记为 template.JS,绕过自动转义逻辑:
import (
"html/template"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*")
router.GET("/index", func(c *gin.Context) {
data := gin.H{
"scheme": template.JS("http"),
"domain": template.JS("meican.loc"),
}
c.HTML(http.StatusOK, "index.html", data)
})
router.Run(":8089")
}对应模板保持不变(无需修改 index.html):
立即学习“前端免费学习笔记(深入)”;
<script type="text/template" charset="utf-8">
<div data="{{.scheme}}://{{.domain}}/qr"></div>
<div data="{{.scheme}}://{{.domain}}/qr"></div>
</script>⚠️ 注意:template.JS 仅适用于可信、无用户输入的字符串。若 domain 来自外部输入(如 URL 参数),必须先严格校验并白名单过滤,否则将引入 XSS 风险。
? 替代方案:拆分变量或使用自定义函数(兼容性更强)
若无法升级 Go 版本且需动态内容,可避免重复动作,改用唯一变量名或模板函数:
// 后端
c.HTML(http.StatusOK, "index.html", gin.H{
"baseURL": template.JS("http://meican.loc/qr"),
})<!-- 模板 -->
<script type="text/template">
<div data="{{.baseURL}}"></div>
<div data="{{.baseURL}}"></div>
</script>✅ 验证与总结
- ✅ 升级 Go 至 1.7+ 是根本解法(推荐生产环境尽快迁移);
- ✅ 使用 template.JS 是 Go 1.4–1.6 下最简洁、安全的临时方案;
- ❌ 不要禁用整个模板转义(如 {{.domain | safe}}),这会破坏整体 XSS 防护;
- ? 关键原则:模板上下文决定转义策略——<script type="text/template"> 对浏览器而言是纯文本,但 Go 模板仍按 HTML 上下文解析,因此必须显式干预。
通过合理使用类型标注与版本管理,即可在保障安全的前提下,彻底规避此类“重复变量引发引号污染”的模板渲染异常。











