use ::std::cell::RefCell;
use ::std::collections::HashMap;
use ::std::io::Read;
use ::std::path;
use bun_core::ZBox;
use bun_sys::fs as bun_fs;
use ::std::ptr::NonNull;
use ::std::sync::atomic::{AtomicU16, AtomicU64, Ordering};
use mozjs::conversions::unsafe_jsstr_to_string;
use mozjs::jsapi::*;
use mozjs::jsval::{
BooleanValue, DoubleValue, Int32Value, JSVal, NullValue, ObjectValue, StringValue,
UndefinedValue,
};
use mozjs::realm::AutoRealm;
use mozjs::rooted;
use mozjs::rust::wrappers2::{
JS_DefineFunction, JS_DefineProperty3, JS_NewPlainObject, NewArrayObject1,
};
use bun_uws_sys::app::App;
use bun_uws_sys::listen_socket::ListenSocket;
use bun_uws_sys::request::Request;
use bun_uws_sys::response::Response;
use bun_uws_sys::socket_context::BunSocketContextOptions;
use bun_uws_sys::web_socket::{NewWebSocket, RawWebSocket, WebSocketBehavior};
use bun_uws_sys::{Opcode, SendStatus, WebSocketUpgradeContext, uws_res};
use crate::gc_store::{gc_store_get, gc_store_insert, gc_store_remove};
pub unsafe fn install_bun_on_target(
cx: &mut mozjs::context::JSContext,
target: mozjs::rust::Handle<*mut JSObject>,
) {
rooted!(&in(cx) let bun_obj = JS_NewPlainObject(cx));
if bun_obj.get().is_null() {
return;
}
populate_bun_object(cx, bun_obj.handle());
JS_DefineProperty3(
cx,
target,
c"Bun".as_ptr(),
bun_obj.handle(),
JSPROP_ENUMERATE as u32,
);
JS_DefineProperty3(
cx,
target,
c"Bao".as_ptr(),
bun_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
unsafe fn populate_bun_object(
cx: &mut mozjs::context::JSContext,
bun_obj: mozjs::rust::Handle<*mut JSObject>,
) {
let version_str = JS_NewStringCopyZ(cx.raw_cx(), c"0.1.0".as_ptr());
if !version_str.is_null() {
rooted!(&in(cx) let ver_val = StringValue(&*version_str));
JS_DefineProperty(
cx.raw_cx(),
bun_obj.into(),
c"version".as_ptr(),
ver_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
JS_DefineProperty1(
cx.raw_cx(),
bun_obj.into(),
c"env".as_ptr(),
::std::option::Option::Some(bun_env_getter),
None,
JSPROP_ENUMERATE as u32,
);
{
let args: Vec<::std::string::String> = ::std::env::args().collect();
rooted!(&in(cx) let argv_arr = NewArrayObject1(cx, args.len()));
if !argv_arr.get().is_null() {
for (i, arg) in args.iter().enumerate() {
let c_arg = ZBox::from_bytes(arg.as_bytes());
let js_str = JS_NewStringCopyZ(cx.raw_cx(), c_arg.as_ptr());
if !js_str.is_null() {
rooted!(&in(cx) let v = StringValue(&*js_str));
JS_DefineElement(
cx.raw_cx(),
argv_arr.handle().into(),
i as u32,
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
JS_DefineProperty3(
cx,
bun_obj,
c"argv".as_ptr(),
argv_arr.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
JS_DefineFunction(
cx,
bun_obj,
c"file".as_ptr(),
Some(bun_file),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"write".as_ptr(),
Some(bun_write),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"readFile".as_ptr(),
Some(bun_read_file),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"serve".as_ptr(),
Some(bun_serve),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"spawn".as_ptr(),
Some(bun_spawn),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"cwd".as_ptr(),
Some(bun_cwd),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"gc".as_ptr(),
Some(bun_gc),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"sleep".as_ptr(),
Some(bun_sleep),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"which".as_ptr(),
Some(bun_which),
1,
JSPROP_ENUMERATE as u32,
);
unsafe {
crate::bun_inspect_api::install(cx, bun_obj);
}
JS_DefineFunction(
cx,
bun_obj,
c"resolve".as_ptr(),
Some(bun_resolve),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"build".as_ptr(),
Some(bun_build),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"test".as_ptr(),
Some(bun_test),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"testRun".as_ptr(),
Some(test_run),
0,
JSPROP_ENUMERATE as u32,
);
{
rooted!(&in(cx) let mut read_val = UndefinedValue());
let _ok = JS_GetProperty(
cx.raw_cx(),
bun_obj.into(),
c"readFile".as_ptr(),
read_val.handle_mut().into(),
);
JS_DefineProperty(
cx.raw_cx(),
bun_obj.into(),
c"read".as_ptr(),
read_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
JS_DefineFunction(
cx,
bun_obj,
c"exit".as_ptr(),
Some(bun_exit),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"sleepSync".as_ptr(),
Some(bun_sleep_sync),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"nanoseconds".as_ptr(),
Some(bun_nanoseconds),
0,
JSPROP_ENUMERATE as u32,
);
{
let rev_str = JS_NewStringCopyZ(cx.raw_cx(), c"0.1.0".as_ptr());
if !rev_str.is_null() {
rooted!(&in(cx) let rv = StringValue(&*rev_str));
JS_DefineProperty(
cx.raw_cx(),
bun_obj.into(),
c"revision".as_ptr(),
rv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
{
let argv: Vec<::std::string::String> = ::std::env::args().collect();
let main_path: ::std::path::PathBuf = argv
.get(2)
.filter(|p| {
::std::path::Path::new(p).is_file()
})
.map(|p| {
if ::std::path::Path::new(p).is_absolute() {
::std::path::PathBuf::from(p)
} else {
::std::env::current_dir().unwrap_or_default().join(p)
}
})
.unwrap_or_else(|| {
crate::require::get_require_dir()
.unwrap_or_else(|| ::std::env::current_dir().unwrap_or_default())
});
let c_main = ZBox::from_vec(main_path.to_string_lossy().into_owned().into_bytes());
let js_str = JS_NewStringCopyZ(cx.raw_cx(), c_main.as_ptr());
if !js_str.is_null() {
rooted!(&in(cx) let mv = StringValue(&*js_str));
JS_DefineProperty(
cx.raw_cx(),
bun_obj.into(),
c"main".as_ptr(),
mv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
unsafe {
crate::bun_hash_api::install(cx, bun_obj);
}
for (name, native, nargs) in [
("gzipSync", crate::node_zlib::zlib_gzip_sync as unsafe extern "C" fn(_, _, _) -> bool, 2),
(
"gunzipSync",
crate::node_zlib::zlib_gunzip_sync,
1,
),
(
"deflateSync",
crate::node_zlib::zlib_deflate_sync,
2,
),
(
"inflateSync",
crate::node_zlib::zlib_inflate_sync,
1,
),
] {
JS_DefineFunction(
cx,
bun_obj,
ZBox::from_bytes(name.as_bytes()).as_ptr(),
Some(native),
nargs,
JSPROP_ENUMERATE as u32,
);
}
{
let ctor_fn = JS_NewFunction(
cx.raw_cx(),
Some(bun_crypto_hasher_ctor),
1,
JSFUN_CONSTRUCTOR as u32,
c"CryptoHasher".as_ptr(),
);
if !ctor_fn.is_null() {
let ctor_obj = JS_GetFunctionObject(ctor_fn);
rooted!(&in(cx) let ctor_root = ctor_obj);
JS_DefineProperty3(
cx,
bun_obj,
c"CryptoHasher".as_ptr(),
ctor_root.handle(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx) let mut sha_val = UndefinedValue());
let _ok = JS_GetProperty(
cx.raw_cx(),
bun_obj.into(),
c"CryptoHasher".as_ptr(),
sha_val.handle_mut().into(),
);
JS_DefineProperty(
cx.raw_cx(),
bun_obj.into(),
c"SHA".as_ptr(),
sha_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
JS_DefineFunction(
cx,
bun_obj,
c"gzip".as_ptr(),
Some(bun_gzip),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"deflate".as_ptr(),
Some(bun_deflate),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"inflate".as_ptr(),
Some(bun_inflate),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"gunzip".as_ptr(),
Some(bun_gunzip),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"fileURLToPath".as_ptr(),
Some(bun_file_url_to_path),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"pathToFileURL".as_ptr(),
Some(bun_path_to_file_url),
1,
JSPROP_ENUMERATE as u32,
);
install_bun_semver(cx, bun_obj);
JS_DefineFunction(
cx,
bun_obj,
c"escapeHTML".as_ptr(),
Some(bun_escape_html),
1,
JSPROP_ENUMERATE as u32,
);
unsafe {
crate::bun_mime_api::install(cx, bun_obj);
}
unsafe {
crate::bun_serde_api::install(cx, bun_obj);
}
unsafe {
crate::bun_glob_api::install(cx, bun_obj);
}
unsafe {
crate::bun_spawn_sync::install(cx, bun_obj);
}
unsafe {
crate::bun_util_api::install(cx, bun_obj);
}
{
let stdin_ptr = make_bun_file_for_fd(cx, 0);
rooted!(&in(cx) let stdin_file = stdin_ptr);
if !stdin_file.get().is_null() {
JS_DefineProperty3(
cx,
bun_obj,
c"stdin".as_ptr(),
stdin_file.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
{
let stdout_ptr = make_bun_file_for_fd(cx, 1);
rooted!(&in(cx) let stdout_file = stdout_ptr);
if !stdout_file.get().is_null() {
JS_DefineProperty3(
cx,
bun_obj,
c"stdout".as_ptr(),
stdout_file.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
{
let stderr_ptr = make_bun_file_for_fd(cx, 2);
rooted!(&in(cx) let stderr_file = stderr_ptr);
if !stderr_file.get().is_null() {
JS_DefineProperty3(
cx,
bun_obj,
c"stderr".as_ptr(),
stderr_file.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
JS_DefineFunction(
cx,
bun_obj,
c"deepLink".as_ptr(),
Some(bun_deep_link),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"openInNewTab".as_ptr(),
Some(bun_open_in_new_tab),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
bun_obj,
c"concatArrayBuffers".as_ptr(),
Some(bun_concat_array_buffers),
3,
JSPROP_ENUMERATE as u32,
);
crate::bao_browser_global::install_bao_browser_on_bun(cx, bun_obj);
crate::workflow_host_global::install_workflow_host_on_bun(cx, bun_obj);
unsafe {
crate::bun_listen::install(cx, bun_obj);
}
unsafe {
crate::bun_udp::install(cx, bun_obj);
}
unsafe {
crate::bun_shell::install_bun_shell(cx, bun_obj);
}
unsafe {
crate::bun_password::install(cx, bun_obj);
}
}
pub fn install_bun_global(
cx: &mut mozjs::context::JSContext,
global: mozjs::rust::Handle<*mut JSObject>,
) {
unsafe {
rooted!(&in(cx) let bun_obj = JS_NewPlainObject(cx));
if bun_obj.get().is_null() {
return;
}
populate_bun_object(cx, bun_obj.handle());
JS_DefineProperty3(
cx,
global,
c"Bun".as_ptr(),
bun_obj.handle(),
JSPROP_ENUMERATE as u32,
);
JS_DefineProperty3(
cx,
global,
c"Bao".as_ptr(),
bun_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
pub unsafe fn install_process_on_target(
cx: &mut mozjs::context::JSContext,
target: mozjs::rust::Handle<*mut JSObject>,
global: mozjs::rust::Handle<*mut JSObject>,
) {
rooted!(&in(cx) let proc_obj = JS_NewPlainObject(cx));
if proc_obj.get().is_null() {
return;
}
populate_process_object(cx, proc_obj.handle(), target, global);
JS_DefineProperty3(
cx,
target,
c"process".as_ptr(),
proc_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
unsafe fn populate_process_object(
cx: &mut mozjs::context::JSContext,
proc_obj: mozjs::rust::Handle<*mut JSObject>,
target: mozjs::rust::Handle<*mut JSObject>,
_global: mozjs::rust::Handle<*mut JSObject>,
) {
let arch_cstr = ZBox::from_bytes(::std::env::consts::ARCH.as_bytes());
let arch_str = JS_NewStringCopyZ(cx.raw_cx(), arch_cstr.as_ptr());
if !arch_str.is_null() {
rooted!(&in(cx) let arch_val = StringValue(&*arch_str));
JS_DefineProperty(
cx.raw_cx(),
proc_obj.into(),
c"arch".as_ptr(),
arch_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let plat_cstr = ZBox::from_bytes(::std::env::consts::OS.as_bytes());
let platform_str = JS_NewStringCopyZ(cx.raw_cx(), plat_cstr.as_ptr());
if !platform_str.is_null() {
rooted!(&in(cx) let plat_val = StringValue(&*platform_str));
JS_DefineProperty(
cx.raw_cx(),
proc_obj.into(),
c"platform".as_ptr(),
plat_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
JS_DefineFunction(
cx,
proc_obj,
c"cwd".as_ptr(),
::std::option::Option::Some(process_cwd),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"binding".as_ptr(),
::std::option::Option::Some(process_binding),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"_linkedBinding".as_ptr(),
::std::option::Option::Some(process_binding),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"exit".as_ptr(),
::std::option::Option::Some(process_exit),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineProperty1(
cx.raw_cx(),
proc_obj.into(),
c"exitCode".as_ptr(),
::std::option::Option::Some(process_exitcode_get),
::std::option::Option::Some(process_exitcode_set),
JSPROP_ENUMERATE as u32,
);
{
let args: Vec<::std::string::String> = ::std::env::args().collect();
rooted!(&in(cx) let argv_arr = NewArrayObject1(cx, args.len()));
if !argv_arr.get().is_null() {
for (i, arg) in args.iter().enumerate() {
let c_arg = ZBox::from_bytes(arg.as_bytes());
let js_str = JS_NewStringCopyZ(cx.raw_cx(), c_arg.as_ptr());
if !js_str.is_null() {
rooted!(&in(cx) let v = StringValue(&*js_str));
JS_DefineElement(
cx.raw_cx(),
argv_arr.handle().into(),
i as u32,
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
JS_DefineProperty3(
cx,
proc_obj,
c"argv".as_ptr(),
argv_arr.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
{
JS_DefineFunction(cx, target, c"__bao_setEnv".as_ptr(), Some(set_env_fn), 2, 0);
JS_DefineFunction(cx, target, c"__bao_delEnv".as_ptr(), Some(del_env_fn), 1, 0);
rooted!(&in(cx) let env_target = JS_NewPlainObject(cx));
if !env_target.get().is_null() {
for (key, value) in ::std::env::vars() {
let c_key = ZBox::from_bytes(key.as_bytes());
let c_val = ZBox::from_bytes(value.as_bytes());
let val_str = JS_NewStringCopyZ(cx.raw_cx(), c_val.as_ptr());
if !val_str.is_null() {
rooted!(&in(cx) let v = StringValue(&*val_str));
JS_DefineProperty(
cx.raw_cx(),
env_target.handle().into(),
c_key.as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let proxy_src = r#"(__bao_envTarget,__bao_setEnv,__bao_delEnv)=>new Proxy(__bao_envTarget,{
set(t,k,v){t[k]=v;try{__bao_setEnv(String(k),String(v))}catch(e){}return true},
deleteProperty(t,k){delete t[k];try{__bao_delEnv(String(k))}catch(e){}return true},
get(t,k){const v=t[k];return typeof v==='string'?v:undefined},
has(t,k){return k in t},
ownKeys(t){return Object.keys(t)},
getOwnPropertyDescriptor(t,k){return k in t?{configurable:true,enumerable:true,value:t[k]}:undefined}
})"#;
let mut src = mozjs::rust::transform_str_to_source_text(proxy_src);
let opts = mozjs::glue::NewCompileOptions(cx.raw_cx(), c"<env>".as_ptr(), 1);
if !opts.is_null() {
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let ok = mozjs_sys::jsapi::JS::Evaluate2(cx.raw_cx(), opts, &mut src, rval_h);
libc::free(opts as *mut _);
if ok && rval.is_object() {
rooted!(&in(cx) let handler_fn = rval.to_object());
rooted!(&in(cx) let fn_val = ObjectValue(handler_fn.get()));
let mut set_env_val = UndefinedValue();
let set_env_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut set_env_val,
};
JS_GetProperty(
cx.raw_cx(),
target.into(),
c"__bao_setEnv".as_ptr(),
set_env_h,
);
let mut del_env_val = UndefinedValue();
let del_env_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut del_env_val,
};
JS_GetProperty(
cx.raw_cx(),
target.into(),
c"__bao_delEnv".as_ptr(),
del_env_h,
);
rooted!(&in(cx) let args_val = ObjectValue(env_target.get()));
rooted!(&in(cx) let set_env_root = set_env_val);
rooted!(&in(cx) let del_env_root = del_env_val);
let args = [
args_val.handle().get(),
set_env_root.handle().get(),
del_env_root.handle().get(),
];
let args_arr = HandleValueArray {
length_: 3,
elements_: args.as_ptr(),
};
rooted!(&in(cx) let null_obj = ::std::ptr::null_mut::<JSObject>());
let mut ret = UndefinedValue();
let ret_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ret,
};
let ok2 = JS_CallFunctionValue(
cx.raw_cx(),
null_obj.handle().into(),
fn_val.handle().into(),
&args_arr,
ret_h,
);
if ok2 && ret.is_object() {
rooted!(&in(cx) let env_proxy = ret.to_object());
JS_DefineProperty3(
cx,
proc_obj,
c"env".as_ptr(),
env_proxy.handle(),
JSPROP_ENUMERATE as u32,
);
} else {
JS_DefineProperty3(
cx,
proc_obj,
c"env".as_ptr(),
env_target.handle(),
JSPROP_ENUMERATE as u32,
);
}
} else {
JS_DefineProperty3(
cx,
proc_obj,
c"env".as_ptr(),
env_target.handle(),
JSPROP_ENUMERATE as u32,
);
}
} else {
JS_DefineProperty3(
cx,
proc_obj,
c"env".as_ptr(),
env_target.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
}
{
let ver_str = JS_NewStringCopyZ(cx.raw_cx(), c"v18.0.0".as_ptr());
if !ver_str.is_null() {
rooted!(&in(cx) let v = StringValue(&*ver_str));
JS_DefineProperty(
cx.raw_cx(),
proc_obj.into(),
c"version".as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
{
rooted!(&in(cx) let ver_obj = JS_NewPlainObject(cx));
if !ver_obj.get().is_null() {
let node_ver = JS_NewStringCopyZ(cx.raw_cx(), c"18.0.0".as_ptr());
if !node_ver.is_null() {
rooted!(&in(cx) let v = StringValue(&*node_ver));
JS_DefineProperty(
cx.raw_cx(),
ver_obj.handle().into(),
c"node".as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let bao_ver = JS_NewStringCopyZ(cx.raw_cx(), c"0.1.0".as_ptr());
if !bao_ver.is_null() {
rooted!(&in(cx) let v = StringValue(&*bao_ver));
JS_DefineProperty(
cx.raw_cx(),
ver_obj.handle().into(),
c"bao".as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let sm_ver = JS_NewStringCopyZ(cx.raw_cx(), c"115.0".as_ptr());
if !sm_ver.is_null() {
rooted!(&in(cx) let v = StringValue(&*sm_ver));
JS_DefineProperty(
cx.raw_cx(),
ver_obj.handle().into(),
c"spidermonkey".as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let rust_ver = JS_NewStringCopyZ(cx.raw_cx(), c"1.80.0".as_ptr());
if !rust_ver.is_null() {
rooted!(&in(cx) let v = StringValue(&*rust_ver));
JS_DefineProperty(
cx.raw_cx(),
ver_obj.handle().into(),
c"rust".as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let bun_alias = JS_NewStringCopyZ(cx.raw_cx(), c"0.1.0".as_ptr());
if !bun_alias.is_null() {
rooted!(&in(cx) let v = StringValue(&*bun_alias));
JS_DefineProperty(
cx.raw_cx(),
ver_obj.handle().into(),
c"bun".as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let openssl_ver = JS_NewStringCopyZ(cx.raw_cx(), c"3.0.0".as_ptr());
if !openssl_ver.is_null() {
rooted!(&in(cx) let v = StringValue(&*openssl_ver));
JS_DefineProperty(
cx.raw_cx(),
ver_obj.handle().into(),
c"openssl".as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
JS_DefineProperty3(
cx,
proc_obj,
c"versions".as_ptr(),
ver_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
{
rooted!(&in(cx) let stdout_obj = JS_NewPlainObject(cx));
if !stdout_obj.get().is_null() {
let fd_val = Int32Value(1);
rooted!(&in(cx) let fd = fd_val);
JS_DefineProperty(
cx.raw_cx(),
stdout_obj.handle().into(),
c"fd".as_ptr(),
fd.handle().into(),
JSPROP_ENUMERATE as u32,
);
let is_tty = libc::isatty(1) == 1;
let tty_val = BooleanValue(is_tty);
rooted!(&in(cx) let tv = tty_val);
JS_DefineProperty(
cx.raw_cx(),
stdout_obj.handle().into(),
c"isTTY".as_ptr(),
tv.handle().into(),
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
stdout_obj.handle(),
c"write".as_ptr(),
::std::option::Option::Some(process_stdout_write),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineProperty3(
cx,
proc_obj,
c"stdout".as_ptr(),
stdout_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
{
rooted!(&in(cx) let stderr_obj = JS_NewPlainObject(cx));
if !stderr_obj.get().is_null() {
let fd_val = Int32Value(2);
rooted!(&in(cx) let fd = fd_val);
JS_DefineProperty(
cx.raw_cx(),
stderr_obj.handle().into(),
c"fd".as_ptr(),
fd.handle().into(),
JSPROP_ENUMERATE as u32,
);
let is_tty = libc::isatty(2) == 1;
let tty_val = BooleanValue(is_tty);
rooted!(&in(cx) let tv = tty_val);
JS_DefineProperty(
cx.raw_cx(),
stderr_obj.handle().into(),
c"isTTY".as_ptr(),
tv.handle().into(),
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
stderr_obj.handle(),
c"write".as_ptr(),
::std::option::Option::Some(process_stderr_write),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineProperty3(
cx,
proc_obj,
c"stderr".as_ptr(),
stderr_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
{
rooted!(&in(cx) let stdin_obj = JS_NewPlainObject(cx));
if !stdin_obj.get().is_null() {
let fd_val = Int32Value(0);
rooted!(&in(cx) let fd = fd_val);
JS_DefineProperty(
cx.raw_cx(),
stdin_obj.handle().into(),
c"fd".as_ptr(),
fd.handle().into(),
JSPROP_ENUMERATE as u32,
);
let is_tty = libc::isatty(0) == 1;
let tty_val = BooleanValue(is_tty);
rooted!(&in(cx) let tv = tty_val);
JS_DefineProperty(
cx.raw_cx(),
stdin_obj.handle().into(),
c"isTTY".as_ptr(),
tv.handle().into(),
JSPROP_ENUMERATE as u32,
);
let bool_true = BooleanValue(true);
rooted!(&in(cx) let rv = bool_true);
JS_DefineProperty(
cx.raw_cx(),
stdin_obj.handle().into(),
c"readable".as_ptr(),
rv.handle().into(),
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
stdin_obj.handle(),
c"read".as_ptr(),
Some(stdin_read),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
stdin_obj.handle(),
c"on".as_ptr(),
Some(stdin_on),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
stdin_obj.handle(),
c"pipe".as_ptr(),
Some(stdin_pipe),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
stdin_obj.handle(),
c"resume".as_ptr(),
Some(stdin_resume),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
stdin_obj.handle(),
c"pause".as_ptr(),
Some(stdin_pause),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
stdin_obj.handle(),
c"destroy".as_ptr(),
Some(stdin_destroy),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineProperty3(
cx,
proc_obj,
c"stdin".as_ptr(),
stdin_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
JS_DefineFunction(
cx,
proc_obj,
c"on".as_ptr(),
Some(crate::node_events::ee_on),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"nextTick".as_ptr(),
::std::option::Option::Some(process_next_tick),
1,
JSPROP_ENUMERATE as u32,
);
{
let pid_val = Int32Value(libc::getpid() as i32);
rooted!(&in(cx) let pid = pid_val);
JS_DefineProperty(
cx.raw_cx(),
proc_obj.into(),
c"pid".as_ptr(),
pid.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
{
let ppid = libc::getppid();
let ppid_val = Int32Value(ppid as i32);
rooted!(&in(cx) let p = ppid_val);
JS_DefineProperty(
cx.raw_cx(),
proc_obj.into(),
c"ppid".as_ptr(),
p.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
{
let title_str = JS_NewStringCopyZ(cx.raw_cx(), c"bao".as_ptr());
if !title_str.is_null() {
rooted!(&in(cx) let v = StringValue(&*title_str));
JS_DefineProperty(
cx.raw_cx(),
proc_obj.into(),
c"title".as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let hrtime_fn = JS_DefineFunction(
cx,
proc_obj,
c"hrtime".as_ptr(),
::std::option::Option::Some(process_hrtime),
0,
JSPROP_ENUMERATE as u32,
);
if !hrtime_fn.is_null() {
let hrtime_obj = JS_GetFunctionObject(hrtime_fn);
let bigint_fn = JS_NewFunction(cx.raw_cx(), Some(hrtime_bigint), 0, 0, c"bigint".as_ptr());
if !bigint_fn.is_null() {
let bigint_obj = JS_GetFunctionObject(bigint_fn);
rooted!(&in(cx) let hrtime_r = hrtime_obj);
rooted!(&in(cx) let bigint_val = ObjectValue(bigint_obj));
JS_DefineProperty(
cx.raw_cx(),
hrtime_r.handle().into(),
c"bigint".as_ptr(),
bigint_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
JS_DefineFunction(
cx,
proc_obj,
c"uptime".as_ptr(),
::std::option::Option::Some(process_uptime),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"chdir".as_ptr(),
::std::option::Option::Some(process_chdir),
1,
JSPROP_ENUMERATE as u32,
);
{
let mu_fn = JS_DefineFunction(
cx,
proc_obj,
c"memoryUsage".as_ptr(),
::std::option::Option::Some(process_memory_usage),
0,
JSPROP_ENUMERATE as u32,
);
if !mu_fn.is_null() {
let mu_obj = JS_GetFunctionObject(mu_fn);
let rss_fn = JS_NewFunction(
cx.raw_cx(),
::std::option::Option::Some(process_memory_usage_rss),
0,
0,
c"rss".as_ptr(),
);
if !rss_fn.is_null() {
let rss_obj = JS_GetFunctionObject(rss_fn);
rooted!(&in(cx) let mu_r = mu_obj);
rooted!(&in(cx) let rss_val = ObjectValue(rss_obj));
JS_DefineProperty(
cx.raw_cx(),
mu_r.handle().into(),
c"rss".as_ptr(),
rss_val.handle().into(),
0,
);
}
}
}
JS_DefineFunction(
cx,
proc_obj,
c"kill".as_ptr(),
::std::option::Option::Some(process_kill),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"umask".as_ptr(),
::std::option::Option::Some(process_umask),
0,
JSPROP_ENUMERATE as u32,
);
{
rooted!(&in(cx) let config_obj = JS_NewPlainObject(cx));
if !config_obj.get().is_null() {
rooted!(&in(cx) let v_obj = JS_NewPlainObject(cx));
if !v_obj.get().is_null() {
let v_val = ObjectValue(v_obj.get());
rooted!(&in(cx) let v_r = v_val);
JS_DefineProperty(
cx.raw_cx(),
config_obj.handle().into(),
c"variables".as_ptr(),
v_r.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
JS_DefineProperty3(
cx,
proc_obj,
c"config".as_ptr(),
config_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
{
rooted!(&in(cx) let release_obj = JS_NewPlainObject(cx));
if !release_obj.get().is_null() {
let s = ZBox::from_bytes("bao".as_bytes());
{
let js_str = JS_NewStringCopyZ(cx.raw_cx(), s.as_ptr());
if !js_str.is_null() {
rooted!(&in(cx) let rv = StringValue(&*js_str));
JS_DefineProperty(
cx.raw_cx(),
release_obj.handle().into(),
c"name".as_ptr(),
rv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let su_str =
JS_NewStringCopyZ(cx.raw_cx(), c"https://github.com/nickelpack/bao".as_ptr());
if !su_str.is_null() {
rooted!(&in(cx) let su_val = StringValue(&*su_str));
JS_DefineProperty(
cx.raw_cx(),
release_obj.handle().into(),
c"sourceUrl".as_ptr(),
su_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
JS_DefineProperty3(
cx,
proc_obj,
c"release".as_ptr(),
release_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
{
let args: Vec<::std::string::String> = ::std::env::args().collect();
if !args.is_empty() {
let c_arg = ZBox::from_bytes(args[0].as_bytes());
let js_str = JS_NewStringCopyZ(cx.raw_cx(), c_arg.as_ptr());
if !js_str.is_null() {
rooted!(&in(cx) let v = StringValue(&*js_str));
JS_DefineProperty(
cx.raw_cx(),
proc_obj.into(),
c"argv0".as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
}
{
let exec_path = ::std::env::current_exe().unwrap_or_default();
let c_path = ZBox::from_vec(exec_path.to_string_lossy().into_owned().into_bytes());
{
let js_str = JS_NewStringCopyZ(cx.raw_cx(), c_path.as_ptr());
if !js_str.is_null() {
rooted!(&in(cx) let v = StringValue(&*js_str));
JS_DefineProperty(
cx.raw_cx(),
proc_obj.into(),
c"execPath".as_ptr(),
v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
}
JS_DefineFunction(
cx,
proc_obj,
c"on".as_ptr(),
Some(crate::node_events::ee_on),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"once".as_ptr(),
Some(crate::node_events::ee_once),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"addListener".as_ptr(),
Some(crate::node_events::ee_on),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"emit".as_ptr(),
Some(crate::node_events::ee_emit),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"off".as_ptr(),
Some(crate::node_events::ee_off),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"removeListener".as_ptr(),
Some(crate::node_events::ee_off),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
proc_obj,
c"removeAllListeners".as_ptr(),
Some(crate::node_events::ee_remove_all),
0,
JSPROP_ENUMERATE as u32,
);
let proc_ptr = proc_obj.get();
if !proc_ptr.is_null() {
crate::require::cache_builtin(cx, "process", proc_ptr);
}
}
pub fn install_process_global(
cx: &mut mozjs::context::JSContext,
global: mozjs::rust::Handle<*mut JSObject>,
) {
unsafe {
rooted!(&in(cx) let proc_obj = JS_NewPlainObject(cx));
if proc_obj.get().is_null() {
return;
}
populate_process_object(cx, proc_obj.handle(), global, global);
JS_DefineProperty3(
cx,
global,
c"process".as_ptr(),
proc_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
thread_local! {
static SPAWNED_PROCS: RefCell<Vec<Box<::std::process::Child>>> = const { RefCell::new(Vec::new()) };
static SPAWN_WATCHES: RefCell<Vec<i32>> = const { RefCell::new(Vec::new()) };
}
pub fn spawn_watch_loop_alive() -> bool {
SPAWN_WATCHES.with(|w| !w.borrow().is_empty())
}
pub fn spawn_watch_pump_all(raw_cx: *mut JSContext) {
let snapshot: Vec<i32> = SPAWN_WATCHES.with(|w| w.borrow().clone());
for pid in snapshot {
let Some((code, signal)) = crate::node_child_process::poll_exit_info(pid) else {
continue;
};
SPAWN_WATCHES.with(|w| w.borrow_mut().retain(|&p| p != pid));
unsafe { fire_spawn_watch_finish(raw_cx, pid, code, signal) };
}
}
unsafe fn fire_spawn_watch_finish(raw_cx: *mut JSContext, pid: i32, code: i32, signal: i32) {
unsafe {
let key = format!("{}", pid);
let Some(finish) = crate::gc_store::gc_store_get_ns(raw_cx, "spawn-watch", &key) else {
return;
};
if finish.is_null() {
return;
}
let global = CurrentGlobalOrNull(raw_cx);
if global.is_null() {
return;
}
let cx_ref = &mut mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(raw_cx));
rooted!(&in(cx_ref) let global_r = global);
rooted!(&in(cx_ref) let fval = ObjectValue(finish));
rooted!(&in(cx_ref) let code_v = Int32Value(code));
rooted!(&in(cx_ref) let sig_v = Int32Value(signal));
let elems = [*code_v.handle(), *sig_v.handle()];
let args = HandleValueArray {
length_: elems.len(),
elements_: elems.as_ptr(),
};
let mut rval = UndefinedValue();
let ok = JS_CallFunctionValue(
raw_cx,
global_r.handle().into(),
fval.handle().into(),
&args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
if !ok {
JS_ClearPendingException(raw_cx);
}
crate::gc_store::gc_store_remove_ns(raw_cx, "spawn-watch", &key);
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn subproc_watch_exit(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let Some(pid) = (unsafe { get_pid_from_this(cx, &args) }) else {
args.rval().set(BooleanValue(false));
return true;
};
if argc == 0 || !(*args.get(0).ptr).is_object() {
args.rval().set(BooleanValue(false));
return true;
}
let finish = (*args.get(0).ptr).to_object();
crate::gc_store::gc_store_insert_ns(cx, "spawn-watch", &format!("{}", pid), finish);
SPAWN_WATCHES.with(|w| {
let mut watches = w.borrow_mut();
if !watches.contains(&pid) {
watches.push(pid);
}
});
args.rval().set(BooleanValue(true));
true
}
unsafe fn js_string_from_child_bytes(
cx: *mut JSContext,
bytes: &[u8],
lossy_text: bool,
) -> *mut JSString {
unsafe {
let units: Vec<u16> = if lossy_text {
String::from_utf8_lossy(bytes).encode_utf16().collect()
} else {
match ::std::str::from_utf8(bytes) {
Ok(s) => s.encode_utf16().collect(),
Err(_) => bytes.iter().map(|&b| b as u16).collect(),
}
};
if units.is_empty() {
return JS_NewStringCopyN(cx, c"".as_ptr(), 0);
}
JS_NewUCStringCopyN(cx, units.as_ptr(), units.len())
}
}
unsafe fn read_string_array_from_obj(
cx: *mut JSContext,
arr_h: Handle<*mut JSObject>,
) -> Vec<String> {
unsafe {
let mut len_val = UndefinedValue();
JS_GetProperty(
cx,
arr_h,
c"length".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut len_val,
},
);
let len = if len_val.is_int32() {
len_val.to_int32().max(0) as u32
} else {
0
};
let mut result = Vec::with_capacity(len as usize);
for i in 0..len {
let mut elem = UndefinedValue();
JS_GetElement(
cx,
arr_h,
i,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut elem,
},
);
if elem.is_string() {
result.push(crate::js_to_rust_string(cx, elem));
} else if elem.is_number() {
result.push(format!("{}", elem.to_double()));
}
}
result
}
}
static SERVE_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
struct TestCase {
name: String,
callback_key: String,
}
static TEST_CB_COUNTER: AtomicU64 = AtomicU64::new(0);
thread_local! {
static TEST_REGISTRY: RefCell<Vec<TestCase>> = const { RefCell::new(Vec::new()) };
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_spawn(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(
cx,
c"Bun.spawn() requires a command array or an options object".as_ptr(),
);
return false;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let first_val = *args.get(0).ptr;
let cmd_args: Vec<String>;
let cmd: String;
let opts_val: Value;
if first_val.is_object() {
rooted!(&in(cx_ref) let first_v = first_val);
let first_obj = first_val.to_object();
rooted!(&in(cx_ref) let first_r = first_obj);
let mut is_arr = false;
let checked = IsArrayObject(cx, first_v.handle().into(), &mut is_arr);
if checked && is_arr {
let arr = read_string_array_from_obj(cx, first_r.handle().into());
if arr.is_empty() {
JS_ReportErrorUTF8(
cx,
c"Bun.spawn() command array must not be empty".as_ptr(),
);
return false;
}
let mut iter = arr.into_iter();
cmd = iter.next().unwrap_or_else(|| "echo".to_string());
cmd_args = iter.collect();
opts_val = if argc > 1 && (*args.get(1).ptr).is_object() {
*args.get(1).ptr
} else {
UndefinedValue()
};
} else {
opts_val = first_val;
let cmd_array = get_string_array_prop(cx, first_r.handle().into(), c"cmd".as_ptr());
if !cmd_array.is_empty() {
let mut iter = cmd_array.into_iter();
cmd = iter.next().unwrap_or_else(|| "echo".to_string());
cmd_args = iter.collect();
} else {
cmd = get_string_prop(cx, first_r.handle().into(), c"cmd".as_ptr())
.unwrap_or_else(|| "echo".to_string());
cmd_args = get_string_array_prop(cx, first_r.handle().into(), c"args".as_ptr());
}
}
} else if first_val.is_string() {
cmd = crate::js_to_rust_string(cx, first_val);
opts_val = if argc > 1 && (*args.get(1).ptr).is_object() {
*args.get(1).ptr
} else {
UndefinedValue()
};
if opts_val.is_object() {
rooted!(&in(cx_ref) let o = opts_val.to_object());
cmd_args = get_string_array_prop(cx, o.handle().into(), c"args".as_ptr());
} else {
cmd_args = Vec::new();
}
} else {
JS_ReportErrorUTF8(
cx,
c"Bun.spawn() requires a command array or an options object".as_ptr(),
);
return false;
}
if cmd.is_empty() {
JS_ReportErrorUTF8(cx, c"Bun.spawn() requires a non-empty command".as_ptr());
return false;
}
let has_opts = opts_val.is_object();
let cwd = if has_opts {
rooted!(&in(cx_ref) let o = opts_val.to_object());
get_string_prop(cx, o.handle().into(), c"cwd".as_ptr())
} else {
None
};
let stdio_of = |name: &str, default_piped: bool| -> ::std::process::Stdio {
unsafe {
if !has_opts {
return if default_piped {
::std::process::Stdio::piped()
} else {
::std::process::Stdio::null()
};
}
rooted!(&in(cx_ref) let o = opts_val.to_object());
let name_c = ZBox::from_bytes(name.as_bytes());
let mode = get_string_prop(cx, o.handle().into(), name_c.as_ptr());
match mode.as_deref() {
Some("pipe") => ::std::process::Stdio::piped(),
Some("inherit") => ::std::process::Stdio::inherit(),
Some("null") | Some("ignore") => ::std::process::Stdio::null(),
_ => if default_piped {
::std::process::Stdio::piped()
} else {
::std::process::Stdio::null()
},
}
}
};
let stdin_mode = stdio_of("stdin", false);
let stdout_mode = stdio_of("stdout", true);
let stderr_mode = stdio_of("stderr", true);
let mut command = ::std::process::Command::new(&cmd);
for arg in &cmd_args {
command.arg(arg);
}
if let Some(ref dir) = cwd {
command.current_dir(dir);
}
if has_opts {
rooted!(&in(cx_ref) let o = opts_val.to_object());
if let Some(env) = get_env_prop(cx, o.handle().into()) {
command.env_clear();
for (k, v) in env {
command.env(k, v);
}
}
}
command.stdin(stdin_mode);
command.stdout(stdout_mode);
command.stderr(stderr_mode);
match command.spawn() {
Ok(mut child) => {
let pid = child.id() as i32;
use ::std::os::fd::AsRawFd;
let stdout_pipe = child.stdout.take();
let stderr_pipe = child.stderr.take();
let stdout_fd = stdout_pipe
.as_ref()
.map(|s| s.as_raw_fd())
.unwrap_or(-1);
let stderr_fd = stderr_pipe
.as_ref()
.map(|s| s.as_raw_fd())
.unwrap_or(-1);
let piped_out = stdout_fd >= 0;
let piped_err = stderr_fd >= 0;
if let Some(s) = stdout_pipe {
::std::mem::forget(s);
}
if let Some(s) = stderr_pipe {
::std::mem::forget(s);
}
if !crate::node_child_process::register_async_child(pid, stdout_fd, stderr_fd, -1) {
if stdout_fd >= 0 {
unsafe {
libc::close(stdout_fd);
}
}
if stderr_fd >= 0 {
unsafe {
libc::close(stderr_fd);
}
}
let _ = unsafe { libc::kill(pid, libc::SIGKILL) };
JS_ReportErrorUTF8(
cx,
c"Bun.spawn(): failed to start the output pump thread".as_ptr(),
);
return false;
}
SPAWNED_PROCS.with(|p| p.borrow_mut().push(Box::new(child)));
rooted!(&in(cx_ref) let subproc_obj = JS_NewPlainObject(cx_ref));
if subproc_obj.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let subproc_h = subproc_obj.handle().into();
let define_int = |cx: *mut JSContext, name: *const ::std::os::raw::c_char, v: i32| {
unsafe {
rooted!(&in(cx_ref) let rv = Int32Value(v));
JS_DefineProperty(cx, subproc_h, name, rv.handle().into(), JSPROP_ENUMERATE as u32);
}
};
define_int(cx, c"pid".as_ptr(), pid);
define_int(cx, c"exitCode".as_ptr(), -1);
define_int(cx, c"_pid".as_ptr(), pid as i32);
define_int(cx, c"_pipedOut".as_ptr(), if piped_out { 1 } else { 0 });
define_int(cx, c"_pipedErr".as_ptr(), if piped_err { 1 } else { 0 });
{
rooted!(&in(cx_ref) let kv = BooleanValue(false));
JS_DefineProperty(
cx,
subproc_h,
c"killed".as_ptr(),
kv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
JS_DefineFunction(
cx_ref,
subproc_obj.handle(),
c"wait".as_ptr(),
::std::option::Option::Some(subproc_wait),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx_ref,
subproc_obj.handle(),
c"kill".as_ptr(),
::std::option::Option::Some(subproc_kill),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx_ref,
subproc_obj.handle(),
c"_pollExitInfo".as_ptr(),
::std::option::Option::Some(subproc_poll_exit_info),
0,
0,
);
JS_DefineFunction(
cx_ref,
subproc_obj.handle(),
c"_stdoutText".as_ptr(),
::std::option::Option::Some(subproc_stdout_text),
0,
0,
);
JS_DefineFunction(
cx_ref,
subproc_obj.handle(),
c"_stdoutRaw".as_ptr(),
::std::option::Option::Some(subproc_stdout_raw),
0,
0,
);
JS_DefineFunction(
cx_ref,
subproc_obj.handle(),
c"_stderrText".as_ptr(),
::std::option::Option::Some(subproc_stderr_text),
0,
0,
);
JS_DefineFunction(
cx_ref,
subproc_obj.handle(),
c"_stderrRaw".as_ptr(),
::std::option::Option::Some(subproc_stderr_raw),
0,
0,
);
JS_DefineFunction(
cx_ref,
subproc_obj.handle(),
c"_watchExit".as_ptr(),
::std::option::Option::Some(subproc_watch_exit),
1,
0,
);
let dispose_src = r#"(function(proc) {
if (!proc) return proc;
var _exitResolve = null;
var _exitedPromise = new Promise(function(resolve) { _exitResolve = resolve; });
Object.defineProperty(proc, 'exited', {
configurable: true,
enumerable: true,
get: function() { return _exitedPromise; },
});
// ── EventEmitter surface (child_process.spawn parity) ──────────────────
var _events = {};
function _arr(ev) { return _events[ev] || (_events[ev] = []); }
proc.on = function(ev, cb) {
if (typeof cb !== 'function') return proc;
_arr(ev).push(cb);
return proc;
};
proc.once = function(ev, cb) {
var g = function() { proc.off(ev, g); cb.apply(null, arguments); };
g.listener = cb;
return proc.on(ev, g);
};
proc.off = function(ev, cb) {
var a = _events[ev];
if (a) {
var i = a.indexOf(cb);
if (i >= 0) a.splice(i, 1);
}
return proc;
};
proc.emit = function(ev) {
var a = _events[ev];
if (!a || a.length === 0) return false;
a = a.slice();
var args = Array.prototype.slice.call(arguments, 1);
for (var i = 0; i < a.length; i++) {
try { a[i].apply(null, args); } catch (e) {}
}
return true;
};
// Exit completion — driven by the NATIVE drain pump: the wrapper registers
// `finish` via proc._watchExit; spawn_watch_pump_all (called on every
// drain, the same integration point as the cluster pump) invokes it once
// the child is reaped AND its pipes drained. No JS-timer chain: the first
// cut's chained-setTimeout watcher spontaneously died at a ~1/300 rate
// (timer popped with its gc_store callback missing → close never fired).
var _finished = false;
function _finish(code, signal) {
if (_finished) return;
_finished = true;
proc.exitCode = code;
try { _exitResolve(code); } catch (e) {}
try { proc.emit('exit', code, signal); } catch (e) {}
if (proc._pipedOut) {
try {
var out = (typeof proc._stdoutRaw === 'function') ? proc._stdoutRaw() : null;
if (out !== null && out !== undefined) proc.emit('stdout_data', out);
proc.emit('stdout_end');
} catch (e) {}
}
if (proc._pipedErr) {
try {
var err = (typeof proc._stderrRaw === 'function') ? proc._stderrRaw() : null;
if (err !== null && err !== undefined) proc.emit('stderr_data', err);
proc.emit('stderr_end');
} catch (e) {}
}
try { proc.emit('close', code, signal); } catch (e) {}
}
if (typeof proc._watchExit === 'function') {
proc._watchExit(_finish);
}
// stdout / stderr stream-like wrappers with text() and data/end events.
function makeStream(textFn, dataEv, endEv) {
return {
text: function() {
var s = (typeof textFn === 'function') ? (textFn.call(proc) || '') : '';
if (s && typeof s.then === 'function') return s;
return Promise.resolve(String(s));
},
on: function(ev, cb) {
return proc.on(ev === 'data' ? dataEv : (ev === 'end' ? endEv : ev), cb);
},
read: function() { return Promise.resolve(null); },
};
}
var _stdoutStream = makeStream(proc._stdoutText, 'stdout_data', 'stdout_end');
var _stderrStream = makeStream(proc._stderrText, 'stderr_data', 'stderr_end');
Object.defineProperty(proc, 'stdout', {
configurable: true, enumerable: true,
get: function() { return _stdoutStream; },
});
Object.defineProperty(proc, 'stderr', {
configurable: true, enumerable: true,
get: function() { return _stderrStream; },
});
// Symbol.asyncDispose — invoked at end of `await using proc { ... }`.
// Contract (TC39 Explicit Resource Management): returns a Promise; we
// resolve it when the child has exited (the pump-backed watcher reaps).
if (typeof Symbol === 'function' && Symbol.asyncDispose) {
proc[Symbol.asyncDispose] = function() {
return Promise.resolve(proc.exited);
};
}
return proc;
})"#;
let dispose_filename = ZBox::from_bytes("bun:spawn-dispose".as_bytes());
let dispose_opts = mozjs::glue::NewCompileOptions(cx, dispose_filename.as_ptr(), 1);
if !dispose_opts.is_null() {
let mut dispose_text = mozjs::rust::transform_str_to_source_text(dispose_src);
let mut dispose_rval = UndefinedValue();
let dispose_rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut dispose_rval,
};
mozjs_sys::jsapi::JS::Evaluate2(
cx,
dispose_opts,
&mut dispose_text,
dispose_rval_h,
);
libc::free(dispose_opts as *mut _);
if dispose_rval.is_object() {
rooted!(&in(cx_ref) let wrapper_fn = dispose_rval.to_object());
rooted!(&in(cx_ref) let proc_val_elem = ObjectValue(subproc_obj.get()));
let args_arr = HandleValueArray {
length_: 1,
elements_: &*proc_val_elem.handle(),
};
let mut call_rval = UndefinedValue();
let call_rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut call_rval,
};
rooted!(&in(cx_ref) let wrapper_val = ObjectValue(wrapper_fn.get()));
let _ = mozjs_sys::jsapi::JS_CallFunctionValue(
cx,
subproc_obj.handle().into(),
wrapper_val.handle().into(),
&args_arr,
call_rval_h,
);
}
}
args.rval().set(ObjectValue(subproc_obj.get()));
true
}
Err(e) => {
let msg = format!("Bun.spawn() failed: {}", e);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
false
}
}
}
unsafe fn get_pid_from_this(cx: *mut JSContext, args: &CallArgs) -> Option<i32> {
unsafe {
let this = args.thisv();
if !this.is_object() {
return None;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj = this.get().to_object());
let mut pid_val = UndefinedValue();
JS_GetProperty(
cx,
obj.handle().into(),
c"_pid".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut pid_val,
},
);
if pid_val.is_int32() {
let pid = pid_val.to_int32();
if pid > 0 {
return Some(pid);
}
}
None
}
}
unsafe fn set_int_prop_on_this(
cx: *mut JSContext,
args: &CallArgs,
name: *const ::std::os::raw::c_char,
v: i32,
) {
unsafe {
let this = args.thisv();
if !this.is_object() {
return;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj = this.to_object());
rooted!(&in(cx_ref) let v_root = Int32Value(v));
JS_SetProperty(cx, obj.handle().into(), name, v_root.handle().into());
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn subproc_wait(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let Some(pid) = get_pid_from_this(cx, &args) else {
args.rval().set(Int32Value(-1));
return true;
};
let info = loop {
if let Some(info) = crate::node_child_process::poll_exit_info(pid) {
break info;
}
::std::thread::sleep(::std::time::Duration::from_millis(2));
};
set_int_prop_on_this(cx, &args, c"exitCode".as_ptr(), info.0);
args.rval().set(Int32Value(info.0));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn subproc_poll_exit_info(
cx: *mut JSContext,
_argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let Some(pid) = get_pid_from_this(cx, &args) else {
args.rval().set(NullValue());
return true;
};
let Some((code, signal)) = crate::node_child_process::poll_exit_info(pid) else {
args.rval().set(NullValue());
return true;
};
set_int_prop_on_this(cx, &args, c"exitCode".as_ptr(), code);
let vals = [Int32Value(code), Int32Value(signal)];
let hva = HandleValueArray {
length_: vals.len(),
elements_: vals.as_ptr(),
};
let arr = unsafe { NewArrayObject(cx, &hva) };
args.rval().set(if arr.is_null() {
NullValue()
} else {
ObjectValue(arr)
});
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn subproc_kill(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let Some(pid) = get_pid_from_this(cx, &args) else {
args.rval().set(BooleanValue(false));
return true;
};
let sig: i32 = if argc > 0 {
let v = *args.get(0).ptr;
if v.is_int32() {
v.to_int32()
} else if v.is_string() {
let name = crate::js_to_rust_string(cx, v).to_uppercase();
match name.as_str() {
"SIGHUP" => libc::SIGHUP as i32,
"SIGINT" => libc::SIGINT as i32,
"SIGQUIT" => libc::SIGQUIT as i32,
"SIGABRT" => libc::SIGABRT as i32,
"SIGKILL" => libc::SIGKILL as i32,
"SIGUSR1" => libc::SIGUSR1 as i32,
"SIGUSR2" => libc::SIGUSR2 as i32,
"SIGTERM" => libc::SIGTERM as i32,
_ => {
let msg = format!("Bun.spawn kill(): unknown signal name");
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
}
} else {
libc::SIGTERM as i32
}
} else {
libc::SIGTERM as i32
};
let sent = unsafe { libc::kill(pid, sig) } == 0;
if sent {
let this = args.thisv();
if this.is_object() {
let mut wrapped_cx_k =
mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref_k = &mut wrapped_cx_k;
rooted!(&in(cx_ref_k) let obj = this.to_object());
rooted!(&in(cx_ref_k) let killed_root = BooleanValue(true));
JS_SetProperty(
cx,
obj.handle().into(),
c"killed".as_ptr(),
killed_root.handle().into(),
);
}
}
args.rval().set(BooleanValue(sent));
true
}
unsafe fn subproc_output_common(
cx: *mut JSContext,
args: &CallArgs,
stdout: bool,
lossy_text: bool,
) -> bool {
unsafe {
let Some(pid) = get_pid_from_this(cx, args) else {
args.rval().set(NullValue());
return true;
};
let Some(bytes) = crate::node_child_process::peek_output(pid, stdout) else {
args.rval().set(NullValue());
return true;
};
let js_str = js_string_from_child_bytes(cx, &bytes, lossy_text);
args.rval().set(if js_str.is_null() {
NullValue()
} else {
StringValue(&*js_str)
});
true
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn subproc_stdout_text(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
subproc_output_common(cx, &args, true, true)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn subproc_stdout_raw(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
subproc_output_common(cx, &args, true, false)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn subproc_stderr_text(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
subproc_output_common(cx, &args, false, true)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn subproc_stderr_raw(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
subproc_output_common(cx, &args, false, false)
}
unsafe fn get_string_prop(
cx: *mut JSContext,
obj_h: Handle<*mut JSObject>,
name: *const ::std::os::raw::c_char,
) -> Option<String> {
unsafe {
let mut val = UndefinedValue();
let mh = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut val,
};
JS_GetProperty(cx, obj_h, name, mh);
if val.is_string() {
Some(crate::js_to_rust_string(cx, val))
} else {
None
}
}
}
unsafe fn get_string_array_prop(
cx: *mut JSContext,
obj_h: Handle<*mut JSObject>,
name: *const ::std::os::raw::c_char,
) -> Vec<String> {
unsafe {
let mut val = UndefinedValue();
let mh = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut val,
};
JS_GetProperty(cx, obj_h, name, mh);
if !val.is_object() {
return Vec::new();
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let arr = val.to_object());
read_string_array_from_obj(cx, arr.handle().into())
}
}
unsafe fn get_env_prop(
cx: *mut JSContext,
obj_h: Handle<*mut JSObject>,
) -> Option<Vec<(String, String)>> {
unsafe {
let mut val = UndefinedValue();
let mh = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut val,
};
JS_GetProperty(cx, obj_h, c"env".as_ptr(), mh);
if !val.is_object() {
return None;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let env_obj = val.to_object());
let mut ids_ptr: *mut JSString = ::std::ptr::null_mut();
let _ids_mh = MutableHandle::<*mut JSString> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut ids_ptr,
};
if !JS_GetProperty(
cx,
env_obj.handle().into(),
c"__envKeys__".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut val,
},
) {
return None;
}
None
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn stdin_read(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut buf = [0u8; 4096];
match ::std::io::stdin().lock().read(&mut buf) {
Ok(0) => {
args.rval().set(NullValue());
}
Ok(n) => {
let s = ::std::str::from_utf8(&buf[..n]).unwrap_or("");
let js_str = JS_NewStringCopyN(cx, s.as_ptr() as *const i8, s.len());
if !js_str.is_null() {
args.rval().set(StringValue(&*js_str));
} else {
args.rval().set(NullValue());
}
}
Err(_) => {
args.rval().set(NullValue());
}
}
true
}
thread_local! {
static STDIN_LISTENER_KEYS: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
}
static STDIN_CB_COUNTER: AtomicU64 = AtomicU64::new(0);
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn stdin_on(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc < 2 {
args.rval().set(UndefinedValue());
return true;
}
let event_val = *args.get(0).ptr;
let fn_val = *args.get(1).ptr;
if !event_val.is_string() || !fn_val.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let event = unsafe_jsstr_to_string(cx, NonNull::new_unchecked(event_val.to_string()));
if event != "data" && event != "end" && event != "close" && event != "error" {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx_on = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref_on = &mut wrapped_cx_on;
rooted!(&in(cx_ref_on) let callback = fn_val.to_object());
let cb_id = STDIN_CB_COUNTER.fetch_add(1, Ordering::Relaxed);
let key = format!("stdin_cb_{}", cb_id);
gc_store_insert(cx, &key, callback.get());
STDIN_LISTENER_KEYS.with(|l| {
l.borrow_mut().push(key);
});
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn stdin_pipe(_cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let this = *args.thisv().ptr;
args.rval().set(this);
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn stdin_resume(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let this = args.thisv();
if !this.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = this.to_object());
let readable_v = BooleanValue(true);
let rv_h = Handle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &readable_v,
};
JS_DefineProperty(
cx,
this_obj.handle().into(),
c"readable".as_ptr(),
rv_h,
JSPROP_ENUMERATE as u32,
);
args.rval().set(ObjectValue(this_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn stdin_pause(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let this = args.thisv();
if !this.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = this.to_object());
let readable_v = BooleanValue(false);
let rv_h = Handle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &readable_v,
};
JS_DefineProperty(
cx,
this_obj.handle().into(),
c"readable".as_ptr(),
rv_h,
JSPROP_ENUMERATE as u32,
);
args.rval().set(ObjectValue(this_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn stdin_destroy(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let this = args.thisv();
if !this.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = this.to_object());
let destroyed_v = BooleanValue(true);
let dv_h = Handle::<JSVal> {
_phantom_0: ::std::marker::PhantomData,
ptr: &destroyed_v,
};
JS_DefineProperty(
cx,
this_obj.handle().into(),
c"destroyed".as_ptr(),
dv_h,
JSPROP_ENUMERATE as u32,
);
args.rval().set(ObjectValue(this_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_serve(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let mut port: u16 = 3000;
let mut hostname = "0.0.0.0".to_string();
let mut fetch_handler: Option<*mut JSObject> = None;
let mut websocket_handler: Option<*mut JSObject> = None;
if argc > 0 {
let opts_val = *args.get(0).ptr;
if opts_val.is_object() {
let mut wrapped_cx_opts =
mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref_opts = &mut wrapped_cx_opts;
rooted!(&in(cx_ref_opts) let opts_obj = opts_val.to_object());
let opts_h = opts_obj.handle().into();
let mut port_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(cx, opts_h, c"port", &mut port_val);
if port_val.is_int32() {
port = port_val.to_int32().max(0) as u16;
} else if port_val.is_double() {
port = port_val.to_double().max(0.0) as u16;
}
let mut hn_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(cx, opts_h, c"hostname", &mut hn_val);
if hn_val.is_string() {
hostname = crate::js_to_rust_string(cx, hn_val);
}
let mut fetch_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(cx, opts_h, c"fetch", &mut fetch_val);
if fetch_val.is_object() {
rooted!(&in(cx_ref_opts) let fetch_obj = fetch_val.to_object());
if JS_ObjectIsFunction(fetch_obj.get()) {
fetch_handler = Some(fetch_obj.get());
}
}
let mut ws_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(cx, opts_h, c"websocket", &mut ws_val);
if ws_val.is_object() {
rooted!(&in(cx_ref_opts) let ws_obj = ws_val.to_object());
websocket_handler = Some(ws_obj.get());
}
}
}
let serve_id = SERVE_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
let fetch_cb_key = fetch_handler.map(|cb| {
let key = format!("serve_fetch_{}", serve_id);
gc_store_insert(cx, &key, cb);
key
});
let websocket_cb_key = websocket_handler.map(|cb| {
let key = format!("serve_ws_{}", serve_id);
gc_store_insert(cx, &key, cb);
key
});
let fetch_cb_key_for_js = fetch_cb_key.clone();
let websocket_cb_key_for_js = websocket_cb_key.clone();
crate::timers::with_event_loop(|_| {});
let opts = BunSocketContextOptions::default();
let app_ptr = App::<false>::create(&opts).unwrap_or(::std::ptr::null_mut());
unsafe {
crate::node_http::register_active_app(app_ptr);
}
let ud = Box::new(BunServeUserData {
fetch_cb_key,
websocket_cb_key: websocket_cb_key.clone(),
app_ptr: app_ptr as *mut ::std::ffi::c_void,
hostname: hostname.clone(),
port,
actual_port: AtomicU16::new(0),
cx,
});
let ud_ptr = Box::into_raw(ud) as *mut ::std::ffi::c_void;
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_serve_route_handler(
res: *mut bun_uws_sys::response::c::uws_res,
req: *mut Request,
user_data: *mut ::std::ffi::c_void,
) {
let ud = &*(user_data as *const BunServeUserData);
let res_mut = Response::<false>::cast_res(res);
let req_ref = bun_opaque::opaque_deref_mut(req);
let upgrade_header = req_ref
.header(b"upgrade")
.map(|h| h.to_vec())
.unwrap_or_default();
let is_ws_upgrade = upgrade_header.eq_ignore_ascii_case(b"websocket")
&& req_ref.header(b"sec-websocket-key").is_some();
if is_ws_upgrade && ud.fetch_cb_key.is_none() {
(*res_mut).write_status(b"426 Upgrade Required");
(*res_mut).write_header(b"Content-Type", b"text/plain");
(*res_mut).end(b"Upgrade Required: no WebSocket handler registered", true);
return;
}
if ud.fetch_cb_key.is_none() {
serve_write_default_response(&mut *res_mut, &*req_ref);
return;
}
let cx = ud.cx;
if cx.is_null() {
eprintln!("[bun:serve] fetch handler registered but cx is null — responding 500");
(*res_mut).write_status(b"500 Internal Server Error");
(*res_mut).write_header(b"Content-Type", b"text/plain");
(*res_mut).end(b"no JS context", true);
return;
}
let global = match bao_engine::context::thread_realm_global() {
Some(g) if !g.is_null() => g,
_ => {
eprintln!("[bun:serve] no JS realm on this thread — responding 500");
(*res_mut).write_status(b"500 Internal Server Error");
(*res_mut).write_header(b"Content-Type", b"text/plain");
(*res_mut).end(b"no JS realm", true);
return;
}
};
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let global_root = global);
let mut realm = AutoRealm::new_from_handle(cx_ref, global_root.handle());
let cx_ref: &mut mozjs::context::JSContext = &mut realm;
let fetch_handler = match ud.fetch_handler() {
Some(h) if !h.is_null() => h,
_ => {
eprintln!("[bun:serve] fetch handler registered but unresolvable — responding 500");
(*res_mut).write_status(b"500 Internal Server Error");
(*res_mut).write_header(b"Content-Type", b"text/plain");
(*res_mut).end(b"fetch handler unavailable", true);
return;
}
};
rooted!(&in(cx_ref) let req_obj = serve_build_request_object(cx_ref, &*req_ref));
if req_obj.get().is_null() {
serve_write_default_response(&mut *res_mut, &*req_ref);
return;
}
rooted!(&in(cx_ref) let handler_val = ObjectValue(fetch_handler));
rooted!(&in(cx_ref) let req_val_elem = ObjectValue(req_obj.get()));
let call_args = HandleValueArray {
length_: 1,
elements_: &*req_val_elem.handle(),
};
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let ok = JS_CallFunctionValue(
cx,
global_root.handle().into(),
handler_val.handle().into(),
&call_args,
rval_h,
);
if !ok {
JS_ClearPendingException(cx);
(*res_mut).write_status(b"500 Internal Server Error");
(*res_mut).write_header(b"Content-Type", b"text/plain");
(*res_mut).end(b"fetch handler threw", true);
return;
}
let resp_obj = serve_resolve_response_value(cx_ref, rval);
if resp_obj.is_null() {
(*res_mut).write_status(b"404 Not Found");
(*res_mut).write_header(b"Content-Type", b"text/plain");
(*res_mut).end(b"Not Found", true);
return;
}
serve_write_response_object(cx, &mut *res_mut, resp_obj);
}
let safe_handler: Option<
extern "C" fn(
*mut bun_uws_sys::response::c::uws_res,
*mut Request,
*mut ::std::ffi::c_void,
),
> = unsafe {
::std::mem::transmute(Some(
bun_serve_route_handler
as unsafe extern "C" fn(
*mut bun_uws_sys::response::c::uws_res,
*mut Request,
*mut ::std::ffi::c_void,
),
))
};
if !app_ptr.is_null() {
if websocket_cb_key.is_some() {
let behavior = ws_build_behavior();
(*app_ptr).ws(b"/*", ud_ptr, 0, behavior);
}
(*app_ptr).any(b"/*", safe_handler, ud_ptr);
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_serve_listen_cb(
listen_socket: *mut ListenSocket,
user_data: *mut ::std::ffi::c_void,
) {
if !listen_socket.is_null() {
let ls_ref = bun_opaque::opaque_deref_mut(listen_socket);
let ls_port = ls_ref.get_local_port();
if ls_port > 0 {
if !user_data.is_null() {
let ud = &*(user_data as *const BunServeUserData);
ud.actual_port.store(ls_port as u16, Ordering::Release);
}
log::info!("Bun.serve() listening (uWS port={})", ls_port);
}
}
}
let safe_listen_cb: extern "C" fn(*mut ListenSocket, *mut ::std::ffi::c_void) = unsafe {
::std::mem::transmute(
bun_serve_listen_cb
as unsafe extern "C" fn(*mut ListenSocket, *mut ::std::ffi::c_void),
)
};
(*app_ptr).listen(port as i32, safe_listen_cb, ud_ptr);
log::info!("Bun.serve() listening on {}:{}", hostname, port);
}
let mut wrapped_cx = unsafe { mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx)) };
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let server_obj = JS_NewPlainObject(cx_ref));
if server_obj.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let srv_h = server_obj.handle().into();
let bound_port = (*(ud_ptr as *const BunServeUserData))
.actual_port
.load(Ordering::Acquire);
let exposed_port = if bound_port > 0 { bound_port } else { port } as i32;
rooted!(&in(cx_ref) let port_root = Int32Value(exposed_port));
JS_DefineProperty(
cx,
srv_h,
c"port".as_ptr(),
port_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
let c_hn = ZBox::from_bytes(hostname.as_bytes());
{
let hn_str = JS_NewStringCopyZ(cx, c_hn.as_ptr());
if !hn_str.is_null() {
rooted!(&in(cx_ref) let hn_v = StringValue(&*hn_str));
JS_DefineProperty(
cx,
srv_h,
c"hostname".as_ptr(),
hn_v.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let app_val = mozjs::jsval::PrivateValue(app_ptr as *const core::ffi::c_void);
rooted!(&in(cx_ref) let app_h = app_val);
JS_DefineProperty(cx, srv_h, c"_appPtr".as_ptr(), app_h.handle().into(), 0);
if let Some(ref fk) = fetch_cb_key_for_js {
let c_fk = ZBox::from_bytes(fk.as_bytes());
{
let fk_str = JS_NewStringCopyZ(cx, c_fk.as_ptr());
if !fk_str.is_null() {
rooted!(&in(cx_ref) let v = StringValue(&*fk_str));
JS_DefineProperty(cx, srv_h, c"_fetchCbKey".as_ptr(), v.handle().into(), 0);
}
}
}
if let Some(ref wk) = websocket_cb_key_for_js {
let c_wk = ZBox::from_bytes(wk.as_bytes());
{
let wk_str = JS_NewStringCopyZ(cx, c_wk.as_ptr());
if !wk_str.is_null() {
rooted!(&in(cx_ref) let v = StringValue(&*wk_str));
JS_DefineProperty(cx, srv_h, c"_wsCbKey".as_ptr(), v.handle().into(), 0);
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn server_stop(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx_stop = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref_stop = &mut wrapped_cx_stop;
rooted!(&in(cx_ref_stop) let this_obj = args.thisv().to_object());
let this_h = this_obj.handle().into();
let mut app_val = UndefinedValue();
JS_GetProperty(
cx,
this_h,
c"_appPtr".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut app_val,
},
);
let app_ptr = if app_val.is_double() && (app_val.asBits_ & 0xFFFF000000000000) == 0 {
app_val.to_private() as *mut App<false>
} else {
core::ptr::null_mut()
};
if !app_ptr.is_null() {
(*app_ptr).close();
unsafe {
crate::node_http::unregister_active_app(app_ptr);
}
App::<false>::destroy(app_ptr);
log::info!("Bun.serve() stopped");
let undef = UndefinedValue();
let undef_h = Handle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &undef,
};
JS_SetProperty(cx, this_h, c"_appPtr".as_ptr(), undef_h);
}
let mut fk_val = UndefinedValue();
JS_GetProperty(
cx,
this_h,
c"_fetchCbKey".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut fk_val,
},
);
if fk_val.is_string() {
let key = crate::js_to_rust_string(cx, fk_val);
gc_store_remove(cx, &key);
}
let mut wk_val = UndefinedValue();
JS_GetProperty(
cx,
this_h,
c"_wsCbKey".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut wk_val,
},
);
if wk_val.is_string() {
let key = crate::js_to_rust_string(cx, wk_val);
gc_store_remove(cx, &key);
}
args.rval().set(UndefinedValue());
true
}
unsafe extern "C" fn server_ref(_cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
args.rval().set(UndefinedValue());
true
}
unsafe extern "C" fn server_unref(_cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
args.rval().set(UndefinedValue());
true
}
mozjs_sys::jsapi::JS_DefineFunction(
cx,
srv_h,
c"stop".as_ptr(),
Some(server_stop),
0,
JSPROP_ENUMERATE as u32,
);
mozjs_sys::jsapi::JS_DefineFunction(
cx,
srv_h,
c"ref".as_ptr(),
Some(server_ref),
0,
JSPROP_ENUMERATE as u32,
);
mozjs_sys::jsapi::JS_DefineFunction(
cx,
srv_h,
c"unref".as_ptr(),
Some(server_unref),
0,
JSPROP_ENUMERATE as u32,
);
args.rval().set(mozjs::jsval::ObjectValue(server_obj.get()));
true
}
#[allow(dead_code)]
struct BunServeUserData {
fetch_cb_key: Option<String>,
websocket_cb_key: Option<String>,
app_ptr: *mut ::std::ffi::c_void,
hostname: String,
port: u16,
actual_port: AtomicU16,
cx: *mut JSContext,
}
impl BunServeUserData {
fn fetch_handler(&self) -> Option<*mut JSObject> {
let key = self.fetch_cb_key.as_ref()?;
if self.cx.is_null() {
return None;
}
gc_store_get(self.cx, key)
}
}
static WS_SOCKET_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
#[allow(dead_code)]
struct BunWsUserData {
ws_obj_key: String,
ws_handler_key: String,
cx: *mut JSContext,
}
fn ws_build_behavior() -> WebSocketBehavior {
WebSocketBehavior {
compression: 0,
max_payload_length: u32::MAX,
idle_timeout: 120,
max_backpressure: 1024 * 1024,
close_on_backpressure_limit: false,
reset_idle_timeout_on_send: true,
send_pings_automatically: true,
max_lifetime: 0,
upgrade: Some(ws_on_upgrade),
open: Some(ws_on_open),
message: Some(ws_on_message),
drain: None,
ping: Some(ws_on_ping),
pong: None,
close: Some(ws_on_close),
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn ws_create_js_object(cx: *mut JSContext, raw_ws: *mut RawWebSocket) -> *mut JSObject {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
let raw_cx = cx_ref.raw_cx();
rooted!(&in(cx_ref) let ws_obj = JS_NewPlainObject(cx_ref));
if ws_obj.get().is_null() {
return ::std::ptr::null_mut();
}
let ptr_bits = raw_ws as u64;
let ptr_hi = (ptr_bits >> 32) as i32;
let ptr_lo = (ptr_bits & 0xFFFFFFFF) as i32;
{
let hi = Int32Value(ptr_hi);
rooted!(&in(cx_ref) let hi_r = hi);
JS_DefineProperty(
raw_cx,
ws_obj.handle().into(),
c"_wsPtrHi".as_ptr(),
hi_r.handle().into(),
0,
);
}
{
let lo = Int32Value(ptr_lo);
rooted!(&in(cx_ref) let lo_r = lo);
JS_DefineProperty(
raw_cx,
ws_obj.handle().into(),
c"_wsPtrLo".as_ptr(),
lo_r.handle().into(),
0,
);
}
JS_DefineFunction(
cx_ref,
ws_obj.handle(),
c"send".as_ptr(),
Some(ws_js_send),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx_ref,
ws_obj.handle(),
c"close".as_ptr(),
Some(ws_js_close),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx_ref,
ws_obj.handle(),
c"ping".as_ptr(),
Some(ws_js_ping),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx_ref,
ws_obj.handle(),
c"terminate".as_ptr(),
Some(ws_js_terminate),
0,
JSPROP_ENUMERATE as u32,
);
{
let ready_val = Int32Value(0);
rooted!(&in(cx_ref) let rv = ready_val);
JS_DefineProperty(
raw_cx,
ws_obj.handle().into(),
c"readyState".as_ptr(),
rv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
{
let ba_val = Int32Value(0);
rooted!(&in(cx_ref) let bav = ba_val);
JS_DefineProperty(
raw_cx,
ws_obj.handle().into(),
c"bufferedAmount".as_ptr(),
bav.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
ws_obj.get()
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn ws_get_raw_ptr(cx: *mut JSContext, obj_h: Handle<*mut JSObject>) -> *mut RawWebSocket {
let mut hi_val = UndefinedValue();
JS_GetProperty(
cx,
obj_h,
c"_wsPtrHi".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut hi_val,
},
);
let mut lo_val = UndefinedValue();
JS_GetProperty(
cx,
obj_h,
c"_wsPtrLo".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut lo_val,
},
);
if hi_val.is_int32() && lo_val.is_int32() {
let hi = (hi_val.to_int32() as u32) as u64;
let lo = (lo_val.to_int32() as u32) as u64;
let ptr = ((hi << 32) | lo) as *mut RawWebSocket;
if !ptr.is_null() {
return ptr;
}
}
::std::ptr::null_mut()
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn ws_js_send(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let this = args.thisv();
if !this.is_object() {
args.rval().set(BooleanValue(false));
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = this.to_object());
let raw_ws = ws_get_raw_ptr(cx, this_obj.handle().into());
if raw_ws.is_null() {
args.rval().set(BooleanValue(false));
return true;
}
let data_val = if argc > 0 {
*args.get(0).ptr
} else {
UndefinedValue()
};
let message: Vec<u8>;
let opcode: Opcode;
if data_val.is_string() {
let s = crate::js_to_rust_string(cx, data_val);
message = s.into_bytes();
opcode = Opcode::Text;
} else if data_val.is_object() {
if let Some(bytes) = extract_bytes_from_jsval(cx, data_val) {
message = bytes;
opcode = Opcode::Binary;
} else {
rooted!(&in(cx_ref) let data_root = data_val);
let jsstr = mozjs::rust::ToString(cx_ref, data_root.handle());
if jsstr.is_null() {
args.rval().set(BooleanValue(false));
return true;
}
let str_val = StringValue(&*jsstr);
message = crate::js_to_rust_string(cx, str_val).into_bytes();
opcode = Opcode::Text;
}
} else {
args.rval().set(BooleanValue(false));
return true;
}
let ws: &mut NewWebSocket<0> = &mut *raw_ws.cast::<NewWebSocket<0>>();
let status = ws.send(&message, opcode);
args.rval()
.set(BooleanValue(matches!(status, SendStatus::Success)));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn ws_js_close(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let this = args.thisv();
if !this.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = this.to_object());
let raw_ws = ws_get_raw_ptr(cx, this_obj.handle().into());
if raw_ws.is_null() {
args.rval().set(UndefinedValue());
return true;
}
let code: i32 = if argc > 0 {
let code_val = *args.get(0).ptr;
if code_val.is_int32() {
code_val.to_int32()
} else {
1000
}
} else {
1000
};
let reason: Vec<u8> = if argc > 1 {
let reason_val = *args.get(1).ptr;
if reason_val.is_string() {
crate::js_to_rust_string(cx, reason_val).into_bytes()
} else {
Vec::new()
}
} else {
Vec::new()
};
let ws: &mut NewWebSocket<0> = &mut *raw_ws.cast::<NewWebSocket<0>>();
ws.end(code, &reason);
let closing_val = Int32Value(2);
rooted!(&in(cx_ref) let cv = closing_val);
JS_SetProperty(
cx,
this_obj.handle().into(),
c"readyState".as_ptr(),
cv.handle().into(),
);
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn ws_js_ping(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let this = args.thisv();
if !this.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = this.to_object());
let raw_ws = ws_get_raw_ptr(cx, this_obj.handle().into());
if raw_ws.is_null() {
args.rval().set(UndefinedValue());
return true;
}
let data: Vec<u8> = if argc > 0 {
let data_val = *args.get(0).ptr;
if data_val.is_string() {
crate::js_to_rust_string(cx, data_val).into_bytes()
} else {
Vec::new()
}
} else {
Vec::new()
};
let ws: &mut NewWebSocket<0> = &mut *raw_ws.cast::<NewWebSocket<0>>();
ws.send(&data, Opcode::Ping);
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn ws_js_terminate(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let this = args.thisv();
if !this.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = this.to_object());
let raw_ws = ws_get_raw_ptr(cx, this_obj.handle().into());
if raw_ws.is_null() {
args.rval().set(UndefinedValue());
return true;
}
let ws: &mut NewWebSocket<0> = &mut *raw_ws.cast::<NewWebSocket<0>>();
ws.close();
let closed_val = Int32Value(3);
rooted!(&in(cx_ref) let cv = closed_val);
JS_SetProperty(
cx,
this_obj.handle().into(),
c"readyState".as_ptr(),
cv.handle().into(),
);
let zero_hi = Int32Value(0);
rooted!(&in(cx_ref) let zh = zero_hi);
JS_SetProperty(
cx,
this_obj.handle().into(),
c"_wsPtrHi".as_ptr(),
zh.handle().into(),
);
let zero_lo = Int32Value(0);
rooted!(&in(cx_ref) let zl = zero_lo);
JS_SetProperty(
cx,
this_obj.handle().into(),
c"_wsPtrLo".as_ptr(),
zl.handle().into(),
);
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn ws_on_upgrade(
user_data: *mut ::std::ffi::c_void,
res: *mut uws_res,
req: *mut Request,
context: *mut WebSocketUpgradeContext,
_id: usize,
) {
let ud = &*(user_data as *const BunServeUserData);
let cx = ud.cx;
if cx.is_null() {
return;
}
let req_ref = bun_opaque::opaque_deref_mut(req);
let res_mut = Response::<false>::cast_res(res);
let ws_key = req_ref
.header(b"sec-websocket-key")
.map(|h| h.to_vec())
.unwrap_or_default();
let ws_protocol = req_ref
.header(b"sec-websocket-protocol")
.map(|h| h.to_vec())
.unwrap_or_default();
let ws_extensions = req_ref
.header(b"sec-websocket-extensions")
.map(|h| h.to_vec())
.unwrap_or_default();
let ws_obj = ws_create_js_object(cx, ::std::ptr::null_mut());
if ws_obj.is_null() {
(*res_mut).write_status(b"500 Internal Server Error");
(*res_mut).end(b"", true);
return;
}
let socket_id = WS_SOCKET_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
let ws_obj_key = format!("ws_obj_{}", socket_id);
gc_store_insert(cx, &ws_obj_key, ws_obj);
let ws_handler_key = ud.websocket_cb_key.clone().unwrap_or_default();
let ws_ud = Box::new(BunWsUserData {
ws_obj_key: ws_obj_key.clone(),
ws_handler_key,
cx,
});
let ws_ud_ptr = Box::into_raw(ws_ud);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let ws_obj_root = ws_obj);
let c_key = ZBox::from_bytes(ws_obj_key.as_bytes());
{
let key_str = JS_NewStringCopyZ(cx, c_key.as_ptr());
if !key_str.is_null() {
rooted!(&in(cx_ref) let kv = StringValue(&*key_str));
JS_DefineProperty(
cx,
ws_obj_root.handle().into(),
c"_wsObjKey".as_ptr(),
kv.handle().into(),
0,
);
}
}
(*res_mut).upgrade::<BunWsUserData>(
ws_ud_ptr,
&ws_key,
&ws_protocol,
&ws_extensions,
Some(&mut *context),
);
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn ws_on_open(raw_ws: *mut RawWebSocket) {
let ud_ptr = bun_uws_sys::web_socket::c::uws_ws_get_user_data(0, &mut *raw_ws);
if ud_ptr.is_null() {
return;
}
let ws_ud = &*(ud_ptr as *const BunWsUserData);
let cx = ws_ud.cx;
if cx.is_null() {
return;
}
let Some(global) = bao_engine::context::thread_realm_global() else {
return;
};
if global.is_null() {
return;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let global_root = global);
let mut realm = AutoRealm::new_from_handle(cx_ref, global_root.handle());
let cx_ref: &mut mozjs::context::JSContext = &mut realm;
let Some(ws_obj) = gc_store_get(cx, &ws_ud.ws_obj_key) else {
return;
};
if ws_obj.is_null() {
return;
}
rooted!(&in(cx_ref) let ws_obj_root = ws_obj);
let ptr_bits = raw_ws as u64;
let ptr_hi = (ptr_bits >> 32) as i32;
let ptr_lo = (ptr_bits & 0xFFFFFFFF) as i32;
rooted!(&in(cx_ref) let hi_val = Int32Value(ptr_hi));
JS_SetProperty(
cx,
ws_obj_root.handle().into(),
c"_wsPtrHi".as_ptr(),
hi_val.handle().into(),
);
rooted!(&in(cx_ref) let lo_val = Int32Value(ptr_lo));
JS_SetProperty(
cx,
ws_obj_root.handle().into(),
c"_wsPtrLo".as_ptr(),
lo_val.handle().into(),
);
rooted!(&in(cx_ref) let open_state = Int32Value(1));
JS_SetProperty(
cx,
ws_obj_root.handle().into(),
c"readyState".as_ptr(),
open_state.handle().into(),
);
let Some(ws_handler) = ws_ud.ws_handler() else {
return;
};
if ws_handler.is_null() {
return;
}
rooted!(&in(cx_ref) let ws_handler_root = ws_handler);
let mut open_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(
cx,
ws_handler_root.handle().into(),
c"open",
&mut open_val,
);
if !open_val.is_object() {
return;
}
rooted!(&in(cx_ref) let open_fn = open_val.to_object());
if !JS_ObjectIsFunction(open_fn.get()) {
return;
}
rooted!(&in(cx_ref) let open_fn_val = ObjectValue(open_fn.get()));
rooted!(&in(cx_ref) let ws_arg = ObjectValue(ws_obj_root.get()));
let call_args = HandleValueArray {
length_: 1,
elements_: &*ws_arg.handle(),
};
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let _ok = JS_CallFunctionValue(
cx,
global_root.handle().into(),
open_fn_val.handle().into(),
&call_args,
rval_h,
);
if !_ok {
JS_ClearPendingException(cx);
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn ws_on_message(
raw_ws: *mut RawWebSocket,
data: *const u8,
length: usize,
opcode: Opcode,
) {
let ud_ptr = bun_uws_sys::web_socket::c::uws_ws_get_user_data(0, &mut *raw_ws);
if ud_ptr.is_null() {
return;
}
let ws_ud = &*(ud_ptr as *const BunWsUserData);
let cx = ws_ud.cx;
if cx.is_null() {
return;
}
let Some(global) = bao_engine::context::thread_realm_global() else {
return;
};
if global.is_null() {
return;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let global_root = global);
let mut realm = AutoRealm::new_from_handle(cx_ref, global_root.handle());
let cx_ref: &mut mozjs::context::JSContext = &mut realm;
let Some(ws_obj) = gc_store_get(cx, &ws_ud.ws_obj_key) else {
return;
};
let Some(ws_handler) = ws_ud.ws_handler() else {
return;
};
if ws_obj.is_null() || ws_handler.is_null() {
return;
}
rooted!(&in(cx_ref) let ws_handler_root = ws_handler);
let mut msg_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(
cx,
ws_handler_root.handle().into(),
c"message",
&mut msg_val,
);
if !msg_val.is_object() {
return;
}
rooted!(&in(cx_ref) let msg_fn = msg_val.to_object());
if !JS_ObjectIsFunction(msg_fn.get()) {
return;
}
let is_text = opcode.0 == Opcode::Text.0;
let mut msg_arg = UndefinedValue();
if is_text {
let bytes = ::std::slice::from_raw_parts(data, length);
let text = ::std::str::from_utf8(bytes).unwrap_or("");
let c_text = ZBox::from_bytes(text.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_text.as_ptr());
if !js_str.is_null() {
msg_arg = StringValue(&*js_str);
}
} else {
let bytes = ::std::slice::from_raw_parts(data, length);
let c_data = ZBox::from_vec(bytes.to_vec());
let js_str = JS_NewStringCopyZ(cx, c_data.as_ptr());
if !js_str.is_null() {
msg_arg = StringValue(&*js_str);
}
}
rooted!(&in(cx_ref) let msg_fn_val = ObjectValue(msg_fn.get()));
rooted!(&in(cx_ref) let ws_arg = ObjectValue(ws_obj));
rooted!(&in(cx_ref) let msg_arg_root = msg_arg);
let args = [ws_arg.handle().get(), msg_arg_root.handle().get()];
let call_args = HandleValueArray {
length_: 2,
elements_: args.as_ptr(),
};
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let _ok = JS_CallFunctionValue(
cx,
global_root.handle().into(),
msg_fn_val.handle().into(),
&call_args,
rval_h,
);
if !_ok {
JS_ClearPendingException(cx);
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn ws_on_close(
raw_ws: *mut RawWebSocket,
code: i32,
message: *const u8,
length: usize,
) {
let ud_ptr = bun_uws_sys::web_socket::c::uws_ws_get_user_data(0, &mut *raw_ws);
if ud_ptr.is_null() {
return;
}
let ws_ud = &*(ud_ptr as *const BunWsUserData);
let cx = ws_ud.cx;
if cx.is_null() {
let _ = Box::from_raw(ud_ptr as *mut BunWsUserData);
return;
}
let global = match bao_engine::context::thread_realm_global() {
Some(g) if !g.is_null() => g,
_ => {
let _ = Box::from_raw(ud_ptr as *mut BunWsUserData);
return;
}
};
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let global_root = global);
let mut realm = AutoRealm::new_from_handle(cx_ref, global_root.handle());
let cx_ref: &mut mozjs::context::JSContext = &mut realm;
let ws_obj = match gc_store_get(cx, &ws_ud.ws_obj_key) {
Some(o) if !o.is_null() => o,
_ => {
let _ = Box::from_raw(ud_ptr as *mut BunWsUserData);
return;
}
};
rooted!(&in(cx_ref) let ws_obj_root = ws_obj);
let closed_val = Int32Value(3);
rooted!(&in(cx_ref) let cv = closed_val);
JS_SetProperty(
cx,
ws_obj_root.handle().into(),
c"readyState".as_ptr(),
cv.handle().into(),
);
let zero_hi = Int32Value(0);
rooted!(&in(cx_ref) let zh = zero_hi);
JS_SetProperty(
cx,
ws_obj_root.handle().into(),
c"_wsPtrHi".as_ptr(),
zh.handle().into(),
);
let zero_lo = Int32Value(0);
rooted!(&in(cx_ref) let zl = zero_lo);
JS_SetProperty(
cx,
ws_obj_root.handle().into(),
c"_wsPtrLo".as_ptr(),
zl.handle().into(),
);
let Some(ws_handler) = ws_ud.ws_handler() else {
gc_store_remove(cx, &ws_ud.ws_obj_key);
let _ = Box::from_raw(ud_ptr as *mut BunWsUserData);
return;
};
if !ws_handler.is_null() {
rooted!(&in(cx_ref) let ws_handler_root = ws_handler);
let mut close_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(
cx,
ws_handler_root.handle().into(),
c"close",
&mut close_val,
);
if close_val.is_object() {
rooted!(&in(cx_ref) let close_fn = close_val.to_object());
if JS_ObjectIsFunction(close_fn.get()) {
let code_arg = Int32Value(code);
let reason_bytes = if !message.is_null() && length > 0 {
::std::slice::from_raw_parts(message, length).to_vec()
} else {
Vec::new()
};
let reason_str = String::from_utf8_lossy(&reason_bytes).into_owned();
let c_reason = ZBox::from_bytes(reason_str.as_bytes());
let js_reason = JS_NewStringCopyZ(cx, c_reason.as_ptr());
rooted!(&in(cx_ref) let close_fn_val = ObjectValue(close_fn.get()));
rooted!(&in(cx_ref) let ws_arg = ObjectValue(ws_obj_root.get()));
rooted!(&in(cx_ref) let code_root = code_arg);
let reason_arg = if !js_reason.is_null() {
StringValue(&*js_reason)
} else {
UndefinedValue()
};
rooted!(&in(cx_ref) let reason_root = reason_arg);
let args = [
ws_arg.handle().get(),
code_root.handle().get(),
reason_root.handle().get(),
];
let call_args = HandleValueArray {
length_: 3,
elements_: args.as_ptr(),
};
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let _ok = JS_CallFunctionValue(
cx,
global_root.handle().into(),
close_fn_val.handle().into(),
&call_args,
rval_h,
);
if !_ok {
JS_ClearPendingException(cx);
}
}
}
}
gc_store_remove(cx, &ws_ud.ws_obj_key);
let _ = Box::from_raw(ud_ptr as *mut BunWsUserData);
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn ws_on_ping(_raw_ws: *mut RawWebSocket, _data: *const u8, _length: usize) {
}
impl BunWsUserData {
fn ws_handler(&self) -> Option<*mut JSObject> {
if self.cx.is_null() {
return None;
}
gc_store_get(self.cx, &self.ws_handler_key)
}
}
const SERVE_PROMISE_POLL_MAX_ITERS: u32 = 10_000;
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn serve_build_request_object(
cx_ref: &mut mozjs::context::JSContext,
req_ref: &Request,
) -> *mut JSObject {
let raw_cx = cx_ref.raw_cx();
rooted!(&in(cx_ref) let req_obj = JS_NewPlainObject(cx_ref));
if req_obj.get().is_null() {
return ::std::ptr::null_mut();
}
let method_bytes = req_ref.method();
let method_str = ::std::str::from_utf8(method_bytes).unwrap_or("GET");
{
let c_m = ZBox::from_bytes(method_str.as_bytes());
let js_m = JS_NewStringCopyZ(raw_cx, c_m.as_ptr());
if !js_m.is_null() {
let mv = StringValue(&*js_m);
rooted!(&in(cx_ref) let mvr = mv);
JS_DefineProperty(
raw_cx,
req_obj.handle().into(),
c"method".as_ptr(),
mvr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let url_bytes = req_ref.url();
let url_str = ::std::str::from_utf8(url_bytes).unwrap_or("/");
{
let c_u = ZBox::from_bytes(url_str.as_bytes());
let js_u = JS_NewStringCopyZ(raw_cx, c_u.as_ptr());
if !js_u.is_null() {
let uv = StringValue(&*js_u);
rooted!(&in(cx_ref) let uvr = uv);
JS_DefineProperty(
raw_cx,
req_obj.handle().into(),
c"url".as_ptr(),
uvr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
rooted!(&in(cx_ref) let headers_obj = JS_NewPlainObject(cx_ref));
if !headers_obj.get().is_null() {
let mut header_pairs: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
req_ref.for_each_header(
|pairs: &mut Vec<(Vec<u8>, Vec<u8>)>, name: &[u8], value: &[u8]| {
pairs.push((name.to_vec(), value.to_vec()));
},
&mut header_pairs as *mut Vec<(Vec<u8>, Vec<u8>)>,
);
for (name, value) in &header_pairs {
let c_k = ZBox::from_bytes(name);
let c_v = ZBox::from_bytes(value);
let js_v = JS_NewStringCopyZ(raw_cx, c_v.as_ptr());
if !js_v.is_null() {
let hv = StringValue(&*js_v);
rooted!(&in(cx_ref) let hvr = hv);
JS_DefineProperty(
raw_cx,
headers_obj.handle().into(),
c_k.as_ptr(),
hvr.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
let hdrs_val = ObjectValue(headers_obj.get());
rooted!(&in(cx_ref) let hdrs_r = hdrs_val);
JS_DefineProperty(
raw_cx,
req_obj.handle().into(),
c"headers".as_ptr(),
hdrs_r.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
{
let content_length: usize = req_ref
.header(b"content-length")
.and_then(|v| ::std::str::from_utf8(v).ok())
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(0);
let method_bytes = req_ref.method();
let is_bodyless_method =
method_bytes.eq_ignore_ascii_case(b"GET") || method_bytes.eq_ignore_ascii_case(b"HEAD");
let body_src = if is_bodyless_method || content_length == 0 {
r#"(function() {
var b = {
text: function() { return Promise.resolve(''); },
json: function() { return Promise.resolve(null); },
arrayBuffer: function() { return Promise.resolve(new ArrayBuffer(0)); },
_bodyText: '',
_bodyBytes: new Uint8Array(0),
};
return b;
})"#
} else {
r#"(function() {
// Lazy body — text/json/arrayBuffer return Promises that resolve
// once the body is read. The _bodyText field is populated by the
// native host when the body arrives via on_data.
var _resolved = false;
var _text = '';
var _bytes = null;
var _promises = [];
function resolveBody(text) {
_resolved = true;
_text = text;
_bytes = new TextEncoder().encode(text);
for (var i = 0; i < _promises.length; i++) {
_promises[i](text);
}
_promises = [];
}
var b = {
text: function() {
if (_resolved) return Promise.resolve(_text);
return new Promise(function(resolve) { _promises.push(resolve); });
},
json: function() {
if (_resolved) {
try { return Promise.resolve(JSON.parse(_text)); }
catch(e) { return Promise.reject(e); }
}
return b.text().then(function(t) {
try { return JSON.parse(t); }
catch(e) { throw e; }
});
},
arrayBuffer: function() {
if (_resolved) return Promise.resolve(_bytes.buffer || new ArrayBuffer(0));
return b.text().then(function(t) {
return new TextEncoder().encode(t).buffer;
});
},
_bodyText: '',
_bodyBytes: null,
_resolveBody: resolveBody,
};
return b;
})"#
};
let mut body_text = mozjs::rust::transform_str_to_source_text(body_src);
let mut body_rval = UndefinedValue();
let body_rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut body_rval,
};
let body_opts = mozjs::glue::NewCompileOptions(raw_cx, c"<body-factory>".as_ptr(), 1);
if !body_opts.is_null() {
if mozjs_sys::jsapi::JS::Evaluate2(raw_cx, body_opts, &mut body_text, body_rval_h)
&& body_rval.is_object()
{
rooted!(&in(cx_ref) let factory_fn = body_rval.to_object());
rooted!(&in(cx_ref) let factory_val = ObjectValue(factory_fn.get()));
rooted!(&in(cx_ref) let null_obj = ::std::ptr::null_mut::<JSObject>());
let mut call_rval = UndefinedValue();
let call_rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut call_rval,
};
let called = JS_CallFunctionValue(
raw_cx,
null_obj.handle().into(),
factory_val.handle().into(),
&HandleValueArray::empty(),
call_rval_h,
);
if !called {
let mut exn = UndefinedValue();
JS_GetPendingException(
raw_cx,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut exn,
},
);
JS_ClearPendingException(raw_cx);
rooted!(&in(cx_ref) let reason_root = exn);
if !exn.is_undefined() {
crate::uncaught::route_uncaught_exception(raw_cx, exn);
}
}
if call_rval.is_object() {
rooted!(&in(cx_ref) let body_obj = call_rval.to_object());
let body_val = ObjectValue(body_obj.get());
rooted!(&in(cx_ref) let body_val_root = body_val);
JS_DefineProperty(
raw_cx,
req_obj.handle().into(),
c"body".as_ptr(),
body_val_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
libc::free(body_opts as *mut _);
}
}
req_obj.get()
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn serve_resolve_response_value(
cx_ref: &mut mozjs::context::JSContext,
rval: JSVal,
) -> *mut JSObject {
if !rval.is_object() {
return ::std::ptr::null_mut();
}
rooted!(&in(cx_ref) let obj = rval.to_object());
if !serve_is_promise(cx_ref, obj.get()) {
return if serve_is_response_like(cx_ref, obj.get()) {
obj.get()
} else {
::std::ptr::null_mut()
};
}
let raw_cx = cx_ref.raw_cx();
let mut iters = 0u32;
loop {
if !JS::IsPromiseObject(obj.handle().into()) {
return ::std::ptr::null_mut();
}
let state = JS::GetPromiseState(obj.handle().into());
match state {
PromiseState::Fulfilled => {
let mut result_val = UndefinedValue();
mozjs::glue::JS_GetPromiseResult(
obj.handle().into(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut result_val,
},
);
if !result_val.is_object() {
return ::std::ptr::null_mut();
}
rooted!(&in(cx_ref) let result_obj = result_val.to_object());
return if serve_is_response_like(cx_ref, result_obj.get()) {
result_obj.get()
} else {
::std::ptr::null_mut()
};
}
PromiseState::Rejected => {
JS_ClearPendingException(raw_cx);
return ::std::ptr::null_mut();
}
_ => {}
}
mozjs_sys::jsapi::js::RunJobs(raw_cx);
crate::timers::with_event_loop(|loop_| {
loop_.tick_without_idle(core::ptr::null_mut());
});
iters += 1;
if iters >= SERVE_PROMISE_POLL_MAX_ITERS {
return ::std::ptr::null_mut();
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
fn serve_is_promise(cx_ref: &mut mozjs::context::JSContext, obj: *mut JSObject) -> bool {
let mut wrapped_cx =
unsafe { mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx_ref.raw_cx())) };
let cx_r = &mut wrapped_cx;
unsafe {
rooted!(&in(cx_r) let obj_r = obj);
JS::IsPromiseObject(obj_r.handle().into())
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn serve_is_response_like(
cx_ref: &mut mozjs::context::JSContext,
obj: *mut JSObject,
) -> bool {
let raw_cx = cx_ref.raw_cx();
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(raw_cx));
let cx_r = &mut wrapped_cx;
rooted!(&in(cx_r) let obj_r = obj);
let mut status_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(raw_cx, obj_r.handle().into(), c"status", &mut status_val);
status_val.is_int32() || status_val.is_double()
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn serve_write_response_object(
raw_cx: *mut JSContext,
res_mut: &mut Response<false>,
resp_obj: *mut JSObject,
) {
let mut wrapped_cx_resp = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(raw_cx));
let cx_ref_resp = &mut wrapped_cx_resp;
rooted!(&in(cx_ref_resp) let obj = resp_obj);
let mut status_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(raw_cx, obj.handle().into(), c"status", &mut status_val);
let status_code: i32 = if status_val.is_int32() {
status_val.to_int32()
} else if status_val.is_double() {
status_val.to_double() as i32
} else {
200
};
let clamped = status_code.clamp(100, 599);
let status_line = status_line_for(clamped);
res_mut.write_status(status_line.as_bytes());
let mut headers_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(raw_cx, obj.handle().into(), c"headers", &mut headers_val);
if headers_val.is_object() {
rooted!(&in(cx_ref_resp) let headers_obj = headers_val.to_object());
let mut for_each_val = UndefinedValue();
let has_for_each = JS_GetProperty(
raw_cx,
headers_obj.handle().into(),
c"forEach".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut for_each_val,
},
);
let headers_face = has_for_each
&& for_each_val.is_object()
&& JS_ObjectIsFunction(for_each_val.to_object());
let mut pairs: Vec<(String, String)> = Vec::new();
if headers_face {
rooted!(&in(cx_ref_resp) let for_each_fn = for_each_val.to_object());
let mut entries_val = UndefinedValue();
let entries_ok = JS_GetProperty(
raw_cx,
headers_obj.handle().into(),
c"entries".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut entries_val,
},
);
if entries_ok && entries_val.is_object() {
rooted!(&in(cx_ref_resp) let entries_fn = entries_val.to_object());
rooted!(&in(cx_ref_resp) let iter_val = {
let mut r = UndefinedValue();
let r_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut r,
};
let empty_args = HandleValueArray {
length_: 0,
elements_: ::std::ptr::null(),
};
rooted!(&in(cx_ref_resp) let entries_cb = ObjectValue(entries_fn.get()));
if !JS_CallFunctionValue(
raw_cx,
headers_obj.handle().into(),
entries_cb.handle().into(),
&empty_args,
r_h,
) {
JS_ClearPendingException(raw_cx);
}
r
});
if iter_val.is_object() {
rooted!(&in(cx_ref_resp) let iter_obj = iter_val.to_object());
for _ in 0..256 {
rooted!(&in(cx_ref_resp) let next_val = {
let mut r = UndefinedValue();
let r_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut r,
};
let empty_args = HandleValueArray {
length_: 0,
elements_: ::std::ptr::null(),
};
let mut next_fn = UndefinedValue();
let got_next = JS_GetProperty(
raw_cx,
iter_obj.handle().into(),
c"next".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut next_fn,
},
);
if !got_next || !next_fn.is_object() {
r = UndefinedValue();
} else {
rooted!(&in(cx_ref_resp) let next_cb = ObjectValue(next_fn.to_object()));
if !JS_CallFunctionValue(
raw_cx,
iter_obj.handle().into(),
next_cb.handle().into(),
&empty_args,
r_h,
) {
JS_ClearPendingException(raw_cx);
}
}
r
});
if !next_val.is_object() {
break;
}
rooted!(&in(cx_ref_resp) let next_obj = next_val.to_object());
let mut done_val = UndefinedValue();
let got_done = JS_GetProperty(
raw_cx,
next_obj.handle().into(),
c"done".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut done_val,
},
);
if !got_done {
JS_ClearPendingException(raw_cx);
break;
}
if done_val.is_boolean() && done_val.to_boolean() {
break;
}
let mut pair_val = UndefinedValue();
let got_pair = JS_GetProperty(
raw_cx,
next_obj.handle().into(),
c"value".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut pair_val,
},
);
if !got_pair || !pair_val.is_object() {
JS_ClearPendingException(raw_cx);
break;
}
rooted!(&in(cx_ref_resp) let pair_obj = pair_val.to_object());
let mut name_v = UndefinedValue();
let mut value_v = UndefinedValue();
let got_name = JS_GetElement(
raw_cx,
pair_obj.handle().into(),
0,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut name_v,
},
);
let got_value = JS_GetElement(
raw_cx,
pair_obj.handle().into(),
1,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut value_v,
},
);
if got_name && got_value && name_v.is_string() && value_v.is_string() {
pairs.push((
crate::js_to_rust_string(raw_cx, name_v),
crate::js_to_rust_string(raw_cx, value_v),
));
}
}
}
}
}
if headers_face && !pairs.is_empty() {
for (key, val_str) in pairs {
if key.eq_ignore_ascii_case("content-length") {
continue;
}
let c_key = ZBox::from_bytes(key.as_bytes());
let c_v = ZBox::from_bytes(val_str.as_bytes());
res_mut.write_header(c_key.as_bytes(), c_v.as_bytes());
}
} else {
let mut ids = mozjs::rust::IdVector::new(raw_cx);
let ok = GetPropertyKeys(
raw_cx,
headers_obj.handle().into(),
JSITER_OWNONLY,
ids.handle_mut(),
);
if ok {
for jsid in &*ids {
if !jsid.is_string() {
continue;
}
let key_str_ptr = jsid.to_string();
let key = unsafe_jsstr_to_string(raw_cx, NonNull::new_unchecked(key_str_ptr));
let mut header_val = UndefinedValue();
let c_key = ZBox::from_bytes(key.as_bytes());
JS_GetProperty(
raw_cx,
headers_obj.handle().into(),
c_key.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut header_val,
},
);
let val_str = if header_val.is_string() {
crate::js_to_rust_string(raw_cx, header_val)
} else {
continue;
};
if key.eq_ignore_ascii_case("content-length") {
continue;
}
let c_v = ZBox::from_bytes(val_str.as_bytes());
res_mut.write_header(c_key.as_bytes(), c_v.as_bytes());
}
}
}
}
let mut body_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(raw_cx, obj.handle().into(), c"_bodyText", &mut body_val);
let body_bytes: Vec<u8> = if body_val.is_string() {
crate::js_to_rust_string(raw_cx, body_val).into_bytes()
} else {
let mut bytes_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(raw_cx, obj.handle().into(), c"_bodyBytes", &mut bytes_val);
if bytes_val.is_object() {
crate::node_buffer::collect_byte_view(raw_cx, bytes_val).unwrap_or_default()
} else {
Vec::new()
}
};
res_mut.end(&body_bytes, true);
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn serve_write_default_response(res_mut: &mut Response<false>, req_ref: &Request) {
let method_bytes = req_ref.method();
let url_bytes = req_ref.url();
let method_str_lower = ::std::str::from_utf8(method_bytes).unwrap_or("get");
let method_str = method_str_lower.to_ascii_uppercase();
let url_str = ::std::str::from_utf8(url_bytes).unwrap_or("/");
let body = serde_json::json!({
"method": method_str,
"url": url_str,
})
.to_string();
let body_bytes = body.as_bytes();
res_mut.write_status(b"200 OK");
res_mut.write_header(b"Content-Type", b"application/json");
res_mut.end(body_bytes, true);
}
fn status_line_for(code: i32) -> String {
let reason = match code {
100 => "Continue",
101 => "Switching Protocols",
200 => "OK",
201 => "Created",
202 => "Accepted",
204 => "No Content",
206 => "Partial Content",
301 => "Moved Permanently",
302 => "Found",
304 => "Not Modified",
307 => "Temporary Redirect",
308 => "Permanent Redirect",
400 => "Bad Request",
401 => "Unauthorized",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
408 => "Request Timeout",
409 => "Conflict",
410 => "Gone",
413 => "Payload Too Large",
414 => "URI Too Long",
415 => "Unsupported Media Type",
426 => "Upgrade Required",
429 => "Too Many Requests",
500 => "Internal Server Error",
501 => "Not Implemented",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Gateway Timeout",
_ => "",
};
if reason.is_empty() {
format!("{}", code)
} else {
format!("{} {}", code, reason)
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_gc(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
JS_GC(cx, JS::GCReason::API);
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_sleep(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
args.rval().set(UndefinedValue());
return true;
}
let val = *args.get(0).ptr;
let ms = if val.is_int32() {
val.to_int32() as u64
} else if val.is_double() {
val.to_double() as u64
} else {
0
};
::std::thread::sleep(::std::time::Duration::from_millis(ms));
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_resolve(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"Bun.resolve requires a specifier".as_ptr());
return false;
}
let spec_val = *args.get(0).ptr;
if !spec_val.is_string() {
JS_ReportErrorUTF8(cx, c"Bun.resolve requires a string".as_ptr());
return false;
}
let specifier =
mozjs::conversions::unsafe_jsstr_to_string(cx, NonNull::new_unchecked(spec_val.to_string()));
let from = if argc > 1 && (*args.get(1).ptr).is_string() {
let from_str = mozjs::conversions::unsafe_jsstr_to_string(
cx,
NonNull::new_unchecked((*args.get(1).ptr).to_string()),
);
Some(::std::path::PathBuf::from(from_str))
} else {
::std::env::current_dir().ok()
};
let spec_path = ::std::path::Path::new(&specifier);
let resolved = if spec_path.is_absolute() {
spec_path.to_path_buf()
} else if specifier.starts_with("./") || specifier.starts_with("../") {
let base = from.as_deref().unwrap_or(::std::path::Path::new("."));
base.join(&specifier)
} else {
match crate::require::resolve_node_modules(&specifier, from.as_deref()) {
Some(p) => {
let s = p.to_string_lossy().into_owned();
let js_str = JS_NewStringCopyN(
cx,
s.as_ptr() as *const ::std::os::raw::c_char,
s.len(),
);
if !js_str.is_null() {
args.rval().set(mozjs::jsval::StringValue(&*js_str));
} else {
args.rval().set(UndefinedValue());
}
return true;
}
None => {
let msg = format!("Cannot resolve '{}'", specifier);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
}
};
let lexical = crate::node_path::normalize_path(&resolved);
let canonical = lexical.canonicalize().unwrap_or(lexical);
let s = canonical.to_string_lossy().into_owned();
let js_str = JS_NewStringCopyN(
cx,
s.as_ptr() as *const ::std::os::raw::c_char,
s.len(),
);
if !js_str.is_null() {
args.rval().set(mozjs::jsval::StringValue(&*js_str));
} else {
args.rval().set(UndefinedValue());
}
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_which(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
args.rval().set(NullValue());
return true;
}
let name_val = *args.get(0).ptr;
if !name_val.is_string() {
args.rval().set(NullValue());
return true;
}
let name = crate::js_to_rust_string(cx, name_val);
let path_var = ::std::env::var("PATH").unwrap_or_default();
let separator = if cfg!(windows) { ';' } else { ':' };
for dir in path_var.split(separator) {
let candidate = ::std::path::Path::new(dir).join(&name);
if candidate.exists() {
let result = candidate.to_string_lossy().into_owned();
let c_result = ZBox::from_vec(result.into_bytes());
let js_str = JS_NewStringCopyZ(cx, c_result.as_ptr());
if !js_str.is_null() {
args.rval().set(StringValue(&*js_str));
} else {
args.rval().set(NullValue());
}
return true;
}
#[cfg(target_family = "unix")]
{
let candidate = ::std::path::Path::new(dir).join(&name);
if candidate.exists() {
let result = candidate.to_string_lossy().into_owned();
let c_result = ZBox::from_vec(result.into_bytes());
let js_str = JS_NewStringCopyZ(cx, c_result.as_ptr());
if !js_str.is_null() {
args.rval().set(StringValue(&*js_str));
} else {
args.rval().set(NullValue());
}
return true;
}
}
}
args.rval().set(NullValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_build(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 || !(*args.get(0).ptr).is_object() {
JS_ReportErrorUTF8(
cx,
c"Expected a config object to be passed to Bun.build".as_ptr(),
);
return false;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let cfg = (*args.get(0).ptr).to_object());
let cfg_h = cfg.handle().into();
let mut entrypoints: Vec<String> = Vec::new();
let mut has_ep: bool = false;
JS_HasProperty(cx, cfg_h, c"entrypoints".as_ptr(), &mut has_ep);
if has_ep {
rooted!(&in(cx_ref) let mut ep_val = UndefinedValue());
JS_GetProperty(cx, cfg_h, c"entrypoints".as_ptr(), ep_val.handle_mut().into());
if ep_val.get().is_object() {
rooted!(&in(cx_ref) let ep_obj = ep_val.get().to_object());
let mut len_val = UndefinedValue();
let len_rv = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut len_val,
};
JS_GetProperty(cx, ep_obj.handle().into(), c"length".as_ptr(), len_rv);
if len_val.is_number() {
let len = len_val.to_number() as u32;
for i in 0..len {
let mut item_val = UndefinedValue();
let item_rv = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut item_val,
};
JS_GetElement(cx, ep_obj.handle().into(), i, item_rv);
if item_val.is_string() {
entrypoints.push(unsafe_jsstr_to_string(
cx,
NonNull::new_unchecked(item_val.to_string()),
));
}
}
}
}
}
if entrypoints.is_empty() {
JS_ReportErrorUTF8(
cx,
c"Bun.build: expected a non-empty array of entrypoints".as_ptr(),
);
return false;
}
let mut config = crate::bun_build::NativeBuildConfig {
entrypoints,
..Default::default()
};
config.outdir = get_opt_string(cx, cfg_h, c"outdir");
config.root = get_opt_string(cx, cfg_h, c"root");
config.public_path = get_opt_string(cx, cfg_h, c"publicPath");
config.banner = get_opt_string(cx, cfg_h, c"banner");
config.footer = get_opt_string(cx, cfg_h, c"footer");
config.naming = get_opt_string(cx, cfg_h, c"naming");
{
rooted!(&in(cx_ref) let mut naming_val = UndefinedValue());
JS_GetProperty(cx, cfg_h, c"naming".as_ptr(), naming_val.handle_mut().into());
if naming_val.get().is_object() {
rooted!(&in(cx_ref) let naming_obj = naming_val.get().to_object());
let naming_h = naming_obj.handle().into();
config.naming_entry = get_opt_string(cx, naming_h, c"entry");
config.naming_chunk = get_opt_string(cx, naming_h, c"chunk");
config.naming_asset = get_opt_string(cx, naming_h, c"asset");
config.naming = None;
}
}
config.target = get_opt_string(cx, cfg_h, c"target").unwrap_or_else(|| "browser".into());
config.format = get_opt_string(cx, cfg_h, c"format").unwrap_or_else(|| "esm".into());
config.sourcemap = get_opt_string(cx, cfg_h, c"sourcemap").unwrap_or_else(|| "none".into());
let mut has_minify: bool = false;
JS_HasProperty(cx, cfg_h, c"minify".as_ptr(), &mut has_minify);
if has_minify {
rooted!(&in(cx_ref) let mut min_val = UndefinedValue());
JS_GetProperty(cx, cfg_h, c"minify".as_ptr(), min_val.handle_mut().into());
if min_val.get().is_boolean() {
if min_val.get().to_boolean() {
config.minify = crate::bun_build::NativeMinify::all();
}
} else if min_val.get().is_object() {
rooted!(&in(cx_ref) let min_obj = min_val.get().to_object());
let min_h = min_obj.handle().into();
config.minify = crate::bun_build::NativeMinify {
whitespace: get_opt_bool(cx, min_h, c"whitespace").unwrap_or(false),
syntax: get_opt_bool(cx, min_h, c"syntax").unwrap_or(false),
identifiers: get_opt_bool(cx, min_h, c"identifiers").unwrap_or(false),
};
}
}
let mut has_ext: bool = false;
JS_HasProperty(cx, cfg_h, c"external".as_ptr(), &mut has_ext);
if has_ext {
rooted!(&in(cx_ref) let mut ext_val = UndefinedValue());
JS_GetProperty(cx, cfg_h, c"external".as_ptr(), ext_val.handle_mut().into());
if ext_val.get().is_object() {
rooted!(&in(cx_ref) let ext_obj = ext_val.get().to_object());
let mut len_val = UndefinedValue();
let len_rv = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut len_val,
};
JS_GetProperty(cx, ext_obj.handle().into(), c"length".as_ptr(), len_rv);
if len_val.is_number() {
let len = len_val.to_number() as u32;
for i in 0..len {
let mut item_val = UndefinedValue();
let item_rv = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut item_val,
};
JS_GetElement(cx, ext_obj.handle().into(), i, item_rv);
if item_val.is_string() {
config.external.push(unsafe_jsstr_to_string(
cx,
NonNull::new_unchecked(item_val.to_string()),
));
}
}
}
}
}
let mut has_def: bool = false;
JS_HasProperty(cx, cfg_h, c"define".as_ptr(), &mut has_def);
if has_def {
rooted!(&in(cx_ref) let mut def_val = UndefinedValue());
JS_GetProperty(cx, cfg_h, c"define".as_ptr(), def_val.handle_mut().into());
if def_val.get().is_object() {
rooted!(&in(cx_ref) let def_obj = def_val.get().to_object());
let mut ids = mozjs::rust::IdVector::new(cx);
if GetPropertyKeys(cx, def_obj.handle().into(), JSITER_OWNONLY, ids.handle_mut()) {
for jsid in &*ids {
if !jsid.is_string() {
continue;
}
let key_ptr = jsid.to_string();
if key_ptr.is_null() {
continue;
}
let key = unsafe_jsstr_to_string(cx, NonNull::new_unchecked(key_ptr));
let c_key = ZBox::from_bytes(key.as_bytes());
rooted!(&in(cx_ref) let mut v_val = UndefinedValue());
JS_GetProperty(
cx,
def_obj.handle().into(),
c_key.as_ptr(),
v_val.handle_mut().into(),
);
let value = if v_val.get().is_string() {
unsafe_jsstr_to_string(
cx,
NonNull::new_unchecked(v_val.get().to_string()),
)
} else if v_val.get().is_number() {
format!("{}", v_val.get().to_number())
} else if v_val.get().is_boolean() {
if v_val.get().to_boolean() { "true" } else { "false" }.to_string()
} else {
continue;
};
config.define.push((key, value));
}
}
}
}
config.splitting = get_opt_bool(cx, cfg_h, c"splitting").unwrap_or(false);
let mut has_jsx: bool = false;
JS_HasProperty(cx, cfg_h, c"jsx".as_ptr(), &mut has_jsx);
if has_jsx {
rooted!(&in(cx_ref) let mut jsx_val = UndefinedValue());
JS_GetProperty(cx, cfg_h, c"jsx".as_ptr(), jsx_val.handle_mut().into());
if jsx_val.get().is_object() {
rooted!(&in(cx_ref) let jsx_obj = jsx_val.get().to_object());
let jsx_h = jsx_obj.handle().into();
config.jsx_runtime = get_opt_string(cx, jsx_h, c"runtime");
config.jsx_factory = get_opt_string(cx, jsx_h, c"factory");
config.jsx_fragment = get_opt_string(cx, jsx_h, c"fragment");
config.jsx_import_source = get_opt_string(cx, jsx_h, c"importSource");
config.jsx_development = get_opt_bool(cx, jsx_h, c"development");
}
}
rooted!(&in(cx_ref) let promise = JS::NewPromiseObject(cx, HandleObject::null()));
if promise.get().is_null() {
args.rval().set(UndefinedValue());
return false;
}
let promise_val = ObjectValue(promise.get());
crate::bun_build::start(cx, promise_val, config);
args.rval().set(promise_val);
true
}
unsafe fn get_opt_string(
cx: *mut JSContext,
obj: mozjs::jsapi::Handle<*mut JSObject>,
name: &::std::ffi::CStr,
) -> Option<String> {
let mut has: bool = false;
unsafe { JS_HasProperty(cx, obj, name.as_ptr(), &mut has) };
if !has {
return None;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let mut val = UndefinedValue());
unsafe { JS_GetProperty(cx, obj, name.as_ptr(), val.handle_mut().into()) };
if val.get().is_string() {
Some(unsafe_jsstr_to_string(
cx,
NonNull::new_unchecked(val.get().to_string()),
))
} else {
None
}
}
unsafe fn get_opt_bool(
cx: *mut JSContext,
obj: mozjs::jsapi::Handle<*mut JSObject>,
name: &::std::ffi::CStr,
) -> Option<bool> {
let mut has: bool = false;
unsafe { JS_HasProperty(cx, obj, name.as_ptr(), &mut has) };
if !has {
return None;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let mut val = UndefinedValue());
unsafe { JS_GetProperty(cx, obj, name.as_ptr(), val.handle_mut().into()) };
if val.get().is_boolean() {
Some(val.get().to_boolean())
} else {
None
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_test(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc < 2 {
args.rval().set(UndefinedValue());
return true;
}
let name_val = *args.get(0).ptr;
let fn_val = *args.get(1).ptr;
if !name_val.is_string() || !fn_val.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let name = unsafe_jsstr_to_string(cx, NonNull::new_unchecked(name_val.to_string()));
let mut wrapped_cx_test = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref_test = &mut wrapped_cx_test;
rooted!(&in(cx_ref_test) let callback = fn_val.to_object());
let cb_id = TEST_CB_COUNTER.fetch_add(1, Ordering::Relaxed);
let callback_key = format!("test_cb_{}", cb_id);
gc_store_insert(cx, &callback_key, callback.get());
TEST_REGISTRY.with(|reg| {
reg.borrow_mut().push(TestCase { name, callback_key });
});
args.rval().set(UndefinedValue());
true
}
unsafe extern "C" fn test_run(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let global = CurrentGlobalOrNull(cx));
if global.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let mut passed: u32 = 0;
let mut failed: u32 = 0;
let mut failures: Vec<String> = Vec::new();
let tests: Vec<TestCase> = TEST_REGISTRY.with(|reg| ::std::mem::take(&mut *reg.borrow_mut()));
for tc in &tests {
rooted!(&in(cx_ref) let cb_obj = match gc_store_get(cx, &tc.callback_key) {
Some(obj) => obj,
None => {
eprint!("\n\u{2717} {} (callback GC'd)\n", tc.name);
failures.push(tc.name.clone());
failed += 1;
continue;
}
});
rooted!(&in(cx_ref) let cb_h = ObjectValue(cb_obj.get()));
let empty_args = HandleValueArray::empty();
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let ok = JS_CallFunctionValue(
cx,
global.handle().into(),
cb_h.handle().into(),
&empty_args,
rval_h,
);
if ok {
eprint!("\n\u{2713} {}\n", tc.name);
passed += 1;
} else {
JS_ClearPendingException(cx);
eprint!("\n\u{2717} {}\n", tc.name);
failures.push(tc.name.clone());
failed += 1;
}
}
for tc in &tests {
gc_store_remove(cx, &tc.callback_key);
}
let total = passed + failed;
eprint!(
"\n{} test(s) ran, {} passed, {} failed\n",
total, passed, failed
);
rooted!(&in(cx_ref) let result_obj = JS_NewPlainObject(cx_ref));
if result_obj.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let obj_h = result_obj.handle().into();
rooted!(&in(cx_ref) let total_root = Int32Value(total as i32));
JS_DefineProperty(
cx,
obj_h,
c"total".as_ptr(),
total_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let passed_root = Int32Value(passed as i32));
JS_DefineProperty(
cx,
obj_h,
c"passed".as_ptr(),
passed_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let failed_root = Int32Value(failed as i32));
JS_DefineProperty(
cx,
obj_h,
c"failed".as_ptr(),
failed_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
let success = failed == 0;
rooted!(&in(cx_ref) let success_root = BooleanValue(success));
JS_DefineProperty(
cx,
obj_h,
c"success".as_ptr(),
success_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
if !failures.is_empty() {
rooted!(&in(cx_ref) let fail_arr = NewArrayObject1(cx_ref, 0));
for (i, fname) in failures.iter().enumerate() {
let c_name = ZBox::from_bytes(fname.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_name.as_ptr());
if !js_str.is_null() {
let fval = StringValue(&*js_str);
rooted!(&in(cx_ref) let fv2 = fval);
JS_SetElement(cx, fail_arr.handle().into(), i as u32, fv2.handle().into());
}
}
let fav = ObjectValue(fail_arr.get());
rooted!(&in(cx_ref) let favh = fav);
JS_DefineProperty(
cx,
obj_h,
c"failures".as_ptr(),
favh.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
args.rval().set(ObjectValue(result_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 || args.get(0).ptr.is_null() {
args.rval().set(UndefinedValue());
return true;
}
let path_val = *args.get(0).ptr;
let mut wrapped_cx_early = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_early = &mut wrapped_cx_early;
if path_val.is_int32() || path_val.is_double() {
let fd = if path_val.is_int32() {
path_val.to_int32()
} else {
path_val.to_double() as i32
};
let fd_obj = make_bun_file_for_fd(cx_early, fd);
if fd_obj.is_null() {
args.rval().set(UndefinedValue());
return true;
}
args.rval().set(mozjs::jsval::ObjectValue(fd_obj));
return true;
}
let mut s: String;
if !path_val.is_string() && path_val.is_object() {
rooted!(&in(cx_early) let url_obj = path_val.to_object());
let mut href_val = UndefinedValue();
let href_ok = JS_GetProperty(
cx,
url_obj.handle().into(),
c"href".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut href_val,
},
);
if !href_ok || !href_val.is_string() {
args.rval().set(UndefinedValue());
return true;
}
let href = crate::js_to_rust_string(cx, href_val);
let mut protocol_val = UndefinedValue();
let proto_ok = JS_GetProperty(
cx,
url_obj.handle().into(),
c"protocol".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut protocol_val,
},
);
let protocol = if proto_ok && protocol_val.is_string() {
crate::js_to_rust_string(cx, protocol_val)
} else {
String::new()
};
if protocol == "file:" {
let bun_str = bun_core::String::borrow_utf8(href.as_bytes());
let result = bun_url::whatwg::path_from_file_url(&bun_str);
let path = if result.tag() == bun_core::Tag::Dead {
href.clone()
} else {
let utf8 = result.to_utf8();
let p = ::std::str::from_utf8(utf8.slice()).unwrap_or(&href).to_string();
result.deref();
p
};
s = percent_decode_path(&path);
} else {
let msg = format!(
"Bun.file() does not support {} URLs in this runtime (only file: URLs)",
if protocol.is_empty() { "this" } else { protocol.as_str() }
);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
} else if path_val.is_string() {
s = crate::js_to_rust_string(cx, path_val);
} else {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let file_obj = JS_NewPlainObject(cx_ref));
if file_obj.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let c_path = ZBox::from_bytes(s.as_bytes());
let path_js_str = JS_NewStringCopyZ(cx, c_path.as_ptr());
if !path_js_str.is_null() {
rooted!(&in(cx_ref) let val = StringValue(&*path_js_str));
JS_DefineProperty(
cx,
file_obj.handle().into(),
c"path".as_ptr(),
val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
if let Ok(meta) = bun_sys::fs::metadata(&s) {
rooted!(&in(cx_ref) let size_root = DoubleValue(meta.size as f64));
JS_DefineProperty(
cx,
file_obj.handle().into(),
c"size".as_ptr(),
size_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
define_bunfile_methods(cx_ref, file_obj.handle());
args.rval().set(mozjs::jsval::ObjectValue(file_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_file_exists(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let this = args.thisv();
if !this.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let obj = this.get().to_object());
let mut path_v = UndefinedValue();
let mut path = String::new();
if JS_GetProperty(
cx,
obj.handle().into(),
c"path".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut path_v,
},
) && path_v.is_string()
{
path = crate::js_to_rust_string(cx, path_v);
}
let exists = !path.is_empty() && bun_sys::fs::metadata(&path).is_ok();
rooted!(&in(cx_ref) let promise = JS::NewPromiseObject(cx, HandleObject::null()));
if promise.get().is_null() {
args.rval().set(mozjs::jsval::BooleanValue(exists));
return true;
}
rooted!(&in(cx_ref) let bv = mozjs::jsval::BooleanValue(exists));
let ok = JS::ResolvePromise(
cx,
promise.handle().into(),
bv.handle().into(),
);
args.rval().set(if ok {
mozjs::jsval::ObjectValue(promise.get())
} else {
JS_ClearPendingException(cx);
mozjs::jsval::BooleanValue(exists)
});
true
}
unsafe fn define_bunfile_methods(
cx: &mut mozjs::context::JSContext,
obj: mozjs::rust::Handle<*mut JSObject>,
) {
JS_DefineFunction(
cx,
obj,
c"exists".as_ptr(),
Some(bun_file_exists),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
obj,
c"text".as_ptr(),
Some(bun_file_text),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
obj,
c"json".as_ptr(),
Some(bun_file_json),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
obj,
c"arrayBuffer".as_ptr(),
Some(bun_file_array_buffer),
0,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
obj,
c"slice".as_ptr(),
Some(bun_file_slice),
3,
JSPROP_ENUMERATE as u32,
);
}
enum BunfileSrc {
Path(String),
Fd(i32),
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn bunfile_src(cx: *mut JSContext, this: Handle<Value>) -> Option<BunfileSrc> {
if !this.is_object() {
return None;
}
let mut wrapped = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let obj = this.get().to_object());
let mut fd_v = UndefinedValue();
if JS_GetProperty(
cx,
obj.handle().into(),
c"fd".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut fd_v,
},
) && fd_v.is_int32()
{
return Some(BunfileSrc::Fd(fd_v.to_int32()));
}
let mut path_v = UndefinedValue();
if JS_GetProperty(
cx,
obj.handle().into(),
c"path".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut path_v,
},
) && path_v.is_string()
{
let s = crate::js_to_rust_string(cx, path_v);
if !s.is_empty() {
return Some(BunfileSrc::Path(s));
}
}
None
}
fn bunfile_display_path(src: &BunfileSrc) -> String {
match src {
BunfileSrc::Path(p) => p.clone(),
BunfileSrc::Fd(fd) => format!("/proc/self/fd/{}", fd),
}
}
fn bunfile_io_code(err: &::std::io::Error) -> &'static str {
match err.kind() {
::std::io::ErrorKind::NotFound => "ENOENT",
::std::io::ErrorKind::PermissionDenied => "EACCES",
::std::io::ErrorKind::IsADirectory => "EISDIR",
::std::io::ErrorKind::NotADirectory => "ENOTDIR",
_ => "ERR",
}
}
fn read_fd_all(fd: i32) -> ::std::result::Result<Vec<u8>, ::std::io::Error> {
let mut st: libc::stat = unsafe { ::std::mem::zeroed() };
let cap = if unsafe { libc::fstat(fd, &mut st) } == 0 && st.st_size > 0 {
st.st_size as usize
} else {
64 * 1024
};
let mut out: Vec<u8> = Vec::with_capacity(cap);
let mut buf = vec![0u8; 64 * 1024];
let mut off: i64 = 0;
loop {
let n = unsafe { libc::pread(fd, buf.as_mut_ptr() as *mut _, buf.len(), off) };
if n < 0 {
let err = ::std::io::Error::last_os_error();
if err.raw_os_error() == Some(libc::ESPIPE) && off == 0 {
return read_fd_sequential(fd);
}
return Err(err);
}
if n == 0 {
return Ok(out);
}
out.extend_from_slice(&buf[..n as usize]);
off += n as i64;
}
}
fn read_fd_sequential(fd: i32) -> ::std::result::Result<Vec<u8>, ::std::io::Error> {
let mut out: Vec<u8> = Vec::new();
let mut buf = vec![0u8; 64 * 1024];
loop {
let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) };
if n < 0 {
return Err(::std::io::Error::last_os_error());
}
if n == 0 {
return Ok(out);
}
out.extend_from_slice(&buf[..n as usize]);
}
}
fn pread_range(fd: i32, start: usize, len: usize) -> ::std::result::Result<Vec<u8>, ::std::io::Error> {
let mut out = vec![0u8; len];
let mut filled = 0usize;
while filled < len {
let n = unsafe {
libc::pread(
fd,
out.as_mut_ptr().add(filled) as *mut _,
len - filled,
start as i64 + filled as i64,
)
};
if n < 0 {
return Err(::std::io::Error::last_os_error());
}
if n == 0 {
break;
}
filled += n as usize;
}
out.truncate(filled);
Ok(out)
}
fn read_path_range(path: &str, start: usize, len: usize) -> ::std::result::Result<Vec<u8>, ::std::io::Error> {
let c_path = ZBox::from_bytes(path.as_bytes());
let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
if fd < 0 {
return Err(::std::io::Error::last_os_error());
}
let res = pread_range(fd, start, len);
unsafe { libc::close(fd) };
res
}
#[derive(Clone, Copy, PartialEq)]
enum BunfileReadMode {
Text,
Json,
ArrayBuffer,
}
enum FileSettleErr {
Coded(&'static str, String),
Value(JSVal),
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn make_coded_error_value(cx: *mut JSContext, code: &str, msg: &str) -> Value {
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
let mut exn = UndefinedValue();
let exn_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut exn,
};
if !JS_GetPendingException(cx, exn_h) || !exn.is_object() {
JS_ClearPendingException(cx);
return UndefinedValue();
}
JS_ClearPendingException(cx);
let mut wrapped = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let exn_obj = exn.to_object());
let c_code = ZBox::from_bytes(code.as_bytes());
let code_js = JS_NewStringCopyZ(cx, c_code.as_ptr());
if !code_js.is_null() {
rooted!(&in(cx_ref) let code_val = StringValue(unsafe { &*code_js }));
JS_DefineProperty(
cx,
exn_obj.handle().into(),
c"code".as_ptr(),
code_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
ObjectValue(exn_obj.get())
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn js_string_from_utf8(cx: *mut JSContext, text: &str) -> *mut JSString {
let chars = mozjs::conversions::Utf8Chars::from(text);
mozjs_sys::jsapi::JS_NewStringCopyUTF8N(
cx,
&*chars as *const _ as *const mozjs_sys::jsapi::JS::UTF8Chars,
)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn file_bytes_to_value(
cx: *mut JSContext,
mode: BunfileReadMode,
bytes: &[u8],
) -> ::std::result::Result<JSVal, FileSettleErr> {
let bad_alloc = |what: &str| FileSettleErr::Coded("ERR", format!("{} allocation failed", what));
match mode {
BunfileReadMode::Text => {
let text = String::from_utf8_lossy(bytes);
let js_str = js_string_from_utf8(cx, &text);
if js_str.is_null() {
return Err(bad_alloc("string"));
}
Ok(StringValue(unsafe { &*js_str }))
}
BunfileReadMode::Json => {
let text = String::from_utf8_lossy(bytes);
let js_str = js_string_from_utf8(cx, &text);
if js_str.is_null() {
return Err(bad_alloc("string"));
}
let mut wrapped = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let str_root = js_str);
let mut parsed = UndefinedValue();
let parsed_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut parsed,
};
if JS_ParseJSON1(cx, str_root.handle().into(), parsed_h) {
return Ok(parsed);
}
if JS_IsExceptionPending(cx) {
let mut exn = UndefinedValue();
let exn_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut exn,
};
if JS_GetPendingException(cx, exn_h) {
JS_ClearPendingException(cx);
return Err(FileSettleErr::Value(exn));
}
}
JS_ClearPendingException(cx);
Err(FileSettleErr::Coded("ERR", "JSON parse failed".into()))
}
BunfileReadMode::ArrayBuffer => {
let ab = mozjs_sys::jsapi::JS::NewArrayBuffer(cx, bytes.len());
if ab.is_null() {
return Err(bad_alloc("array buffer"));
}
let mut wrapped = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let ab_root = ab);
if !bytes.is_empty() {
let mut is_shared = false;
let data_ptr = mozjs_sys::jsapi::JS::GetArrayBufferData(
ab_root.get(),
&mut is_shared,
::std::ptr::null(),
);
if data_ptr.is_null() {
return Err(FileSettleErr::Coded(
"ERR",
"array buffer data access failed".into(),
));
}
::std::ptr::copy_nonoverlapping(bytes.as_ptr(), data_ptr, bytes.len());
}
Ok(ObjectValue(ab_root.get()))
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn file_read_promise(cx: *mut JSContext, args: &CallArgs, mode: BunfileReadMode) -> bool {
let this = args.thisv();
let src = match bunfile_src(cx, this) {
Some(s) => s,
None => {
let msg = ZBox::from_bytes(
"BunFile method: receiver has no readable path or fd".as_bytes(),
);
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
}
};
let read_res: ::std::result::Result<Vec<u8>, ::std::io::Error> = match &src {
BunfileSrc::Fd(fd) => read_fd_all(*fd),
BunfileSrc::Path(p) => bun_fs::read(p),
};
let mut wrapped = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let promise = JS::NewPromiseObject(cx, HandleObject::null()));
if promise.get().is_null() {
JS_ClearPendingException(cx);
args.rval().set(UndefinedValue());
return true;
}
match read_res {
Err(e) => {
let code = bunfile_io_code(&e);
let msg = format!("open '{}': {}", bunfile_display_path(&src), e);
rooted!(&in(cx_ref) let err_val = make_coded_error_value(cx, code, &msg));
let _ = JS::RejectPromise(cx, promise.handle().into(), err_val.handle().into());
}
Ok(bytes) => match file_bytes_to_value(cx, mode, &bytes) {
Ok(v) => {
rooted!(&in(cx_ref) let rv = v);
let _ = JS::ResolvePromise(cx, promise.handle().into(), rv.handle().into());
}
Err(FileSettleErr::Coded(code, msg)) => {
rooted!(&in(cx_ref) let err_val = make_coded_error_value(cx, code, &msg));
let _ = JS::RejectPromise(cx, promise.handle().into(), err_val.handle().into());
}
Err(FileSettleErr::Value(v)) => {
rooted!(&in(cx_ref) let rv = v);
let _ = JS::RejectPromise(cx, promise.handle().into(), rv.handle().into());
}
},
}
args.rval().set(ObjectValue(promise.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_file_text(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
file_read_promise(cx, &args, BunfileReadMode::Text)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_file_json(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
file_read_promise(cx, &args, BunfileReadMode::Json)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_file_array_buffer(
cx: *mut JSContext,
_argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, _argc);
file_read_promise(cx, &args, BunfileReadMode::ArrayBuffer)
}
fn to_int32_f64(v: f64) -> f64 {
let m = v.trunc().rem_euclid(4294967296.0);
if m >= 2147483648.0 {
m - 4294967296.0
} else {
m
}
}
fn clamp_slice_index(v: Option<f64>, size: usize, default: f64) -> usize {
let size_f = size as f64;
let rel = match v {
None => default,
Some(x) if x.is_nan() => 0.0,
Some(x) => to_int32_f64(x),
};
let clamped = if rel < 0.0 {
(size_f + rel).max(0.0)
} else {
rel.min(size_f)
};
clamped as usize
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_file_slice(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let this = args.thisv();
let src = match bunfile_src(cx, this) {
Some(s) => s,
None => {
let msg =
ZBox::from_bytes("BunFile.slice: receiver has no readable path or fd".as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
}
};
let size_res: ::std::result::Result<usize, ::std::io::Error> = match &src {
BunfileSrc::Fd(fd) => {
let mut st: libc::stat = unsafe { ::std::mem::zeroed() };
if unsafe { libc::fstat(*fd, &mut st) } == 0 {
Ok(st.st_size.max(0) as usize)
} else {
Err(::std::io::Error::last_os_error())
}
}
BunfileSrc::Path(p) => bun_fs::metadata(p).map(|m| m.size as usize),
};
let size = match size_res {
Ok(s) => s,
Err(e) => {
let mut wrapped = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
let code = bunfile_io_code(&e);
let msg = format!("open '{}': {}", bunfile_display_path(&src), e);
rooted!(&in(cx_ref) let err_val = make_coded_error_value(cx, code, &msg));
JS_SetPendingException(
cx,
err_val.handle().into(),
ExceptionStackBehavior::DoNotCapture,
);
return false;
}
};
let begin_v = if argc > 0 {
let v = *args.get(0).ptr;
if v.is_int32() || v.is_double() {
Some(v.to_number())
} else {
None
}
} else {
None
};
let end_v = if argc > 1 {
let v = *args.get(1).ptr;
if v.is_int32() || v.is_double() {
Some(v.to_number())
} else {
None
}
} else {
None
};
let start = clamp_slice_index(begin_v, size, 0.0);
let stop = clamp_slice_index(end_v, size, size as f64);
let span = stop.saturating_sub(start);
let range_res: ::std::result::Result<Vec<u8>, ::std::io::Error> = match &src {
BunfileSrc::Fd(fd) => pread_range(*fd, start, span),
BunfileSrc::Path(p) => read_path_range(p, start, span),
};
let bytes = match range_res {
Ok(b) => b,
Err(e) => {
let msg =
ZBox::from_vec(format!("open '{}': {}", bunfile_display_path(&src), e).into_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
}
};
let mut wrapped = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
let u8_obj = mozjs_sys::jsapi::JS_NewUint8Array(cx, bytes.len());
if u8_obj.is_null() {
let msg = ZBox::from_bytes("BunFile.slice: typed array allocation failed".as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
}
rooted!(&in(cx_ref) let view = u8_obj);
if !bytes.is_empty() {
let mut is_shared = false;
let data_ptr = mozjs_sys::jsapi::JS_GetUint8ArrayData(
view.get(),
&mut is_shared,
::std::ptr::null(),
);
if !data_ptr.is_null() {
::std::ptr::copy_nonoverlapping(bytes.as_ptr(), data_ptr, bytes.len());
}
}
rooted!(&in(cx_ref) let parts = NewArrayObject1(cx_ref, 1));
if parts.get().is_null() {
let msg = ZBox::from_bytes("BunFile.slice: parts allocation failed".as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
}
rooted!(&in(cx_ref) let view_val = ObjectValue(view.get()));
if !JS_SetElement(cx, parts.handle().into(), 0, view_val.handle().into()) {
let msg = ZBox::from_bytes("BunFile.slice: parts element set failed".as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
}
rooted!(&in(cx_ref) let opts = JS_NewPlainObject(cx_ref));
if opts.get().is_null() {
let msg = ZBox::from_bytes("BunFile.slice: opts allocation failed".as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
}
let ct = if argc > 2 && (*args.get(2).ptr).is_string() {
crate::js_to_rust_string(cx, *args.get(2).ptr)
} else {
String::new()
};
let c_ct = ZBox::from_bytes(ct.as_bytes());
let ct_js = JS_NewStringCopyZ(cx, c_ct.as_ptr());
if !ct_js.is_null() {
rooted!(&in(cx_ref) let ct_val = StringValue(unsafe { &*ct_js }));
JS_DefineProperty(
cx,
opts.handle().into(),
c"type".as_ptr(),
ct_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
rooted!(&in(cx_ref) let global = CurrentGlobalOrNull(cx));
if global.get().is_null() {
let msg = ZBox::from_bytes("BunFile.slice: no global for Blob".as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
}
rooted!(&in(cx_ref) let parts_arg = ObjectValue(parts.get()));
rooted!(&in(cx_ref) let opts_arg = ObjectValue(opts.get()));
let elems = [parts_arg.handle().get(), opts_arg.handle().get()];
let call_args = HandleValueArray {
length_: 2,
elements_: elems.as_ptr(),
};
let mut blob = UndefinedValue();
let blob_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut blob,
};
let ok = JS_CallFunctionName(
cx,
global.handle().into(),
c"Blob".as_ptr(),
&call_args,
blob_h,
);
if !ok || !blob.is_object() {
if !ok {
JS_ClearPendingException(cx);
}
let msg = ZBox::from_bytes("BunFile.slice: Blob construction failed".as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), msg.as_ptr());
return false;
}
args.rval().set(blob);
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_write(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc < 2 {
JS_ReportErrorUTF8(cx, c"Bun.write requires 2 arguments".as_ptr());
return false;
}
let path_val = *args.get(0).ptr;
let content_val = *args.get(1).ptr;
if !path_val.is_string() || !content_val.is_string() {
JS_ReportErrorUTF8(cx, c"Bun.write requires string arguments".as_ptr());
return false;
}
let fpath = crate::js_to_rust_string(cx, path_val);
let content = crate::js_to_rust_string(cx, content_val);
match bun_sys::fs::write(fpath.as_str(), content.as_bytes()) {
Ok(()) => {
let written = DoubleValue(content.len() as f64);
args.rval().set(written);
true
}
Err(e) => {
let msg = format!("Bun.write failed: {}", e);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
false
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_read_file(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"Bun.readFile requires a path argument".as_ptr());
return false;
}
let path_val = *args.get(0).ptr;
if !path_val.is_string() {
JS_ReportErrorUTF8(cx, c"Bun.readFile requires a string path".as_ptr());
return false;
}
let fpath = crate::js_to_rust_string(cx, path_val);
match bun_sys::fs::read_to_string(fpath.as_str()) {
Ok(content) => {
let c_content = ZBox::from_bytes(content.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_content.as_ptr());
if !js_str.is_null() {
args.rval().set(StringValue(&*js_str));
} else {
args.rval().set(UndefinedValue());
}
true
}
Err(e) => {
let msg = format!("Bun.readFile failed: {}", e);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
false
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_cwd(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
match ::std::env::current_dir() {
Ok(dir) => {
let s = dir.to_string_lossy().into_owned();
let c_s = ZBox::from_bytes(s.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_s.as_ptr());
if !js_str.is_null() {
args.rval().set(StringValue(&*js_str));
} else {
args.rval().set(UndefinedValue());
}
}
Err(_) => {
args.rval().set(UndefinedValue());
}
}
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_cwd(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
process_cwd(cx, argc, vp)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_exit(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let code = if argc > 0 {
let v = *args.get(0).ptr;
if v.is_int32() { v.to_int32() } else { 0 }
} else {
0
};
crate::request_exit(code);
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_chdir(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"process.chdir requires a directory path".as_ptr());
return false;
}
let dir_val = *args.get(0).ptr;
if !dir_val.is_string() {
JS_ReportErrorUTF8(cx, c"process.chdir requires a string".as_ptr());
return false;
}
let dir = unsafe_jsstr_to_string(cx, NonNull::new_unchecked(dir_val.to_string()));
if let Err(e) = ::std::env::set_current_dir(&dir) {
let msg = format!("process.chdir failed: {}", e);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn process_write_chunk(cx: *mut JSContext, val: mozjs::jsval::JSVal) -> Vec<u8> {
if val.is_string() {
crate::js_to_rust_string(cx, val).into_bytes()
} else if let Some(bytes) = extract_bytes_from_jsval(cx, val) {
bytes
} else {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let val_root = val);
let jsstr = mozjs::rust::ToString(cx_ref, val_root.handle());
if jsstr.is_null() {
return Vec::new();
}
let str_val = StringValue(&*jsstr);
crate::js_to_rust_string(cx, str_val).into_bytes()
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn process_write(
cx: *mut JSContext,
argc: u32,
vp: *mut JSVal,
dest: bun_core::output::Destination,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc > 0 {
let val = *args.get(0).ptr;
let chunk = process_write_chunk(cx, val);
if !chunk.is_empty() {
bun_core::output::Source::ensure_thread_source();
bun_core::output::write_bytes(dest, &chunk);
bun_core::output::flush();
}
}
args.rval().set(mozjs::jsval::BooleanValue(true));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_stdout_write(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
process_write(
cx,
argc,
vp,
bun_core::output::Destination::Stdout,
)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_stderr_write(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
process_write(
cx,
argc,
vp,
bun_core::output::Destination::Stderr,
)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_noop(_cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_binding(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
args.rval().set(UndefinedValue());
return true;
}
let name_val = *args.get(0).ptr;
let name = if name_val.is_string() {
crate::js_to_rust_string(cx, name_val)
} else {
String::new()
};
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj = JS_NewPlainObject(cx_ref));
if obj.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let obj_h = obj.handle().into();
match name.as_str() {
"tty_wrap" => {
let tty_fn = mozjs_sys::jsapi::JS_NewFunction(
cx,
::std::option::Option::Some(tty_wrap_tty_ctor),
1,
mozjs_sys::jsapi::JSFUN_CONSTRUCTOR,
c"TTY".as_ptr(),
);
if !tty_fn.is_null() {
let tty_obj = mozjs_sys::jsapi::JS_GetFunctionObject(tty_fn);
rooted!(&in(cx_ref) let proto = JS_NewPlainObject(cx_ref));
if !proto.get().is_null() {
let proto_h = proto.handle().into();
for name in [c"getWindowSize".as_ptr(), c"setRawMode".as_ptr()] {
let m = mozjs_sys::jsapi::JS_NewFunction(
cx,
::std::option::Option::Some(process_noop),
1,
0,
name,
);
if !m.is_null() {
let m_obj = mozjs_sys::jsapi::JS_GetFunctionObject(m);
rooted!(&in(cx_ref) let m_val = mozjs::jsval::ObjectValue(m_obj));
let _ = mozjs_sys::jsapi::JS_DefineProperty(
cx,
proto_h,
name,
m_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
rooted!(&in(cx_ref) let tty_obj_r = tty_obj);
let proto_val = mozjs::jsval::ObjectValue(proto.get());
rooted!(&in(cx_ref) let proto_h_val = proto_val);
let _ = mozjs_sys::jsapi::JS_DefineProperty(
cx,
tty_obj_r.handle().into(),
c"prototype".as_ptr(),
proto_h_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
rooted!(&in(cx_ref) let tty_val = mozjs::jsval::ObjectValue(tty_obj));
let _ = mozjs_sys::jsapi::JS_DefineProperty(
cx,
obj_h,
c"TTY".as_ptr(),
tty_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let istty_fn = mozjs_sys::jsapi::JS_NewFunction(
cx,
::std::option::Option::Some(tty_wrap_is_tty),
1,
0,
c"isTTY".as_ptr(),
);
if !istty_fn.is_null() {
let istty_obj = mozjs_sys::jsapi::JS_GetFunctionObject(istty_fn);
rooted!(&in(cx_ref) let istty_val = mozjs::jsval::ObjectValue(istty_obj));
let _ = mozjs_sys::jsapi::JS_DefineProperty(
cx,
obj_h,
c"isTTY".as_ptr(),
istty_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
"tcp_wrap" | "pipe_wrap" | "udp_wrap" | "fs_wrap" | "spawn_wrap" | "signal_wrap"
| "timer_wrap" | "stream_wrap" | "crypto" | "fs" | "tty" | "tcp" | "udp" | "pipe" => {
let ctor = mozjs_sys::jsapi::JS_NewFunction(
cx,
::std::option::Option::Some(process_noop),
0,
0,
b"Constructor\0".as_ptr() as *const _,
);
if !ctor.is_null() {
let ctor_obj = mozjs_sys::jsapi::JS_GetFunctionObject(ctor);
rooted!(&in(cx_ref) let ctor_val = mozjs::jsval::ObjectValue(ctor_obj));
let _ = mozjs_sys::jsapi::JS_DefineProperty(
cx,
obj_h,
c"Constructor".as_ptr(),
ctor_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
_ => {
}
}
args.rval().set(mozjs::jsval::ObjectValue(obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tty_wrap_tty_ctor(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let fd = if argc > 0 && (*args.get(0).ptr).is_int32() {
(*args.get(0).ptr).to_int32()
} else {
-1
};
if fd < 0 || libc::isatty(fd) != 1 {
let c_msg = c"UV_EINVAL: invalid file descriptor";
mozjs::error::throw_type_error(cx, c_msg.as_ref());
return false;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj = JS_NewPlainObject(cx_ref));
if obj.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let obj_h = obj.handle().into();
let fd_val = mozjs::jsval::Int32Value(fd);
let h = Handle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &fd_val,
};
let _ =
mozjs_sys::jsapi::JS_DefineProperty(cx, obj_h, c"fd".as_ptr(), h, JSPROP_ENUMERATE as u32);
args.rval().set(mozjs::jsval::ObjectValue(obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn tty_wrap_is_tty(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let fd = if argc > 0 && (*args.get(0).ptr).is_int32() {
(*args.get(0).ptr).to_int32()
} else {
-1
};
let result = if fd >= 0 { libc::isatty(fd) } else { 0 };
args.rval().set(mozjs::jsval::BooleanValue(result == 1));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_exitcode_get(
_cx: *mut JSContext,
_argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, _argc);
args.rval().set(Int32Value(crate::exit_code()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_exitcode_set(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc > 0 {
let v = *args.get(0).ptr;
let code = if v.is_int32() {
v.to_int32()
} else if v.is_double() {
v.to_double() as i32
} else if v.is_null_or_undefined() {
0
} else if v.is_string() {
crate::js_to_rust_string(_cx, v)
.trim()
.parse::<i32>()
.unwrap_or(0)
} else {
0
};
crate::set_exit_code(code);
}
args.rval().set(BooleanValue(true));
true
}
pub fn dispatch_exit_handlers(cx: *mut JSContext) -> bool {
let global = unsafe { CurrentGlobalOrNull(cx) };
if global.is_null() {
return false;
}
let mut wrapped_cx = unsafe { mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx)) };
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let global_root = global);
let mut proc_val = UndefinedValue();
unsafe {
JS_GetProperty(
cx,
global_root.handle().into(),
c"process".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut proc_val,
},
);
}
if !proc_val.is_object() {
return false;
}
rooted!(&in(cx_ref) let proc_obj = proc_val.to_object());
let code = crate::exit_code();
let exit_str = unsafe { JS_NewStringCopyZ(cx, c"exit".as_ptr()) };
if exit_str.is_null() {
return false;
}
rooted!(&in(cx_ref) let exit_str_val = unsafe { StringValue(&*exit_str) });
let args_vals = [*exit_str_val.handle(), Int32Value(code)];
let call_args = HandleValueArray {
length_: 2,
elements_: args_vals.as_ptr(),
};
let mut rval = UndefinedValue();
let ok = unsafe {
JS_CallFunctionName(
cx,
proc_obj.handle().into(),
c"emit".as_ptr(),
&call_args,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
)
};
if !ok {
unsafe { JS_ClearPendingException(cx) };
return false;
}
rval.is_boolean() && rval.to_boolean()
}
pub fn post_eval_drain_then_exit(cx: &mut mozjs::context::JSContext) -> bool {
let more = crate::timers::drain_and_check(cx);
if !more {
dispatch_exit_handlers(unsafe { cx.raw_cx() });
}
more
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_next_tick(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"process.nextTick() requires a callback".as_ptr());
return false;
}
let cb_val = *args.get(0).ptr;
if !cb_val.is_object() {
JS_ReportErrorUTF8(
cx,
c"process.nextTick() callback must be a function".as_ptr(),
);
return false;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let cb_obj = cb_val.to_object());
rooted!(&in(cx_ref) let global = CurrentGlobalOrNull(cx));
if global.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let mut qmt_val = UndefinedValue();
let qmt_rv = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut qmt_val,
};
let qmt_name = ZBox::from_bytes("queueMicrotask".as_bytes());
JS_GetProperty(cx, global.handle().into(), qmt_name.as_ptr(), qmt_rv);
if qmt_val.is_object() {
let _qmt_obj = qmt_val.to_object();
rooted!(&in(cx_ref) let cb_val_obj = mozjs::jsval::ObjectValue(cb_obj.get()));
let _empty_args = HandleValueArray::empty();
let cb_name = ZBox::from_bytes("__nextTickCb".as_bytes());
JS_SetProperty(
cx,
global.handle().into(),
cb_name.as_ptr(),
cb_val_obj.handle().into(),
);
let eval_src = "queueMicrotask(__nextTickCb); delete globalThis.__nextTickCb;";
let _c_src = ZBox::from_bytes(eval_src.as_bytes());
let c_filename = ZBox::from_bytes("<nextTick>".as_bytes());
let opts = mozjs::glue::NewCompileOptions(cx, c_filename.as_ptr(), 1);
if !opts.is_null() {
let mut src = mozjs::rust::transform_str_to_source_text(eval_src);
let mut eval_rval = UndefinedValue();
let eval_rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut eval_rval,
};
mozjs_sys::jsapi::JS::Evaluate2(cx, opts, &mut src, eval_rval_h);
libc::free(opts as *mut _);
}
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_hrtime(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let now = ::std::time::SystemTime::now()
.duration_since(::std::time::UNIX_EPOCH)
.unwrap_or_default();
let sec = now.as_secs() as i32;
let nsec = now.subsec_nanos() as i32;
let mut wrapped_cx =
mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let arr = unsafe { NewArrayObject1(cx_ref, 2) });
if arr.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
rooted!(&in(cx_ref) let sec_val = Int32Value(sec));
unsafe {
JS_DefineElement(
cx,
arr.handle().into(),
0,
sec_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
rooted!(&in(cx_ref) let nsec_val = Int32Value(nsec));
unsafe {
JS_DefineElement(
cx,
arr.handle().into(),
1,
nsec_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let bigint_fn = unsafe { JS_NewFunction(cx, Some(hrtime_bigint), 0, 0, c"bigint".as_ptr()) };
if !bigint_fn.is_null() {
let fn_obj = unsafe { JS_GetFunctionObject(bigint_fn) };
rooted!(&in(cx_ref) let fn_val = mozjs::jsval::ObjectValue(fn_obj));
unsafe {
JS_DefineProperty(
cx,
arr.handle().into(),
c"bigint".as_ptr(),
fn_val.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
args.rval().set(mozjs::jsval::ObjectValue(arr.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn hrtime_bigint(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let now = ::std::time::SystemTime::now()
.duration_since(::std::time::UNIX_EPOCH)
.unwrap_or_default();
let total_ns = (now.as_secs() as i64) * 1_000_000_000i64 + (now.subsec_nanos() as i64);
let src = format!("BigInt(\"{}\")", total_ns);
let mut rval = UndefinedValue();
let opts = mozjs::glue::NewCompileOptions(cx, c"hrtime_bigint".as_ptr(), 1);
if !opts.is_null() {
let mut eval_src = mozjs::rust::transform_str_to_source_text(&src);
mozjs_sys::jsapi::JS::Evaluate2(
cx,
opts,
&mut eval_src,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
},
);
libc::free(opts as *mut _);
}
args.rval().set(rval);
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_uptime(_cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let uptime_secs = match PROCESS_START.with(|s| *s.borrow()) {
Some(start) => {
let now = ::std::time::Instant::now();
now.duration_since(start).as_secs_f64()
}
None => 0.0,
};
args.rval().set(mozjs::jsval::DoubleValue(uptime_secs));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_memory_usage(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj = JS_NewPlainObject(cx_ref));
if obj.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let obj_h = obj.handle().into();
let rss = bun_fs::read_to_string("/proc/self/statm")
.ok()
.and_then(|s| {
s.split_whitespace()
.nth(1)
.and_then(|v| v.parse::<f64>().ok())
})
.unwrap_or(0.0)
* 4096.0;
rooted!(&in(cx_ref) let rss_root = mozjs::jsval::DoubleValue(rss));
JS_DefineProperty(
cx,
obj_h,
c"rss".as_ptr(),
rss_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let heap_total_root = mozjs::jsval::DoubleValue(0.0));
JS_DefineProperty(
cx,
obj_h,
c"heapTotal".as_ptr(),
heap_total_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let heap_used_root = mozjs::jsval::DoubleValue(0.0));
JS_DefineProperty(
cx,
obj_h,
c"heapUsed".as_ptr(),
heap_used_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let external_root = mozjs::jsval::DoubleValue(0.0));
JS_DefineProperty(
cx,
obj_h,
c"external".as_ptr(),
external_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx_ref) let array_buffers_root = mozjs::jsval::DoubleValue(0.0));
JS_DefineProperty(
cx,
obj_h,
c"arrayBuffers".as_ptr(),
array_buffers_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
args.rval().set(ObjectValue(obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_memory_usage_rss(
_cx: *mut JSContext,
_argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let rss = bun_fs::read_to_string("/proc/self/statm")
.ok()
.and_then(|s| {
s.split_whitespace()
.nth(1)
.and_then(|v| v.parse::<f64>().ok())
})
.unwrap_or(0.0)
* 4096.0;
args.rval().set(mozjs::jsval::DoubleValue(rss));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_kill(_cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
if _argc < 1 {
args.rval().set(BooleanValue(false));
return true;
}
let pid_val = args.get(0);
let pid = if pid_val.is_int32() {
pid_val.to_int32()
} else {
args.rval().set(BooleanValue(false));
return true;
};
let sig_num = if _argc >= 2 {
let sig_val = args.get(1);
if sig_val.is_int32() {
sig_val.to_int32()
} else {
15
}
} else {
15
};
let _ = libc::kill(pid, sig_num);
args.rval().set(BooleanValue(true));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn process_umask(_cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let old = unsafe { libc::umask(0o022) };
unsafe { libc::umask(old) };
args.rval().set(Int32Value(old as i32));
true
}
thread_local! {
static PROCESS_START: RefCell<Option<::std::time::Instant>> = const { RefCell::new(None) };
}
pub fn init_process_start() {
PROCESS_START.with(|s| *s.borrow_mut() = Some(::std::time::Instant::now()));
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn set_env_fn(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc < 2 {
args.rval().set(UndefinedValue());
return true;
}
let key_val = *args.get(0).ptr;
let val_val = *args.get(1).ptr;
if !key_val.is_string() || !val_val.is_string() {
args.rval().set(UndefinedValue());
return true;
}
let key = crate::js_to_rust_string(cx, key_val);
let value = crate::js_to_rust_string(cx, val_val);
if !key.is_empty() && !key.contains('\0') && !value.contains('\0') {
::std::env::set_var(&key, &value);
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn del_env_fn(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc < 1 {
args.rval().set(UndefinedValue());
return true;
}
let key_val = *args.get(0).ptr;
if !key_val.is_string() {
args.rval().set(UndefinedValue());
return true;
}
let key = crate::js_to_rust_string(cx, key_val);
if !key.is_empty() && !key.contains('\0') {
::std::env::remove_var(&key);
}
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_exit(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let code = if argc > 0 && (*args.get(0).ptr).is_int32() {
(*args.get(0).ptr).to_int32()
} else {
0
};
crate::request_exit(code);
args.rval().set(UndefinedValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_sleep_sync(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc > 0 && (*args.get(0).ptr).is_number() {
let ms = (*args.get(0).ptr).to_number() as u64;
::std::thread::sleep(::std::time::Duration::from_millis(ms));
}
args.rval().set(UndefinedValue());
true
}
static BAO_PROCESS_START_NS: ::std::sync::OnceLock<Option<u64>> = ::std::sync::OnceLock::new();
fn process_start_ns_since_boot() -> Option<u64> {
let stat = ::std::fs::read_to_string("/proc/self/stat").ok()?;
let after = stat.rsplit(')').next()?;
let fields: Vec<&str> = after.split_whitespace().collect();
let ticks: u64 = fields.get(19)?.parse().ok()?;
let hz = unsafe { libc::sysconf(libc::_SC_CLK_TCK) } as u64;
if hz == 0 {
return None;
}
Some(ticks * 1_000_000_000 / hz)
}
fn boottime_now_ns() -> Option<u64> {
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
if unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut ts) } != 0 {
return None;
}
Some(ts.tv_sec as u64 * 1_000_000_000 + ts.tv_nsec as u64)
}
unsafe extern "C" fn bun_nanoseconds(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let start = BAO_PROCESS_START_NS.get_or_init(process_start_ns_since_boot);
let elapsed_ns = match (start, boottime_now_ns()) {
(Some(s), Some(now)) => now.saturating_sub(*s),
_ => {
static FALLBACK_START: ::std::sync::OnceLock<::std::time::Instant> =
::std::sync::OnceLock::new();
FALLBACK_START
.get_or_init(::std::time::Instant::now)
.elapsed()
.as_nanos() as u64
}
};
let v = mozjs::jsval::DoubleValue(elapsed_ns as f64);
args.rval().set(v);
let _ = cx;
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_env_getter(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
let global = CurrentGlobalOrNull(cx);
if global.is_null() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped;
rooted!(&in(cx_ref) let global_root = global);
let mut process_v = UndefinedValue();
if !JS_GetProperty(
cx,
global_root.handle().into(),
c"process".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut process_v,
},
) || !process_v.is_object()
{
JS_ClearPendingException(cx);
args.rval().set(UndefinedValue());
return true;
}
rooted!(&in(cx_ref) let proc_obj = process_v.to_object());
let mut env_v = UndefinedValue();
if !JS_GetProperty(
cx,
proc_obj.handle().into(),
c"env".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut env_v,
},
) || env_v.is_null_or_undefined()
{
JS_ClearPendingException(cx);
args.rval().set(UndefinedValue());
return true;
}
args.rval().set(env_v);
true
}
#[allow(unsafe_op_in_unsafe_fn)]
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_concat_array_buffers(
cx: *mut JSContext,
argc: u32,
vp: *mut JSVal,
) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 || !(*args.get(0).ptr).is_object() {
args.rval().set(UndefinedValue());
return true;
}
let list_val = *args.get(0).ptr;
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let list_obj = list_val.to_object());
let list_h = list_obj.handle().into();
unsafe fn is_array_buffer(obj: *mut JSObject) -> bool {
mozjs_sys::jsapi::JS::IsArrayBufferObjectMaybeShared(obj)
}
unsafe fn ab_bytes(obj: *mut JSObject) -> (usize, *mut u8) {
let mut len: usize = 0;
let mut is_shared = false;
let mut data: *mut u8 = ::std::ptr::null_mut();
mozjs_sys::jsapi::JS::GetArrayBufferMaybeSharedLengthAndData(
obj,
&mut len,
&mut is_shared,
&mut data,
);
(len, data)
}
unsafe fn ta_bytes(obj: *mut JSObject) -> (usize, *mut u8) {
let mut length: usize = 0;
let mut is_shared = false;
let mut data: *mut u8 = ::std::ptr::null_mut();
let unwrapped =
mozjs_sys::jsapi::JS_GetObjectAsUint8Array(obj, &mut length, &mut is_shared, &mut data);
if unwrapped.is_null() {
(0, ::std::ptr::null_mut())
} else {
(length, data)
}
}
let mut len_val = UndefinedValue();
JS_GetProperty(
cx,
list_h,
c"length".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut len_val,
},
);
let list_len: usize = if len_val.is_int32() {
len_val.to_int32().max(0) as usize
} else if len_val.is_double() {
let d = len_val.to_double();
if d.is_finite() && d > 0.0 {
d as usize
} else {
0
}
} else {
0
};
let mut element_objs: Vec<*mut JSObject> = Vec::with_capacity(list_len);
for i in 0..list_len {
let mut elem = UndefinedValue();
JS_GetElement(
cx,
list_h,
i as u32,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut elem,
},
);
element_objs.push(if elem.is_object() {
elem.to_object()
} else {
::std::ptr::null_mut()
});
}
let mut element_lengths: Vec<usize> = Vec::with_capacity(list_len);
let mut element_data: Vec<*mut u8> = Vec::with_capacity(list_len);
let mut total: usize = 0;
for obj in element_objs.iter() {
if obj.is_null() {
element_lengths.push(0);
element_data.push(::std::ptr::null_mut());
continue;
}
let (len, data) = if unsafe { is_array_buffer(*obj) } {
unsafe { ab_bytes(*obj) }
} else {
unsafe { ta_bytes(*obj) }
};
if data.is_null() {
let c_msg = c"Cannot perform Bun.concatArrayBuffers on a detached ArrayBuffer";
mozjs::error::throw_type_error(cx, c_msg.as_ref());
return false;
}
element_lengths.push(len);
element_data.push(data);
total = total.saturating_add(len);
}
let mut target_total = total;
if argc > 1 {
let tl_val = *args.get(1).ptr;
if tl_val.is_int32() {
let v = tl_val.to_int32();
if v >= 0 {
target_total = v as usize;
}
} else if tl_val.is_double() {
let d = tl_val.to_double();
if d.is_finite() && d >= 0.0 {
target_total = d as usize;
}
} else if !tl_val.is_undefined() {
if !(tl_val.is_double()
&& tl_val.to_double().is_infinite()
&& tl_val.to_double().is_sign_positive())
{
target_total = total;
}
}
}
const MAX_CONCAT_BYTES: usize = 4 * 1024 * 1024 * 1024 - 1; if target_total > MAX_CONCAT_BYTES {
let msg = format!(
"Failed to allocate ArrayBuffer of size {} bytes (exceeds {} byte limit)",
target_total, MAX_CONCAT_BYTES
);
let c_msg = ::std::ffi::CString::new(msg).unwrap_or_else(|_e| {
::std::ffi::CString::new("Failed to allocate ArrayBuffer").unwrap()
});
mozjs::error::throw_range_error(cx, c_msg.as_ref());
return false;
}
let mut all_bytes: Vec<u8> = match target_total.checked_mul(1) {
Some(_) => Vec::new(),
None => {
let c_msg = c"Failed to allocate ArrayBuffer: size overflow";
mozjs::error::throw_range_error(cx, c_msg.as_ref());
return false;
}
};
if let Err(_) = all_bytes.try_reserve(target_total) {
let msg = format!(
"Failed to allocate ArrayBuffer of size {} bytes",
target_total
);
let c_msg = ::std::ffi::CString::new(msg).unwrap_or_else(|_e| {
::std::ffi::CString::new("Failed to allocate ArrayBuffer").unwrap()
});
mozjs::error::throw_range_error(cx, c_msg.as_ref());
return false;
}
all_bytes.resize(target_total, 0u8);
let mut cursor: usize = 0;
for (i, obj) in element_objs.iter().enumerate() {
if obj.is_null() || cursor >= target_total {
continue;
}
let len = *element_lengths.get(i).unwrap_or(&0);
if len == 0 {
continue;
}
let data = *element_data.get(i).unwrap_or(&::std::ptr::null_mut());
let copy_len = len.min(target_total.saturating_sub(cursor));
if copy_len > 0 && !data.is_null() {
::std::ptr::copy_nonoverlapping(data, all_bytes.as_mut_ptr().add(cursor), copy_len);
}
cursor = cursor.saturating_add(len);
}
let as_uint8 = argc > 2 && (*args.get(2).ptr).to_boolean();
let cx_ref = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
if as_uint8 {
let u8_obj = mozjs_sys::jsapi::JS_NewUint8Array(cx, all_bytes.len());
if u8_obj.is_null() {
args.rval().set(UndefinedValue());
return true;
}
rooted!(&in(cx_ref) let u8_root = u8_obj);
if !all_bytes.is_empty() {
let mut is_shared = false;
let data_ptr = mozjs_sys::jsapi::JS_GetUint8ArrayData(
u8_root.get(),
&mut is_shared,
::std::ptr::null(),
);
if !data_ptr.is_null() {
::std::ptr::copy_nonoverlapping(all_bytes.as_ptr(), data_ptr, all_bytes.len());
}
}
args.rval().set(mozjs::jsval::ObjectValue(u8_root.get()));
} else {
let ab_obj = mozjs_sys::jsapi::JS::NewArrayBuffer(cx, all_bytes.len());
if ab_obj.is_null() {
args.rval().set(UndefinedValue());
return true;
}
rooted!(&in(cx_ref) let ab_root = ab_obj);
if !all_bytes.is_empty() {
let mut is_shared = false;
let data_ptr = mozjs_sys::jsapi::JS::GetArrayBufferMaybeSharedData(
ab_root.get(),
&mut is_shared,
::std::ptr::null(),
);
if !data_ptr.is_null() {
::std::ptr::copy_nonoverlapping(all_bytes.as_ptr(), data_ptr, all_bytes.len());
}
}
args.rval().set(mozjs::jsval::ObjectValue(ab_root.get()));
}
true
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
enum CryptoHasherAlgo {
Sha256 = 0,
Sha512 = 1,
Sha1 = 2,
Md5 = 3,
}
struct CryptoHasherState {
algo: CryptoHasherAlgo,
data: Vec<u8>,
}
#[allow(dead_code)]
static CRYPTO_HASHER_CB_COUNTER: AtomicU64 = AtomicU64::new(0);
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_crypto_hasher_ctor(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let algo_str = if argc > 0 && (*args.get(0).ptr).is_string() {
crate::js_to_rust_string(cx, *args.get(0).ptr).to_ascii_lowercase()
} else {
"sha256".to_string()
};
let algo = match algo_str.as_str() {
"sha256" | "sha-256" => CryptoHasherAlgo::Sha256,
"sha512" | "sha-512" => CryptoHasherAlgo::Sha512,
"sha1" | "sha-1" => CryptoHasherAlgo::Sha1,
"md5" => CryptoHasherAlgo::Md5,
_ => {
let msg = format!("Unsupported algorithm: {}", algo_str);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
};
let state = Box::new(CryptoHasherState {
algo,
data: Vec::new(),
});
let state_ptr = Box::into_raw(state) as *mut ::std::ffi::c_void;
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj = JS_NewPlainObject(cx_ref));
if obj.get().is_null() {
args.rval().set(UndefinedValue());
return true;
}
let priv_val = mozjs::jsval::PrivateValue(state_ptr);
rooted!(&in(cx_ref) let pv = priv_val);
JS_DefineProperty(
cx,
obj.handle().into(),
c"_statePtr".as_ptr(),
pv.handle().into(),
0,
);
let algo_val = Int32Value(algo as i32);
rooted!(&in(cx_ref) let av = algo_val);
JS_DefineProperty(
cx,
obj.handle().into(),
c"_algo".as_ptr(),
av.handle().into(),
0,
);
JS_DefineFunction(
cx_ref,
obj.handle(),
c"update".as_ptr(),
Some(crypto_hasher_update),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx_ref,
obj.handle(),
c"digest".as_ptr(),
Some(crypto_hasher_digest),
0,
JSPROP_ENUMERATE as u32,
);
args.rval().set(ObjectValue(obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn crypto_hasher_update(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
args.rval().set(*args.thisv().ptr);
return true;
}
let this = args.thisv();
if !this.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = this.to_object());
let mut state_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(
cx,
this_obj.handle().into(),
c"_statePtr",
&mut state_val,
);
if !state_val.is_double() || (state_val.asBits_ & 0xFFFF000000000000) != 0 {
args.rval().set(*args.thisv().ptr);
return true;
}
let state_ptr = state_val.to_private() as *mut CryptoHasherState;
if state_ptr.is_null() {
args.rval().set(*args.thisv().ptr);
return true;
}
let input = *args.get(0).ptr;
let data = if input.is_string() {
crate::js_to_rust_string(cx, input).into_bytes()
} else if input.is_object() {
rooted!(&in(cx_ref) let arr_obj = input.to_object());
let mut len_val = UndefinedValue();
JS_GetProperty(
cx,
arr_obj.handle().into(),
c"length".as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut len_val,
},
);
let len = if len_val.is_int32() {
len_val.to_int32().max(0) as u32
} else {
0
};
let mut bytes = Vec::with_capacity(len as usize);
for i in 0..len {
let mut byte_val = Int32Value(0);
JS_GetElement(
cx,
arr_obj.handle().into(),
i,
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut byte_val,
},
);
bytes.push(if byte_val.is_int32() {
byte_val.to_int32() as u8
} else {
0
});
}
bytes
} else {
Vec::new()
};
(*state_ptr).data.extend_from_slice(&data);
args.rval().set(ObjectValue(this_obj.get()));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn crypto_hasher_digest(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
let this = args.thisv();
if !this.is_object() {
args.rval().set(UndefinedValue());
return true;
}
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let this_obj = this.to_object());
let mut state_val = UndefinedValue();
let _ = bao_stealth::engine_props::get_property_clearing(
cx,
this_obj.handle().into(),
c"_statePtr",
&mut state_val,
);
if !state_val.is_double() || (state_val.asBits_ & 0xFFFF000000000000) != 0 {
args.rval().set(UndefinedValue());
return true;
}
let state_ptr = state_val.to_private() as *mut CryptoHasherState;
if state_ptr.is_null() {
args.rval().set(UndefinedValue());
return true;
}
let encoding = if argc > 0 && (*args.get(0).ptr).is_string() {
crate::js_to_rust_string(cx, *args.get(0).ptr).to_ascii_lowercase()
} else {
"hex".to_string()
};
let state = &*state_ptr;
let hash_bytes = match state.algo {
CryptoHasherAlgo::Sha256 => {
let mut hasher = bun_sha_hmac::SHA256::init();
hasher.update(&state.data);
let mut out = [0u8; bun_sha_hmac::SHA256::DIGEST];
hasher.r#final(&mut out);
out.to_vec()
}
CryptoHasherAlgo::Sha512 => {
let mut hasher = bun_sha_hmac::SHA512::init();
hasher.update(&state.data);
let mut out = [0u8; bun_sha_hmac::SHA512::DIGEST];
hasher.r#final(&mut out);
out.to_vec()
}
CryptoHasherAlgo::Sha1 => {
let mut hasher = bun_sha_hmac::SHA1::init();
hasher.update(&state.data);
let mut out = [0u8; bun_sha_hmac::SHA1::DIGEST];
hasher.r#final(&mut out);
out.to_vec()
}
CryptoHasherAlgo::Md5 => {
let mut hasher = bun_sha_hmac::MD5::init();
hasher.update(&state.data);
let mut out = [0u8; bun_sha_hmac::MD5::DIGEST];
hasher.r#final(&mut out);
out.to_vec()
}
};
match encoding.as_str() {
"buffer" => {
let u8_obj = mozjs_sys::jsapi::JS_NewUint8Array(cx, hash_bytes.len());
if u8_obj.is_null() {
args.rval().set(UndefinedValue());
return true;
}
rooted!(&in(cx_ref) let u8_root = u8_obj);
if !hash_bytes.is_empty() {
let mut is_shared = false;
let data_ptr = mozjs_sys::jsapi::JS_GetUint8ArrayData(
u8_root.get(),
&mut is_shared,
::std::ptr::null(),
);
if !data_ptr.is_null() {
::std::ptr::copy_nonoverlapping(
hash_bytes.as_ptr(),
data_ptr,
hash_bytes.len(),
);
}
}
args.rval().set(ObjectValue(u8_root.get()));
}
"base64" => {
let b64_bytes = bun_base64::encode_alloc(&hash_bytes);
let b64_str = String::from_utf8_lossy(&b64_bytes).into_owned();
let c_b64 = ZBox::from_bytes(b64_str.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_b64.as_ptr());
args.rval().set(if js_str.is_null() {
UndefinedValue()
} else {
StringValue(&*js_str)
});
}
_ => {
let hex: String = bun_core::fmt::bytes_to_hex_lower_string(&hash_bytes);
let c_hex = ZBox::from_bytes(hex.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_hex.as_ptr());
args.rval().set(if js_str.is_null() {
UndefinedValue()
} else {
StringValue(&*js_str)
});
}
}
true
}
pub(crate) unsafe fn extract_bytes_from_jsval(cx: *mut JSContext, val: JSVal) -> Option<Vec<u8>> {
if val.is_string() {
Some(crate::js_to_rust_string(cx, val).into_bytes())
} else if val.is_object() {
let mut wrapped_cx = mozjs::context::JSContext::from_ptr(NonNull::new_unchecked(cx));
let cx_ref = &mut wrapped_cx;
rooted!(&in(cx_ref) let obj = val.to_object());
if mozjs_sys::jsapi::JS::IsArrayBufferObjectMaybeShared(obj.get()) {
let mut len: usize = 0;
let mut is_shared = false;
let mut data: *mut u8 = ::std::ptr::null_mut();
mozjs_sys::jsapi::JS::GetArrayBufferMaybeSharedLengthAndData(
obj.get(),
&mut len,
&mut is_shared,
&mut data,
);
if data.is_null() {
return Some(Vec::new());
}
let mut bytes = vec![0u8; len];
::std::ptr::copy_nonoverlapping(data, bytes.as_mut_ptr(), len);
return Some(bytes);
}
let mut length: usize = 0;
let mut is_shared = false;
let mut data: *mut u8 = ::std::ptr::null_mut();
let unwrapped = mozjs_sys::jsapi::JS_GetObjectAsUint8Array(
obj.get(),
&mut length,
&mut is_shared,
&mut data,
);
if !unwrapped.is_null() && !data.is_null() {
let mut bytes = vec![0u8; length];
::std::ptr::copy_nonoverlapping(data, bytes.as_mut_ptr(), length);
return Some(bytes);
}
if !unwrapped.is_null() {
return Some(Vec::new());
}
let mut view_length: usize = 0;
let mut view_shared = false;
let mut view_data: *mut u8 = ::std::ptr::null_mut();
let view_unwrapped = mozjs_sys::jsapi::JS_GetObjectAsArrayBufferView(
obj.get(),
&mut view_length,
&mut view_shared,
&mut view_data,
);
if !view_unwrapped.is_null() && !view_data.is_null() {
let mut bytes = vec![0u8; view_length];
::std::ptr::copy_nonoverlapping(view_data, bytes.as_mut_ptr(), view_length);
return Some(bytes);
}
if !view_unwrapped.is_null() {
return Some(Vec::new());
}
None
} else {
None
}
}
pub(crate) unsafe fn bytes_to_js_uint8array(cx: *mut JSContext, bytes: &[u8]) -> JSVal {
let u8_obj = mozjs_sys::jsapi::JS_NewUint8Array(cx, bytes.len());
if u8_obj.is_null() {
return UndefinedValue();
}
if !bytes.is_empty() {
let mut is_shared = false;
let data_ptr =
mozjs_sys::jsapi::JS_GetUint8ArrayData(u8_obj, &mut is_shared, ::std::ptr::null());
if !data_ptr.is_null() {
::std::ptr::copy_nonoverlapping(bytes.as_ptr(), data_ptr, bytes.len());
}
}
ObjectValue(u8_obj)
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_gzip(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"Bun.gzip() requires data".as_ptr());
return false;
}
let input = match extract_bytes_from_jsval(cx, *args.get(0).ptr) {
Some(d) => d,
None => {
JS_ReportErrorUTF8(cx, c"Bun.gzip() requires string or ArrayBuffer".as_ptr());
return false;
}
};
use flate2::Compression;
use flate2::write::GzEncoder;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
::std::io::Write::write_all(&mut encoder, &input).ok();
match encoder.finish() {
Ok(compressed) => {
args.rval().set(bytes_to_js_uint8array(cx, &compressed));
true
}
Err(e) => {
let msg = format!("Bun.gzip() failed: {}", e);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
false
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_deflate(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"Bun.deflate() requires data".as_ptr());
return false;
}
let input = match extract_bytes_from_jsval(cx, *args.get(0).ptr) {
Some(d) => d,
None => {
JS_ReportErrorUTF8(cx, c"Bun.deflate() requires string or ArrayBuffer".as_ptr());
return false;
}
};
use flate2::Compression;
use flate2::write::DeflateEncoder;
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
::std::io::Write::write_all(&mut encoder, &input).ok();
match encoder.finish() {
Ok(compressed) => {
args.rval().set(bytes_to_js_uint8array(cx, &compressed));
true
}
Err(e) => {
let msg = format!("Bun.deflate() failed: {}", e);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
false
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_inflate(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"Bun.inflate() requires data".as_ptr());
return false;
}
let input = match extract_bytes_from_jsval(cx, *args.get(0).ptr) {
Some(d) => d,
None => {
JS_ReportErrorUTF8(cx, c"Bun.inflate() requires string or ArrayBuffer".as_ptr());
return false;
}
};
use flate2::write::DeflateDecoder;
let mut decoder = DeflateDecoder::new(Vec::new());
::std::io::Write::write_all(&mut decoder, &input).ok();
match decoder.finish() {
Ok(decompressed) => {
args.rval().set(bytes_to_js_uint8array(cx, &decompressed));
true
}
Err(e) => {
let msg = format!("Bun.inflate() failed: {}", e);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
false
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_gunzip(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"Bun.gunzip() requires data".as_ptr());
return false;
}
let input = match extract_bytes_from_jsval(cx, *args.get(0).ptr) {
Some(d) => d,
None => {
JS_ReportErrorUTF8(cx, c"Bun.gunzip() requires string or ArrayBuffer".as_ptr());
return false;
}
};
use flate2::write::GzDecoder;
let mut decoder = GzDecoder::new(Vec::new());
::std::io::Write::write_all(&mut decoder, &input).ok();
match decoder.finish() {
Ok(decompressed) => {
args.rval().set(bytes_to_js_uint8array(cx, &decompressed));
true
}
Err(e) => {
let msg = format!("Bun.gunzip() failed: {}", e);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
false
}
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_file_url_to_path(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"Bun.fileURLToPath() requires a URL".as_ptr());
return false;
}
let url_val = *args.get(0).ptr;
let url_str = if url_val.is_string() {
crate::js_to_rust_string(cx, url_val)
} else {
JS_ReportErrorUTF8(cx, c"Bun.fileURLToPath() requires a string".as_ptr());
return false;
};
let path = if url_str.starts_with("file://") {
let bun_str = bun_core::String::borrow_utf8(url_str.as_bytes());
let result = bun_url::whatwg::path_from_file_url(&bun_str);
if result.tag() == bun_core::Tag::Dead {
url_str.clone()
} else {
let utf8 = result.to_utf8();
let s = ::std::str::from_utf8(utf8.slice())
.unwrap_or(&url_str)
.to_string();
result.deref();
s
}
} else {
url_str.clone()
};
let decoded = percent_decode_path(&path);
let c_path = ZBox::from_bytes(decoded.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_path.as_ptr());
args.rval().set(if js_str.is_null() {
UndefinedValue()
} else {
StringValue(&*js_str)
});
true
}
fn percent_decode_path(s: &str) -> String {
let mut result = Vec::with_capacity(s.len());
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' && i + 2 < bytes.len() {
let hi = hex_digit(bytes[i + 1]);
let lo = hex_digit(bytes[i + 2]);
if hi.is_some() && lo.is_some() {
result.push(hi.unwrap() << 4 | lo.unwrap());
i += 3;
continue;
}
}
result.push(bytes[i]);
i += 1;
}
String::from_utf8_lossy(&result).into_owned()
}
fn hex_digit(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_path_to_file_url(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"Bun.pathToFileURL() requires a path".as_ptr());
return false;
}
let path_val = *args.get(0).ptr;
let path_str = if path_val.is_string() {
crate::js_to_rust_string(cx, path_val)
} else {
JS_ReportErrorUTF8(cx, c"Bun.pathToFileURL() requires a string".as_ptr());
return false;
};
let url = {
let bun_str = bun_core::String::borrow_utf8(path_str.as_bytes());
let result = bun_url::whatwg::file_url_from_string(&bun_str);
if result.tag() == bun_core::Tag::Dead {
let canonical = ::std::path::Path::new(&path_str)
.canonicalize()
.unwrap_or_else(|_| ::std::path::PathBuf::from(&path_str));
format!("file://{}", canonical.to_string_lossy())
} else {
let utf8 = result.to_utf8();
let s = ::std::str::from_utf8(utf8.slice())
.unwrap_or("")
.to_string();
result.deref();
s
}
};
let c_url = ZBox::from_bytes(url.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_url.as_ptr());
args.rval().set(if js_str.is_null() {
UndefinedValue()
} else {
StringValue(&*js_str)
});
true
}
unsafe fn install_bun_semver(
cx: &mut mozjs::context::JSContext,
bun_obj: mozjs::rust::Handle<*mut JSObject>,
) {
let src = r#"(function() {
var semver = {};
// Minimal semver.satisfies(version, range) — supports exact match,
// ^x.y.z (caret), ~x.y.z (tilde), >x.y.z, >=x.y.z, <x.y.z, <=x.y.z,
// and x.y.z - a.b.c ranges.
function parseSemver(v) {
var m = String(v).trim().match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
if (!m) return null;
// Split trailing build metadata (`+…`) off the prerelease tail so
// order() can apply SemVer 2.0 precedence (build is ignored).
var rest = m[4];
var plus = rest.indexOf('+');
var pre = plus === -1 ? rest : rest.slice(0, plus);
return {
major: +m[1],
minor: +m[2],
patch: +m[3],
prerelease: pre,
};
}
function cmpSemver(a, b) {
if (a.major !== b.major) return a.major - b.major;
if (a.minor !== b.minor) return a.minor - b.minor;
return a.patch - b.patch;
}
// `order` is a NATIVE function defined after evaluation (see
// install_bun_semver): Bun's own bun_semver crate, mirroring upstream
// SemverObject.order (parse → max() → orderWithoutBuild).
semver.satisfies = function satisfies(version, range) {
var v = parseSemver(version);
if (!v) return false;
range = String(range).trim();
// Caret range: ^1.2.3 → >=1.2.3 <2.0.0
if (range[0] === '^') {
var r = parseSemver(range.slice(1));
if (!r) return false;
return cmpSemver(v, r) >= 0 && v.major === r.major;
}
// Tilde range: ~1.2.3 → >=1.2.3 <1.3.0
if (range[0] === '~') {
var r = parseSemver(range.slice(1));
if (!r) return false;
return cmpSemver(v, r) >= 0 && v.major === r.major && v.minor === r.minor;
}
// Comparison operators
if (range.slice(0, 2) === '>=') {
var r = parseSemver(range.slice(2));
return r ? cmpSemver(v, r) >= 0 : false;
}
if (range[0] === '>') {
var r = parseSemver(range.slice(1));
return r ? cmpSemver(v, r) > 0 : false;
}
if (range.slice(0, 2) === '<=') {
var r = parseSemver(range.slice(2));
return r ? cmpSemver(v, r) <= 0 : false;
}
if (range[0] === '<') {
var r = parseSemver(range.slice(1));
return r ? cmpSemver(v, r) < 0 : false;
}
// Exact match
var r = parseSemver(range);
return r ? cmpSemver(v, r) === 0 : false;
};
semver.parse = parseSemver;
return semver;
})()"#;
let mut text = mozjs::rust::transform_str_to_source_text(src);
let opts = mozjs::glue::NewCompileOptions(cx.raw_cx(), c"<bun:semver>".as_ptr(), 1);
if opts.is_null() {
return;
}
let mut rval = UndefinedValue();
let rval_h = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let ok = mozjs_sys::jsapi::JS::Evaluate2(cx.raw_cx(), opts, &mut text, rval_h);
libc::free(opts as *mut _);
if ok && rval.is_object() {
rooted!(&in(cx) let semver_obj = rval.to_object());
JS_DefineFunction(
cx,
semver_obj.handle(),
c"order".as_ptr(),
Some(bun_semver_order),
2,
JSPROP_ENUMERATE as u32,
);
JS_DefineProperty3(
cx,
bun_obj,
c"semver".as_ptr(),
semver_obj.handle(),
JSPROP_ENUMERATE as u32,
);
}
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_semver_order(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
use bun_semver::version::VersionInt;
use ::std::cmp::Ordering;
let args = CallArgs::from_vp(vp, argc);
if argc < 2 {
JS_ReportErrorUTF8(cx, c"Expected two arguments".as_ptr());
return false;
}
let a_val = *args.get(0).ptr;
let b_val = *args.get(1).ptr;
if !a_val.is_string() || !b_val.is_string() {
JS_ReportErrorUTF8(cx, c"Expected two arguments".as_ptr());
return false;
}
let a_str = crate::js_to_rust_string(cx, a_val);
let b_str = crate::js_to_rust_string(cx, b_val);
if !a_str.is_ascii() || !b_str.is_ascii() {
args.rval().set(Int32Value(0));
return true;
}
let a_bytes = a_str.as_bytes().to_vec();
let b_bytes = b_str.as_bytes().to_vec();
let a_result = bun_semver::VersionType::<u32>::parse_utf8(&a_bytes);
let b_result = bun_semver::VersionType::<u32>::parse_utf8(&b_bytes);
if !a_result.valid {
let msg = format!("Invalid SemVer: {}", a_str);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
if !b_result.valid {
let msg = format!("Invalid SemVer: {}", b_str);
let c_msg = ZBox::from_bytes(msg.as_bytes());
JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
return false;
}
let a_version = a_result.version.max();
let b_version = b_result.version.max();
let out = match a_version.order_without_build(b_version, &a_bytes, &b_bytes) {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
};
args.rval().set(Int32Value(out));
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_escape_html(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
let js_str = JS_NewStringCopyZ(cx, c"".as_ptr());
args.rval().set(if js_str.is_null() {
UndefinedValue()
} else {
StringValue(&*js_str)
});
return true;
}
let val = *args.get(0).ptr;
if !val.is_string() {
args.rval().set(UndefinedValue());
return true;
}
let s = crate::js_to_rust_string(cx, val);
let mut out = String::with_capacity(s.len() * 2);
for ch in s.chars() {
match ch {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
_ => out.push(ch),
}
}
let c_out = ZBox::from_bytes(out.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_out.as_ptr());
args.rval().set(if js_str.is_null() {
UndefinedValue()
} else {
StringValue(&*js_str)
});
true
}
unsafe fn make_bun_file_for_fd(cx: &mut mozjs::context::JSContext, fd: i32) -> *mut JSObject {
rooted!(&in(cx) let file_obj = JS_NewPlainObject(cx));
if file_obj.get().is_null() {
return ::std::ptr::null_mut();
}
let fd_val = Int32Value(fd);
rooted!(&in(cx) let fv = fd_val);
JS_DefineProperty(
cx.raw_cx(),
file_obj.handle().into(),
c"fd".as_ptr(),
fv.handle().into(),
JSPROP_ENUMERATE as u32,
);
let proc_path;
let path_str = match fd {
0 => "/dev/stdin",
1 => "/dev/stdout",
2 => "/dev/stderr",
_ => {
proc_path = format!("/proc/self/fd/{}", fd);
proc_path.as_str()
}
};
let c_path = ZBox::from_bytes(path_str.as_bytes());
let js_path = JS_NewStringCopyZ(cx.raw_cx(), c_path.as_ptr());
if !js_path.is_null() {
rooted!(&in(cx) let pv = StringValue(&*js_path));
JS_DefineProperty(
cx.raw_cx(),
file_obj.handle().into(),
c"path".as_ptr(),
pv.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
let (readable, writable) = match fd {
0 => (true, false),
1 | 2 => (false, true),
_ => (true, true),
};
rooted!(&in(cx) let rv = BooleanValue(readable));
JS_DefineProperty(
cx.raw_cx(),
file_obj.handle().into(),
c"readable".as_ptr(),
rv.handle().into(),
JSPROP_ENUMERATE as u32,
);
rooted!(&in(cx) let wv = BooleanValue(writable));
JS_DefineProperty(
cx.raw_cx(),
file_obj.handle().into(),
c"writable".as_ptr(),
wv.handle().into(),
JSPROP_ENUMERATE as u32,
);
define_bunfile_methods(cx, file_obj.handle());
file_obj.get()
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_deep_link(cx: *mut JSContext, _argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, _argc);
JS_ReportErrorUTF8(
cx,
c"Bun.deepLink() is not implemented in this environment".as_ptr(),
);
args.rval().set(UndefinedValue());
false
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn bun_open_in_new_tab(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if argc == 0 {
JS_ReportErrorUTF8(cx, c"Bun.openInNewTab() requires a URL".as_ptr());
return false;
}
let url_val = *args.get(0).ptr;
if !url_val.is_string() {
JS_ReportErrorUTF8(cx, c"Bun.openInNewTab() requires a string URL".as_ptr());
return false;
}
let url = crate::js_to_rust_string(cx, url_val);
#[cfg(target_family = "unix")]
{
let opener = if cfg!(target_os = "macos") {
"open"
} else {
"xdg-open"
};
let _ = ::std::process::Command::new(opener).arg(&url).spawn();
}
args.rval().set(UndefinedValue());
true
}
#[cfg(test)]
mod tests {
use super::*;
use bun_sha_hmac;
#[test]
fn test_case_stores_name() {
let tc = TestCase {
name: "my test".to_string(),
callback_key: "test_cb_0".to_string(),
};
assert_eq!(tc.name, "my test");
}
#[test]
fn test_case_callback_key_default() {
let tc = TestCase {
name: String::new(),
callback_key: "test_cb_1".to_string(),
};
assert!(!tc.callback_key.is_empty());
}
#[test]
fn bun_serve_user_data_default_fields() {
let data = BunServeUserData {
fetch_cb_key: None,
websocket_cb_key: None,
app_ptr: ::std::ptr::null_mut(),
hostname: "localhost".to_string(),
port: 3000,
actual_port: AtomicU16::new(0),
cx: ::std::ptr::null_mut(),
};
assert!(data.fetch_cb_key.is_none());
assert!(data.websocket_cb_key.is_none());
assert!(data.app_ptr.is_null());
assert_eq!(data.hostname, "localhost");
assert_eq!(data.port, 3000);
}
#[test]
fn bun_serve_user_data_with_fetch_cb() {
let data = BunServeUserData {
fetch_cb_key: Some("serve_fetch_0".to_string()),
websocket_cb_key: None,
app_ptr: ::std::ptr::null_mut(),
hostname: "0.0.0.0".to_string(),
port: 8080,
actual_port: AtomicU16::new(0),
cx: ::std::ptr::null_mut(),
};
assert!(data.fetch_cb_key.is_some());
assert!(data.websocket_cb_key.is_none());
assert_eq!(data.port, 8080);
}
#[test]
fn bun_serve_user_data_with_websocket_cb() {
let data = BunServeUserData {
fetch_cb_key: None,
websocket_cb_key: Some("serve_ws_0".to_string()),
app_ptr: ::std::ptr::null_mut(),
hostname: "0.0.0.0".to_string(),
port: 8080,
actual_port: AtomicU16::new(0),
cx: ::std::ptr::null_mut(),
};
assert!(data.fetch_cb_key.is_none());
assert!(data.websocket_cb_key.is_some());
}
#[test]
fn bun_serve_user_data_hostname_variants() {
for host in &["localhost", "0.0.0.0", "127.0.0.1", "::"] {
let data = BunServeUserData {
fetch_cb_key: None,
websocket_cb_key: None,
app_ptr: ::std::ptr::null_mut(),
hostname: host.to_string(),
port: 80,
actual_port: AtomicU16::new(0),
cx: ::std::ptr::null_mut(),
};
assert_eq!(data.hostname, *host);
}
}
#[test]
fn bun_serve_user_data_port_boundaries() {
let data = BunServeUserData {
fetch_cb_key: None,
websocket_cb_key: None,
app_ptr: ::std::ptr::null_mut(),
hostname: String::new(),
port: 0,
actual_port: AtomicU16::new(0),
cx: ::std::ptr::null_mut(),
};
assert_eq!(data.port, 0);
let data = BunServeUserData {
fetch_cb_key: None,
websocket_cb_key: None,
app_ptr: ::std::ptr::null_mut(),
hostname: String::new(),
port: 65535,
actual_port: AtomicU16::new(0),
cx: ::std::ptr::null_mut(),
};
assert_eq!(data.port, 65535);
}
#[test]
fn bun_serve_actual_port_dynamic_bind_channel() {
let data = BunServeUserData {
fetch_cb_key: None,
websocket_cb_key: None,
app_ptr: ::std::ptr::null_mut(),
hostname: "0.0.0.0".to_string(),
port: 0,
actual_port: AtomicU16::new(0),
cx: ::std::ptr::null_mut(),
};
assert_eq!(data.actual_port.load(Ordering::Acquire), 0);
data.actual_port.store(54321, Ordering::Release);
let bound = data.actual_port.load(Ordering::Acquire);
assert_eq!(bound, 54321);
let exposed = if bound > 0 { bound } else { data.port };
assert_eq!(exposed, 54321);
assert!(
exposed > 0,
"BCE-005: server.port must be > 0 after dynamic bind"
);
}
#[test]
fn init_process_start_sets_instant() {
init_process_start();
PROCESS_START.with(|s| {
let start = *s.borrow();
assert!(start.is_some());
});
}
#[test]
fn init_process_start_idempotent() {
init_process_start();
let first = PROCESS_START.with(|s| s.borrow().unwrap());
init_process_start();
let second = PROCESS_START.with(|s| s.borrow().unwrap());
assert!(second >= first);
}
#[test]
fn sha256_empty_input() {
let mut hasher = bun_sha_hmac::SHA256::init();
hasher.update(b"");
let mut result = [0u8; bun_sha_hmac::SHA256::DIGEST];
hasher.r#final(&mut result);
assert_eq!(result.len(), 32);
let hex: String = result.iter().map(|b| format!("{:02x}", b)).collect();
assert!(hex.starts_with("e3b0c442"));
}
#[test]
fn sha256_hello_world() {
let mut hasher = bun_sha_hmac::SHA256::init();
hasher.update(b"hello world");
let mut result = [0u8; bun_sha_hmac::SHA256::DIGEST];
hasher.r#final(&mut result);
let hex: String = result.iter().map(|b| format!("{:02x}", b)).collect();
assert!(hex.starts_with("b94d27b9"));
}
#[test]
fn sha512_empty_input() {
let mut hasher = bun_sha_hmac::SHA512::init();
hasher.update(b"");
let mut result = [0u8; bun_sha_hmac::SHA512::DIGEST];
hasher.r#final(&mut result);
assert_eq!(result.len(), 64);
let hex: String = result.iter().map(|b| format!("{:02x}", b)).collect();
assert!(hex.starts_with("cf83e135"));
}
#[test]
fn sha512_hello_world() {
let mut hasher = bun_sha_hmac::SHA512::init();
hasher.update(b"hello world");
let mut result = [0u8; bun_sha_hmac::SHA512::DIGEST];
hasher.r#final(&mut result);
let hex: String = result.iter().map(|b| format!("{:02x}", b)).collect();
assert!(hex.starts_with("309ecc48"));
}
#[test]
fn hash_hex_format_lowercase() {
let mut hasher = bun_sha_hmac::SHA256::init();
hasher.update(b"\xff");
let mut result = [0u8; bun_sha_hmac::SHA256::DIGEST];
hasher.r#final(&mut result);
let hex: String = result.iter().map(|b| format!("{:02x}", b)).collect();
assert_eq!(hex, hex.to_lowercase());
}
#[test]
fn sha256_deterministic() {
let mut h1 = bun_sha_hmac::SHA256::init();
h1.update(b"test data");
let mut r1 = [0u8; bun_sha_hmac::SHA256::DIGEST];
h1.r#final(&mut r1);
let mut h2 = bun_sha_hmac::SHA256::init();
h2.update(b"test data");
let mut r2 = [0u8; bun_sha_hmac::SHA256::DIGEST];
h2.r#final(&mut r2);
assert_eq!(r1.as_slice(), r2.as_slice());
}
#[test]
fn sha256_different_inputs_different_outputs() {
let mut h1 = bun_sha_hmac::SHA256::init();
h1.update(b"input1");
let mut r1 = [0u8; bun_sha_hmac::SHA256::DIGEST];
h1.r#final(&mut r1);
let mut h2 = bun_sha_hmac::SHA256::init();
h2.update(b"input2");
let mut r2 = [0u8; bun_sha_hmac::SHA256::DIGEST];
h2.r#final(&mut r2);
assert_ne!(r1.as_slice(), r2.as_slice());
}
#[test]
fn sha256_incremental_update() {
let mut h1 = bun_sha_hmac::SHA256::init();
h1.update(b"hello");
h1.update(b" world");
let mut r1 = [0u8; bun_sha_hmac::SHA256::DIGEST];
h1.r#final(&mut r1);
let mut h2 = bun_sha_hmac::SHA256::init();
h2.update(b"hello world");
let mut r2 = [0u8; bun_sha_hmac::SHA256::DIGEST];
h2.r#final(&mut r2);
assert_eq!(r1.as_slice(), r2.as_slice());
}
#[test]
fn sha256_large_input() {
let data = vec![0xABu8; 10_000];
let mut hasher = bun_sha_hmac::SHA256::init();
hasher.update(&data);
let mut result = [0u8; bun_sha_hmac::SHA256::DIGEST];
hasher.r#final(&mut result);
assert_eq!(result.len(), 32);
}
#[test]
fn sha512_large_input() {
let data = vec![0xCDu8; 10_000];
let mut hasher = bun_sha_hmac::SHA512::init();
hasher.update(&data);
let mut result = [0u8; bun_sha_hmac::SHA512::DIGEST];
hasher.r#final(&mut result);
assert_eq!(result.len(), 64);
}
}