From cc9fed7c1a9214b511544b53942fd4de07c76bb3 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Tue, 5 Jul 2011 15:58:54 +1000 Subject: [PATCH] exp/template: add an html escaping function. R=golang-dev, dsymonds, adg CC=golang-dev https://golang.org/cl/4626092 --- src/pkg/exp/template/exec_test.go | 4 ++ src/pkg/exp/template/funcs.go | 65 +++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/pkg/exp/template/exec_test.go b/src/pkg/exp/template/exec_test.go index 8784a0b9fd..d9e2cda069 100644 --- a/src/pkg/exp/template/exec_test.go +++ b/src/pkg/exp/template/exec_test.go @@ -150,6 +150,10 @@ var execTests = []execTest{ {"printf field", `{{printf "%s" .U.V}}`, "v", tVal, true}, {"printf method", `{{printf "%s" .Method0}}`, "resultOfMethod0", tVal, true}, {"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) resultOfMethod0", tVal, true}, + {"html", `{{html ""}}`, + "<script>alert("XSS");</script>", tVal, true}, + {"html pipeline", `{{printf "" | html}}`, + "<script>alert("XSS");</script>", tVal, true}, // With. {"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true}, {"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true}, diff --git a/src/pkg/exp/template/funcs.go b/src/pkg/exp/template/funcs.go index 88f82f3b2c..93f8816eb5 100644 --- a/src/pkg/exp/template/funcs.go +++ b/src/pkg/exp/template/funcs.go @@ -5,8 +5,11 @@ package template import ( + "bytes" + "io" "fmt" "reflect" + "strings" ) // FuncMap is the type of the map defining the mapping from names to functions. @@ -16,6 +19,7 @@ type FuncMap map[string]interface{} var funcs = map[string]reflect.Value{ "printf": reflect.ValueOf(fmt.Sprintf), + "html": reflect.ValueOf(HTMLEscaper), } // addFuncs adds to values the functions in funcs, converting them to reflect.Values. @@ -61,3 +65,64 @@ func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) { } return reflect.Value{}, false } + +// HTML escaping + +var ( + escQuot = []byte(""") // shorter than """ + escApos = []byte("'") // shorter than "'" + escAmp = []byte("&") + escLt = []byte("<") + escGt = []byte(">") +) + +// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. +func HTMLEscape(w io.Writer, b []byte) { + last := 0 + for i, c := range b { + var esc []byte + switch c { + case '"': + esc = escQuot + case '\'': + esc = escApos + case '&': + esc = escAmp + case '<': + esc = escLt + case '>': + esc = escGt + default: + continue + } + w.Write(b[last:i]) + w.Write(esc) + last = i + 1 + } + w.Write(b[last:]) +} + +// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. +func HTMLEscapeString(s string) string { + // Avoid allocation if we can. + if strings.IndexAny(s, `'"&<>`) < 0 { + return s + } + var b bytes.Buffer + HTMLEscape(&b, []byte(s)) + return b.String() +} + +// HTMLEscaper returns the escaped HTML equivalent of the textual +// representation of its arguments. +func HTMLEscaper(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return HTMLEscapeString(s) +}