Типы экранирования HTML/JavaScript/URL пакета html/template языка Go
type CSS
type CSS string
CSS инкапсулирует заранее проверенное содержимое, соответствующее:
- Производству CSS3 таблиц стилей (напр.
p { color: purple }
) - Производству CSS3 правил (напр.
a[href=~"https:"].foo#bar
) - Производству CSS3 деклараций (напр.
color: red; margin: 2px
) - Производству CSS3 значений (напр.
rgba(0, 0, 255, 127)
)
Внимание: Использование этого типа представляет угрозу безопасности - содержимое должно поступать из доверенного источника, так как включается в вывод шаблона без изменений.
type Error
type Error struct {
ErrorCode ErrorCode // Тип ошибки
Node parse.Node // Узел, вызвавший проблему (если известен)
Name string // Имя шаблона с ошибкой
Line int // Номер строки в исходнике шаблона
Description string // Человекочитаемое описание
}
Описывает проблему, возникшую при экранировании шаблона.
Коды ошибок (ErrorCode)
const (
OK ErrorCode = iota // Нет ошибки
// Неоднозначный контекст в URL
ErrAmbigContext
// Некорректный HTML (незакрытые теги/атрибуты)
ErrBadHTML
// Ветки if/range заканчиваются в разных контекстах
ErrBranchEnd
// Шаблон заканчивается в нетекстовом контексте
ErrEndContext
// Отсутствует указанный шаблон
ErrNoSuchTemplate
// Невозможно вычислить контекст вывода
ErrOutputContext
// Незавершённый набор символов в regexp JS
ErrPartialCharset
// Незавершённая escape-последовательность
ErrPartialEscape
// Повторный вход в range с изменением контекста
ErrRangeLoopReentry
// Неоднозначная интерпретация '/' (деление или regexp)
ErrSlashAmbig
// Использование запрещённых экранировщиков
ErrPredefinedEscaper
// Устаревшая ошибка для JS template literals
ErrJSTemplate
)
Особое значение ZgotmplZ
Пример:
<img src="{{.X}}">
где {{.X}}
вычисляется в javascript:...
Результат:
<img src="#ZgotmplZ">
Объяснение:
“ZgotmplZ” - специальное значение, указывающее, что небезопасное содержимое достигло CSS или URL контекста во время выполнения. Для доверенных источников используйте типы содержимого для отключения фильтрации: URL(
javascript:…)
.
type FuncMap
type FuncMap = template.FuncMap
FuncMap - это псевдоним для типа template.FuncMap
из пакета text/template, представляющий карту функций для использования в шаблонах.
type HTML
type HTML string
HTML инкапсулирует безопасный фрагмент HTML-документа. Не следует использовать для:
- HTML из ненадёжных источников
- HTML с незакрытыми тегами или комментариями
Внимание: Содержимое должно поступать из доверенного источника, так как включается в вывод шаблона без изменений.
type HTMLAttr
type HTMLAttr string
HTMLAttr инкапсулирует безопасный HTML-атрибут, например dir="ltr"
.
Внимание: Как и с HTML, содержимое должно быть доверенным, так как включается без экранирования.
type JS
type JS string
JS инкапсулирует безопасное выражение EcmaScript5, например (x + y * z())
.
Особенности:
- Авторы шаблонов должны обеспечивать правильный порядок операций
- Не должно быть неоднозначности между выражениями и инструкциями
Внимание: Не безопасно использовать для непроверенного JSON. Для JSON лучше использовать json.Unmarshal
.
type JSStr
type JSStr string
JSStr инкапсулирует безопасную строку для JavaScript, которая будет заключена в кавычки.
Требования:
- Допустимые символы или escape-последовательности
- Запрещены переносы строк внутри escape-последовательностей
Пример:
JSStr("foo\\nbar") // допустимо
JSStr("foo\\\nbar") // недопустимо
type Srcset
type Srcset string
Srcset инкапсулирует безопасный атрибут srcset для тегов <img>
(спецификация HTML5).
type Template
type Template struct {
Tree *parse.Tree // Абстрактное синтаксическое дерево шаблона
// ... скрытые поля
}
Template - это специализированная версия text/template
, которая генерирует безопасные фрагменты HTML-документов.
Ключевые особенности:
- Расширяет базовый функционал text/template
- Автоматически применяет контекстно-зависимое экранирование
- Гарантирует безопасность вывода при правильном использовании
Все типы безопасного контента (HTML, JS и др.) требуют, чтобы их содержимое поступало из доверенных источников, так как они включаются в вывод без дополнительного экранирования.
Пример Block
package main
import (
"html/template"
"log"
"os"
"strings"
)
func main() {
const (
master = `Names:{{block "list" .}}{{"\n"}}{{range .}}{{println "-" .}}{{end}}{{end}}`
overlay = `{{define "list"}} {{join . ", "}}{{end}} `
)
var (
funcs = template.FuncMap{"join": strings.Join}
guardians = []string{"Gamora", "Groot", "Nebula", "Rocket", "Star-Lord"}
)
masterTmpl, err := template.New("master").Funcs(funcs).Parse(master)
if err != nil {
log.Fatal(err)
}
overlayTmpl, err := template.Must(masterTmpl.Clone()).Parse(overlay)
if err != nil {
log.Fatal(err)
}
if err := masterTmpl.Execute(os.Stdout, guardians); err != nil {
log.Fatal(err)
}
if err := overlayTmpl.Execute(os.Stdout, guardians); err != nil {
log.Fatal(err)
}
}
Output:
Names:
- Gamora
- Groot
- Nebula
- Rocket
- Star-Lord
Names: Gamora, Groot, Nebula, Rocket, Star-Lord
Пример Glob
Здесь мы демонстрируем загрузку набора шаблонов из каталога.
package main
import (
"io"
"log"
"os"
"path/filepath"
"text/template"
)
// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
name string
contents string
}
func createTestDir(files []templateFile) string {
dir, err := os.MkdirTemp("", "template")
if err != nil {
log.Fatal(err)
}
for _, file := range files {
f, err := os.Create(filepath.Join(dir, file.name))
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = io.WriteString(f, file.contents)
if err != nil {
log.Fatal(err)
}
}
return dir
}
func main() {
// Here we create a temporary directory and populate it with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir := createTestDir([]templateFile{
// T0.tmpl is a plain template file that just invokes T1.
{"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
// T1.tmpl defines a template, T1 that invokes T2.
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
// T2.tmpl defines a template T2.
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer os.RemoveAll(dir)
// pattern is the glob pattern used to find all the template files.
pattern := filepath.Join(dir, "*.tmpl")
// Here starts the example proper.
// T0.tmpl is the first name matched, so it becomes the starting template,
// the value returned by ParseGlob.
tmpl := template.Must(template.ParseGlob(pattern))
err := tmpl.Execute(os.Stdout, nil)
if err != nil {
log.Fatalf("template execution: %s", err)
}
}
Output:
T0 invokes T1: (T1 invokes T2: (This is T2))
Пример Helpers
Этот пример демонстрирует один из способов поделиться некоторыми шаблонами и использовать их в разных контекстах. В этом варианте мы вручную добавляем несколько шаблонов драйверов в существующий пакет шаблонов.
package main
import (
"io"
"log"
"os"
"path/filepath"
"text/template"
)
// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
name string
contents string
}
func createTestDir(files []templateFile) string {
dir, err := os.MkdirTemp("", "template")
if err != nil {
log.Fatal(err)
}
for _, file := range files {
f, err := os.Create(filepath.Join(dir, file.name))
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = io.WriteString(f, file.contents)
if err != nil {
log.Fatal(err)
}
}
return dir
}
func main() {
// Here we create a temporary directory and populate it with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir := createTestDir([]templateFile{
// T1.tmpl defines a template, T1 that invokes T2.
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
// T2.tmpl defines a template T2.
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer os.RemoveAll(dir)
// pattern is the glob pattern used to find all the template files.
pattern := filepath.Join(dir, "*.tmpl")
// Here starts the example proper.
// Load the helpers.
templates := template.Must(template.ParseGlob(pattern))
// Add one driver template to the bunch; we do this with an explicit template definition.
_, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
if err != nil {
log.Fatal("parsing driver1: ", err)
}
// Add another driver template.
_, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
if err != nil {
log.Fatal("parsing driver2: ", err)
}
// We load all the templates before execution. This package does not require
// that behavior but html/template's escaping does, so it's a good habit.
err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
if err != nil {
log.Fatalf("driver1 execution: %s", err)
}
err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
if err != nil {
log.Fatalf("driver2 execution: %s", err)
}
}
Output:
Driver 1 calls T1: (T1 invokes T2: (This is T2))
Driver 2 calls T2: (This is T2)
Пример Parsefiles
Здесь мы демонстрируем загрузку набора шаблонов из файлов в разных каталогах.
package main
import (
"io"
"log"
"os"
"path/filepath"
"text/template"
)
// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
name string
contents string
}
func createTestDir(files []templateFile) string {
dir, err := os.MkdirTemp("", "template")
if err != nil {
log.Fatal(err)
}
for _, file := range files {
f, err := os.Create(filepath.Join(dir, file.name))
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = io.WriteString(f, file.contents)
if err != nil {
log.Fatal(err)
}
}
return dir
}
func main() {
// Here we create different temporary directories and populate them with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir1 := createTestDir([]templateFile{
// T1.tmpl is a plain template file that just invokes T2.
{"T1.tmpl", `T1 invokes T2: ({{template "T2"}})`},
})
dir2 := createTestDir([]templateFile{
// T2.tmpl defines a template T2.
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer func(dirs ...string) {
for _, dir := range dirs {
os.RemoveAll(dir)
}
}(dir1, dir2)
// Here starts the example proper.
// Let's just parse only dir1/T0 and dir2/T2
paths := []string{
filepath.Join(dir1, "T1.tmpl"),
filepath.Join(dir2, "T2.tmpl"),
}
tmpl := template.Must(template.ParseFiles(paths...))
err := tmpl.Execute(os.Stdout, nil)
if err != nil {
log.Fatalf("template execution: %s", err)
}
}
Output:
T1 invokes T2: (This is T2)
Пример Share
В этом примере показано, как использовать одну группу шаблонов драйверов с различными наборами вспомогательных шаблонов.
package main
import (
"io"
"log"
"os"
"path/filepath"
"text/template"
)
// templateFile defines the contents of a template to be stored in a file, for testing.
type templateFile struct {
name string
contents string
}
func createTestDir(files []templateFile) string {
dir, err := os.MkdirTemp("", "template")
if err != nil {
log.Fatal(err)
}
for _, file := range files {
f, err := os.Create(filepath.Join(dir, file.name))
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = io.WriteString(f, file.contents)
if err != nil {
log.Fatal(err)
}
}
return dir
}
func main() {
// Here we create a temporary directory and populate it with our sample
// template definition files; usually the template files would already
// exist in some location known to the program.
dir := createTestDir([]templateFile{
// T0.tmpl is a plain template file that just invokes T1.
{"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
// T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
})
// Clean up after the test; another quirk of running as an example.
defer os.RemoveAll(dir)
// pattern is the glob pattern used to find all the template files.
pattern := filepath.Join(dir, "*.tmpl")
// Here starts the example proper.
// Load the drivers.
drivers := template.Must(template.ParseGlob(pattern))
// We must define an implementation of the T2 template. First we clone
// the drivers, then add a definition of T2 to the template name space.
// 1. Clone the helper set to create a new name space from which to run them.
first, err := drivers.Clone()
if err != nil {
log.Fatal("cloning helpers: ", err)
}
// 2. Define T2, version A, and parse it.
_, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
if err != nil {
log.Fatal("parsing T2: ", err)
}
// Now repeat the whole thing, using a different version of T2.
// 1. Clone the drivers.
second, err := drivers.Clone()
if err != nil {
log.Fatal("cloning drivers: ", err)
}
// 2. Define T2, version B, and parse it.
_, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
if err != nil {
log.Fatal("parsing T2: ", err)
}
// Execute the templates in the reverse order to verify the
// first is unaffected by the second.
err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
if err != nil {
log.Fatalf("second execution: %s", err)
}
err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
if err != nil {
log.Fatalf("first: execution: %s", err)
}
}
Output:
T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))
func Must
func Must(t *Template, err error) *Template
Must - вспомогательная функция, которая оборачивает вызов функции, возвращающей (*Template, error), и вызывает панику, если ошибка не равна nil. Предназначена для инициализации переменных:
Пример:
var t = template.Must(template.New("name").Parse("html"))
func New
func New(name string) *Template
New создает новый HTML-шаблон с указанным именем.
func ParseFS (добавлена в Go 1.16)
func ParseFS(fs fs.FS, patterns ...string) (*Template, error)
ParseFS аналогична ParseFiles и ParseGlob, но читает из файловой системы fs вместо файловой системы хоста. Принимает список glob-шаблонов (большинство имен файлов работают как glob-шаблоны, соответствующие только самим себе).
func ParseFiles
func ParseFiles(filenames ...string) (*Template, error)
ParseFiles создает новый шаблон и парсит определения шаблонов из указанных файлов. Имя возвращаемого шаблона будет соответствовать базовому имени первого файла. Должен быть указан хотя бы один файл.
Особенности:
- При парсинге нескольких файлов с одинаковыми именами будет использован последний указанный файл
- Например,
ParseFiles("a/foo", "b/foo")
сохранит “b/foo” как шаблон “foo”, а “a/foo” будет недоступен
func ParseGlob
func ParseGlob(pattern string) (*Template, error)
ParseGlob создает новый шаблон и парсит определения шаблонов из файлов, соответствующих указанному шаблону (согласно семантике filepath.Match). Шаблон должен соответствовать хотя бы одному файлу.
Эквивалентно вызову ParseFiles со списком файлов, соответствующих шаблону. Как и в ParseFiles, при дублировании имен используется последний файл.
func (*Template) AddParseTree
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)
AddParseTree создает новый шаблон с указанным именем и деревом разбора, ассоциируя его с t.
Возвращает ошибку, если t или любой ассоциированный шаблон уже были выполнены.
func (*Template) Clone
func (t *Template) Clone() (*Template, error)
Clone создает копию шаблона, включая все ассоциированные шаблоны. Последующие вызовы Parse в копии будут добавлять шаблоны только в копию.
Возвращает ошибку, если t уже был выполнен.
func (*Template) DefinedTemplates (добавлена в Go 1.6)
func (t *Template) DefinedTemplates() string
DefinedTemplates возвращает строку с перечислением определенных шаблонов с префиксом “; defined templates are: “. Если шаблонов нет, возвращает пустую строку. Используется для генерации сообщений об ошибках.
func (*Template) Delims
func (t *Template) Delims(left, right string) *Template
Delims устанавливает разделители действий для последующих вызовов Parse, ParseFiles или ParseGlob. Вложенные определения шаблонов наследуют эти настройки. Пустые разделители соответствуют значениям по умолчанию: {{
и }}
.
Возвращает сам шаблон, позволяя объединять вызовы в цепочку.
Пример
package main
import (
"html/template"
"log"
"os"
)
func main() {
const text = "<<.Greeting>> {{.Name}}"
data := struct {
Greeting string
Name string
}{
Greeting: "Hello",
Name: "Joe",
}
t := template.Must(template.New("tpl").Delims("<<", ">>").Parse(text))
err := t.Execute(os.Stdout, data)
if err != nil {
log.Fatal(err)
}
}
Output:
Hello {{.Name}}
func (*Template) Execute
func (t *Template) Execute(wr io.Writer, data any) error
Применяет разобранный шаблон к указанным данным, записывая результат в wr
. В случае ошибки выполнение прерывается, но частичные результаты могут быть уже записаны. Шаблон можно выполнять параллельно, но при использовании общего Writer вывод может перемешиваться.
func (*Template) ExecuteTemplate
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error
Применяет шаблон с указанным именем, ассоциированный с t
, к данным и записывает результат в wr
. Поведение при ошибках и параллельном выполнении аналогично Execute
.
Отличие Execute от ExecuteTemplate
Отличие между Execute
и ExecuteTemplate
Основное различие между этими методами заключается в том, какой шаблон они выполняют:
Execute
- выполняет основной (корневой) шаблонExecuteTemplate
- выполняет указанный именованный шаблон (включая ассоциированные шаблоны)
Пример использования:
package main
import (
"os"
"text/template"
)
func main() {
// Создаем шаблон с именем "base"
tmpl := template.Must(template.New("base").Parse(`
{{define "header"}}<h1>Заголовок</h1>{{end}}
{{define "content"}}<p>Основное содержимое</p>{{end}}
<!DOCTYPE html>
<html>
<head><title>Пример</title></head>
<body>
{{template "header"}}
{{template "content"}}
</body>
</html>
`))
// 1. Execute - выполнит основной шаблон "base"
err := tmpl.Execute(os.Stdout, nil)
if err != nil {
panic(err)
}
// 2. ExecuteTemplate - можно выполнить любой из определенных шаблонов
err = tmpl.ExecuteTemplate(os.Stdout, "header", nil)
if err != nil {
panic(err)
}
}
Ключевые отличия:
Характеристика | Execute | ExecuteTemplate |
---|---|---|
Какой шаблон выполняется | Только основной (корневой) | Любой именованный (включая основной) |
Использование | Когда нужно выполнить весь шаблон | Когда нужна часть шаблона |
Производительность | Чуть быстрее | Чуть медленнее (поиск по имени) |
Гибкость | Меньше | Больше (доступ ко всем шаблонам) |
Когда использовать:
Execute
- для рендеринга всего шаблона целикомExecuteTemplate
- когда нужно:- выполнить часть шаблона
- переиспользовать компоненты в разных контекстах
- отладить отдельный блок шаблона
func (*Template) Funcs
func (t *Template) Funcs(funcMap FuncMap) *Template
Добавляет функции из переданной мапы в функциональную мапу шаблона. Должен вызываться до парсинга шаблона. Вызывает панику, если значение в мапе не является функцией с подходящим типом возвращаемого значения. Возвращает сам шаблон для цепочки вызовов.
func (*Template) Lookup
func (t *Template) Lookup(name string) *Template
Возвращает шаблон с указанным именем, ассоциированный с t
, или nil
если такого шаблона нет.
func (*Template) Name
func (t *Template) Name() string
Возвращает имя шаблона.
func (*Template) New
func (t *Template) New(name string) *Template
Создает новый HTML-шаблон с указанным именем, ассоциированный с текущим и имеющий те же разделители. Позволяет использовать вызовы шаблонов через {{template}}
. Если шаблон с таким именем уже существует, он будет заменен.
func (*Template) Option
func (t *Template) Option(opt ...string) *Template
Устанавливает опции шаблона в формате строк “ключ=значение” или простых строк. Вызывает панику при неизвестной или неверной опции.
Поддерживаемые опции:
missingkey=default
илиmissingkey=invalid
- поведение по умолчанию (продолжать выполнение)missingkey=zero
- возвращать нулевое значение для отсутствующего ключаmissingkey=error
- прерывать выполнение с ошибкой
func (*Template) Parse
func (t *Template) Parse(text string) (*Template, error)
Разбирает текст как тело шаблона для t
. Определения именованных шаблонов ({{define}}
/{{block}}
) становятся ассоциированными шаблонами. Шаблоны можно переопределять до первого вызова Execute
. Пустые определения (только пробелы/комментарии) не заменяют существующие шаблоны.
func (*Template) ParseFS
func (t *Template) ParseFS(fs fs.FS, patterns ...string) (*Template, error)
ParseFS аналогична Template.ParseFiles и Template.ParseGlob, но читает из абстрактной файловой системы fs вместо файловой системы хоста. Принимает список glob-шаблонов (большинство имен файлов работают как glob-шаблоны, соответствующие только самим себе).
func (*Template) ParseFiles
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
ParseFiles парсит указанные файлы и ассоциирует полученные шаблоны с t. При ошибке парсинг останавливается и возвращается nil, иначе возвращается t. Должен быть указан хотя бы один файл.
Особенности:
- При парсинге нескольких файлов с одинаковыми именами используется последний указанный файл
- Возвращает ошибку, если t или любой ассоциированный шаблон уже были выполнены
func (*Template) ParseGlob
func (t *Template) ParseGlob(pattern string) (*Template, error)
ParseGlob парсит шаблоны из файлов, соответствующих указанному шаблону (согласно семантике filepath.Match), и ассоциирует их с t. Эквивалентно вызову t.ParseFiles со списком файлов, соответствующих шаблону.
Особенности:
- Как и в ParseFiles, при дублировании имен используется последний файл
- Возвращает ошибку, если t или любой ассоциированный шаблон уже были выполнены
func (*Template) Templates
func (t *Template) Templates() []*Template
Templates возвращает срез всех шаблонов, ассоциированных с t, включая сам t.
type URL
type URL string
URL инкапсулирует безопасный URL или подстроку URL (согласно RFC 3986). URL типа javascript:checkThatFormNotEditedBeforeLeavingPage()
из доверенного источника может быть использован, но по умолчанию динамические javascript:
URL фильтруются как частый вектор атак.
Внимание: Использование этого типа представляет угрозу безопасности - содержимое должно поступать из доверенного источника, так как включается в вывод шаблона без изменений.