| | |
| | |
| | |
| |
|
| | package template |
| |
|
| | import ( |
| | "errors" |
| | "fmt" |
| | "io" |
| | "net/url" |
| | "reflect" |
| | "strings" |
| | "sync" |
| | "unicode" |
| | "unicode/utf8" |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | type FuncMap map[string]any |
| |
|
| | |
| | |
| | |
| | |
| | func builtins() FuncMap { |
| | return FuncMap{ |
| | "and": and, |
| | "call": emptyCall, |
| | "html": HTMLEscaper, |
| | "index": index, |
| | "slice": slice, |
| | "js": JSEscaper, |
| | "len": length, |
| | "not": not, |
| | "or": or, |
| | "print": fmt.Sprint, |
| | "printf": fmt.Sprintf, |
| | "println": fmt.Sprintln, |
| | "urlquery": URLQueryEscaper, |
| |
|
| | |
| | "eq": eq, |
| | "ge": ge, |
| | "gt": gt, |
| | "le": le, |
| | "lt": lt, |
| | "ne": ne, |
| | } |
| | } |
| |
|
| | |
| | var builtinFuncs = sync.OnceValue(func() map[string]reflect.Value { |
| | funcMap := builtins() |
| | m := make(map[string]reflect.Value, len(funcMap)) |
| | addValueFuncs(m, funcMap) |
| | return m |
| | }) |
| |
|
| | |
| | func addValueFuncs(out map[string]reflect.Value, in FuncMap) { |
| | for name, fn := range in { |
| | if !goodName(name) { |
| | panic(fmt.Errorf("function name %q is not a valid identifier", name)) |
| | } |
| | v := reflect.ValueOf(fn) |
| | if v.Kind() != reflect.Func { |
| | panic("value for " + name + " not a function") |
| | } |
| | if err := goodFunc(name, v.Type()); err != nil { |
| | panic(err) |
| | } |
| | out[name] = v |
| | } |
| | } |
| |
|
| | |
| | |
| | func addFuncs(out, in FuncMap) { |
| | for name, fn := range in { |
| | out[name] = fn |
| | } |
| | } |
| |
|
| | |
| | func goodFunc(name string, typ reflect.Type) error { |
| | |
| | switch numOut := typ.NumOut(); { |
| | case numOut == 1: |
| | return nil |
| | case numOut == 2 && typ.Out(1) == errorType: |
| | return nil |
| | case numOut == 2: |
| | return fmt.Errorf("invalid function signature for %s: second return value should be error; is %s", name, typ.Out(1)) |
| | default: |
| | return fmt.Errorf("function %s has %d return values; should be 1 or 2", name, typ.NumOut()) |
| | } |
| | } |
| |
|
| | |
| | func goodName(name string) bool { |
| | if name == "" { |
| | return false |
| | } |
| | for i, r := range name { |
| | switch { |
| | case r == '_': |
| | case i == 0 && !unicode.IsLetter(r): |
| | return false |
| | case !unicode.IsLetter(r) && !unicode.IsDigit(r): |
| | return false |
| | } |
| | } |
| | return true |
| | } |
| |
|
| | |
| | func findFunction(name string, tmpl *Template) (v reflect.Value, isBuiltin, ok bool) { |
| | if tmpl != nil && tmpl.common != nil { |
| | tmpl.muFuncs.RLock() |
| | defer tmpl.muFuncs.RUnlock() |
| | if fn := tmpl.execFuncs[name]; fn.IsValid() { |
| | return fn, false, true |
| | } |
| | } |
| | if fn := builtinFuncs()[name]; fn.IsValid() { |
| | return fn, true, true |
| | } |
| | return reflect.Value{}, false, false |
| | } |
| |
|
| | |
| | |
| | func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) { |
| | if !value.IsValid() { |
| | if !canBeNil(argType) { |
| | return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType) |
| | } |
| | value = reflect.Zero(argType) |
| | } |
| | if value.Type().AssignableTo(argType) { |
| | return value, nil |
| | } |
| | if intLike(value.Kind()) && intLike(argType.Kind()) && value.Type().ConvertibleTo(argType) { |
| | value = value.Convert(argType) |
| | return value, nil |
| | } |
| | return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType) |
| | } |
| |
|
| | func intLike(typ reflect.Kind) bool { |
| | switch typ { |
| | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| | return true |
| | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
| | return true |
| | } |
| | return false |
| | } |
| |
|
| | |
| | func indexArg(index reflect.Value, cap int) (int, error) { |
| | 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()) |
| | case reflect.Invalid: |
| | return 0, fmt.Errorf("cannot index slice/array with nil") |
| | default: |
| | return 0, fmt.Errorf("cannot index slice/array with type %s", index.Type()) |
| | } |
| | if x < 0 || int(x) < 0 || int(x) > cap { |
| | return 0, fmt.Errorf("index out of range: %d", x) |
| | } |
| | return int(x), nil |
| | } |
| |
|
| | |
| |
|
| | |
| | |
| | |
| | func index(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) { |
| | item = indirectInterface(item) |
| | if !item.IsValid() { |
| | return reflect.Value{}, fmt.Errorf("index of untyped nil") |
| | } |
| | for _, index := range indexes { |
| | index = indirectInterface(index) |
| | var isNil bool |
| | if item, isNil = indirect(item); isNil { |
| | return reflect.Value{}, fmt.Errorf("index of nil pointer") |
| | } |
| | switch item.Kind() { |
| | case reflect.Array, reflect.Slice, reflect.String: |
| | x, err := indexArg(index, item.Len()) |
| | if err != nil { |
| | return reflect.Value{}, err |
| | } |
| | item = item.Index(x) |
| | case reflect.Map: |
| | index, err := prepareArg(index, item.Type().Key()) |
| | if err != nil { |
| | return reflect.Value{}, err |
| | } |
| | if x := item.MapIndex(index); x.IsValid() { |
| | item = x |
| | } else { |
| | item = reflect.Zero(item.Type().Elem()) |
| | } |
| | case reflect.Invalid: |
| | |
| | panic("unreachable") |
| | default: |
| | return reflect.Value{}, fmt.Errorf("can't index item of type %s", item.Type()) |
| | } |
| | } |
| | return item, nil |
| | } |
| |
|
| | |
| |
|
| | |
| | |
| | |
| | |
| | func slice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) { |
| | item = indirectInterface(item) |
| | if !item.IsValid() { |
| | return reflect.Value{}, fmt.Errorf("slice of untyped nil") |
| | } |
| | var isNil bool |
| | if item, isNil = indirect(item); isNil { |
| | return reflect.Value{}, fmt.Errorf("slice of nil pointer") |
| | } |
| | if len(indexes) > 3 { |
| | return reflect.Value{}, fmt.Errorf("too many slice indexes: %d", len(indexes)) |
| | } |
| | var cap int |
| | switch item.Kind() { |
| | case reflect.String: |
| | if len(indexes) == 3 { |
| | return reflect.Value{}, fmt.Errorf("cannot 3-index slice a string") |
| | } |
| | cap = item.Len() |
| | case reflect.Array, reflect.Slice: |
| | cap = item.Cap() |
| | default: |
| | return reflect.Value{}, fmt.Errorf("can't slice item of type %s", item.Type()) |
| | } |
| | |
| | idx := [3]int{0, item.Len()} |
| | for i, index := range indexes { |
| | x, err := indexArg(index, cap) |
| | if err != nil { |
| | return reflect.Value{}, err |
| | } |
| | idx[i] = x |
| | } |
| | |
| | if idx[0] > idx[1] { |
| | return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[0], idx[1]) |
| | } |
| | if len(indexes) < 3 { |
| | return item.Slice(idx[0], idx[1]), nil |
| | } |
| | |
| | if idx[1] > idx[2] { |
| | return reflect.Value{}, fmt.Errorf("invalid slice index: %d > %d", idx[1], idx[2]) |
| | } |
| | return item.Slice3(idx[0], idx[1], idx[2]), nil |
| | } |
| |
|
| | |
| |
|
| | |
| | func length(item reflect.Value) (int, error) { |
| | item, isNil := indirect(item) |
| | if isNil { |
| | return 0, fmt.Errorf("len of nil pointer") |
| | } |
| | switch item.Kind() { |
| | case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: |
| | return item.Len(), nil |
| | } |
| | return 0, fmt.Errorf("len of type %s", item.Type()) |
| | } |
| |
|
| | |
| |
|
| | func emptyCall(fn reflect.Value, args ...reflect.Value) reflect.Value { |
| | panic("unreachable") |
| | } |
| |
|
| | |
| | |
| | func call(name string, fn reflect.Value, args ...reflect.Value) (reflect.Value, error) { |
| | fn = indirectInterface(fn) |
| | if !fn.IsValid() { |
| | return reflect.Value{}, fmt.Errorf("call of nil") |
| | } |
| | typ := fn.Type() |
| | if typ.Kind() != reflect.Func { |
| | return reflect.Value{}, fmt.Errorf("non-function %s of type %s", name, typ) |
| | } |
| |
|
| | if err := goodFunc(name, typ); err != nil { |
| | return reflect.Value{}, err |
| | } |
| | numIn := typ.NumIn() |
| | var dddType reflect.Type |
| | if typ.IsVariadic() { |
| | if len(args) < numIn-1 { |
| | return reflect.Value{}, fmt.Errorf("wrong number of args for %s: got %d want at least %d", name, len(args), numIn-1) |
| | } |
| | dddType = typ.In(numIn - 1).Elem() |
| | } else { |
| | if len(args) != numIn { |
| | return reflect.Value{}, fmt.Errorf("wrong number of args for %s: got %d want %d", name, len(args), numIn) |
| | } |
| | } |
| | argv := make([]reflect.Value, len(args)) |
| | for i, arg := range args { |
| | arg = indirectInterface(arg) |
| | |
| | argType := dddType |
| | if !typ.IsVariadic() || i < numIn-1 { |
| | argType = typ.In(i) |
| | } |
| |
|
| | var err error |
| | if argv[i], err = prepareArg(arg, argType); err != nil { |
| | return reflect.Value{}, fmt.Errorf("arg %d: %w", i, err) |
| | } |
| | } |
| | return safeCall(fn, argv) |
| | } |
| |
|
| | |
| | |
| | func safeCall(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error) { |
| | defer func() { |
| | if r := recover(); r != nil { |
| | if e, ok := r.(error); ok { |
| | err = e |
| | } else { |
| | err = fmt.Errorf("%v", r) |
| | } |
| | } |
| | }() |
| | ret := fun.Call(args) |
| | if len(ret) == 2 && !ret[1].IsNil() { |
| | return ret[0], ret[1].Interface().(error) |
| | } |
| | return ret[0], nil |
| | } |
| |
|
| | |
| |
|
| | func truth(arg reflect.Value) bool { |
| | t, _ := isTrue(indirectInterface(arg)) |
| | return t |
| | } |
| |
|
| | |
| | |
| | func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value { |
| | panic("unreachable") |
| | } |
| |
|
| | |
| | |
| | func or(arg0 reflect.Value, args ...reflect.Value) reflect.Value { |
| | panic("unreachable") |
| | } |
| |
|
| | |
| | func not(arg reflect.Value) bool { |
| | return !truth(arg) |
| | } |
| |
|
| | |
| |
|
| | |
| |
|
| | var ( |
| | errBadComparisonType = errors.New("invalid type for comparison") |
| | errNoComparison = errors.New("missing argument for comparison") |
| | ) |
| |
|
| | type kind int |
| |
|
| | const ( |
| | invalidKind kind = iota |
| | boolKind |
| | complexKind |
| | intKind |
| | floatKind |
| | stringKind |
| | uintKind |
| | ) |
| |
|
| | func basicKind(v reflect.Value) (kind, error) { |
| | switch v.Kind() { |
| | case reflect.Bool: |
| | return boolKind, nil |
| | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| | return intKind, nil |
| | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
| | return uintKind, nil |
| | case reflect.Float32, reflect.Float64: |
| | return floatKind, nil |
| | case reflect.Complex64, reflect.Complex128: |
| | return complexKind, nil |
| | case reflect.String: |
| | return stringKind, nil |
| | } |
| | return invalidKind, errBadComparisonType |
| | } |
| |
|
| | |
| | func isNil(v reflect.Value) bool { |
| | if !v.IsValid() { |
| | return true |
| | } |
| | switch v.Kind() { |
| | case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: |
| | return v.IsNil() |
| | } |
| | return false |
| | } |
| |
|
| | |
| | |
| | func canCompare(v1, v2 reflect.Value) bool { |
| | k1 := v1.Kind() |
| | k2 := v2.Kind() |
| | if k1 == k2 { |
| | return true |
| | } |
| | |
| | return k1 == reflect.Invalid || k2 == reflect.Invalid |
| | } |
| |
|
| | |
| | func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) { |
| | arg1 = indirectInterface(arg1) |
| | if len(arg2) == 0 { |
| | return false, errNoComparison |
| | } |
| | k1, _ := basicKind(arg1) |
| | for _, arg := range arg2 { |
| | arg = indirectInterface(arg) |
| | k2, _ := basicKind(arg) |
| | truth := false |
| | if k1 != k2 { |
| | |
| | switch { |
| | case k1 == intKind && k2 == uintKind: |
| | truth = arg1.Int() >= 0 && uint64(arg1.Int()) == arg.Uint() |
| | case k1 == uintKind && k2 == intKind: |
| | truth = arg.Int() >= 0 && arg1.Uint() == uint64(arg.Int()) |
| | default: |
| | if arg1.IsValid() && arg.IsValid() { |
| | return false, fmt.Errorf("incompatible types for comparison: %v and %v", arg1.Type(), arg.Type()) |
| | } |
| | } |
| | } else { |
| | switch k1 { |
| | case boolKind: |
| | truth = arg1.Bool() == arg.Bool() |
| | case complexKind: |
| | truth = arg1.Complex() == arg.Complex() |
| | case floatKind: |
| | truth = arg1.Float() == arg.Float() |
| | case intKind: |
| | truth = arg1.Int() == arg.Int() |
| | case stringKind: |
| | truth = arg1.String() == arg.String() |
| | case uintKind: |
| | truth = arg1.Uint() == arg.Uint() |
| | default: |
| | if !canCompare(arg1, arg) { |
| | return false, fmt.Errorf("non-comparable types %s: %v, %s: %v", arg1, arg1.Type(), arg.Type(), arg) |
| | } |
| | if isNil(arg1) || isNil(arg) { |
| | truth = isNil(arg) == isNil(arg1) |
| | } else { |
| | if !arg.Type().Comparable() { |
| | return false, fmt.Errorf("non-comparable type %s: %v", arg, arg.Type()) |
| | } |
| | truth = arg1.Interface() == arg.Interface() |
| | } |
| | } |
| | } |
| | if truth { |
| | return true, nil |
| | } |
| | } |
| | return false, nil |
| | } |
| |
|
| | |
| | func ne(arg1, arg2 reflect.Value) (bool, error) { |
| | |
| | equal, err := eq(arg1, arg2) |
| | return !equal, err |
| | } |
| |
|
| | |
| | func lt(arg1, arg2 reflect.Value) (bool, error) { |
| | arg1 = indirectInterface(arg1) |
| | k1, err := basicKind(arg1) |
| | if err != nil { |
| | return false, err |
| | } |
| | arg2 = indirectInterface(arg2) |
| | k2, err := basicKind(arg2) |
| | if err != nil { |
| | return false, err |
| | } |
| | truth := false |
| | if k1 != k2 { |
| | |
| | switch { |
| | case k1 == intKind && k2 == uintKind: |
| | truth = arg1.Int() < 0 || uint64(arg1.Int()) < arg2.Uint() |
| | case k1 == uintKind && k2 == intKind: |
| | truth = arg2.Int() >= 0 && arg1.Uint() < uint64(arg2.Int()) |
| | default: |
| | return false, fmt.Errorf("incompatible types for comparison: %v and %v", arg1.Type(), arg2.Type()) |
| | } |
| | } else { |
| | switch k1 { |
| | case boolKind, complexKind: |
| | return false, errBadComparisonType |
| | case floatKind: |
| | truth = arg1.Float() < arg2.Float() |
| | case intKind: |
| | truth = arg1.Int() < arg2.Int() |
| | case stringKind: |
| | truth = arg1.String() < arg2.String() |
| | case uintKind: |
| | truth = arg1.Uint() < arg2.Uint() |
| | default: |
| | panic("invalid kind") |
| | } |
| | } |
| | return truth, nil |
| | } |
| |
|
| | |
| | func le(arg1, arg2 reflect.Value) (bool, error) { |
| | |
| | lessThan, err := lt(arg1, arg2) |
| | if lessThan || err != nil { |
| | return lessThan, err |
| | } |
| | return eq(arg1, arg2) |
| | } |
| |
|
| | |
| | func gt(arg1, arg2 reflect.Value) (bool, error) { |
| | |
| | lessOrEqual, err := le(arg1, arg2) |
| | if err != nil { |
| | return false, err |
| | } |
| | return !lessOrEqual, nil |
| | } |
| |
|
| | |
| | func ge(arg1, arg2 reflect.Value) (bool, error) { |
| | |
| | lessThan, err := lt(arg1, arg2) |
| | if err != nil { |
| | return false, err |
| | } |
| | return !lessThan, nil |
| | } |
| |
|
| | |
| |
|
| | var ( |
| | htmlQuot = []byte(""") |
| | htmlApos = []byte("'") |
| | htmlAmp = []byte("&") |
| | htmlLt = []byte("<") |
| | htmlGt = []byte(">") |
| | htmlNull = []byte("\uFFFD") |
| | ) |
| |
|
| | |
| | func HTMLEscape(w io.Writer, b []byte) { |
| | last := 0 |
| | for i, c := range b { |
| | var html []byte |
| | switch c { |
| | case '\000': |
| | html = htmlNull |
| | 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:]) |
| | } |
| |
|
| | |
| | func HTMLEscapeString(s string) string { |
| | |
| | if !strings.ContainsAny(s, "'\"&<>\000") { |
| | return s |
| | } |
| | var b strings.Builder |
| | HTMLEscape(&b, []byte(s)) |
| | return b.String() |
| | } |
| |
|
| | |
| | |
| | func HTMLEscaper(args ...any) string { |
| | return HTMLEscapeString(evalArgs(args)) |
| | } |
| |
|
| | |
| |
|
| | var ( |
| | jsLowUni = []byte(`\u00`) |
| | hex = []byte("0123456789ABCDEF") |
| |
|
| | jsBackslash = []byte(`\\`) |
| | jsApos = []byte(`\'`) |
| | jsQuot = []byte(`\"`) |
| | jsLt = []byte(`\u003C`) |
| | jsGt = []byte(`\u003E`) |
| | jsAmp = []byte(`\u0026`) |
| | jsEq = []byte(`\u003D`) |
| | ) |
| |
|
| | |
| | func JSEscape(w io.Writer, b []byte) { |
| | last := 0 |
| | for i := 0; i < len(b); i++ { |
| | c := b[i] |
| |
|
| | if !jsIsSpecial(rune(c)) { |
| | |
| | continue |
| | } |
| | w.Write(b[last:i]) |
| |
|
| | if c < utf8.RuneSelf { |
| | |
| | |
| | switch c { |
| | case '\\': |
| | w.Write(jsBackslash) |
| | case '\'': |
| | w.Write(jsApos) |
| | case '"': |
| | w.Write(jsQuot) |
| | case '<': |
| | w.Write(jsLt) |
| | case '>': |
| | w.Write(jsGt) |
| | case '&': |
| | w.Write(jsAmp) |
| | case '=': |
| | w.Write(jsEq) |
| | default: |
| | w.Write(jsLowUni) |
| | t, b := c>>4, c&0x0f |
| | w.Write(hex[t : t+1]) |
| | w.Write(hex[b : b+1]) |
| | } |
| | } else { |
| | |
| | r, size := utf8.DecodeRune(b[i:]) |
| | if unicode.IsPrint(r) { |
| | w.Write(b[i : i+size]) |
| | } else { |
| | fmt.Fprintf(w, "\\u%04X", r) |
| | } |
| | i += size - 1 |
| | } |
| | last = i + 1 |
| | } |
| | w.Write(b[last:]) |
| | } |
| |
|
| | |
| | func JSEscapeString(s string) string { |
| | |
| | if strings.IndexFunc(s, jsIsSpecial) < 0 { |
| | return s |
| | } |
| | var b strings.Builder |
| | JSEscape(&b, []byte(s)) |
| | return b.String() |
| | } |
| |
|
| | func jsIsSpecial(r rune) bool { |
| | switch r { |
| | case '\\', '\'', '"', '<', '>', '&', '=': |
| | return true |
| | } |
| | return r < ' ' || utf8.RuneSelf <= r |
| | } |
| |
|
| | |
| | |
| | func JSEscaper(args ...any) string { |
| | return JSEscapeString(evalArgs(args)) |
| | } |
| |
|
| | |
| | |
| | func URLQueryEscaper(args ...any) string { |
| | return url.QueryEscape(evalArgs(args)) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func evalArgs(args []any) string { |
| | ok := false |
| | var s string |
| | |
| | if len(args) == 1 { |
| | s, ok = args[0].(string) |
| | } |
| | if !ok { |
| | for i, arg := range args { |
| | a, ok := printableValue(reflect.ValueOf(arg)) |
| | if ok { |
| | args[i] = a |
| | } |
| | } |
| | s = fmt.Sprint(args...) |
| | } |
| | return s |
| | } |
| |
|