1use base64::{Engine, engine::general_purpose};
2use tera::{Kwargs, State, TeraResult};
3
4const STANDARD_DECODE: general_purpose::GeneralPurpose = general_purpose::GeneralPurpose::new(
5 &base64::alphabet::STANDARD,
6 general_purpose::GeneralPurposeConfig::new()
7 .with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
8);
9
10const URL_SAFE_DECODE: general_purpose::GeneralPurpose = general_purpose::GeneralPurpose::new(
11 &base64::alphabet::URL_SAFE,
12 general_purpose::GeneralPurposeConfig::new()
13 .with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
14);
15
16pub fn b64_encode(val: &str, kwargs: Kwargs, _: &State) -> TeraResult<String> {
26 let url_safe = kwargs.get::<bool>("url_safe")?.unwrap_or(false);
27 let padded = kwargs.get::<bool>("padded")?.unwrap_or(true);
28 let encoded = match (url_safe, padded) {
29 (false, true) => general_purpose::STANDARD.encode(val),
30 (false, false) => general_purpose::STANDARD_NO_PAD.encode(val),
31 (true, true) => general_purpose::URL_SAFE.encode(val),
32 (true, false) => general_purpose::URL_SAFE_NO_PAD.encode(val),
33 };
34 Ok(encoded)
35}
36
37pub fn b64_decode(val: &str, kwargs: Kwargs, _: &State) -> TeraResult<String> {
44 let url_safe = kwargs.get::<bool>("url_safe")?.unwrap_or(false);
45 let decoded = if url_safe {
46 URL_SAFE_DECODE.decode(val)
47 } else {
48 STANDARD_DECODE.decode(val)
49 };
50 let bytes = decoded.map_err(|e| tera::Error::message(format!("Invalid base64: {e}")))?;
51 String::from_utf8(bytes).map_err(|e| tera::Error::message(format!("Invalid UTF-8: {e}")))
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use std::sync::Arc;
58 use tera::value::Map;
59 use tera::{Context, Kwargs, State};
60
61 #[test]
62 fn test_b64_encode() {
63 let ctx = Context::new();
64 let state = State::new(&ctx);
65 assert_eq!(
66 b64_encode("hello world", Kwargs::default(), &state).unwrap(),
67 "aGVsbG8gd29ybGQ="
68 );
69 }
70
71 #[test]
72 fn test_b64_encode_no_padding() {
73 let ctx = Context::new();
74 let state = State::new(&ctx);
75 let mut kwargs_map = Map::new();
76 kwargs_map.insert("padded".into(), false.into());
77 let kwargs = Kwargs::new(Arc::new(kwargs_map));
78 assert_eq!(
79 b64_encode("hello world", kwargs, &state).unwrap(),
80 "aGVsbG8gd29ybGQ"
81 );
82 }
83
84 #[test]
85 fn test_b64_encode_url_safe() {
86 let ctx = Context::new();
87 let state = State::new(&ctx);
88 let mut kwargs_map = Map::new();
89 kwargs_map.insert("url_safe".into(), true.into());
90 let kwargs = Kwargs::new(Arc::new(kwargs_map));
91 assert_eq!(b64_encode("<<??>>", kwargs, &state).unwrap(), "PDw_Pz4-");
93 }
94
95 #[test]
96 fn test_b64_decode() {
97 let ctx = Context::new();
98 let state = State::new(&ctx);
99 assert_eq!(
100 b64_decode("aGVsbG8gd29ybGQ=", Kwargs::default(), &state).unwrap(),
101 "hello world"
102 );
103 assert_eq!(
105 b64_decode("aGVsbG8gd29ybGQ", Kwargs::default(), &state).unwrap(),
106 "hello world"
107 );
108 }
109
110 #[test]
111 fn test_b64_decode_url_safe() {
112 let ctx = Context::new();
113 let state = State::new(&ctx);
114 let mut kwargs_map = Map::new();
115 kwargs_map.insert("url_safe".into(), true.into());
116 let kwargs = Kwargs::new(Arc::new(kwargs_map));
117 assert_eq!(b64_decode("PDw_Pz4-", kwargs, &state).unwrap(), "<<??>>");
118 }
119
120 #[test]
121 fn test_b64_roundtrip() {
122 let ctx = Context::new();
123 let state = State::new(&ctx);
124 let original = "Hello, δΈη! π";
125 let encoded = b64_encode(original, Kwargs::default(), &state).unwrap();
126 let decoded = b64_decode(&encoded, Kwargs::default(), &state).unwrap();
127 assert_eq!(decoded, original);
128 }
129
130 #[test]
131 fn test_b64_decode_invalid() {
132 let ctx = Context::new();
133 let state = State::new(&ctx);
134 let result = b64_decode("not valid base64!!!", Kwargs::default(), &state);
135 assert!(result.is_err());
136 }
137
138 #[test]
139 fn test_register() {
140 let mut tera = tera::Tera::default();
141 tera.register_filter("b64_encode", b64_encode);
142 tera.register_filter("b64_decode", b64_decode);
143 }
144}