MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3#include <ctype.h>
4#include <stdio.h>
5
6#include "ant.h"
7#include "errors.h"
8#include "runtime.h"
9#include "internal.h"
10#include "descriptors.h"
11#include "silver/engine.h"
12
13#include "modules/headers.h"
14#include "modules/symbol.h"
15
16typedef struct hdr_entry {
17 char *name;
18 char *value;
19 struct hdr_entry *next;
20} hdr_entry_t;
21
22typedef struct {
23 hdr_entry_t *head;
24 hdr_entry_t **tail;
25 size_t count;
26} hdr_list_t;
27
28typedef struct {
29 char *name;
30 char *value;
31} sorted_pair_t;
32
33typedef struct {
34 hdr_list_t *list;
35 size_t index;
36 int kind;
37} hdr_iter_t;
38
39enum {
40 ITER_ENTRIES = 0,
41 ITER_KEYS = 1,
42 ITER_VALUES = 2
43};
44
45ant_value_t g_headers_proto = 0;
46ant_value_t g_headers_iter_proto = 0;
47
48static hdr_list_t *list_new(void) {
49 hdr_list_t *l = ant_calloc(sizeof(hdr_list_t));
50 if (!l) return NULL;
51 l->head = NULL;
52 l->tail = &l->head;
53 return l;
54}
55
56static void list_free(hdr_list_t *l) {
57 if (!l) return;
58 for (hdr_entry_t *e = l->head; e; ) {
59 hdr_entry_t *n = e->next;
60 free(e->name); free(e->value); free(e);
61 e = n;
62 }
63 free(l);
64}
65
66static hdr_list_t *get_list(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 (hdr_list_t *)(uintptr_t)(size_t)js_getnum(slot);
70}
71
72static headers_guard_t get_guard(ant_value_t obj) {
73 ant_value_t slot = js_get_slot(obj, SLOT_HEADERS_GUARD);
74 if (vtype(slot) != T_NUM) return HEADERS_GUARD_NONE;
75 return (headers_guard_t)(int)js_getnum(slot);
76}
77
78bool headers_is_headers(ant_value_t obj) {
79 return js_check_brand(obj, BRAND_HEADERS);
80}
81
82static bool is_token_char(unsigned char c) {
83 if (c > 127) return false;
84 static const char ok[] =
85 "!#$%&'*+-.^_`|~"
86 "0123456789"
87 "abcdefghijklmnopqrstuvwxyz"
88 "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
89 return strchr(ok, (char)c) != NULL;
90}
91
92static bool is_valid_name(const char *s) {
93 if (!s || !*s) return false;
94 for (const unsigned char *p = (const unsigned char *)s; *p; p++)
95 if (!is_token_char(*p)) return false;
96 return true;
97}
98
99static bool is_valid_value(const char *s) {
100 if (!s) return false;
101 for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
102 unsigned char c = *p;
103 if (c == 0 || c == '\r' || c == '\n' || c > 127) return false;
104 }
105 return true;
106}
107
108static char *normalize_value(const char *s) {
109 if (!s) return strdup("");
110 while (*s == ' ' || *s == '\t') s++;
111
112 size_t len = strlen(s);
113 while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t')) len--;
114
115 char *out = malloc(len + 1);
116 if (!out) return NULL;
117
118 memcpy(out, s, len);
119 out[len] = '\0';
120
121 return out;
122}
123
124static char *lowercase_dup(const char *s) {
125 if (!s) return strdup("");
126 size_t len = strlen(s);
127 char *out = malloc(len + 1);
128 if (!out) return NULL;
129 for (size_t i = 0; i <= len; i++)
130 out[i] = (char)tolower((unsigned char)s[i]);
131 return out;
132}
133
134typedef struct {
135 const char *name;
136 bool prefix;
137} header_rule_t;
138
139static const header_rule_t k_forbidden_request_headers[] = {
140 { "accept-charset", false },
141 { "accept-encoding", false },
142 { "access-control-request-headers", false },
143 { "access-control-request-method", false },
144 { "connection", false },
145 { "content-length", false },
146 { "cookie", false },
147 { "cookie2", false },
148 { "date", false },
149 { "dnt", false },
150 { "expect", false },
151 { "host", false },
152 { "keep-alive", false },
153 { "origin", false },
154 { "referer", false },
155 { "set-cookie", false },
156 { "te", false },
157 { "trailer", false },
158 { "transfer-encoding", false },
159 { "upgrade", false },
160 { "via", false },
161 { "proxy-", true },
162 { "sec-", true },
163};
164
165static const header_rule_t k_forbidden_response_headers[] = {
166 { "set-cookie", false },
167 { "set-cookie2", false },
168};
169
170static const char *k_cors_safelisted_content_types[] = {
171 "application/x-www-form-urlencoded",
172 "multipart/form-data",
173 "text/plain",
174};
175
176static const char *k_no_cors_safelisted_names[] = {
177 "accept",
178 "accept-language",
179 "content-language",
180};
181
182static bool matches_rule(const char *name, const header_rule_t *rules, size_t count) {
183 for (size_t i = 0; i < count; i++) {
184 size_t len = strlen(rules[i].name);
185 if (rules[i].prefix) { if (strncmp(name, rules[i].name, len) == 0) return true; }
186 else if (strcmp(name, rules[i].name) == 0) return true;
187 }
188 return false;
189}
190
191static bool matches_string(const char *value, const char *const *list, size_t count) {
192 for (size_t i = 0; i < count; i++) {
193 if (strcmp(value, list[i]) == 0) return true;
194 }
195 return false;
196}
197
198static bool is_forbidden_request_header_name(const char *lower_name) {
199 return matches_rule(lower_name, k_forbidden_request_headers,
200 sizeof(k_forbidden_request_headers) / sizeof(k_forbidden_request_headers[0]));
201}
202
203static bool is_forbidden_response_header_name(const char *lower_name) {
204 return matches_rule(lower_name, k_forbidden_response_headers,
205 sizeof(k_forbidden_response_headers) / sizeof(k_forbidden_response_headers[0]));
206}
207
208static bool is_cors_safelisted_content_type_value(const char *value) {
209 char *lower = lowercase_dup(value ? value : "");
210 if (!lower) return false;
211 char *semi = strchr(lower, ';');
212
213 if (!semi) {
214 bool ok = matches_string(
215 lower,
216 k_cors_safelisted_content_types,
217 sizeof(k_cors_safelisted_content_types) / sizeof(k_cors_safelisted_content_types[0])
218 );
219 free(lower);
220 return ok;
221 }
222
223 *semi++ = '\0';
224 while (*semi == ' ' || *semi == '\t') semi++;
225 bool essence_ok = matches_string(
226 lower,
227 k_cors_safelisted_content_types,
228 sizeof(k_cors_safelisted_content_types) / sizeof(k_cors_safelisted_content_types[0])
229 );
230
231 bool param_ok = strcmp(semi, "charset=utf-8") == 0;
232 free(lower);
233
234 return essence_ok && param_ok;
235}
236
237static bool is_no_cors_safelisted_name_value(const char *lower_name, const char *value) {
238 if (
239 matches_string(
240 lower_name, k_no_cors_safelisted_names,
241 sizeof(k_no_cors_safelisted_names) / sizeof(k_no_cors_safelisted_names[0]))
242 ) return true;
243
244 if (strcmp(lower_name, "content-type") == 0)
245 return value && value[0] && is_cors_safelisted_content_type_value(value);
246
247 return false;
248}
249
250static bool header_allowed_for_guard(const char *lower_name, const char *value, headers_guard_t guard) {
251 if (guard == HEADERS_GUARD_NONE) return true;
252 if (guard == HEADERS_GUARD_IMMUTABLE) return true;
253 if (is_forbidden_request_header_name(lower_name)) return false;
254 if (guard == HEADERS_GUARD_RESPONSE) return !is_forbidden_response_header_name(lower_name);
255 if (guard == HEADERS_GUARD_REQUEST_NO_CORS) return is_no_cors_safelisted_name_value(lower_name, value);
256 return true;
257}
258
259static ant_value_t headers_guard_error(ant_t *js, headers_guard_t guard) {
260 if (guard != HEADERS_GUARD_IMMUTABLE) return js_mkundef();
261 return js_mkerr_typed(js, JS_ERR_TYPE, "Headers are immutable");
262}
263
264static void list_apply_guard(hdr_list_t *l, headers_guard_t guard) {
265 if (!l || guard == HEADERS_GUARD_NONE || guard == HEADERS_GUARD_IMMUTABLE) return;
266
267 hdr_entry_t **pp = &l->head;
268 l->tail = &l->head;
269
270 while (*pp) {
271 hdr_entry_t *cur = *pp;
272 if (!header_allowed_for_guard(cur->name, cur->value, guard)) {
273 *pp = cur->next;
274 free(cur->name);
275 free(cur->value);
276 free(cur);
277 l->count--;
278 continue;
279 }
280
281 l->tail = &cur->next;
282 pp = &cur->next;
283 }
284}
285
286static void list_append_raw(hdr_list_t *l, const char *lower_name, const char *value) {
287 hdr_entry_t *e = ant_calloc(sizeof(hdr_entry_t));
288 if (!e) return;
289 e->name = strdup(lower_name);
290 e->value = strdup(value);
291 *l->tail = e;
292 l->tail = &e->next;
293 l->count++;
294}
295
296static void list_delete_name(hdr_list_t *l, const char *lower_name) {
297 hdr_entry_t **pp = &l->head;
298 l->tail = &l->head;
299 while (*pp) {
300 if (strcmp((*pp)->name, lower_name) == 0) {
301 hdr_entry_t *dead = *pp;
302 *pp = dead->next;
303 free(dead->name); free(dead->value); free(dead);
304 l->count--;
305 } else {
306 l->tail = &(*pp)->next;
307 pp = &(*pp)->next;
308 }}
309}
310
311static int cmp_pairs(const void *a, const void *b) {
312 return strcmp(((const sorted_pair_t *)a)->name, ((const sorted_pair_t *)b)->name);
313}
314
315static sorted_pair_t *build_sorted_view(hdr_list_t *l, size_t *out) {
316 *out = 0;
317 if (!l || l->count == 0) return NULL;
318
319 sorted_pair_t *raw = malloc(l->count * sizeof(sorted_pair_t));
320 if (!raw) return NULL;
321
322 size_t n = 0;
323 for (hdr_entry_t *e = l->head; e; e = e->next) {
324 raw[n].name = e->name;
325 raw[n].value = e->value;
326 n++;
327 }
328
329 qsort(raw, n, sizeof(sorted_pair_t), cmp_pairs);
330 sorted_pair_t *res = malloc(n * sizeof(sorted_pair_t));
331 if (!res) { free(raw); return NULL; }
332
333 size_t ri = 0;
334 for (size_t i = 0; i < n; ) {
335 if (strcmp(raw[i].name, "set-cookie") == 0) {
336 res[ri].name = strdup(raw[i].name);
337 res[ri].value = strdup(raw[i].value);
338 ri++; i++;
339 } else {
340 size_t j = i + 1;
341 size_t total = strlen(raw[i].value);
342 while (j < n && strcmp(raw[j].name, raw[i].name) == 0) {
343 total += 2 + strlen(raw[j].value);
344 j++;
345 }
346 char *combined = malloc(total + 1);
347 if (!combined) combined = strdup("");
348 size_t pos = 0;
349 for (size_t k = i; k < j; k++) {
350 if (k > i) { combined[pos++] = ','; combined[pos++] = ' '; }
351 size_t vl = strlen(raw[k].value);
352 memcpy(combined + pos, raw[k].value, vl);
353 pos += vl;
354 }
355 combined[pos] = '\0';
356 res[ri].name = strdup(raw[i].name);
357 res[ri].value = combined;
358 ri++; i = j;
359 }}
360
361 free(raw);
362 *out = ri;
363
364 return res;
365}
366
367static void free_sorted_view(sorted_pair_t *v, size_t n) {
368 if (!v) return;
369 for (size_t i = 0; i < n; i++) { free(v[i].name); free(v[i].value); }
370 free(v);
371}
372
373static ant_value_t headers_append_name_value(ant_t *js, hdr_list_t *l, const char *name, const char *value) {
374 if (!is_valid_name(name))
375 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name: %s", name ? name : "");
376
377 char *norm = normalize_value(value);
378 if (!norm) return js_mkerr(js, "out of memory");
379 if (!is_valid_value(norm)) {
380 free(norm);
381 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header value");
382 }
383
384 char *lower = lowercase_dup(name);
385 if (!lower) { free(norm); return js_mkerr(js, "out of memory"); }
386
387 list_append_raw(l, lower, norm);
388 free(lower); free(norm);
389 return js_mkundef();
390}
391
392static ant_value_t headers_append_pair(ant_t *js, hdr_list_t *l, ant_value_t name_v, ant_value_t value_v) {
393 const char *name = NULL;
394 const char *value = NULL;
395
396 if (vtype(name_v) != T_STR) {
397 name_v = js_tostring_val(js, name_v);
398 if (is_err(name_v)) return name_v;
399 }
400
401 if (vtype(value_v) != T_STR) {
402 value_v = js_tostring_val(js, value_v);
403 if (is_err(value_v)) return value_v;
404 }
405
406 name = js_getstr(js, name_v, NULL);
407 value = js_getstr(js, value_v, NULL);
408 return headers_append_name_value(js, l, name, value);
409}
410
411ant_value_t headers_append_value(ant_t *js, ant_value_t hdrs, ant_value_t name_v, ant_value_t value_v) {
412 hdr_list_t *l = get_list(hdrs);
413 ant_value_t r = 0;
414
415 if (!l) return js_mkerr(js, "Invalid Headers object");
416 r = headers_append_pair(js, l, name_v, value_v);
417
418 if (is_err(r)) return r;
419 list_apply_guard(l, get_guard(hdrs));
420
421 return js_mkundef();
422}
423
424ant_value_t headers_append_literal(ant_t *js, ant_value_t hdrs, const char *name, const char *value) {
425 hdr_list_t *l = get_list(hdrs);
426 ant_value_t r = 0;
427
428 if (!l) return js_mkerr(js, "Invalid Headers object");
429 r = headers_append_name_value(js, l, name, value);
430
431 if (is_err(r)) return r;
432 list_apply_guard(l, get_guard(hdrs));
433
434 return js_mkundef();
435}
436
437static ant_value_t init_from_sequence(ant_t *js, hdr_list_t *l, ant_value_t seq) {
438 js_iter_t it;
439 if (!js_iter_open(js, seq, &it)) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers init is not iterable");
440
441 ant_value_t pair;
442 while (js_iter_next(js, &it, &pair)) {
443 uint8_t pt = vtype(pair);
444 if (pt != T_ARR && pt != T_OBJ) {
445 js_iter_close(js, &it);
446 return js_mkerr_typed(js, JS_ERR_TYPE, "Each header init pair must be a sequence");
447 }
448
449 if (js_arr_len(js, pair) != 2) {
450 js_iter_close(js, &it);
451 return js_mkerr_typed(js, JS_ERR_TYPE, "Each header init pair must have exactly 2 elements");
452 }
453
454 ant_value_t r = headers_append_pair(js, l, js_arr_get(js, pair, 0), js_arr_get(js, pair, 1));
455 if (is_err(r)) { js_iter_close(js, &it); return r; }
456 }
457
458 return js_mkundef();
459}
460
461static ant_value_t init_from_record(ant_t *js, hdr_list_t *l, ant_value_t obj) {
462 ant_iter_t it = js_prop_iter_begin(js, obj);
463 const char *key;
464 size_t key_len;
465 ant_value_t val;
466
467 while (js_prop_iter_next(&it, &key, &key_len, &val)) {
468 ant_value_t r = headers_append_pair(js, l, js_mkstr(js, key, key_len), val);
469 if (is_err(r)) { js_prop_iter_end(&it); return r; }
470 }
471
472 js_prop_iter_end(&it);
473 return js_mkundef();
474}
475
476bool advance_headers(ant_t *js, js_iter_t *it, ant_value_t *out) {
477 ant_value_t state_val = js_get_slot(it->iterator, SLOT_ITER_STATE);
478 if (vtype(state_val) == T_UNDEF) return false;
479 hdr_iter_t *st = (hdr_iter_t *)(uintptr_t)(size_t)js_getnum(state_val);
480
481 size_t count = 0;
482 sorted_pair_t *view = build_sorted_view(st->list, &count);
483
484 if (st->index >= count) {
485 free_sorted_view(view, count);
486 return false;
487 }
488
489 sorted_pair_t *e = &view[st->index];
490 switch (st->kind) {
491 case ITER_KEYS:
492 *out = js_mkstr(js, e->name, strlen(e->name));
493 break;
494 case ITER_VALUES:
495 *out = js_mkstr(js, e->value, strlen(e->value));
496 break;
497 default: {
498 *out = js_mkarr(js);
499 js_arr_push(js, *out, js_mkstr(js, e->name, strlen(e->name)));
500 js_arr_push(js, *out, js_mkstr(js, e->value, strlen(e->value)));
501 break;
502 }}
503
504 free_sorted_view(view, count);
505 st->index++;
506 return true;
507}
508
509static ant_value_t headers_iter_next(ant_t *js, ant_value_t *args, int nargs) {
510 js_iter_t it = { .iterator = js->this_val };
511 ant_value_t value;
512 return js_iter_result(js, advance_headers(js, &it, &value), value);
513}
514
515static ant_value_t make_headers_iter(ant_t *js, ant_value_t headers_obj, int kind) {
516 hdr_list_t *l = get_list(headers_obj);
517
518 hdr_iter_t *st = ant_calloc(sizeof(hdr_iter_t));
519 if (!st) return js_mkerr(js, "out of memory");
520 st->list = l ? l : list_new();
521 st->kind = kind;
522
523 ant_value_t iter = js_mkobj(js);
524 js_set_proto_init(iter, g_headers_iter_proto);
525 js_set_slot(iter, SLOT_ITER_STATE, ANT_PTR(st));
526 return iter;
527}
528
529static ant_value_t js_headers_append(ant_t *js, ant_value_t *args, int nargs) {
530 if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.append requires 2 arguments");
531 hdr_list_t *l = get_list(js->this_val);
532 if (!l) return js_mkerr(js, "Invalid Headers object");
533 ant_value_t guard_err = headers_guard_error(js, get_guard(js->this_val));
534 if (is_err(guard_err)) return guard_err;
535 ant_value_t r = headers_append_pair(js, l, args[0], args[1]);
536 if (is_err(r)) return r;
537 list_apply_guard(l, get_guard(js->this_val));
538 return js_mkundef();
539}
540
541static ant_value_t js_headers_set(ant_t *js, ant_value_t *args, int nargs) {
542 if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.set requires 2 arguments");
543 hdr_list_t *l = get_list(js->this_val);
544 if (!l) return js_mkerr(js, "Invalid Headers object");
545 ant_value_t guard_err = headers_guard_error(js, get_guard(js->this_val));
546 if (is_err(guard_err)) return guard_err;
547
548 ant_value_t name_v = args[0];
549 ant_value_t value_v = args[1];
550 if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; }
551 if (vtype(value_v) != T_STR) { value_v = js_tostring_val(js, value_v); if (is_err(value_v)) return value_v; }
552
553 const char *name = js_getstr(js, name_v, NULL);
554 const char *value = js_getstr(js, value_v, NULL);
555
556 if (!is_valid_name(name))
557 return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name: %s", name ? name : "");
558
559 char *norm = normalize_value(value);
560 if (!norm) return js_mkerr(js, "out of memory");
561 if (!is_valid_value(norm)) { free(norm); return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header value"); }
562
563 char *lower = lowercase_dup(name);
564 if (!lower) { free(norm); return js_mkerr(js, "out of memory"); }
565
566 list_delete_name(l, lower);
567 if (header_allowed_for_guard(lower, norm, get_guard(js->this_val)))
568 list_append_raw(l, lower, norm);
569 free(lower); free(norm);
570 return js_mkundef();
571}
572
573static ant_value_t js_headers_get(ant_t *js, ant_value_t *args, int nargs) {
574 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.get requires 1 argument");
575 hdr_list_t *l = get_list(js->this_val);
576 if (!l) return js_mknull();
577
578 ant_value_t name_v = args[0];
579 if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; }
580 const char *name = js_getstr(js, name_v, NULL);
581 if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name");
582
583 char *lower = lowercase_dup(name);
584 if (!lower) return js_mkerr(js, "out of memory");
585
586 // set-cookie is never combined per Fetch spec
587 if (strcmp(lower, "set-cookie") == 0) {
588 for (hdr_entry_t *e = l->head; e; e = e->next) {
589 if (strcmp(e->name, lower) == 0) {
590 ant_value_t ret = js_mkstr(js, e->value, strlen(e->value));
591 free(lower);
592 return ret;
593 }
594 }
595 free(lower);
596 return js_mknull();
597 }
598
599 size_t total = 0;
600 int count = 0;
601 for (hdr_entry_t *e = l->head; e; e = e->next) {
602 if (strcmp(e->name, lower) == 0) {
603 if (count > 0) total += 2;
604 total += strlen(e->value);
605 count++;
606 }
607 }
608
609 if (count == 0) { free(lower); return js_mknull(); }
610
611 char *combined = malloc(total + 1);
612 if (!combined) { free(lower); return js_mkerr(js, "out of memory"); }
613
614 size_t pos = 0;
615 int seen = 0;
616 for (hdr_entry_t *e = l->head; e; e = e->next) {
617 if (strcmp(e->name, lower) == 0) {
618 if (seen > 0) { combined[pos++] = ','; combined[pos++] = ' '; }
619 size_t vl = strlen(e->value);
620 memcpy(combined + pos, e->value, vl);
621 pos += vl;
622 seen++;
623 }
624 }
625 combined[pos] = '\0';
626 free(lower);
627
628 ant_value_t ret = js_mkstr(js, combined, pos);
629 free(combined);
630 return ret;
631}
632
633static ant_value_t js_headers_has(ant_t *js, ant_value_t *args, int nargs) {
634 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.has requires 1 argument");
635 hdr_list_t *l = get_list(js->this_val);
636 if (!l) return js_false;
637
638 ant_value_t name_v = args[0];
639 if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; }
640 const char *name = js_getstr(js, name_v, NULL);
641 if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name");
642
643 char *lower = lowercase_dup(name);
644 if (!lower) return js_mkerr(js, "out of memory");
645
646 bool found = false;
647 for (hdr_entry_t *e = l->head; e; e = e->next) {
648 if (strcmp(e->name, lower) == 0) { found = true; break; }
649 }
650 free(lower);
651 return js_bool(found);
652}
653
654static ant_value_t js_headers_delete(ant_t *js, ant_value_t *args, int nargs) {
655 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.delete requires 1 argument");
656 hdr_list_t *l = get_list(js->this_val);
657 if (!l) return js_mkundef();
658 ant_value_t guard_err = headers_guard_error(js, get_guard(js->this_val));
659 if (is_err(guard_err)) return guard_err;
660
661 ant_value_t name_v = args[0];
662 if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; }
663 const char *name = js_getstr(js, name_v, NULL);
664 if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name");
665
666 char *lower = lowercase_dup(name);
667 if (!lower) return js_mkerr(js, "out of memory");
668 list_delete_name(l, lower);
669 free(lower);
670 return js_mkundef();
671}
672
673static ant_value_t js_headers_get_set_cookie(ant_t *js, ant_value_t *args, int nargs) {
674 (void)args; (void)nargs;
675 hdr_list_t *l = get_list(js->this_val);
676 ant_value_t arr = js_mkarr(js);
677 if (!l) return arr;
678 for (hdr_entry_t *e = l->head; e; e = e->next) {
679 if (strcmp(e->name, "set-cookie") == 0)
680 js_arr_push(js, arr, js_mkstr(js, e->value, strlen(e->value)));
681 }
682 return arr;
683}
684
685static ant_value_t js_headers_for_each(ant_t *js, ant_value_t *args, int nargs) {
686 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.forEach requires 1 argument");
687
688 ant_value_t cb = args[0];
689 uint8_t cbt = vtype(cb);
690 if (cbt != T_FUNC && cbt != T_CFUNC)
691 return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.forEach callback must be callable");
692
693 ant_value_t this_obj = js->this_val;
694 hdr_list_t *l = get_list(this_obj);
695 if (!l) return js_mkundef();
696
697 ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
698
699 size_t count = 0;
700 sorted_pair_t *view = build_sorted_view(l, &count);
701
702 for (size_t i = 0; i < count; i++) {
703 ant_value_t call_args[3] = {
704 js_mkstr(js, view[i].value, strlen(view[i].value)),
705 js_mkstr(js, view[i].name, strlen(view[i].name)),
706 this_obj
707 };
708 ant_value_t r = sv_vm_call(js->vm, js, cb, this_arg, call_args, 3, NULL, false);
709 if (is_err(r)) { free_sorted_view(view, count); return r; }
710 }
711
712 free_sorted_view(view, count);
713 return js_mkundef();
714}
715
716static ant_value_t js_headers_keys(ant_t *js, ant_value_t *args, int nargs) {
717 return make_headers_iter(js, js->this_val, ITER_KEYS);
718}
719
720static ant_value_t js_headers_values(ant_t *js, ant_value_t *args, int nargs) {
721 return make_headers_iter(js, js->this_val, ITER_VALUES);
722}
723
724static ant_value_t js_headers_entries(ant_t *js, ant_value_t *args, int nargs) {
725 return make_headers_iter(js, js->this_val, ITER_ENTRIES);
726}
727
728static ant_value_t headers_inspect_finish(ant_t *js, ant_value_t this_obj, ant_value_t body_obj) {
729 ant_value_t tag_val = js_get_sym(js, this_obj, get_toStringTag_sym());
730 const char *tag = vtype(tag_val) == T_STR ? js_getstr(js, tag_val, NULL) : "Headers";
731
732 js_inspect_builder_t builder;
733 if (!js_inspect_builder_init_dynamic(&builder, js, 128)) {
734 return js_mkerr(js, "out of memory");
735 }
736
737 bool ok = js_inspect_header_for(&builder, body_obj, "%s", tag);
738 if (ok) ok = js_inspect_object_body(&builder, body_obj);
739 if (ok) ok = js_inspect_close(&builder);
740
741 if (!ok) {
742 js_inspect_builder_dispose(&builder);
743 return js_mkerr(js, "out of memory");
744 }
745
746 return js_inspect_builder_result(&builder);
747}
748
749static ant_value_t headers_inspect(ant_t *js, ant_value_t *args, int nargs) {
750 ant_value_t this_obj = js_getthis(js);
751 hdr_list_t *list = get_list(this_obj);
752 ant_value_t out = js_mkobj(js);
753
754 if (!list) return js_mkerr(js, "Invalid Headers object");
755
756 for (hdr_entry_t *e = list->head; e; e = e->next) {
757 ant_value_t existing = js_get(js, out, e->name);
758 if (vtype(existing) == T_UNDEF) {
759 js_set(js, out, e->name, js_mkstr(js, e->value, strlen(e->value)));
760 continue;
761 }
762
763 size_t existing_len = 0;
764 const char *existing_str = js_getstr(js, existing, &existing_len);
765 size_t value_len = strlen(e->value);
766 size_t combined_len = existing_len + 2 + value_len;
767 char *combined = malloc(combined_len + 1);
768 if (!combined) return js_mkerr(js, "out of memory");
769
770 memcpy(combined, existing_str, existing_len);
771 combined[existing_len] = ',';
772 combined[existing_len + 1] = ' ';
773 memcpy(combined + existing_len + 2, e->value, value_len);
774 combined[combined_len] = '\0';
775
776 js_set(js, out, e->name, js_mkstr(js, combined, combined_len));
777 free(combined);
778 }
779
780 return headers_inspect_finish(js, this_obj, out);
781}
782
783static ant_value_t js_headers_ctor(ant_t *js, ant_value_t *args, int nargs) {
784 if (vtype(js->new_target) == T_UNDEF)
785 return js_mkerr_typed(js, JS_ERR_TYPE, "Headers constructor requires 'new'");
786
787 hdr_list_t *l = list_new();
788 if (!l) return js_mkerr(js, "out of memory");
789
790 ant_value_t init = (nargs >= 1) ? args[0] : js_mkundef();
791
792 if (vtype(init) != T_UNDEF) {
793 uint8_t t = vtype(init);
794
795 if (t == T_NULL || (t != T_OBJ && t != T_ARR && t != T_FUNC && t != T_CFUNC)) {
796 list_free(l);
797 return js_mkerr_typed(js, JS_ERR_TYPE,
798 "Failed to construct 'Headers': The provided value is not of type 'HeadersInit'");
799 }
800
801 ant_value_t iter_fn = js_get_sym(js, init, get_iterator_sym());
802 bool has_iter = (vtype(iter_fn) == T_FUNC || vtype(iter_fn) == T_CFUNC);
803
804 ant_value_t r;
805 if (t == T_ARR || has_iter) r = init_from_sequence(js, l, init);
806 else r = init_from_record(js, l, init);
807 if (is_err(r)) { list_free(l); return r; }
808 }
809
810 ant_value_t obj = js_mkobj(js);
811 ant_value_t proto = js_instance_proto_from_new_target(js, g_headers_proto);
812 if (is_object_type(proto)) js_set_proto_init(obj, proto);
813
814 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_HEADERS));
815 js_set_slot(obj, SLOT_DATA, ANT_PTR(l));
816 js_set_slot(obj, SLOT_HEADERS_GUARD, js_mknum(HEADERS_GUARD_NONE));
817
818 return obj;
819}
820
821ant_value_t headers_create_empty(ant_t *js) {
822 hdr_list_t *l = list_new();
823 if (!l) return js_mkerr(js, "out of memory");
824
825 ant_value_t obj = js_mkobj(js);
826 js_set_proto_init(obj, g_headers_proto);
827 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_HEADERS));
828 js_set_slot(obj, SLOT_DATA, ANT_PTR(l));
829 js_set_slot(obj, SLOT_HEADERS_GUARD, js_mknum(HEADERS_GUARD_NONE));
830
831 return obj;
832}
833
834bool headers_copy_from(ant_t *js, ant_value_t dst, ant_value_t src) {
835 hdr_list_t *src_list = get_list(src);
836 hdr_list_t *dst_list = get_list(dst);
837
838 if (!dst_list) return false;
839 if (!src_list) return true;
840
841 for (hdr_entry_t *e = src_list->head; e; e = e->next)
842 list_append_raw(dst_list, e->name, e->value);
843 return true;
844}
845
846void headers_set_guard(ant_value_t hdrs, headers_guard_t guard) {
847 js_set_slot(hdrs, SLOT_HEADERS_GUARD, js_mknum(guard));
848}
849
850headers_guard_t headers_get_guard(ant_value_t hdrs) {
851 return get_guard(hdrs);
852}
853
854void headers_apply_guard(ant_value_t hdrs) {
855 list_apply_guard(get_list(hdrs), get_guard(hdrs));
856}
857
858void headers_append_if_missing(ant_value_t hdrs, const char *name, const char *value) {
859 hdr_list_t *l = get_list(hdrs);
860 if (!l || !name || !value) return;
861 char *lower = lowercase_dup(name);
862 if (!lower) return;
863 for (hdr_entry_t *e = l->head; e; e = e->next) {
864 if (strcmp(e->name, lower) == 0) { free(lower); return; }
865 }
866 list_append_raw(l, lower, value);
867 free(lower);
868}
869
870void headers_for_each(ant_value_t hdrs, headers_foreach_cb cb, void *ctx) {
871 hdr_list_t *l = get_list(hdrs);
872 if (!l || !cb) return;
873 for (hdr_entry_t *e = l->head; e; e = e->next) cb(e->name, e->value, ctx);
874}
875
876bool headers_set_literal(ant_t *js, ant_value_t hdrs, const char *name, const char *value) {
877 hdr_list_t *l = get_list(hdrs);
878 headers_guard_t guard = 0;
879
880 char *norm = NULL;
881 char *lower = NULL;
882
883 if (!l || !name || !value) return false;
884 if (!is_valid_name(name)) return false;
885
886 norm = normalize_value(value);
887 if (!norm) return false;
888 if (!is_valid_value(norm)) {
889 free(norm);
890 return false;
891 }
892
893 lower = lowercase_dup(name);
894 if (!lower) {
895 free(norm);
896 return false;
897 }
898
899 guard = get_guard(hdrs);
900 if (guard == HEADERS_GUARD_IMMUTABLE) {
901 free(lower);
902 free(norm);
903 return false;
904 }
905
906 list_delete_name(l, lower);
907 if (header_allowed_for_guard(lower, norm, guard)) list_append_raw(l, lower, norm);
908 free(lower);
909 free(norm);
910
911 return true;
912}
913
914ant_value_t headers_create_from_init(ant_t *js, ant_value_t init) {
915 ant_value_t new_hdrs = 0;
916 uint8_t ht = vtype(init);
917
918 new_hdrs = headers_create_empty(js);
919 if (is_err(new_hdrs)) return new_hdrs;
920 if (ht == T_UNDEF) return new_hdrs;
921
922 if (headers_is_headers(init)) {
923 headers_copy_from(js, new_hdrs, init);
924 return new_hdrs;
925 }
926
927 if (ht == T_ARR) {
928 ant_offset_t len = js_arr_len(js, init);
929 for (ant_offset_t i = 0; i < len; i++) {
930 ant_value_t pair = js_arr_get(js, init, i);
931 ant_value_t r = 0;
932 if (js_arr_len(js, pair) < 2) continue;
933 r = headers_append_value(js, new_hdrs, js_arr_get(js, pair, 0), js_arr_get(js, pair, 1));
934 if (is_err(r)) return r;
935 }
936 return new_hdrs;
937 }
938
939 if (ht == T_OBJ) {
940 ant_iter_t it = js_prop_iter_begin(js, init);
941 const char *key = NULL;
942 size_t key_len = 0;
943 ant_value_t val = 0;
944
945 while (js_prop_iter_next(&it, &key, &key_len, &val)) {
946 ant_value_t r = headers_append_value(js, new_hdrs, js_mkstr(js, key, key_len), val);
947 if (is_err(r)) {
948 js_prop_iter_end(&it);
949 return r;
950 }
951 }
952
953 js_prop_iter_end(&it);
954 }
955
956 return new_hdrs;
957}
958
959bool headers_init_has_name(ant_t *js, ant_value_t init, const char *name) {
960 uint8_t ht = vtype(init);
961
962 if (ht == T_UNDEF) return false;
963 if (headers_is_headers(init)) {
964 ant_value_t value = headers_get_value(js, init, name);
965 return !is_err(value) && vtype(value) != T_NULL;
966 }
967
968 if (ht == T_ARR) {
969 ant_offset_t len = js_arr_len(js, init);
970 for (ant_offset_t i = 0; i < len; i++) {
971 ant_value_t pair = js_arr_get(js, init, i);
972 ant_value_t key_v = 0;
973 const char *key = NULL;
974 if (js_arr_len(js, pair) < 1) continue;
975 key_v = js_arr_get(js, pair, 0);
976 if (vtype(key_v) != T_STR) {
977 key_v = js_tostring_val(js, key_v);
978 if (is_err(key_v)) continue;
979 }
980 key = js_getstr(js, key_v, NULL);
981 if (key && strcasecmp(key, name) == 0) return true;
982 }
983 return false;
984 }
985
986 if (ht == T_OBJ) {
987 ant_iter_t it = js_prop_iter_begin(js, init);
988 const char *key = NULL;
989 size_t key_len = 0;
990 ant_value_t value = 0;
991 bool found = false;
992
993 while (js_prop_iter_next(&it, &key, &key_len, &value)) {
994 (void)value;
995 if (key && strcasecmp(key, name) == 0) {
996 found = true;
997 break;
998 }
999 }
1000
1001 js_prop_iter_end(&it);
1002 return found;
1003 }
1004
1005 return false;
1006}
1007
1008ant_value_t headers_get_value(ant_t *js, ant_value_t hdrs, const char *name) {
1009 hdr_list_t *l = get_list(hdrs);
1010
1011 if (!l) return js_mknull();
1012 if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name");
1013
1014 char *lower = lowercase_dup(name);
1015 if (!lower) return js_mkerr(js, "out of memory");
1016
1017 if (strcmp(lower, "set-cookie") == 0) {
1018 for (hdr_entry_t *e = l->head; e; e = e->next) {
1019 if (strcmp(e->name, lower) == 0) {
1020 ant_value_t ret = js_mkstr(js, e->value, strlen(e->value));
1021 free(lower);
1022 return ret;
1023 }}
1024 free(lower);
1025 return js_mknull();
1026 }
1027
1028 size_t total = 0;
1029 int count = 0;
1030
1031 for (hdr_entry_t *e = l->head; e; e = e->next) {
1032 if (strcmp(e->name, lower) == 0) {
1033 if (count > 0) total += 2;
1034 total += strlen(e->value);
1035 count++;
1036 }}
1037
1038 if (count == 0) {
1039 free(lower);
1040 return js_mknull();
1041 }
1042
1043 char *combined = malloc(total + 1);
1044 if (!combined) {
1045 free(lower);
1046 return js_mkerr(js, "out of memory");
1047 }
1048
1049 size_t pos = 0;
1050 int seen = 0;
1051
1052 for (hdr_entry_t *e = l->head; e; e = e->next) {
1053 if (strcmp(e->name, lower) == 0) {
1054 if (seen > 0) { combined[pos++] = ','; combined[pos++] = ' '; }
1055 size_t vl = strlen(e->value);
1056 memcpy(combined + pos, e->value, vl);
1057 pos += vl;
1058 seen++;
1059 }}
1060
1061 combined[pos] = '\0';
1062 free(lower);
1063
1064 ant_value_t ret = js_mkstr(js, combined, pos);
1065 free(combined);
1066
1067 return ret;
1068}
1069
1070void init_headers_module(void) {
1071 ant_t *js = rt->js;
1072 ant_value_t g = js_glob(js);
1073
1074 g_headers_iter_proto = js_mkobj(js);
1075 js_set_proto_init(g_headers_iter_proto, js->sym.iterator_proto);
1076 js_set(js, g_headers_iter_proto, "next", js_mkfun(headers_iter_next));
1077 js_set_descriptor(js, g_headers_iter_proto, "next", 4, JS_DESC_W | JS_DESC_E | JS_DESC_C);
1078 js_set_sym(js, g_headers_iter_proto, get_iterator_sym(), js_mkfun(sym_this_cb));
1079 js_iter_register_advance(g_headers_iter_proto, advance_headers);
1080
1081 g_headers_proto = js_mkobj(js);
1082
1083 js_set(js, g_headers_proto, "append", js_mkfun(js_headers_append));
1084 js_set(js, g_headers_proto, "set", js_mkfun(js_headers_set));
1085 js_set(js, g_headers_proto, "get", js_mkfun(js_headers_get));
1086 js_set(js, g_headers_proto, "has", js_mkfun(js_headers_has));
1087 js_set(js, g_headers_proto, "delete", js_mkfun(js_headers_delete));
1088 js_set(js, g_headers_proto, "forEach", js_mkfun(js_headers_for_each));
1089 js_set(js, g_headers_proto, "keys", js_mkfun(js_headers_keys));
1090 js_set(js, g_headers_proto, "values", js_mkfun(js_headers_values));
1091 js_set(js, g_headers_proto, "entries", js_mkfun(js_headers_entries));
1092 js_set(js, g_headers_proto, "getSetCookie", js_mkfun(js_headers_get_set_cookie));
1093
1094 js_set_sym(js, g_headers_proto, get_iterator_sym(), js_get(js, g_headers_proto, "entries"));
1095 js_set_sym(js, g_headers_proto, get_inspect_sym(), js_mkfun(headers_inspect));
1096 js_set_sym(js, g_headers_proto, get_toStringTag_sym(), js_mkstr(js, "Headers", 7));
1097
1098 ant_value_t ctor_obj = js_mkobj(js);
1099 js_set_slot(ctor_obj, SLOT_CFUNC, js_mkfun(js_headers_ctor));
1100 js_mkprop_fast(js, ctor_obj, "prototype", 9, g_headers_proto);
1101 js_mkprop_fast(js, ctor_obj, "name", 4, js_mkstr(js, "Headers", 7));
1102 js_set_descriptor(js, ctor_obj, "name", 4, 0);
1103
1104 ant_value_t ctor = js_obj_to_func(ctor_obj);
1105 js_set(js, g_headers_proto, "constructor", ctor);
1106 js_set_descriptor(js, g_headers_proto, "constructor", 11, JS_DESC_W | JS_DESC_C);
1107
1108 js_set(js, g, "Headers", ctor);
1109 js_set_descriptor(js, g, "Headers", 7, JS_DESC_W | JS_DESC_C);
1110}