mirror of https://github.com/k3s-io/k3s
696 lines
17 KiB
Go
696 lines
17 KiB
Go
package pongo2
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/juju/errors"
|
|
)
|
|
|
|
const (
|
|
varTypeInt = iota
|
|
varTypeIdent
|
|
)
|
|
|
|
var (
|
|
typeOfValuePtr = reflect.TypeOf(new(Value))
|
|
typeOfExecCtxPtr = reflect.TypeOf(new(ExecutionContext))
|
|
)
|
|
|
|
type variablePart struct {
|
|
typ int
|
|
s string
|
|
i int
|
|
|
|
isFunctionCall bool
|
|
callingArgs []functionCallArgument // needed for a function call, represents all argument nodes (INode supports nested function calls)
|
|
}
|
|
|
|
type functionCallArgument interface {
|
|
Evaluate(*ExecutionContext) (*Value, *Error)
|
|
}
|
|
|
|
// TODO: Add location tokens
|
|
type stringResolver struct {
|
|
locationToken *Token
|
|
val string
|
|
}
|
|
|
|
type intResolver struct {
|
|
locationToken *Token
|
|
val int
|
|
}
|
|
|
|
type floatResolver struct {
|
|
locationToken *Token
|
|
val float64
|
|
}
|
|
|
|
type boolResolver struct {
|
|
locationToken *Token
|
|
val bool
|
|
}
|
|
|
|
type variableResolver struct {
|
|
locationToken *Token
|
|
|
|
parts []*variablePart
|
|
}
|
|
|
|
type nodeFilteredVariable struct {
|
|
locationToken *Token
|
|
|
|
resolver IEvaluator
|
|
filterChain []*filterCall
|
|
}
|
|
|
|
type nodeVariable struct {
|
|
locationToken *Token
|
|
expr IEvaluator
|
|
}
|
|
|
|
type executionCtxEval struct{}
|
|
|
|
func (v *nodeFilteredVariable) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
|
value, err := v.Evaluate(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writer.WriteString(value.String())
|
|
return nil
|
|
}
|
|
|
|
func (vr *variableResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
|
value, err := vr.Evaluate(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writer.WriteString(value.String())
|
|
return nil
|
|
}
|
|
|
|
func (s *stringResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
|
value, err := s.Evaluate(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writer.WriteString(value.String())
|
|
return nil
|
|
}
|
|
|
|
func (i *intResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
|
value, err := i.Evaluate(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writer.WriteString(value.String())
|
|
return nil
|
|
}
|
|
|
|
func (f *floatResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
|
value, err := f.Evaluate(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writer.WriteString(value.String())
|
|
return nil
|
|
}
|
|
|
|
func (b *boolResolver) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
|
value, err := b.Evaluate(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
writer.WriteString(value.String())
|
|
return nil
|
|
}
|
|
|
|
func (v *nodeFilteredVariable) GetPositionToken() *Token {
|
|
return v.locationToken
|
|
}
|
|
|
|
func (vr *variableResolver) GetPositionToken() *Token {
|
|
return vr.locationToken
|
|
}
|
|
|
|
func (s *stringResolver) GetPositionToken() *Token {
|
|
return s.locationToken
|
|
}
|
|
|
|
func (i *intResolver) GetPositionToken() *Token {
|
|
return i.locationToken
|
|
}
|
|
|
|
func (f *floatResolver) GetPositionToken() *Token {
|
|
return f.locationToken
|
|
}
|
|
|
|
func (b *boolResolver) GetPositionToken() *Token {
|
|
return b.locationToken
|
|
}
|
|
|
|
func (s *stringResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
|
return AsValue(s.val), nil
|
|
}
|
|
|
|
func (i *intResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
|
return AsValue(i.val), nil
|
|
}
|
|
|
|
func (f *floatResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
|
return AsValue(f.val), nil
|
|
}
|
|
|
|
func (b *boolResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
|
return AsValue(b.val), nil
|
|
}
|
|
|
|
func (s *stringResolver) FilterApplied(name string) bool {
|
|
return false
|
|
}
|
|
|
|
func (i *intResolver) FilterApplied(name string) bool {
|
|
return false
|
|
}
|
|
|
|
func (f *floatResolver) FilterApplied(name string) bool {
|
|
return false
|
|
}
|
|
|
|
func (b *boolResolver) FilterApplied(name string) bool {
|
|
return false
|
|
}
|
|
|
|
func (nv *nodeVariable) FilterApplied(name string) bool {
|
|
return nv.expr.FilterApplied(name)
|
|
}
|
|
|
|
func (nv *nodeVariable) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
|
value, err := nv.expr.Evaluate(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !nv.expr.FilterApplied("safe") && !value.safe && value.IsString() && ctx.Autoescape {
|
|
// apply escape filter
|
|
value, err = filters["escape"](value, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
writer.WriteString(value.String())
|
|
return nil
|
|
}
|
|
|
|
func (executionCtxEval) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
|
return AsValue(ctx), nil
|
|
}
|
|
|
|
func (vr *variableResolver) FilterApplied(name string) bool {
|
|
return false
|
|
}
|
|
|
|
func (vr *variableResolver) String() string {
|
|
parts := make([]string, 0, len(vr.parts))
|
|
for _, p := range vr.parts {
|
|
switch p.typ {
|
|
case varTypeInt:
|
|
parts = append(parts, strconv.Itoa(p.i))
|
|
case varTypeIdent:
|
|
parts = append(parts, p.s)
|
|
default:
|
|
panic("unimplemented")
|
|
}
|
|
}
|
|
return strings.Join(parts, ".")
|
|
}
|
|
|
|
func (vr *variableResolver) resolve(ctx *ExecutionContext) (*Value, error) {
|
|
var current reflect.Value
|
|
var isSafe bool
|
|
|
|
for idx, part := range vr.parts {
|
|
if idx == 0 {
|
|
// We're looking up the first part of the variable.
|
|
// First we're having a look in our private
|
|
// context (e. g. information provided by tags, like the forloop)
|
|
val, inPrivate := ctx.Private[vr.parts[0].s]
|
|
if !inPrivate {
|
|
// Nothing found? Then have a final lookup in the public context
|
|
val = ctx.Public[vr.parts[0].s]
|
|
}
|
|
current = reflect.ValueOf(val) // Get the initial value
|
|
} else {
|
|
// Next parts, resolve it from current
|
|
|
|
// Before resolving the pointer, let's see if we have a method to call
|
|
// Problem with resolving the pointer is we're changing the receiver
|
|
isFunc := false
|
|
if part.typ == varTypeIdent {
|
|
funcValue := current.MethodByName(part.s)
|
|
if funcValue.IsValid() {
|
|
current = funcValue
|
|
isFunc = true
|
|
}
|
|
}
|
|
|
|
if !isFunc {
|
|
// If current a pointer, resolve it
|
|
if current.Kind() == reflect.Ptr {
|
|
current = current.Elem()
|
|
if !current.IsValid() {
|
|
// Value is not valid (anymore)
|
|
return AsValue(nil), nil
|
|
}
|
|
}
|
|
|
|
// Look up which part must be called now
|
|
switch part.typ {
|
|
case varTypeInt:
|
|
// Calling an index is only possible for:
|
|
// * slices/arrays/strings
|
|
switch current.Kind() {
|
|
case reflect.String, reflect.Array, reflect.Slice:
|
|
if part.i >= 0 && current.Len() > part.i {
|
|
current = current.Index(part.i)
|
|
} else {
|
|
// In Django, exceeding the length of a list is just empty.
|
|
return AsValue(nil), nil
|
|
}
|
|
default:
|
|
return nil, errors.Errorf("Can't access an index on type %s (variable %s)",
|
|
current.Kind().String(), vr.String())
|
|
}
|
|
case varTypeIdent:
|
|
// debugging:
|
|
// fmt.Printf("now = %s (kind: %s)\n", part.s, current.Kind().String())
|
|
|
|
// Calling a field or key
|
|
switch current.Kind() {
|
|
case reflect.Struct:
|
|
current = current.FieldByName(part.s)
|
|
case reflect.Map:
|
|
current = current.MapIndex(reflect.ValueOf(part.s))
|
|
default:
|
|
return nil, errors.Errorf("Can't access a field by name on type %s (variable %s)",
|
|
current.Kind().String(), vr.String())
|
|
}
|
|
default:
|
|
panic("unimplemented")
|
|
}
|
|
}
|
|
}
|
|
|
|
if !current.IsValid() {
|
|
// Value is not valid (anymore)
|
|
return AsValue(nil), nil
|
|
}
|
|
|
|
// If current is a reflect.ValueOf(pongo2.Value), then unpack it
|
|
// Happens in function calls (as a return value) or by injecting
|
|
// into the execution context (e.g. in a for-loop)
|
|
if current.Type() == typeOfValuePtr {
|
|
tmpValue := current.Interface().(*Value)
|
|
current = tmpValue.val
|
|
isSafe = tmpValue.safe
|
|
}
|
|
|
|
// Check whether this is an interface and resolve it where required
|
|
if current.Kind() == reflect.Interface {
|
|
current = reflect.ValueOf(current.Interface())
|
|
}
|
|
|
|
// Check if the part is a function call
|
|
if part.isFunctionCall || current.Kind() == reflect.Func {
|
|
// Check for callable
|
|
if current.Kind() != reflect.Func {
|
|
return nil, errors.Errorf("'%s' is not a function (it is %s)", vr.String(), current.Kind().String())
|
|
}
|
|
|
|
// Check for correct function syntax and types
|
|
// func(*Value, ...) *Value
|
|
t := current.Type()
|
|
currArgs := part.callingArgs
|
|
|
|
// If an implicit ExecCtx is needed
|
|
if t.NumIn() > 0 && t.In(0) == typeOfExecCtxPtr {
|
|
currArgs = append([]functionCallArgument{executionCtxEval{}}, currArgs...)
|
|
}
|
|
|
|
// Input arguments
|
|
if len(currArgs) != t.NumIn() && !(len(currArgs) >= t.NumIn()-1 && t.IsVariadic()) {
|
|
return nil,
|
|
errors.Errorf("Function input argument count (%d) of '%s' must be equal to the calling argument count (%d).",
|
|
t.NumIn(), vr.String(), len(currArgs))
|
|
}
|
|
|
|
// Output arguments
|
|
if t.NumOut() != 1 && t.NumOut() != 2 {
|
|
return nil, errors.Errorf("'%s' must have exactly 1 or 2 output arguments, the second argument must be of type error", vr.String())
|
|
}
|
|
|
|
// Evaluate all parameters
|
|
var parameters []reflect.Value
|
|
|
|
numArgs := t.NumIn()
|
|
isVariadic := t.IsVariadic()
|
|
var fnArg reflect.Type
|
|
|
|
for idx, arg := range currArgs {
|
|
pv, err := arg.Evaluate(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if isVariadic {
|
|
if idx >= t.NumIn()-1 {
|
|
fnArg = t.In(numArgs - 1).Elem()
|
|
} else {
|
|
fnArg = t.In(idx)
|
|
}
|
|
} else {
|
|
fnArg = t.In(idx)
|
|
}
|
|
|
|
if fnArg != typeOfValuePtr {
|
|
// Function's argument is not a *pongo2.Value, then we have to check whether input argument is of the same type as the function's argument
|
|
if !isVariadic {
|
|
if fnArg != reflect.TypeOf(pv.Interface()) && fnArg.Kind() != reflect.Interface {
|
|
return nil, errors.Errorf("Function input argument %d of '%s' must be of type %s or *pongo2.Value (not %T).",
|
|
idx, vr.String(), fnArg.String(), pv.Interface())
|
|
}
|
|
// Function's argument has another type, using the interface-value
|
|
parameters = append(parameters, reflect.ValueOf(pv.Interface()))
|
|
} else {
|
|
if fnArg != reflect.TypeOf(pv.Interface()) && fnArg.Kind() != reflect.Interface {
|
|
return nil, errors.Errorf("Function variadic input argument of '%s' must be of type %s or *pongo2.Value (not %T).",
|
|
vr.String(), fnArg.String(), pv.Interface())
|
|
}
|
|
// Function's argument has another type, using the interface-value
|
|
parameters = append(parameters, reflect.ValueOf(pv.Interface()))
|
|
}
|
|
} else {
|
|
// Function's argument is a *pongo2.Value
|
|
parameters = append(parameters, reflect.ValueOf(pv))
|
|
}
|
|
}
|
|
|
|
// Check if any of the values are invalid
|
|
for _, p := range parameters {
|
|
if p.Kind() == reflect.Invalid {
|
|
return nil, errors.Errorf("Calling a function using an invalid parameter")
|
|
}
|
|
}
|
|
|
|
// Call it and get first return parameter back
|
|
values := current.Call(parameters)
|
|
rv := values[0]
|
|
if t.NumOut() == 2 {
|
|
e := values[1].Interface()
|
|
if e != nil {
|
|
err, ok := e.(error)
|
|
if !ok {
|
|
return nil, errors.Errorf("The second return value is not an error")
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
if rv.Type() != typeOfValuePtr {
|
|
current = reflect.ValueOf(rv.Interface())
|
|
} else {
|
|
// Return the function call value
|
|
current = rv.Interface().(*Value).val
|
|
isSafe = rv.Interface().(*Value).safe
|
|
}
|
|
}
|
|
|
|
if !current.IsValid() {
|
|
// Value is not valid (e. g. NIL value)
|
|
return AsValue(nil), nil
|
|
}
|
|
}
|
|
|
|
return &Value{val: current, safe: isSafe}, nil
|
|
}
|
|
|
|
func (vr *variableResolver) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
|
value, err := vr.resolve(ctx)
|
|
if err != nil {
|
|
return AsValue(nil), ctx.Error(err.Error(), vr.locationToken)
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func (v *nodeFilteredVariable) FilterApplied(name string) bool {
|
|
for _, filter := range v.filterChain {
|
|
if filter.name == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (v *nodeFilteredVariable) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
|
value, err := v.resolver.Evaluate(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, filter := range v.filterChain {
|
|
value, err = filter.Execute(value, ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// IDENT | IDENT.(IDENT|NUMBER)...
|
|
func (p *Parser) parseVariableOrLiteral() (IEvaluator, *Error) {
|
|
t := p.Current()
|
|
|
|
if t == nil {
|
|
return nil, p.Error("Unexpected EOF, expected a number, string, keyword or identifier.", p.lastToken)
|
|
}
|
|
|
|
// Is first part a number or a string, there's nothing to resolve (because there's only to return the value then)
|
|
switch t.Typ {
|
|
case TokenNumber:
|
|
p.Consume()
|
|
|
|
// One exception to the rule that we don't have float64 literals is at the beginning
|
|
// of an expression (or a variable name). Since we know we started with an integer
|
|
// which can't obviously be a variable name, we can check whether the first number
|
|
// is followed by dot (and then a number again). If so we're converting it to a float64.
|
|
|
|
if p.Match(TokenSymbol, ".") != nil {
|
|
// float64
|
|
t2 := p.MatchType(TokenNumber)
|
|
if t2 == nil {
|
|
return nil, p.Error("Expected a number after the '.'.", nil)
|
|
}
|
|
f, err := strconv.ParseFloat(fmt.Sprintf("%s.%s", t.Val, t2.Val), 64)
|
|
if err != nil {
|
|
return nil, p.Error(err.Error(), t)
|
|
}
|
|
fr := &floatResolver{
|
|
locationToken: t,
|
|
val: f,
|
|
}
|
|
return fr, nil
|
|
}
|
|
i, err := strconv.Atoi(t.Val)
|
|
if err != nil {
|
|
return nil, p.Error(err.Error(), t)
|
|
}
|
|
nr := &intResolver{
|
|
locationToken: t,
|
|
val: i,
|
|
}
|
|
return nr, nil
|
|
|
|
case TokenString:
|
|
p.Consume()
|
|
sr := &stringResolver{
|
|
locationToken: t,
|
|
val: t.Val,
|
|
}
|
|
return sr, nil
|
|
case TokenKeyword:
|
|
p.Consume()
|
|
switch t.Val {
|
|
case "true":
|
|
br := &boolResolver{
|
|
locationToken: t,
|
|
val: true,
|
|
}
|
|
return br, nil
|
|
case "false":
|
|
br := &boolResolver{
|
|
locationToken: t,
|
|
val: false,
|
|
}
|
|
return br, nil
|
|
default:
|
|
return nil, p.Error("This keyword is not allowed here.", nil)
|
|
}
|
|
}
|
|
|
|
resolver := &variableResolver{
|
|
locationToken: t,
|
|
}
|
|
|
|
// First part of a variable MUST be an identifier
|
|
if t.Typ != TokenIdentifier {
|
|
return nil, p.Error("Expected either a number, string, keyword or identifier.", t)
|
|
}
|
|
|
|
resolver.parts = append(resolver.parts, &variablePart{
|
|
typ: varTypeIdent,
|
|
s: t.Val,
|
|
})
|
|
|
|
p.Consume() // we consumed the first identifier of the variable name
|
|
|
|
variableLoop:
|
|
for p.Remaining() > 0 {
|
|
t = p.Current()
|
|
|
|
if p.Match(TokenSymbol, ".") != nil {
|
|
// Next variable part (can be either NUMBER or IDENT)
|
|
t2 := p.Current()
|
|
if t2 != nil {
|
|
switch t2.Typ {
|
|
case TokenIdentifier:
|
|
resolver.parts = append(resolver.parts, &variablePart{
|
|
typ: varTypeIdent,
|
|
s: t2.Val,
|
|
})
|
|
p.Consume() // consume: IDENT
|
|
continue variableLoop
|
|
case TokenNumber:
|
|
i, err := strconv.Atoi(t2.Val)
|
|
if err != nil {
|
|
return nil, p.Error(err.Error(), t2)
|
|
}
|
|
resolver.parts = append(resolver.parts, &variablePart{
|
|
typ: varTypeInt,
|
|
i: i,
|
|
})
|
|
p.Consume() // consume: NUMBER
|
|
continue variableLoop
|
|
default:
|
|
return nil, p.Error("This token is not allowed within a variable name.", t2)
|
|
}
|
|
} else {
|
|
// EOF
|
|
return nil, p.Error("Unexpected EOF, expected either IDENTIFIER or NUMBER after DOT.",
|
|
p.lastToken)
|
|
}
|
|
} else if p.Match(TokenSymbol, "(") != nil {
|
|
// Function call
|
|
// FunctionName '(' Comma-separated list of expressions ')'
|
|
part := resolver.parts[len(resolver.parts)-1]
|
|
part.isFunctionCall = true
|
|
argumentLoop:
|
|
for {
|
|
if p.Remaining() == 0 {
|
|
return nil, p.Error("Unexpected EOF, expected function call argument list.", p.lastToken)
|
|
}
|
|
|
|
if p.Peek(TokenSymbol, ")") == nil {
|
|
// No closing bracket, so we're parsing an expression
|
|
exprArg, err := p.ParseExpression()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
part.callingArgs = append(part.callingArgs, exprArg)
|
|
|
|
if p.Match(TokenSymbol, ")") != nil {
|
|
// If there's a closing bracket after an expression, we will stop parsing the arguments
|
|
break argumentLoop
|
|
} else {
|
|
// If there's NO closing bracket, there MUST be an comma
|
|
if p.Match(TokenSymbol, ",") == nil {
|
|
return nil, p.Error("Missing comma or closing bracket after argument.", nil)
|
|
}
|
|
}
|
|
} else {
|
|
// We got a closing bracket, so stop parsing arguments
|
|
p.Consume()
|
|
break argumentLoop
|
|
}
|
|
|
|
}
|
|
// We're done parsing the function call, next variable part
|
|
continue variableLoop
|
|
}
|
|
|
|
// No dot or function call? Then we're done with the variable parsing
|
|
break
|
|
}
|
|
|
|
return resolver, nil
|
|
}
|
|
|
|
func (p *Parser) parseVariableOrLiteralWithFilter() (*nodeFilteredVariable, *Error) {
|
|
v := &nodeFilteredVariable{
|
|
locationToken: p.Current(),
|
|
}
|
|
|
|
// Parse the variable name
|
|
resolver, err := p.parseVariableOrLiteral()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v.resolver = resolver
|
|
|
|
// Parse all the filters
|
|
filterLoop:
|
|
for p.Match(TokenSymbol, "|") != nil {
|
|
// Parse one single filter
|
|
filter, err := p.parseFilter()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check sandbox filter restriction
|
|
if _, isBanned := p.template.set.bannedFilters[filter.name]; isBanned {
|
|
return nil, p.Error(fmt.Sprintf("Usage of filter '%s' is not allowed (sandbox restriction active).", filter.name), nil)
|
|
}
|
|
|
|
v.filterChain = append(v.filterChain, filter)
|
|
|
|
continue filterLoop
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
func (p *Parser) parseVariableElement() (INode, *Error) {
|
|
node := &nodeVariable{
|
|
locationToken: p.Current(),
|
|
}
|
|
|
|
p.Consume() // consume '{{'
|
|
|
|
expr, err := p.ParseExpression()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
node.expr = expr
|
|
|
|
if p.Match(TokenSymbol, "}}") == nil {
|
|
return nil, p.Error("'}}' expected", nil)
|
|
}
|
|
|
|
return node, nil
|
|
}
|