// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package template import ( "bytes" "fmt" "io" "reflect" "strings" "unicode" "url" "utf8" ) // FuncMap is the type of the map defining the mapping from names to functions. // Each function must have either a single return value, or two return values of // which the second has type error. If the second argument evaluates to non-nil // during execution, execution terminates and Execute returns an error. type FuncMap map[string]interface{} var builtins = FuncMap{ "and": and, "html": HTMLEscaper, "index": index, "js": JSEscaper, "len": length, "not": not, "or": or, "print": fmt.Sprint, "printf": fmt.Sprintf, "println": fmt.Sprintln, "urlquery": URLQueryEscaper, } var builtinFuncs = createValueFuncs(builtins) // createValueFuncs turns a FuncMap into a map[string]reflect.Value func createValueFuncs(funcMap FuncMap) map[string]reflect.Value { m := make(map[string]reflect.Value) addValueFuncs(m, funcMap) return m } // addValueFuncs adds to values the functions in funcs, converting them to reflect.Values. func addValueFuncs(out map[string]reflect.Value, in FuncMap) { for name, fn := range in { v := reflect.ValueOf(fn) if v.Kind() != reflect.Func { panic("value for " + name + " not a function") } if !goodFunc(v.Type()) { panic(fmt.Errorf("can't handle multiple results from method/function %q", name)) } out[name] = v } } // addFuncs adds to values the functions in funcs. It does no checking of the input - // call addValueFuncs first. func addFuncs(out, in FuncMap) { for name, fn := range in { out[name] = fn } } // goodFunc checks that the function or method has the right result signature. func goodFunc(typ reflect.Type) bool { // We allow functions with 1 result or 2 results where the second is an error. switch { case typ.NumOut() == 1: return true case typ.NumOut() == 2 && typ.Out(1) == osErrorType: return true } return false } // findFunction looks for a function in the template, set, and global map. func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) { if tmpl != nil { if fn := tmpl.execFuncs[name]; fn.IsValid() { return fn, true } } if set != nil { if fn := set.execFuncs[name]; fn.IsValid() { return fn, true } } if fn := builtinFuncs[name]; fn.IsValid() { return fn, true } return reflect.Value{}, false } // Indexing. // index returns the result of indexing its first argument by the following // arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each // indexed item must be a map, slice, or array. func index(item interface{}, indices ...interface{}) (interface{}, error) { v := reflect.ValueOf(item) for _, i := range indices { index := reflect.ValueOf(i) var isNil bool if v, isNil = indirect(v); isNil { return nil, fmt.Errorf("index of nil pointer") } switch v.Kind() { case reflect.Array, reflect.Slice: var x int64 switch index.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: x = index.Int() case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: x = int64(index.Uint()) default: return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type()) } if x < 0 || x >= int64(v.Len()) { return nil, fmt.Errorf("index out of range: %d", x) } v = v.Index(int(x)) case reflect.Map: if !index.Type().AssignableTo(v.Type().Key()) { return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type()) } if x := v.MapIndex(index); x.IsValid() { v = x } else { v = reflect.Zero(v.Type().Key()) } default: return nil, fmt.Errorf("can't index item of type %s", index.Type()) } } return v.Interface(), nil } // Length // length returns the length of the item, with an error if it has no defined length. func length(item interface{}) (int, error) { v, isNil := indirect(reflect.ValueOf(item)) if isNil { return 0, fmt.Errorf("len of nil pointer") } switch v.Kind() { case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: return v.Len(), nil } return 0, fmt.Errorf("len of type %s", v.Type()) } // Boolean logic. func truth(a interface{}) bool { t, _ := isTrue(reflect.ValueOf(a)) return t } // and computes the Boolean AND of its arguments, returning // the first false argument it encounters, or the last argument. func and(arg0 interface{}, args ...interface{}) interface{} { if !truth(arg0) { return arg0 } for i := range args { arg0 = args[i] if !truth(arg0) { break } } return arg0 } // or computes the Boolean OR of its arguments, returning // the first true argument it encounters, or the last argument. func or(arg0 interface{}, args ...interface{}) interface{} { if truth(arg0) { return arg0 } for i := range args { arg0 = args[i] if truth(arg0) { break } } return arg0 } // not returns the Boolean negation of its argument. func not(arg interface{}) (truth bool) { truth, _ = isTrue(reflect.ValueOf(arg)) return !truth } // HTML escaping. var ( 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 html []byte switch c { case '"': html = htmlQuot case '\'': html = htmlApos case '&': html = htmlAmp case '<': html = htmlLt case '>': html = htmlGt default: continue } w.Write(b[last:i]) w.Write(html) 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) } // JavaScript escaping. var ( jsLowUni = []byte(`\u00`) hex = []byte("0123456789ABCDEF") jsBackslash = []byte(`\\`) jsApos = []byte(`\'`) jsQuot = []byte(`\"`) jsLt = []byte(`\x3C`) jsGt = []byte(`\x3E`) ) // 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 !jsIsSpecial(rune(c)) { // fast path: nothing to do continue } w.Write(b[last:i]) if c < utf8.RuneSelf { // Quotes, slashes and angle brackets get quoted. // Control characters get written as \u00XX. switch c { case '\\': w.Write(jsBackslash) case '\'': w.Write(jsApos) case '"': w.Write(jsQuot) case '<': w.Write(jsLt) case '>': w.Write(jsGt) 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. r, size := utf8.DecodeRune(b[i:]) if unicode.IsPrint(r) { w.Write(b[i : i+size]) } else { // TODO(dsymonds): Do this without fmt? fmt.Fprintf(w, "\\u%04X", r) } 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(r rune) bool { switch r { case '\\', '\'', '"', '<', '>': return true } return r < ' ' || utf8.RuneSelf <= r } // 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) } // URLQueryEscaper returns the escaped value of the textual representation of // its arguments in a form suitable for embedding in a URL query. func URLQueryEscaper(args ...interface{}) string { s, ok := "", false if len(args) == 1 { s, ok = args[0].(string) } if !ok { s = fmt.Sprint(args...) } return url.QueryEscape(s) }