MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3#include <strings.h>
4#include <stdint.h>
5#include <time.h>
6
7#include "ant.h"
8#include "errors.h"
9#include "runtime.h"
10#include "internal.h"
11#include "descriptors.h"
12
13#include "silver/engine.h"
14#include "modules/formdata.h"
15#include "modules/blob.h"
16#include "modules/symbol.h"
17
18typedef struct {
19 size_t index;
20 int kind;
21} fd_iter_t;
22
23enum {
24 FD_ITER_ENTRIES = 0,
25 FD_ITER_KEYS = 1,
26 FD_ITER_VALUES = 2
27};
28
29static ant_value_t g_formdata_proto = 0;
30static ant_value_t g_formdata_iter_proto = 0;
31static fd_data_t *get_fd_data(ant_value_t obj);
32
33bool formdata_is_formdata(ant_t *js, ant_value_t obj) {
34 return js_check_brand(obj, BRAND_FORMDATA);
35}
36
37bool formdata_is_empty(ant_value_t fd) {
38 fd_data_t *d = get_fd_data(fd);
39 return d ? d->count == 0 : true;
40}
41
42static fd_data_t *fd_data_new(void) {
43 fd_data_t *d = calloc(1, sizeof(fd_data_t));
44 if (!d) return NULL;
45 d->tail = &d->head;
46 return d;
47}
48
49static void fd_entry_free(fd_entry_t *e) {
50 if (!e) return;
51 free(e->name);
52 free(e->str_value);
53 free(e);
54}
55
56static void fd_data_free(fd_data_t *d) {
57 if (!d) return;
58 for (fd_entry_t *e = d->head; e; ) {
59 fd_entry_t *n = e->next;
60 fd_entry_free(e);
61 e = n;
62 }
63 free(d);
64}
65
66static fd_data_t *get_fd_data(ant_value_t obj) {
67 ant_value_t slot = js_get_slot(obj, SLOT_DATA);
68 if (vtype(slot) != T_NUM) return NULL;
69 return (fd_data_t *)(uintptr_t)(size_t)js_getnum(slot);
70}
71
72static ant_value_t get_fd_values(ant_value_t obj) {
73 return js_get_slot(obj, SLOT_ENTRIES);
74}
75
76static void formdata_finalize(ant_t *js, ant_object_t *obj) {
77 if (!obj->extra_slots) return;
78 ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
79
80 for (uint8_t i = 0; i < obj->extra_count; i++) {
81 if (entries[i].slot == SLOT_DATA && vtype(entries[i].value) == T_NUM) {
82 fd_data_t *d = (fd_data_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
83 fd_data_free(d);
84 return;
85 }}
86}
87
88static bool fd_append_str(fd_data_t *d, const char *name, const char *value) {
89 fd_entry_t *e = calloc(1, sizeof(fd_entry_t));
90 if (!e) return false;
91
92 e->name = strdup(name);
93 e->str_value = strdup(value);
94
95 if (!e->name || !e->str_value) { fd_entry_free(e); return false; }
96
97 *d->tail = e;
98 d->tail = &e->next;
99 d->count++;
100 return true;
101}
102
103static bool fd_append_file(fd_data_t *d, const char *name, size_t val_idx) {
104 fd_entry_t *e = calloc(1, sizeof(fd_entry_t));
105 if (!e) return false;
106
107 e->is_file = true;
108 e->name = strdup(name);
109 e->val_idx = val_idx;
110
111 if (!e->name) { free(e); return false; }
112
113 *d->tail = e;
114 d->tail = &e->next;
115 d->count++;
116 return true;
117}
118
119static void fd_delete_name(fd_data_t *d, const char *name) {
120 fd_entry_t **pp = &d->head;
121 d->tail = &d->head;
122
123 while (*pp) {
124 if (strcmp((*pp)->name, name) == 0) {
125 fd_entry_t *dead = *pp;
126 *pp = dead->next;
127 d->count--;
128 fd_entry_free(dead);
129 } else {
130 d->tail = &(*pp)->next;
131 pp = &(*pp)->next;
132 }}
133}
134
135ant_value_t formdata_create_empty(ant_t *js) {
136 fd_data_t *d = fd_data_new();
137 if (!d) return js_mkerr(js, "out of memory");
138
139 ant_value_t obj = js_mkobj(js);
140 js_set_proto_init(obj, g_formdata_proto);
141 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_FORMDATA));
142 js_set_slot(obj, SLOT_DATA, ANT_PTR(d));
143
144 ant_value_t vals = js_mkarr(js);
145 js_set_slot_wb(js, obj, SLOT_ENTRIES, vals);
146 js_set_finalizer(obj, formdata_finalize);
147
148 return obj;
149}
150
151static ant_value_t entry_to_js_value(ant_t *js, ant_value_t values_arr, fd_entry_t *e) {
152 if (!e->is_file)
153 return js_mkstr(js, e->str_value ? e->str_value : "", strlen(e->str_value ? e->str_value : ""));
154 return js_arr_get(js, values_arr, (ant_offset_t)e->val_idx);
155}
156
157static const char *resolve_name(ant_t *js, ant_value_t *name_v) {
158 if (vtype(*name_v) != T_STR) {
159 *name_v = js_tostring_val(js, *name_v);
160 if (is_err(*name_v)) return NULL;
161 }
162 return js_getstr(js, *name_v, NULL);
163}
164
165static ant_value_t extract_file_entry(
166 ant_t *js, fd_data_t *d, ant_value_t values_arr,
167 const char *name, ant_value_t val, ant_value_t filename_v, bool is_set
168) {
169 blob_data_t *bd = blob_get_data(val);
170 if (!bd) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData value must be a string, Blob, or File");
171
172 bool is_file = (bd->name != NULL);
173 bool no_filename_override = (vtype(filename_v) == T_UNDEF);
174
175 ant_value_t stored_val;
176
177 if (is_file && no_filename_override) {
178 stored_val = val;
179 } else {
180 const char *fname = NULL;
181 char *fname_owned = NULL;
182
183 if (!no_filename_override) {
184 ant_value_t fv = filename_v;
185 if (vtype(fv) != T_STR) { fv = js_tostring_val(js, fv); if (is_err(fv)) return fv; }
186 fname_owned = strdup(js_getstr(js, fv, NULL));
187 fname = fname_owned;
188 }
189
190 else if (bd->name && bd->name[0]) fname = bd->name;
191 else fname = "blob";
192
193 int64_t last_modified = bd->last_modified;
194 if (!last_modified) {
195 struct timespec ts;
196 clock_gettime(CLOCK_REALTIME, &ts);
197 last_modified = (int64_t)ts.tv_sec * 1000LL + (int64_t)(ts.tv_nsec / 1000000);
198 }
199
200 ant_value_t file_obj = blob_create(js, bd->data, bd->size, bd->type);
201 if (is_err(file_obj)) { free(fname_owned); return file_obj; }
202
203 blob_data_t *nbd = blob_get_data(file_obj);
204 if (nbd) {
205 nbd->name = strdup(fname);
206 nbd->last_modified = last_modified;
207 }
208
209 free(fname_owned);
210 js_set_proto_init(file_obj, g_file_proto);
211 stored_val = file_obj;
212 }
213
214 if (is_set) fd_delete_name(d, name);
215 size_t idx = (size_t)js_arr_len(js, values_arr);
216 js_arr_push(js, values_arr, stored_val);
217
218 return fd_append_file(d, name, idx) ? js_mkundef() : js_mkerr(js, "out of memory");
219}
220
221ant_value_t formdata_append_string(ant_t *js, ant_value_t fd, ant_value_t name_v, ant_value_t value_v) {
222 fd_data_t *d = get_fd_data(fd);
223 if (!d) return js_mkerr(js, "Invalid FormData object");
224
225 const char *name = resolve_name(js, &name_v);
226 if (!name) return name_v;
227
228 if (vtype(value_v) != T_STR) {
229 value_v = js_tostring_val(js, value_v);
230 if (is_err(value_v)) return value_v;
231 }
232
233 return fd_append_str(d, name, js_getstr(js, value_v, NULL))
234 ? js_mkundef()
235 : js_mkerr(js, "out of memory"
236 );
237}
238
239ant_value_t formdata_append_file(ant_t *js, ant_value_t fd, ant_value_t name_v, ant_value_t blob_v, ant_value_t filename_v) {
240 fd_data_t *d = get_fd_data(fd);
241 if (!d) return js_mkerr(js, "Invalid FormData object");
242 const char *name = resolve_name(js, &name_v);
243 if (!name) return name_v;
244 ant_value_t values_arr = get_fd_values(fd);
245 return extract_file_entry(js, d, values_arr, name, blob_v, filename_v, false);
246}
247
248static ant_value_t js_formdata_append(ant_t *js, ant_value_t *args, int nargs) {
249 if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData.append requires 2 arguments");
250 fd_data_t *d = get_fd_data(js->this_val);
251 if (!d) return js_mkerr(js, "Invalid FormData object");
252
253 ant_value_t name_v = args[0];
254 const char *name = resolve_name(js, &name_v);
255 if (!name) return name_v;
256
257 ant_value_t val = args[1];
258 if (is_object_type(val) && blob_get_data(val)) {
259 ant_value_t fname = (nargs >= 3) ? args[2] : js_mkundef();
260 ant_value_t values_arr = get_fd_values(js->this_val);
261 return extract_file_entry(js, d, values_arr, name, val, fname, false);
262 }
263
264 if (vtype(val) != T_STR) { val = js_tostring_val(js, val); if (is_err(val)) return val; }
265 return fd_append_str(d, name, js_getstr(js, val, NULL)) ? js_mkundef() : js_mkerr(js, "out of memory");
266}
267
268static ant_value_t js_formdata_set(ant_t *js, ant_value_t *args, int nargs) {
269 if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData.set requires 2 arguments");
270 fd_data_t *d = get_fd_data(js->this_val);
271 if (!d) return js_mkerr(js, "Invalid FormData object");
272
273 ant_value_t name_v = args[0];
274 const char *name = resolve_name(js, &name_v);
275 if (!name) return name_v;
276
277 ant_value_t val = args[1];
278 if (is_object_type(val) && blob_get_data(val)) {
279 ant_value_t fname = (nargs >= 3) ? args[2] : js_mkundef();
280 ant_value_t values_arr = get_fd_values(js->this_val);
281 return extract_file_entry(js, d, values_arr, name, val, fname, true);
282 }
283
284 if (vtype(val) != T_STR) { val = js_tostring_val(js, val); if (is_err(val)) return val; }
285 fd_delete_name(d, name);
286 return fd_append_str(d, name, js_getstr(js, val, NULL)) ? js_mkundef() : js_mkerr(js, "out of memory");
287}
288
289static ant_value_t js_formdata_get(ant_t *js, ant_value_t *args, int nargs) {
290 if (nargs < 1) return js_mknull();
291 fd_data_t *d = get_fd_data(js->this_val);
292 if (!d) return js_mknull();
293
294 ant_value_t name_v = args[0];
295 const char *name = resolve_name(js, &name_v);
296 if (!name) return name_v;
297
298 ant_value_t values_arr = get_fd_values(js->this_val);
299 for (fd_entry_t *e = d->head; e; e = e->next) {
300 if (strcmp(e->name, name) == 0)
301 return entry_to_js_value(js, values_arr, e);
302 }
303 return js_mknull();
304}
305
306static ant_value_t js_formdata_get_all(ant_t *js, ant_value_t *args, int nargs) {
307 ant_value_t result = js_mkarr(js);
308 if (nargs < 1) return result;
309 fd_data_t *d = get_fd_data(js->this_val);
310 if (!d) return result;
311
312 ant_value_t name_v = args[0];
313 const char *name = resolve_name(js, &name_v);
314 if (!name) return name_v;
315
316 ant_value_t values_arr = get_fd_values(js->this_val);
317 for (fd_entry_t *e = d->head; e; e = e->next) {
318 if (strcmp(e->name, name) == 0) {
319 ant_value_t v = entry_to_js_value(js, values_arr, e);
320 if (is_err(v)) return v;
321 js_arr_push(js, result, v);
322 }}
323 return result;
324}
325
326static ant_value_t js_formdata_has(ant_t *js, ant_value_t *args, int nargs) {
327 if (nargs < 1) return js_false;
328 fd_data_t *d = get_fd_data(js->this_val);
329 if (!d) return js_false;
330
331 ant_value_t name_v = args[0];
332 const char *name = resolve_name(js, &name_v);
333 if (!name) return name_v;
334
335 for (fd_entry_t *e = d->head; e; e = e->next) {
336 if (strcmp(e->name, name) == 0) return js_true;
337 }
338 return js_false;
339}
340
341static ant_value_t js_formdata_delete(ant_t *js, ant_value_t *args, int nargs) {
342 if (nargs < 1) return js_mkundef();
343 fd_data_t *d = get_fd_data(js->this_val);
344 if (!d) return js_mkundef();
345
346 ant_value_t name_v = args[0];
347 const char *name = resolve_name(js, &name_v);
348 if (!name) return name_v;
349
350 fd_delete_name(d, name);
351 return js_mkundef();
352}
353
354static ant_value_t js_formdata_foreach(ant_t *js, ant_value_t *args, int nargs) {
355 if (nargs < 1 || !is_callable(args[0]))
356 return js_mkerr_typed(js, JS_ERR_TYPE, "FormData.forEach requires a function");
357 fd_data_t *d = get_fd_data(js->this_val);
358 if (!d) return js_mkundef();
359
360 ant_value_t fn = args[0];
361 ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
362 ant_value_t self = js->this_val;
363 ant_value_t values_arr = get_fd_values(self);
364
365 for (fd_entry_t *e = d->head; e; e = e->next) {
366 ant_value_t val = entry_to_js_value(js, values_arr, e);
367 if (is_err(val)) return val;
368 ant_value_t name = js_mkstr(js, e->name, strlen(e->name));
369 ant_value_t cb_args[3] = { val, name, self };
370 ant_value_t r = sv_vm_call(js->vm, js, fn, this_arg, cb_args, 3, NULL, false);
371 if (is_err(r)) return r;
372 }
373 return js_mkundef();
374}
375
376static ant_value_t formdata_iter_next(ant_t *js, ant_value_t *args, int nargs) {
377 ant_value_t state_v = js_get_slot(js->this_val, SLOT_ITER_STATE);
378 if (vtype(state_v) != T_NUM) return js_iter_result(js, false, js_mkundef());
379
380 fd_iter_t *st = (fd_iter_t *)(uintptr_t)(size_t)js_getnum(state_v);
381 ant_value_t fd_obj = js_get_slot(js->this_val, SLOT_DATA);
382 fd_data_t *d = get_fd_data(fd_obj);
383 if (!d) return js_iter_result(js, false, js_mkundef());
384
385 ant_value_t values_arr = get_fd_values(fd_obj);
386
387 size_t idx = 0;
388 for (fd_entry_t *e = d->head; e; e = e->next, idx++) {
389 if (idx == st->index) {
390 st->index++;
391 ant_value_t out;
392 switch (st->kind) {
393 case FD_ITER_KEYS:
394 out = js_mkstr(js, e->name, strlen(e->name));
395 break;
396 case FD_ITER_VALUES:
397 out = entry_to_js_value(js, values_arr, e);
398 break;
399 default: {
400 ant_value_t v = entry_to_js_value(js, values_arr, e);
401 if (is_err(v)) return v;
402 out = js_mkarr(js);
403 js_arr_push(js, out, js_mkstr(js, e->name, strlen(e->name)));
404 js_arr_push(js, out, v);
405 break;
406 }}
407 if (is_err(out)) return out;
408 return js_iter_result(js, true, out);
409 }}
410
411 return js_iter_result(js, false, js_mkundef());
412}
413
414static void formdata_iter_finalize(ant_t *js, ant_object_t *obj) {
415 if (!obj->extra_slots) return;
416 ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots;
417
418 for (uint8_t i = 0; i < obj->extra_count; i++) {
419 if (entries[i].slot == SLOT_ITER_STATE && vtype(entries[i].value) == T_NUM) {
420 fd_iter_t *st = (fd_iter_t *)(uintptr_t)(size_t)js_getnum(entries[i].value);
421 free(st);
422 return;
423 }}
424}
425
426static ant_value_t make_formdata_iter(ant_t *js, ant_value_t fd_obj, int kind) {
427 fd_iter_t *st = calloc(1, sizeof(fd_iter_t));
428 if (!st) return js_mkerr(js, "out of memory");
429 st->kind = kind;
430
431 ant_value_t iter = js_mkobj(js);
432 js_set_proto_init(iter, g_formdata_iter_proto);
433 js_set_slot(iter, SLOT_ITER_STATE, ANT_PTR(st));
434 js_set_slot_wb(js, iter, SLOT_DATA, fd_obj);
435 js_set_finalizer(iter, formdata_iter_finalize);
436 return iter;
437}
438
439static ant_value_t js_formdata_entries(ant_t *js, ant_value_t *args, int nargs) {
440 return make_formdata_iter(js, js->this_val, FD_ITER_ENTRIES);
441}
442
443static ant_value_t js_formdata_keys(ant_t *js, ant_value_t *args, int nargs) {
444 return make_formdata_iter(js, js->this_val, FD_ITER_KEYS);
445}
446
447static ant_value_t js_formdata_values(ant_t *js, ant_value_t *args, int nargs) {
448 return make_formdata_iter(js, js->this_val, FD_ITER_VALUES);
449}
450
451static ant_value_t js_formdata_ctor(ant_t *js, ant_value_t *args, int nargs) {
452 if (vtype(js->new_target) == T_UNDEF)
453 return js_mkerr_typed(js, JS_ERR_TYPE, "FormData constructor requires 'new'");
454 if (nargs >= 1 && vtype(args[0]) != T_UNDEF)
455 return js_mkerr_typed(js, JS_ERR_TYPE, "FormData does not support a form element argument");
456
457 fd_data_t *d = fd_data_new();
458 if (!d) return js_mkerr(js, "out of memory");
459
460 ant_value_t obj = js_mkobj(js);
461 ant_value_t proto = js_instance_proto_from_new_target(js, g_formdata_proto);
462
463 if (is_object_type(proto)) js_set_proto_init(obj, proto);
464 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_FORMDATA));
465 js_set_slot(obj, SLOT_DATA, ANT_PTR(d));
466
467 ant_value_t vals = js_mkarr(js);
468 js_set_slot_wb(js, obj, SLOT_ENTRIES, vals);
469 js_set_finalizer(obj, formdata_finalize);
470
471 return obj;
472}
473
474void init_formdata_module(void) {
475 ant_t *js = rt->js;
476 ant_value_t g = js_glob(js);
477 g_formdata_proto = js_mkobj(js);
478
479 js_set(js, g_formdata_proto, "append", js_mkfun(js_formdata_append));
480 js_set(js, g_formdata_proto, "set", js_mkfun(js_formdata_set));
481 js_set(js, g_formdata_proto, "get", js_mkfun(js_formdata_get));
482 js_set(js, g_formdata_proto, "getAll", js_mkfun(js_formdata_get_all));
483 js_set(js, g_formdata_proto, "has", js_mkfun(js_formdata_has));
484 js_set(js, g_formdata_proto, "delete", js_mkfun(js_formdata_delete));
485 js_set(js, g_formdata_proto, "forEach", js_mkfun(js_formdata_foreach));
486 js_set(js, g_formdata_proto, "entries", js_mkfun(js_formdata_entries));
487 js_set(js, g_formdata_proto, "keys", js_mkfun(js_formdata_keys));
488 js_set(js, g_formdata_proto, "values", js_mkfun(js_formdata_values));
489
490 js_set_sym(js, g_formdata_proto, get_iterator_sym(), js_get(js, g_formdata_proto, "entries"));
491 js_set_sym(js, g_formdata_proto, get_toStringTag_sym(), js_mkstr(js, "FormData", 8));
492
493 ant_value_t ctor_obj = js_mkobj(js);
494 js_set_slot(ctor_obj, SLOT_CFUNC, js_mkfun(js_formdata_ctor));
495 js_mkprop_fast(js, ctor_obj, "prototype", 9, g_formdata_proto);
496 js_mkprop_fast(js, ctor_obj, "name", 4, js_mkstr(js, "FormData", 8));
497 js_set_descriptor(js, ctor_obj, "name", 4, 0);
498
499 ant_value_t ctor = js_obj_to_func(ctor_obj);
500 js_set(js, g_formdata_proto, "constructor", ctor);
501 js_set_descriptor(js, g_formdata_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
502
503 js_set(js, g, "FormData", ctor);
504 js_set_descriptor(js, g, "FormData", 8, JS_DESC_W | JS_DESC_C);
505
506 g_formdata_iter_proto = js_mkobj(js);
507 js_set_proto_init(g_formdata_iter_proto, js->sym.iterator_proto);
508 js_set(js, g_formdata_iter_proto, "next", js_mkfun(formdata_iter_next));
509 js_set_descriptor(js, g_formdata_iter_proto, "next", 4, JS_DESC_W | JS_DESC_E | JS_DESC_C);
510 js_set_sym(js, g_formdata_iter_proto, get_iterator_sym(), js_mkfun(sym_this_cb));
511}