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 "aborted",
33 "callbackify",
34 "parseEnv",
35 "debug",
36];
37
38pub fn constant(name: &str) -> Option<Value> {
41 match name {
42 "types" => Some(with_host(|h| h.alloc(JsObj::Builtin("util/types".into())))),
43 "TextEncoder" | "TextDecoder" | "MIMEType" | "MIMEParams" => {
44 Some(with_host(|h| h.alloc(JsObj::Builtin(name.into()))))
45 }
46 _ => None,
47 }
48}
49
50pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
51 if let Some(pred) = method.strip_prefix("types.") {
52 return Some(Ok(Value::Bool(type_predicate(pred, args.first()))));
53 }
54 Some(match method {
55 "format" => {
56 match format(args) {
59 Ok(s) => Ok(with_host(|h| h.new_str(s))),
60 Err(e) => Err(e),
61 }
62 }
63 "formatWithOptions" => match format(args.get(1..).unwrap_or(&[])) {
67 Ok(s) => Ok(with_host(|h| h.new_str(s))),
68 Err(e) => Err(e),
69 },
70 "inspect" => {
71 if let Some(v) = args.first() {
72 crate::builtins::materialize_stack(v);
73 }
74 let depth = match args.get(1) {
76 Some(opts) => match crate::builtins::get_property(opts, "depth") {
77 Ok(Value::Undef) => 2,
78 Ok(v) if with_host(|h| h.is_null(&v)) => i64::MAX,
79 Ok(v) => {
80 let n = with_host(|h| h.to_number(&v));
81 match n {
86 _ if n.is_nan() || n == f64::INFINITY => i64::MAX,
87 _ if n == f64::NEG_INFINITY => i64::MIN,
88 _ => n.trunc().clamp(i64::MIN as f64, i64::MAX as f64) as i64,
89 }
90 }
91 Err(_) => 2,
92 },
93 None => 2,
94 };
95 crate::host::set_inspect_max_depth(depth);
96 let opts = args.get(1).cloned().unwrap_or(Value::Undef);
100 let read = |k: &str| crate::builtins::get_property(&opts, k).unwrap_or(Value::Undef);
101 let compact = match read("compact") {
102 Value::Undef => 3,
103 Value::Bool(false) => 0,
104 Value::Bool(true) => i64::MAX,
107 v => with_host(|h| h.to_number(&v)).max(0.0) as i64,
108 };
109 let break_length = match read("breakLength") {
110 Value::Undef => 80,
111 v => {
112 let n = with_host(|h| h.to_number(&v));
113 if n.is_finite() {
114 n.max(0.0) as usize
115 } else {
116 usize::MAX
117 }
118 }
119 };
120 let sorted_opt = read("sorted");
127 let sorted = with_host(|h| h.truthy(&sorted_opt));
128 let custom_inspect = match read("customInspect") {
131 Value::Undef => true,
132 v => with_host(|h| h.truthy(&v)),
133 };
134 let show_hidden_opt = read("showHidden");
135 let show_hidden = with_host(|h| h.truthy(&show_hidden_opt));
136 let max_array_length = match read("maxArrayLength") {
140 Value::Undef => crate::host::DEFAULT_MAX_ARRAY_LENGTH,
141 v if with_host(|h| h.is_null(&v)) => usize::MAX,
142 v => {
143 let n = with_host(|h| h.to_number(&v));
144 if n.is_finite() {
145 n.max(0.0) as usize
146 } else {
147 usize::MAX
148 }
149 }
150 };
151 crate::host::set_inspect_compact(compact);
152 crate::host::set_inspect_break_length(break_length);
153 crate::host::set_inspect_sorted(sorted);
154 crate::host::set_inspect_max_array_length(max_array_length);
155 crate::host::set_inspect_custom(custom_inspect);
156 crate::host::set_inspect_show_hidden(show_hidden);
157 let out = with_host(|h| {
158 let s = h.inspect(&args.first().cloned().unwrap_or(Value::Undef));
159 h.new_str(s)
160 });
161 crate::host::set_inspect_max_depth(2);
162 crate::host::set_inspect_compact(3);
163 crate::host::set_inspect_break_length(80);
164 crate::host::set_inspect_sorted(false);
165 crate::host::set_inspect_max_array_length(crate::host::DEFAULT_MAX_ARRAY_LENGTH);
166 crate::host::set_inspect_custom(true);
167 crate::host::set_inspect_show_hidden(false);
168 Ok(out)
169 }
170 "deprecate" => Ok(args.first().cloned().unwrap_or(Value::Undef)),
174 "inherits" => Ok(inherits(args)),
178 "types" => Ok(Value::Undef),
181 "isDeepStrictEqual" => Ok(Value::Bool(super::assert::deep_equal(
182 &args.first().cloned().unwrap_or(Value::Undef),
183 &args.get(1).cloned().unwrap_or(Value::Undef),
184 true,
185 ))),
186 "isArray" => Ok(Value::Bool(with_host(|h| {
188 matches!(
189 h.get(&args.first().cloned().unwrap_or(Value::Undef)),
190 Some(JsObj::Array(_))
191 )
192 }))),
193 "stripVTControlCharacters" => {
195 let stripped = strip_vt(&super::arg_str(args, 0));
196 Ok(with_host(|h| h.new_str(stripped)))
197 }
198 "toUSVString" => Ok(with_host(|h| {
202 let s = h.str_of(&args.first().cloned().unwrap_or(Value::Undef));
203 h.new_str(s)
204 })),
205 "getSystemErrorName" => {
206 let e = errno_of(super::arg_num(args, 0));
207 let name = errno_name(e)
208 .map(str::to_string)
209 .unwrap_or_else(|| format!("Unknown system error {e}"));
210 Ok(with_host(|h| h.new_str(name)))
211 }
212 "getSystemErrorMessage" => {
213 let e = errno_of(super::arg_num(args, 0));
214 let msg = errno_message(e)
215 .map(str::to_string)
216 .unwrap_or_else(|| format!("Unknown system error {e}"));
217 Ok(with_host(|h| h.new_str(msg)))
218 }
219 "getSystemErrorMap" => Ok(system_error_map()),
220 "styleText" => return Some(style_text(args)),
221 "parseArgs" => return Some(parse_args(args)),
222 "debuglog" => return Some(debuglog(args)),
223 "promisify" => return Some(promisify(args)),
224 "aborted" => {
228 let signal = args.first().cloned().unwrap_or(Value::Undef);
229 return Some(aborted(&signal));
230 }
231 "callbackify" => return Some(callbackify(args)),
232 "parseEnv" => Ok(parse_env(&super::arg_str(args, 0))),
234 "debug" => return Some(debuglog(args)),
236 _ => return None,
237 })
238}
239
240fn inherits(args: &[Value]) -> Value {
242 let ctor = args.first().cloned().unwrap_or(Value::Undef);
243 let sup = args.get(1).cloned().unwrap_or(Value::Undef);
244 with_host(|h| {
245 let sup_proto = h.fn_prop(&sup, "prototype").unwrap_or_else(|| {
248 let mut props = indexmap::IndexMap::new();
249 props.insert("constructor".to_string(), sup.clone());
250 let p = h.new_object(props);
251 h.set_fn_prop(&sup, "prototype", p.clone());
252 p
253 });
254 let ctor_proto = h.fn_prop(&ctor, "prototype").unwrap_or_else(|| {
256 let mut props = indexmap::IndexMap::new();
257 props.insert("constructor".to_string(), ctor.clone());
258 let p = h.new_object(props);
259 h.set_fn_prop(&ctor, "prototype", p.clone());
260 p
261 });
262 h.set_proto(&ctor_proto, sup_proto);
263 h.set_fn_prop(&ctor, "super_", sup);
264 });
265 Value::Undef
266}
267
268fn type_predicate(pred: &str, v: Option<&Value>) -> bool {
269 let Some(v) = v else { return false };
270 with_host(|h| match pred {
271 "isMap" => matches!(h.get(v), Some(JsObj::Map { weak: false, .. })),
272 "isSet" => matches!(h.get(v), Some(JsObj::Set { weak: false, .. })),
273 "isPromise" => matches!(h.get(v), Some(JsObj::Promise { .. })),
274 _ => false,
275 })
276}
277
278fn fmt_directive_number(n: f64) -> String {
282 if n == 0.0 && n.is_sign_negative() {
283 return "-0".into();
284 }
285 crate::host::fmt_number(n)
286}
287
288fn coerce_via(parser: &str, arg: &Value) -> Result<f64, String> {
292 let v = crate::builtins::call_builtin_function(parser, vec![arg.clone()])?;
297 Ok(with_host(|h| h.to_number(&v)))
298}
299
300pub fn format(args: &[Value]) -> Result<String, String> {
308 for a in args {
313 crate::builtins::materialize_stack(a);
314 }
315 if args.is_empty() {
316 return Ok(String::new());
317 }
318 if args.len() == 1 {
321 return Ok(with_host(|h| h.console_format(&args[0])));
322 }
323 let fmt = with_host(|h| h.str_of(&args[0]));
324 if !matches!(args[0], Value::Str(_))
326 && !with_host(|h| matches!(h.get(&args[0]), Some(JsObj::Str(_))))
327 {
328 return Ok(with_host(|h| {
329 args.iter()
330 .map(|a| h.console_format(a))
331 .collect::<Vec<_>>()
332 .join(" ")
333 }));
334 }
335
336 let mut out = String::new();
337 let mut ai = 1usize;
338 let mut chars = fmt.chars().peekable();
339 while let Some(c) = chars.next() {
340 if c != '%' {
341 out.push(c);
342 continue;
343 }
344 let Some(&spec) = chars.peek() else {
345 out.push('%');
346 break;
347 };
348 if spec == '%' {
349 out.push('%');
350 chars.next();
351 continue;
352 }
353 if !matches!(spec, 's' | 'd' | 'i' | 'f' | 'j' | 'o' | 'O' | 'c') || ai >= args.len() {
354 out.push('%');
355 continue;
356 }
357 chars.next();
358 let arg = &args[ai];
359 ai += 1;
360 match spec {
361 's' => {
372 if let Value::Float(n) = arg {
376 out.push_str(&fmt_directive_number(*n));
377 continue;
378 }
379 let (bigint, use_inspect) = with_host(|h| match h.get(arg) {
380 Some(JsObj::BigInt(b)) => (Some(format!("{b}n")), false),
381 _ => {
382 let is_obj = h.type_of(arg) == "object" && !h.is_null(arg);
383 let stringifies = matches!(h.get(arg), Some(JsObj::Object(p))
402 if matches!(
403 p.get("@@native").map(|t| h.str_of(t)).as_deref(),
404 Some("Buffer") | Some("TypedArray")
405 ));
406 if stringifies {
407 return (None, false);
408 }
409 let scripted = ["toString", "@@toPrimitive"].iter().any(|k| {
410 crate::host::lookup_chain(h, arg, k).is_some_and(|f| {
411 matches!(
412 h.get(&f),
413 Some(JsObj::Func(_))
414 | Some(JsObj::Class(_))
415 | Some(JsObj::BoundFunc { .. })
416 )
417 })
418 });
419 (None, is_obj && !scripted)
420 }
421 });
422 if let Some(b) = bigint {
423 out.push_str(&b);
424 } else if use_inspect {
425 crate::host::set_inspect_max_depth(0);
426 let s = with_host(|h| h.inspect(arg));
427 crate::host::set_inspect_max_depth(2);
428 out.push_str(&s);
429 } else {
430 let s = crate::host::to_string_value(arg)
434 .map(|v| with_host(|h| h.str_of(&v)))
435 .unwrap_or_else(|_| with_host(|h| h.str_of(arg)));
436 out.push_str(&s);
437 }
438 }
439 'd' | 'i' => {
448 let big = with_host(|h| match h.get(arg) {
449 Some(JsObj::BigInt(b)) => Some(format!("{b}n")),
450 _ => None,
451 });
452 let is_symbol = with_host(|h| h.type_of(arg) == "symbol");
456 match big {
457 Some(s) => out.push_str(&s),
458 None if is_symbol => out.push_str("NaN"),
459 None if spec == 'd' => {
460 out.push_str(&fmt_directive_number(crate::host::to_number_value(arg)?))
461 }
462 None => out.push_str(&fmt_directive_number(coerce_via("parseInt", arg)?)),
463 }
464 }
465 'f' => {
466 let is_symbol = with_host(|h| h.type_of(arg) == "symbol");
467 if is_symbol {
468 out.push_str("NaN");
469 } else {
470 out.push_str(&fmt_directive_number(coerce_via("parseFloat", arg)?));
471 }
472 }
473 'j' => {
474 match crate::builtins::call_builtin_function("JSON.stringify", vec![arg.clone()]) {
478 Ok(v) => out.push_str(&with_host(|h| h.str_of(&v))),
479 Err(e) if e.contains("circular structure") => out.push_str("[Circular]"),
480 Err(e) => return Err(e),
481 }
482 }
483 'O' => out.push_str(&with_host(|h| h.inspect(arg))),
490 'o' => {
491 crate::host::set_inspect_show_hidden(true);
492 crate::host::set_inspect_max_depth(4);
493 let s = with_host(|h| h.inspect(arg));
494 crate::host::set_inspect_show_hidden(false);
495 crate::host::set_inspect_max_depth(2);
496 out.push_str(&s);
497 }
498 'c' => {} _ => {}
500 }
501 }
502 for a in &args[ai..] {
504 out.push(' ');
505 out.push_str(&with_host(|h| h.console_format(a)));
506 }
507 Ok(out)
508}
509
510fn run_completion(src: &str) -> Result<Value, String> {
520 crate::eval_in_global_scope(src)
521}
522
523const PROMISIFY_SRC: &str = "(function(original){\n\
524 return function(){\n\
525 var self = this;\n\
526 var args = Array.prototype.slice.call(arguments);\n\
527 return new Promise(function(resolve, reject){\n\
528 args.push(function(err, value){ if (err) reject(err); else resolve(value); });\n\
529 original.apply(self, args);\n\
530 });\n\
531 };\n\
532})";
533
534const CALLBACKIFY_SRC: &str = "(function(original){\n\
535 return function(){\n\
536 var self = this;\n\
537 var args = Array.prototype.slice.call(arguments);\n\
538 var cb = args.pop();\n\
539 Promise.resolve(original.apply(self, args)).then(\n\
540 function(value){ cb.call(self, null, value); },\n\
541 function(err){ cb.call(self, err || new Error('Promise was rejected with a falsy value')); }\n\
542 );\n\
543 };\n\
544})";
545
546fn aborted(signal: &Value) -> Result<Value, String> {
554 let already = crate::builtins::get_property(signal, "aborted").unwrap_or(Value::Undef);
555 if with_host(|h| h.truthy(&already)) {
556 return crate::builtins::promise_resolve_pub(Value::Undef);
557 }
558 let (promise, resolve) = crate::builtins::pending_promise_with_resolver();
559 crate::host::call_method(
562 signal,
563 "addEventListener",
564 vec![with_host(|h| h.new_str("abort")), resolve],
565 )?;
566 Ok(promise)
567}
568
569fn promisify(args: &[Value]) -> Result<Value, String> {
570 let orig = args.first().cloned().unwrap_or(Value::Undef);
571 if !with_host(|h| crate::host::is_callable(h, &orig)) {
572 return Err(std::format!(
573 "TypeError [ERR_INVALID_ARG_TYPE]: The \"original\" argument must be of \
574 type function. Received {}",
575 super::received_desc(&orig)
576 ));
577 }
578 let factory = run_completion(PROMISIFY_SRC)?;
579 crate::host::invoke(&factory, vec![orig], None)
580}
581
582fn callbackify(args: &[Value]) -> Result<Value, String> {
585 let orig = args.first().cloned().unwrap_or(Value::Undef);
586 if !with_host(|h| crate::host::is_callable(h, &orig)) {
587 return Err(std::format!(
588 "TypeError [ERR_INVALID_ARG_TYPE]: The \"original\" argument must be of \
589 type function. Received {}",
590 super::received_desc(&orig)
591 ));
592 }
593 let factory = run_completion(CALLBACKIFY_SRC)?;
594 crate::host::invoke(&factory, vec![orig], None)
595}
596
597const DEBUGLOG_ENABLED_SRC: &str = "(function(prefix){\n\
600 var util = require('util');\n\
601 return function(){\n\
602 console.error(prefix + ' ' + util.format.apply(null, arguments));\n\
603 };\n\
604})";
605
606fn debuglog(args: &[Value]) -> Result<Value, String> {
609 let section = super::arg_str(args, 0);
610 if debuglog_enabled(§ion) {
611 let prefix = format!("{} {}:", section.to_uppercase(), std::process::id());
612 let factory = run_completion(DEBUGLOG_ENABLED_SRC)?;
613 let pfx = with_host(|h| h.new_str(prefix));
614 crate::host::invoke(&factory, vec![pfx], None)
615 } else {
616 run_completion("(function(){})")
617 }
618}
619
620fn debuglog_enabled(section: &str) -> bool {
623 let Ok(env) = std::env::var("NODE_DEBUG") else {
624 return false;
625 };
626 let sec = section.to_uppercase();
627 env.split(|c: char| c == ',' || c.is_whitespace())
628 .filter(|s| !s.is_empty())
629 .any(|pat| {
630 let pat = pat.to_uppercase();
631 if pat.contains('*') {
632 wildcard_match(&pat, &sec)
633 } else {
634 pat == sec
635 }
636 })
637}
638
639fn wildcard_match(pat: &str, s: &str) -> bool {
641 let parts: Vec<&str> = pat.split('*').collect();
642 if parts.len() == 1 {
643 return pat == s;
644 }
645 let mut pos = 0usize;
646 for (i, part) in parts.iter().enumerate() {
647 if part.is_empty() {
648 continue;
649 }
650 if i == 0 {
651 if !s[pos..].starts_with(part) {
652 return false;
653 }
654 pos += part.len();
655 } else if i == parts.len() - 1 {
656 return s[pos..].ends_with(part);
657 } else if let Some(idx) = s[pos..].find(part) {
658 pos += idx + part.len();
659 } else {
660 return false;
661 }
662 }
663 true
664}
665
666fn strip_vt(s: &str) -> String {
671 let mut out = String::with_capacity(s.len());
672 let mut chars = s.chars().peekable();
673 while let Some(c) = chars.next() {
674 if c == '\u{9b}' {
675 while let Some(&n) = chars.peek() {
677 chars.next();
678 if ('\u{40}'..='\u{7e}').contains(&n) {
679 break;
680 }
681 }
682 continue;
683 }
684 if c != '\u{1b}' {
685 out.push(c);
686 continue;
687 }
688 match chars.peek() {
689 Some('[') => {
690 chars.next();
691 while let Some(&n) = chars.peek() {
692 chars.next();
693 if ('\u{40}'..='\u{7e}').contains(&n) {
694 break;
695 }
696 }
697 }
698 Some(']') => {
699 chars.next();
700 while let Some(&n) = chars.peek() {
701 if n == '\u{7}' {
702 chars.next();
703 break;
704 }
705 if n == '\u{1b}' {
706 chars.next();
707 if chars.peek() == Some(&'\\') {
708 chars.next();
709 }
710 break;
711 }
712 chars.next();
713 }
714 }
715 Some(_) => {
716 chars.next();
717 }
718 None => {}
719 }
720 }
721 out
722}
723
724fn style_names(v: &Value) -> Vec<String> {
728 with_host(|h| match h.get(v) {
729 Some(JsObj::Array(items)) => items.iter().map(|x| h.str_of(x)).collect(),
730 _ => vec![h.str_of(v)],
731 })
732}
733
734pub(crate) const STYLES: &[(&str, u16, u16)] = &[
745 ("reset", 0, 0),
746 ("bold", 1, 22),
747 ("dim", 2, 22),
748 ("italic", 3, 23),
749 ("underline", 4, 24),
750 ("blink", 5, 25),
751 ("inverse", 7, 27),
752 ("hidden", 8, 28),
753 ("strikethrough", 9, 29),
754 ("doubleunderline", 21, 24),
755 ("black", 30, 39),
756 ("red", 31, 39),
757 ("green", 32, 39),
758 ("yellow", 33, 39),
759 ("blue", 34, 39),
760 ("magenta", 35, 39),
761 ("cyan", 36, 39),
762 ("white", 37, 39),
763 ("bgBlack", 40, 49),
764 ("bgRed", 41, 49),
765 ("bgGreen", 42, 49),
766 ("bgYellow", 43, 49),
767 ("bgBlue", 44, 49),
768 ("bgMagenta", 45, 49),
769 ("bgCyan", 46, 49),
770 ("bgWhite", 47, 49),
771 ("framed", 51, 54),
772 ("overlined", 53, 55),
773 ("gray", 90, 39),
774 ("redBright", 91, 39),
775 ("greenBright", 92, 39),
776 ("yellowBright", 93, 39),
777 ("blueBright", 94, 39),
778 ("magentaBright", 95, 39),
779 ("cyanBright", 96, 39),
780 ("whiteBright", 97, 39),
781 ("bgGray", 100, 49),
782 ("bgRedBright", 101, 49),
783 ("bgGreenBright", 102, 49),
784 ("bgYellowBright", 103, 49),
785 ("bgBlueBright", 104, 49),
786 ("bgMagentaBright", 105, 49),
787 ("bgCyanBright", 106, 49),
788 ("bgWhiteBright", 107, 49),
789];
790
791pub(crate) const STYLE_ALIASES: &[(&str, u16, u16)] = &[
794 ("grey", 90, 39),
795 ("blackBright", 90, 39),
796 ("bgGrey", 100, 49),
797 ("bgBlackBright", 100, 49),
798 ("faint", 2, 22),
799 ("crossedout", 9, 29),
800 ("strikeThrough", 9, 29),
801 ("crossedOut", 9, 29),
802 ("conceal", 8, 28),
803 ("swapColors", 7, 27),
804 ("swapcolors", 7, 27),
805 ("doubleUnderline", 21, 24),
806];
807
808fn style_codes(name: &str) -> Option<(u16, u16)> {
810 STYLES
811 .iter()
812 .chain(STYLE_ALIASES.iter())
813 .find(|(n, _, _)| *n == name)
814 .map(|(_, o, c)| (*o, *c))
815}
816
817fn style_text(args: &[Value]) -> Result<Value, String> {
820 let fmt = args.first().cloned().unwrap_or(Value::Undef);
821 let names = style_names(&fmt);
822 let mut result = super::arg_str(args, 1);
823 for name in &names {
824 if name == "none" {
825 continue;
826 }
827 let (open, close) = style_codes(name).ok_or_else(|| {
828 let listed: Vec<String> = STYLES
831 .iter()
832 .chain(STYLE_ALIASES.iter())
833 .map(|(n, _, _)| format!("'{n}'"))
834 .collect();
835 crate::host::coded_error(
836 "TypeError",
837 "ERR_INVALID_ARG_VALUE",
838 &format!(
839 "The argument 'format' must be one of: {}. Received '{name}'",
840 listed.join(", ")
841 ),
842 )
843 })?;
844 result = format!("\u{1b}[{open}m{result}\u{1b}[{close}m");
845 }
846 Ok(with_host(|h| h.new_str(result)))
847}
848
849const ERRNO_TABLE: &[(&str, i32, &str)] = &[
857 ("E2BIG", libc::E2BIG, "argument list too long"),
858 ("EACCES", libc::EACCES, "permission denied"),
859 ("EADDRINUSE", libc::EADDRINUSE, "address already in use"),
860 (
861 "EADDRNOTAVAIL",
862 libc::EADDRNOTAVAIL,
863 "address not available",
864 ),
865 (
866 "EAFNOSUPPORT",
867 libc::EAFNOSUPPORT,
868 "address family not supported",
869 ),
870 ("EAGAIN", libc::EAGAIN, "resource temporarily unavailable"),
871 ("EALREADY", libc::EALREADY, "connection already in progress"),
872 ("EBADF", libc::EBADF, "bad file descriptor"),
873 ("EBUSY", libc::EBUSY, "resource busy or locked"),
874 ("ECANCELED", libc::ECANCELED, "operation canceled"),
875 (
876 "ECONNABORTED",
877 libc::ECONNABORTED,
878 "software caused connection abort",
879 ),
880 ("ECONNREFUSED", libc::ECONNREFUSED, "connection refused"),
881 ("ECONNRESET", libc::ECONNRESET, "connection reset by peer"),
882 (
883 "EDESTADDRREQ",
884 libc::EDESTADDRREQ,
885 "destination address required",
886 ),
887 ("EEXIST", libc::EEXIST, "file already exists"),
888 (
889 "EFAULT",
890 libc::EFAULT,
891 "bad address in system call argument",
892 ),
893 ("EFBIG", libc::EFBIG, "file too large"),
894 ("EHOSTDOWN", libc::EHOSTDOWN, "host is down"),
895 ("EHOSTUNREACH", libc::EHOSTUNREACH, "host is unreachable"),
896 ("EINTR", libc::EINTR, "interrupted system call"),
897 ("EINVAL", libc::EINVAL, "invalid argument"),
898 ("EIO", libc::EIO, "i/o error"),
899 ("EISCONN", libc::EISCONN, "socket is already connected"),
900 ("EISDIR", libc::EISDIR, "illegal operation on a directory"),
901 ("ELOOP", libc::ELOOP, "too many symbolic links encountered"),
902 ("EMFILE", libc::EMFILE, "too many open files"),
903 ("EMLINK", libc::EMLINK, "too many links"),
904 ("EMSGSIZE", libc::EMSGSIZE, "message too long"),
905 ("ENAMETOOLONG", libc::ENAMETOOLONG, "name too long"),
906 ("ENETDOWN", libc::ENETDOWN, "network is down"),
907 ("ENETUNREACH", libc::ENETUNREACH, "network is unreachable"),
908 ("ENFILE", libc::ENFILE, "file table overflow"),
909 ("ENOBUFS", libc::ENOBUFS, "no buffer space available"),
910 ("ENODEV", libc::ENODEV, "no such device"),
911 ("ENOENT", libc::ENOENT, "no such file or directory"),
912 ("ENOMEM", libc::ENOMEM, "not enough memory"),
913 ("ENOPROTOOPT", libc::ENOPROTOOPT, "protocol not available"),
914 ("ENOSPC", libc::ENOSPC, "no space left on device"),
915 ("ENOSYS", libc::ENOSYS, "function not implemented"),
916 ("ENOTCONN", libc::ENOTCONN, "socket is not connected"),
917 ("ENOTDIR", libc::ENOTDIR, "not a directory"),
918 ("ENOTEMPTY", libc::ENOTEMPTY, "directory not empty"),
919 ("ENOTSOCK", libc::ENOTSOCK, "socket operation on non-socket"),
920 ("ENXIO", libc::ENXIO, "no such device or address"),
921 (
922 "EOPNOTSUPP",
923 libc::EOPNOTSUPP,
924 "operation not supported on socket",
925 ),
926 (
927 "EOVERFLOW",
928 libc::EOVERFLOW,
929 "value too large for defined data type",
930 ),
931 ("EPERM", libc::EPERM, "operation not permitted"),
932 ("EPIPE", libc::EPIPE, "broken pipe"),
933 ("EPROTO", libc::EPROTO, "protocol error"),
934 (
935 "EPROTONOSUPPORT",
936 libc::EPROTONOSUPPORT,
937 "protocol not supported",
938 ),
939 (
940 "EPROTOTYPE",
941 libc::EPROTOTYPE,
942 "protocol wrong type for socket",
943 ),
944 ("ERANGE", libc::ERANGE, "result too large"),
945 ("EROFS", libc::EROFS, "read-only file system"),
946 (
947 "ESHUTDOWN",
948 libc::ESHUTDOWN,
949 "cannot send after transport endpoint shutdown",
950 ),
951 ("ESPIPE", libc::ESPIPE, "invalid seek"),
952 ("ESRCH", libc::ESRCH, "no such process"),
953 ("ETIMEDOUT", libc::ETIMEDOUT, "connection timed out"),
954 ("ETXTBSY", libc::ETXTBSY, "text file is busy"),
955 ("EXDEV", libc::EXDEV, "cross-device link not permitted"),
956];
957
958fn errno_of(err: f64) -> i32 {
961 if err < 0.0 {
962 (-err) as i32
963 } else {
964 err as i32
965 }
966}
967
968fn errno_name(e: i32) -> Option<&'static str> {
969 ERRNO_TABLE
970 .iter()
971 .find(|(_, code, _)| *code == e)
972 .map(|(n, _, _)| *n)
973}
974
975fn errno_message(e: i32) -> Option<&'static str> {
976 ERRNO_TABLE
977 .iter()
978 .find(|(_, code, _)| *code == e)
979 .map(|(_, _, m)| *m)
980}
981
982fn system_error_map() -> Value {
984 with_host(|h| {
985 let mut entries = indexmap::IndexMap::new();
986 for (name, code, msg) in ERRNO_TABLE {
987 let key_val = Value::Float(-(*code as f64));
988 let name_v = h.new_str(*name);
989 let msg_v = h.new_str(*msg);
990 let pair = h.new_array(vec![name_v, msg_v]);
991 let key = crate::host::map_key(h, &key_val);
992 entries.insert(key, (key_val, pair));
993 }
994 h.alloc(JsObj::Map {
995 entries,
996 weak: false,
997 })
998 })
999}
1000
1001enum Slot {
1005 Bool(bool),
1006 Str(String),
1007 ListBool(Vec<bool>),
1008 ListStr(Vec<String>),
1009}
1010
1011struct OptCfg {
1013 long: String,
1014 is_string: bool,
1015 multiple: bool,
1016 short: Option<String>,
1017 default: Option<Value>,
1018}
1019
1020fn parse_args(config_args: &[Value]) -> Result<Value, String> {
1023 let config = config_args.first().cloned().unwrap_or(Value::Undef);
1024 let tokens = read_arg_tokens(&config);
1025 let strict = read_bool_prop(&config, "strict", true);
1026 let allow_positionals = read_bool_prop(&config, "allowPositionals", false);
1027 let allow_negative = read_bool_prop(&config, "allowNegative", false);
1028 let opts = read_options(&config);
1029
1030 let lookup_long = |name: &str| opts.iter().find(|o| o.long == name);
1031 let lookup_short = |c: &str| opts.iter().find(|o| o.short.as_deref() == Some(c));
1032 let is_bool_long = |name: &str| opts.iter().any(|o| o.long == name && !o.is_string);
1033
1034 let mut values: IndexMap<String, Slot> = IndexMap::new();
1035 let mut positionals: Vec<String> = Vec::new();
1036
1037 let mut i = 0usize;
1038 while i < tokens.len() {
1039 let tok = tokens[i].clone();
1040 if tok == "--" {
1041 for t in &tokens[i + 1..] {
1042 positionals.push(t.clone());
1043 }
1044 break;
1045 }
1046 if let Some(rest) = tok.strip_prefix("--") {
1047 let (raw_name, inline) = match rest.split_once('=') {
1048 Some((n, v)) => (n.to_string(), Some(v.to_string())),
1049 None => (rest.to_string(), None),
1050 };
1051 let (name, negate) = match raw_name.strip_prefix("no-") {
1053 Some(base) if allow_negative && is_bool_long(base) => (base.to_string(), true),
1054 _ => (raw_name, false),
1055 };
1056 match lookup_long(&name) {
1057 None if strict => {
1058 return Err(format!(
1059 "TypeError [ERR_PARSE_ARGS_UNKNOWN_OPTION]: Unknown option '--{name}'"
1060 ))
1061 }
1062 None => {
1063 store(&mut values, &name, Slot::Bool(true), false);
1065 }
1066 Some(cfg) if cfg.is_string => {
1067 let val = match inline {
1068 Some(v) => v,
1069 None => {
1070 i += 1;
1071 tokens.get(i).cloned().ok_or_else(|| {
1072 format!(
1073 "TypeError [ERR_PARSE_ARGS_INVALID_OPTION_VALUE]: \
1074 Option '--{name} <value>' argument missing"
1075 )
1076 })?
1077 }
1078 };
1079 store(&mut values, &cfg.long, Slot::Str(val), cfg.multiple);
1080 }
1081 Some(cfg) => {
1082 if inline.is_some() && strict {
1083 return Err(format!(
1084 "TypeError [ERR_PARSE_ARGS_INVALID_OPTION_VALUE]: \
1085 Option '--{}' does not take an argument",
1086 cfg.long
1087 ));
1088 }
1089 store(&mut values, &cfg.long, Slot::Bool(!negate), cfg.multiple);
1090 }
1091 }
1092 } else if tok.len() > 1 && tok.starts_with('-') {
1093 let chars: Vec<char> = tok[1..].chars().collect();
1094 let mut ci = 0usize;
1095 while ci < chars.len() {
1096 let short = chars[ci].to_string();
1097 match lookup_short(&short) {
1098 None if strict => {
1099 return Err(format!(
1100 "TypeError [ERR_PARSE_ARGS_UNKNOWN_OPTION]: Unknown option '-{short}'"
1101 ))
1102 }
1103 None => {
1104 store(&mut values, &short, Slot::Bool(true), false);
1105 ci += 1;
1106 }
1107 Some(cfg) if cfg.is_string => {
1108 let remainder: String = chars[ci + 1..].iter().collect();
1109 let val = if !remainder.is_empty() {
1110 remainder
1111 } else {
1112 i += 1;
1113 tokens.get(i).cloned().ok_or_else(|| {
1114 format!(
1115 "TypeError [ERR_PARSE_ARGS_INVALID_OPTION_VALUE]: \
1116 Option '-{short}, --{} <value>' argument missing",
1117 cfg.long
1118 )
1119 })?
1120 };
1121 store(&mut values, &cfg.long, Slot::Str(val), cfg.multiple);
1122 break;
1123 }
1124 Some(cfg) => {
1125 store(&mut values, &cfg.long, Slot::Bool(true), cfg.multiple);
1126 ci += 1;
1127 }
1128 }
1129 }
1130 } else {
1131 if !allow_positionals && strict {
1132 return Err(format!(
1133 "TypeError [ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL]: \
1134 Unexpected argument '{tok}'. This command does not take positional arguments"
1135 ));
1136 }
1137 positionals.push(tok);
1138 }
1139 i += 1;
1140 }
1141
1142 for cfg in &opts {
1144 if !values.contains_key(&cfg.long) {
1145 if let Some(def) = &cfg.default {
1146 store_default(&mut values, cfg, def.clone());
1147 }
1148 }
1149 }
1150
1151 Ok(build_parse_result(values, positionals))
1152}
1153
1154fn store(values: &mut IndexMap<String, Slot>, name: &str, slot: Slot, multiple: bool) {
1156 if !multiple {
1157 values.insert(name.to_string(), slot);
1158 return;
1159 }
1160 match values.get_mut(name) {
1161 Some(Slot::ListBool(v)) => {
1162 if let Slot::Bool(b) = slot {
1163 v.push(b);
1164 }
1165 }
1166 Some(Slot::ListStr(v)) => {
1167 if let Slot::Str(s) = slot {
1168 v.push(s);
1169 }
1170 }
1171 _ => {
1172 let init = match slot {
1173 Slot::Bool(b) => Slot::ListBool(vec![b]),
1174 Slot::Str(s) => Slot::ListStr(vec![s]),
1175 other => other,
1176 };
1177 values.insert(name.to_string(), init);
1178 }
1179 }
1180}
1181
1182fn store_default(values: &mut IndexMap<String, Slot>, cfg: &OptCfg, def: Value) {
1184 let slot = with_host(|h| match h.get(&def) {
1187 Some(JsObj::Array(items)) => {
1188 if cfg.is_string {
1189 Slot::ListStr(items.iter().map(|v| h.str_of(v)).collect())
1190 } else {
1191 Slot::ListBool(items.iter().map(|v| h.truthy(v)).collect())
1192 }
1193 }
1194 _ if cfg.is_string => Slot::Str(h.str_of(&def)),
1195 _ => Slot::Bool(h.truthy(&def)),
1196 });
1197 values.insert(cfg.long.clone(), slot);
1198}
1199
1200fn build_parse_result(values: IndexMap<String, Slot>, positionals: Vec<String>) -> Value {
1202 with_host(|h| {
1203 let mut vobj = IndexMap::new();
1204 for (k, slot) in values {
1205 let v = match slot {
1206 Slot::Bool(b) => Value::Bool(b),
1207 Slot::Str(s) => h.new_str(s),
1208 Slot::ListBool(items) => {
1209 let arr = items.into_iter().map(Value::Bool).collect();
1210 h.new_array(arr)
1211 }
1212 Slot::ListStr(items) => {
1213 let arr = items.into_iter().map(|s| h.new_str(s)).collect();
1214 h.new_array(arr)
1215 }
1216 };
1217 vobj.insert(k, v);
1218 }
1219 let values_v = h.new_object(vobj);
1220 let pos: Vec<Value> = positionals.into_iter().map(|s| h.new_str(s)).collect();
1221 let positionals_v = h.new_array(pos);
1222 let mut out = IndexMap::new();
1223 out.insert("values".to_string(), values_v);
1224 out.insert("positionals".to_string(), positionals_v);
1225 h.new_object(out)
1226 })
1227}
1228
1229fn read_arg_tokens(config: &Value) -> Vec<String> {
1231 let arr = crate::builtins::get_property(config, "args").unwrap_or(Value::Undef);
1232 let from_config = with_host(|h| match h.get(&arr) {
1233 Some(JsObj::Array(items)) => Some(items.iter().map(|v| h.str_of(v)).collect::<Vec<_>>()),
1234 _ => None,
1235 });
1236 from_config.unwrap_or_else(|| std::env::args().skip(2).collect())
1237}
1238
1239fn read_bool_prop(config: &Value, name: &str, default: bool) -> bool {
1241 match crate::builtins::get_property(config, name) {
1242 Ok(Value::Undef) | Err(_) => default,
1243 Ok(v) => with_host(|h| h.truthy(&v)),
1244 }
1245}
1246
1247fn read_options(config: &Value) -> Vec<OptCfg> {
1249 let options = crate::builtins::get_property(config, "options").unwrap_or(Value::Undef);
1250 let keys: Vec<String> = with_host(|h| match h.get(&options) {
1251 Some(JsObj::Object(m)) => m.keys().filter(|k| !k.starts_with("@@")).cloned().collect(),
1252 _ => Vec::new(),
1253 });
1254 keys.into_iter()
1255 .map(|long| {
1256 let spec = crate::builtins::get_property(&options, &long).unwrap_or(Value::Undef);
1257 let type_str = crate::builtins::get_property(&spec, "type")
1258 .ok()
1259 .map(|v| with_host(|h| h.str_of(&v)))
1260 .unwrap_or_default();
1261 let short = match crate::builtins::get_property(&spec, "short") {
1262 Ok(Value::Undef) | Err(_) => None,
1263 Ok(v) => Some(with_host(|h| h.str_of(&v))),
1264 };
1265 let multiple = read_bool_prop(&spec, "multiple", false);
1266 let default = match crate::builtins::get_property(&spec, "default") {
1267 Ok(Value::Undef) | Err(_) => None,
1268 Ok(v) => Some(v),
1269 };
1270 OptCfg {
1271 long,
1272 is_string: type_str == "string",
1273 multiple,
1274 short,
1275 default,
1276 }
1277 })
1278 .collect()
1279}
1280
1281fn env_trim(s: &str) -> &str {
1291 s.trim_matches(|c: char| c == ' ' || c == '\t' || c == '\n')
1292}
1293
1294fn parse_env(input: &str) -> Value {
1296 let lines = input.replace('\r', "");
1297 let mut pairs: IndexMap<String, String> = IndexMap::new();
1298 let mut content: &str = env_trim(&lines);
1299
1300 while !content.is_empty() {
1301 let first = content.as_bytes()[0];
1302 if first == b'\n' || first == b'#' {
1304 match content.find('\n') {
1305 Some(nl) => content = &content[nl + 1..],
1306 None => content = "",
1307 }
1308 continue;
1309 }
1310 let Some(eq_or_nl) = content.find(['=', '\n']) else {
1312 break;
1313 };
1314 if content.as_bytes()[eq_or_nl] == b'\n' {
1315 content = env_trim(&content[eq_or_nl + 1..]);
1316 continue;
1317 }
1318 let mut key = env_trim(&content[..eq_or_nl]);
1320 content = &content[eq_or_nl + 1..];
1321 if content.is_empty() || content.as_bytes()[0] == b'\n' {
1323 pairs.insert(key.to_string(), String::new());
1324 continue;
1325 }
1326 content = env_trim(content);
1327 if key.is_empty() {
1329 continue;
1330 }
1331 if let Some(rest) = key.strip_prefix("export ") {
1333 key = env_trim(rest);
1334 }
1335 if content.is_empty() {
1336 pairs.insert(key.to_string(), String::new());
1337 break;
1338 }
1339 let vfirst = content.as_bytes()[0];
1340 if vfirst == b'"' {
1342 if let Some(rel) = content[1..].find('"') {
1343 let closing = rel + 1;
1344 let value = content[1..closing].replace("\\n", "\n");
1345 pairs.insert(key.to_string(), value);
1346 match content[closing + 1..].find('\n') {
1347 Some(nl) => content = &content[closing + 1 + nl + 1..],
1348 None => content = "",
1349 }
1350 continue;
1351 }
1352 }
1354 if vfirst == b'\'' || vfirst == b'"' || vfirst == b'`' {
1356 match content[1..].find(vfirst as char) {
1357 None => match content.find('\n') {
1358 Some(nl) => {
1359 pairs.insert(key.to_string(), content[..nl].to_string());
1360 content = &content[nl + 1..];
1361 }
1362 None => {
1363 pairs.insert(key.to_string(), content.to_string());
1364 break;
1365 }
1366 },
1367 Some(rel) => {
1368 let closing = rel + 1;
1369 pairs.insert(key.to_string(), content[1..closing].to_string());
1370 match content[closing + 1..].find('\n') {
1371 Some(nl) => content = &content[closing + 1 + nl + 1..],
1372 None => content = "",
1373 }
1374 continue;
1375 }
1376 }
1377 } else {
1378 let (raw, next) = match content.find('\n') {
1380 Some(nl) => (&content[..nl], &content[nl + 1..]),
1381 None => (content, ""),
1382 };
1383 let value = match raw.find('#') {
1384 Some(h) => &raw[..h],
1385 None => raw,
1386 };
1387 pairs.insert(key.to_string(), env_trim(value).to_string());
1388 content = next;
1389 }
1390 content = env_trim(content);
1391 }
1392
1393 pairs.sort_keys();
1394 with_host(|h| {
1395 let mut m = IndexMap::new();
1396 for (k, v) in pairs {
1397 let val = h.new_str(v);
1398 m.insert(k, val);
1399 }
1400 h.new_object(m)
1401 })
1402}
1403
1404fn is_token_char(c: char) -> bool {
1413 c.is_ascii_alphanumeric()
1414 || matches!(
1415 c,
1416 '!' | '#'
1417 | '$'
1418 | '%'
1419 | '&'
1420 | '\''
1421 | '*'
1422 | '+'
1423 | '-'
1424 | '.'
1425 | '^'
1426 | '_'
1427 | '`'
1428 | '|'
1429 | '~'
1430 )
1431}
1432
1433fn is_quoted_string_char(c: char) -> bool {
1436 c == '\t' || ('\u{20}'..='\u{7e}').contains(&c) || ('\u{80}'..='\u{ff}').contains(&c)
1437}
1438
1439fn is_http_ws(c: char) -> bool {
1441 matches!(c, '\r' | '\n' | '\t' | ' ')
1442}
1443
1444fn ascii_lower(s: &str) -> String {
1447 s.to_ascii_lowercase()
1448}
1449
1450fn mime_syntax_err(part: &str, s: &str, index: Option<usize>) -> String {
1453 match index {
1454 Some(i) => format!(
1455 "TypeError [ERR_INVALID_MIME_SYNTAX]: The MIME syntax for a {part} in \"{s}\" is invalid at {i}"
1456 ),
1457 None => format!(
1458 "TypeError [ERR_INVALID_MIME_SYNTAX]: The MIME syntax for a {part} in \"{s}\" is invalid"
1459 ),
1460 }
1461}
1462
1463fn parse_type_and_subtype(s: &str) -> Result<(String, String, String), String> {
1466 let chars: Vec<char> = s.chars().collect();
1467 let n = chars.len();
1468 let mut pos = 0;
1470 while pos < n && is_http_ws(chars[pos]) {
1471 pos += 1;
1472 }
1473 let type_end = (pos..n).find(|&i| chars[i] == '/');
1475 let trimmed_type: String = match type_end {
1476 Some(e) => chars[pos..e].iter().collect(),
1477 None => chars[pos..].iter().collect(),
1478 };
1479 let type_invalid = trimmed_type.chars().position(|c| !is_token_char(c));
1480 if trimmed_type.is_empty() || type_invalid.is_some() || type_end.is_none() {
1481 return Err(mime_syntax_err("type", s, type_invalid));
1482 }
1483 let type_end = type_end.unwrap();
1484 pos = type_end + 1;
1485 let mime_type = ascii_lower(&trimmed_type);
1486 let sub_end = (pos..n).find(|&i| chars[i] == ';');
1488 let raw_subtype: &[char] = match sub_end {
1489 Some(e) => &chars[pos..e],
1490 None => &chars[pos..],
1491 };
1492 let mut new_pos = pos + raw_subtype.len();
1493 if sub_end.is_some() {
1494 new_pos += 1;
1495 }
1496 let mut end = raw_subtype.len();
1498 while end > 0 && is_http_ws(raw_subtype[end - 1]) {
1499 end -= 1;
1500 }
1501 let trimmed_subtype: String = raw_subtype[..end].iter().collect();
1502 let sub_invalid = trimmed_subtype.chars().position(|c| !is_token_char(c));
1503 if trimmed_subtype.is_empty() || sub_invalid.is_some() {
1504 return Err(mime_syntax_err("subtype", s, sub_invalid));
1505 }
1506 let subtype = ascii_lower(&trimmed_subtype);
1507 let params: String = chars[new_pos.min(n)..].iter().collect();
1508 Ok((mime_type, subtype, params))
1509}
1510
1511fn scan_quoted(chars: &[char], start: usize) -> (usize, bool, bool) {
1514 let n = chars.len();
1515 let mut i = start;
1516 let mut lone_backslash = false;
1517 let mut closing_quote = false;
1518 while i < n {
1519 match chars[i] {
1520 '\\' => {
1521 if i + 1 >= n {
1522 lone_backslash = true;
1523 i += 1;
1524 break;
1525 }
1526 i += 2;
1527 }
1528 '"' => {
1529 closing_quote = true;
1530 i += 1;
1531 break;
1532 }
1533 _ => i += 1,
1534 }
1535 }
1536 (i - start, lone_backslash, closing_quote)
1537}
1538
1539fn remove_backslashes(s: &[char]) -> String {
1541 let n = s.len();
1542 if n == 0 {
1543 return String::new();
1544 }
1545 let mut ret = String::new();
1546 let mut i = 0usize;
1547 while i < n - 1 {
1548 if s[i] == '\\' {
1549 i += 1;
1550 ret.push(s[i]);
1551 } else {
1552 ret.push(s[i]);
1553 }
1554 i += 1;
1555 }
1556 if i == n - 1 {
1557 ret.push(s[i]);
1558 }
1559 ret
1560}
1561
1562fn parse_mime_params(s: &str) -> Vec<(String, String)> {
1564 let chars: Vec<char> = s.chars().collect();
1565 let n = chars.len();
1566 let mut end_of_source = n;
1568 while end_of_source > 0 && is_http_ws(chars[end_of_source - 1]) {
1569 end_of_source -= 1;
1570 }
1571 let mut out: Vec<(String, String)> = Vec::new();
1572 let mut position = 0usize;
1573 while position < end_of_source {
1574 while position < n && is_http_ws(chars[position]) {
1576 position += 1;
1577 }
1578 let mut after = position;
1580 while after < n && chars[after] != ';' && chars[after] != '=' {
1581 after += 1;
1582 }
1583 let name = ascii_lower(&chars[position..after].iter().collect::<String>());
1584 position = after;
1585 if position < end_of_source {
1586 let ch = chars[position];
1587 position += 1;
1588 if ch == ';' {
1590 continue;
1591 }
1592 }
1593 if position >= end_of_source {
1594 break;
1595 }
1596 let value = if chars[position] == '"' {
1597 position += 1;
1599 let (matched_len, lone_backslash, closing_quote) = scan_quoted(&chars, position);
1600 let matched = &chars[position..position + matched_len];
1601 position += matched_len;
1602 let inside: &[char] = if lone_backslash || closing_quote {
1603 &matched[..matched.len().saturating_sub(1)]
1604 } else {
1605 matched
1606 };
1607 let mut v = remove_backslashes(inside);
1608 if lone_backslash {
1609 v.push('\\');
1610 }
1611 v
1612 } else {
1613 let value_end = (position..n).find(|&i| chars[i] == ';').unwrap_or(n);
1615 let raw = &chars[position..value_end];
1616 position += raw.len();
1617 let mut end = raw.len();
1618 while end > 0 && is_http_ws(raw[end - 1]) {
1619 end -= 1;
1620 }
1621 let trimmed: String = raw[..end].iter().collect();
1622 if trimmed.is_empty() {
1623 continue;
1626 }
1627 trimmed
1628 };
1629 let name_ok = !name.is_empty() && name.chars().all(is_token_char);
1631 let value_ok = value.chars().all(is_quoted_string_char);
1632 if name_ok && value_ok && !out.iter().any(|(k, _)| *k == name) {
1633 out.push((name, value));
1634 }
1635 position += 1;
1636 }
1637 out
1638}
1639
1640fn encode_param_value(value: &str) -> String {
1643 if value.is_empty() {
1644 return "\"\"".to_string();
1645 }
1646 if value.chars().all(is_token_char) {
1647 return value.to_string();
1648 }
1649 let mut escaped = String::with_capacity(value.len() + 2);
1650 for c in value.chars() {
1651 if c == '"' || c == '\\' {
1652 escaped.push('\\');
1653 }
1654 escaped.push(c);
1655 }
1656 format!("\"{escaped}\"")
1657}
1658
1659fn serialize_mime_params(pairs: &[(String, String)]) -> String {
1661 let mut ret = String::new();
1662 for (k, v) in pairs {
1663 if !ret.is_empty() {
1664 ret.push(';');
1665 }
1666 ret.push_str(k);
1667 ret.push('=');
1668 ret.push_str(&encode_param_value(v));
1669 }
1670 ret
1671}
1672
1673pub const MIME_PARAMS_METHODS: &[&str] = &[
1676 "get",
1677 "set",
1678 "has",
1679 "delete",
1680 "entries",
1681 "keys",
1682 "values",
1683 "toString",
1684 "toJSON",
1685 "@@iterator",
1686];
1687
1688pub const MIME_TYPE_METHODS: &[&str] = &["toString", "toJSON"];
1690
1691fn make_mime_params(pairs: &[(String, String)]) -> Value {
1693 with_host(|h| {
1694 let items: Vec<Value> = pairs
1695 .iter()
1696 .map(|(k, v)| {
1697 let kv = vec![h.new_str(k.clone()), h.new_str(v.clone())];
1698 h.new_array(kv)
1699 })
1700 .collect();
1701 let arr = h.new_array(items);
1702 let mut m = IndexMap::new();
1703 m.insert("@@native".into(), h.new_str("MIMEParams"));
1704 m.insert("@@pairs".into(), arr);
1705 h.new_object(m)
1706 })
1707}
1708
1709fn mime_pairs_of(recv: &Value) -> Vec<(String, String)> {
1711 with_host(|h| {
1712 let items: Vec<Value> = match h.get(recv) {
1713 Some(JsObj::Object(p)) => match p.get("@@pairs").and_then(|a| h.get(a)) {
1714 Some(JsObj::Array(items)) => items.clone(),
1715 _ => Vec::new(),
1716 },
1717 _ => Vec::new(),
1718 };
1719 items
1720 .iter()
1721 .map(|it| match h.get(it) {
1722 Some(JsObj::Array(kv)) => {
1723 let kv = kv.clone();
1724 let k = kv.first().map(|x| h.str_of(x)).unwrap_or_default();
1725 let v = kv.get(1).map(|x| h.str_of(x)).unwrap_or_default();
1726 (k, v)
1727 }
1728 _ => (h.str_of(it), String::new()),
1729 })
1730 .collect()
1731 })
1732}
1733
1734fn set_mime_pairs(recv: &Value, pairs: &[(String, String)]) {
1736 with_host(|h| {
1737 let items: Vec<Value> = pairs
1738 .iter()
1739 .map(|(k, v)| {
1740 let kv = vec![h.new_str(k.clone()), h.new_str(v.clone())];
1741 h.new_array(kv)
1742 })
1743 .collect();
1744 let arr = h.new_array(items);
1745 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
1746 p.insert("@@pairs".into(), arr);
1747 }
1748 });
1749}
1750
1751pub fn construct_mime_params(_args: &[Value]) -> Result<Value, String> {
1754 Ok(make_mime_params(&[]))
1755}
1756
1757pub fn mime_params_instance_call(
1759 recv: &Value,
1760 method: &str,
1761 args: &[Value],
1762) -> Result<Value, String> {
1763 match method {
1764 "get" => {
1765 let name = super::arg_str(args, 0);
1766 match mime_pairs_of(recv).into_iter().find(|(k, _)| *k == name) {
1767 Some((_, v)) => Ok(with_host(|h| h.new_str(v))),
1768 None => Ok(with_host(|h| h.null())),
1769 }
1770 }
1771 "has" => {
1772 let name = super::arg_str(args, 0);
1773 Ok(Value::Bool(
1774 mime_pairs_of(recv).iter().any(|(k, _)| *k == name),
1775 ))
1776 }
1777 "set" => {
1778 let name = super::arg_str(args, 0);
1779 let value = super::arg_str(args, 1);
1780 if let Some(i) = name.chars().position(|c| !is_token_char(c)) {
1781 return Err(mime_syntax_err("parameter name", &name, Some(i)));
1782 }
1783 if name.is_empty() {
1784 return Err(mime_syntax_err("parameter name", &name, None));
1785 }
1786 if let Some(i) = value.chars().position(|c| !is_quoted_string_char(c)) {
1787 return Err(mime_syntax_err("parameter value", &value, Some(i)));
1788 }
1789 let mut pairs = mime_pairs_of(recv);
1790 match pairs.iter_mut().find(|(k, _)| *k == name) {
1791 Some(slot) => slot.1 = value,
1792 None => pairs.push((name, value)),
1793 }
1794 set_mime_pairs(recv, &pairs);
1795 Ok(Value::Undef)
1796 }
1797 "delete" => {
1798 let name = super::arg_str(args, 0);
1799 let mut pairs = mime_pairs_of(recv);
1800 pairs.retain(|(k, _)| *k != name);
1801 set_mime_pairs(recv, &pairs);
1802 Ok(Value::Undef)
1803 }
1804 "keys" => {
1805 let pairs = mime_pairs_of(recv);
1806 Ok(with_host(|h| {
1807 let items = pairs.into_iter().map(|(k, _)| h.new_str(k)).collect();
1808 h.alloc(JsObj::Iter {
1809 items,
1810 idx: 0,
1811 array: None,
1812 })
1813 }))
1814 }
1815 "values" => {
1816 let pairs = mime_pairs_of(recv);
1817 Ok(with_host(|h| {
1818 let items = pairs.into_iter().map(|(_, v)| h.new_str(v)).collect();
1819 h.alloc(JsObj::Iter {
1820 items,
1821 idx: 0,
1822 array: None,
1823 })
1824 }))
1825 }
1826 "entries" | "@@iterator" => {
1827 let pairs = mime_pairs_of(recv);
1828 Ok(with_host(|h| {
1829 let items = pairs
1830 .into_iter()
1831 .map(|(k, v)| {
1832 let kv = vec![h.new_str(k), h.new_str(v)];
1833 h.new_array(kv)
1834 })
1835 .collect();
1836 h.alloc(JsObj::Iter {
1837 items,
1838 idx: 0,
1839 array: None,
1840 })
1841 }))
1842 }
1843 "toString" | "toJSON" => {
1844 let s = serialize_mime_params(&mime_pairs_of(recv));
1845 Ok(with_host(|h| h.new_str(s)))
1846 }
1847 _ => Err(crate::host::type_error(&format!(
1848 "mimeParams.{method} is not a function"
1849 ))),
1850 }
1851}
1852
1853pub fn construct_mime_type(args: &[Value]) -> Result<Value, String> {
1855 let input = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
1858 let (mime_type, subtype, params_str) = parse_type_and_subtype(&input)?;
1859 let essence = format!("{mime_type}/{subtype}");
1860 let params = make_mime_params(&parse_mime_params(¶ms_str));
1862 Ok(with_host(|h| {
1863 let mut m = IndexMap::new();
1864 m.insert("@@native".into(), h.new_str("MIMEType"));
1865 m.insert("type".into(), h.new_str(mime_type));
1866 m.insert("subtype".into(), h.new_str(subtype));
1867 m.insert("essence".into(), h.new_str(essence));
1868 m.insert("params".into(), params);
1869 h.new_object(m)
1870 }))
1871}
1872
1873pub fn mime_type_instance_call(
1877 recv: &Value,
1878 method: &str,
1879 _args: &[Value],
1880) -> Result<Value, String> {
1881 match method {
1882 "toString" | "toJSON" => {
1883 let (essence, params) = with_host(|h| match h.get(recv) {
1885 Some(JsObj::Object(p)) => (
1886 p.get("essence").map(|x| h.str_of(x)).unwrap_or_default(),
1887 p.get("params").cloned().unwrap_or(Value::Undef),
1888 ),
1889 _ => (String::new(), Value::Undef),
1890 });
1891 let param_str = serialize_mime_params(&mime_pairs_of(¶ms));
1892 let out = if param_str.is_empty() {
1893 essence
1894 } else {
1895 format!("{essence};{param_str}")
1896 };
1897 Ok(with_host(|h| h.new_str(out)))
1898 }
1899 _ => Err(crate::host::type_error(&format!(
1900 "mimeType.{method} is not a function"
1901 ))),
1902 }
1903}