js_deobfuscator/value/
runtime.rs1use super::JsValue;
7
8pub fn call(func: &str, args: &[JsValue]) -> Option<JsValue> {
10 let s = match args.first()? {
11 JsValue::String(s) => s.as_str(),
12 _ => return None,
13 };
14 let result = match func {
15 "atob" => atob(s)?,
16 "btoa" => btoa(s)?,
17 "escape" => escape(s),
18 "unescape" => unescape(s)?,
19 _ => return None,
20 };
21 Some(JsValue::String(result))
22}
23
24fn atob(input: &str) -> Option<String> {
26 use base64::Engine as _;
27 let bytes = base64::engine::general_purpose::STANDARD.decode(input).ok()?;
28 Some(bytes.iter().map(|&b| b as char).collect())
29}
30
31fn btoa(input: &str) -> Option<String> {
33 if input.chars().any(|c| c as u32 > 0xFF) {
34 return None; }
36 use base64::Engine as _;
37 let bytes: Vec<u8> = input.chars().map(|c| c as u8).collect();
38 Some(base64::engine::general_purpose::STANDARD.encode(&bytes))
39}
40
41fn escape(input: &str) -> String {
43 let mut result = String::with_capacity(input.len());
44 for c in input.chars() {
45 match c {
46 'A'..='Z' | 'a'..='z' | '0'..='9' | '@' | '*' | '_' | '+' | '-' | '.' | '/' => {
47 result.push(c);
48 }
49 c if (c as u32) <= 0xFF => {
50 result.push_str(&format!("%{:02X}", c as u32));
51 }
52 c => {
53 result.push_str(&format!("%u{:04X}", c as u32));
54 }
55 }
56 }
57 result
58}
59
60fn unescape(input: &str) -> Option<String> {
62 let mut result = String::with_capacity(input.len());
63 let chars: Vec<char> = input.chars().collect();
64 let mut i = 0;
65 while i < chars.len() {
66 if chars[i] == '%' {
67 if i + 6 <= chars.len() && chars[i + 1] == 'u' {
68 let hex: String = chars[i+2..i+6].iter().collect();
70 if let Some(code) = u32::from_str_radix(&hex, 16).ok().and_then(char::from_u32) {
71 result.push(code);
72 i += 6;
73 continue;
74 }
75 }
76 if i + 3 <= chars.len() {
77 let hex: String = chars[i+1..i+3].iter().collect();
79 if let Ok(code) = u8::from_str_radix(&hex, 16) {
80 result.push(code as char);
81 i += 3;
82 continue;
83 }
84 }
85 result.push('%');
87 i += 1;
88 } else {
89 result.push(chars[i]);
90 i += 1;
91 }
92 }
93 Some(result)
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 fn s(v: &str) -> JsValue { JsValue::String(v.into()) }
100
101 #[test]
102 fn test_atob() {
103 assert_eq!(call("atob", &[s("SGVsbG8=")]), Some(s("Hello")));
104 assert_eq!(call("atob", &[s("dGVzdA==")]), Some(s("test")));
105 }
106
107 #[test]
108 fn test_btoa() {
109 assert_eq!(call("btoa", &[s("Hello")]), Some(s("SGVsbG8=")));
110 assert_eq!(call("btoa", &[s("test")]), Some(s("dGVzdA==")));
111 }
112
113 #[test]
114 fn test_roundtrip() {
115 let original = "Hello, World!";
116 let encoded = btoa(original).unwrap();
117 let decoded = atob(&encoded).unwrap();
118 assert_eq!(decoded, original);
119 }
120
121 #[test]
122 fn test_escape() {
123 assert_eq!(call("escape", &[s("hello world")]), Some(s("hello%20world")));
124 assert_eq!(call("escape", &[s("abc")]), Some(s("abc")));
125 }
126
127 #[test]
128 fn test_unescape() {
129 assert_eq!(call("unescape", &[s("hello%20world")]), Some(s("hello world")));
130 assert_eq!(call("unescape", &[s("%u0041")]), Some(s("A")));
131 }
132
133 #[test]
134 fn test_unescape_boundary() {
135 assert_eq!(call("unescape", &[s("%u0042")]), Some(s("B")));
137 assert_eq!(call("unescape", &[s("abc%u004")]), Some(s("abc%u004")));
139 }
140}