1use crate::host::{with_host, JsObj};
4use fusevm::Value;
5use indexmap::IndexMap;
6
7pub const METHODS: &[&str] = &[
8 "format",
9 "formatWithOptions",
10 "inspect",
11 "deprecate",
12 "inherits",
13 "types",
14 "types.isMap",
15 "types.isSet",
16 "types.isPromise",
17 "types.isDate",
18 "types.isRegExp",
19 "types.isNativeError",
20 "types.isAsyncFunction",
21 "isDeepStrictEqual",
22 "isArray",
23 "debuglog",
24 "stripVTControlCharacters",
25 "toUSVString",
26 "getSystemErrorName",
27 "getSystemErrorMessage",
28 "getSystemErrorMap",
29 "styleText",
30 "parseArgs",
31 "promisify",
32 "callbackify",
33 "parseEnv",
34 "debug",
35];
36
37pub fn constant(name: &str) -> Option<Value> {
40 match name {
41 "types" => Some(with_host(|h| h.alloc(JsObj::Builtin("util/types".into())))),
42 "TextEncoder" | "TextDecoder" | "MIMEType" | "MIMEParams" => {
43 Some(with_host(|h| h.alloc(JsObj::Builtin(name.into()))))
44 }
45 _ => None,
46 }
47}
48
49pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
50 if let Some(pred) = method.strip_prefix("types.") {
51 return Some(Ok(Value::Bool(type_predicate(pred, args.first()))));
52 }
53 Some(match method {
54 "format" => {
55 let s = format(args);
58 Ok(with_host(|h| h.new_str(s)))
59 }
60 "formatWithOptions" => {
64 let s = format(args.get(1..).unwrap_or(&[]));
65 Ok(with_host(|h| h.new_str(s)))
66 }
67 "inspect" => {
68 let depth = match args.get(1) {
70 Some(opts) => match crate::builtins::get_property(opts, "depth") {
71 Ok(Value::Undef) => 2,
72 Ok(v) if with_host(|h| h.is_null(&v)) => usize::MAX,
73 Ok(v) => {
74 let n = with_host(|h| h.to_number(&v));
75 if n.is_finite() && n >= 0.0 {
76 n as usize
77 } else {
78 usize::MAX
79 }
80 }
81 Err(_) => 2,
82 },
83 None => 2,
84 };
85 crate::host::set_inspect_max_depth(depth);
86 let out = with_host(|h| {
87 let s = h.inspect(&args.first().cloned().unwrap_or(Value::Undef));
88 h.new_str(s)
89 });
90 crate::host::set_inspect_max_depth(2);
91 Ok(out)
92 }
93 "deprecate" => Ok(args.first().cloned().unwrap_or(Value::Undef)),
97 "inherits" => Ok(inherits(args)),
101 "types" => Ok(Value::Undef),
104 "isDeepStrictEqual" => Ok(Value::Bool(super::assert::deep_equal(
105 &args.first().cloned().unwrap_or(Value::Undef),
106 &args.get(1).cloned().unwrap_or(Value::Undef),
107 true,
108 ))),
109 "isArray" => Ok(Value::Bool(with_host(|h| {
111 matches!(
112 h.get(&args.first().cloned().unwrap_or(Value::Undef)),
113 Some(JsObj::Array(_))
114 )
115 }))),
116 "stripVTControlCharacters" => {
118 let stripped = strip_vt(&super::arg_str(args, 0));
119 Ok(with_host(|h| h.new_str(stripped)))
120 }
121 "toUSVString" => Ok(with_host(|h| {
125 let s = h.str_of(&args.first().cloned().unwrap_or(Value::Undef));
126 h.new_str(s)
127 })),
128 "getSystemErrorName" => {
129 let e = errno_of(super::arg_num(args, 0));
130 let name = errno_name(e)
131 .map(str::to_string)
132 .unwrap_or_else(|| format!("Unknown system error {e}"));
133 Ok(with_host(|h| h.new_str(name)))
134 }
135 "getSystemErrorMessage" => {
136 let e = errno_of(super::arg_num(args, 0));
137 let msg = errno_message(e)
138 .map(str::to_string)
139 .unwrap_or_else(|| format!("Unknown system error {e}"));
140 Ok(with_host(|h| h.new_str(msg)))
141 }
142 "getSystemErrorMap" => Ok(system_error_map()),
143 "styleText" => return Some(style_text(args)),
144 "parseArgs" => return Some(parse_args(args)),
145 "debuglog" => return Some(debuglog(args)),
146 "promisify" => return Some(promisify(args)),
147 "callbackify" => return Some(callbackify(args)),
148 "parseEnv" => Ok(parse_env(&super::arg_str(args, 0))),
150 "debug" => return Some(debuglog(args)),
152 _ => return None,
153 })
154}
155
156fn inherits(args: &[Value]) -> Value {
158 let ctor = args.first().cloned().unwrap_or(Value::Undef);
159 let sup = args.get(1).cloned().unwrap_or(Value::Undef);
160 with_host(|h| {
161 let sup_proto = h.fn_prop(&sup, "prototype").unwrap_or_else(|| {
164 let mut props = indexmap::IndexMap::new();
165 props.insert("constructor".to_string(), sup.clone());
166 let p = h.new_object(props);
167 h.set_fn_prop(&sup, "prototype", p.clone());
168 p
169 });
170 let ctor_proto = h.fn_prop(&ctor, "prototype").unwrap_or_else(|| {
172 let mut props = indexmap::IndexMap::new();
173 props.insert("constructor".to_string(), ctor.clone());
174 let p = h.new_object(props);
175 h.set_fn_prop(&ctor, "prototype", p.clone());
176 p
177 });
178 h.set_proto(&ctor_proto, sup_proto);
179 h.set_fn_prop(&ctor, "super_", sup);
180 });
181 Value::Undef
182}
183
184fn type_predicate(pred: &str, v: Option<&Value>) -> bool {
185 let Some(v) = v else { return false };
186 with_host(|h| match pred {
187 "isMap" => matches!(h.get(v), Some(JsObj::Map { weak: false, .. })),
188 "isSet" => matches!(h.get(v), Some(JsObj::Set { weak: false, .. })),
189 "isPromise" => matches!(h.get(v), Some(JsObj::Promise { .. })),
190 _ => false,
191 })
192}
193
194pub fn format(args: &[Value]) -> String {
197 if args.is_empty() {
198 return String::new();
199 }
200 if args.len() == 1 {
203 return with_host(|h| h.console_format(&args[0]));
204 }
205 let fmt = with_host(|h| h.str_of(&args[0]));
206 if !matches!(args[0], Value::Str(_))
208 && !with_host(|h| matches!(h.get(&args[0]), Some(JsObj::Str(_))))
209 {
210 return with_host(|h| {
211 args.iter()
212 .map(|a| h.console_format(a))
213 .collect::<Vec<_>>()
214 .join(" ")
215 });
216 }
217
218 let mut out = String::new();
219 let mut ai = 1usize;
220 let mut chars = fmt.chars().peekable();
221 while let Some(c) = chars.next() {
222 if c != '%' {
223 out.push(c);
224 continue;
225 }
226 let Some(&spec) = chars.peek() else {
227 out.push('%');
228 break;
229 };
230 if spec == '%' {
231 out.push('%');
232 chars.next();
233 continue;
234 }
235 if !matches!(spec, 's' | 'd' | 'i' | 'f' | 'j' | 'o' | 'O' | 'c') || ai >= args.len() {
236 out.push('%');
237 continue;
238 }
239 chars.next();
240 let arg = &args[ai];
241 ai += 1;
242 match spec {
243 's' => {
245 let s = with_host(|h| match h.get(arg) {
246 Some(JsObj::BigInt(b)) => format!("{b}n"),
247 _ => h.str_of(arg),
248 });
249 out.push_str(&s);
250 }
251 'd' | 'i' => {
252 let n = with_host(|h| h.to_number(arg));
253 out.push_str(&if n.is_nan() {
254 "NaN".into()
255 } else {
256 (n.trunc() as i64).to_string()
257 });
258 }
259 'f' => out.push_str(&crate::host::fmt_number(with_host(|h| h.to_number(arg)))),
260 'j' => {
261 let s = crate::builtins::call_builtin_function("JSON.stringify", vec![arg.clone()])
262 .ok()
263 .map(|v| with_host(|h| h.str_of(&v)))
264 .unwrap_or_else(|| "undefined".into());
265 out.push_str(&s);
266 }
267 'o' | 'O' => out.push_str(&with_host(|h| h.inspect(arg))),
268 'c' => {} _ => {}
270 }
271 }
272 for a in &args[ai..] {
274 out.push(' ');
275 out.push_str(&with_host(|h| h.console_format(a)));
276 }
277 out
278}
279
280fn run_completion(src: &str) -> Result<Value, String> {
288 let prog = crate::compile_completion(src)?;
289 let chunk = crate::load_merged(prog);
290 crate::host::run_chunk_on(chunk)
291}
292
293const PROMISIFY_SRC: &str = "(function(original){\n\
294 return function(){\n\
295 var self = this;\n\
296 var args = Array.prototype.slice.call(arguments);\n\
297 return new Promise(function(resolve, reject){\n\
298 args.push(function(err, value){ if (err) reject(err); else resolve(value); });\n\
299 original.apply(self, args);\n\
300 });\n\
301 };\n\
302})";
303
304const CALLBACKIFY_SRC: &str = "(function(original){\n\
305 return function(){\n\
306 var self = this;\n\
307 var args = Array.prototype.slice.call(arguments);\n\
308 var cb = args.pop();\n\
309 Promise.resolve(original.apply(self, args)).then(\n\
310 function(value){ cb.call(self, null, value); },\n\
311 function(err){ cb.call(self, err || new Error('Promise was rejected with a falsy value')); }\n\
312 );\n\
313 };\n\
314})";
315
316fn promisify(args: &[Value]) -> Result<Value, String> {
319 let orig = args.first().cloned().unwrap_or(Value::Undef);
320 if !with_host(|h| crate::host::is_callable(h, &orig)) {
321 return Err(
322 "TypeError [ERR_INVALID_ARG_TYPE]: The \"original\" argument must be of type function"
323 .into(),
324 );
325 }
326 let factory = run_completion(PROMISIFY_SRC)?;
327 crate::host::invoke(&factory, vec![orig], None)
328}
329
330fn callbackify(args: &[Value]) -> Result<Value, String> {
333 let orig = args.first().cloned().unwrap_or(Value::Undef);
334 if !with_host(|h| crate::host::is_callable(h, &orig)) {
335 return Err(
336 "TypeError [ERR_INVALID_ARG_TYPE]: The \"original\" argument must be of type function"
337 .into(),
338 );
339 }
340 let factory = run_completion(CALLBACKIFY_SRC)?;
341 crate::host::invoke(&factory, vec![orig], None)
342}
343
344const DEBUGLOG_ENABLED_SRC: &str = "(function(prefix){\n\
347 var util = require('util');\n\
348 return function(){\n\
349 console.error(prefix + ' ' + util.format.apply(null, arguments));\n\
350 };\n\
351})";
352
353fn debuglog(args: &[Value]) -> Result<Value, String> {
356 let section = super::arg_str(args, 0);
357 if debuglog_enabled(§ion) {
358 let prefix = format!("{} {}:", section.to_uppercase(), std::process::id());
359 let factory = run_completion(DEBUGLOG_ENABLED_SRC)?;
360 let pfx = with_host(|h| h.new_str(prefix));
361 crate::host::invoke(&factory, vec![pfx], None)
362 } else {
363 run_completion("(function(){})")
364 }
365}
366
367fn debuglog_enabled(section: &str) -> bool {
370 let Ok(env) = std::env::var("NODE_DEBUG") else {
371 return false;
372 };
373 let sec = section.to_uppercase();
374 env.split(|c: char| c == ',' || c.is_whitespace())
375 .filter(|s| !s.is_empty())
376 .any(|pat| {
377 let pat = pat.to_uppercase();
378 if pat.contains('*') {
379 wildcard_match(&pat, &sec)
380 } else {
381 pat == sec
382 }
383 })
384}
385
386fn wildcard_match(pat: &str, s: &str) -> bool {
388 let parts: Vec<&str> = pat.split('*').collect();
389 if parts.len() == 1 {
390 return pat == s;
391 }
392 let mut pos = 0usize;
393 for (i, part) in parts.iter().enumerate() {
394 if part.is_empty() {
395 continue;
396 }
397 if i == 0 {
398 if !s[pos..].starts_with(part) {
399 return false;
400 }
401 pos += part.len();
402 } else if i == parts.len() - 1 {
403 return s[pos..].ends_with(part);
404 } else if let Some(idx) = s[pos..].find(part) {
405 pos += idx + part.len();
406 } else {
407 return false;
408 }
409 }
410 true
411}
412
413fn strip_vt(s: &str) -> String {
418 let mut out = String::with_capacity(s.len());
419 let mut chars = s.chars().peekable();
420 while let Some(c) = chars.next() {
421 if c == '\u{9b}' {
422 while let Some(&n) = chars.peek() {
424 chars.next();
425 if ('\u{40}'..='\u{7e}').contains(&n) {
426 break;
427 }
428 }
429 continue;
430 }
431 if c != '\u{1b}' {
432 out.push(c);
433 continue;
434 }
435 match chars.peek() {
436 Some('[') => {
437 chars.next();
438 while let Some(&n) = chars.peek() {
439 chars.next();
440 if ('\u{40}'..='\u{7e}').contains(&n) {
441 break;
442 }
443 }
444 }
445 Some(']') => {
446 chars.next();
447 while let Some(&n) = chars.peek() {
448 if n == '\u{7}' {
449 chars.next();
450 break;
451 }
452 if n == '\u{1b}' {
453 chars.next();
454 if chars.peek() == Some(&'\\') {
455 chars.next();
456 }
457 break;
458 }
459 chars.next();
460 }
461 }
462 Some(_) => {
463 chars.next();
464 }
465 None => {}
466 }
467 }
468 out
469}
470
471fn style_names(v: &Value) -> Vec<String> {
475 with_host(|h| match h.get(v) {
476 Some(JsObj::Array(items)) => items.iter().map(|x| h.str_of(x)).collect(),
477 _ => vec![h.str_of(v)],
478 })
479}
480
481fn style_codes(name: &str) -> Option<(u16, u16)> {
483 Some(match name {
484 "reset" => (0, 0),
485 "bold" => (1, 22),
486 "dim" => (2, 22),
487 "italic" => (3, 23),
488 "underline" => (4, 24),
489 "blink" => (5, 25),
490 "inverse" => (7, 27),
491 "hidden" => (8, 28),
492 "strikethrough" => (9, 29),
493 "doubleunderline" => (21, 24),
494 "overline" => (53, 55),
495 "black" => (30, 39),
496 "red" => (31, 39),
497 "green" => (32, 39),
498 "yellow" => (33, 39),
499 "blue" => (34, 39),
500 "magenta" => (35, 39),
501 "cyan" => (36, 39),
502 "white" => (37, 39),
503 "gray" | "grey" | "blackBright" => (90, 39),
504 "redBright" => (91, 39),
505 "greenBright" => (92, 39),
506 "yellowBright" => (93, 39),
507 "blueBright" => (94, 39),
508 "magentaBright" => (95, 39),
509 "cyanBright" => (96, 39),
510 "whiteBright" => (97, 39),
511 "bgBlack" => (40, 49),
512 "bgRed" => (41, 49),
513 "bgGreen" => (42, 49),
514 "bgYellow" => (43, 49),
515 "bgBlue" => (44, 49),
516 "bgMagenta" => (45, 49),
517 "bgCyan" => (46, 49),
518 "bgWhite" => (47, 49),
519 "bgGray" | "bgGrey" | "bgBlackBright" => (100, 49),
520 "bgRedBright" => (101, 49),
521 "bgGreenBright" => (102, 49),
522 "bgYellowBright" => (103, 49),
523 "bgBlueBright" => (104, 49),
524 "bgMagentaBright" => (105, 49),
525 "bgCyanBright" => (106, 49),
526 "bgWhiteBright" => (107, 49),
527 _ => return None,
528 })
529}
530
531fn style_text(args: &[Value]) -> Result<Value, String> {
534 let fmt = args.first().cloned().unwrap_or(Value::Undef);
535 let names = style_names(&fmt);
536 let mut result = super::arg_str(args, 1);
537 for name in &names {
538 if name == "none" {
539 continue;
540 }
541 let (open, close) = style_codes(name).ok_or_else(|| {
542 format!(
543 "TypeError [ERR_INVALID_ARG_VALUE]: The argument 'format' must be a valid \
544 util.inspect.colors key. Received '{name}'"
545 )
546 })?;
547 result = format!("\u{1b}[{open}m{result}\u{1b}[{close}m");
548 }
549 Ok(with_host(|h| h.new_str(result)))
550}
551
552const ERRNO_TABLE: &[(&str, i32, &str)] = &[
560 ("E2BIG", libc::E2BIG, "argument list too long"),
561 ("EACCES", libc::EACCES, "permission denied"),
562 ("EADDRINUSE", libc::EADDRINUSE, "address already in use"),
563 (
564 "EADDRNOTAVAIL",
565 libc::EADDRNOTAVAIL,
566 "address not available",
567 ),
568 (
569 "EAFNOSUPPORT",
570 libc::EAFNOSUPPORT,
571 "address family not supported",
572 ),
573 ("EAGAIN", libc::EAGAIN, "resource temporarily unavailable"),
574 ("EALREADY", libc::EALREADY, "connection already in progress"),
575 ("EBADF", libc::EBADF, "bad file descriptor"),
576 ("EBUSY", libc::EBUSY, "resource busy or locked"),
577 ("ECANCELED", libc::ECANCELED, "operation canceled"),
578 (
579 "ECONNABORTED",
580 libc::ECONNABORTED,
581 "software caused connection abort",
582 ),
583 ("ECONNREFUSED", libc::ECONNREFUSED, "connection refused"),
584 ("ECONNRESET", libc::ECONNRESET, "connection reset by peer"),
585 (
586 "EDESTADDRREQ",
587 libc::EDESTADDRREQ,
588 "destination address required",
589 ),
590 ("EEXIST", libc::EEXIST, "file already exists"),
591 (
592 "EFAULT",
593 libc::EFAULT,
594 "bad address in system call argument",
595 ),
596 ("EFBIG", libc::EFBIG, "file too large"),
597 ("EHOSTDOWN", libc::EHOSTDOWN, "host is down"),
598 ("EHOSTUNREACH", libc::EHOSTUNREACH, "host is unreachable"),
599 ("EINTR", libc::EINTR, "interrupted system call"),
600 ("EINVAL", libc::EINVAL, "invalid argument"),
601 ("EIO", libc::EIO, "i/o error"),
602 ("EISCONN", libc::EISCONN, "socket is already connected"),
603 ("EISDIR", libc::EISDIR, "illegal operation on a directory"),
604 ("ELOOP", libc::ELOOP, "too many symbolic links encountered"),
605 ("EMFILE", libc::EMFILE, "too many open files"),
606 ("EMLINK", libc::EMLINK, "too many links"),
607 ("EMSGSIZE", libc::EMSGSIZE, "message too long"),
608 ("ENAMETOOLONG", libc::ENAMETOOLONG, "name too long"),
609 ("ENETDOWN", libc::ENETDOWN, "network is down"),
610 ("ENETUNREACH", libc::ENETUNREACH, "network is unreachable"),
611 ("ENFILE", libc::ENFILE, "file table overflow"),
612 ("ENOBUFS", libc::ENOBUFS, "no buffer space available"),
613 ("ENODEV", libc::ENODEV, "no such device"),
614 ("ENOENT", libc::ENOENT, "no such file or directory"),
615 ("ENOMEM", libc::ENOMEM, "not enough memory"),
616 ("ENOPROTOOPT", libc::ENOPROTOOPT, "protocol not available"),
617 ("ENOSPC", libc::ENOSPC, "no space left on device"),
618 ("ENOSYS", libc::ENOSYS, "function not implemented"),
619 ("ENOTCONN", libc::ENOTCONN, "socket is not connected"),
620 ("ENOTDIR", libc::ENOTDIR, "not a directory"),
621 ("ENOTEMPTY", libc::ENOTEMPTY, "directory not empty"),
622 ("ENOTSOCK", libc::ENOTSOCK, "socket operation on non-socket"),
623 ("ENXIO", libc::ENXIO, "no such device or address"),
624 (
625 "EOPNOTSUPP",
626 libc::EOPNOTSUPP,
627 "operation not supported on socket",
628 ),
629 (
630 "EOVERFLOW",
631 libc::EOVERFLOW,
632 "value too large for defined data type",
633 ),
634 ("EPERM", libc::EPERM, "operation not permitted"),
635 ("EPIPE", libc::EPIPE, "broken pipe"),
636 ("EPROTO", libc::EPROTO, "protocol error"),
637 (
638 "EPROTONOSUPPORT",
639 libc::EPROTONOSUPPORT,
640 "protocol not supported",
641 ),
642 (
643 "EPROTOTYPE",
644 libc::EPROTOTYPE,
645 "protocol wrong type for socket",
646 ),
647 ("ERANGE", libc::ERANGE, "result too large"),
648 ("EROFS", libc::EROFS, "read-only file system"),
649 (
650 "ESHUTDOWN",
651 libc::ESHUTDOWN,
652 "cannot send after transport endpoint shutdown",
653 ),
654 ("ESPIPE", libc::ESPIPE, "invalid seek"),
655 ("ESRCH", libc::ESRCH, "no such process"),
656 ("ETIMEDOUT", libc::ETIMEDOUT, "connection timed out"),
657 ("ETXTBSY", libc::ETXTBSY, "text file is busy"),
658 ("EXDEV", libc::EXDEV, "cross-device link not permitted"),
659];
660
661fn errno_of(err: f64) -> i32 {
664 if err < 0.0 {
665 (-err) as i32
666 } else {
667 err as i32
668 }
669}
670
671fn errno_name(e: i32) -> Option<&'static str> {
672 ERRNO_TABLE
673 .iter()
674 .find(|(_, code, _)| *code == e)
675 .map(|(n, _, _)| *n)
676}
677
678fn errno_message(e: i32) -> Option<&'static str> {
679 ERRNO_TABLE
680 .iter()
681 .find(|(_, code, _)| *code == e)
682 .map(|(_, _, m)| *m)
683}
684
685fn system_error_map() -> Value {
687 with_host(|h| {
688 let mut entries = indexmap::IndexMap::new();
689 for (name, code, msg) in ERRNO_TABLE {
690 let key_val = Value::Float(-(*code as f64));
691 let name_v = h.new_str(*name);
692 let msg_v = h.new_str(*msg);
693 let pair = h.new_array(vec![name_v, msg_v]);
694 let key = crate::host::map_key(h, &key_val);
695 entries.insert(key, (key_val, pair));
696 }
697 h.alloc(JsObj::Map {
698 entries,
699 weak: false,
700 })
701 })
702}
703
704enum Slot {
708 Bool(bool),
709 Str(String),
710 ListBool(Vec<bool>),
711 ListStr(Vec<String>),
712}
713
714struct OptCfg {
716 long: String,
717 is_string: bool,
718 multiple: bool,
719 short: Option<String>,
720 default: Option<Value>,
721}
722
723fn parse_args(config_args: &[Value]) -> Result<Value, String> {
726 let config = config_args.first().cloned().unwrap_or(Value::Undef);
727 let tokens = read_arg_tokens(&config);
728 let strict = read_bool_prop(&config, "strict", true);
729 let allow_positionals = read_bool_prop(&config, "allowPositionals", false);
730 let allow_negative = read_bool_prop(&config, "allowNegative", false);
731 let opts = read_options(&config);
732
733 let lookup_long = |name: &str| opts.iter().find(|o| o.long == name);
734 let lookup_short = |c: &str| opts.iter().find(|o| o.short.as_deref() == Some(c));
735 let is_bool_long = |name: &str| opts.iter().any(|o| o.long == name && !o.is_string);
736
737 let mut values: IndexMap<String, Slot> = IndexMap::new();
738 let mut positionals: Vec<String> = Vec::new();
739
740 let mut i = 0usize;
741 while i < tokens.len() {
742 let tok = tokens[i].clone();
743 if tok == "--" {
744 for t in &tokens[i + 1..] {
745 positionals.push(t.clone());
746 }
747 break;
748 }
749 if let Some(rest) = tok.strip_prefix("--") {
750 let (raw_name, inline) = match rest.split_once('=') {
751 Some((n, v)) => (n.to_string(), Some(v.to_string())),
752 None => (rest.to_string(), None),
753 };
754 let (name, negate) = match raw_name.strip_prefix("no-") {
756 Some(base) if allow_negative && is_bool_long(base) => (base.to_string(), true),
757 _ => (raw_name, false),
758 };
759 match lookup_long(&name) {
760 None if strict => {
761 return Err(format!(
762 "Error [ERR_PARSE_ARGS_UNKNOWN_OPTION]: Unknown option '--{name}'"
763 ))
764 }
765 None => {
766 store(&mut values, &name, Slot::Bool(true), false);
768 }
769 Some(cfg) if cfg.is_string => {
770 let val = match inline {
771 Some(v) => v,
772 None => {
773 i += 1;
774 tokens.get(i).cloned().ok_or_else(|| {
775 format!(
776 "Error [ERR_PARSE_ARGS_INVALID_OPTION_VALUE]: \
777 Option '--{name} <value>' argument missing"
778 )
779 })?
780 }
781 };
782 store(&mut values, &cfg.long, Slot::Str(val), cfg.multiple);
783 }
784 Some(cfg) => {
785 if inline.is_some() && strict {
786 return Err(format!(
787 "Error [ERR_PARSE_ARGS_INVALID_OPTION_VALUE]: \
788 Option '--{}' does not take an argument",
789 cfg.long
790 ));
791 }
792 store(&mut values, &cfg.long, Slot::Bool(!negate), cfg.multiple);
793 }
794 }
795 } else if tok.len() > 1 && tok.starts_with('-') {
796 let chars: Vec<char> = tok[1..].chars().collect();
797 let mut ci = 0usize;
798 while ci < chars.len() {
799 let short = chars[ci].to_string();
800 match lookup_short(&short) {
801 None if strict => {
802 return Err(format!(
803 "Error [ERR_PARSE_ARGS_UNKNOWN_OPTION]: Unknown option '-{short}'"
804 ))
805 }
806 None => {
807 store(&mut values, &short, Slot::Bool(true), false);
808 ci += 1;
809 }
810 Some(cfg) if cfg.is_string => {
811 let remainder: String = chars[ci + 1..].iter().collect();
812 let val = if !remainder.is_empty() {
813 remainder
814 } else {
815 i += 1;
816 tokens.get(i).cloned().ok_or_else(|| {
817 format!(
818 "Error [ERR_PARSE_ARGS_INVALID_OPTION_VALUE]: \
819 Option '-{short}, --{} <value>' argument missing",
820 cfg.long
821 )
822 })?
823 };
824 store(&mut values, &cfg.long, Slot::Str(val), cfg.multiple);
825 break;
826 }
827 Some(cfg) => {
828 store(&mut values, &cfg.long, Slot::Bool(true), cfg.multiple);
829 ci += 1;
830 }
831 }
832 }
833 } else {
834 if !allow_positionals && strict {
835 return Err(format!(
836 "Error [ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL]: \
837 Unexpected argument '{tok}'. This command does not take positional arguments"
838 ));
839 }
840 positionals.push(tok);
841 }
842 i += 1;
843 }
844
845 for cfg in &opts {
847 if !values.contains_key(&cfg.long) {
848 if let Some(def) = &cfg.default {
849 store_default(&mut values, cfg, def.clone());
850 }
851 }
852 }
853
854 Ok(build_parse_result(values, positionals))
855}
856
857fn store(values: &mut IndexMap<String, Slot>, name: &str, slot: Slot, multiple: bool) {
859 if !multiple {
860 values.insert(name.to_string(), slot);
861 return;
862 }
863 match values.get_mut(name) {
864 Some(Slot::ListBool(v)) => {
865 if let Slot::Bool(b) = slot {
866 v.push(b);
867 }
868 }
869 Some(Slot::ListStr(v)) => {
870 if let Slot::Str(s) = slot {
871 v.push(s);
872 }
873 }
874 _ => {
875 let init = match slot {
876 Slot::Bool(b) => Slot::ListBool(vec![b]),
877 Slot::Str(s) => Slot::ListStr(vec![s]),
878 other => other,
879 };
880 values.insert(name.to_string(), init);
881 }
882 }
883}
884
885fn store_default(values: &mut IndexMap<String, Slot>, cfg: &OptCfg, def: Value) {
887 let slot = with_host(|h| match h.get(&def) {
890 Some(JsObj::Array(items)) => {
891 if cfg.is_string {
892 Slot::ListStr(items.iter().map(|v| h.str_of(v)).collect())
893 } else {
894 Slot::ListBool(items.iter().map(|v| h.truthy(v)).collect())
895 }
896 }
897 _ if cfg.is_string => Slot::Str(h.str_of(&def)),
898 _ => Slot::Bool(h.truthy(&def)),
899 });
900 values.insert(cfg.long.clone(), slot);
901}
902
903fn build_parse_result(values: IndexMap<String, Slot>, positionals: Vec<String>) -> Value {
905 with_host(|h| {
906 let mut vobj = IndexMap::new();
907 for (k, slot) in values {
908 let v = match slot {
909 Slot::Bool(b) => Value::Bool(b),
910 Slot::Str(s) => h.new_str(s),
911 Slot::ListBool(items) => {
912 let arr = items.into_iter().map(Value::Bool).collect();
913 h.new_array(arr)
914 }
915 Slot::ListStr(items) => {
916 let arr = items.into_iter().map(|s| h.new_str(s)).collect();
917 h.new_array(arr)
918 }
919 };
920 vobj.insert(k, v);
921 }
922 let values_v = h.new_object(vobj);
923 let pos: Vec<Value> = positionals.into_iter().map(|s| h.new_str(s)).collect();
924 let positionals_v = h.new_array(pos);
925 let mut out = IndexMap::new();
926 out.insert("values".to_string(), values_v);
927 out.insert("positionals".to_string(), positionals_v);
928 h.new_object(out)
929 })
930}
931
932fn read_arg_tokens(config: &Value) -> Vec<String> {
934 let arr = crate::builtins::get_property(config, "args").unwrap_or(Value::Undef);
935 let from_config = with_host(|h| match h.get(&arr) {
936 Some(JsObj::Array(items)) => Some(items.iter().map(|v| h.str_of(v)).collect::<Vec<_>>()),
937 _ => None,
938 });
939 from_config.unwrap_or_else(|| std::env::args().skip(2).collect())
940}
941
942fn read_bool_prop(config: &Value, name: &str, default: bool) -> bool {
944 match crate::builtins::get_property(config, name) {
945 Ok(Value::Undef) | Err(_) => default,
946 Ok(v) => with_host(|h| h.truthy(&v)),
947 }
948}
949
950fn read_options(config: &Value) -> Vec<OptCfg> {
952 let options = crate::builtins::get_property(config, "options").unwrap_or(Value::Undef);
953 let keys: Vec<String> = with_host(|h| match h.get(&options) {
954 Some(JsObj::Object(m)) => m.keys().filter(|k| !k.starts_with("@@")).cloned().collect(),
955 _ => Vec::new(),
956 });
957 keys.into_iter()
958 .map(|long| {
959 let spec = crate::builtins::get_property(&options, &long).unwrap_or(Value::Undef);
960 let type_str = crate::builtins::get_property(&spec, "type")
961 .ok()
962 .map(|v| with_host(|h| h.str_of(&v)))
963 .unwrap_or_default();
964 let short = match crate::builtins::get_property(&spec, "short") {
965 Ok(Value::Undef) | Err(_) => None,
966 Ok(v) => Some(with_host(|h| h.str_of(&v))),
967 };
968 let multiple = read_bool_prop(&spec, "multiple", false);
969 let default = match crate::builtins::get_property(&spec, "default") {
970 Ok(Value::Undef) | Err(_) => None,
971 Ok(v) => Some(v),
972 };
973 OptCfg {
974 long,
975 is_string: type_str == "string",
976 multiple,
977 short,
978 default,
979 }
980 })
981 .collect()
982}
983
984fn env_trim(s: &str) -> &str {
994 s.trim_matches(|c: char| c == ' ' || c == '\t' || c == '\n')
995}
996
997fn parse_env(input: &str) -> Value {
999 let lines = input.replace('\r', "");
1000 let mut pairs: IndexMap<String, String> = IndexMap::new();
1001 let mut content: &str = env_trim(&lines);
1002
1003 while !content.is_empty() {
1004 let first = content.as_bytes()[0];
1005 if first == b'\n' || first == b'#' {
1007 match content.find('\n') {
1008 Some(nl) => content = &content[nl + 1..],
1009 None => content = "",
1010 }
1011 continue;
1012 }
1013 let Some(eq_or_nl) = content.find(['=', '\n']) else {
1015 break;
1016 };
1017 if content.as_bytes()[eq_or_nl] == b'\n' {
1018 content = env_trim(&content[eq_or_nl + 1..]);
1019 continue;
1020 }
1021 let mut key = env_trim(&content[..eq_or_nl]);
1023 content = &content[eq_or_nl + 1..];
1024 if content.is_empty() || content.as_bytes()[0] == b'\n' {
1026 pairs.insert(key.to_string(), String::new());
1027 continue;
1028 }
1029 content = env_trim(content);
1030 if key.is_empty() {
1032 continue;
1033 }
1034 if let Some(rest) = key.strip_prefix("export ") {
1036 key = env_trim(rest);
1037 }
1038 if content.is_empty() {
1039 pairs.insert(key.to_string(), String::new());
1040 break;
1041 }
1042 let vfirst = content.as_bytes()[0];
1043 if vfirst == b'"' {
1045 if let Some(rel) = content[1..].find('"') {
1046 let closing = rel + 1;
1047 let value = content[1..closing].replace("\\n", "\n");
1048 pairs.insert(key.to_string(), value);
1049 match content[closing + 1..].find('\n') {
1050 Some(nl) => content = &content[closing + 1 + nl + 1..],
1051 None => content = "",
1052 }
1053 continue;
1054 }
1055 }
1057 if vfirst == b'\'' || vfirst == b'"' || vfirst == b'`' {
1059 match content[1..].find(vfirst as char) {
1060 None => match content.find('\n') {
1061 Some(nl) => {
1062 pairs.insert(key.to_string(), content[..nl].to_string());
1063 content = &content[nl + 1..];
1064 }
1065 None => {
1066 pairs.insert(key.to_string(), content.to_string());
1067 break;
1068 }
1069 },
1070 Some(rel) => {
1071 let closing = rel + 1;
1072 pairs.insert(key.to_string(), content[1..closing].to_string());
1073 match content[closing + 1..].find('\n') {
1074 Some(nl) => content = &content[closing + 1 + nl + 1..],
1075 None => content = "",
1076 }
1077 continue;
1078 }
1079 }
1080 } else {
1081 let (raw, next) = match content.find('\n') {
1083 Some(nl) => (&content[..nl], &content[nl + 1..]),
1084 None => (content, ""),
1085 };
1086 let value = match raw.find('#') {
1087 Some(h) => &raw[..h],
1088 None => raw,
1089 };
1090 pairs.insert(key.to_string(), env_trim(value).to_string());
1091 content = next;
1092 }
1093 content = env_trim(content);
1094 }
1095
1096 pairs.sort_keys();
1097 with_host(|h| {
1098 let mut m = IndexMap::new();
1099 for (k, v) in pairs {
1100 let val = h.new_str(v);
1101 m.insert(k, val);
1102 }
1103 h.new_object(m)
1104 })
1105}
1106
1107fn is_token_char(c: char) -> bool {
1116 c.is_ascii_alphanumeric()
1117 || matches!(
1118 c,
1119 '!' | '#'
1120 | '$'
1121 | '%'
1122 | '&'
1123 | '\''
1124 | '*'
1125 | '+'
1126 | '-'
1127 | '.'
1128 | '^'
1129 | '_'
1130 | '`'
1131 | '|'
1132 | '~'
1133 )
1134}
1135
1136fn is_quoted_string_char(c: char) -> bool {
1139 c == '\t' || ('\u{20}'..='\u{7e}').contains(&c) || ('\u{80}'..='\u{ff}').contains(&c)
1140}
1141
1142fn is_http_ws(c: char) -> bool {
1144 matches!(c, '\r' | '\n' | '\t' | ' ')
1145}
1146
1147fn ascii_lower(s: &str) -> String {
1150 s.to_ascii_lowercase()
1151}
1152
1153fn mime_syntax_err(part: &str, s: &str, index: Option<usize>) -> String {
1156 match index {
1157 Some(i) => format!(
1158 "TypeError [ERR_INVALID_MIME_SYNTAX]: The MIME syntax for a {part} in \"{s}\" is invalid at {i}"
1159 ),
1160 None => format!(
1161 "TypeError [ERR_INVALID_MIME_SYNTAX]: The MIME syntax for a {part} in \"{s}\" is invalid"
1162 ),
1163 }
1164}
1165
1166fn parse_type_and_subtype(s: &str) -> Result<(String, String, String), String> {
1169 let chars: Vec<char> = s.chars().collect();
1170 let n = chars.len();
1171 let mut pos = 0;
1173 while pos < n && is_http_ws(chars[pos]) {
1174 pos += 1;
1175 }
1176 let type_end = (pos..n).find(|&i| chars[i] == '/');
1178 let trimmed_type: String = match type_end {
1179 Some(e) => chars[pos..e].iter().collect(),
1180 None => chars[pos..].iter().collect(),
1181 };
1182 let type_invalid = trimmed_type.chars().position(|c| !is_token_char(c));
1183 if trimmed_type.is_empty() || type_invalid.is_some() || type_end.is_none() {
1184 return Err(mime_syntax_err("type", s, type_invalid));
1185 }
1186 let type_end = type_end.unwrap();
1187 pos = type_end + 1;
1188 let mime_type = ascii_lower(&trimmed_type);
1189 let sub_end = (pos..n).find(|&i| chars[i] == ';');
1191 let raw_subtype: &[char] = match sub_end {
1192 Some(e) => &chars[pos..e],
1193 None => &chars[pos..],
1194 };
1195 let mut new_pos = pos + raw_subtype.len();
1196 if sub_end.is_some() {
1197 new_pos += 1;
1198 }
1199 let mut end = raw_subtype.len();
1201 while end > 0 && is_http_ws(raw_subtype[end - 1]) {
1202 end -= 1;
1203 }
1204 let trimmed_subtype: String = raw_subtype[..end].iter().collect();
1205 let sub_invalid = trimmed_subtype.chars().position(|c| !is_token_char(c));
1206 if trimmed_subtype.is_empty() || sub_invalid.is_some() {
1207 return Err(mime_syntax_err("subtype", s, sub_invalid));
1208 }
1209 let subtype = ascii_lower(&trimmed_subtype);
1210 let params: String = chars[new_pos.min(n)..].iter().collect();
1211 Ok((mime_type, subtype, params))
1212}
1213
1214fn scan_quoted(chars: &[char], start: usize) -> (usize, bool, bool) {
1217 let n = chars.len();
1218 let mut i = start;
1219 let mut lone_backslash = false;
1220 let mut closing_quote = false;
1221 while i < n {
1222 match chars[i] {
1223 '\\' => {
1224 if i + 1 >= n {
1225 lone_backslash = true;
1226 i += 1;
1227 break;
1228 }
1229 i += 2;
1230 }
1231 '"' => {
1232 closing_quote = true;
1233 i += 1;
1234 break;
1235 }
1236 _ => i += 1,
1237 }
1238 }
1239 (i - start, lone_backslash, closing_quote)
1240}
1241
1242fn remove_backslashes(s: &[char]) -> String {
1244 let n = s.len();
1245 if n == 0 {
1246 return String::new();
1247 }
1248 let mut ret = String::new();
1249 let mut i = 0usize;
1250 while i < n - 1 {
1251 if s[i] == '\\' {
1252 i += 1;
1253 ret.push(s[i]);
1254 } else {
1255 ret.push(s[i]);
1256 }
1257 i += 1;
1258 }
1259 if i == n - 1 {
1260 ret.push(s[i]);
1261 }
1262 ret
1263}
1264
1265fn parse_mime_params(s: &str) -> Vec<(String, String)> {
1267 let chars: Vec<char> = s.chars().collect();
1268 let n = chars.len();
1269 let mut end_of_source = n;
1271 while end_of_source > 0 && is_http_ws(chars[end_of_source - 1]) {
1272 end_of_source -= 1;
1273 }
1274 let mut out: Vec<(String, String)> = Vec::new();
1275 let mut position = 0usize;
1276 while position < end_of_source {
1277 while position < n && is_http_ws(chars[position]) {
1279 position += 1;
1280 }
1281 let mut after = position;
1283 while after < n && chars[after] != ';' && chars[after] != '=' {
1284 after += 1;
1285 }
1286 let name = ascii_lower(&chars[position..after].iter().collect::<String>());
1287 position = after;
1288 if position < end_of_source {
1289 let ch = chars[position];
1290 position += 1;
1291 if ch == ';' {
1293 continue;
1294 }
1295 }
1296 if position >= end_of_source {
1297 break;
1298 }
1299 let value = if chars[position] == '"' {
1300 position += 1;
1302 let (matched_len, lone_backslash, closing_quote) = scan_quoted(&chars, position);
1303 let matched = &chars[position..position + matched_len];
1304 position += matched_len;
1305 let inside: &[char] = if lone_backslash || closing_quote {
1306 &matched[..matched.len().saturating_sub(1)]
1307 } else {
1308 matched
1309 };
1310 let mut v = remove_backslashes(inside);
1311 if lone_backslash {
1312 v.push('\\');
1313 }
1314 v
1315 } else {
1316 let value_end = (position..n).find(|&i| chars[i] == ';').unwrap_or(n);
1318 let raw = &chars[position..value_end];
1319 position += raw.len();
1320 let mut end = raw.len();
1321 while end > 0 && is_http_ws(raw[end - 1]) {
1322 end -= 1;
1323 }
1324 let trimmed: String = raw[..end].iter().collect();
1325 if trimmed.is_empty() {
1326 continue;
1329 }
1330 trimmed
1331 };
1332 let name_ok = !name.is_empty() && name.chars().all(is_token_char);
1334 let value_ok = value.chars().all(is_quoted_string_char);
1335 if name_ok && value_ok && !out.iter().any(|(k, _)| *k == name) {
1336 out.push((name, value));
1337 }
1338 position += 1;
1339 }
1340 out
1341}
1342
1343fn encode_param_value(value: &str) -> String {
1346 if value.is_empty() {
1347 return "\"\"".to_string();
1348 }
1349 if value.chars().all(is_token_char) {
1350 return value.to_string();
1351 }
1352 let mut escaped = String::with_capacity(value.len() + 2);
1353 for c in value.chars() {
1354 if c == '"' || c == '\\' {
1355 escaped.push('\\');
1356 }
1357 escaped.push(c);
1358 }
1359 format!("\"{escaped}\"")
1360}
1361
1362fn serialize_mime_params(pairs: &[(String, String)]) -> String {
1364 let mut ret = String::new();
1365 for (k, v) in pairs {
1366 if !ret.is_empty() {
1367 ret.push(';');
1368 }
1369 ret.push_str(k);
1370 ret.push('=');
1371 ret.push_str(&encode_param_value(v));
1372 }
1373 ret
1374}
1375
1376pub const MIME_PARAMS_METHODS: &[&str] = &[
1379 "get",
1380 "set",
1381 "has",
1382 "delete",
1383 "entries",
1384 "keys",
1385 "values",
1386 "toString",
1387 "toJSON",
1388 "@@iterator",
1389];
1390
1391pub const MIME_TYPE_METHODS: &[&str] = &["toString", "toJSON"];
1393
1394fn make_mime_params(pairs: &[(String, String)]) -> Value {
1396 with_host(|h| {
1397 let items: Vec<Value> = pairs
1398 .iter()
1399 .map(|(k, v)| {
1400 let kv = vec![h.new_str(k.clone()), h.new_str(v.clone())];
1401 h.new_array(kv)
1402 })
1403 .collect();
1404 let arr = h.new_array(items);
1405 let mut m = IndexMap::new();
1406 m.insert("@@native".into(), h.new_str("MIMEParams"));
1407 m.insert("@@pairs".into(), arr);
1408 h.new_object(m)
1409 })
1410}
1411
1412fn mime_pairs_of(recv: &Value) -> Vec<(String, String)> {
1414 with_host(|h| {
1415 let items: Vec<Value> = match h.get(recv) {
1416 Some(JsObj::Object(p)) => match p.get("@@pairs").and_then(|a| h.get(a)) {
1417 Some(JsObj::Array(items)) => items.clone(),
1418 _ => Vec::new(),
1419 },
1420 _ => Vec::new(),
1421 };
1422 items
1423 .iter()
1424 .map(|it| match h.get(it) {
1425 Some(JsObj::Array(kv)) => {
1426 let kv = kv.clone();
1427 let k = kv.first().map(|x| h.str_of(x)).unwrap_or_default();
1428 let v = kv.get(1).map(|x| h.str_of(x)).unwrap_or_default();
1429 (k, v)
1430 }
1431 _ => (h.str_of(it), String::new()),
1432 })
1433 .collect()
1434 })
1435}
1436
1437fn set_mime_pairs(recv: &Value, pairs: &[(String, String)]) {
1439 with_host(|h| {
1440 let items: Vec<Value> = pairs
1441 .iter()
1442 .map(|(k, v)| {
1443 let kv = vec![h.new_str(k.clone()), h.new_str(v.clone())];
1444 h.new_array(kv)
1445 })
1446 .collect();
1447 let arr = h.new_array(items);
1448 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
1449 p.insert("@@pairs".into(), arr);
1450 }
1451 });
1452}
1453
1454pub fn construct_mime_params(_args: &[Value]) -> Result<Value, String> {
1457 Ok(make_mime_params(&[]))
1458}
1459
1460pub fn mime_params_instance_call(
1462 recv: &Value,
1463 method: &str,
1464 args: &[Value],
1465) -> Result<Value, String> {
1466 match method {
1467 "get" => {
1468 let name = super::arg_str(args, 0);
1469 match mime_pairs_of(recv).into_iter().find(|(k, _)| *k == name) {
1470 Some((_, v)) => Ok(with_host(|h| h.new_str(v))),
1471 None => Ok(with_host(|h| h.null())),
1472 }
1473 }
1474 "has" => {
1475 let name = super::arg_str(args, 0);
1476 Ok(Value::Bool(
1477 mime_pairs_of(recv).iter().any(|(k, _)| *k == name),
1478 ))
1479 }
1480 "set" => {
1481 let name = super::arg_str(args, 0);
1482 let value = super::arg_str(args, 1);
1483 if let Some(i) = name.chars().position(|c| !is_token_char(c)) {
1484 return Err(mime_syntax_err("parameter name", &name, Some(i)));
1485 }
1486 if name.is_empty() {
1487 return Err(mime_syntax_err("parameter name", &name, None));
1488 }
1489 if let Some(i) = value.chars().position(|c| !is_quoted_string_char(c)) {
1490 return Err(mime_syntax_err("parameter value", &value, Some(i)));
1491 }
1492 let mut pairs = mime_pairs_of(recv);
1493 match pairs.iter_mut().find(|(k, _)| *k == name) {
1494 Some(slot) => slot.1 = value,
1495 None => pairs.push((name, value)),
1496 }
1497 set_mime_pairs(recv, &pairs);
1498 Ok(Value::Undef)
1499 }
1500 "delete" => {
1501 let name = super::arg_str(args, 0);
1502 let mut pairs = mime_pairs_of(recv);
1503 pairs.retain(|(k, _)| *k != name);
1504 set_mime_pairs(recv, &pairs);
1505 Ok(Value::Undef)
1506 }
1507 "keys" => {
1508 let pairs = mime_pairs_of(recv);
1509 Ok(with_host(|h| {
1510 let items = pairs.into_iter().map(|(k, _)| h.new_str(k)).collect();
1511 h.alloc(JsObj::Iter { items, idx: 0 })
1512 }))
1513 }
1514 "values" => {
1515 let pairs = mime_pairs_of(recv);
1516 Ok(with_host(|h| {
1517 let items = pairs.into_iter().map(|(_, v)| h.new_str(v)).collect();
1518 h.alloc(JsObj::Iter { items, idx: 0 })
1519 }))
1520 }
1521 "entries" | "@@iterator" => {
1522 let pairs = mime_pairs_of(recv);
1523 Ok(with_host(|h| {
1524 let items = pairs
1525 .into_iter()
1526 .map(|(k, v)| {
1527 let kv = vec![h.new_str(k), h.new_str(v)];
1528 h.new_array(kv)
1529 })
1530 .collect();
1531 h.alloc(JsObj::Iter { items, idx: 0 })
1532 }))
1533 }
1534 "toString" | "toJSON" => {
1535 let s = serialize_mime_params(&mime_pairs_of(recv));
1536 Ok(with_host(|h| h.new_str(s)))
1537 }
1538 _ => Err(crate::host::type_error(&format!(
1539 "mimeParams.{method} is not a function"
1540 ))),
1541 }
1542}
1543
1544pub fn construct_mime_type(args: &[Value]) -> Result<Value, String> {
1546 let input = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
1549 let (mime_type, subtype, params_str) = parse_type_and_subtype(&input)?;
1550 let essence = format!("{mime_type}/{subtype}");
1551 let params = make_mime_params(&parse_mime_params(¶ms_str));
1553 Ok(with_host(|h| {
1554 let mut m = IndexMap::new();
1555 m.insert("@@native".into(), h.new_str("MIMEType"));
1556 m.insert("type".into(), h.new_str(mime_type));
1557 m.insert("subtype".into(), h.new_str(subtype));
1558 m.insert("essence".into(), h.new_str(essence));
1559 m.insert("params".into(), params);
1560 h.new_object(m)
1561 }))
1562}
1563
1564pub fn mime_type_instance_call(
1568 recv: &Value,
1569 method: &str,
1570 _args: &[Value],
1571) -> Result<Value, String> {
1572 match method {
1573 "toString" | "toJSON" => {
1574 let (essence, params) = with_host(|h| match h.get(recv) {
1576 Some(JsObj::Object(p)) => (
1577 p.get("essence").map(|x| h.str_of(x)).unwrap_or_default(),
1578 p.get("params").cloned().unwrap_or(Value::Undef),
1579 ),
1580 _ => (String::new(), Value::Undef),
1581 });
1582 let param_str = serialize_mime_params(&mime_pairs_of(¶ms));
1583 let out = if param_str.is_empty() {
1584 essence
1585 } else {
1586 format!("{essence};{param_str}")
1587 };
1588 Ok(with_host(|h| h.new_str(out)))
1589 }
1590 _ => Err(crate::host::type_error(&format!(
1591 "mimeType.{method} is not a function"
1592 ))),
1593 }
1594}