aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Pike <r@golang.org>2011-07-05 15:58:54 +1000
committerRob Pike <r@golang.org>2011-07-05 15:58:54 +1000
commitcc9fed7c1a9214b511544b53942fd4de07c76bb3 (patch)
tree329f92d6f148fb92c199d43cf3afe527e292a709
parent104f57ad39afb40449d1835af3fefc2e9fa4f102 (diff)
downloadgo-cc9fed7c1a9214b511544b53942fd4de07c76bb3.tar.gz
go-cc9fed7c1a9214b511544b53942fd4de07c76bb3.zip
exp/template: add an html escaping function.
R=golang-dev, dsymonds, adg CC=golang-dev https://golang.org/cl/4626092
-rw-r--r--src/pkg/exp/template/exec_test.go4
-rw-r--r--src/pkg/exp/template/funcs.go65
2 files changed, 69 insertions, 0 deletions
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>"}}`,
+ "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", tVal, true},
+ {"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
+ "&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", 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("&#34;") // shorter than "&quot;"
+ escApos = []byte("&#39;") // shorter than "&apos;"
+ escAmp = []byte("&amp;")
+ escLt = []byte("&lt;")
+ escGt = []byte("&gt;")
+)
+
+// 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)
+}