1use super::{arg_str, from_base64, from_hex, to_base64, to_hex};
6use crate::host::{with_host, JsObj};
7use fusevm::Value;
8use indexmap::IndexMap;
9
10pub const STATIC_METHODS: &[&str] = &[
11 "from",
12 "alloc",
13 "allocUnsafe",
14 "concat",
15 "isBuffer",
16 "byteLength",
17];
18
19pub const MODULE_METHODS: &[&str] = &["atob", "btoa", "isAscii", "isUtf8", "transcode"];
23
24pub fn module_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
26 Some(match method {
27 "atob" => {
29 let s = arg_str(args, 0);
30 let bytes = from_base64(&s);
31 let bin: String = bytes.iter().map(|b| *b as char).collect();
32 Ok(with_host(|h| h.new_str(bin)))
33 }
34 "btoa" => {
36 let s = arg_str(args, 0);
37 let bytes: Vec<u8> = s.chars().map(|c| c as u32 as u8).collect();
38 let b64 = to_base64(&bytes);
39 Ok(with_host(|h| h.new_str(b64)))
40 }
41 "isAscii" => {
42 let bytes = input_bytes(args.first());
43 Ok(Value::Bool(bytes.iter().all(|b| *b < 0x80)))
44 }
45 "isUtf8" => {
46 let bytes = input_bytes(args.first());
47 Ok(Value::Bool(std::str::from_utf8(&bytes).is_ok()))
48 }
49 "transcode" => {
52 let src = input_bytes(args.first());
53 let from = arg_str(args, 1);
54 let to = arg_str(args, 2);
55 let s = bytes_to_string(&src, &from);
56 let out = string_to_bytes(&s, &to);
57 Ok(from_bytes(&out))
58 }
59 _ => return None,
60 })
61}
62
63fn input_bytes(v: Option<&Value>) -> Vec<u8> {
65 match v {
66 None => Vec::new(),
67 Some(v) => {
68 if let Some(s) = with_host(|h| h.as_str(v)) {
69 s.into_bytes()
70 } else {
71 bytes_of(v)
72 }
73 }
74 }
75}
76
77fn bytes_to_string(bytes: &[u8], enc: &str) -> String {
79 match enc.to_ascii_lowercase().as_str() {
80 "ascii" | "latin1" | "binary" => bytes.iter().map(|b| *b as char).collect(),
81 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => {
82 let units: Vec<u16> = bytes
83 .chunks_exact(2)
84 .map(|c| u16::from_le_bytes([c[0], c[1]]))
85 .collect();
86 String::from_utf16_lossy(&units)
87 }
88 _ => String::from_utf8_lossy(bytes).into_owned(),
89 }
90}
91
92fn string_to_bytes(s: &str, enc: &str) -> Vec<u8> {
94 match enc.to_ascii_lowercase().as_str() {
95 "ascii" | "latin1" | "binary" => s.chars().map(|c| c as u32 as u8).collect(),
96 "utf16le" | "utf-16le" | "ucs2" | "ucs-2" => {
97 s.encode_utf16().flat_map(|u| u.to_le_bytes()).collect()
98 }
99 _ => s.as_bytes().to_vec(),
100 }
101}
102
103fn part_bytes(v: &Value) -> Vec<u8> {
113 match with_host(|h| h.as_str(v)) {
114 Some(s) => s.into_bytes(),
115 None => bytes_of(v),
116 }
117}
118
119fn gather_parts(parts: &Value) -> Vec<u8> {
121 let items = with_host(|h| match h.get(parts) {
122 Some(JsObj::Array(it)) => it.clone(),
123 _ => Vec::new(),
124 });
125 let mut out = Vec::new();
126 for it in &items {
127 out.extend(part_bytes(it));
128 }
129 out
130}
131
132fn opt_type(opts: Option<&Value>) -> String {
134 match opts {
135 Some(v) => with_host(|h| match h.get(v) {
136 Some(JsObj::Object(p)) => p.get("type").map(|x| h.str_of(x)).unwrap_or_default(),
137 _ => String::new(),
138 }),
139 None => String::new(),
140 }
141}
142
143fn build_blob(tag: &str, bytes: &[u8], typ: &str, extra: IndexMap<String, Value>) -> Value {
146 with_host(|h| {
147 let arr = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
148 let mut m = IndexMap::new();
149 m.insert("@@native".into(), h.new_str(tag.to_string()));
150 m.insert("@@bytes".into(), arr);
151 m.insert("size".into(), Value::Float(bytes.len() as f64));
152 m.insert("type".into(), h.new_str(typ.to_string()));
153 for (k, v) in extra {
154 m.insert(k, v);
155 }
156 h.new_object(m)
157 })
158}
159
160pub fn construct_blob(args: &[Value]) -> Result<Value, String> {
162 let bytes = gather_parts(&args.first().cloned().unwrap_or(Value::Undef));
163 let typ = opt_type(args.get(1));
164 Ok(build_blob("Blob", &bytes, &typ, IndexMap::new()))
165}
166
167pub fn construct_file(args: &[Value]) -> Result<Value, String> {
169 let bytes = gather_parts(&args.first().cloned().unwrap_or(Value::Undef));
170 let name = arg_str(args, 1);
171 let typ = opt_type(args.get(2));
172 let last_modified = args
174 .get(2)
175 .map(|v| {
176 with_host(|h| match h.get(v) {
177 Some(JsObj::Object(p)) => {
178 p.get("lastModified").map(|x| h.to_number(x)).unwrap_or(0.0)
179 }
180 _ => 0.0,
181 })
182 })
183 .unwrap_or(0.0);
184 let extra = with_host(|h| {
185 let mut m = IndexMap::new();
186 m.insert("name".to_string(), h.new_str(name));
187 m.insert("lastModified".to_string(), Value::Float(last_modified));
188 m
189 });
190 Ok(build_blob("File", &bytes, &typ, extra))
191}
192
193pub const BLOB_METHODS: &[&str] = &["text", "arrayBuffer", "bytes", "slice"];
195
196pub fn blob_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
201 let bytes = bytes_of(recv);
202 match method {
203 "text" => {
204 let s = String::from_utf8_lossy(&bytes).into_owned();
205 let sv = with_host(|h| h.new_str(s));
206 Ok(crate::host::promise_of(&sv))
207 }
208 "arrayBuffer" | "bytes" => {
209 let buf = from_bytes(&bytes);
210 Ok(crate::host::promise_of(&buf))
211 }
212 "slice" => {
213 let (s, e) = slice_bounds(args, bytes.len());
214 let typ = if args.len() > 2 {
215 arg_str(args, 2)
216 } else {
217 String::new()
218 };
219 Ok(build_blob("Blob", &bytes[s..e], &typ, IndexMap::new()))
220 }
221 _ => Err(crate::host::type_error(&format!(
222 "blob.{method} is not a function"
223 ))),
224 }
225}
226
227pub fn from_bytes(bytes: &[u8]) -> Value {
229 with_host(|h| {
230 let arr = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
231 let mut m = IndexMap::new();
232 m.insert("@@native".into(), h.new_str("Buffer"));
233 m.insert("@@bytes".into(), arr);
234 m.insert("length".into(), Value::Float(bytes.len() as f64));
235 h.new_object(m)
236 })
237}
238
239fn bytes_of(recv: &Value) -> Vec<u8> {
240 with_host(|h| match h.get(recv) {
241 Some(JsObj::Object(p)) => match p.get("@@bytes").and_then(|v| h.get(v)) {
242 Some(JsObj::Array(items)) => items.iter().map(|v| h.to_number(v) as u8).collect(),
243 _ => Vec::new(),
244 },
245 _ => Vec::new(),
246 })
247}
248
249pub fn static_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
250 Some(match method {
251 "from" => from(args),
252 "alloc" => {
253 let n = super::arg_num(args, 0).max(0.0) as usize;
254 let pat = if args.len() > 1 {
256 fill_pattern(args, 1)
257 } else {
258 vec![0]
259 };
260 let bytes: Vec<u8> = if pat.is_empty() {
261 vec![0u8; n]
262 } else {
263 (0..n).map(|i| pat[i % pat.len()]).collect()
264 };
265 Ok(from_bytes(&bytes))
266 }
267 "allocUnsafe" => Ok(from_bytes(&vec![
268 0u8;
269 super::arg_num(args, 0).max(0.0) as usize
270 ])),
271 "concat" => concat(args),
272 "isBuffer" => Ok(Value::Bool(
273 super::native_tag(&args.first().cloned().unwrap_or(Value::Undef)).as_deref()
274 == Some("Buffer"),
275 )),
276 "byteLength" => {
277 let enc = args
278 .get(1)
279 .map(|_| arg_str(args, 1))
280 .unwrap_or_else(|| "utf8".into());
281 Ok(Value::Float(
282 decode_str(&arg_str(args, 0), &enc).len() as f64
283 ))
284 }
285 _ => return None,
286 })
287}
288
289fn from(args: &[Value]) -> Result<Value, String> {
290 let v = args.first().cloned().unwrap_or(Value::Undef);
291 let arr = with_host(|h| match h.get(&v) {
293 Some(JsObj::Array(items)) => Some(
294 items
295 .iter()
296 .map(|x| h.to_number(x) as u8)
297 .collect::<Vec<u8>>(),
298 ),
299 _ => None,
300 });
301 if let Some(bytes) = arr {
302 return Ok(from_bytes(&bytes));
303 }
304 if super::native_tag(&v).as_deref() == Some("Buffer") {
306 return Ok(from_bytes(&bytes_of(&v)));
307 }
308 let enc = if args.len() > 1 {
310 arg_str(args, 1)
311 } else {
312 "utf8".into()
313 };
314 Ok(from_bytes(&decode_str(&arg_str(args, 0), &enc)))
315}
316
317fn concat(args: &[Value]) -> Result<Value, String> {
318 let list = with_host(
319 |h| match h.get(&args.first().cloned().unwrap_or(Value::Undef)) {
320 Some(JsObj::Array(items)) => items.clone(),
321 _ => Vec::new(),
322 },
323 );
324 let mut out = Vec::new();
325 for b in &list {
326 out.extend(bytes_of(b));
327 }
328 Ok(from_bytes(&out))
329}
330
331pub fn instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
333 let bytes = bytes_of(recv);
334 match method {
335 "toString" => {
336 let enc = if args.is_empty() {
337 "utf8".into()
338 } else {
339 arg_str(args, 0)
340 };
341 Ok(with_host(|h| h.new_str(encode_bytes(&bytes, &enc))))
342 }
343 "toJSON" => Ok(with_host(|h| {
344 let data = h.new_array(bytes.iter().map(|b| Value::Float(*b as f64)).collect());
345 let mut m = IndexMap::new();
346 m.insert("type".into(), h.new_str("Buffer"));
347 m.insert("data".into(), data);
348 h.new_object(m)
349 })),
350 "equals" => {
351 let other = bytes_of(&args.first().cloned().unwrap_or(Value::Undef));
352 Ok(Value::Bool(bytes == other))
353 }
354 "slice" | "subarray" => {
355 let (s, e) = slice_bounds(args, bytes.len());
356 Ok(from_bytes(&bytes[s..e]))
357 }
358 "readUInt8" => {
359 let i = super::arg_num(args, 0).max(0.0) as usize;
360 Ok(Value::Float(*bytes.get(i).unwrap_or(&0) as f64))
361 }
362 "includes" | "indexOf" | "lastIndexOf" => {
363 let needle = decode_str(&arg_str(args, 0), "utf8");
364 let pos = if needle.is_empty() {
366 Some(if method == "lastIndexOf" {
367 bytes.len()
368 } else {
369 0
370 })
371 } else if method == "lastIndexOf" {
372 bytes
373 .windows(needle.len())
374 .rposition(|w| w == needle.as_slice())
375 } else {
376 bytes
377 .windows(needle.len())
378 .position(|w| w == needle.as_slice())
379 };
380 if method == "includes" {
381 Ok(Value::Bool(pos.is_some()))
382 } else {
383 Ok(Value::Float(pos.map(|p| p as f64).unwrap_or(-1.0)))
384 }
385 }
386 "compare" => {
388 let other = bytes_of(&args.first().cloned().unwrap_or(Value::Undef));
389 Ok(Value::Float(match bytes.cmp(&other) {
390 std::cmp::Ordering::Less => -1.0,
391 std::cmp::Ordering::Equal => 0.0,
392 std::cmp::Ordering::Greater => 1.0,
393 }))
394 }
395 "readUInt16BE" => {
397 let i = super::arg_num(args, 0).max(0.0) as usize;
398 let v = ((*bytes.get(i).unwrap_or(&0) as u16) << 8)
399 | *bytes.get(i + 1).unwrap_or(&0) as u16;
400 Ok(Value::Float(v as f64))
401 }
402 "readUInt16LE" => {
403 let i = super::arg_num(args, 0).max(0.0) as usize;
404 let v = (*bytes.get(i).unwrap_or(&0) as u16)
405 | ((*bytes.get(i + 1).unwrap_or(&0) as u16) << 8);
406 Ok(Value::Float(v as f64))
407 }
408 "writeUInt8" => {
410 let mut b = bytes.clone();
411 let off = super::arg_num(args, 1).max(0.0) as usize;
412 if off < b.len() {
413 b[off] = super::arg_num(args, 0) as u8;
414 }
415 set_bytes(recv, &b);
416 Ok(Value::Float((off + 1) as f64))
417 }
418 "writeUInt16BE" | "writeUInt16LE" => {
419 let mut b = bytes.clone();
420 let val = super::arg_num(args, 0) as u16;
421 let off = super::arg_num(args, 1).max(0.0) as usize;
422 let (hi, lo) = ((val >> 8) as u8, (val & 0xff) as u8);
423 let (b0, b1) = if method == "writeUInt16BE" {
424 (hi, lo)
425 } else {
426 (lo, hi)
427 };
428 if off + 1 < b.len() {
429 b[off] = b0;
430 b[off + 1] = b1;
431 }
432 set_bytes(recv, &b);
433 Ok(Value::Float((off + 2) as f64))
434 }
435 "write" => {
437 let mut b = bytes.clone();
438 let src = decode_str(&arg_str(args, 0), "utf8");
439 let off = if args.len() > 1 {
440 super::arg_num(args, 1).max(0.0) as usize
441 } else {
442 0
443 };
444 let mut n = 0;
445 for (k, &byte) in src.iter().enumerate() {
446 if off + k < b.len() {
447 b[off + k] = byte;
448 n += 1;
449 }
450 }
451 set_bytes(recv, &b);
452 Ok(Value::Float(n as f64))
453 }
454 "fill" => {
456 let mut b = bytes.clone();
457 let len = b.len();
458 let start = if args.len() > 1 {
459 super::arg_num(args, 1).max(0.0) as usize
460 } else {
461 0
462 };
463 let end = if args.len() > 2 {
464 (super::arg_num(args, 2) as usize).min(len)
465 } else {
466 len
467 };
468 let pat = fill_pattern(args, 0);
469 if !pat.is_empty() {
470 for (k, slot) in b[start..end.max(start)].iter_mut().enumerate() {
471 *slot = pat[k % pat.len()];
472 }
473 }
474 set_bytes(recv, &b);
475 Ok(recv.clone())
476 }
477 "copy" => {
479 let target = args.first().cloned().unwrap_or(Value::Undef);
480 let mut tb = bytes_of(&target);
481 let tstart = if args.len() > 1 {
482 super::arg_num(args, 1).max(0.0) as usize
483 } else {
484 0
485 };
486 let sstart = if args.len() > 2 {
487 super::arg_num(args, 2).max(0.0) as usize
488 } else {
489 0
490 };
491 let send = if args.len() > 3 {
492 (super::arg_num(args, 3) as usize).min(bytes.len())
493 } else {
494 bytes.len()
495 };
496 let mut n = 0;
497 for (k, &byte) in bytes[sstart..send.max(sstart)].iter().enumerate() {
498 if tstart + k < tb.len() {
499 tb[tstart + k] = byte;
500 n += 1;
501 }
502 }
503 set_bytes(&target, &tb);
504 Ok(Value::Float(n as f64))
505 }
506 _ => Err(crate::host::type_error(&format!(
507 "buffer.{method} is not a function"
508 ))),
509 }
510}
511
512fn fill_pattern(args: &[Value], idx: usize) -> Vec<u8> {
514 match args.get(idx) {
515 None => vec![0],
516 Some(v) => {
517 let is_str = matches!(v, Value::Str(_))
518 || with_host(|h| matches!(h.get(v), Some(JsObj::Str(_))));
519 if is_str {
520 decode_str(&arg_str(args, idx), "utf8")
521 } else {
522 vec![super::arg_num(args, idx) as u8]
523 }
524 }
525 }
526}
527
528fn set_bytes(recv: &Value, new: &[u8]) {
530 with_host(|h| {
531 let arr = match h.get(recv) {
532 Some(JsObj::Object(p)) => p.get("@@bytes").cloned(),
533 _ => None,
534 };
535 if let Some(a) = arr {
536 if let Some(JsObj::Array(items)) = h.get_mut(&a) {
537 *items = new.iter().map(|b| Value::Float(*b as f64)).collect();
538 }
539 }
540 });
541}
542
543fn slice_bounds(args: &[Value], len: usize) -> (usize, usize) {
544 let norm = |n: f64| -> usize {
545 if n < 0.0 {
546 (len as f64 + n).max(0.0) as usize
547 } else {
548 (n as usize).min(len)
549 }
550 };
551 let s = if args.is_empty() {
552 0
553 } else {
554 norm(super::arg_num(args, 0))
555 };
556 let e = if args.len() < 2 {
557 len
558 } else {
559 norm(super::arg_num(args, 1))
560 };
561 (s.min(e), e.max(s))
562}
563
564fn decode_str(s: &str, enc: &str) -> Vec<u8> {
565 match enc.to_ascii_lowercase().as_str() {
566 "hex" => from_hex(s),
567 "base64" | "base64url" => from_base64(s),
568 "ascii" | "latin1" | "binary" => s.chars().map(|c| c as u8).collect(),
569 _ => s.as_bytes().to_vec(),
570 }
571}
572
573fn encode_bytes(bytes: &[u8], enc: &str) -> String {
574 match enc.to_ascii_lowercase().as_str() {
575 "hex" => to_hex(bytes),
576 "base64" | "base64url" => to_base64(bytes),
577 "ascii" | "latin1" | "binary" => bytes.iter().map(|b| *b as char).collect(),
578 _ => String::from_utf8_lossy(bytes).into_owned(),
579 }
580}