diff --git a/ast/builtins.go b/ast/builtins.go index fd6ee9160e..35d38e4f5d 100644 --- a/ast/builtins.go +++ b/ast/builtins.go @@ -130,6 +130,7 @@ var DefaultBuiltins = [...]*Builtin{ JSONUnmarshal, Base64Encode, Base64Decode, + Base64IsValid, Base64UrlEncode, Base64UrlDecode, URLQueryDecode, @@ -1231,6 +1232,15 @@ var Base64Decode = &Builtin{ ), } +// Base64IsValid verifies the input string is base64 encoded. +var Base64IsValid = &Builtin{ + Name: "base64.is_valid", + Decl: types.NewFunction( + types.Args(types.S), + types.B, + ), +} + // Base64UrlEncode serializes the input string into base64url encoding. var Base64UrlEncode = &Builtin{ Name: "base64url.encode", diff --git a/capabilities.json b/capabilities.json index 08e9df2d56..5767c4eb57 100644 --- a/capabilities.json +++ b/capabilities.json @@ -195,6 +195,20 @@ "type": "function" } }, + { + "name": "base64.is_valid", + "decl": { + "args": [ + { + "type": "string" + } + ], + "result": { + "type": "boolean" + }, + "type": "function" + } + }, { "name": "base64url.decode", "decl": { diff --git a/test/cases/testdata/base64builtins/test-base64builtins-0935.yaml b/test/cases/testdata/base64builtins/test-base64builtins-0935.yaml new file mode 100644 index 0000000000..18d840ffb2 --- /dev/null +++ b/test/cases/testdata/base64builtins/test-base64builtins-0935.yaml @@ -0,0 +1,25 @@ +cases: +- data: {} + modules: + - | + package generated + + p = x { + base64.is_valid("aGVsbG8=", x) + } + note: base64builtins/is_valid-true + query: data.generated.p = x + want_result: + - x: true +- data: {} + modules: + - | + package generated + + p = x { + base64.is_valid("{'not':'base64'}", x) + } + note: base64builtins/is_valid-false + query: data.generated.p = x + want_result: + - x: false diff --git a/topdown/encoding.go b/topdown/encoding.go index b69bdc92ca..09556d8133 100644 --- a/topdown/encoding.go +++ b/topdown/encoding.go @@ -69,6 +69,16 @@ func builtinBase64Decode(a ast.Value) (ast.Value, error) { return ast.String(result), err } +func builtinBase64IsValid(a ast.Value) (ast.Value, error) { + str, err := builtins.StringOperand(a, 1) + if err != nil { + return nil, err + } + + _, err = base64.StdEncoding.DecodeString(string(str)) + return ast.Boolean(err == nil), nil +} + func builtinBase64UrlEncode(a ast.Value) (ast.Value, error) { str, err := builtins.StringOperand(a, 1) if err != nil { @@ -230,6 +240,7 @@ func init() { RegisterFunctionalBuiltin1(ast.JSONUnmarshal.Name, builtinJSONUnmarshal) RegisterFunctionalBuiltin1(ast.Base64Encode.Name, builtinBase64Encode) RegisterFunctionalBuiltin1(ast.Base64Decode.Name, builtinBase64Decode) + RegisterFunctionalBuiltin1(ast.Base64IsValid.Name, builtinBase64IsValid) RegisterFunctionalBuiltin1(ast.Base64UrlEncode.Name, builtinBase64UrlEncode) RegisterFunctionalBuiltin1(ast.Base64UrlDecode.Name, builtinBase64UrlDecode) RegisterFunctionalBuiltin1(ast.URLQueryDecode.Name, builtinURLQueryDecode)