diff --git a/go.mod b/go.mod index 39d360a19c..878a116cbe 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/owncloud/ocis/v2 go 1.21 require ( - github.com/CiscoM31/godata v1.0.8 + github.com/CiscoM31/godata v1.0.9 github.com/KimMachineGun/automemlimit v0.3.0 github.com/Masterminds/semver v1.5.0 github.com/MicahParks/keyfunc v1.9.0 diff --git a/go.sum b/go.sum index 9db2d10fea..559fa6b04b 100644 --- a/go.sum +++ b/go.sum @@ -793,8 +793,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CiscoM31/godata v1.0.8 h1:ZhPjm1dSwZWMUvb33P4bcVm048iiQ1wbncoCc9bLChQ= -github.com/CiscoM31/godata v1.0.8/go.mod h1:ZMiT6JuD3Rm83HEtiTx4JEChsd25YCrxchKGag/sdTc= +github.com/CiscoM31/godata v1.0.9 h1:7ovi2efjWb6RloX96AJqBB9eyIfBdarzj8kzg7glPC4= +github.com/CiscoM31/godata v1.0.9/go.mod h1:ZMiT6JuD3Rm83HEtiTx4JEChsd25YCrxchKGag/sdTc= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/KimMachineGun/automemlimit v0.3.0 h1:khgwM5ESVN85cE6Bq2ozMAAWDfrOEwQ51D/YlmThE04= diff --git a/vendor/github.com/CiscoM31/godata/expression_parser.go b/vendor/github.com/CiscoM31/godata/expression_parser.go index 52b4730dbd..c634e1451a 100644 --- a/vendor/github.com/CiscoM31/godata/expression_parser.go +++ b/vendor/github.com/CiscoM31/godata/expression_parser.go @@ -42,21 +42,23 @@ const ( ExpressionTokenFunc // Function, e.g. contains, substring... ExpressionTokenLambdaNav // "/" token when used in lambda expression, e.g. tags/any() ExpressionTokenLambda // [10] any(), all() lambda functions + ExpressionTokenCase // A case() statement. See https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_case + ExpressionTokenCasePair // A case statement expression pair [ : ] ExpressionTokenNull // ExpressionTokenIt // The '$it' token - ExpressionTokenRoot // The '$root' token + ExpressionTokenRoot // [15] The '$root' token ExpressionTokenFloat // A floating point value. - ExpressionTokenInteger // [15] An integer value + ExpressionTokenInteger // An integer value ExpressionTokenString // SQUOTE *( SQUOTE-in-string / pchar-no-SQUOTE ) SQUOTE ExpressionTokenDate // A date value - ExpressionTokenTime // A time value + ExpressionTokenTime // [20] A time value ExpressionTokenDateTime // A date-time value - ExpressionTokenBoolean // [20] - ExpressionTokenLiteral // + ExpressionTokenBoolean // A literal boolean value + ExpressionTokenLiteral // A literal non-boolean value ExpressionTokenDuration // duration = [ "duration" ] SQUOTE durationValue SQUOTE - ExpressionTokenGuid // A 128-bit GUID + ExpressionTokenGuid // [25] A 128-bit GUID ExpressionTokenAssignement // The '=' assignement for function arguments. - ExpressionTokenGeographyPolygon // [25] + ExpressionTokenGeographyPolygon // ExpressionTokenGeometryPolygon // expressionTokenLast ) @@ -74,6 +76,8 @@ func (e ExpressionTokenType) String() string { "ExpressionTokenFunc", "ExpressionTokenLambdaNav", "ExpressionTokenLambda", + "ExpressionTokenCase", + "ExpressionTokenCasePair", "ExpressionTokenNull", "ExpressionTokenIt", "ExpressionTokenRoot", @@ -121,23 +125,8 @@ func (p *ExpressionParser) ParseExpressionString(ctx context.Context, expression if tree == nil || tree.Token == nil { return nil, BadRequestError("Expression cannot be nil") } - if p.ExpectBoolExpr { - switch tree.Token.Type { - case ExpressionTokenBoolean: - // Valid boolean expression - case ExpressionTokenLogical: - // eq|ne|gt|ge|lt|le|and|or|not|has|in - // Valid boolean expression - case ExpressionTokenFunc: - // We need to know the return type of the function. - // TODO - case ExpressionTokenLambdaNav: - // Lambda Navigation. - // Valid boolean expression - default: - // Not a boolean expression - return nil, BadRequestError("Expression does not return a boolean value") - } + if p.ExpectBoolExpr && !p.isBooleanExpression(tree.Token) { + return nil, BadRequestError("Expression does not return a boolean value") } return &GoDataExpression{tree, expression}, nil } @@ -220,6 +209,7 @@ func NewExpressionTokenizer() *Tokenizer { // anyExpr = "any" OPEN BWS [ lambdaVariableExpr BWS COLON BWS lambdaPredicateExpr ] BWS CLOSE // allExpr = "all" OPEN BWS lambdaVariableExpr BWS COLON BWS lambdaPredicateExpr BWS CLOSE t.Add("(?i)^(?P(any|all))[\\s(]", ExpressionTokenLambda) + t.Add("(?i)^(?P(case))[\\s(]", ExpressionTokenCase) t.Add("^null", ExpressionTokenNull) t.Add("^\\$it", ExpressionTokenIt) t.Add("^\\$root", ExpressionTokenRoot) @@ -288,52 +278,58 @@ func NewExpressionParser() *ExpressionParser { parser.DefineOperator("and", 2, OpAssociationLeft, 2) parser.DefineOperator("or", 2, OpAssociationLeft, 1) parser.DefineOperator("=", 2, OpAssociationRight, 0) // Function argument assignment. E.g. MyFunc(Arg1='abc') - parser.DefineFunction("contains", []int{2}) - parser.DefineFunction("endswith", []int{2}) - parser.DefineFunction("startswith", []int{2}) - parser.DefineFunction("exists", []int{2}) - parser.DefineFunction("length", []int{1}) - parser.DefineFunction("indexof", []int{2}) - parser.DefineFunction("substring", []int{2, 3}) - parser.DefineFunction("substringof", []int{2}) - parser.DefineFunction("tolower", []int{1}) - parser.DefineFunction("toupper", []int{1}) - parser.DefineFunction("trim", []int{1}) - parser.DefineFunction("concat", []int{2}) - parser.DefineFunction("year", []int{1}) - parser.DefineFunction("month", []int{1}) - parser.DefineFunction("day", []int{1}) - parser.DefineFunction("hour", []int{1}) - parser.DefineFunction("minute", []int{1}) - parser.DefineFunction("second", []int{1}) - parser.DefineFunction("fractionalseconds", []int{1}) - parser.DefineFunction("date", []int{1}) - parser.DefineFunction("time", []int{1}) - parser.DefineFunction("totaloffsetminutes", []int{1}) - parser.DefineFunction("now", []int{0}) - parser.DefineFunction("maxdatetime", []int{0}) - parser.DefineFunction("mindatetime", []int{0}) - parser.DefineFunction("totalseconds", []int{1}) - parser.DefineFunction("round", []int{1}) - parser.DefineFunction("floor", []int{1}) - parser.DefineFunction("ceiling", []int{1}) - parser.DefineFunction("isof", []int{1, 2}) // isof function can take one or two arguments. - parser.DefineFunction("cast", []int{2}) - parser.DefineFunction("geo.distance", []int{2}) + parser.DefineFunction("contains", []int{2}, true) + parser.DefineFunction("endswith", []int{2}, true) + parser.DefineFunction("startswith", []int{2}, true) + parser.DefineFunction("exists", []int{2}, true) + parser.DefineFunction("length", []int{1}, false) + parser.DefineFunction("indexof", []int{2}, false) + parser.DefineFunction("substring", []int{2, 3}, false) + parser.DefineFunction("substringof", []int{2}, false) + parser.DefineFunction("tolower", []int{1}, false) + parser.DefineFunction("toupper", []int{1}, false) + parser.DefineFunction("trim", []int{1}, false) + parser.DefineFunction("concat", []int{2}, false) + parser.DefineFunction("year", []int{1}, false) + parser.DefineFunction("month", []int{1}, false) + parser.DefineFunction("day", []int{1}, false) + parser.DefineFunction("hour", []int{1}, false) + parser.DefineFunction("minute", []int{1}, false) + parser.DefineFunction("second", []int{1}, false) + parser.DefineFunction("fractionalseconds", []int{1}, false) + parser.DefineFunction("date", []int{1}, false) + parser.DefineFunction("time", []int{1}, false) + parser.DefineFunction("totaloffsetminutes", []int{1}, false) + parser.DefineFunction("now", []int{0}, false) + parser.DefineFunction("maxdatetime", []int{0}, false) + parser.DefineFunction("mindatetime", []int{0}, false) + parser.DefineFunction("totalseconds", []int{1}, false) + parser.DefineFunction("round", []int{1}, false) + parser.DefineFunction("floor", []int{1}, false) + parser.DefineFunction("ceiling", []int{1}, false) + parser.DefineFunction("isof", []int{1, 2}, true) // isof function can take one or two arguments. + parser.DefineFunction("cast", []int{2}, false) + parser.DefineFunction("geo.distance", []int{2}, false) // The geo.intersects function has the following signatures: // Edm.Boolean geo.intersects(Edm.GeographyPoint,Edm.GeographyPolygon) // Edm.Boolean geo.intersects(Edm.GeometryPoint,Edm.GeometryPolygon) // The geo.intersects function returns true if the specified point lies within the interior // or on the boundary of the specified polygon, otherwise it returns false. - parser.DefineFunction("geo.intersects", []int{2}) + parser.DefineFunction("geo.intersects", []int{2}, false) // The geo.length function has the following signatures: // Edm.Double geo.length(Edm.GeographyLineString) // Edm.Double geo.length(Edm.GeometryLineString) // The geo.length function returns the total length of its line string parameter // in the coordinate reference system signified by its SRID. - parser.DefineFunction("geo.length", []int{1}) - parser.DefineFunction("any", []int{0, 2}) // 'any' can take either zero or one argument. - parser.DefineFunction("all", []int{2}) + parser.DefineFunction("geo.length", []int{1}, false) + // 'any' can take either zero or two arguments with the later having the form any(d:d/Prop eq 1). + // Godata interprets the colon as an argument delimiter and considers the function to have two arguments. + parser.DefineFunction("any", []int{0, 2}, true) + // 'all' requires two arguments of a form similar to 'any'. + parser.DefineFunction("all", []int{2}, true) + // Define 'case' as a function accepting 1-10 arguments. Each argument is a pair of expressions separated by a colon. + // See https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_case + parser.DefineFunction("case", []int{1,2,3,4,5,6,7,8,9,10}, true) return parser } diff --git a/vendor/github.com/CiscoM31/godata/parser.go b/vendor/github.com/CiscoM31/godata/parser.go index 600c7271b1..5863355492 100644 --- a/vendor/github.com/CiscoM31/godata/parser.go +++ b/vendor/github.com/CiscoM31/godata/parser.go @@ -233,6 +233,7 @@ func (o *Operator) WithListExprPreference(v bool) *Operator { type Function struct { Token string // The function token Params []int // The number of parameters this function accepts + ReturnsBool bool // Indicates if the function has a boolean return value } type ParseNode struct { @@ -291,11 +292,12 @@ func (p *Parser) DefineOperator(token string, operands, assoc, precedence int) * return op } -// DefineFunction adds a function to the language -// params is the number of parameters this function accepts -func (p *Parser) DefineFunction(token string, params []int) *Function { +// DefineFunction adds a function to the language. +// - params is the number of parameters this function accepts +// - returnsBool indicates if the function has a boolean return value +func (p *Parser) DefineFunction(token string, params []int, returnsBool bool) *Function { sort.Sort(sort.Reverse(sort.IntSlice(params))) - f := &Function{token, params} + f := &Function{token, params, returnsBool} p.Functions[token] = f return f } @@ -304,6 +306,7 @@ func (p *Parser) DefineFunction(token string, params []int) *Function { type CustomFunctionInput struct { Name string // case-insensitive function name NumParams []int // number of allowed parameters + ReturnsBool bool // indicates if the function has a boolean return value } // DefineCustomFunctions introduces additional function names to be considered as legal function @@ -322,7 +325,7 @@ func DefineCustomFunctions(functions []CustomFunctionInput) error { return fmt.Errorf("custom function '%s' may not override odata operator", name) } - GlobalExpressionParser.DefineFunction(name, v.NumParams) + GlobalExpressionParser.DefineFunction(name, v.NumParams, v.ReturnsBool) funcNames = append(funcNames, name) } @@ -362,6 +365,29 @@ func (p *Parser) isOperator(token *Token) bool { return ok } +// isBooleanExpression returns True when the expression token 't' has a resulting boolean value +func (p *Parser) isBooleanExpression(t *Token) bool { + switch t.Type { + case ExpressionTokenBoolean: + // Valid boolean expression + case ExpressionTokenLogical: + // eq|ne|gt|ge|lt|le|and|or|not|has|in + // Valid boolean expression + case ExpressionTokenFunc: + // Depends on function return type + f := p.Functions[t.Value] + if !f.ReturnsBool { + return false + } + case ExpressionTokenLambdaNav: + // Lambda Navigation. + // Valid boolean expression + default: + return false + } + return true +} + // InfixToPostfix parses the input string of tokens using the given definitions of operators // and functions. // Everything else is assumed to be a literal. @@ -653,12 +679,31 @@ func (p *Parser) PostfixToTree(ctx context.Context, queue *tokenQueue) (*ParseNo return nil, fmt.Errorf("expected list expression token, got '%v'", n.Token.Type) } - // Get function parameters. - // Some functions, e.g. substring, can take a variable number of arguments. - for _, c := range n.Children { - c.Parent = node + if node.Token.Type == ExpressionTokenCase { + // Create argument pairs for case() statement by translating flat list into pairs + if len(n.Children)%2 != 0 { + return nil, fmt.Errorf("expected even number of comma-separated arguments to case statement") + } + for i:=0; i