diff --git a/src/pkg/exp/template/exec.go b/src/pkg/exp/template/exec.go index 636bc4c3347..3ea54eafaaf 100644 --- a/src/pkg/exp/template/exec.go +++ b/src/pkg/exp/template/exec.go @@ -89,7 +89,23 @@ func (s *state) walk(data reflect.Value, n node) { // are identical in behavior except that 'with' sets dot. func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe []*commandNode, list, elseList *listNode) { val := s.evalPipeline(data, pipe) - truth := false + truth, ok := isTrue(val) + if !ok { + s.errorf("if/with can't use value of type %T", val.Interface()) + } + if truth { + if typ == nodeWith { + data = val + } + s.walk(data, list) + } else if elseList != nil { + s.walk(data, elseList) + } +} + +// isTrue returns whether the value is 'true', in the sense of not the zero of its type, +// and whether the value has a meaningful truth value. +func isTrue(val reflect.Value) (truth, ok bool) { switch val.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: truth = val.Len() > 0 @@ -106,16 +122,9 @@ func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe []*commandNo case reflect.Chan, reflect.Func, reflect.Ptr: truth = !val.IsNil() default: - s.errorf("if/with can't use value of type %T", val.Interface()) - } - if truth { - if typ == nodeWith { - data = val - } - s.walk(data, list) - } else if elseList != nil { - s.walk(data, elseList) + return } + return truth, true } func (s *state) walkRange(data reflect.Value, r *rangeNode) { diff --git a/src/pkg/exp/template/exec_test.go b/src/pkg/exp/template/exec_test.go index d9e2cda0694..534690a6a3e 100644 --- a/src/pkg/exp/template/exec_test.go +++ b/src/pkg/exp/template/exec_test.go @@ -140,7 +140,7 @@ var execTests = []execTest{ {"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true}, {"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true}, {"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true}, - // Function calls. + // Printf. {"printf", `{{printf "hello, printf"}}`, "hello, printf", tVal, true}, {"printf int", `{{printf "%04x" 127}}`, "007f", tVal, true}, {"printf float", `{{printf "%g" 3.5}}`, "3.5", tVal, true}, @@ -150,10 +150,17 @@ 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", `{{html ""}}`, - "<script>alert("XSS");</script>", tVal, true}, + "<script>alert("XSS");</script>", nil, true}, {"html pipeline", `{{printf "" | html}}`, - "<script>alert("XSS");</script>", tVal, true}, + "<script>alert("XSS");</script>", nil, 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}, + {"or", "{{or 0 0}} {{or 1 0}} {{or 0 1}} {{or 1 1}}", "false true true true", nil, true}, + {"boolean if", "{{if and true 1 `hi`}}TRUE{{else}}FALSE{{end}}", "TRUE", tVal, true}, + {"boolean if not", "{{if and true 1 `hi` | not}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, 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 93f8816eb55..44770c7044e 100644 --- a/src/pkg/exp/template/funcs.go +++ b/src/pkg/exp/template/funcs.go @@ -20,6 +20,9 @@ type FuncMap map[string]interface{} var funcs = map[string]reflect.Value{ "printf": reflect.ValueOf(fmt.Sprintf), "html": reflect.ValueOf(HTMLEscaper), + "and": reflect.ValueOf(and), + "or": reflect.ValueOf(or), + "not": reflect.ValueOf(not), } // addFuncs adds to values the functions in funcs, converting them to reflect.Values. @@ -66,7 +69,33 @@ func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) { return reflect.Value{}, false } -// HTML escaping +// Boolean logic. + +// and returns the Boolean AND of its arguments. +func and(arg0 interface{}, args ...interface{}) (truth bool) { + truth, _ = isTrue(reflect.ValueOf(arg0)) + for i := 0; truth && i < len(args); i++ { + truth, _ = isTrue(reflect.ValueOf(args[i])) + } + return +} + +// or returns the Boolean OR of its arguments. +func or(arg0 interface{}, args ...interface{}) (truth bool) { + truth, _ = isTrue(reflect.ValueOf(arg0)) + for i := 0; !truth && i < len(args); i++ { + truth, _ = isTrue(reflect.ValueOf(args[i])) + } + return +} + +// not returns the Boolean negation of its argument. +func not(arg interface{}) (truth bool) { + truth, _ = isTrue(reflect.ValueOf(arg)) + return !truth +} + +// HTML escaping. var ( escQuot = []byte(""") // shorter than """ diff --git a/src/pkg/exp/template/set.go b/src/pkg/exp/template/set.go index bda4600192d..7100e7e3ec8 100644 --- a/src/pkg/exp/template/set.go +++ b/src/pkg/exp/template/set.go @@ -5,6 +5,7 @@ package template import ( + "fmt" "os" "reflect" "runtime" @@ -35,6 +36,19 @@ func (s *Set) Funcs(funcMap FuncMap) *Set { return s } +// Add adds the argument templates to the set. It panics if the call +// attempts to reuse a name defined in the template. +// The return value is the set, so calls can be chained. +func (s *Set) Add(templates ...*Template) *Set { + for _, t := range templates { + if _, ok := s.tmpl[t.name]; ok { + panic(fmt.Errorf("template: %q already defined in set", t.name)) + } + s.tmpl[t.name] = t + } + return s +} + // recover is the handler that turns panics into returns from the top // level of Parse. func (s *Set) recover(errp *os.Error) {