vrl/stdlib/
decode_base64.rs

1use 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    // Find the position of padding char '='
20    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        // Always fallible due to the possibility of decoding errors that VRL can't detect in
104        // advance: https://docs.rs/base64/0.13.0/base64/enum.DecodeError.html
105        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        // decode_base64 function should be able to decode base64 string with or without padding
160        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        // https://github.com/vectordotdev/vrl/issues/959
173        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}