Ce face html/template.Template
html/template are aceleași funcții și aceeași sintaxă ca text/template (New, Parse, Execute, {{range}}, {{if}}…), dar înțelege HTML-ul. La Execute, fiecare valoare este codată potrivit locului în care apare: în text < devine <, într-un atribut href se blochează adresele javascript:, într-un <script> valoarea devine un literal JavaScript sigur.
Asta te protejează de XSS (cross-site scripting): un comentariu care conține <script> apare pe pagină ca text, nu se execută. Dacă ai HTML de încredere pe care vrei să-l inserezi ca atare, îl marchezi explicit cu tipul template.HTML; nu face asta niciodată cu date de la utilizatori.
Regula: pentru orice ieșire HTML, importă html/template, nu text/template. Cele două pachete au același nume (template), deci verifică importul.
Ce returnează
La fel ca la text/template: Execute returnează o eroare.
Exemple
Exemplul 1: Comentarii afișate în siguranță
Scriptul din comentariu apare ca text. Adresa javascript: din href este înlocuită cu valoarea inofensivă #ZgotmplZ, iar & din adresa lui Ion devine &, cum cere HTML-ul.
package main
import (
"html/template"
"os"
)
type Comentariu struct {
Autor, Text, Site string
}
func main() {
t := template.Must(template.New("comentarii").Parse(`<ul>
{{range .}} <li><a href="{{.Site}}">{{.Autor}}</a>: {{.Text}}</li>
{{end}}</ul>
`))
comentarii := []Comentariu{
{"Ana", "Articol foarte util!", "https://ana.example.ro"},
{"Atacator", `<script>alert("XSS")</script>`, `javascript:alert(1)`},
{"Ion", `Prețul e < 100 & merită "luat"`, "https://ion.example.ro/?a=1&b=2"},
}
t.Execute(os.Stdout, comentarii)
}
Rezultat
<ul>
<li><a href="https://ana.example.ro">Ana</a>: Articol foarte util!</li>
<li><a href="#ZgotmplZ">Atacator</a>: <script>alert("XSS")</script></li>
<li><a href="https://ion.example.ro/?a=1&b=2">Ion</a>: Prețul e < 100 & merită "luat"</li>
</ul>
Exemplul 2: Aceeași valoare în contexte diferite
Valoarea este codată diferit în text, în atribut, în URL și în JavaScript. template.HTML marchează HTML de încredere, pus neschimbat.
package main
import (
"html/template"
"os"
)
func main() {
t := template.Must(template.New("pagina").Parse(`<p title="{{.Nume}}">{{.Nume}}</p>
<a href="/cauta?q={{.Nume}}">caută</a>
<script>var nume = {{.Nume}};</script>
{{.Banner}}
`))
t.Execute(os.Stdout, map[string]any{
"Nume": `Ana "Mica" & <Co>`,
"Banner": template.HTML(`<strong>Reduceri -20%</strong>`),
})
}
Rezultat
<p title="Ana "Mica" & <Co>">Ana "Mica" & <Co></p>
<a href="/cauta?q=Ana%20%22Mica%22%20%26%20%3cCo%3e">caută</a>
<script>var nume = "Ana \"Mica\" \u0026 \u003cCo\u003e";</script>
<strong>Reduceri -20%</strong>
Exemplul 3: Pagină servită de un handler
package main
import (
"bytes"
"fmt"
"html/template"
"net/http"
"net/http/httptest"
)
var pagina = template.Must(template.New("salut").Parse(
`<h1>Salut, {{.}}!</h1>`))
func salut(w http.ResponseWriter, r *http.Request) {
var buf bytes.Buffer
if err := pagina.Execute(&buf, r.URL.Query().Get("nume")); err != nil {
http.Error(w, "eroare internă", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
buf.WriteTo(w)
}
func main() {
for _, q := range []string{"Maria", "<img src=x onerror=alert(1)>"} {
rec := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
qv := req.URL.Query()
qv.Set("nume", q)
req.URL.RawQuery = qv.Encode()
salut(rec, req)
fmt.Println(rec.Body.String())
}
}
Rezultat
<h1>Salut, Maria!</h1>
<h1>Salut, <img src=x onerror=alert(1)>!</h1>
Greșeli frecvente
- Imporți
text/templatepentru o pagină web: datele nu sunt codate și pagina este vulnerabilă la XSS. - Convertești date de la utilizatori în
template.HTMLca să „meargă formatarea”: anulezi protecția. Curăță HTML-ul cu o bibliotecă dedicată înainte, sau nu permite HTML. - Construiești HTML prin concatenare de texte în Go și îl dai șablonului: șablonul nu mai poate proteja ce nu vede ca date separate.
Comenzi înrudite
html/template.HTMLEscapeString: Codează caracterele speciale HTML (< > & ‘ „) dintr-un text, ca să poată fi pus sigur într-o pagină.text/template.New: Creează un șablon text cu un nume; Parse îi încarcă textul cu acțiuni {{…}} care vor fi completate din date.text/template.Template.Execute: Completează șablonul cu datele date și scrie rezultatul într-un io.Writer (ecran, fișier, răspuns HTTP, buffer).net/http.ResponseWriter: Interfața prin care handler-ul construiește răspunsul: antete (Header), cod de stare (WriteHeader) și corp (Write).