1use charset_normalizer_rs::Encoding as CharsetEncoding;
2use config::{any_err, get_or_create_sub_module};
3use data_encoding::{
4 Encoding, BASE32, BASE32HEX, BASE32HEX_NOPAD, BASE32_NOPAD, BASE64, BASE64URL, BASE64URL_NOPAD,
5 BASE64_NOPAD, HEXLOWER,
6};
7use mlua::{Lua, Value};
8
9fn decode(enc: &Encoding, data: &[u8]) -> mlua::Result<Vec<u8>> {
18 let mut spec = enc.specification();
19 if let Some(pad_char) = spec.padding {
20 let padding_bytes = [pad_char as u8];
21 let mut stripped = data;
22 while let Some(s) = stripped.strip_suffix(&padding_bytes) {
23 stripped = s;
24 }
25 spec.padding.take();
26 return spec
27 .encoding()
28 .map_err(any_err)?
29 .decode(stripped)
30 .map_err(any_err);
31 }
32 enc.decode(data).map_err(any_err)
33}
34
35pub fn register(lua: &Lua) -> anyhow::Result<()> {
36 let digest_mod = get_or_create_sub_module(lua, "encode")?;
37
38 for (name, enc) in [
39 ("base32", BASE32),
40 ("base32hex", BASE32HEX),
41 ("base32hex_nopad", BASE32HEX_NOPAD),
42 ("base32_nopad", BASE32_NOPAD),
43 ("base64", BASE64),
44 ("base64url", BASE64URL),
45 ("base64url_nopad", BASE64URL_NOPAD),
46 ("base64_nopad", BASE64_NOPAD),
47 ("hex", HEXLOWER),
48 ] {
49 let encoder = enc.clone();
50 digest_mod.set(
51 format!("{name}_encode"),
52 lua.create_function(move |_, data: mlua::Value| match data {
53 Value::String(s) => Ok(encoder.encode(&s.as_bytes())),
54 _ => Err(mlua::Error::external(
55 "parameter must be a string".to_string(),
56 )),
57 })?,
58 )?;
59 digest_mod.set(
60 format!("{name}_decode"),
61 lua.create_function(move |lua, data: String| {
62 let bytes = decode(&enc, data.as_bytes())?;
63 lua.create_string(&bytes)
64 })?,
65 )?;
66 }
67
68 digest_mod.set(
69 "charset_decode",
70 lua.create_function(
71 move |_lua, (charset, input_bytes): (String, mlua::String)| {
72 let encoding = CharsetEncoding::by_name(&charset)
73 .ok_or_else(|| mlua::Error::external(format!("unknown charset {charset}")))?;
74 encoding
75 .decode_simple(&input_bytes.as_bytes())
76 .map_err(|err| {
77 mlua::Error::external(format!(
78 "input string did not decode from {charset} bytes: {err}"
79 ))
80 })
81 },
82 )?,
83 )?;
84
85 digest_mod.set(
86 "charset_encode",
87 lua.create_function(
88 move |lua, (charset, input_string, ignore_errors): (String, String, Option<bool>)| {
89 let encoding = CharsetEncoding::by_name(&charset)
90 .ok_or_else(|| mlua::Error::external(format!("unknown charset {charset}")))?;
91 let ignore_errors = ignore_errors.unwrap_or(true);
92 let output_bytes =
93 encoding
94 .encode(&input_string, ignore_errors)
95 .map_err(|err| {
96 mlua::Error::external(format!(
97 "input string did not encode into {charset} bytes: {err}"
98 ))
99 })?;
100 lua.create_string(output_bytes)
101 },
102 )?,
103 )?;
104
105 Ok(())
106}
107
108#[cfg(test)]
109#[test]
110fn test_decode_padding() {
111 assert_eq!(
112 std::str::from_utf8(&decode(&BASE64, b"MmVtYWlsLmxvZwAuY3N2").unwrap()).unwrap(),
113 "2email.log\0.csv"
114 );
115 assert_eq!(
116 std::str::from_utf8(&decode(&BASE64, b"MmVtYWlsLmxvZwAuY3N2=").unwrap()).unwrap(),
117 "2email.log\0.csv"
118 );
119 assert_eq!(
120 std::str::from_utf8(&decode(&BASE64, b"MmVtYWlsLmxvZwAuY3N2==").unwrap()).unwrap(),
121 "2email.log\0.csv"
122 );
123 assert_eq!(
124 std::str::from_utf8(&decode(&BASE64, b"MmVtYWlsLmxvZwAuY3N2===").unwrap()).unwrap(),
125 "2email.log\0.csv"
126 );
127}