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