Golang中[]uint8在HTML模板中打印出现异常问题

Golang中[]uint8在HTML模板中打印出现异常问题 我尝试在HTML模板的<script></script>块中打印一个[]uint8切片,但它出现了奇怪的行为。我这么说是因为打印[]int8的表现符合我的预期。

如果上面的Playground链接失效,代码是:

package main

import (
	"html/template"
	"os"
)

func main() {
	Create := func(name, t string) *template.Template {
		return template.Must(template.New(name).Parse(t))
	}
	t2 := Create("t2", "outside script:\n{{.INT8_ARR}}\n{{.UINT8_ARR}}\n{{.INT8}}\n{{.UINT8}}\n<script>\n{{.INT8_ARR}}\n{{.UINT8_ARR}}\n{{.INT8}}\n{{.UINT8}}\n</script>")

	t2.Execute(os.Stdout, struct {
		INT8_ARR  []int8
		UINT8_ARR []uint8
		INT8      int8
		UINT8     uint8
	}{
		INT8_ARR:  []int8{2, 3, 4},
		UINT8_ARR: []uint8{2, 3, 4},
		INT8:      5,
		UINT8:     6,
	})
}

输出结果是:

outside script:
[2 3 4]
[2 3 4]
5
6
<script>
[2,3,4]
"AgME"
 5 
 6 
</script>

出于某种原因,它似乎对uint8数组进行了base64编码(即"AgME")。有人知道这是怎么回事吗?是不是html/template认为这是一个字节数组并试图对其进行清理?


更多关于Golang中[]uint8在HTML模板中打印出现异常问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

在调试器中运行了模板代码,并找到了一个更简短的示例来展示同样的问题。

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	v, _ := json.Marshal([]int8{3, 4, 5})
	fmt.Println(string(v))
	v, _ = json.Marshal([]uint8{3, 4, 5})
	fmt.Println(string(v))
}

这将打印:

[3,4,5]
"AwQF"

深入研究后,我发现了:src/encoding/json/encode.go - go - Git at Google

看来我是对的。代码似乎将字节切片与 uint8 切片混淆了。:( 规范指出 byte 只是 uint8 的别名(依据:The Go Programming Language Specification - The Go Programming Language)。所以 uint8 与 json.Encoder 配合得不太好。

更多关于Golang中[]uint8在HTML模板中打印出现异常问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是 html/template 包对 []uint8(即 []byte)类型的特殊处理。在 HTML 模板中,[]uint8 被识别为字节切片,在 <script> 标签内会自动进行 JavaScript 字符串转义,导致显示为 base64 编码的字符串。

示例代码演示了这个问题:

package main

import (
	"html/template"
	"os"
)

func main() {
	tmpl := `Outside script: {{.ByteSlice}}
<script>
console.log({{.ByteSlice}});
console.log({{.Uint8Slice}});
</script>`

	t := template.Must(template.New("test").Parse(tmpl))
	
	data := struct {
		ByteSlice  []byte
		Uint8Slice []uint8
	}{
		ByteSlice:  []byte{72, 101, 108, 108, 111}, // "Hello" 的 ASCII 码
		Uint8Slice: []uint8{72, 101, 108, 108, 111},
	}
	
	t.Execute(os.Stdout, data)
}

输出:

Outside script: [72 101 108 108 111]
<script>
console.log("SGVsbG8=");
console.log("SGVsbG8=");
</script>

要避免这个问题,可以将 []uint8 转换为其他类型。以下是几种解决方案:

  1. 转换为字符串
package main

import (
	"html/template"
	"os"
)

func main() {
	tmpl := `<script>
console.log({{.AsString}});
</script>`

	t := template.Must(template.New("test").Parse(tmpl))
	
	byteSlice := []byte{72, 101, 108, 108, 111}
	
	data := struct {
		AsString string
	}{
		AsString: string(byteSlice),
	}
	
	t.Execute(os.Stdout, data)
}
  1. 使用自定义函数
package main

import (
	"html/template"
	"os"
)

func main() {
	funcMap := template.FuncMap{
		"toJSArray": func(b []byte) template.JS {
			str := "["
			for i, v := range b {
				if i > 0 {
					str += ","
				}
				str += string('0' + v/10)
				str += string('0' + v%10)
			}
			str += "]"
			return template.JS(str)
		},
	}

	tmpl := `<script>
console.log({{.ByteSlice | toJSArray}});
</script>`

	t := template.Must(template.New("test").Funcs(funcMap).Parse(tmpl))
	
	data := struct {
		ByteSlice []byte
	}{
		ByteSlice: []byte{2, 3, 4},
	}
	
	t.Execute(os.Stdout, data)
}
  1. 使用 template.JS 类型
package main

import (
	"html/template"
	"os"
)

func main() {
	tmpl := `<script>
console.log({{.AsJS}});
</script>`

	t := template.Must(template.New("test").Parse(tmpl))
	
	byteSlice := []byte{2, 3, 4}
	jsArray := "["
	for i, v := range byteSlice {
		if i > 0 {
			jsArray += ","
		}
		jsArray += string('0' + v)
	}
	jsArray += "]"
	
	data := struct {
		AsJS template.JS
	}{
		AsJS: template.JS(jsArray),
	}
	
	t.Execute(os.Stdout, data)
}

html/template 包对 <script> 标签内的内容进行自动转义是为了防止 XSS 攻击。[]uint8 被当作原始字节数据处理,因此会进行 base64 编码以确保安全。

回到顶部