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)
}