1use rquickjs::{
4 atom::PredefinedAtom,
5 cstr,
6 function::{Constructor, Opt},
7 module::{Declarations, Exports, ModuleDef},
8 prelude::{Func, This},
9 Array, ArrayBuffer, Ctx, Exception, IntoJs, Object, Result, TypedArray, Value,
10};
11
12use crate::{
13 modules::{encoding::encoder::Encoder, module::export_default},
15 utils::{
16 object::{
17 get_array_buffer_bytes, get_array_bytes, get_bytes, get_coerced_string_bytes,
18 get_start_end_indexes, get_string_bytes, obj_to_array_buffer,
19 },
20 result::ResultExt,
21 },
22};
23
24pub struct Buffer(pub Vec<u8>);
25
26impl<'js> IntoJs<'js> for Buffer {
27 fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
28 let array_buffer = ArrayBuffer::new(ctx.clone(), self.0)?;
29 Self::from_array_buffer(ctx, array_buffer)
30 }
31}
32
33impl<'js> Buffer {
34 pub fn to_string(&self, ctx: &Ctx<'js>, encoding: &str) -> Result<String> {
35 Encoder::from_str(encoding)
36 .and_then(|enc| enc.encode_to_string(self.0.as_ref()))
37 .or_throw(ctx)
38 }
39
40 fn from_array_buffer(ctx: &Ctx<'js>, buffer: ArrayBuffer<'js>) -> Result<Value<'js>> {
41 let constructor: Constructor = ctx.globals().get(stringify!(Buffer))?;
42 constructor.construct((buffer,))
43 }
44
45 fn from_array_buffer_offset_length(
46 ctx: &Ctx<'js>,
47 array_buffer: ArrayBuffer<'js>,
48 offset: usize,
49 length: usize,
50 ) -> Result<Value<'js>> {
51 let constructor: Constructor = ctx.globals().get(stringify!(Buffer))?;
52 constructor.construct((array_buffer, offset, length))
53 }
54
55 fn from_encoding(
56 ctx: &Ctx<'js>,
57 mut bytes: Vec<u8>,
58 encoding: Option<String>,
59 ) -> Result<Value<'js>> {
60 if let Some(encoding) = encoding {
61 let encoder = Encoder::from_str(&encoding).or_throw(ctx)?;
62 bytes = encoder.decode(bytes).or_throw(ctx)?;
63 }
64 Buffer(bytes).into_js(ctx)
65 }
66}
67
68fn byte_length<'js>(ctx: Ctx<'js>, value: Value<'js>, encoding: Opt<String>) -> Result<usize> {
69 if let Some(encoding) = encoding.0 {
71 let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
72 let bytes = get_bytes(&ctx, value)?;
73 return Ok(encoder.decode(bytes).or_throw(&ctx)?.len());
74 }
75 if let Some(val) = value.as_string() {
77 return Ok(val.to_string()?.len());
78 }
79
80 if value.is_array() {
81 let array = value.as_array().unwrap();
82
83 for val in array.iter::<u8>() {
84 val.or_throw_msg(&ctx, "array value is not u8")?;
85 }
86
87 return Ok(array.len());
88 }
89
90 if let Some(obj) = value.as_object() {
91 if let Some((_, source_length, _)) = obj_to_array_buffer(obj)? {
92 return Ok(source_length);
93 }
94 }
95
96 Err(Exception::throw_message(
97 &ctx,
98 "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or string",
99 ))
100}
101
102fn to_string(this: This<Object<'_>>, ctx: Ctx, encoding: Opt<String>) -> Result<String> {
103 let typed_array = TypedArray::<u8>::from_object(this.0)?;
104 let bytes: &[u8] = typed_array.as_ref();
105 let encoding = encoding.0.unwrap_or_else(|| String::from("utf-8"));
106 let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
107 encoder.encode_to_string(bytes).or_throw(&ctx)
108}
109
110fn alloc<'js>(
111 ctx: Ctx<'js>,
112 length: usize,
113 fill: Opt<Value<'js>>,
114 encoding: Opt<String>,
115) -> Result<Value<'js>> {
116 if let Some(value) = fill.0 {
117 if let Some(value) = value.as_string() {
118 let string = value.to_string()?;
119
120 if let Some(encoding) = encoding.0 {
121 let encoder = Encoder::from_str(&encoding).or_throw(&ctx)?;
122 let bytes = encoder.decode_from_string(string).or_throw(&ctx)?;
123 return alloc_byte_ref(&ctx, &bytes, length);
124 }
125
126 let byte_ref = string.as_bytes();
127
128 return alloc_byte_ref(&ctx, byte_ref, length);
129 }
130 if let Some(value) = value.as_int() {
131 let bytes = vec![value as u8; length];
132 return Buffer(bytes).into_js(&ctx);
133 }
134 if let Some(obj) = value.as_object() {
135 if let Some((array_buffer, source_length, offset)) = obj_to_array_buffer(obj)? {
136 let bytes: &[u8] = array_buffer.as_ref();
137 return alloc_byte_ref(&ctx, &bytes[offset..offset + source_length], length);
138 }
139 }
140 }
141
142 Buffer(vec![0; length]).into_js(&ctx)
143}
144
145fn alloc_byte_ref<'js>(ctx: &Ctx<'js>, byte_ref: &[u8], length: usize) -> Result<Value<'js>> {
146 let mut bytes = vec![0; length];
147 let byte_ref_length = byte_ref.len();
148 for i in 0..length {
149 bytes[i] = byte_ref[i % byte_ref_length];
150 }
151 Buffer(bytes).into_js(ctx)
152}
153
154fn concat<'js>(ctx: Ctx<'js>, list: Array<'js>, max_length: Opt<usize>) -> Result<Value<'js>> {
155 let mut bytes = Vec::new();
156 let mut total_length = 0;
157 let mut length;
158 for value in list.iter::<Object>() {
159 let typed_array = TypedArray::<u8>::from_object(value?)?;
160 let bytes_ref: &[u8] = typed_array.as_ref();
161
162 length = bytes_ref.len();
163
164 if length == 0 {
165 continue;
166 }
167
168 if let Some(max_length) = max_length.0 {
169 total_length += length;
170 if total_length > max_length {
171 let diff = max_length - (total_length - length);
172 bytes.extend_from_slice(&bytes_ref[0..diff]);
173 break;
174 }
175 }
176 bytes.extend_from_slice(bytes_ref);
177 }
178
179 Buffer(bytes).into_js(&ctx)
180}
181
182fn from<'js>(
183 ctx: Ctx<'js>,
184 value: Value<'js>,
185 offset_or_encoding: Opt<Value<'js>>,
186 length: Opt<usize>,
187) -> Result<Value<'js>> {
188 let mut encoding: Option<String> = None;
189 let mut offset = 0;
190
191 if let Some(offset_or_encoding) = offset_or_encoding.0 {
192 if offset_or_encoding.is_string() {
193 encoding = Some(offset_or_encoding.get()?);
194 } else if offset_or_encoding.is_number() {
195 offset = offset_or_encoding.get()?;
196 }
197 }
198
199 println!("encoding {:?}", encoding);
200
201 if let Some(bytes) = get_string_bytes(&value, offset, length.0)? {
202 return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
203 }
204 if let Some(bytes) = get_array_bytes(&ctx, &value, offset, length.0)? {
205 return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
206 }
207
208 if let Some(obj) = value.as_object() {
209 if let Some((array_buffer, source_length, source_offset)) = obj_to_array_buffer(obj)? {
210 let (start, end) = get_start_end_indexes(source_length, length.0, offset);
211
212 if obj
214 .get::<_, Option<String>>(PredefinedAtom::Meta)?
215 .as_deref()
216 == Some(stringify!(Buffer))
217 || encoding.is_some()
218 {
219 let bytes = get_array_buffer_bytes(
220 array_buffer,
221 start + source_offset,
222 end - source_offset,
223 );
224 return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
225 } else {
226 return Buffer::from_array_buffer_offset_length(
227 &ctx,
228 array_buffer,
229 start + source_offset,
230 end - start,
231 );
232 }
233 }
234 }
235
236 if let Some(bytes) = get_coerced_string_bytes(&value, offset, length.0) {
237 return Buffer::from_encoding(&ctx, bytes, encoding)?.into_js(&ctx);
238 }
239
240 Err(Exception::throw_message(
241 &ctx,
242 "value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string",
243 ))
244}
245
246fn set_prototype<'js>(ctx: &Ctx<'js>, constructor: Object<'js>) -> Result<()> {
247 let _ = &constructor.set(PredefinedAtom::From, Func::from(from))?;
248 let _ = &constructor.set(stringify!(alloc), Func::from(alloc))?;
249 let _ = &constructor.set(stringify!(concat), Func::from(concat))?;
250 let _ = &constructor.set("byteLength", Func::from(byte_length))?;
251
252 let prototype: &Object = &constructor.get(PredefinedAtom::Prototype)?;
253 prototype.set(PredefinedAtom::ToString, Func::from(to_string))?;
254 prototype.prop(PredefinedAtom::Meta, stringify!(Buffer))?;
256
257 ctx.globals().set(stringify!(Buffer), constructor)?;
258
259 Ok(())
260}
261
262pub fn init<'js>(ctx: &Ctx<'js>) -> Result<()> {
263 let buffer = ctx.eval::<Object<'js>, &str>(&format!(
264 "class {0} extends Uint8Array {{}}\n{0}",
265 stringify!(Buffer)
266 ))?;
267 set_prototype(ctx, buffer)
268}
269
270pub struct BufferModule;
271
272impl ModuleDef for BufferModule {
273 fn declare(declare: &Declarations<'_>) -> Result<()> {
274 declare.declare(stringify!(Buffer))?;
275 declare.declare(cstr!("default").to_bytes())?;
276
277 Ok(())
278 }
279
280 fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
281 let globals = ctx.globals();
282 let buf: Constructor = globals.get(stringify!(Buffer))?;
283
284 export_default(ctx, exports, |default| {
285 default.set(stringify!(Buffer), buf)?;
286 Ok(())
287 })?;
288
289 Ok(())
290 }
291}
292
293