use rhai::Engine;
fn encode_b64_impl(input: &str) -> String {
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, input.as_bytes())
}
fn decode_b64_impl(input: &str) -> Result<String, Box<rhai::EvalAltResult>> {
match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, input) {
Ok(bytes) => match String::from_utf8(bytes) {
Ok(string) => Ok(string),
Err(e) => Err(format!("Invalid UTF-8 in base64 decoded data: {}", e).into()),
},
Err(e) => Err(format!("Invalid base64 string: {}", e).into()),
}
}
fn encode_hex_impl(input: &str) -> String {
hex::encode(input.as_bytes())
}
fn decode_hex_impl(input: &str) -> Result<String, Box<rhai::EvalAltResult>> {
match hex::decode(input) {
Ok(bytes) => match String::from_utf8(bytes) {
Ok(string) => Ok(string),
Err(e) => Err(format!("Invalid UTF-8 in hex decoded data: {}", e).into()),
},
Err(e) => Err(format!("Invalid hex string: {}", e).into()),
}
}
fn encode_url_impl(input: &str) -> String {
urlencoding::encode(input).to_string()
}
fn decode_url_impl(input: &str) -> Result<String, Box<rhai::EvalAltResult>> {
match urlencoding::decode(input) {
Ok(cow_str) => Ok(cow_str.to_string()),
Err(e) => Err(format!("Invalid URL encoded string: {}", e).into()),
}
}
fn escape_html_impl(input: &str) -> String {
html_escape::encode_text(input).to_string()
}
fn unescape_html_impl(input: &str) -> String {
html_escape::decode_html_entities(input).to_string()
}
fn escape_json_impl(input: &str) -> String {
let json_str = serde_json::to_string(input).unwrap_or_else(|_| String::new());
if json_str.len() >= 2 && json_str.starts_with('"') && json_str.ends_with('"') {
json_str[1..json_str.len() - 1].to_string()
} else {
json_str
}
}
fn unescape_json_impl(input: &str) -> Result<String, Box<rhai::EvalAltResult>> {
let json_string = format!("\"{}\"", input);
match serde_json::from_str::<String>(&json_string) {
Ok(unescaped) => Ok(unescaped),
Err(e) => Err(format!("Invalid JSON escape sequences: {}", e).into()),
}
}
pub fn register_functions(engine: &mut Engine) {
engine.register_fn("encode_b64", encode_b64_impl);
engine.register_fn("decode_b64", decode_b64_impl);
engine.register_fn("encode_hex", encode_hex_impl);
engine.register_fn("decode_hex", decode_hex_impl);
engine.register_fn("encode_url", encode_url_impl);
engine.register_fn("decode_url", decode_url_impl);
engine.register_fn("escape_html", escape_html_impl);
engine.register_fn("unescape_html", unescape_html_impl);
engine.register_fn("escape_json", escape_json_impl);
engine.register_fn("unescape_json", unescape_json_impl);
}
#[cfg(test)]
mod tests {
use super::*;
use rhai::Scope;
#[test]
fn test_base64_encoding() {
let mut engine = rhai::Engine::new();
register_functions(&mut engine);
let mut scope = Scope::new();
scope.push("text", "Hello, World!");
let result: String = engine
.eval_with_scope(&mut scope, r#"encode_b64(text)"#)
.unwrap();
assert_eq!(result, "SGVsbG8sIFdvcmxkIQ==");
scope.push("encoded", "SGVsbG8sIFdvcmxkIQ==");
let result: String = engine
.eval_with_scope(&mut scope, r#"decode_b64(encoded)"#)
.unwrap();
assert_eq!(result, "Hello, World!");
let result: String = engine
.eval_with_scope(&mut scope, r#"decode_b64(encode_b64(text))"#)
.unwrap();
assert_eq!(result, "Hello, World!");
scope.push("invalid", "invalid base64!");
let result = engine.eval_with_scope::<String>(&mut scope, r#"decode_b64(invalid)"#);
assert!(result.is_err());
}
#[test]
fn test_hex_encoding() {
let mut engine = rhai::Engine::new();
register_functions(&mut engine);
let mut scope = Scope::new();
scope.push("text", "Hello!");
let result: String = engine
.eval_with_scope(&mut scope, r#"encode_hex(text)"#)
.unwrap();
assert_eq!(result, "48656c6c6f21");
scope.push("encoded", "48656c6c6f21");
let result: String = engine
.eval_with_scope(&mut scope, r#"decode_hex(encoded)"#)
.unwrap();
assert_eq!(result, "Hello!");
let result: String = engine
.eval_with_scope(&mut scope, r#"decode_hex(encode_hex(text))"#)
.unwrap();
assert_eq!(result, "Hello!");
scope.push("invalid", "gghhii");
let result = engine.eval_with_scope::<String>(&mut scope, r#"decode_hex(invalid)"#);
assert!(result.is_err());
}
#[test]
fn test_url_encoding() {
let mut engine = rhai::Engine::new();
register_functions(&mut engine);
let mut scope = Scope::new();
scope.push("text", "Hello World & Special chars!");
let result: String = engine
.eval_with_scope(&mut scope, r#"encode_url(text)"#)
.unwrap();
assert_eq!(result, "Hello%20World%20%26%20Special%20chars%21");
scope.push("encoded", "Hello%20World%20%26%20Special%20chars%21");
let result: String = engine
.eval_with_scope(&mut scope, r#"decode_url(encoded)"#)
.unwrap();
assert_eq!(result, "Hello World & Special chars!");
let result: String = engine
.eval_with_scope(&mut scope, r#"decode_url(encode_url(text))"#)
.unwrap();
assert_eq!(result, "Hello World & Special chars!");
scope.push("invalid", "invalid%ZZ");
let result = engine.eval_with_scope::<String>(&mut scope, r#"decode_url(invalid)"#);
scope.push("invalid2", "invalid%G");
let result2 = engine.eval_with_scope::<String>(&mut scope, r#"decode_url(invalid2)"#);
assert!(result.is_err() || result2.is_err() || result.unwrap() == "invalid%ZZ");
}
#[test]
fn test_html_escaping() {
let mut engine = rhai::Engine::new();
register_functions(&mut engine);
let mut scope = Scope::new();
scope.push("text", "<script>alert('xss')</script>");
let result: String = engine
.eval_with_scope(&mut scope, r#"escape_html(text)"#)
.unwrap();
assert_eq!(result, "<script>alert('xss')</script>");
scope.push(
"escaped",
"<script>alert('xss')</script>",
);
let result: String = engine
.eval_with_scope(&mut scope, r#"unescape_html(escaped)"#)
.unwrap();
assert_eq!(result, "<script>alert('xss')</script>");
let result: String = engine
.eval_with_scope(&mut scope, r#"unescape_html(escape_html(text))"#)
.unwrap();
assert_eq!(result, "<script>alert('xss')</script>");
scope.push("entities", "& < > " '");
let result: String = engine
.eval_with_scope(&mut scope, r#"unescape_html(entities)"#)
.unwrap();
assert_eq!(result, "& < > \" '");
}
#[test]
fn test_json_escaping() {
let mut engine = rhai::Engine::new();
register_functions(&mut engine);
let mut scope = Scope::new();
scope.push("text", "Hello\nWorld\t\"test\"");
let result: String = engine
.eval_with_scope(&mut scope, r#"escape_json(text)"#)
.unwrap();
assert_eq!(result, "Hello\\nWorld\\t\\\"test\\\"");
scope.push("escaped", "Hello\\nWorld\\t\\\"test\\\"");
let result: String = engine
.eval_with_scope(&mut scope, r#"unescape_json(escaped)"#)
.unwrap();
assert_eq!(result, "Hello\nWorld\t\"test\"");
let result: String = engine
.eval_with_scope(&mut scope, r#"unescape_json(escape_json(text))"#)
.unwrap();
assert_eq!(result, "Hello\nWorld\t\"test\"");
scope.push("invalid", "invalid\\x escape");
let result = engine.eval_with_scope::<String>(&mut scope, r#"unescape_json(invalid)"#);
assert!(result.is_err());
}
#[test]
fn test_empty_strings() {
let mut engine = rhai::Engine::new();
register_functions(&mut engine);
let mut scope = Scope::new();
scope.push("empty", "");
let result: String = engine
.eval_with_scope(&mut scope, r#"encode_b64(empty)"#)
.unwrap();
assert_eq!(result, "");
let result: String = engine
.eval_with_scope(&mut scope, r#"encode_hex(empty)"#)
.unwrap();
assert_eq!(result, "");
let result: String = engine
.eval_with_scope(&mut scope, r#"encode_url(empty)"#)
.unwrap();
assert_eq!(result, "");
let result: String = engine
.eval_with_scope(&mut scope, r#"escape_html(empty)"#)
.unwrap();
assert_eq!(result, "");
let result: String = engine
.eval_with_scope(&mut scope, r#"escape_json(empty)"#)
.unwrap();
assert_eq!(result, "");
let result: String = engine
.eval_with_scope(&mut scope, r#"decode_b64(empty)"#)
.unwrap();
assert_eq!(result, "");
let result: String = engine
.eval_with_scope(&mut scope, r#"decode_hex(empty)"#)
.unwrap();
assert_eq!(result, "");
let result: String = engine
.eval_with_scope(&mut scope, r#"decode_url(empty)"#)
.unwrap();
assert_eq!(result, "");
let result: String = engine
.eval_with_scope(&mut scope, r#"unescape_html(empty)"#)
.unwrap();
assert_eq!(result, "");
let result: String = engine
.eval_with_scope(&mut scope, r#"unescape_json(empty)"#)
.unwrap();
assert_eq!(result, "");
}
}