1use std::collections::HashSet;
4
5use serde_json::Value;
6
7use crate::functions::Function;
8use crate::interpreter::SearchResult;
9use crate::registry::register_if_enabled;
10use crate::{Context, Runtime, arg, defn};
11
12use base64::{
13 Engine,
14 engine::general_purpose::{STANDARD as BASE64_STANDARD, URL_SAFE_NO_PAD as BASE64_URL_SAFE},
15};
16
17pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
19 register_if_enabled(
20 runtime,
21 "base64_encode",
22 enabled,
23 Box::new(Base64EncodeFn::new()),
24 );
25 register_if_enabled(
26 runtime,
27 "base64_decode",
28 enabled,
29 Box::new(Base64DecodeFn::new()),
30 );
31 register_if_enabled(runtime, "hex_encode", enabled, Box::new(HexEncodeFn::new()));
32 register_if_enabled(runtime, "hex_decode", enabled, Box::new(HexDecodeFn::new()));
33 register_if_enabled(runtime, "jwt_decode", enabled, Box::new(JwtDecodeFn::new()));
34 register_if_enabled(runtime, "jwt_header", enabled, Box::new(JwtHeaderFn::new()));
35 register_if_enabled(
36 runtime,
37 "html_escape",
38 enabled,
39 Box::new(HtmlEscapeFn::new()),
40 );
41 register_if_enabled(
42 runtime,
43 "html_unescape",
44 enabled,
45 Box::new(HtmlUnescapeFn::new()),
46 );
47 register_if_enabled(
48 runtime,
49 "shell_escape",
50 enabled,
51 Box::new(ShellEscapeFn::new()),
52 );
53}
54
55defn!(Base64EncodeFn, vec![arg!(string)], None);
60
61impl Function for Base64EncodeFn {
62 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
63 self.signature.validate(args, ctx)?;
64
65 let input = args[0].as_str().ok_or_else(|| {
66 crate::JmespathError::from_ctx(
67 ctx,
68 crate::ErrorReason::Parse("Expected string argument".to_owned()),
69 )
70 })?;
71
72 let encoded = BASE64_STANDARD.encode(input.as_bytes());
73 Ok(Value::String(encoded))
74 }
75}
76
77defn!(Base64DecodeFn, vec![arg!(string)], None);
82
83impl Function for Base64DecodeFn {
84 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
85 self.signature.validate(args, ctx)?;
86
87 let input = args[0].as_str().ok_or_else(|| {
88 crate::JmespathError::from_ctx(
89 ctx,
90 crate::ErrorReason::Parse("Expected string argument".to_owned()),
91 )
92 })?;
93
94 match BASE64_STANDARD.decode(input.as_bytes()) {
95 Ok(decoded) => {
96 let s = String::from_utf8(decoded).map_err(|_| {
97 crate::JmespathError::from_ctx(
98 ctx,
99 crate::ErrorReason::Parse("Decoded bytes are not valid UTF-8".to_owned()),
100 )
101 })?;
102 Ok(Value::String(s))
103 }
104 Err(_) => Err(crate::JmespathError::from_ctx(
105 ctx,
106 crate::ErrorReason::Parse("Invalid base64 input".to_owned()),
107 )),
108 }
109 }
110}
111
112defn!(HexEncodeFn, vec![arg!(string)], None);
117
118impl Function for HexEncodeFn {
119 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
120 self.signature.validate(args, ctx)?;
121
122 let input = args[0].as_str().ok_or_else(|| {
123 crate::JmespathError::from_ctx(
124 ctx,
125 crate::ErrorReason::Parse("Expected string argument".to_owned()),
126 )
127 })?;
128
129 let encoded = hex::encode(input.as_bytes());
130 Ok(Value::String(encoded))
131 }
132}
133
134defn!(HexDecodeFn, vec![arg!(string)], None);
139
140impl Function for HexDecodeFn {
141 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
142 self.signature.validate(args, ctx)?;
143
144 let input = args[0].as_str().ok_or_else(|| {
145 crate::JmespathError::from_ctx(
146 ctx,
147 crate::ErrorReason::Parse("Expected string argument".to_owned()),
148 )
149 })?;
150
151 match hex::decode(input) {
152 Ok(decoded) => {
153 match String::from_utf8(decoded) {
155 Ok(s) => Ok(Value::String(s)),
156 Err(_) => Ok(Value::Null),
157 }
158 }
159 Err(_) => Ok(Value::Null),
161 }
162 }
163}
164
165fn decode_jwt_part(part: &str) -> Option<serde_json::Value> {
171 let decoded = BASE64_URL_SAFE.decode(part).ok()?;
173 let json_str = String::from_utf8(decoded).ok()?;
174 serde_json::from_str(&json_str).ok()
175}
176
177defn!(JwtDecodeFn, vec![arg!(string)], None);
182
183impl Function for JwtDecodeFn {
184 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
185 self.signature.validate(args, ctx)?;
186
187 let token = args[0].as_str().ok_or_else(|| {
188 crate::JmespathError::from_ctx(
189 ctx,
190 crate::ErrorReason::Parse("Expected string argument".to_owned()),
191 )
192 })?;
193
194 let parts: Vec<&str> = token.split('.').collect();
196 if parts.len() != 3 {
197 return Ok(Value::Null);
198 }
199
200 match decode_jwt_part(parts[1]) {
202 Some(json) => Ok(json),
203 None => Ok(Value::Null),
204 }
205 }
206}
207
208defn!(JwtHeaderFn, vec![arg!(string)], None);
213
214impl Function for JwtHeaderFn {
215 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
216 self.signature.validate(args, ctx)?;
217
218 let token = args[0].as_str().ok_or_else(|| {
219 crate::JmespathError::from_ctx(
220 ctx,
221 crate::ErrorReason::Parse("Expected string argument".to_owned()),
222 )
223 })?;
224
225 let parts: Vec<&str> = token.split('.').collect();
227 if parts.len() != 3 {
228 return Ok(Value::Null);
229 }
230
231 match decode_jwt_part(parts[0]) {
233 Some(json) => Ok(json),
234 None => Ok(Value::Null),
235 }
236 }
237}
238
239defn!(HtmlEscapeFn, vec![arg!(string)], None);
244
245impl Function for HtmlEscapeFn {
246 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
247 self.signature.validate(args, ctx)?;
248
249 let s = args[0].as_str().ok_or_else(|| {
250 crate::JmespathError::from_ctx(
251 ctx,
252 crate::ErrorReason::Parse("Expected string argument".to_owned()),
253 )
254 })?;
255
256 let escaped = s
257 .replace('&', "&")
258 .replace('<', "<")
259 .replace('>', ">")
260 .replace('"', """)
261 .replace('\'', "'");
262
263 Ok(Value::String(escaped))
264 }
265}
266
267defn!(HtmlUnescapeFn, vec![arg!(string)], None);
272
273impl Function for HtmlUnescapeFn {
274 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
275 self.signature.validate(args, ctx)?;
276
277 let s = args[0].as_str().ok_or_else(|| {
278 crate::JmespathError::from_ctx(
279 ctx,
280 crate::ErrorReason::Parse("Expected string argument".to_owned()),
281 )
282 })?;
283
284 let unescaped = s
286 .replace("'", "'")
287 .replace("'", "'")
288 .replace("'", "'")
289 .replace(""", "\"")
290 .replace(">", ">")
291 .replace("<", "<")
292 .replace("&", "&");
293
294 Ok(Value::String(unescaped))
295 }
296}
297
298defn!(ShellEscapeFn, vec![arg!(string)], None);
303
304impl Function for ShellEscapeFn {
305 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
306 self.signature.validate(args, ctx)?;
307
308 let s = args[0].as_str().ok_or_else(|| {
309 crate::JmespathError::from_ctx(
310 ctx,
311 crate::ErrorReason::Parse("Expected string argument".to_owned()),
312 )
313 })?;
314
315 let escaped = format!("'{}'", s.replace('\'', "'\\''"));
318
319 Ok(Value::String(escaped))
320 }
321}