diff --git a/ast/builtins.go b/ast/builtins.go index e73f3fceb5..fd6ee9160e 100644 --- a/ast/builtins.go +++ b/ast/builtins.go @@ -135,6 +135,7 @@ var DefaultBuiltins = [...]*Builtin{ URLQueryDecode, URLQueryEncode, URLQueryEncodeObject, + URLQueryDecodeObject, YAMLMarshal, YAMLUnmarshal, @@ -1283,6 +1284,17 @@ var URLQueryEncodeObject = &Builtin{ ), } +// URLQueryDecodeObject decodes the given URL query string into an object. +var URLQueryDecodeObject = &Builtin{ + Name: "urlquery.decode_object", + Decl: types.NewFunction( + types.Args(types.S), + types.NewObject(nil, types.NewDynamicProperty( + types.S, + types.NewArray(nil, types.S))), + ), +} + // YAMLMarshal serializes the input term. var YAMLMarshal = &Builtin{ Name: "yaml.marshal", diff --git a/capabilities.json b/capabilities.json index 8eeceda588..08e9df2d56 100644 --- a/capabilities.json +++ b/capabilities.json @@ -3043,6 +3043,31 @@ "type": "function" } }, + { + "name": "urlquery.decode_object", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "dynamic": { + "key": { + "type": "string" + }, + "value": { + "dynamic": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "function" + } + }, { "name": "urlquery.encode", "decl": { diff --git a/docs/content/policy-reference.md b/docs/content/policy-reference.md index 2118821c30..da9898ae81 100644 --- a/docs/content/policy-reference.md +++ b/docs/content/policy-reference.md @@ -447,6 +447,7 @@ The following table shows examples of how ``glob.match`` works: | ``output := urlquery.encode(string)`` | ``output`` is ``string`` serialized to a URL query parameter encoded string | | ``output := urlquery.encode_object(object)`` | ``output`` is ``object`` serialized to a URL query parameter encoded string | | ``output := urlquery.decode(string)`` | ``output`` is ``string`` deserialized from a URL query parameter encoded string | +| ``output := urlquery.decode_object(string)`` | ``output`` is ``object`` deserialized from a URL query parameter string | | ``output := json.marshal(x)`` | ``output`` is ``x`` serialized to a JSON string | | ``output := json.unmarshal(string)`` | ``output`` is ``string`` deserialized to a term from a JSON encoded string | | ``output := yaml.marshal(x)`` | ``output`` is ``x`` serialized to a YAML string | diff --git a/test/cases/testdata/urlbuiltins/test-urlbuiltins-1076.yaml b/test/cases/testdata/urlbuiltins/test-urlbuiltins-1076.yaml new file mode 100644 index 0000000000..eb3f727d6e --- /dev/null +++ b/test/cases/testdata/urlbuiltins/test-urlbuiltins-1076.yaml @@ -0,0 +1,34 @@ +cases: +- modules: + - | + package decode_object + + p = x { + x = urlquery.decode_object("a=value_a1&b=value_b&a=value_a2") + } + note: urlbuiltins/decode_object multiple + query: data.decode_object.p = x + want_result: + - x: {"a": ["value_a1", "value_a2"], "b": ["value_b"]} +- modules: + - | + package decode_object + + p = x { + x = urlquery.decode_object("a=value_a1&b") + } + note: urlbuiltins/decode_object empty parameter + query: data.decode_object.p = x + want_result: + - x: {"a": ["value_a1"], "b": [""]} +- modules: + - | + package decode_object + + p = x { + x = urlquery.decode_object("") + } + note: urlbuiltins/decode_object empty string + query: data.decode_object.p = x + want_result: + - x: {} \ No newline at end of file diff --git a/topdown/encoding.go b/topdown/encoding.go index f2074bb463..b69bdc92ca 100644 --- a/topdown/encoding.go +++ b/topdown/encoding.go @@ -158,6 +158,29 @@ func builtinURLQueryEncodeObject(a ast.Value) (ast.Value, error) { return ast.String(query.Encode()), nil } +func builtinURLQueryDecodeObject(bctx BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error { + query, err := builtins.StringOperand(operands[0].Value, 1) + if err != nil { + return err + } + + queryParams, err := url.ParseQuery(string(query)) + if err != nil { + return err + } + + queryObject := ast.NewObject() + for k, v := range queryParams { + paramsArray := make([]*ast.Term, len(v)) + for i, param := range v { + paramsArray[i] = ast.StringTerm(param) + } + queryObject.Insert(ast.StringTerm(k), ast.ArrayTerm(paramsArray...)) + } + + return iter(ast.NewTerm(queryObject)) +} + func builtinYAMLMarshal(a ast.Value) (ast.Value, error) { asJSON, err := ast.JSON(a) @@ -212,6 +235,7 @@ func init() { RegisterFunctionalBuiltin1(ast.URLQueryDecode.Name, builtinURLQueryDecode) RegisterFunctionalBuiltin1(ast.URLQueryEncode.Name, builtinURLQueryEncode) RegisterFunctionalBuiltin1(ast.URLQueryEncodeObject.Name, builtinURLQueryEncodeObject) + RegisterBuiltinFunc(ast.URLQueryDecodeObject.Name, builtinURLQueryDecodeObject) RegisterFunctionalBuiltin1(ast.YAMLMarshal.Name, builtinYAMLMarshal) RegisterFunctionalBuiltin1(ast.YAMLUnmarshal.Name, builtinYAMLUnmarshal) }