hyperlight_js/sandbox/
js_sandbox.rs1use std::collections::HashMap;
17use std::fmt::Debug;
18use std::sync::Arc;
19
20use hyperlight_host::sandbox::snapshot::Snapshot;
21use hyperlight_host::{new_error, MultiUseSandbox, Result};
22use tracing::{instrument, Level};
23
24use super::loaded_js_sandbox::LoadedJSSandbox;
25use crate::sandbox::metrics::SandboxMetricsGuard;
26use crate::Script;
27
28pub struct JSSandbox {
30 pub(super) inner: MultiUseSandbox,
31 handlers: HashMap<String, Script>,
32 snapshot: Arc<Snapshot>,
35 _metric_guard: SandboxMetricsGuard<JSSandbox>,
37}
38
39impl JSSandbox {
40 #[instrument(err(Debug), skip(inner), level=Level::INFO)]
41 pub(super) fn new(mut inner: MultiUseSandbox) -> Result<Self> {
42 let snapshot = inner.snapshot()?;
43 Ok(Self {
44 inner,
45 handlers: HashMap::new(),
46 snapshot,
47 _metric_guard: SandboxMetricsGuard::new(),
48 })
49 }
50
51 pub(crate) fn from_loaded(
53 mut loaded: MultiUseSandbox,
54 snapshot: Arc<Snapshot>,
55 ) -> Result<Self> {
56 loaded.restore(snapshot.clone())?;
57 Ok(Self {
58 inner: loaded,
59 handlers: HashMap::new(),
60 snapshot,
61 _metric_guard: SandboxMetricsGuard::new(),
62 })
63 }
64
65 #[instrument(err(Debug), skip(self, script), level=Level::DEBUG)]
68 pub fn add_handler<F>(&mut self, function_name: F, script: Script) -> Result<()>
69 where
70 F: Into<String> + std::fmt::Debug,
71 {
72 let function_name = function_name.into();
73 if function_name.is_empty() {
74 return Err(new_error!("Handler name must not be empty"));
75 }
76 if self.handlers.contains_key(&function_name) {
77 return Err(new_error!(
78 "Handler already exists for function name: {}",
79 function_name
80 ));
81 }
82
83 self.handlers.insert(function_name, script);
84 Ok(())
85 }
86
87 #[instrument(err(Debug), skip(self), level=Level::DEBUG)]
89 pub fn remove_handler(&mut self, function_name: &str) -> Result<()> {
90 if function_name.is_empty() {
91 return Err(new_error!("Handler name must not be empty"));
92 }
93 match self.handlers.remove(function_name) {
94 Some(_) => Ok(()),
95 None => Err(new_error!(
96 "Handler does not exist for function name: {}",
97 function_name
98 )),
99 }
100 }
101
102 #[instrument(skip_all, level=Level::TRACE)]
104 pub fn clear_handlers(&mut self) {
105 self.handlers.clear();
106 }
107
108 pub fn poisoned(&self) -> bool {
115 self.inner.poisoned()
116 }
117
118 #[cfg(test)]
119 fn get_number_of_handlers(&self) -> usize {
120 self.handlers.len()
121 }
122
123 #[instrument(err(Debug), skip_all, level=Level::TRACE)]
125 pub fn get_loaded_sandbox(mut self) -> Result<LoadedJSSandbox> {
126 if self.handlers.is_empty() {
127 return Err(new_error!("No handlers have been added to the sandbox"));
128 }
129
130 let handlers = self.handlers.clone();
131 for (function_name, script) in handlers {
132 let content = script.content().to_owned();
133
134 let path = script
135 .base_path()
136 .map(|p| p.to_string_lossy().to_string())
137 .unwrap_or_default();
138 self.inner
139 .call::<()>("register_handler", (function_name, content, path))?;
140 }
141
142 LoadedJSSandbox::new(self.inner, self.snapshot)
143 }
144 #[cfg(feature = "crashdump")]
180 pub fn generate_crashdump(&self) -> Result<()> {
181 self.inner.generate_crashdump()
182 }
183}
184
185impl Debug for JSSandbox {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 f.debug_struct("JSSandbox")
188 .field("handlers", &self.handlers)
189 .finish()
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use crate::SandboxBuilder;
197
198 #[test]
199 fn test_add_handler() {
200 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
201 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
202 sandbox.add_handler("handler1", "script1".into()).unwrap();
203 sandbox.add_handler("handler2", "script2".into()).unwrap();
204
205 assert_eq!(sandbox.get_number_of_handlers(), 2);
206 }
207
208 #[test]
209 fn test_remove_handler() {
210 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
211 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
212 sandbox.add_handler("handler1", "script1".into()).unwrap();
213 sandbox.add_handler("handler2", "script2".into()).unwrap();
214
215 sandbox.remove_handler("handler1").unwrap();
216
217 assert_eq!(sandbox.get_number_of_handlers(), 1);
218 }
219
220 #[test]
221 fn test_clear_handlers() {
222 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
223 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
224 sandbox.add_handler("handler1", "script1".into()).unwrap();
225 sandbox.add_handler("handler2", "script2".into()).unwrap();
226
227 sandbox.clear_handlers();
228
229 assert_eq!(sandbox.get_number_of_handlers(), 0);
230 }
231
232 #[test]
233 fn test_get_loaded_sandbox() {
234 let proto_js_sandbox = SandboxBuilder::new().build().unwrap();
235 let mut sandbox = proto_js_sandbox.load_runtime().unwrap();
236 sandbox
237 .add_handler(
238 "handler1",
239 Script::from_content(
240 r#"function handler(event) {
241 event.request.uri = "/redirected.html";
242 return event
243 }"#,
244 ),
245 )
246 .unwrap();
247
248 let res = sandbox.get_loaded_sandbox();
249 assert!(res.is_ok());
250 }
251
252 #[test]
257 fn handler_with_export_in_string_literal() {
258 let handler = Script::from_content(
260 r#"
261 function handler(event) {
262 const xml = '<config mode="export">value</config>';
263 return { result: xml };
264 }
265 "#,
266 );
267
268 let proto = SandboxBuilder::new().build().unwrap();
269 let mut sandbox = proto.load_runtime().unwrap();
270 sandbox.add_handler("handler", handler).unwrap();
271 let mut loaded = sandbox.get_loaded_sandbox().unwrap();
272
273 let res = loaded
274 .handle_event("handler", "{}".to_string(), None)
275 .unwrap();
276 assert_eq!(
277 res,
278 r#"{"result":"<config mode=\"export\">value</config>"}"#
279 );
280 }
281
282 #[test]
283 fn handler_with_export_in_comment() {
284 let handler = Script::from_content(
286 r#"
287 function handler(event) {
288 // TODO: export this data to CSV
289 return { result: 42 };
290 }
291 "#,
292 );
293
294 let proto = SandboxBuilder::new().build().unwrap();
295 let mut sandbox = proto.load_runtime().unwrap();
296 sandbox.add_handler("handler", handler).unwrap();
297 let mut loaded = sandbox.get_loaded_sandbox().unwrap();
298
299 let res = loaded
300 .handle_event("handler", "{}".to_string(), None)
301 .unwrap();
302 assert_eq!(res, r#"{"result":42}"#);
303 }
304
305 #[test]
306 fn handler_with_export_in_identifier() {
307 let handler = Script::from_content(
309 r#"
310 function handler(event) {
311 const exportPath = "/tmp/out.csv";
312 return { result: exportPath };
313 }
314 "#,
315 );
316
317 let proto = SandboxBuilder::new().build().unwrap();
318 let mut sandbox = proto.load_runtime().unwrap();
319 sandbox.add_handler("handler", handler).unwrap();
320 let mut loaded = sandbox.get_loaded_sandbox().unwrap();
321
322 let res = loaded
323 .handle_event("handler", "{}".to_string(), None)
324 .unwrap();
325 assert_eq!(res, r#"{"result":"/tmp/out.csv"}"#);
326 }
327
328 #[test]
329 fn handler_with_explicit_export_is_not_doubled() {
330 let handler = Script::from_content(
332 r#"
333 function handler(event) {
334 return { result: "explicit" };
335 }
336 export { handler };
337 "#,
338 );
339
340 let proto = SandboxBuilder::new().build().unwrap();
341 let mut sandbox = proto.load_runtime().unwrap();
342 sandbox.add_handler("handler", handler).unwrap();
343 let mut loaded = sandbox.get_loaded_sandbox().unwrap();
344
345 let res = loaded
346 .handle_event("handler", "{}".to_string(), None)
347 .unwrap();
348 assert_eq!(res, r#"{"result":"explicit"}"#);
349 }
350
351 #[test]
352 fn handler_with_export_default_function() {
353 let handler = Script::from_content(
355 r#"
356 export function handler(event) {
357 return { result: "inline-export" };
358 }
359 "#,
360 );
361
362 let proto = SandboxBuilder::new().build().unwrap();
363 let mut sandbox = proto.load_runtime().unwrap();
364 sandbox.add_handler("handler", handler).unwrap();
365 let mut loaded = sandbox.get_loaded_sandbox().unwrap();
366
367 let res = loaded
368 .handle_event("handler", "{}".to_string(), None)
369 .unwrap();
370 assert_eq!(res, r#"{"result":"inline-export"}"#);
371 }
372}