mirror of
https://github.com/golang/go
synced 2024-11-22 01:24:42 -07:00
exp/template: add a JavaScript escaper.
R=r CC=golang-dev https://golang.org/cl/4671048
This commit is contained in:
parent
a33cc423b4
commit
33705ddea1
@ -158,6 +158,8 @@ var execTests = []execTest{
|
|||||||
"<script>alert("XSS");</script>", nil, true},
|
"<script>alert("XSS");</script>", nil, true},
|
||||||
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
||||||
"<script>alert("XSS");</script>", nil, true},
|
"<script>alert("XSS");</script>", nil, true},
|
||||||
|
// JS.
|
||||||
|
{"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true},
|
||||||
// Booleans
|
// Booleans
|
||||||
{"not", "{{not true}} {{not false}}", "false true", nil, true},
|
{"not", "{{not true}} {{not false}}", "false true", nil, true},
|
||||||
{"and", "{{and 0 0}} {{and 1 0}} {{and 0 1}} {{and 1 1}}", "false false false true", nil, true},
|
{"and", "{{and 0 0}} {{and 1 0}} {{and 0 1}} {{and 1 1}}", "false false false true", nil, true},
|
||||||
@ -248,3 +250,21 @@ func TestExecuteError(t *testing.T) {
|
|||||||
t.Errorf("expected os.EPERM; got %s", err)
|
t.Errorf("expected os.EPERM; got %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJSEscaping(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
in, exp string
|
||||||
|
}{
|
||||||
|
{`a`, `a`},
|
||||||
|
{`'foo`, `\'foo`},
|
||||||
|
{`Go "jump" \`, `Go \"jump\" \\`},
|
||||||
|
{`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`},
|
||||||
|
{"unprintable \uFDFF", `unprintable \uFDFF`},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
s := JSEscapeString(tc.in)
|
||||||
|
if s != tc.exp {
|
||||||
|
t.Errorf("JS escaping [%s] got [%s] want [%s]", tc.in, s, tc.exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,10 +6,12 @@ package template
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FuncMap is the type of the map defining the mapping from names to functions.
|
// FuncMap is the type of the map defining the mapping from names to functions.
|
||||||
@ -20,6 +22,7 @@ type FuncMap map[string]interface{}
|
|||||||
var funcs = map[string]reflect.Value{
|
var funcs = map[string]reflect.Value{
|
||||||
"printf": reflect.ValueOf(fmt.Sprintf),
|
"printf": reflect.ValueOf(fmt.Sprintf),
|
||||||
"html": reflect.ValueOf(HTMLEscaper),
|
"html": reflect.ValueOf(HTMLEscaper),
|
||||||
|
"js": reflect.ValueOf(JSEscaper),
|
||||||
"and": reflect.ValueOf(and),
|
"and": reflect.ValueOf(and),
|
||||||
"or": reflect.ValueOf(or),
|
"or": reflect.ValueOf(or),
|
||||||
"not": reflect.ValueOf(not),
|
"not": reflect.ValueOf(not),
|
||||||
@ -98,34 +101,34 @@ func not(arg interface{}) (truth bool) {
|
|||||||
// HTML escaping.
|
// HTML escaping.
|
||||||
|
|
||||||
var (
|
var (
|
||||||
escQuot = []byte(""") // shorter than """
|
htmlQuot = []byte(""") // shorter than """
|
||||||
escApos = []byte("'") // shorter than "'"
|
htmlApos = []byte("'") // shorter than "'"
|
||||||
escAmp = []byte("&")
|
htmlAmp = []byte("&")
|
||||||
escLt = []byte("<")
|
htmlLt = []byte("<")
|
||||||
escGt = []byte(">")
|
htmlGt = []byte(">")
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
||||||
func HTMLEscape(w io.Writer, b []byte) {
|
func HTMLEscape(w io.Writer, b []byte) {
|
||||||
last := 0
|
last := 0
|
||||||
for i, c := range b {
|
for i, c := range b {
|
||||||
var esc []byte
|
var html []byte
|
||||||
switch c {
|
switch c {
|
||||||
case '"':
|
case '"':
|
||||||
esc = escQuot
|
html = htmlQuot
|
||||||
case '\'':
|
case '\'':
|
||||||
esc = escApos
|
html = htmlApos
|
||||||
case '&':
|
case '&':
|
||||||
esc = escAmp
|
html = htmlAmp
|
||||||
case '<':
|
case '<':
|
||||||
esc = escLt
|
html = htmlLt
|
||||||
case '>':
|
case '>':
|
||||||
esc = escGt
|
html = htmlGt
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w.Write(b[last:i])
|
w.Write(b[last:i])
|
||||||
w.Write(esc)
|
w.Write(html)
|
||||||
last = i + 1
|
last = i + 1
|
||||||
}
|
}
|
||||||
w.Write(b[last:])
|
w.Write(b[last:])
|
||||||
@ -155,3 +158,92 @@ func HTMLEscaper(args ...interface{}) string {
|
|||||||
}
|
}
|
||||||
return HTMLEscapeString(s)
|
return HTMLEscapeString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JavaScript escaping.
|
||||||
|
|
||||||
|
var (
|
||||||
|
jsLowUni = []byte(`\u00`)
|
||||||
|
hex = []byte("0123456789ABCDEF")
|
||||||
|
|
||||||
|
jsBackslash = []byte(`\\`)
|
||||||
|
jsApos = []byte(`\'`)
|
||||||
|
jsQuot = []byte(`\"`)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
||||||
|
func JSEscape(w io.Writer, b []byte) {
|
||||||
|
last := 0
|
||||||
|
for i := 0; i < len(b); i++ {
|
||||||
|
c := b[i]
|
||||||
|
|
||||||
|
if ' ' <= c && c < utf8.RuneSelf && c != '\\' && c != '"' && c != '\'' {
|
||||||
|
// fast path: nothing to do
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.Write(b[last:i])
|
||||||
|
|
||||||
|
if c < utf8.RuneSelf {
|
||||||
|
// Quotes and slashes get quoted.
|
||||||
|
// Control characters get written as \u00XX.
|
||||||
|
switch c {
|
||||||
|
case '\\':
|
||||||
|
w.Write(jsBackslash)
|
||||||
|
case '\'':
|
||||||
|
w.Write(jsApos)
|
||||||
|
case '"':
|
||||||
|
w.Write(jsQuot)
|
||||||
|
default:
|
||||||
|
w.Write(jsLowUni)
|
||||||
|
t, b := c>>4, c&0x0f
|
||||||
|
w.Write(hex[t : t+1])
|
||||||
|
w.Write(hex[b : b+1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unicode rune.
|
||||||
|
rune, size := utf8.DecodeRune(b[i:])
|
||||||
|
if unicode.IsPrint(rune) {
|
||||||
|
w.Write(b[i : i+size])
|
||||||
|
} else {
|
||||||
|
// TODO(dsymonds): Do this without fmt?
|
||||||
|
fmt.Fprintf(w, "\\u%04X", rune)
|
||||||
|
}
|
||||||
|
i += size - 1
|
||||||
|
}
|
||||||
|
last = i + 1
|
||||||
|
}
|
||||||
|
w.Write(b[last:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
|
||||||
|
func JSEscapeString(s string) string {
|
||||||
|
// Avoid allocation if we can.
|
||||||
|
if strings.IndexFunc(s, jsIsSpecial) < 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
JSEscape(&b, []byte(s))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsIsSpecial(rune int) bool {
|
||||||
|
switch rune {
|
||||||
|
case '\\', '\'', '"':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return rune < ' ' || utf8.RuneSelf <= rune
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
||||||
|
// representation of its arguments.
|
||||||
|
func JSEscaper(args ...interface{}) string {
|
||||||
|
ok := false
|
||||||
|
var s string
|
||||||
|
if len(args) == 1 {
|
||||||
|
s, ok = args[0].(string)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
s = fmt.Sprint(args...)
|
||||||
|
}
|
||||||
|
return JSEscapeString(s)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user