mirror of
https://github.com/golang/go
synced 2024-11-24 18:10:02 -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},
|
||||
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
|
||||
"<script>alert("XSS");</script>", nil, true},
|
||||
// JS.
|
||||
{"js", `{{js .}}`, `It\'d be nice.`, `It'd be nice.`, true},
|
||||
// Booleans
|
||||
{"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},
|
||||
@ -248,3 +250,21 @@ func TestExecuteError(t *testing.T) {
|
||||
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 (
|
||||
"bytes"
|
||||
"io"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"utf8"
|
||||
)
|
||||
|
||||
// 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{
|
||||
"printf": reflect.ValueOf(fmt.Sprintf),
|
||||
"html": reflect.ValueOf(HTMLEscaper),
|
||||
"js": reflect.ValueOf(JSEscaper),
|
||||
"and": reflect.ValueOf(and),
|
||||
"or": reflect.ValueOf(or),
|
||||
"not": reflect.ValueOf(not),
|
||||
@ -98,34 +101,34 @@ func not(arg interface{}) (truth bool) {
|
||||
// HTML escaping.
|
||||
|
||||
var (
|
||||
escQuot = []byte(""") // shorter than """
|
||||
escApos = []byte("'") // shorter than "'"
|
||||
escAmp = []byte("&")
|
||||
escLt = []byte("<")
|
||||
escGt = []byte(">")
|
||||
htmlQuot = []byte(""") // shorter than """
|
||||
htmlApos = []byte("'") // shorter than "'"
|
||||
htmlAmp = []byte("&")
|
||||
htmlLt = []byte("<")
|
||||
htmlGt = []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
|
||||
var html []byte
|
||||
switch c {
|
||||
case '"':
|
||||
esc = escQuot
|
||||
html = htmlQuot
|
||||
case '\'':
|
||||
esc = escApos
|
||||
html = htmlApos
|
||||
case '&':
|
||||
esc = escAmp
|
||||
html = htmlAmp
|
||||
case '<':
|
||||
esc = escLt
|
||||
html = htmlLt
|
||||
case '>':
|
||||
esc = escGt
|
||||
html = htmlGt
|
||||
default:
|
||||
continue
|
||||
}
|
||||
w.Write(b[last:i])
|
||||
w.Write(esc)
|
||||
w.Write(html)
|
||||
last = i + 1
|
||||
}
|
||||
w.Write(b[last:])
|
||||
@ -155,3 +158,92 @@ func HTMLEscaper(args ...interface{}) string {
|
||||
}
|
||||
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