vrl/stdlib/
decode_base64.rs1use crate::compiler::function::EnumVariant;
2use crate::compiler::prelude::*;
3use crate::stdlib::util::Base64Charset;
4use std::sync::LazyLock;
5
6static DEFAULT_CHARSET: LazyLock<Value> = LazyLock::new(|| Value::Bytes(Bytes::from("standard")));
7
8static CHARSET_ENUM: &[EnumVariant] = &[
9 EnumVariant {
10 value: "standard",
11 description: "[Standard](https://tools.ietf.org/html/rfc4648#section-4) Base64 format.",
12 },
13 EnumVariant {
14 value: "url_safe",
15 description: "Modified Base64 for [URL variants](https://en.wikipedia.org/wiki/Base64#URL_applications).",
16 },
17];
18
19static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
20 vec data to decode.",
25 ),
26 Parameter::optional(
27 "charset",
28 kind::BYTES,
29 "The character set to use when decoding the data.",
30 )
31 .default(&DEFAULT_CHARSET)
32 .enum_variants(CHARSET_ENUM),
33 ]
34});
35
36fn decode_base64(charset: Value, value: Value) -> Resolved {
37 let value = value.try_bytes()?;
38 let charset = Base64Charset::from_slice(&charset.try_bytes()?)?;
39
40 let decoder = match charset {
41 Base64Charset::Standard => base64_simd::STANDARD_NO_PAD,
42 Base64Charset::UrlSafe => base64_simd::URL_SAFE_NO_PAD,
43 };
44
45 let pos = value
47 .iter()
48 .rev()
49 .position(|c| *c != b'=')
50 .map_or(value.len(), |p| value.len() - p);
51
52 let decoded_vec = decoder
53 .decode_to_vec(&value[0..pos])
54 .map_err(|_| "unable to decode value from base64")?;
55
56 Ok(Value::Bytes(Bytes::from(decoded_vec)))
57}
58
59#[derive(Clone, Copy, Debug)]
60pub struct DecodeBase64;
61
62impl Function for DecodeBase64 {
63 fn identifier(&self) -> &'static str {
64 "decode_base64"
65 }
66
67 fn usage(&self) -> &'static str {
68 "Decodes the `value` (a [Base64](https://en.wikipedia.org/wiki/Base64) string) into its original string."
69 }
70
71 fn category(&self) -> &'static str {
72 Category::Codec.as_ref()
73 }
74
75 fn internal_failure_reasons(&self) -> &'static [&'static str] {
76 &["`value` isn't a valid encoded Base64 string."]
77 }
78
79 fn return_kind(&self) -> u16 {
80 kind::BYTES
81 }
82
83 fn parameters(&self) -> &'static [Parameter] {
84 PARAMETERS.as_slice()
85 }
86
87 fn compile(
88 &self,
89 _state: &state::TypeState,
90 _ctx: &mut FunctionCompileContext,
91 arguments: ArgumentList,
92 ) -> Compiled {
93 let value = arguments.required("value");
94 let charset = arguments.optional("charset");
95
96 Ok(DecodeBase64Fn { value, charset }.as_expr())
97 }
98
99 fn examples(&self) -> &'static [Example] {
100 &[
101 example! {
102 title: "Decode Base64 data (default)",
103 source: r#"decode_base64!("eW91IGhhdmUgc3VjY2Vzc2Z1bGx5IGRlY29kZWQgbWU=")"#,
104 result: Ok("you have successfully decoded me"),
105 },
106 example! {
107 title: "Decode Base64 data (URL safe)",
108 source: r#"decode_base64!("eW91IGNhbid0IG1ha2UgeW91ciBoZWFydCBmZWVsIHNvbWV0aGluZyBpdCB3b24ndA==", charset: "url_safe")"#,
109 result: Ok("you can't make your heart feel something it won't"),
110 },
111 ]
112 }
113}
114
115#[derive(Clone, Debug)]
116struct DecodeBase64Fn {
117 value: Box<dyn Expression>,
118 charset: Option<Box<dyn Expression>>,
119}
120
121impl FunctionExpression for DecodeBase64Fn {
122 fn resolve(&self, ctx: &mut Context) -> Resolved {
123 let value = self.value.resolve(ctx)?;
124 let charset = self
125 .charset
126 .map_resolve_with_default(ctx, || DEFAULT_CHARSET.clone())?;
127
128 decode_base64(charset, value)
129 }
130
131 fn type_def(&self, _: &state::TypeState) -> TypeDef {
132 TypeDef::bytes().fallible()
135 }
136}
137
138#[cfg(test)]
139mod test {
140 use super::*;
141 use crate::value;
142
143 test_function![
144 decode_base64 => DecodeBase64;
145
146 with_defaults {
147 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVl")],
148 want: Ok(value!("some+=string/value")),
149 tdef: TypeDef::bytes().fallible(),
150 }
151
152 with_standard_charset {
153 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVl"), charset: value!["standard"]],
154 want: Ok(value!("some+=string/value")),
155 tdef: TypeDef::bytes().fallible(),
156 }
157
158 with_urlsafe_charset {
159 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVl"), charset: value!("url_safe")],
160 want: Ok(value!("some+=string/value")),
161 tdef: TypeDef::bytes().fallible(),
162 }
163
164 with_invalid_charset {
165 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVl"), charset: value!("invalid")],
166 want: Err("unknown charset"),
167 tdef: TypeDef::bytes().fallible(),
168 }
169
170 with_defaults_invalid_value {
171 args: func_args![value: value!("helloworld")],
172 want: Err("unable to decode value from base64"),
173 tdef: TypeDef::bytes().fallible(),
174 }
175
176 empty_string_standard_charset {
177 args: func_args![value: value!(""), charset: value!("standard")],
178 want: Ok(value!("")),
179 tdef: TypeDef::bytes().fallible(),
180 }
181
182 empty_string_urlsafe_charset {
183 args: func_args![value: value!(""), charset: value!("url_safe")],
184 want: Ok(value!("")),
185 tdef: TypeDef::bytes().fallible(),
186 }
187
188 padding_not_included {
190 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVlXw")],
191 want: Ok(value!("some+=string/value_")),
192 tdef: TypeDef::bytes().fallible(),
193 }
194
195 padding_included {
196 args: func_args![value: value!("c29tZSs9c3RyaW5nL3ZhbHVlXw==")],
197 want: Ok(value!("some+=string/value_")),
198 tdef: TypeDef::bytes().fallible(),
199 }
200
201 no_padding {
203 args: func_args![value: value!("eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy91bnN0cnVjdF9ldmVudC9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy9saW5rX2NsaWNrL2pzb25zY2hlbWEvMS0wLTEiLCJkYXRhIjp7InRhcmdldFVybCI6Imh0dHBzOi8vaWRwLWF1dGguZ2FyLmVkdWNhdGlvbi5mci9kb21haW5lR2FyP2lkRU5UPVNqQT0maWRTcmM9WVhKck9pODBPRFUyTmk5d2RERTRNREF3TVE9PSIsImVsZW1lbnRJZCI6IiIsImVsZW1lbnRDbGFzc2VzIjpbImxpbmstYnV0dG9uIiwidHJhY2tlZCJdLCJlbGVtZW50VGFyZ2V0IjoiX2JsYW5rIn19fQ")],
204 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"}}}"#)),
205 tdef: TypeDef::bytes().fallible(),
206 }
207 ];
208}