MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3#include <stdint.h>
4#include <inttypes.h>
5#include <time.h>
6#include <ctype.h>
7#include <stdio.h>
8
9#include "ant.h"
10#include "errors.h"
11#include "runtime.h"
12#include "internal.h"
13#include "descriptors.h"
14
15#include "modules/blob.h"
16#include "modules/buffer.h"
17#include "modules/symbol.h"
18#include "streams/readable.h"
19
20ant_value_t g_blob_proto = 0;
21ant_value_t g_file_proto = 0;
22
23bool blob_is_blob(ant_t *js, ant_value_t obj) {
24 int id = js_brand_id(obj);
25 return id == BRAND_BLOB || id == BRAND_FILE;
26}
27
28blob_data_t *blob_get_data(ant_value_t obj) {
29 ant_value_t slot = js_get_slot(obj, SLOT_DATA);
30 if (vtype(slot) != T_NUM) return NULL;
31 return (blob_data_t *)(uintptr_t)(size_t)js_getnum(slot);
32}
33
34static blob_data_t *blob_data_new(const uint8_t *data, size_t size, const char *type) {
35 blob_data_t *bd = calloc(1, sizeof(blob_data_t));
36
37 if (!bd) return NULL;
38 if (size > 0 && data) {
39 bd->data = malloc(size);
40 if (!bd->data) { free(bd); return NULL; }
41 memcpy(bd->data, data, size);
42 }
43
44 bd->size = size;
45 bd->type = type ? strdup(type) : strdup("");
46
47 return bd;
48}
49
50static char *normalize_mime_type(const char *s) {
51 if (!s) return strdup("");
52 for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
53 if (*p < 0x20 || *p > 0x7E) return strdup("");
54 }
55
56 size_t len = strlen(s);
57 char *out = malloc(len + 1);
58
59 if (!out) return strdup("");
60 for (size_t i = 0; i <= len; i++) out[i] = (char)tolower((unsigned char)s[i]);
61
62 return out;
63}
64
65typedef struct {
66 uint8_t *buf;
67 size_t size;
68 size_t cap;
69} byte_buf_t;
70
71static bool byte_buf_grow(byte_buf_t *b, size_t extra) {
72 size_t needed = b->size + extra;
73 if (needed <= b->cap) return true;
74 size_t new_cap = b->cap ? b->cap * 2 : 64;
75 while (new_cap < needed) new_cap *= 2;
76 uint8_t *p = realloc(b->buf, new_cap);
77 if (!p) return false;
78 b->buf = p;
79 b->cap = new_cap;
80 return true;
81}
82
83static bool byte_buf_append(byte_buf_t *b, const uint8_t *data, size_t len) {
84 if (!byte_buf_grow(b, len)) return false;
85 memcpy(b->buf + b->size, data, len);
86 b->size += len;
87 return true;
88}
89
90static ant_value_t process_blob_part(ant_t *js, byte_buf_t *buf, ant_value_t part) {
91 uint8_t t = vtype(part);
92
93 if (t == T_TYPEDARRAY) {
94 TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(part);
95 if (!ta || !ta->buffer) return js_mkundef();
96 if (!byte_buf_append(buf, ta->buffer->data + ta->byte_offset, ta->byte_length))
97 return js_mkerr(js, "out of memory");
98 return js_mkundef();
99 }
100
101 if (t == T_OBJ) {
102 TypedArrayData *ta = buffer_get_typedarray_data(part);
103 if (ta && ta->buffer && !ta->buffer->is_detached) {
104 if (!byte_buf_append(buf, ta->buffer->data + ta->byte_offset, ta->byte_length)) return js_mkerr(js, "out of memory");
105 return js_mkundef();
106 }
107 ArrayBufferData *abd = buffer_get_arraybuffer_data(part);
108 if (abd && !abd->is_detached) {
109 if (!byte_buf_append(buf, abd->data, abd->length)) return js_mkerr(js, "out of memory");
110 return js_mkundef();
111 }
112 blob_data_t *bd = blob_get_data(part);
113 if (bd && bd->size > 0) {
114 if (!byte_buf_append(buf, bd->data, bd->size)) return js_mkerr(js, "out of memory");
115 return js_mkundef();
116 }
117 }
118
119 ant_value_t str = (t == T_STR) ? part : js_tostring_val(js, part);
120 if (is_err(str)) return str;
121
122 size_t len;
123 char *s = js_getstr(js, str, &len);
124 if (s && len > 0) {
125 if (!byte_buf_append(buf, (const uint8_t *)s, len))
126 return js_mkerr(js, "out of memory");
127 }
128 return js_mkundef();
129}
130
131static ant_value_t process_blob_parts(ant_t *js, byte_buf_t *buf, ant_value_t parts) {
132 uint8_t t = vtype(parts);
133 if (t == T_UNDEF || t == T_NULL) return js_mkundef();
134
135 if (t != T_OBJ && t != T_ARR && t != T_FUNC)
136 return js_mkerr_typed(js, JS_ERR_TYPE,
137 "Failed to construct 'Blob': The provided value cannot be converted to a sequence.");
138
139 js_iter_t it;
140 if (!js_iter_open(js, parts, &it))
141 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Blob': The provided value is not of type 'BlobPart'");
142
143 ant_value_t value;
144 while (js_iter_next(js, &it, &value)) {
145 ant_value_t r = process_blob_part(js, buf, value);
146 if (is_err(r)) { js_iter_close(js, &it); return r; }
147 }
148
149 return js_mkundef();
150}
151
152static void blob_finalize(ant_t *js, ant_object_t *obj) {
153 if (!obj->extra_slots) return;
154 ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
155 for (uint8_t i = 0; i < obj->extra_count; i++) {
156 if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
157 blob_data_t *bd = (blob_data_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
158 if (bd) { free(bd->data); free(bd->type); free(bd->name); free(bd); }
159 return;
160 }}
161}
162
163ant_value_t blob_create(ant_t *js, const uint8_t *data, size_t size, const char *type) {
164 blob_data_t *bd = blob_data_new(data, size, type);
165 if (!bd) return js_mkerr(js, "out of memory");
166 ant_value_t obj = js_mkobj(js);
167
168 js_set_proto_init(obj, g_blob_proto);
169 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_BLOB));
170 js_set_slot(obj, SLOT_DATA, ANT_PTR(bd));
171 js_set_finalizer(obj, blob_finalize);
172
173 return obj;
174}
175
176static ant_value_t blob_get_size(ant_t *js, ant_value_t *args, int nargs) {
177 (void)args; (void)nargs;
178 blob_data_t *bd = blob_get_data(js->this_val);
179 return js_mknum(bd ? (double)bd->size : 0);
180}
181
182static ant_value_t blob_get_type(ant_t *js, ant_value_t *args, int nargs) {
183 (void)args; (void)nargs;
184 blob_data_t *bd = blob_get_data(js->this_val);
185 if (!bd || !bd->type) return js_mkstr(js, "", 0);
186 return js_mkstr(js, bd->type, strlen(bd->type));
187}
188
189static ant_value_t file_get_name(ant_t *js, ant_value_t *args, int nargs) {
190 (void)args; (void)nargs;
191 blob_data_t *bd = blob_get_data(js->this_val);
192 if (!bd || !bd->name) return js_mkstr(js, "", 0);
193 return js_mkstr(js, bd->name, strlen(bd->name));
194}
195
196static ant_value_t file_get_last_modified(ant_t *js, ant_value_t *args, int nargs) {
197 (void)args; (void)nargs;
198 blob_data_t *bd = blob_get_data(js->this_val);
199 return js_mknum(bd ? (double)bd->last_modified : 0);
200}
201
202static ant_value_t js_blob_text(ant_t *js, ant_value_t *args, int nargs) {
203 (void)args; (void)nargs;
204 blob_data_t *bd = blob_get_data(js->this_val);
205 ant_value_t promise = js_mkpromise(js);
206 ant_value_t str = (!bd || bd->size == 0)
207 ? js_mkstr(js, "", 0)
208 : js_mkstr(js, (const char *)bd->data, bd->size);
209 js_resolve_promise(js, promise, str);
210 return promise;
211}
212
213static ant_value_t js_blob_array_buffer(ant_t *js, ant_value_t *args, int nargs) {
214 (void)args; (void)nargs;
215 blob_data_t *bd = blob_get_data(js->this_val);
216 ant_value_t promise = js_mkpromise(js);
217
218 size_t sz = (bd && bd->data) ? bd->size : 0;
219 ArrayBufferData *abd = create_array_buffer_data(sz);
220 if (!abd) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); return promise; }
221 if (sz > 0 && bd) memcpy(abd->data, bd->data, sz);
222
223 js_resolve_promise(js, promise, create_arraybuffer_obj(js, abd));
224 return promise;
225}
226
227static ant_value_t js_blob_bytes(ant_t *js, ant_value_t *args, int nargs) {
228 (void)args; (void)nargs;
229 blob_data_t *bd = blob_get_data(js->this_val);
230 ant_value_t promise = js_mkpromise(js);
231
232 size_t sz = (bd && bd->data) ? bd->size : 0;
233 ArrayBufferData *abd = create_array_buffer_data(sz);
234 if (!abd) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); return promise; }
235 if (sz > 0 && bd) memcpy(abd->data, bd->data, sz);
236
237 js_resolve_promise(js, promise,
238 create_typed_array(js, TYPED_ARRAY_UINT8, abd, 0, sz, "Uint8Array"));
239 return promise;
240}
241
242static ant_value_t js_blob_slice(ant_t *js, ant_value_t *args, int nargs) {
243 blob_data_t *bd = blob_get_data(js->this_val);
244 size_t blob_size = bd ? bd->size : 0;
245
246 ssize_t start = 0;
247 if (nargs >= 1 && vtype(args[0]) != T_UNDEF) {
248 double d = js_to_number(js, args[0]);
249 start = (ssize_t)d;
250 if (start < 0) start = (ssize_t)blob_size + start;
251 if (start < 0) start = 0;
252 if ((size_t)start > blob_size) start = (ssize_t)blob_size;
253 }
254
255 ssize_t end = (ssize_t)blob_size;
256 if (nargs >= 2 && vtype(args[1]) != T_UNDEF) {
257 double d = js_to_number(js, args[1]);
258 end = (ssize_t)d;
259 if (end < 0) end = (ssize_t)blob_size + end;
260 if (end < 0) end = 0;
261 if ((size_t)end > blob_size) end = (ssize_t)blob_size;
262 }
263
264 if (end < start) end = start;
265
266 size_t new_size = (size_t)(end - start);
267 const uint8_t *src = (bd && bd->data && new_size > 0) ? (bd->data + start) : NULL;
268
269 const char *new_type = (bd && bd->type) ? bd->type : "";
270 char *type_owned = NULL;
271 if (nargs >= 3 && vtype(args[2]) != T_UNDEF) {
272 ant_value_t tv = args[2];
273 if (vtype(tv) != T_STR) { tv = js_tostring_val(js, tv); if (is_err(tv)) return tv; }
274 type_owned = normalize_mime_type(js_getstr(js, tv, NULL));
275 new_type = type_owned;
276 }
277
278 ant_value_t result = blob_create(js, src, new_size, new_type);
279 free(type_owned);
280 return result;
281}
282
283static ant_value_t blob_stream_pull(ant_t *js, ant_value_t *args, int nargs) {
284 ant_value_t blob_obj = js_get_slot(js->current_func, SLOT_DATA);
285 blob_data_t *bd = blob_get_data(blob_obj);
286 ant_value_t ctrl = (nargs > 0) ? args[0] : js_mkundef();
287
288 if (bd && bd->size > 0 && bd->data) {
289 ArrayBufferData *ab = create_array_buffer_data(bd->size);
290 if (ab) {
291 memcpy(ab->data, bd->data, bd->size);
292 rs_controller_enqueue(js, ctrl, create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, bd->size, "Uint8Array"));
293 }}
294
295 rs_controller_close(js, ctrl);
296 return js_mkundef();
297}
298
299static ant_value_t js_blob_stream(ant_t *js, ant_value_t *args, int nargs) {
300 ant_value_t pull_fn = js_heavy_mkfun(js, blob_stream_pull, js->this_val);
301 return rs_create_stream(js, pull_fn, js_mkundef(), 1);
302}
303
304static ant_value_t js_blob_ctor(ant_t *js, ant_value_t *args, int nargs) {
305 if (vtype(js->new_target) == T_UNDEF)
306 return js_mkerr_typed(js, JS_ERR_TYPE, "Blob constructor requires 'new'");
307
308 byte_buf_t buf = {NULL, 0, 0};
309
310 if (nargs >= 1 && vtype(args[0]) != T_UNDEF) {
311 uint8_t pt = vtype(args[0]);
312 if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC)
313 return js_mkerr_typed(js, JS_ERR_TYPE,
314 "Failed to construct 'Blob': The provided value cannot be converted to a sequence.");
315 ant_value_t r = process_blob_parts(js, &buf, args[0]);
316 if (is_err(r)) { free(buf.buf); return r; }
317 }
318
319 const char *type_str = "";
320 char *type_owned = NULL;
321
322 if (nargs >= 2 && vtype(args[1]) != T_UNDEF && vtype(args[1]) != T_NULL) {
323 uint8_t ot = vtype(args[1]);
324 if (ot != T_OBJ && ot != T_ARR && ot != T_FUNC && ot != T_CFUNC) {
325 free(buf.buf);
326 return js_mkerr_typed(js, JS_ERR_TYPE,
327 "Failed to construct 'Blob': The 'options' argument is not an object.");
328 }
329 // access "endings" before "type" per lexicographic order (WPT requirement)
330 (void)js_get(js, args[1], "endings");
331 ant_value_t type_v = js_get(js, args[1], "type");
332 if (vtype(type_v) != T_UNDEF) {
333 if (vtype(type_v) != T_STR) {
334 type_v = js_tostring_val(js, type_v);
335 if (is_err(type_v)) { free(buf.buf); return type_v; }
336 }
337 type_owned = normalize_mime_type(js_getstr(js, type_v, NULL));
338 type_str = type_owned;
339 }
340 }
341
342 blob_data_t *bd = blob_data_new(buf.buf, buf.size, type_str);
343 free(buf.buf); free(type_owned);
344 if (!bd) return js_mkerr(js, "out of memory");
345
346 ant_value_t obj = js_mkobj(js);
347 ant_value_t proto = js_instance_proto_from_new_target(js, g_blob_proto);
348 if (is_object_type(proto)) js_set_proto_init(obj, proto);
349
350 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_BLOB));
351 js_set_slot(obj, SLOT_DATA, ANT_PTR(bd));
352 js_set_finalizer(obj, blob_finalize);
353
354 return obj;
355}
356
357static ant_value_t js_file_ctor(ant_t *js, ant_value_t *args, int nargs) {
358 if (vtype(js->new_target) == T_UNDEF)
359 return js_mkerr_typed(js, JS_ERR_TYPE, "File constructor requires 'new'");
360 if (nargs < 2)
361 return js_mkerr_typed(js, JS_ERR_TYPE, "File constructor requires at least 2 arguments");
362
363 byte_buf_t buf = {NULL, 0, 0};
364
365 if (vtype(args[0]) != T_UNDEF) {
366 uint8_t pt = vtype(args[0]);
367 if (pt != T_OBJ && pt != T_ARR && pt != T_FUNC) {
368 return js_mkerr_typed(js, JS_ERR_TYPE,
369 "Failed to construct 'File': The provided value cannot be converted to a sequence.");
370 }
371 ant_value_t r = process_blob_parts(js, &buf, args[0]);
372 if (is_err(r)) { free(buf.buf); return r; }
373 }
374
375 ant_value_t name_v = args[1];
376 if (vtype(name_v) != T_STR) {
377 name_v = js_tostring_val(js, name_v);
378 if (is_err(name_v)) { free(buf.buf); return name_v; }
379 }
380 const char *name_str = js_getstr(js, name_v, NULL);
381
382 const char *type_str = "";
383 char *type_owned = NULL;
384
385 struct timespec ts;
386 clock_gettime(CLOCK_REALTIME, &ts);
387 int64_t last_modified = (int64_t)ts.tv_sec * 1000LL + (int64_t)(ts.tv_nsec / 1000000);
388
389 if (nargs >= 3 && vtype(args[2]) != T_UNDEF && vtype(args[2]) != T_NULL) {
390 ant_value_t opts = args[2];
391 uint8_t ot = vtype(opts);
392 if (ot == T_OBJ || ot == T_ARR) {
393 ant_value_t type_v = js_get(js, opts, "type");
394 if (vtype(type_v) != T_UNDEF) {
395 if (vtype(type_v) != T_STR) {
396 type_v = js_tostring_val(js, type_v);
397 if (is_err(type_v)) { free(buf.buf); return type_v; }
398 }
399 type_owned = normalize_mime_type(js_getstr(js, type_v, NULL));
400 type_str = type_owned;
401 }
402 ant_value_t lm_v = js_get(js, opts, "lastModified");
403 if (vtype(lm_v) != T_UNDEF) {
404 double d = js_to_number(js, lm_v);
405 if (d == d) last_modified = (int64_t)d;
406 }
407 }
408 }
409
410 blob_data_t *bd = blob_data_new(buf.buf, buf.size, type_str);
411 free(buf.buf); free(type_owned);
412 if (!bd) return js_mkerr(js, "out of memory");
413
414 bd->name = strdup(name_str ? name_str : "");
415 bd->last_modified = last_modified;
416
417 ant_value_t obj = js_mkobj(js);
418 ant_value_t proto = js_instance_proto_from_new_target(js, g_file_proto);
419 if (is_object_type(proto)) js_set_proto_init(obj, proto);
420
421 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_FILE));
422 js_set_slot(obj, SLOT_DATA, ANT_PTR(bd));
423 js_set_finalizer(obj, blob_finalize);
424
425 return obj;
426}
427
428void init_blob_module(void) {
429 ant_t *js = rt->js;
430 ant_value_t g = js_glob(js);
431 g_blob_proto = js_mkobj(js);
432
433 js_set_getter_desc(js, g_blob_proto, "size", 4, js_mkfun(blob_get_size), JS_DESC_C);
434 js_set_getter_desc(js, g_blob_proto, "type", 4, js_mkfun(blob_get_type), JS_DESC_C);
435
436 js_set(js, g_blob_proto, "text", js_mkfun(js_blob_text));
437 js_set(js, g_blob_proto, "arrayBuffer", js_mkfun(js_blob_array_buffer));
438 js_set(js, g_blob_proto, "bytes", js_mkfun(js_blob_bytes));
439 js_set(js, g_blob_proto, "slice", js_mkfun(js_blob_slice));
440 js_set(js, g_blob_proto, "stream", js_mkfun(js_blob_stream));
441
442 js_set_sym(js, g_blob_proto, get_toStringTag_sym(), js_mkstr(js, "Blob", 4));
443 ant_value_t blob_ctor = js_make_ctor(js, js_blob_ctor, g_blob_proto, "Blob", 4);
444
445 js_set(js, g, "Blob", blob_ctor);
446 js_set_descriptor(js, g, "Blob", 4, JS_DESC_W | JS_DESC_C);
447
448 g_file_proto = js_mkobj(js);
449 js_set_proto_init(g_file_proto, g_blob_proto);
450
451 js_set_getter_desc(js, g_file_proto, "name", 4, js_mkfun(file_get_name), JS_DESC_C);
452 js_set_getter_desc(js, g_file_proto, "lastModified", 12, js_mkfun(file_get_last_modified), JS_DESC_C);
453
454 js_set_sym(js, g_file_proto, get_toStringTag_sym(), js_mkstr(js, "File", 4));
455 ant_value_t file_ctor = js_make_ctor(js, js_file_ctor, g_file_proto, "File", 4);
456
457 js_set(js, g, "File", file_ctor);
458 js_set_descriptor(js, g, "File", 4, JS_DESC_W | JS_DESC_C);
459}