#include #include #include #include #include "ant.h" #include "errors.h" #include "runtime.h" #include "internal.h" #include "descriptors.h" #include "silver/engine.h" #include "modules/headers.h" #include "modules/symbol.h" typedef struct hdr_entry { char *name; char *value; struct hdr_entry *next; } hdr_entry_t; typedef struct { hdr_entry_t *head; hdr_entry_t **tail; size_t count; } hdr_list_t; typedef struct { char *name; char *value; } sorted_pair_t; typedef struct { hdr_list_t *list; size_t index; int kind; } hdr_iter_t; enum { ITER_ENTRIES = 0, ITER_KEYS = 1, ITER_VALUES = 2 }; ant_value_t g_headers_proto = 0; ant_value_t g_headers_iter_proto = 0; static hdr_list_t *list_new(void) { hdr_list_t *l = ant_calloc(sizeof(hdr_list_t)); if (!l) return NULL; l->head = NULL; l->tail = &l->head; return l; } static void list_free(hdr_list_t *l) { if (!l) return; for (hdr_entry_t *e = l->head; e; ) { hdr_entry_t *n = e->next; free(e->name); free(e->value); free(e); e = n; } free(l); } static hdr_list_t *get_list(ant_value_t obj) { ant_value_t slot = js_get_slot(obj, SLOT_DATA); if (vtype(slot) != T_NUM) return NULL; return (hdr_list_t *)(uintptr_t)(size_t)js_getnum(slot); } static headers_guard_t get_guard(ant_value_t obj) { ant_value_t slot = js_get_slot(obj, SLOT_HEADERS_GUARD); if (vtype(slot) != T_NUM) return HEADERS_GUARD_NONE; return (headers_guard_t)(int)js_getnum(slot); } bool headers_is_headers(ant_value_t obj) { return js_check_brand(obj, BRAND_HEADERS); } static bool is_token_char(unsigned char c) { if (c > 127) return false; static const char ok[] = "!#$%&'*+-.^_`|~" "0123456789" "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; return strchr(ok, (char)c) != NULL; } static bool is_valid_name(const char *s) { if (!s || !*s) return false; for (const unsigned char *p = (const unsigned char *)s; *p; p++) if (!is_token_char(*p)) return false; return true; } static bool is_valid_value(const char *s) { if (!s) return false; for (const unsigned char *p = (const unsigned char *)s; *p; p++) { unsigned char c = *p; if (c == 0 || c == '\r' || c == '\n' || c > 127) return false; } return true; } static char *normalize_value(const char *s) { if (!s) return strdup(""); while (*s == ' ' || *s == '\t') s++; size_t len = strlen(s); while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t')) len--; char *out = malloc(len + 1); if (!out) return NULL; memcpy(out, s, len); out[len] = '\0'; return out; } static char *lowercase_dup(const char *s) { if (!s) return strdup(""); size_t len = strlen(s); char *out = malloc(len + 1); if (!out) return NULL; for (size_t i = 0; i <= len; i++) out[i] = (char)tolower((unsigned char)s[i]); return out; } typedef struct { const char *name; bool prefix; } header_rule_t; static const header_rule_t k_forbidden_request_headers[] = { { "accept-charset", false }, { "accept-encoding", false }, { "access-control-request-headers", false }, { "access-control-request-method", false }, { "connection", false }, { "content-length", false }, { "cookie", false }, { "cookie2", false }, { "date", false }, { "dnt", false }, { "expect", false }, { "host", false }, { "keep-alive", false }, { "origin", false }, { "referer", false }, { "set-cookie", false }, { "te", false }, { "trailer", false }, { "transfer-encoding", false }, { "upgrade", false }, { "via", false }, { "proxy-", true }, { "sec-", true }, }; static const header_rule_t k_forbidden_response_headers[] = { { "set-cookie", false }, { "set-cookie2", false }, }; static const char *k_cors_safelisted_content_types[] = { "application/x-www-form-urlencoded", "multipart/form-data", "text/plain", }; static const char *k_no_cors_safelisted_names[] = { "accept", "accept-language", "content-language", }; static bool matches_rule(const char *name, const header_rule_t *rules, size_t count) { for (size_t i = 0; i < count; i++) { size_t len = strlen(rules[i].name); if (rules[i].prefix) { if (strncmp(name, rules[i].name, len) == 0) return true; } else if (strcmp(name, rules[i].name) == 0) return true; } return false; } static bool matches_string(const char *value, const char *const *list, size_t count) { for (size_t i = 0; i < count; i++) { if (strcmp(value, list[i]) == 0) return true; } return false; } static bool is_forbidden_request_header_name(const char *lower_name) { return matches_rule(lower_name, k_forbidden_request_headers, sizeof(k_forbidden_request_headers) / sizeof(k_forbidden_request_headers[0])); } static bool is_forbidden_response_header_name(const char *lower_name) { return matches_rule(lower_name, k_forbidden_response_headers, sizeof(k_forbidden_response_headers) / sizeof(k_forbidden_response_headers[0])); } static bool is_cors_safelisted_content_type_value(const char *value) { char *lower = lowercase_dup(value ? value : ""); if (!lower) return false; char *semi = strchr(lower, ';'); if (!semi) { bool ok = matches_string( lower, k_cors_safelisted_content_types, sizeof(k_cors_safelisted_content_types) / sizeof(k_cors_safelisted_content_types[0]) ); free(lower); return ok; } *semi++ = '\0'; while (*semi == ' ' || *semi == '\t') semi++; bool essence_ok = matches_string( lower, k_cors_safelisted_content_types, sizeof(k_cors_safelisted_content_types) / sizeof(k_cors_safelisted_content_types[0]) ); bool param_ok = strcmp(semi, "charset=utf-8") == 0; free(lower); return essence_ok && param_ok; } static bool is_no_cors_safelisted_name_value(const char *lower_name, const char *value) { if ( matches_string( lower_name, k_no_cors_safelisted_names, sizeof(k_no_cors_safelisted_names) / sizeof(k_no_cors_safelisted_names[0])) ) return true; if (strcmp(lower_name, "content-type") == 0) return value && value[0] && is_cors_safelisted_content_type_value(value); return false; } static bool header_allowed_for_guard(const char *lower_name, const char *value, headers_guard_t guard) { if (guard == HEADERS_GUARD_NONE) return true; if (guard == HEADERS_GUARD_IMMUTABLE) return true; if (is_forbidden_request_header_name(lower_name)) return false; if (guard == HEADERS_GUARD_RESPONSE) return !is_forbidden_response_header_name(lower_name); if (guard == HEADERS_GUARD_REQUEST_NO_CORS) return is_no_cors_safelisted_name_value(lower_name, value); return true; } static ant_value_t headers_guard_error(ant_t *js, headers_guard_t guard) { if (guard != HEADERS_GUARD_IMMUTABLE) return js_mkundef(); return js_mkerr_typed(js, JS_ERR_TYPE, "Headers are immutable"); } static void list_apply_guard(hdr_list_t *l, headers_guard_t guard) { if (!l || guard == HEADERS_GUARD_NONE || guard == HEADERS_GUARD_IMMUTABLE) return; hdr_entry_t **pp = &l->head; l->tail = &l->head; while (*pp) { hdr_entry_t *cur = *pp; if (!header_allowed_for_guard(cur->name, cur->value, guard)) { *pp = cur->next; free(cur->name); free(cur->value); free(cur); l->count--; continue; } l->tail = &cur->next; pp = &cur->next; } } static void list_append_raw(hdr_list_t *l, const char *lower_name, const char *value) { hdr_entry_t *e = ant_calloc(sizeof(hdr_entry_t)); if (!e) return; e->name = strdup(lower_name); e->value = strdup(value); *l->tail = e; l->tail = &e->next; l->count++; } static void list_delete_name(hdr_list_t *l, const char *lower_name) { hdr_entry_t **pp = &l->head; l->tail = &l->head; while (*pp) { if (strcmp((*pp)->name, lower_name) == 0) { hdr_entry_t *dead = *pp; *pp = dead->next; free(dead->name); free(dead->value); free(dead); l->count--; } else { l->tail = &(*pp)->next; pp = &(*pp)->next; }} } static int cmp_pairs(const void *a, const void *b) { return strcmp(((const sorted_pair_t *)a)->name, ((const sorted_pair_t *)b)->name); } static sorted_pair_t *build_sorted_view(hdr_list_t *l, size_t *out) { *out = 0; if (!l || l->count == 0) return NULL; sorted_pair_t *raw = malloc(l->count * sizeof(sorted_pair_t)); if (!raw) return NULL; size_t n = 0; for (hdr_entry_t *e = l->head; e; e = e->next) { raw[n].name = e->name; raw[n].value = e->value; n++; } qsort(raw, n, sizeof(sorted_pair_t), cmp_pairs); sorted_pair_t *res = malloc(n * sizeof(sorted_pair_t)); if (!res) { free(raw); return NULL; } size_t ri = 0; for (size_t i = 0; i < n; ) { if (strcmp(raw[i].name, "set-cookie") == 0) { res[ri].name = strdup(raw[i].name); res[ri].value = strdup(raw[i].value); ri++; i++; } else { size_t j = i + 1; size_t total = strlen(raw[i].value); while (j < n && strcmp(raw[j].name, raw[i].name) == 0) { total += 2 + strlen(raw[j].value); j++; } char *combined = malloc(total + 1); if (!combined) combined = strdup(""); size_t pos = 0; for (size_t k = i; k < j; k++) { if (k > i) { combined[pos++] = ','; combined[pos++] = ' '; } size_t vl = strlen(raw[k].value); memcpy(combined + pos, raw[k].value, vl); pos += vl; } combined[pos] = '\0'; res[ri].name = strdup(raw[i].name); res[ri].value = combined; ri++; i = j; }} free(raw); *out = ri; return res; } static void free_sorted_view(sorted_pair_t *v, size_t n) { if (!v) return; for (size_t i = 0; i < n; i++) { free(v[i].name); free(v[i].value); } free(v); } static ant_value_t headers_append_name_value(ant_t *js, hdr_list_t *l, const char *name, const char *value) { if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name: %s", name ? name : ""); char *norm = normalize_value(value); if (!norm) return js_mkerr(js, "out of memory"); if (!is_valid_value(norm)) { free(norm); return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header value"); } char *lower = lowercase_dup(name); if (!lower) { free(norm); return js_mkerr(js, "out of memory"); } list_append_raw(l, lower, norm); free(lower); free(norm); return js_mkundef(); } static ant_value_t headers_append_pair(ant_t *js, hdr_list_t *l, ant_value_t name_v, ant_value_t value_v) { const char *name = NULL; const char *value = NULL; if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; } if (vtype(value_v) != T_STR) { value_v = js_tostring_val(js, value_v); if (is_err(value_v)) return value_v; } name = js_getstr(js, name_v, NULL); value = js_getstr(js, value_v, NULL); return headers_append_name_value(js, l, name, value); } ant_value_t headers_append_value(ant_t *js, ant_value_t hdrs, ant_value_t name_v, ant_value_t value_v) { hdr_list_t *l = get_list(hdrs); ant_value_t r = 0; if (!l) return js_mkerr(js, "Invalid Headers object"); r = headers_append_pair(js, l, name_v, value_v); if (is_err(r)) return r; list_apply_guard(l, get_guard(hdrs)); return js_mkundef(); } ant_value_t headers_append_literal(ant_t *js, ant_value_t hdrs, const char *name, const char *value) { hdr_list_t *l = get_list(hdrs); ant_value_t r = 0; if (!l) return js_mkerr(js, "Invalid Headers object"); r = headers_append_name_value(js, l, name, value); if (is_err(r)) return r; list_apply_guard(l, get_guard(hdrs)); return js_mkundef(); } static ant_value_t init_from_sequence(ant_t *js, hdr_list_t *l, ant_value_t seq) { js_iter_t it; if (!js_iter_open(js, seq, &it)) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers init is not iterable"); ant_value_t pair; while (js_iter_next(js, &it, &pair)) { uint8_t pt = vtype(pair); if (pt != T_ARR && pt != T_OBJ) { js_iter_close(js, &it); return js_mkerr_typed(js, JS_ERR_TYPE, "Each header init pair must be a sequence"); } if (js_arr_len(js, pair) != 2) { js_iter_close(js, &it); return js_mkerr_typed(js, JS_ERR_TYPE, "Each header init pair must have exactly 2 elements"); } ant_value_t r = headers_append_pair(js, l, js_arr_get(js, pair, 0), js_arr_get(js, pair, 1)); if (is_err(r)) { js_iter_close(js, &it); return r; } } return js_mkundef(); } static ant_value_t init_from_record(ant_t *js, hdr_list_t *l, ant_value_t obj) { ant_iter_t it = js_prop_iter_begin(js, obj); const char *key; size_t key_len; ant_value_t val; while (js_prop_iter_next(&it, &key, &key_len, &val)) { ant_value_t r = headers_append_pair(js, l, js_mkstr(js, key, key_len), val); if (is_err(r)) { js_prop_iter_end(&it); return r; } } js_prop_iter_end(&it); return js_mkundef(); } bool advance_headers(ant_t *js, js_iter_t *it, ant_value_t *out) { ant_value_t state_val = js_get_slot(it->iterator, SLOT_ITER_STATE); if (vtype(state_val) == T_UNDEF) return false; hdr_iter_t *st = (hdr_iter_t *)(uintptr_t)(size_t)js_getnum(state_val); size_t count = 0; sorted_pair_t *view = build_sorted_view(st->list, &count); if (st->index >= count) { free_sorted_view(view, count); return false; } sorted_pair_t *e = &view[st->index]; switch (st->kind) { case ITER_KEYS: *out = js_mkstr(js, e->name, strlen(e->name)); break; case ITER_VALUES: *out = js_mkstr(js, e->value, strlen(e->value)); break; default: { *out = js_mkarr(js); js_arr_push(js, *out, js_mkstr(js, e->name, strlen(e->name))); js_arr_push(js, *out, js_mkstr(js, e->value, strlen(e->value))); break; }} free_sorted_view(view, count); st->index++; return true; } static ant_value_t headers_iter_next(ant_t *js, ant_value_t *args, int nargs) { js_iter_t it = { .iterator = js->this_val }; ant_value_t value; return js_iter_result(js, advance_headers(js, &it, &value), value); } static ant_value_t make_headers_iter(ant_t *js, ant_value_t headers_obj, int kind) { hdr_list_t *l = get_list(headers_obj); hdr_iter_t *st = ant_calloc(sizeof(hdr_iter_t)); if (!st) return js_mkerr(js, "out of memory"); st->list = l ? l : list_new(); st->kind = kind; ant_value_t iter = js_mkobj(js); js_set_proto_init(iter, g_headers_iter_proto); js_set_slot(iter, SLOT_ITER_STATE, ANT_PTR(st)); return iter; } static ant_value_t js_headers_append(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.append requires 2 arguments"); hdr_list_t *l = get_list(js->this_val); if (!l) return js_mkerr(js, "Invalid Headers object"); ant_value_t guard_err = headers_guard_error(js, get_guard(js->this_val)); if (is_err(guard_err)) return guard_err; ant_value_t r = headers_append_pair(js, l, args[0], args[1]); if (is_err(r)) return r; list_apply_guard(l, get_guard(js->this_val)); return js_mkundef(); } static ant_value_t js_headers_set(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.set requires 2 arguments"); hdr_list_t *l = get_list(js->this_val); if (!l) return js_mkerr(js, "Invalid Headers object"); ant_value_t guard_err = headers_guard_error(js, get_guard(js->this_val)); if (is_err(guard_err)) return guard_err; ant_value_t name_v = args[0]; ant_value_t value_v = args[1]; if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; } if (vtype(value_v) != T_STR) { value_v = js_tostring_val(js, value_v); if (is_err(value_v)) return value_v; } const char *name = js_getstr(js, name_v, NULL); const char *value = js_getstr(js, value_v, NULL); if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name: %s", name ? name : ""); char *norm = normalize_value(value); if (!norm) return js_mkerr(js, "out of memory"); if (!is_valid_value(norm)) { free(norm); return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header value"); } char *lower = lowercase_dup(name); if (!lower) { free(norm); return js_mkerr(js, "out of memory"); } list_delete_name(l, lower); if (header_allowed_for_guard(lower, norm, get_guard(js->this_val))) list_append_raw(l, lower, norm); free(lower); free(norm); return js_mkundef(); } static ant_value_t js_headers_get(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.get requires 1 argument"); hdr_list_t *l = get_list(js->this_val); if (!l) return js_mknull(); ant_value_t name_v = args[0]; if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; } const char *name = js_getstr(js, name_v, NULL); if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name"); char *lower = lowercase_dup(name); if (!lower) return js_mkerr(js, "out of memory"); // set-cookie is never combined per Fetch spec if (strcmp(lower, "set-cookie") == 0) { for (hdr_entry_t *e = l->head; e; e = e->next) { if (strcmp(e->name, lower) == 0) { ant_value_t ret = js_mkstr(js, e->value, strlen(e->value)); free(lower); return ret; } } free(lower); return js_mknull(); } size_t total = 0; int count = 0; for (hdr_entry_t *e = l->head; e; e = e->next) { if (strcmp(e->name, lower) == 0) { if (count > 0) total += 2; total += strlen(e->value); count++; } } if (count == 0) { free(lower); return js_mknull(); } char *combined = malloc(total + 1); if (!combined) { free(lower); return js_mkerr(js, "out of memory"); } size_t pos = 0; int seen = 0; for (hdr_entry_t *e = l->head; e; e = e->next) { if (strcmp(e->name, lower) == 0) { if (seen > 0) { combined[pos++] = ','; combined[pos++] = ' '; } size_t vl = strlen(e->value); memcpy(combined + pos, e->value, vl); pos += vl; seen++; } } combined[pos] = '\0'; free(lower); ant_value_t ret = js_mkstr(js, combined, pos); free(combined); return ret; } static ant_value_t js_headers_has(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.has requires 1 argument"); hdr_list_t *l = get_list(js->this_val); if (!l) return js_false; ant_value_t name_v = args[0]; if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; } const char *name = js_getstr(js, name_v, NULL); if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name"); char *lower = lowercase_dup(name); if (!lower) return js_mkerr(js, "out of memory"); bool found = false; for (hdr_entry_t *e = l->head; e; e = e->next) { if (strcmp(e->name, lower) == 0) { found = true; break; } } free(lower); return js_bool(found); } static ant_value_t js_headers_delete(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.delete requires 1 argument"); hdr_list_t *l = get_list(js->this_val); if (!l) return js_mkundef(); ant_value_t guard_err = headers_guard_error(js, get_guard(js->this_val)); if (is_err(guard_err)) return guard_err; ant_value_t name_v = args[0]; if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; } const char *name = js_getstr(js, name_v, NULL); if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name"); char *lower = lowercase_dup(name); if (!lower) return js_mkerr(js, "out of memory"); list_delete_name(l, lower); free(lower); return js_mkundef(); } static ant_value_t js_headers_get_set_cookie(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; hdr_list_t *l = get_list(js->this_val); ant_value_t arr = js_mkarr(js); if (!l) return arr; for (hdr_entry_t *e = l->head; e; e = e->next) { if (strcmp(e->name, "set-cookie") == 0) js_arr_push(js, arr, js_mkstr(js, e->value, strlen(e->value))); } return arr; } static ant_value_t js_headers_for_each(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.forEach requires 1 argument"); ant_value_t cb = args[0]; uint8_t cbt = vtype(cb); if (cbt != T_FUNC && cbt != T_CFUNC) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.forEach callback must be callable"); ant_value_t this_obj = js->this_val; hdr_list_t *l = get_list(this_obj); if (!l) return js_mkundef(); ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef(); size_t count = 0; sorted_pair_t *view = build_sorted_view(l, &count); for (size_t i = 0; i < count; i++) { ant_value_t call_args[3] = { js_mkstr(js, view[i].value, strlen(view[i].value)), js_mkstr(js, view[i].name, strlen(view[i].name)), this_obj }; ant_value_t r = sv_vm_call(js->vm, js, cb, this_arg, call_args, 3, NULL, false); if (is_err(r)) { free_sorted_view(view, count); return r; } } free_sorted_view(view, count); return js_mkundef(); } static ant_value_t js_headers_keys(ant_t *js, ant_value_t *args, int nargs) { return make_headers_iter(js, js->this_val, ITER_KEYS); } static ant_value_t js_headers_values(ant_t *js, ant_value_t *args, int nargs) { return make_headers_iter(js, js->this_val, ITER_VALUES); } static ant_value_t js_headers_entries(ant_t *js, ant_value_t *args, int nargs) { return make_headers_iter(js, js->this_val, ITER_ENTRIES); } static ant_value_t headers_inspect_finish(ant_t *js, ant_value_t this_obj, ant_value_t body_obj) { ant_value_t tag_val = js_get_sym(js, this_obj, get_toStringTag_sym()); const char *tag = vtype(tag_val) == T_STR ? js_getstr(js, tag_val, NULL) : "Headers"; js_inspect_builder_t builder; if (!js_inspect_builder_init_dynamic(&builder, js, 128)) { return js_mkerr(js, "out of memory"); } bool ok = js_inspect_header_for(&builder, body_obj, "%s", tag); if (ok) ok = js_inspect_object_body(&builder, body_obj); if (ok) ok = js_inspect_close(&builder); if (!ok) { js_inspect_builder_dispose(&builder); return js_mkerr(js, "out of memory"); } return js_inspect_builder_result(&builder); } static ant_value_t headers_inspect(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); hdr_list_t *list = get_list(this_obj); ant_value_t out = js_mkobj(js); if (!list) return js_mkerr(js, "Invalid Headers object"); for (hdr_entry_t *e = list->head; e; e = e->next) { ant_value_t existing = js_get(js, out, e->name); if (vtype(existing) == T_UNDEF) { js_set(js, out, e->name, js_mkstr(js, e->value, strlen(e->value))); continue; } size_t existing_len = 0; const char *existing_str = js_getstr(js, existing, &existing_len); size_t value_len = strlen(e->value); size_t combined_len = existing_len + 2 + value_len; char *combined = malloc(combined_len + 1); if (!combined) return js_mkerr(js, "out of memory"); memcpy(combined, existing_str, existing_len); combined[existing_len] = ','; combined[existing_len + 1] = ' '; memcpy(combined + existing_len + 2, e->value, value_len); combined[combined_len] = '\0'; js_set(js, out, e->name, js_mkstr(js, combined, combined_len)); free(combined); } return headers_inspect_finish(js, this_obj, out); } static ant_value_t js_headers_ctor(ant_t *js, ant_value_t *args, int nargs) { if (vtype(js->new_target) == T_UNDEF) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers constructor requires 'new'"); hdr_list_t *l = list_new(); if (!l) return js_mkerr(js, "out of memory"); ant_value_t init = (nargs >= 1) ? args[0] : js_mkundef(); if (vtype(init) != T_UNDEF) { uint8_t t = vtype(init); if (t == T_NULL || (t != T_OBJ && t != T_ARR && t != T_FUNC && t != T_CFUNC)) { list_free(l); return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Headers': The provided value is not of type 'HeadersInit'"); } ant_value_t iter_fn = js_get_sym(js, init, get_iterator_sym()); bool has_iter = (vtype(iter_fn) == T_FUNC || vtype(iter_fn) == T_CFUNC); ant_value_t r; if (t == T_ARR || has_iter) r = init_from_sequence(js, l, init); else r = init_from_record(js, l, init); if (is_err(r)) { list_free(l); return r; } } ant_value_t obj = js_mkobj(js); ant_value_t proto = js_instance_proto_from_new_target(js, g_headers_proto); if (is_object_type(proto)) js_set_proto_init(obj, proto); js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_HEADERS)); js_set_slot(obj, SLOT_DATA, ANT_PTR(l)); js_set_slot(obj, SLOT_HEADERS_GUARD, js_mknum(HEADERS_GUARD_NONE)); return obj; } ant_value_t headers_create_empty(ant_t *js) { hdr_list_t *l = list_new(); if (!l) return js_mkerr(js, "out of memory"); ant_value_t obj = js_mkobj(js); js_set_proto_init(obj, g_headers_proto); js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_HEADERS)); js_set_slot(obj, SLOT_DATA, ANT_PTR(l)); js_set_slot(obj, SLOT_HEADERS_GUARD, js_mknum(HEADERS_GUARD_NONE)); return obj; } bool headers_copy_from(ant_t *js, ant_value_t dst, ant_value_t src) { hdr_list_t *src_list = get_list(src); hdr_list_t *dst_list = get_list(dst); if (!dst_list) return false; if (!src_list) return true; for (hdr_entry_t *e = src_list->head; e; e = e->next) list_append_raw(dst_list, e->name, e->value); return true; } void headers_set_guard(ant_value_t hdrs, headers_guard_t guard) { js_set_slot(hdrs, SLOT_HEADERS_GUARD, js_mknum(guard)); } headers_guard_t headers_get_guard(ant_value_t hdrs) { return get_guard(hdrs); } void headers_apply_guard(ant_value_t hdrs) { list_apply_guard(get_list(hdrs), get_guard(hdrs)); } void headers_append_if_missing(ant_value_t hdrs, const char *name, const char *value) { hdr_list_t *l = get_list(hdrs); if (!l || !name || !value) return; char *lower = lowercase_dup(name); if (!lower) return; for (hdr_entry_t *e = l->head; e; e = e->next) { if (strcmp(e->name, lower) == 0) { free(lower); return; } } list_append_raw(l, lower, value); free(lower); } void headers_for_each(ant_value_t hdrs, headers_foreach_cb cb, void *ctx) { hdr_list_t *l = get_list(hdrs); if (!l || !cb) return; for (hdr_entry_t *e = l->head; e; e = e->next) cb(e->name, e->value, ctx); } bool headers_set_literal(ant_t *js, ant_value_t hdrs, const char *name, const char *value) { hdr_list_t *l = get_list(hdrs); headers_guard_t guard = 0; char *norm = NULL; char *lower = NULL; if (!l || !name || !value) return false; if (!is_valid_name(name)) return false; norm = normalize_value(value); if (!norm) return false; if (!is_valid_value(norm)) { free(norm); return false; } lower = lowercase_dup(name); if (!lower) { free(norm); return false; } guard = get_guard(hdrs); if (guard == HEADERS_GUARD_IMMUTABLE) { free(lower); free(norm); return false; } list_delete_name(l, lower); if (header_allowed_for_guard(lower, norm, guard)) list_append_raw(l, lower, norm); free(lower); free(norm); return true; } ant_value_t headers_create_from_init(ant_t *js, ant_value_t init) { ant_value_t new_hdrs = 0; uint8_t ht = vtype(init); new_hdrs = headers_create_empty(js); if (is_err(new_hdrs)) return new_hdrs; if (ht == T_UNDEF) return new_hdrs; if (headers_is_headers(init)) { headers_copy_from(js, new_hdrs, init); return new_hdrs; } if (ht == T_ARR) { ant_offset_t len = js_arr_len(js, init); for (ant_offset_t i = 0; i < len; i++) { ant_value_t pair = js_arr_get(js, init, i); ant_value_t r = 0; if (js_arr_len(js, pair) < 2) continue; r = headers_append_value(js, new_hdrs, js_arr_get(js, pair, 0), js_arr_get(js, pair, 1)); if (is_err(r)) return r; } return new_hdrs; } if (ht == T_OBJ) { ant_iter_t it = js_prop_iter_begin(js, init); const char *key = NULL; size_t key_len = 0; ant_value_t val = 0; while (js_prop_iter_next(&it, &key, &key_len, &val)) { ant_value_t r = headers_append_value(js, new_hdrs, js_mkstr(js, key, key_len), val); if (is_err(r)) { js_prop_iter_end(&it); return r; } } js_prop_iter_end(&it); } return new_hdrs; } bool headers_init_has_name(ant_t *js, ant_value_t init, const char *name) { uint8_t ht = vtype(init); if (ht == T_UNDEF) return false; if (headers_is_headers(init)) { ant_value_t value = headers_get_value(js, init, name); return !is_err(value) && vtype(value) != T_NULL; } if (ht == T_ARR) { ant_offset_t len = js_arr_len(js, init); for (ant_offset_t i = 0; i < len; i++) { ant_value_t pair = js_arr_get(js, init, i); ant_value_t key_v = 0; const char *key = NULL; if (js_arr_len(js, pair) < 1) continue; key_v = js_arr_get(js, pair, 0); if (vtype(key_v) != T_STR) { key_v = js_tostring_val(js, key_v); if (is_err(key_v)) continue; } key = js_getstr(js, key_v, NULL); if (key && strcasecmp(key, name) == 0) return true; } return false; } if (ht == T_OBJ) { ant_iter_t it = js_prop_iter_begin(js, init); const char *key = NULL; size_t key_len = 0; ant_value_t value = 0; bool found = false; while (js_prop_iter_next(&it, &key, &key_len, &value)) { (void)value; if (key && strcasecmp(key, name) == 0) { found = true; break; } } js_prop_iter_end(&it); return found; } return false; } ant_value_t headers_get_value(ant_t *js, ant_value_t hdrs, const char *name) { hdr_list_t *l = get_list(hdrs); if (!l) return js_mknull(); if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name"); char *lower = lowercase_dup(name); if (!lower) return js_mkerr(js, "out of memory"); if (strcmp(lower, "set-cookie") == 0) { for (hdr_entry_t *e = l->head; e; e = e->next) { if (strcmp(e->name, lower) == 0) { ant_value_t ret = js_mkstr(js, e->value, strlen(e->value)); free(lower); return ret; }} free(lower); return js_mknull(); } size_t total = 0; int count = 0; for (hdr_entry_t *e = l->head; e; e = e->next) { if (strcmp(e->name, lower) == 0) { if (count > 0) total += 2; total += strlen(e->value); count++; }} if (count == 0) { free(lower); return js_mknull(); } char *combined = malloc(total + 1); if (!combined) { free(lower); return js_mkerr(js, "out of memory"); } size_t pos = 0; int seen = 0; for (hdr_entry_t *e = l->head; e; e = e->next) { if (strcmp(e->name, lower) == 0) { if (seen > 0) { combined[pos++] = ','; combined[pos++] = ' '; } size_t vl = strlen(e->value); memcpy(combined + pos, e->value, vl); pos += vl; seen++; }} combined[pos] = '\0'; free(lower); ant_value_t ret = js_mkstr(js, combined, pos); free(combined); return ret; } void init_headers_module(void) { ant_t *js = rt->js; ant_value_t g = js_glob(js); g_headers_iter_proto = js_mkobj(js); js_set_proto_init(g_headers_iter_proto, js->sym.iterator_proto); js_set(js, g_headers_iter_proto, "next", js_mkfun(headers_iter_next)); js_set_descriptor(js, g_headers_iter_proto, "next", 4, JS_DESC_W | JS_DESC_E | JS_DESC_C); js_set_sym(js, g_headers_iter_proto, get_iterator_sym(), js_mkfun(sym_this_cb)); js_iter_register_advance(g_headers_iter_proto, advance_headers); g_headers_proto = js_mkobj(js); js_set(js, g_headers_proto, "append", js_mkfun(js_headers_append)); js_set(js, g_headers_proto, "set", js_mkfun(js_headers_set)); js_set(js, g_headers_proto, "get", js_mkfun(js_headers_get)); js_set(js, g_headers_proto, "has", js_mkfun(js_headers_has)); js_set(js, g_headers_proto, "delete", js_mkfun(js_headers_delete)); js_set(js, g_headers_proto, "forEach", js_mkfun(js_headers_for_each)); js_set(js, g_headers_proto, "keys", js_mkfun(js_headers_keys)); js_set(js, g_headers_proto, "values", js_mkfun(js_headers_values)); js_set(js, g_headers_proto, "entries", js_mkfun(js_headers_entries)); js_set(js, g_headers_proto, "getSetCookie", js_mkfun(js_headers_get_set_cookie)); js_set_sym(js, g_headers_proto, get_iterator_sym(), js_get(js, g_headers_proto, "entries")); js_set_sym(js, g_headers_proto, get_inspect_sym(), js_mkfun(headers_inspect)); js_set_sym(js, g_headers_proto, get_toStringTag_sym(), js_mkstr(js, "Headers", 7)); ant_value_t ctor_obj = js_mkobj(js); js_set_slot(ctor_obj, SLOT_CFUNC, js_mkfun(js_headers_ctor)); js_mkprop_fast(js, ctor_obj, "prototype", 9, g_headers_proto); js_mkprop_fast(js, ctor_obj, "name", 4, js_mkstr(js, "Headers", 7)); js_set_descriptor(js, ctor_obj, "name", 4, 0); ant_value_t ctor = js_obj_to_func(ctor_obj); js_set(js, g_headers_proto, "constructor", ctor); js_set_descriptor(js, g_headers_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); js_set(js, g, "Headers", ctor); js_set_descriptor(js, g, "Headers", 7, JS_DESC_W | JS_DESC_C); }