use bun_core::ZBox;
use mozjs::jsapi::*;
use mozjs::jsval::UndefinedValue;
use mozjs::rooted;
use mozjs::rust::wrappers2 as w2;
use crate::require::cache_builtin;
const QS_JS: &str = r#"
(function() {
// BCE-20260816-QS-ESCAPE — Node's querystring.escape/stringify percent-encode
// spaces as %20 (documented equivalent of encodeURIComponent). The old
// encoder emitted '+' (application/x-www-form-urlencoded shape, the
// URLSearchParams convention) so stringify({q:'x y'}) produced 'q=x+y' and
// escape('a b') produced 'a+b'. parse() keeps decoding BOTH '+' and %20 as
// space, matching Node's lenient decoder.
function encode(s) {
return encodeURIComponent(s);
}
function decode(s) {
return decodeURIComponent(s.replace(/\+/g, ' '));
}
function parse(str, sep, eq, options) {
sep = sep || '&';
eq = eq || '=';
var decoder = (options && typeof options.decodeURIComponent === 'function') ? options.decodeURIComponent : decode;
var maxKeys = (options && typeof options.maxKeys === 'number') ? options.maxKeys : 1000;
var obj = {};
if (!str || str.length === 0) return obj;
str = str.replace(/^\?/, '');
var pairs = str.split(sep);
var count = 0;
for (var i = 0; i < pairs.length; i++) {
if (maxKeys > 0 && count >= maxKeys) break;
var pair = pairs[i];
var idx = pair.indexOf(eq);
var key, val;
if (idx >= 0) {
key = decoder(pair.slice(0, idx));
val = decoder(pair.slice(idx + 1));
} else {
key = decoder(pair);
val = '';
}
if (obj.hasOwnProperty(key)) {
if (!Array.isArray(obj[key])) {
obj[key] = [obj[key]];
}
obj[key].push(val);
} else {
obj[key] = val;
}
count++;
}
return obj;
}
function stringify(obj, sep, eq, options) {
sep = sep || '&';
eq = eq || '=';
var encoder = (options && typeof options.encodeURIComponent === 'function') ? options.encodeURIComponent : encode;
var pairs = [];
for (var key in obj) {
if (!obj.hasOwnProperty(key)) continue;
var val = obj[key];
if (val === null || val === undefined) {
pairs.push(encoder(key) + eq);
} else if (Array.isArray(val)) {
for (var i = 0; i < val.length; i++) {
pairs.push(encoder(key) + eq + encoder(String(val[i])));
}
} else {
pairs.push(encoder(key) + eq + encoder(String(val)));
}
}
return pairs.join(sep);
}
function escape(str) {
return encode(str);
}
function unescape(str) {
return decode(str);
}
return {
parse: parse,
stringify: stringify,
escape: escape,
unescape: unescape,
};
})();
"#;
pub fn install(cx: &mut mozjs::context::JSContext) {
rooted!(&in(cx) let mod_obj = unsafe { w2::JS_NewPlainObject(cx) });
if mod_obj.get().is_null() {
return;
}
unsafe {
let cx_raw = cx.raw_cx();
let c_filename = ZBox::from_bytes("node:querystring".as_bytes());
let opts = mozjs::glue::NewCompileOptions(cx_raw, c_filename.as_ptr(), 1);
if opts.is_null() {
return;
}
let mut src = mozjs::rust::transform_str_to_source_text(QS_JS);
let mut rval = UndefinedValue();
let rval_handle = MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut rval,
};
let ok = mozjs_sys::jsapi::JS::Evaluate2(cx_raw, opts, &mut src, rval_handle);
libc::free(opts as *mut _);
if !ok || !rval.is_object() {
return;
}
let exports_obj = rval.to_object();
rooted!(&in(cx) let exports_rooted = exports_obj);
for name in &["parse", "stringify", "escape", "unescape"] {
let cname = ZBox::from_bytes(name.as_bytes());
let mut val = UndefinedValue();
JS_GetProperty(
cx_raw,
exports_rooted.handle().into(),
cname.as_ptr(),
MutableHandle::<Value> {
_phantom_0: ::std::marker::PhantomData,
ptr: &mut val,
},
);
if !val.is_undefined() {
rooted!(&in(cx) let val_root = val);
JS_DefineProperty(
cx_raw,
mod_obj.handle().into(),
cname.as_ptr(),
val_root.handle().into(),
JSPROP_ENUMERATE as u32,
);
}
}
cache_builtin(cx, "querystring", mod_obj.get());
}
}