vrl/stdlib/
decode_base64.rs1use crate::compiler::prelude::*;
2use crate::stdlib::util::Base64Charset;
3
4fn decode_base64(charset: Option<Value>, value: Value) -> Resolved {
5 let value = value.try_bytes()?;
6 let charset = charset
7 .map(Value::try_bytes)
8 .transpose()?
9 .as_deref()
10 .map(Base64Charset::from_slice)
11 .transpose()?
12 .unwrap_or_default();
13
14 let decoder = match charset {
15 Base64Charset::Standard => base64_simd::STANDARD_NO_PAD,
16 Base64Charset::UrlSafe => base64_simd::URL_SAFE_NO_PAD,
17 };
18
19 let pos = value
21 .iter()
22 .rev()
23 .position(|c| *c != b'=')
24 .map_or(value.len(), |p| value.len() - p);
25
26 let decoded_vec = decoder
27 .decode_to_vec(&value[0..pos])
28 .map_err(|_| "unable to decode value from base64")?;
29
30 Ok(Value::Bytes(Bytes::from(decoded_vec)))
31}
32
33#[derive(Clone, Copy, Debug)]
34pub struct DecodeBase64;
35
36impl Function for DecodeBase64 {
37 fn identifier(&self) -> &'static str {
38 "decode_base64"
39 }
40
41 fn usage(&self) -> &'static str {
42 "Decodes the `value` (a [Base64](https://en.wikipedia.org/wiki/Base64) string) into its original string."
43 }
44
45 fn parameters(&self) -> &'static [Parameter] {
46 &[
47 Parameter {
48 keyword: "value",
49 kind: kind::BYTES,
50 required: true,
51 },
52 Parameter {
53 keyword: "charset",
54 kind: kind::BYTES,
55 required: false,
56 },
57 ]
58 }
59
60 fn compile(
61 &self,
62 _state: &state::TypeState,
63 _ctx: &mut FunctionCompileContext,
64 arguments: ArgumentList,
65 ) -> Compiled {
66 let value = arguments.required("value");
67 let charset = arguments.optional("charset");
68
69 Ok(DecodeBase64Fn { value, charset }.as_expr())
70 }
71
72 fn examples(&self) -> &'static [Example] {
73 &[
74 example! {
75 title: "Decode Base64 data (default)",
76 source: r#"decode_base64!("eW91IGhhdmUgc3VjY2Vzc2Z1bGx5IGRlY29kZWQgbWU=")"#,
77 result: Ok("you have successfully decoded me"),
78 },
79 example! {
80 title: "Decode Base64 data (URL safe)",
81 source: r#"decode_base64!("eW91IGNhbid0IG1ha2UgeW91ciBoZWFydCBmZWVsIHNvbWV0aGluZyBpdCB3b24ndA==", charset: "url_safe")"#,
82 result: Ok("you can't make your heart feel something it won't"),
83 },
84 ]
85 }
86}
87
88#[derive(Clone, Debug)]
89struct DecodeBase64Fn {
90 value: Box<dyn Expression>,
91 charset: Option<Box<dyn Expression>>,
92}
93
94impl FunctionExpression for DecodeBase64Fn {
95 fn resolve(&self, ctx: &mut Context) -> Resolved {
96 let value = self.value.resolve(ctx)?;
97 let charset = self.charset.as_ref().map(|c| c.resolve(ctx)).transpose()?;
98
99 decode_base64(charset, value)
100 }
101
102 fn type_def(&self, _: &state::TypeState) -> TypeDef {
103 TypeDef::bytes().fallible()
106 }
107}
108
109#[cfg(test)]
110mod test {
111 use super::*;
112 use crate::value;
113
114 test_function![
115 decode_base64 => DecodeBase64;
116
117 with_defaults {
118 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVl")],
119 want: Ok(value!("some+=string/value")),
120 tdef: TypeDef::bytes().fallible(),
121 }
122
123 with_standard_charset {
124 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVl"), charset: value!["standard"]],
125 want: Ok(value!("some+=string/value")),
126 tdef: TypeDef::bytes().fallible(),
127 }
128
129 with_urlsafe_charset {
130 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVl"), charset: value!("url_safe")],
131 want: Ok(value!("some+=string/value")),
132 tdef: TypeDef::bytes().fallible(),
133 }
134
135 with_invalid_charset {
136 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVl"), charset: value!("invalid")],
137 want: Err("unknown charset"),
138 tdef: TypeDef::bytes().fallible(),
139 }
140
141 with_defaults_invalid_value {
142 args: func_args![value: value!("helloworld")],
143 want: Err("unable to decode value from base64"),
144 tdef: TypeDef::bytes().fallible(),
145 }
146
147 empty_string_standard_charset {
148 args: func_args![value: value!(""), charset: value!("standard")],
149 want: Ok(value!("")),
150 tdef: TypeDef::bytes().fallible(),
151 }
152
153 empty_string_urlsafe_charset {
154 args: func_args![value: value!(""), charset: value!("url_safe")],
155 want: Ok(value!("")),
156 tdef: TypeDef::bytes().fallible(),
157 }
158
159 padding_not_included {
161 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVlXw")],
162 want: Ok(value!("some+=string/value_")),
163 tdef: TypeDef::bytes().fallible(),
164 }
165
166 padding_included {
167 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVlXw==")],
168 want: Ok(value!("some+=string/value_")),
169 tdef: TypeDef::bytes().fallible(),
170 }
171
172 no_padding {
174 args: func_args![value: value!("eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy91bnN0cnVjdF9ldmVudC9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy9saW5rX2NsaWNrL2pzb25zY2hlbWEvMS0wLTEiLCJkYXRhIjp7InRhcmdldFVybCI6Imh0dHBzOi8vaWRwLWF1dGguZ2FyLmVkdWNhdGlvbi5mci9kb21haW5lR2FyP2lkRU5UPVNqQT0maWRTcmM9WVhKck9pODBPRFUyTmk5d2RERTRNREF3TVE9PSIsImVsZW1lbnRJZCI6IiIsImVsZW1lbnRDbGFzc2VzIjpbImxpbmstYnV0dG9uIiwidHJhY2tlZCJdLCJlbGVtZW50VGFyZ2V0IjoiX2JsYW5rIn19fQ")],
175 want: Ok(value!(r#"{"schema":"iglu:com.snowplowanalytics.snowplow/unstruct_event/jsonschema/1-0-0","data":{"schema":"iglu:com.snowplowanalytics.snowplow/link_click/jsonschema/1-0-1","data":{"targetUrl":"https://idp-auth.gar.education.fr/domaineGar?idENT=SjA=&idSrc=YXJrOi80ODU2Ni9wdDE4MDAwMQ==","elementId":"","elementClasses":["link-button","tracked"],"elementTarget":"_blank"}}}"#)),
176 tdef: TypeDef::bytes().fallible(),
177 }
178 ];
179}