tatara_lisp_script/stdlib/
encoding.rs1use std::sync::Arc;
13
14use tatara_lisp_eval::{Arity, EvalError, Interpreter, Value};
15
16use crate::script_ctx::ScriptCtx;
17use crate::stdlib::env::str_arg;
18
19pub fn install(interp: &mut Interpreter<ScriptCtx>) {
20 use base64::Engine;
21
22 interp.register_fn(
23 "base64-encode",
24 Arity::Exact(1),
25 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
26 let engine = base64::engine::general_purpose::STANDARD;
27 let s = str_arg(&args[0], "base64-encode", sp)?;
28 Ok(Value::Str(Arc::from(engine.encode(s.as_bytes()))))
29 },
30 );
31
32 interp.register_fn(
33 "base64-decode",
34 Arity::Exact(1),
35 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
36 let engine = base64::engine::general_purpose::STANDARD;
37 let s = str_arg(&args[0], "base64-decode", sp)?;
38 let bytes = engine
39 .decode(s.as_bytes())
40 .map_err(|e| EvalError::native_fn("base64-decode", e.to_string(), sp))?;
41 Ok(Value::Str(Arc::from(String::from_utf8(bytes).map_err(
42 |e| EvalError::native_fn("base64-decode", format!("not utf8: {e}"), sp),
43 )?)))
44 },
45 );
46
47 interp.register_fn(
48 "base64url-encode",
49 Arity::Exact(1),
50 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
51 let engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
52 let s = str_arg(&args[0], "base64url-encode", sp)?;
53 Ok(Value::Str(Arc::from(engine.encode(s.as_bytes()))))
54 },
55 );
56
57 interp.register_fn(
58 "base64url-decode",
59 Arity::Exact(1),
60 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
61 let engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
62 let s = str_arg(&args[0], "base64url-decode", sp)?;
63 let bytes = engine
64 .decode(s.as_bytes())
65 .map_err(|e| EvalError::native_fn("base64url-decode", e.to_string(), sp))?;
66 Ok(Value::Str(Arc::from(String::from_utf8(bytes).map_err(
67 |e| EvalError::native_fn("base64url-decode", format!("not utf8: {e}"), sp),
68 )?)))
69 },
70 );
71
72 interp.register_fn(
73 "url-encode",
74 Arity::Exact(1),
75 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
76 let s = str_arg(&args[0], "url-encode", sp)?;
77 Ok(Value::Str(Arc::from(url_pct_encode(&s))))
78 },
79 );
80
81 interp.register_fn(
82 "url-decode",
83 Arity::Exact(1),
84 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
85 let s = str_arg(&args[0], "url-decode", sp)?;
86 Ok(Value::Str(Arc::from(
87 url_pct_decode(&s).map_err(|e| EvalError::native_fn("url-decode", e, sp))?,
88 )))
89 },
90 );
91
92 interp.register_fn(
93 "hex-encode",
94 Arity::Exact(1),
95 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
96 let s = str_arg(&args[0], "hex-encode", sp)?;
97 Ok(Value::Str(Arc::from(hex::encode(s.as_bytes()))))
98 },
99 );
100
101 interp.register_fn(
102 "hex-decode",
103 Arity::Exact(1),
104 |args: &[Value], _ctx: &mut ScriptCtx, sp| {
105 let s = str_arg(&args[0], "hex-decode", sp)?;
106 let bytes = hex::decode(s.as_bytes())
107 .map_err(|e| EvalError::native_fn("hex-decode", e.to_string(), sp))?;
108 Ok(Value::Str(Arc::from(String::from_utf8(bytes).map_err(
109 |e| EvalError::native_fn("hex-decode", format!("not utf8: {e}"), sp),
110 )?)))
111 },
112 );
113}
114
115fn url_pct_encode(s: &str) -> String {
116 let mut out = String::with_capacity(s.len());
117 for &b in s.as_bytes() {
118 if matches!(b, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~') {
119 out.push(b as char);
120 } else {
121 out.push_str(&format!("%{b:02X}"));
122 }
123 }
124 out
125}
126
127fn url_pct_decode(s: &str) -> Result<String, String> {
128 let bytes = s.as_bytes();
129 let mut out = Vec::with_capacity(bytes.len());
130 let mut i = 0;
131 while i < bytes.len() {
132 if bytes[i] == b'%' {
133 if i + 2 >= bytes.len() {
134 return Err(format!("truncated percent-escape at {i}"));
135 }
136 let hex_str = std::str::from_utf8(&bytes[i + 1..i + 3])
137 .map_err(|e| format!("bad utf8 in escape: {e}"))?;
138 let byte =
139 u8::from_str_radix(hex_str, 16).map_err(|e| format!("bad hex {hex_str:?}: {e}"))?;
140 out.push(byte);
141 i += 3;
142 } else {
143 out.push(bytes[i]);
144 i += 1;
145 }
146 }
147 String::from_utf8(out).map_err(|e| format!("non-utf8 result: {e}"))
148}