Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

html - HTML 文本转义

html 包提供了 HTML 文本的转义和反转义功能,用于安全地处理 HTML 内容。

概述

html 包是 Go 标准库中用于处理 HTML 文本转义的基础包,主要提供两个函数:EscapeString 用于转义 HTML 特殊字符,UnescapeString 用于还原 HTML 实体。这些函数在生成 HTML 内容、防止 XSS 攻击等场景中非常有用。

包导入

import "html"

基本使用

// 1. 转义 HTML 特殊字符
escaped := html.EscapeString("<script>alert('XSS')</script>")

// 2. 反转义 HTML 实体
unescaped := html.UnescapeString("&lt;hello&gt;")

典型示例

示例 1:基本转义

package main

import (
    "fmt"
    "html"
)

func main() {
    // 原始字符串
    original := `<script>alert("XSS")</script>`
    
    // 转义
    escaped := html.EscapeString(original)
    fmt.Printf("转义后:%s\n", escaped)
    
    // 反转换
    unescaped := html.UnescapeString(escaped)
    fmt.Printf("反转换后:%s\n", unescaped)
    
    // 验证
    fmt.Printf("原始 == 反转换:%v\n", original == unescaped)
}

运行

$ go run main.go
转义后:&lt;script&gt;alert(&#34;XSS&#34;)&lt;/script&gt;
反转换后:<script>alert("XSS")</script>
原始 == 反转换:true

示例 2:用户输入安全显示

package main

import (
    "fmt"
    "html"
    "strings"
)

// 安全地显示用户评论
func displayComment(userInput string) string {
    // 转义 HTML 特殊字符,防止 XSS
    safe := html.EscapeString(userInput)
    return fmt.Sprintf("<div class='comment'>%s</div>", safe)
}

func main() {
    // 恶意用户输入
    malicious := "<script>alert('hacked')</script>"
    normal := "Hello, World!"
    
    fmt.Println("恶意输入处理:")
    fmt.Println(displayComment(malicious))
    
    fmt.Println("\n正常输入处理:")
    fmt.Println(displayComment(normal))
    
    // 在 HTML 中安全显示
    fmt.Println("\n完整 HTML:")
    var sb strings.Builder
    sb.WriteString("<html><body>")
    sb.WriteString(displayComment(malicious))
    sb.WriteString("</body></html>")
    fmt.Println(sb.String())
}

运行

$ go run main.go
恶意输入处理:
<div class='comment'>&lt;script&gt;alert(&#39;hacked&#39;)&lt;/script&gt;</div>

正常输入处理:
<div class='comment'>Hello, World!</div>

完整 HTML:
<html><body><div class='comment'>&lt;script&gt;alert(&#39;hacked&#39;)&lt;/script&gt;</div></body></html>

示例 3:处理各种 HTML 实体

package main

import (
    "fmt"
    "html"
)

func main() {
    // 各种 HTML 实体
    entities := []string{
        "&lt;hello&gt;",      // <hello>
        "&amp;and&amp;",      // &and&
        "&#34;quoted&#34;",   // "quoted"
        "&apos;single&apos;", // 'single'
        "&aacute;",           // á
        "&#225;",             // á (十进制)
        "&#xE1;",             // á (十六进制)
    }
    
    fmt.Println("反转换 HTML 实体:")
    for _, entity := range entities {
        unescaped := html.UnescapeString(entity)
        fmt.Printf("%-20s -> %s\n", entity, unescaped)
    }
    
    fmt.Println("\n转义特殊字符:")
    special := `<>&'"`
    escaped := html.EscapeString(special)
    fmt.Printf("原始:%s\n", special)
    fmt.Printf("转义:%s\n", escaped)
}

运行

$ go run main.go
反转换 HTML 实体:
&lt;hello&gt;       -> <hello>
&amp;and&amp;      -> &and&
&#34;quoted&#34;   -> "quoted"
&apos;single&apos; -> 'single'
&aacute;           -> á
&#225;             -> á
&#xE1;             -> á

转义特殊字符:
原始:<>&'"
转义:&lt;&gt;&amp;&#39;&#34;

一、核心函数(按字母顺序)

EscapeString - 转义 HTML 字符串

EscapeString(s string) string

说明

  • 转义 HTML 特殊字符
  • 只转义 5 个字符:<>&'"
  • < 转为 &lt;
  • > 转为 &gt;
  • & 转为 &amp;
  • ' 转为 &#39;
  • " 转为 &#34;
  • UnescapeString(EscapeString(s)) == s 总是成立

定义

func EscapeString(s string) string

参数

  • s:要转义的字符串

返回值

  • string:转义后的字符串

示例

package main

import (
    "fmt"
    "html"
)

func main() {
    // 转义 5 个特殊字符
    tests := []string{
        "<script>",
        "a > b",
        "Tom & Jerry",
        "It's fine",
        "He said \"Hello\"",
    }
    
    for _, test := range tests {
        escaped := html.EscapeString(test)
        fmt.Printf("原始:%-25s 转义:%s\n", test, escaped)
    }
    
    // 验证可逆性
    original := "<>&'\""
    escaped := html.EscapeString(original)
    unescaped := html.UnescapeString(escaped)
    fmt.Printf("\n原始:%s\n", original)
    fmt.Printf("转义:%s\n", escaped)
    fmt.Printf("反转换:%s\n", unescaped)
    fmt.Printf("原始 == 反转换:%v\n", original == unescaped)
}

运行

$ go run main.go
原始:<script>                  转义:&lt;script&gt;
原始:a > b                     转义:a &gt; b
原始:Tom & Jerry               转义:Tom &amp; Jerry
原始:It's fine                 转义:It&#39;s fine
原始:He said "Hello"           转义:He said &#34;Hello&#34;

原始:<>&'"
转义:&lt;&gt;&amp;&#39;&#34;
反转换:<>&'"
原始 == 反转换:true

UnescapeString - 反转换 HTML 字符串

UnescapeString(s string) string

说明

  • 反转换 HTML 实体为原始字符
  • 支持的实体范围比 EscapeString 转义的范围更广
  • 支持命名实体(如 &aacute;á
  • 支持十进制实体(如 &#225;á
  • 支持十六进制实体(如 &#xE1;á
  • UnescapeString(EscapeString(s)) == s 总是成立,但反过来不一定成立

定义

func UnescapeString(s string) string

参数

  • s:包含 HTML 实体的字符串

返回值

  • string:反转换后的字符串

示例

package main

import (
    "fmt"
    "html"
)

func main() {
    // 各种 HTML 实体
    tests := map[string]string{
        "&lt;":           "<",
        "&gt;":           ">",
        "&amp;":          "&",
        "&#39;":          "'",
        "&#34;":          "\"",
        "&aacute;":       "á",
        "&copy;":         "©",
        "&reg;":          "®",
        "&trade;":        "™",
        "&nbsp;":         " ",
        "&#225;":         "á",
        "&#xE1;":         "á",
        "&quot;":         "\"",
    }
    
    fmt.Println("HTML 实体反转换:")
    for entity, expected := range tests {
        result := html.UnescapeString(entity)
        match := "✓"
        if result != expected {
            match = "✗"
        }
        fmt.Printf("%-15s -> %-5s (期望:%s) %s\n", entity, result, expected, match)
    }
    
    // 复杂示例
    complex := "&quot;Fran &amp; Freddie&#39;s Diner&quot; &lt;tasty@example.com&gt;"
    fmt.Printf("\n复杂示例:\n")
    fmt.Printf("转义:%s\n", complex)
    fmt.Printf("原始:%s\n", html.UnescapeString(complex))
}

运行

$ go run main.go
HTML 实体反转换:
&lt;            -> <     (期望:<) ✓
&gt;            -> >     (期望:>) ✓
&amp;           -> &     (期望:&) ✓
&#39;           -> '     (期望:') ✓
&#34;           -> "     (期望:") ✓
&aacute;        -> á     (期望:á) ✓
&copy;          -> ©     (期望:©) ✓
&reg;           -> ®     (期望:®) ✓
&trade;         -> ™     (期望:™) ✓
&nbsp;          ->       (期望: ) ✓
&#225;          -> á     (期望:á) ✓
&#xE1;          -> á     (期望:á) ✓
&quot;          -> "     (期望:") ✓

复杂示例:
转义:"Fran &amp; Freddie&#39;s Diner" &lt;tasty@example.com&gt;
原始:"Fran & Freddie's Diner" <tasty@example.com>

二、使用场景

场景 1:防止 XSS 攻击

package main

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

// 安全的 HTTP 响应处理器
func safeHandler(w http.ResponseWriter, r *http.Request) {
    // 获取用户输入
    userInput := r.URL.Query().Get("name")
    
    // 转义后输出,防止 XSS
    safeName := html.EscapeString(userInput)
    
    fmt.Fprintf(w, "<h1>Hello, %s!</h1>", safeName)
}

func main() {
    http.HandleFunc("/greet", safeHandler)
    fmt.Println("服务器启动在 :8080")
    http.ListenAndServe(":8080", nil)
}

使用示例

# 正常请求
$ curl "http://localhost:8080/greet?name=Alice"
<h1>Hello, Alice!</h1>

# 恶意请求(脚本会被转义,不会执行)
$ curl "http://localhost:8080/greet?name=<script>alert('XSS')</script>"
<h1>Hello, &lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;!</h1>

场景 2:安全地显示用户评论

package main

import (
    "fmt"
    "html"
    "strings"
)

type Comment struct {
    Author  string
    Content string
}

// 安全地渲染评论
func renderComment(comment Comment) string {
    var sb strings.Builder
    
    sb.WriteString("<div class='comment'>\n")
    sb.WriteString("  <div class='author'>")
    sb.WriteString(html.EscapeString(comment.Author))
    sb.WriteString("</div>\n")
    sb.WriteString("  <div class='content'>")
    sb.WriteString(html.EscapeString(comment.Content))
    sb.WriteString("</div>\n")
    sb.WriteString("</div>\n")
    
    return sb.String()
}

func main() {
    comments := []Comment{
        {"Alice", "Great article!"},
        {"Bob", "<script>alert('spam')</script>"},
        {"Charlie", "Tom & Jerry's show"},
    }
    
    for _, comment := range comments {
        fmt.Println(renderComment(comment))
    }
}

运行

$ go run main.go
<div class='comment'>
  <div class='author'>Alice</div>
  <div class='content'>Great article!</div>
</div>

<div class='comment'>
  <div class='author'>Bob</div>
  <div class='content'>&lt;script&gt;alert(&#39;spam&#39;)&lt;/script&gt;</div>
</div>

<div class='comment'>
  <div class='author'>Charlie</div>
  <div class='content'>Tom &amp; Jerry&#39;s show</div>
</div>

场景 3:生成安全的 HTML 属性

package main

import (
    "fmt"
    "html"
)

// 生成安全的 HTML 标签
func generateLink(href, text string) string {
    return fmt.Sprintf("<a href='%s'>%s</a>", 
        html.EscapeString(href), 
        html.EscapeString(text))
}

func generateImage(src, alt string) string {
    return fmt.Sprintf("<img src='%s' alt='%s'>", 
        html.EscapeString(src), 
        html.EscapeString(alt))
}

func main() {
    // 正常链接
    fmt.Println(generateLink("https://example.com", "Example"))
    
    // 恶意链接(尝试注入 JavaScript)
    fmt.Println(generateLink("javascript:alert('XSS')", "Click me"))
    
    // 图片
    fmt.Println(generateImage("/images/photo.jpg", "A beautiful photo"))
    
    // 恶意 alt 文本
    fmt.Println(generateImage("/images/photo.jpg", "' onerror='alert('XSS')"))
}

运行

$ go run main.go
<a href='https://example.com'>Example</a>
<a href='javascript:alert(&#39;XSS&#39;)'>Click me</a>
<img src='/images/photo.jpg' alt='A beautiful photo'>
<img src='/images/photo.jpg' alt='&#39; onerror=&#39;alert(&#39;XSS&#39;)'/>

场景 4:处理 XML/HTML 数据

package main

import (
    "fmt"
    "html"
)

// 解析并显示 XML/HTML 内容
func processContent(content string) {
    fmt.Println("原始内容:")
    fmt.Println(content)
    fmt.Println()
    
    // 如果需要显示原始内容(不解析)
    fmt.Println("安全显示:")
    fmt.Println(html.EscapeString(content))
    fmt.Println()
    
    // 如果内容包含需要解析的实体
    fmt.Println("解析实体:")
    fmt.Println(html.UnescapeString(content))
}

func main() {
    // 混合内容
    content := `&lt;div&gt;Hello &amp; Welcome!&lt;/div&gt;`
    processContent(content)
    
    // 包含特殊字符的内容
    content2 := `<div>Tom & Jerry's "adventure"</div>`
    processContent(content2)
}

运行

$ go run main.go
原始内容:
&lt;div&gt;Hello &amp; Welcome!&lt;/div&gt;

安全显示:
&amp;lt;div&amp;gt;Hello &amp;amp; Welcome!&amp;lt;/div&amp;gt;

解析实体:
<div>Hello & Welcome!</div>

原始内容:
<div>Tom & Jerry's "adventure"</div>

安全显示:
&lt;div&gt;Tom &amp; Jerry&#39;s &#34;adventure&#34;&lt;/div&gt;

解析实体:
<div>Tom & Jerry's "adventure"</div>

场景 5:日志记录中的安全处理

package main

import (
    "fmt"
    "html"
    "time"
)

// 安全地记录用户输入日志
func logUserInput(action, userInput string) {
    timestamp := time.Now().Format("2006-01-02 15:04:05")
    
    // 转义后记录,防止日志查看器注入
    safeInput := html.EscapeString(userInput)
    
    fmt.Printf("[%s] %s: %s\n", timestamp, action, safeInput)
}

func main() {
    // 正常操作
    logUserInput("LOGIN", "user@example.com")
    
    // 恶意输入
    logUserInput("SEARCH", "<script>document.cookie</script>")
    
    // 特殊字符
    logUserInput("COMMENT", "Tom & Jerry <tom@example.com>")
}

运行

$ go run main.go
[2026-04-04 10:30:00] LOGIN: user@example.com
[2026-04-04 10:30:01] SEARCH: &lt;script&gt;document.cookie&lt;/script&gt;
[2026-04-04 10:30:02] COMMENT: Tom &amp; Jerry &lt;tom@example.com&gt;

三、最佳实践

1. 始终转义用户输入

// 推荐:始终转义用户输入
func renderUserInput(input string) string {
    return html.EscapeString(input)
}

// 不推荐:直接使用未转义的输入
func renderUserInput(input string) string {
    return input  // 危险!
}

2. 在 HTML 上下文中使用

// 在 HTML 内容中
func renderHTML(name string) string {
    return fmt.Sprintf("<div>%s</div>", html.EscapeString(name))
}

// 在 HTML 属性中
func renderAttribute(value string) string {
    return fmt.Sprintf("value='%s'", html.EscapeString(value))
}

// 在 JavaScript 中(需要额外处理)
// 注意:html.EscapeString 不足以防止 JS 注入

3. 组合使用转义和模板

package main

import (
    "html"
    "html/template"
    "strings"
)

// 使用 template 包(更安全)
func renderWithTemplate(name string) string {
    tmpl := `<div>Hello, {{.}}!</div>`
    t := template.Must(template.New("greet").Parse(tmpl))
    
    var sb strings.Builder
    t.Execute(&sb, name)  // template 会自动转义
    return sb.String()
}

// 手动转义(当不能使用 template 时)
func renderManual(name string) string {
    return "<div>Hello, " + html.EscapeString(name) + "!</div>"
}

4. 处理富文本内容

package main

import (
    "html"
    "regexp"
)

// 允许特定 HTML 标签的富文本处理
func sanitizeRichText(input string) string {
    // 1. 转义所有 HTML
    escaped := html.EscapeString(input)
    
    // 2. 恢复允许的标签(如 <b>, <i>)
    allowedTags := map[string]string{
        "&lt;b&gt;":  "<b>",
        "&lt;/b&gt;": "</b>",
        "&lt;i&gt;":  "<i>",
        "&lt;/i&gt;": "</i>",
    }
    
    result := escaped
    for escapedTag, originalTag := range allowedTags {
        result = regexp.MustCompile(regexp.QuoteMeta(escapedTag)).ReplaceAllString(result, originalTag)
    }
    
    return result
}

5. 性能优化

// 批量处理时复用转义结果
func processComments(comments []string) []string {
    results := make([]string, len(comments))
    
    // 缓存已转义的内容(如果可能重复)
    cache := make(map[string]string)
    
    for i, comment := range comments {
        if escaped, ok := cache[comment]; ok {
            results[i] = escaped
        } else {
            escaped := html.EscapeString(comment)
            cache[comment] = escaped
            results[i] = escaped
        }
    }
    
    return results
}

四、快速参考

核心函数

函数说明转义字符示例
EscapeString(s)转义 HTML 特殊字符< > & ' "html.EscapeString("<>")&lt;&gt;
UnescapeString(s)反转换 HTML 实体所有 HTML 实体html.UnescapeString("&lt;")<

转义字符对照表

字符转义后说明
<&lt;小于号 / 标签开始
>&gt;大于号 / 标签结束
&&amp;和号 / 实体开始
'&#39;单引号
"&#34;双引号

支持的 HTML 实体类型

类型示例说明
命名实体&amp; &lt; &copy;预定义的实体名称
十进制实体&#39; &#225;&# + 十进制数字
十六进制实体&#x27; &#xE1;&#x + 十六进制数字

使用场景

场景推荐函数说明
显示用户输入EscapeString防止 XSS 攻击
生成 HTML 属性EscapeString防止属性注入
解析 HTML 实体UnescapeString还原原始文本
日志记录EscapeString防止日志注入
处理 XML/HTML两者结合根据需求选择

与 template 包配合

用途自动转义
html手动转义
html/template模板渲染是(推荐)

五、与其他包配合

与 fmt 包配合

package main

import (
    "fmt"
    "html"
)

func main() {
    name := "<script>alert('XSS')</script>"
    
    // 错误:直接输出
    fmt.Printf("错误:<div>%s</div>\n", name)
    
    // 正确:转义后输出
    fmt.Printf("正确:<div>%s</div>\n", html.EscapeString(name))
}

运行

$ go run main.go
错误:<div><script>alert('XSS')</script></div>
正确:<div>&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;</div>

与 strings 包配合

package main

import (
    "fmt"
    "html"
    "strings"
)

func main() {
    userInput := []string{
        "<script>",
        "Tom & Jerry",
        "\"Hello\"",
    }
    
    // 批量转义
    var sb strings.Builder
    sb.WriteString("<ul>\n")
    for _, input := range userInput {
        sb.WriteString("  <li>")
        sb.WriteString(html.EscapeString(input))
        sb.WriteString("</li>\n")
    }
    sb.WriteString("</ul>\n")
    
    fmt.Println(sb.String())
}

运行

$ go run main.go
<ul>
  <li>&lt;script&gt;</li>
  <li>Tom &amp; Jerry</li>
  <li>&#34;Hello&#34;</li>
</ul>

与 net/http 包配合

package main

import (
    "html"
    "net/http"
)

func main() {
    http.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
        query := r.URL.Query().Get("q")
        
        w.Header().Set("Content-Type", "text/html; charset=utf-8")
        
        // 转义后显示
        fmt.Fprintf(w, "<h1>搜索结果:%s</h1>", html.EscapeString(query))
    })
    
    http.ListenAndServe(":8080", nil)
}

与 html/template 包配合

package main

import (
    "html"
    "html/template"
    "os"
)

func main() {
    // 手动转义(当需要精细控制时)
    manual := html.EscapeString("<script>")
    
    // 使用 template(推荐,自动转义)
    tmpl := template.Must(template.New("test").Parse("{{.}}"))
    tmpl.Execute(os.Stdout, "<script>")  // 自动转义
}

六、注意事项

1. EscapeString 只转义 5 个字符

// EscapeString 只转义:< > & ' "
input := "<>&'\""
escaped := html.EscapeString(input)
fmt.Println(escaped)  // &lt;&gt;&amp;&#39;&#34;

// 其他字符不会转义
input2 := "你好,世界!\n\r\t"
escaped2 := html.EscapeString(input2)
fmt.Println(escaped2)  // 你好,世界!\n\r\t (不变)

2. UnescapeString 支持更多实体

// 命名实体
fmt.Println(html.UnescapeString("&copy;"))  // ©
fmt.Println(html.UnescapeString("&reg;"))   // ®

// 十进制实体
fmt.Println(html.UnescapeString("&#169;"))  // ©

// 十六进制实体
fmt.Println(html.UnescapeString("&#xA9;"))  // ©

3. 不适用于 JavaScript 上下文

// 错误:在 JavaScript 中使用 html.EscapeString
// <script>var x = "{{.}}";</script>
// 即使转义了引号,仍可能有其他注入方式

// 正确:使用专门的 JS 转义或 template.JS

4. 转义是单向的(信息丢失)

// 多个不同的原始字符串可能转义为相同结果
s1 := "&amp;"
s2 := "&"

escaped1 := html.EscapeString(s1)  // &amp;amp;
escaped2 := html.EscapeString(s2)  // &amp;

// UnescapeString 后
fmt.Println(html.UnescapeString(escaped1))  // &amp;
fmt.Println(html.UnescapeString(escaped2))  // &

5. 性能考虑

// 对于大量文本,转义操作有开销
// 建议:
// 1. 缓存已转义的内容
// 2. 使用 html/template 自动处理
// 3. 避免重复转义同一内容

七、常见问题

Q1: EscapeString 和 html/template 有什么区别?

A:

  • html.EscapeString 是手动转义,需要开发者显式调用
  • html/template 是自动转义,在模板渲染时自动处理
  • 推荐使用 html/template 生成 HTML,更安全方便

Q2: 为什么 UnescapeString(EscapeString(s)) == s 总是成立,但反过来不一定?

A:

  • EscapeString 只转义 5 个字符,信息不丢失
  • UnescapeString 支持更多实体(如 &copy;©
  • 所以 UnescapeString("&copy;")©,但 EscapeString("©")©(不变)

Q3: 如何处理富文本(允许部分 HTML 标签)?

A:

  1. 先用 EscapeString 转义所有内容
  2. 再用正则恢复允许的标签
  3. 或使用专门的 HTML 清理库(如 bluemonday)

最后更新:2026-04-04
Go 版本:Go 1.23+