Go 語言標準庫 html/template 包深刻淺出

html/template 包是對 text/template 包的二次封裝,增長了一些安全性的處理,核心的接口和邏輯都是同樣的。html

安全性

渲染模板技術一直存在跨站腳本攻擊的風險,本質上是網站將用戶的輸入不做轉義寫入生成的頁面中,若是用戶提交一段瀏覽器腳本,則會在用戶的頁面中執行,進而產生不可預知的風險。前端

html/template 自動開啓安全模式將須要編碼的數據處理成純文本,各類不一樣的轉義上下文(escaping contextual)能夠安全的嵌入 HTML 模板,如 JavaScript、CSS 和 URI 上下文。json

舉個例子:api

import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")

輸出:

Hello, <script>alert('you have been pwned')</script>!
複製代碼

能夠看到 text/template 包中的 JavaScript 上下文沒有被轉義。瀏覽器

import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
複製代碼

輸出:安全

Hello, <script>alert('you have been pwned')</script>!bash

JavaScript 上下文被成功轉義。函數

上下文

包內有這幾種上下文: HTML、CSS、JavaScript 和 URI,在不一樣的上下文中會自動添加不一樣的轉義函數。post

<a href="/search?q={{.}}">{{.}}</a>網站

在解析時,每個 {{.}} 根據所處的上下文,都會被添加轉義函數。

<a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a>

URL 上下文中的添加 urlescaper 和 attrescaper 轉義函數。

HTML 上下文中的添加 htmlescaper 轉義函數

假設 {{.}} 的字符串表示爲 O'Reilly: How are <i>you</i>? 其中包含多個有害字符,下面是在不一樣上下文中的不一樣轉義結果:

Context                          {{.}} After
{{.}}                            O'Reilly: How are &lt;i&gt;you&lt;/i&gt;? <a title='{{.}}'> O&#39;Reilly: How are you? <a href="/{{.}}"> O&#39;Reilly: How are %3ci%3eyou%3c/i%3e? <a href="?q={{.}}"> O&#39;Reilly%3a%20How%20are%3ci%3e...%3f <a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...? <a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?" <a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f 複製代碼

能夠發現,'、<、>、? 在不一樣的上下文中會被轉義成不一樣的字符編碼。

若是 {{.}} 只包括無害字符,如字符串 left,則不會進行任何轉義。

Context                              {{.}} After
{{.}}                                left
<a title='{{.}}'>                    left
<a href='{{.}}'>                     left
<a href='/{{.}}'>                    left
<a href='?dir={{.}}'>                left
<a style="border-{{.}}: 4px">        left
<a style="align: {{.}}">             left
<a style="background: '{{.}}'> left <a style="background: url('{{.}}')>  left
<style>p.{{.}} {color:red}</style>   left
複製代碼

沒有任何字符串能夠在 JavaScript 上下文中使用。

若是 {{.}} 等於結構體 struct{A, B string}{ "foo", "bar" } 在模板 <script>var pair = {{.}};</script> 會渲染成:

<script>var pair = {"A": "foo", "B": "bar"};</script>

也就是將結構體用 json 包 marshaled 序列化以後嵌入 JavaScript 上下文中。

避免轉義的方法

默認狀況下,html/template 包假設全部的流(pipeline)提供純文本的輸入。添加轉義流階段必須正確和安全的嵌入不一樣上下文的純文本流中。

當一個數據不是純文本時,須要確保不會對它進行轉義。

類型 HTML,JS,URL 和其餘來自 content.go 的類型能夠避免轉義,由於它們不是純文本!

說白了就是傳入的時候不傳傳文本,使用各類類型的函數包裝一下再傳遞。

避免轉義的方法:

// 模板
Hello, {{.}}!

// 使用 template.HTML 包裝
tmpl.Execute(out, template.HTML(`<b>World</b>`))

輸出:

// 沒有被轉義
Hello, <b>World</b>!


// 添加轉義函數
{{ html .HTMLContent }}
複製代碼

類型函數有這麼幾種:CSS、HTML、HTMLAttr、JS、JSStr、URL 和 Srcset。

總結

由於是 HTML 模板,最終會生成前端頁面,爲了保證安全性,在內部將不一樣的字符串識別成不一樣的上下文,對每種上下文會自動添加不一樣的轉義函數對不一樣的內容進行轉義,若是不想內容被轉義,能夠把純文本使用各類類型函數包裝,包裝過的純文本會被轉義函數忽略。

參考資料

相關文章
相關標籤/搜索