js_deobfuscator/eval/
node.rs1use std::collections::HashMap;
7use std::io::{BufRead, BufReader, Write};
8use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
9
10const WORKER_JS: &str = r#"
11const readline = require('readline');
12const rl = readline.createInterface({ input: process.stdin });
13rl.on('line', (line) => {
14 try {
15 const result = (0, eval)(line);
16 const type = typeof result;
17 if (type === 'number' || type === 'string' || type === 'boolean' || result === null) {
18 process.stdout.write(JSON.stringify({ ok: true, value: result }) + '\n');
19 } else {
20 process.stdout.write(JSON.stringify({ ok: false, error: 'non-primitive' }) + '\n');
21 }
22 } catch (e) {
23 process.stdout.write(JSON.stringify({ ok: false, error: String(e) }) + '\n');
24 }
25});
26"#;
27
28pub struct NodeProcess {
30 child: Option<Child>,
31 stdin: Option<ChildStdin>,
32 stdout: Option<BufReader<ChildStdout>>,
33 cache: HashMap<String, Option<serde_json::Value>>,
34}
35
36impl NodeProcess {
37 pub fn spawn() -> std::io::Result<Self> {
39 let mut child = Command::new("node")
40 .arg("-e")
41 .arg(WORKER_JS)
42 .stdin(Stdio::piped())
43 .stdout(Stdio::piped())
44 .stderr(Stdio::null())
45 .spawn()?;
46
47 let stdin = child.stdin.take();
48 let stdout = child.stdout.take().map(BufReader::new);
49
50 Ok(Self {
51 child: Some(child),
52 stdin,
53 stdout,
54 cache: HashMap::new(),
55 })
56 }
57
58 fn ensure_running(&mut self) -> bool {
60 if let Some(ref mut child) = self.child {
62 match child.try_wait() {
63 Ok(Some(_)) => {
64 tracing::warn!("Node.js process died, restarting");
66 }
67 Ok(None) => return true, Err(_) => {}
69 }
70 }
71
72 match Self::spawn() {
74 Ok(mut new_proc) => {
75 if let Some(ref mut child) = self.child {
77 let _ = child.kill();
78 }
79 self.child = std::mem::take(&mut new_proc.child);
81 self.stdin = std::mem::take(&mut new_proc.stdin);
82 self.stdout = std::mem::take(&mut new_proc.stdout);
83 true
85 }
86 Err(e) => {
87 tracing::error!("Failed to spawn Node.js: {}", e);
88 false
89 }
90 }
91 }
92
93 pub fn eval(&mut self, expr: &str) -> Option<serde_json::Value> {
97 if let Some(cached) = self.cache.get(expr) {
99 return cached.clone();
100 }
101
102 let result = self.eval_uncached(expr);
103 self.cache.insert(expr.to_string(), result.clone());
104 result
105 }
106
107 fn eval_uncached(&mut self, expr: &str) -> Option<serde_json::Value> {
108 if !self.ensure_running() {
110 return None;
111 }
112
113 let stdin = self.stdin.as_mut()?;
115 writeln!(stdin, "{expr}").ok()?;
116 stdin.flush().ok()?;
117
118 let stdout = self.stdout.as_mut()?;
120 let mut line = String::new();
121 match stdout.read_line(&mut line) {
122 Ok(0) => {
123 tracing::warn!("Node.js returned EOF");
125 return None;
126 }
127 Ok(_) => {}
128 Err(e) => {
129 tracing::warn!("Node.js read error: {}", e);
130 return None;
131 }
132 }
133
134 let response: serde_json::Value = serde_json::from_str(line.trim()).ok()?;
135
136 if response.get("ok")?.as_bool()? {
137 Some(response.get("value")?.clone())
138 } else {
139 None
140 }
141 }
142
143 pub fn is_healthy(&mut self) -> bool {
145 if let Some(ref mut child) = self.child {
146 matches!(child.try_wait(), Ok(None))
147 } else {
148 false
149 }
150 }
151}
152
153impl Drop for NodeProcess {
154 fn drop(&mut self) {
155 if let Some(ref mut child) = self.child {
156 let _ = child.kill();
157 }
158 }
159}