bun_runtime/
node_string_decoder.rs1use bun_core::ZBox;
3use mozjs::jsapi::*;
4use mozjs::jsval::UndefinedValue;
5use mozjs::rooted;
6use mozjs::rust::wrappers2 as w2;
7
8use crate::require::cache_builtin;
9
10const STRING_DECODER_JS: &str = r#"
19(function() {
20 function toBytes(buf) {
21 if (buf instanceof Uint8Array) return buf;
22 if (typeof buf === 'string') return Buffer.from(buf, 'utf8');
23 return new Uint8Array(0);
24 }
25
26 // Length of the longest prefix of `bytes` that ends on a complete UTF-8
27 // character boundary. A trailing partial sequence (lead byte + only some of
28 // its continuation bytes) is NOT included; malformed sequences are treated
29 // as complete (Buffer.toString renders U+FFFD, matching Node).
30 function completeUtf8Length(bytes) {
31 var n = bytes.length;
32 if (n === 0) return 0;
33 var i = n - 1;
34 var back = 0;
35 while (i > 0 && (bytes[i] & 0xC0) === 0x80 && back < 3) { i--; back++; }
36 var b = bytes[i];
37 var need = 1;
38 if (b >= 0xF0) need = 4;
39 else if (b >= 0xE0) need = 3;
40 else if (b >= 0xC0) need = 2;
41 if (n - i >= need) return n;
42 for (var j = i + 1; j < n; j++) {
43 if ((bytes[j] & 0xC0) !== 0x80) return n;
44 }
45 return i;
46 }
47
48 function StringDecoder(encoding) {
49 var enc = (encoding || 'utf8').toLowerCase();
50 if (enc === 'utf-8' || enc === 'utf_8') enc = 'utf8';
51 if (enc === 'ucs2' || enc === 'ucs-2') enc = 'utf16le';
52 this.encoding = enc;
53 this._partial = new Uint8Array(0);
54 }
55
56 StringDecoder.prototype.write = function(buf) {
57 if (this.encoding !== 'utf8') {
58 return Buffer.from(toBytes(buf)).toString(this.encoding);
59 }
60 var bytes = toBytes(buf);
61 var combined = new Uint8Array(this._partial.length + bytes.length);
62 combined.set(this._partial, 0);
63 combined.set(bytes, this._partial.length);
64 var complete = completeUtf8Length(combined);
65 this._partial = combined.slice(complete);
66 if (complete === 0) return '';
67 return Buffer.from(combined.buffer, combined.byteOffset, complete).toString('utf8');
68 };
69
70 StringDecoder.prototype.end = function(buf) {
71 var str = buf ? this.write(buf) : '';
72 if (this._partial.length > 0) {
73 // Leftover partial bytes decode as U+FFFD (Node semantics) and clear.
74 str += Buffer.from(this._partial).toString('utf8');
75 this._partial = new Uint8Array(0);
76 }
77 return str;
78 };
79
80 StringDecoder.prototype.text = function(buf, offset) {
81 if (!offset || offset < 0) offset = 0;
82 var bytes = toBytes(buf);
83 if (offset >= bytes.length) {
84 var keep = this._partial;
85 this._partial = new Uint8Array(0);
86 return keep.length ? Buffer.from(keep).toString('utf8') : '';
87 }
88 return this.write(bytes.subarray(offset));
89 };
90
91 StringDecoder.prototype.fill = function(buf) {
92 return this.write(buf);
93 };
94
95 return {
96 StringDecoder: StringDecoder,
97 };
98})();
99"#;
100
101pub fn install(cx: &mut mozjs::context::JSContext) {
102 rooted!(&in(cx) let mod_obj = unsafe { w2::JS_NewPlainObject(cx) });
103 if mod_obj.get().is_null() {
104 return;
105 }
106
107 unsafe {
108 let cx_raw = cx.raw_cx();
109
110 let c_filename = ZBox::from_bytes("node:string_decoder".as_bytes());
111 let opts = mozjs::glue::NewCompileOptions(cx_raw, c_filename.as_ptr(), 1);
112 if opts.is_null() {
113 return;
114 }
115
116 let mut src = mozjs::rust::transform_str_to_source_text(STRING_DECODER_JS);
117 let mut rval = UndefinedValue();
118 let rval_handle = MutableHandle::<Value> {
119 _phantom_0: ::std::marker::PhantomData,
120 ptr: &mut rval,
121 };
122 let ok = mozjs_sys::jsapi::JS::Evaluate2(cx_raw, opts, &mut src, rval_handle);
123 libc::free(opts as *mut _);
124
125 if !ok || !rval.is_object() {
126 return;
127 }
128
129 let exports_obj = rval.to_object();
130 rooted!(&in(cx) let exports_rooted = exports_obj);
131
132 {
133 let name = &"StringDecoder";
134 let cname = ZBox::from_bytes(name.as_bytes());
135 let mut val = UndefinedValue();
136 JS_GetProperty(
137 cx_raw,
138 exports_rooted.handle().into(),
139 cname.as_ptr(),
140 MutableHandle::<Value> {
141 _phantom_0: ::std::marker::PhantomData,
142 ptr: &mut val,
143 },
144 );
145 if !val.is_undefined() {
146 rooted!(&in(cx) let val_root = val);
147 JS_DefineProperty(
148 cx_raw,
149 mod_obj.handle().into(),
150 cname.as_ptr(),
151 val_root.handle().into(),
152 JSPROP_ENUMERATE as u32,
153 );
154 }
155 }
156
157 cache_builtin(cx, "string_decoder", mod_obj.get());
158 }
159}