use mozjs::jsapi::*;
use mozjs::jsval::{JSVal, NullValue, StringValue, UndefinedValue};
use mozjs::rooted;
use mozjs::rust::wrappers2::{JS_DefineFunction, JS_DefineProperty3};
use bun_core::ZBox;
fn path_extension(input: &str) -> Option<String> {
let base = input.rsplit('/').next().unwrap_or(input);
let ext = match base.rfind('.') {
Some(dot) if dot + 1 < base.len() => &base[dot + 1..],
_ => base,
};
if ext.is_empty() {
None
} else {
Some(ext.to_ascii_lowercase())
}
}
const CANONICAL_EXTENSIONS: &[(&str, &str)] = &[
("application/json", "json"),
("application/xml", "xml"),
("application/pdf", "pdf"),
("application/zip", "zip"),
("application/gzip", "gz"),
("application/x-tar", "tar"),
("application/x-7z-compressed", "7z"),
("application/x-rar-compressed", "rar"),
("application/wasm", "wasm"),
("application/webassembly", "wasm"), ("application/javascript", "js"), ("application/octet-stream", "bin"),
("application/manifest+json", "webmanifest"),
("application/msword", "doc"),
("application/vnd.ms-excel", "xls"),
("application/vnd.ms-powerpoint", "ppt"),
(
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"docx",
),
(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"xlsx",
),
(
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"pptx",
),
("application/sql", "sql"),
("application/graphql", "graphql"),
("font/woff", "woff"),
("font/woff2", "woff2"),
("font/ttf", "ttf"),
("font/otf", "otf"),
("image/png", "png"),
("image/jpeg", "jpg"),
("image/gif", "gif"),
("image/svg+xml", "svg"),
("image/webp", "webp"),
("image/avif", "avif"),
("image/x-icon", "ico"),
("image/bmp", "bmp"),
("image/tiff", "tif"),
("image/heic", "heic"),
("text/html", "html"),
("text/css", "css"),
("text/javascript", "js"),
("text/plain", "txt"),
("text/markdown", "md"),
("text/csv", "csv"),
("text/xml", "xml"),
("text/yaml", "yaml"),
("text/tab-separated-values", "tsv"),
("text/vtt", "vtt"),
("audio/mpeg", "mp3"),
("audio/mp4", "m4a"),
("audio/ogg", "ogg"),
("audio/wav", "wav"),
("audio/flac", "flac"),
("audio/aac", "aac"),
("video/mp4", "mp4"),
("video/webm", "webm"),
("video/quicktime", "mov"),
("video/x-msvideo", "avi"),
("video/mpeg", "mpeg"),
("video/ogg", "ogv"),
("model/gltf-binary", "glb"),
("model/gltf+json", "gltf"),
];
const NORMALIZE_KINDS: &[(&str, &str)] = &[
("text", "text/plain"),
("binary", "application/octet-stream"),
("bytes", "application/octet-stream"),
("byte", "application/octet-stream"),
("arraybuffer", "application/octet-stream"),
("arrayBuffer", "application/octet-stream"),
("form", "application/x-www-form-urlencoded"),
("form-data", "multipart/form-data"),
("json", "application/json"),
("html", "text/html"),
("js", "text/javascript"),
("css", "text/css"),
("xml", "application/xml"),
("plain", "text/plain"),
];
#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn set_string_result(cx: *mut JSContext, args: &CallArgs, s: &str) {
let c_s = ZBox::from_bytes(s.as_bytes());
let js_str = JS_NewStringCopyZ(cx, c_s.as_ptr());
args.rval().set(if js_str.is_null() {
UndefinedValue()
} else {
StringValue(&*js_str)
});
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn mime_get_type(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if args.argc_ == 0 || !(*args.get(0).ptr).is_string() {
JS_ReportErrorUTF8(cx, c"Bun.Mime.getType expects a string path or extension".as_ptr());
return false;
}
let input = crate::js_to_rust_string(cx, *args.get(0).ptr);
let Some(ext) = path_extension(&input) else {
args.rval().set(NullValue());
return true;
};
match bun_http_types::MimeType::by_extension_no_default(ext.as_bytes()) {
Some(t) => {
let full = String::from_utf8_lossy(&t.value).into_owned();
let essence = full.split(';').next().unwrap_or(&full).trim().to_string();
set_string_result(cx, &args, &essence);
}
None => args.rval().set(NullValue()),
}
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn mime_get_extension(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if args.argc_ == 0 || !(*args.get(0).ptr).is_string() {
JS_ReportErrorUTF8(cx, c"Bun.Mime.getExtension expects a string MIME type".as_ptr());
return false;
}
let input = crate::js_to_rust_string(cx, *args.get(0).ptr);
let essence = input
.split(';')
.next()
.unwrap_or(&input)
.trim()
.to_ascii_lowercase();
for (t, ext) in CANONICAL_EXTENSIONS {
if *t == essence.as_str() {
set_string_result(cx, &args, ext);
return true;
}
}
args.rval().set(NullValue());
true
}
#[allow(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn mime_normalize_kind(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
let args = CallArgs::from_vp(vp, argc);
if args.argc_ == 0 || !(*args.get(0).ptr).is_string() {
JS_ReportErrorUTF8(cx, c"Bun.Mime.normalizeKind expects a string kind".as_ptr());
return false;
}
let kind = crate::js_to_rust_string(cx, *args.get(0).ptr);
if kind.contains('/') {
set_string_result(cx, &args, &kind);
return true;
}
let lower = kind.to_ascii_lowercase();
for (k, full) in NORMALIZE_KINDS {
if *k == lower.as_str() {
set_string_result(cx, &args, full);
return true;
}
}
set_string_result(cx, &args, &kind);
true
}
pub unsafe fn install(
cx: &mut mozjs::context::JSContext,
bun_obj: mozjs::rust::Handle<*mut JSObject>,
) {
let ctor_src = r#"(function() {
function Mime(type, subtype, params) {
this.type = String(type || '');
this.subtype = String(subtype || '');
this.params = (params && typeof params === 'object') ? params : {};
}
Mime.prototype.toString = function() {
var s = this.type + '/' + this.subtype;
var keys = Object.keys(this.params);
if (keys.length > 0) {
s += '; ' + keys.map(function(k) { return k + '=' + this.params[k]; }.bind(this)).join('; ');
}
return s;
};
Mime.prototype.essence = function() {
return this.type + '/' + this.subtype;
};
// npm-mime instance face: instances carry the same lookups as the class
// (they consult the shared Bun.Mime statics — the global server table).
Mime.prototype.getType = function getType(pathOrExt) {
return Mime.getType(pathOrExt);
};
Mime.prototype.getExtension = function getExtension(type) {
return Mime.getExtension(type);
};
Mime.prototype.normalizeKind = function normalizeKind(kind) {
return Mime.normalizeKind(kind);
};
return Mime;
})()"#;
let mut text = mozjs::rust::transform_str_to_source_text(ctor_src);
let opts = mozjs::glue::NewCompileOptions(cx.raw_cx(), c"<bun:Mime>".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() {
JS_ClearPendingException(cx.raw_cx());
return;
}
rooted!(&in(cx) let mime_ctor = rval.to_object());
JS_DefineFunction(
cx,
mime_ctor.handle(),
c"getType".as_ptr(),
Some(mime_get_type),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
mime_ctor.handle(),
c"getExtension".as_ptr(),
Some(mime_get_extension),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineFunction(
cx,
mime_ctor.handle(),
c"normalizeKind".as_ptr(),
Some(mime_normalize_kind),
1,
JSPROP_ENUMERATE as u32,
);
JS_DefineProperty3(
cx,
bun_obj,
c"Mime".as_ptr(),
mime_ctor.handle(),
JSPROP_ENUMERATE as u32,
);
}