目录

Go 踩坑之实现 html-template 不转义 HTML 标签

实实在在踩了个 GO 的坑。

需求很简单:把用户提交的带换行、加粗标签的评论内容,通过 html-template 渲染到页面上。

结果运行后发现,页面上直接显示了<strong>好评</strong>这种原始标签,完全没有被解析成加粗样式——这就是 html-template 默认的HTML 标签转义机制在搞鬼。

今天就把这些踩坑经验分享出来,帮大家少走弯路。

为什么 html-template 会转义 HTML 标签?

一开始会疑惑为什么 html-template 要“多此一举”转义标签。

其实这是框架的安全设计,目的是防止XSS攻击。

比如用户恶意提交<script>alert('攻击')</script>这样的内容,如果直接渲染执行,可能会窃取用户信息。

但实际开发中,我们确实有合法渲染HTML标签的场景,比如用户评论的富文本、后台编辑的带格式内容等。

这时候就需要手动关闭指定内容的转义功能。

方法 1:使用 template.HTML 类型

最直接——使用 template.HTML 类型转换

这是最常用的方法,核心是把要渲染的字符串转换成 template.HTML 类型。

html-template 会识别这种类型,不对其进行转义。

package main

import (
    "html/template"
    "net/http"
)

func main() {
    // 定义带HTML标签的内容
    comment := "<strong>这个功能太好用了!</strong><br/>必须好评~"
    // 转换为template.HTML类型,避免转义
    unsafeComment := template.HTML(comment)

    // 定义模板内容
    tplStr := `
    <!DOCTYPE html>
    <html>
    <body>
        <h3>用户评论</h3>
        {{.}}
    </body>
    </html>
    `

    // 解析模板
    tpl, err := template.New("commentTpl").Parse(tplStr)
    if err != nil {
        panic(err)
    }

    // 启动HTTP服务,渲染模板
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        err := tpl.Execute(w, unsafeComment)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })

    panic(http.ListenAndServe(":8080", nil))
}

运行后访问localhost:8080,就能看到“这个功能太好用了!”被加粗,并且有换行效果。

需要注意的是,这种方法要确保内容是可信的,比如是后台管理员编辑的内容,避免直接渲染未知用户提交的内容,防止 XSS 风险。

方法 2:通过 FuncMap 自定义转义函数

更灵活——通过 FuncMap 自定义转义函数

如果需要在模板中动态控制是否转义,或者有多个内容需要处理,用 FuncMap 自定义函数更合适。

我们可以定义一个“不转义”函数,在模板中按需调用。

package main

import (
    "html/template"
    "net/http"
)

func main() {
    // 定义模板内容,调用自定义的unescape函数
    tplStr := `
    <!DOCTYPE html>
    <html>
    <body>
        <h3>可信内容(不转义)</h3>
        {{unescape .TrustedContent}}
        <h3>不可信内容(默认转义)</h3>
        {{.UntrustedContent}}
    </body>
    </html>
    `

    // 创建FuncMap,注册unescape函数
    funcMap := template.FuncMap{
        "unescape": func(s string) template.HTML {
            // 核心逻辑:将字符串转为template.HTML类型
            return template.HTML(s)
        },
    }

    // 解析模板时关联FuncMap
    tpl, err := template.New("contentTpl").Funcs(funcMap).Parse(tplStr)
    if err != nil {
        panic(err)
    }

    // 准备渲染数据:可信内容和不可信内容
    data := map[string]string{
        "TrustedContent":  "<h4>官方公告:系统将于24点维护</h4>",
        "UntrustedContent": "<script>alert('恶意代码')</script>",
    }

    // 启动服务
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        err := tpl.Execute(w, data)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })

    panic(http.ListenAndServe(":8080", nil))
}

这个示例中,可信的官方公告会正常渲染为标题样式,而恶意代码会被转义成纯文本显示。

这种方法的优势在于灵活性高,能在模板中精准控制转义范围。

方法 3:使用 text/template

最谨慎——使用 text/template(不推荐轻易用)

还有一种极端情况:如果整个模板都不需要转义(比如渲染纯HTML静态页面),可以用 text/template 替代 html-template

因为 text/template 本身就没有自动转义功能。

package main

import (
    "net/http"
    "text/template" // 注意这里用的是text/template
)

func main() {
    tplStr := `
    <!DOCTYPE html>
    <html>
    <body>
        {{.}}
    </body>
    </html>
    `

    // 用text/template解析
    tpl, err := template.New("textTpl").Parse(tplStr)
    if err != nil {
        panic(err)
    }

    htmlContent := "<div style='color:red'>这是红色文本</div>"

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        err := tpl.Execute(w, htmlContent)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })

    panic(http.ListenAndServe(":8080", nil))
}

重点提醒:这种方法会完全关闭转义功能,安全性极低。

除非你能 100% 确保渲染的内容绝对可信,否则强烈不推荐使用,避免留下 XSS 漏洞。

常见问题

Q1.用了template.HTML还是转义了?

我第一次尝试时就遇到过这个问题,后来发现是因为在模板中重复处理了变量。

比如先把变量转成template.HTML,又在模板里用{{. | html}}强制转义,相当于画蛇添足。

解决方法:检查模板语法,去掉多余的转义过滤器。

Q2.FuncMap 注册后模板调用提示“函数未定义”?

这是新手常犯的错误,原因是注册FuncMap的顺序不对。

必须先调用Funcs(funcMap),再调用ParseParseFiles

如果先解析模板再注册函数,模板就找不到对应的函数了。

Q3.如何平衡渲染需求和安全性?

核心原则:“最小权限”。能局部不转义就不全局不转义,能过滤标签就不直接渲染原始内容。

比如用户提交的评论,可以用第三方库(如github.com/microcosm-cc/bluemonday)过滤危险标签(scriptiframe等),只保留strongbr等安全标签,再用方法1或2渲染。

总结

处理 html-template 的转义问题:

首选方法1( template.HTML 类型转换)和方法2( FuncMap 自定义函数),这两种方法既能满足需求又能保证安全。

方法3( text/template )尽量少用,除非有特殊场景且能把控安全风险。

最后:关闭转义时一定要确保内容可信,必要时结合标签过滤库做二次处理,别为了图方便留下安全隐患。

如果还有其他踩坑经历,欢迎在评论区交流~

版权声明

未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!

本文原文链接: https://fiveyoboy.com/articles/go- template-html-code/

备用原文链接: https://blog.fiveyoboy.com/articles/go- template-html-code/