]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: add an html escaping function.
authorRob Pike <r@golang.org>
Tue, 5 Jul 2011 05:58:54 +0000 (15:58 +1000)
committerRob Pike <r@golang.org>
Tue, 5 Jul 2011 05:58:54 +0000 (15:58 +1000)
R=golang-dev, dsymonds, adg
CC=golang-dev
https://golang.org/cl/4626092

src/pkg/exp/template/exec_test.go
src/pkg/exp/template/funcs.go

index 8784a0b9fdd4afe6664dd980252101f9cc112804..d9e2cda069465fb494b907c5bd74881188b94f8c 100644 (file)
@@ -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},
index 88f82f3b2c1fc080585ac853edc1e0cf3227a201..93f8816eb554ede0fc20eaef74a22935a3105954 100644 (file)
@@ -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)
+}