MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add Response constructor

+1330 -47
+70
examples/spec/response.js
··· 1 + import { test, testThrows, summary } from './helpers.js'; 2 + 3 + console.log('Response Tests\n'); 4 + 5 + test('Response typeof', typeof Response, 'function'); 6 + test('Response toStringTag', Object.prototype.toString.call(new Response()), '[object Response]'); 7 + testThrows('Response requires new', () => Response()); 8 + 9 + const res0 = new Response(); 10 + test('Response type default', res0.type, 'default'); 11 + test('Response url default', res0.url, ''); 12 + test('Response redirected default', res0.redirected, false); 13 + test('Response status default', res0.status, 200); 14 + test('Response ok default', res0.ok, true); 15 + test('Response statusText default', res0.statusText, ''); 16 + test('Response body default', res0.body, null); 17 + test('Response headers same object', res0.headers === res0.headers, true); 18 + 19 + const res1 = new Response('hello', { 20 + status: 201, 21 + statusText: 'Created', 22 + headers: { 'X-Test': 'ok' } 23 + }); 24 + test('Response init status', res1.status, 201); 25 + test('Response init statusText', res1.statusText, 'Created'); 26 + test('Response init header', res1.headers.get('x-test'), 'ok'); 27 + test('Response init text', await res1.text(), 'hello'); 28 + 29 + testThrows('Response invalid status low', () => new Response('', { status: 199 })); 30 + testThrows('Response invalid status high', () => new Response('', { status: 600 })); 31 + testThrows('Response invalid statusText newline', () => new Response('', { statusText: '\n' })); 32 + testThrows('Response invalid statusText non-ByteString', () => new Response('', { statusText: 'ฤ€' })); 33 + testThrows('Response null body status rejects body', () => new Response('x', { status: 204 })); 34 + 35 + const streamRes = new Response('stream me'); 36 + const bodyStream = streamRes.body; 37 + test('Response body stream stable', streamRes.body === bodyStream, true); 38 + test('Response bodyUsed before consume', streamRes.bodyUsed, false); 39 + test('Response text after body access', await streamRes.text(), 'stream me'); 40 + test('Response bodyUsed after consume', streamRes.bodyUsed, true); 41 + 42 + const cloned = new Response('clone me', { 43 + status: 202, 44 + statusText: 'Accepted', 45 + headers: { 'X-One': '1' } 46 + }).clone(); 47 + test('Response clone status', cloned.status, 202); 48 + test('Response clone statusText', cloned.statusText, 'Accepted'); 49 + test('Response clone header', cloned.headers.get('x-one'), '1'); 50 + test('Response clone text', await cloned.text(), 'clone me'); 51 + 52 + const jsonRes = Response.json({ hello: 'world' }); 53 + test('Response.json content-type', jsonRes.headers.get('content-type'), 'application/json'); 54 + test('Response.json body', (await jsonRes.json()).hello, 'world'); 55 + testThrows('Response.json rejects symbol', () => Response.json(Symbol('x'))); 56 + testThrows('Response.json null body status rejects', () => Response.json('x', { status: 204 })); 57 + 58 + const redirectRes = Response.redirect('https://example.com/next', 301); 59 + test('Response.redirect status', redirectRes.status, 301); 60 + test('Response.redirect location', redirectRes.headers.get('location'), 'https://example.com/next'); 61 + test('Response.redirect ok false', redirectRes.ok, false); 62 + testThrows('Response.redirect bad status', () => Response.redirect('https://example.com/', 200)); 63 + 64 + const errorRes = Response.error(); 65 + test('Response.error type', errorRes.type, 'error'); 66 + test('Response.error status', errorRes.status, 0); 67 + test('Response.error body null', errorRes.body, null); 68 + testThrows('Response.error headers immutable', () => errorRes.headers.append('x-test', '1')); 69 + 70 + summary();
+3
include/common.h
··· 76 76 SLOT_REQUEST_HEADERS, 77 77 SLOT_REQUEST_SIGNAL, 78 78 SLOT_REQUEST_BODY_STREAM, 79 + SLOT_RESPONSE_HEADERS, 80 + SLOT_RESPONSE_BODY_STREAM, 79 81 SLOT_MAX = 255 80 82 } internal_slot_t; 81 83 ··· 93 95 BRAND_URLSEARCHPARAMS, 94 96 BRAND_DATAVIEW, 95 97 BRAND_REQUEST, 98 + BRAND_RESPONSE, 96 99 BRAND_READABLE_STREAM, 97 100 BRAND_READABLE_STREAM_READER, 98 101 BRAND_READABLE_STREAM_CONTROLLER,
+7 -2
include/modules/headers.h
··· 10 10 typedef enum { 11 11 HEADERS_GUARD_NONE = 0, 12 12 HEADERS_GUARD_REQUEST, 13 - HEADERS_GUARD_REQUEST_NO_CORS 13 + HEADERS_GUARD_REQUEST_NO_CORS, 14 + HEADERS_GUARD_RESPONSE, 15 + HEADERS_GUARD_IMMUTABLE 14 16 } headers_guard_t; 15 17 16 18 headers_guard_t headers_get_guard(ant_value_t hdrs); 17 19 18 20 void init_headers_module(void); 21 + void headers_apply_guard(ant_value_t hdrs); 19 22 void headers_set_guard(ant_value_t hdrs, headers_guard_t guard); 20 - void headers_apply_guard(ant_value_t hdrs); 21 23 void headers_append_if_missing(ant_value_t hdrs, const char *name, const char *value); 22 24 23 25 bool headers_is_headers(ant_value_t obj); 24 26 bool headers_copy_from(ant_t *js, ant_value_t dst, ant_value_t src); 25 27 bool advance_headers(ant_t *js, struct js_iter_t *it, ant_value_t *out); 28 + bool headers_init_has_name(ant_t *js, ant_value_t init, const char *name); 29 + bool headers_set_literal(ant_t *js, ant_value_t hdrs, const char *name, const char *value); 26 30 27 31 ant_value_t headers_create_empty(ant_t *js); 32 + ant_value_t headers_create_from_init(ant_t *js, ant_value_t init); 28 33 ant_value_t headers_get_value(ant_t *js, ant_value_t hdrs, const char *name); 29 34 ant_value_t headers_append_value(ant_t *js, ant_value_t hdrs, ant_value_t name_v, ant_value_t value_v); 30 35
+7 -2
include/modules/request.h
··· 36 36 ant_value_t request_get_signal(ant_value_t obj); 37 37 38 38 ant_value_t request_create( 39 - ant_t *js, const char *method, const char *url, ant_value_t headers, 40 - const uint8_t *body, size_t body_len, const char *body_type 39 + ant_t *js, 40 + const char *method, 41 + const char *url, 42 + ant_value_t headers, 43 + const uint8_t *body, 44 + size_t body_len, 45 + const char *body_type 41 46 ); 42 47 43 48 #endif
+45
include/modules/response.h
··· 1 + #ifndef RESPONSE_H 2 + #define RESPONSE_H 3 + 4 + #include <stdbool.h> 5 + #include <stddef.h> 6 + #include <stdint.h> 7 + #include "types.h" 8 + #include "modules/headers.h" 9 + #include "modules/url.h" 10 + 11 + typedef struct { 12 + char *type; 13 + url_state_t url; 14 + bool has_url; 15 + int url_list_size; 16 + int status; 17 + char *status_text; 18 + uint8_t *body_data; 19 + size_t body_size; 20 + char *body_type; 21 + bool body_is_stream; 22 + bool has_body; 23 + bool body_used; 24 + } response_data_t; 25 + 26 + extern ant_value_t g_response_proto; 27 + 28 + void init_response_module(void); 29 + 30 + response_data_t *response_get_data(ant_value_t obj); 31 + ant_value_t response_get_headers(ant_value_t obj); 32 + 33 + ant_value_t response_create( 34 + ant_t *js, 35 + const char *type, 36 + int status, 37 + const char *status_text, 38 + ant_value_t headers_obj, 39 + const uint8_t *body, 40 + size_t body_len, 41 + const char *body_type, 42 + headers_guard_t guard 43 + ); 44 + 45 + #endif
+2
src/main.c
··· 40 40 #include "modules/json.h" 41 41 #include "modules/fetch.h" 42 42 #include "modules/request.h" 43 + #include "modules/response.h" 43 44 #include "modules/shell.h" 44 45 #include "modules/process.h" 45 46 #include "modules/tty.h" ··· 614 615 init_atomics_module(); 615 616 init_crypto_module(); 616 617 init_request_module(); 618 + init_response_module(); 617 619 init_fetch_module(); 618 620 init_console_module(); 619 621 init_json_module();
+156 -1
src/modules/headers.c
··· 163 163 { "sec-", true }, 164 164 }; 165 165 166 + static const header_rule_t k_forbidden_response_headers[] = { 167 + { "set-cookie", false }, 168 + { "set-cookie2", false }, 169 + }; 170 + 166 171 static const char *k_cors_safelisted_content_types[] = { 167 172 "application/x-www-form-urlencoded", 168 173 "multipart/form-data", ··· 194 199 static bool is_forbidden_request_header_name(const char *lower_name) { 195 200 return matches_rule(lower_name, k_forbidden_request_headers, 196 201 sizeof(k_forbidden_request_headers) / sizeof(k_forbidden_request_headers[0])); 202 + } 203 + 204 + static bool is_forbidden_response_header_name(const char *lower_name) { 205 + return matches_rule(lower_name, k_forbidden_response_headers, 206 + sizeof(k_forbidden_response_headers) / sizeof(k_forbidden_response_headers[0])); 197 207 } 198 208 199 209 static bool is_cors_safelisted_content_type_value(const char *value) { ··· 240 250 241 251 static bool header_allowed_for_guard(const char *lower_name, const char *value, headers_guard_t guard) { 242 252 if (guard == HEADERS_GUARD_NONE) return true; 253 + if (guard == HEADERS_GUARD_IMMUTABLE) return true; 243 254 if (is_forbidden_request_header_name(lower_name)) return false; 255 + if (guard == HEADERS_GUARD_RESPONSE) return !is_forbidden_response_header_name(lower_name); 244 256 if (guard == HEADERS_GUARD_REQUEST_NO_CORS) return is_no_cors_safelisted_name_value(lower_name, value); 245 257 return true; 246 258 } 247 259 260 + static ant_value_t headers_guard_error(ant_t *js, headers_guard_t guard) { 261 + if (guard != HEADERS_GUARD_IMMUTABLE) return js_mkundef(); 262 + return js_mkerr_typed(js, JS_ERR_TYPE, "Headers are immutable"); 263 + } 264 + 248 265 static void list_apply_guard(hdr_list_t *l, headers_guard_t guard) { 249 - if (!l || guard == HEADERS_GUARD_NONE) return; 266 + if (!l || guard == HEADERS_GUARD_NONE || guard == HEADERS_GUARD_IMMUTABLE) return; 250 267 251 268 hdr_entry_t **pp = &l->head; 252 269 l->tail = &l->head; ··· 491 508 if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.append requires 2 arguments"); 492 509 hdr_list_t *l = get_list(js->this_val); 493 510 if (!l) return js_mkerr(js, "Invalid Headers object"); 511 + ant_value_t guard_err = headers_guard_error(js, get_guard(js->this_val)); 512 + if (is_err(guard_err)) return guard_err; 494 513 ant_value_t r = headers_append_pair(js, l, args[0], args[1]); 495 514 if (is_err(r)) return r; 496 515 list_apply_guard(l, get_guard(js->this_val)); ··· 501 520 if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.set requires 2 arguments"); 502 521 hdr_list_t *l = get_list(js->this_val); 503 522 if (!l) return js_mkerr(js, "Invalid Headers object"); 523 + ant_value_t guard_err = headers_guard_error(js, get_guard(js->this_val)); 524 + if (is_err(guard_err)) return guard_err; 504 525 505 526 ant_value_t name_v = args[0]; 506 527 ant_value_t value_v = args[1]; ··· 612 633 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Headers.delete requires 1 argument"); 613 634 hdr_list_t *l = get_list(js->this_val); 614 635 if (!l) return js_mkundef(); 636 + ant_value_t guard_err = headers_guard_error(js, get_guard(js->this_val)); 637 + if (is_err(guard_err)) return guard_err; 615 638 616 639 ant_value_t name_v = args[0]; 617 640 if (vtype(name_v) != T_STR) { name_v = js_tostring_val(js, name_v); if (is_err(name_v)) return name_v; } ··· 769 792 } 770 793 list_append_raw(l, lower, value); 771 794 free(lower); 795 + } 796 + 797 + bool headers_set_literal(ant_t *js, ant_value_t hdrs, const char *name, const char *value) { 798 + hdr_list_t *l = get_list(hdrs); 799 + headers_guard_t guard = 0; 800 + 801 + char *norm = NULL; 802 + char *lower = NULL; 803 + 804 + if (!l || !name || !value) return false; 805 + if (!is_valid_name(name)) return false; 806 + 807 + norm = normalize_value(value); 808 + if (!norm) return false; 809 + if (!is_valid_value(norm)) { 810 + free(norm); 811 + return false; 812 + } 813 + 814 + lower = lowercase_dup(name); 815 + if (!lower) { 816 + free(norm); 817 + return false; 818 + } 819 + 820 + guard = get_guard(hdrs); 821 + if (guard == HEADERS_GUARD_IMMUTABLE) { 822 + free(lower); 823 + free(norm); 824 + return false; 825 + } 826 + 827 + list_delete_name(l, lower); 828 + if (header_allowed_for_guard(lower, norm, guard)) list_append_raw(l, lower, norm); 829 + free(lower); 830 + free(norm); 831 + 832 + return true; 833 + } 834 + 835 + ant_value_t headers_create_from_init(ant_t *js, ant_value_t init) { 836 + ant_value_t new_hdrs = 0; 837 + uint8_t ht = vtype(init); 838 + 839 + new_hdrs = headers_create_empty(js); 840 + if (is_err(new_hdrs)) return new_hdrs; 841 + if (ht == T_UNDEF) return new_hdrs; 842 + 843 + if (headers_is_headers(init)) { 844 + headers_copy_from(js, new_hdrs, init); 845 + return new_hdrs; 846 + } 847 + 848 + if (ht == T_ARR) { 849 + ant_offset_t len = js_arr_len(js, init); 850 + for (ant_offset_t i = 0; i < len; i++) { 851 + ant_value_t pair = js_arr_get(js, init, i); 852 + ant_value_t r = 0; 853 + if (js_arr_len(js, pair) < 2) continue; 854 + r = headers_append_value(js, new_hdrs, js_arr_get(js, pair, 0), js_arr_get(js, pair, 1)); 855 + if (is_err(r)) return r; 856 + } 857 + return new_hdrs; 858 + } 859 + 860 + if (ht == T_OBJ) { 861 + ant_iter_t it = js_prop_iter_begin(js, init); 862 + const char *key = NULL; 863 + size_t key_len = 0; 864 + ant_value_t val = 0; 865 + 866 + while (js_prop_iter_next(&it, &key, &key_len, &val)) { 867 + ant_value_t r = headers_append_value(js, new_hdrs, js_mkstr(js, key, key_len), val); 868 + if (is_err(r)) { 869 + js_prop_iter_end(&it); 870 + return r; 871 + } 872 + } 873 + 874 + js_prop_iter_end(&it); 875 + } 876 + 877 + return new_hdrs; 878 + } 879 + 880 + bool headers_init_has_name(ant_t *js, ant_value_t init, const char *name) { 881 + uint8_t ht = vtype(init); 882 + 883 + if (ht == T_UNDEF) return false; 884 + if (headers_is_headers(init)) { 885 + ant_value_t value = headers_get_value(js, init, name); 886 + return !is_err(value) && vtype(value) != T_NULL; 887 + } 888 + 889 + if (ht == T_ARR) { 890 + ant_offset_t len = js_arr_len(js, init); 891 + for (ant_offset_t i = 0; i < len; i++) { 892 + ant_value_t pair = js_arr_get(js, init, i); 893 + ant_value_t key_v = 0; 894 + const char *key = NULL; 895 + if (js_arr_len(js, pair) < 1) continue; 896 + key_v = js_arr_get(js, pair, 0); 897 + if (vtype(key_v) != T_STR) { 898 + key_v = js_tostring_val(js, key_v); 899 + if (is_err(key_v)) continue; 900 + } 901 + key = js_getstr(js, key_v, NULL); 902 + if (key && strcasecmp(key, name) == 0) return true; 903 + } 904 + return false; 905 + } 906 + 907 + if (ht == T_OBJ) { 908 + ant_iter_t it = js_prop_iter_begin(js, init); 909 + const char *key = NULL; 910 + size_t key_len = 0; 911 + ant_value_t value = 0; 912 + bool found = false; 913 + 914 + while (js_prop_iter_next(&it, &key, &key_len, &value)) { 915 + (void)value; 916 + if (key && strcasecmp(key, name) == 0) { 917 + found = true; 918 + break; 919 + } 920 + } 921 + 922 + js_prop_iter_end(&it); 923 + return found; 924 + } 925 + 926 + return false; 772 927 } 773 928 774 929 ant_value_t headers_get_value(ant_t *js, ant_value_t hdrs, const char *name) {
+1 -42
src/modules/request.c
··· 1018 1018 1019 1019 static ant_value_t request_apply_init_headers(ant_t *js, ant_value_t init, ant_value_t headers) { 1020 1020 ant_value_t init_headers = js_get(js, init, "headers"); 1021 - ant_value_t new_hdrs = 0; 1022 - 1023 - uint8_t ht = vtype(init_headers); 1024 1021 if (vtype(init_headers) == T_UNDEF) return headers; 1025 - 1026 - new_hdrs = headers_create_empty(js); 1027 - if (is_err(new_hdrs)) return new_hdrs; 1028 - 1029 - if (headers_is_headers(init_headers)) { 1030 - headers_copy_from(js, new_hdrs, init_headers); 1031 - return new_hdrs; 1032 - } 1033 - 1034 - if (ht == T_ARR) { 1035 - ant_offset_t len = js_arr_len(js, init_headers); 1036 - for (ant_offset_t i = 0; i < len; i++) { 1037 - ant_value_t pair = js_arr_get(js, init_headers, i); 1038 - ant_value_t r = 0; 1039 - if (js_arr_len(js, pair) < 2) continue; 1040 - r = headers_append_value(js, new_hdrs, js_arr_get(js, pair, 0), js_arr_get(js, pair, 1)); 1041 - if (is_err(r)) return r; 1042 - } 1043 - return new_hdrs; 1044 - } 1045 - 1046 - if (ht == T_OBJ) { 1047 - ant_iter_t it = js_prop_iter_begin(js, init_headers); 1048 - const char *key = NULL; 1049 - 1050 - size_t key_len = 0; 1051 - ant_value_t val = 0; 1052 - 1053 - while (js_prop_iter_next(&it, &key, &key_len, &val)) { 1054 - ant_value_t r = headers_append_value(js, new_hdrs, js_mkstr(js, key, key_len), val); 1055 - if (is_err(r)) { 1056 - js_prop_iter_end(&it); 1057 - return r; 1058 - }} 1059 - 1060 - js_prop_iter_end(&it); 1061 - } 1062 - 1063 - return new_hdrs; 1022 + return headers_create_from_init(js, init_headers); 1064 1023 } 1065 1024 1066 1025 static ant_value_t request_parse_duplex(ant_t *js, ant_value_t init, bool *out_duplex_provided) {
+1039
src/modules/response.c
··· 1 + #include <stdlib.h> 2 + #include <string.h> 3 + #include <strings.h> 4 + #include <ctype.h> 5 + #include <stdio.h> 6 + 7 + #include "ant.h" 8 + #include "errors.h" 9 + #include "runtime.h" 10 + #include "internal.h" 11 + #include "common.h" 12 + #include "descriptors.h" 13 + #include "utf8.h" 14 + 15 + #include "modules/assert.h" 16 + #include "modules/blob.h" 17 + #include "modules/buffer.h" 18 + #include "modules/formdata.h" 19 + #include "modules/headers.h" 20 + #include "modules/multipart.h" 21 + #include "modules/response.h" 22 + #include "modules/symbol.h" 23 + #include "modules/url.h" 24 + #include "modules/json.h" 25 + #include "streams/pipes.h" 26 + #include "streams/readable.h" 27 + 28 + ant_value_t g_response_proto = 0; 29 + 30 + static response_data_t *get_data(ant_value_t obj) { 31 + ant_value_t slot = js_get_slot(obj, SLOT_DATA); 32 + if (vtype(slot) != T_NUM) return NULL; 33 + return (response_data_t *)(uintptr_t)(size_t)js_getnum(slot); 34 + } 35 + 36 + response_data_t *response_get_data(ant_value_t obj) { 37 + return get_data(obj); 38 + } 39 + 40 + ant_value_t response_get_headers(ant_value_t obj) { 41 + return js_get_slot(obj, SLOT_RESPONSE_HEADERS); 42 + } 43 + 44 + static void data_free(response_data_t *d) { 45 + if (!d) return; 46 + free(d->type); 47 + url_state_clear(&d->url); 48 + free(d->status_text); 49 + free(d->body_data); 50 + free(d->body_type); 51 + free(d); 52 + } 53 + 54 + static response_data_t *data_new(void) { 55 + response_data_t *d = calloc(1, sizeof(response_data_t)); 56 + if (!d) return NULL; 57 + d->type = strdup("default"); 58 + d->status = 200; 59 + d->status_text = strdup(""); 60 + d->url_list_size = 0; 61 + if (!d->type || !d->status_text) { 62 + data_free(d); 63 + return NULL; 64 + } 65 + return d; 66 + } 67 + 68 + static response_data_t *data_dup(const response_data_t *src) { 69 + response_data_t *d = calloc(1, sizeof(response_data_t)); 70 + url_state_t *su = NULL; 71 + url_state_t *du = NULL; 72 + 73 + if (!d) return NULL; 74 + d->type = src->type ? strdup(src->type) : NULL; 75 + d->status_text = src->status_text ? strdup(src->status_text) : NULL; 76 + d->has_url = src->has_url; 77 + d->url_list_size = src->url_list_size; 78 + d->status = src->status; 79 + d->body_is_stream = src->body_is_stream; 80 + d->has_body = src->has_body; 81 + d->body_used = src->body_used; 82 + d->body_size = src->body_size; 83 + d->body_type = src->body_type ? strdup(src->body_type) : NULL; 84 + 85 + su = (url_state_t *)&src->url; 86 + du = &d->url; 87 + #define DUP_US(f) do { du->f = su->f ? strdup(su->f) : NULL; } while (0) 88 + DUP_US(protocol); 89 + DUP_US(username); 90 + DUP_US(password); 91 + DUP_US(hostname); 92 + DUP_US(port); 93 + DUP_US(pathname); 94 + DUP_US(search); 95 + DUP_US(hash); 96 + #undef DUP_US 97 + 98 + if (src->body_data && src->body_size > 0) { 99 + d->body_data = malloc(src->body_size); 100 + if (!d->body_data) { 101 + data_free(d); 102 + return NULL; 103 + } 104 + memcpy(d->body_data, src->body_data, src->body_size); 105 + } 106 + 107 + return d; 108 + } 109 + 110 + static ant_value_t response_rejection_reason(ant_t *js, ant_value_t value) { 111 + if (!is_err(value)) return value; 112 + ant_value_t reason = js->thrown_exists ? js->thrown_value : value; 113 + js->thrown_exists = false; 114 + js->thrown_value = js_mkundef(); 115 + js->thrown_stack = js_mkundef(); 116 + return reason; 117 + } 118 + 119 + static bool copy_body_bytes( 120 + ant_t *js, const uint8_t *src, size_t src_len, 121 + uint8_t **out_data, size_t *out_size, ant_value_t *err_out 122 + ) { 123 + uint8_t *buf = NULL; 124 + 125 + *out_data = NULL; 126 + *out_size = 0; 127 + if (src_len == 0) return true; 128 + 129 + buf = malloc(src_len); 130 + if (!buf) { 131 + *err_out = js_mkerr(js, "out of memory"); 132 + return false; 133 + } 134 + 135 + memcpy(buf, src, src_len); 136 + *out_data = buf; 137 + *out_size = src_len; 138 + return true; 139 + } 140 + 141 + static bool extract_buffer_source_body( 142 + ant_t *js, ant_value_t body_val, 143 + uint8_t **out_data, size_t *out_size, ant_value_t *err_out 144 + ) { 145 + const uint8_t *src = NULL; 146 + size_t src_len = 0; 147 + 148 + if (!(( 149 + vtype(body_val) == T_TYPEDARRAY || vtype(body_val) == T_OBJ) && 150 + buffer_source_get_bytes(js, body_val, &src, &src_len)) 151 + ) return false; 152 + 153 + return copy_body_bytes(js, src, src_len, out_data, out_size, err_out); 154 + } 155 + 156 + static bool extract_stream_body( 157 + ant_t *js, ant_value_t body_val, 158 + ant_value_t *out_stream, ant_value_t *err_out 159 + ) { 160 + if (!rs_is_stream(body_val)) return false; 161 + if (rs_stream_unusable(body_val)) { 162 + *err_out = js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked"); 163 + return false; 164 + } 165 + *out_stream = body_val; 166 + return true; 167 + } 168 + 169 + static bool extract_blob_body( 170 + ant_t *js, ant_value_t body_val, 171 + uint8_t **out_data, size_t *out_size, char **out_type, ant_value_t *err_out 172 + ) { 173 + blob_data_t *bd = blob_is_blob(js, body_val) ? blob_get_data(body_val) : NULL; 174 + if (!bd) return false; 175 + if (!copy_body_bytes(js, bd->data, bd->size, out_data, out_size, err_out)) return false; 176 + if (bd->type && bd->type[0]) *out_type = strdup(bd->type); 177 + return true; 178 + } 179 + 180 + static bool extract_urlsearchparams_body( 181 + ant_t *js, ant_value_t body_val, 182 + uint8_t **out_data, size_t *out_size, char **out_type 183 + ) { 184 + char *serialized = NULL; 185 + if (!usp_is_urlsearchparams(js, body_val)) return false; 186 + serialized = usp_serialize(js, body_val); 187 + if (!serialized) return true; 188 + 189 + *out_data = (uint8_t *)serialized; 190 + *out_size = strlen(serialized); 191 + *out_type = strdup("application/x-www-form-urlencoded;charset=UTF-8"); 192 + 193 + return true; 194 + } 195 + 196 + static bool extract_formdata_body( 197 + ant_t *js, ant_value_t body_val, 198 + uint8_t **out_data, size_t *out_size, char **out_type, ant_value_t *err_out 199 + ) { 200 + char *boundary = NULL; 201 + char *content_type = NULL; 202 + size_t mp_size = 0; 203 + uint8_t *mp = NULL; 204 + 205 + if (!formdata_is_formdata(js, body_val)) return false; 206 + mp = formdata_serialize_multipart(js, body_val, &mp_size, &boundary); 207 + if (!mp) { 208 + *err_out = js_mkerr(js, "out of memory"); 209 + return false; 210 + } 211 + 212 + if (mp_size > 0) *out_data = mp; 213 + else free(mp); 214 + *out_size = mp_size; 215 + 216 + if (!boundary) return true; 217 + 218 + size_t ct_len = snprintf(NULL, 0, "multipart/form-data; boundary=%s", boundary); 219 + content_type = malloc(ct_len + 1); 220 + if (!content_type) { 221 + free(boundary); 222 + if (mp_size > 0) free(mp); 223 + *out_data = NULL; 224 + *out_size = 0; 225 + *err_out = js_mkerr(js, "out of memory"); 226 + return false; 227 + } 228 + 229 + snprintf(content_type, ct_len + 1, "multipart/form-data; boundary=%s", boundary); 230 + free(boundary); 231 + *out_type = content_type; 232 + return true; 233 + } 234 + 235 + static bool extract_string_body( 236 + ant_t *js, ant_value_t body_val, 237 + uint8_t **out_data, size_t *out_size, char **out_type, ant_value_t *err_out 238 + ) { 239 + size_t len = 0; 240 + const char *s = NULL; 241 + 242 + if (vtype(body_val) != T_STR) { 243 + body_val = js_tostring_val(js, body_val); 244 + if (is_err(body_val)) { 245 + *err_out = body_val; 246 + return false; 247 + }} 248 + 249 + s = js_getstr(js, body_val, &len); 250 + if (!copy_body_bytes(js, (const uint8_t *)s, len, out_data, out_size, err_out)) return false; 251 + *out_type = strdup("text/plain;charset=UTF-8"); 252 + 253 + return true; 254 + } 255 + 256 + static bool extract_body( 257 + ant_t *js, ant_value_t body_val, 258 + uint8_t **out_data, size_t *out_size, char **out_type, 259 + ant_value_t *out_stream, ant_value_t *err_out 260 + ) { 261 + *out_data = NULL; 262 + *out_size = 0; 263 + *out_type = NULL; 264 + *out_stream = js_mkundef(); 265 + *err_out = js_mkundef(); 266 + 267 + if (vtype(body_val) == T_NULL || vtype(body_val) == T_UNDEF) return true; 268 + if (extract_buffer_source_body(js, body_val, out_data, out_size, err_out)) return true; 269 + if (vtype(body_val) == T_OBJ && rs_is_stream(body_val)) return extract_stream_body(js, body_val, out_stream, err_out); 270 + if (vtype(body_val) == T_OBJ && extract_blob_body(js, body_val, out_data, out_size, out_type, err_out)) return true; 271 + if (vtype(body_val) == T_OBJ && extract_urlsearchparams_body(js, body_val, out_data, out_size, out_type)) return true; 272 + if (vtype(body_val) == T_OBJ && extract_formdata_body(js, body_val, out_data, out_size, out_type, err_out)) return true; 273 + 274 + return extract_string_body(js, body_val, out_data, out_size, out_type, err_out); 275 + } 276 + 277 + enum { 278 + BODY_TEXT = 0, 279 + BODY_JSON, 280 + BODY_ARRAYBUFFER, 281 + BODY_BLOB, 282 + BODY_BYTES, 283 + BODY_FORMDATA 284 + }; 285 + 286 + static const char *response_effective_body_type(ant_t *js, ant_value_t resp_obj, response_data_t *d) { 287 + ant_value_t headers = js_get_slot(resp_obj, SLOT_RESPONSE_HEADERS); 288 + if (!headers_is_headers(headers)) return d ? d->body_type : NULL; 289 + ant_value_t ct = headers_get_value(js, headers, "content-type"); 290 + if (vtype(ct) == T_STR) return js_getstr(js, ct, NULL); 291 + return d ? d->body_type : NULL; 292 + } 293 + 294 + static void resolve_body_promise( 295 + ant_t *js, ant_value_t promise, 296 + const uint8_t *data, size_t size, 297 + const char *body_type, int mode, bool has_body 298 + ) { 299 + switch (mode) { 300 + case BODY_TEXT: { 301 + ant_value_t str = (data && size > 0) ? js_mkstr(js, (const char *)data, size) : js_mkstr(js, "", 0); 302 + js_resolve_promise(js, promise, str); 303 + break; 304 + } 305 + case BODY_JSON: { 306 + ant_value_t str = (data && size > 0) ? js_mkstr(js, (const char *)data, size) : js_mkstr(js, "", 0); 307 + ant_value_t parsed = js_json_parse(js, &str, 1); 308 + if (is_err(parsed)) js_reject_promise(js, promise, response_rejection_reason(js, parsed)); 309 + else js_resolve_promise(js, promise, parsed); 310 + break; 311 + } 312 + case BODY_ARRAYBUFFER: { 313 + ArrayBufferData *ab = create_array_buffer_data(size); 314 + if (!ab) { 315 + js_reject_promise(js, promise, js_mkerr(js, "out of memory")); 316 + break; 317 + } 318 + if (data && size > 0) memcpy(ab->data, data, size); 319 + js_resolve_promise(js, promise, create_arraybuffer_obj(js, ab)); 320 + break; 321 + } 322 + case BODY_BLOB: { 323 + const char *type = body_type ? body_type : ""; 324 + js_resolve_promise(js, promise, blob_create(js, data, size, type)); 325 + break; 326 + } 327 + case BODY_BYTES: { 328 + ArrayBufferData *ab = create_array_buffer_data(size); 329 + if (!ab) { 330 + js_reject_promise(js, promise, js_mkerr(js, "out of memory")); 331 + break; 332 + } 333 + if (data && size > 0) memcpy(ab->data, data, size); 334 + js_resolve_promise(js, promise, 335 + create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, size, "Uint8Array")); 336 + break; 337 + } 338 + case BODY_FORMDATA: { 339 + ant_value_t fd = formdata_parse_body(js, data, size, body_type, has_body); 340 + if (is_err(fd)) js_reject_promise(js, promise, response_rejection_reason(js, fd)); 341 + else js_resolve_promise(js, promise, fd); 342 + break; 343 + }} 344 + } 345 + 346 + static uint8_t *concat_chunks(ant_t *js, ant_value_t chunks, size_t *out_size) { 347 + ant_offset_t n = js_arr_len(js, chunks); 348 + size_t total = 0; 349 + size_t pos = 0; 350 + uint8_t *buf = NULL; 351 + 352 + for (ant_offset_t i = 0; i < n; i++) { 353 + ant_value_t chunk = js_arr_get(js, chunks, i); 354 + if (vtype(chunk) != T_TYPEDARRAY) continue; 355 + TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(chunk); 356 + if (ta && ta->buffer && !ta->buffer->is_detached) total += ta->byte_length; 357 + } 358 + 359 + buf = total > 0 ? malloc(total) : NULL; 360 + if (total > 0 && !buf) return NULL; 361 + 362 + for (ant_offset_t i = 0; i < n; i++) { 363 + ant_value_t chunk = js_arr_get(js, chunks, i); 364 + if (vtype(chunk) != T_TYPEDARRAY) continue; 365 + TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(chunk); 366 + if (!ta || !ta->buffer || ta->buffer->is_detached || ta->byte_length == 0) continue; 367 + memcpy(buf + pos, ta->buffer->data + ta->byte_offset, ta->byte_length); 368 + pos += ta->byte_length; 369 + } 370 + 371 + *out_size = pos; 372 + return buf; 373 + } 374 + 375 + static ant_value_t stream_body_read(ant_t *js, ant_value_t *args, int nargs); 376 + static ant_value_t stream_body_rejected(ant_t *js, ant_value_t *args, int nargs) { 377 + ant_value_t state = js_get_slot(js->current_func, SLOT_DATA); 378 + ant_value_t promise = js_get(js, state, "promise"); 379 + ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef(); 380 + js_reject_promise(js, promise, reason); 381 + return js_mkundef(); 382 + } 383 + 384 + static void stream_schedule_next_read(ant_t *js, ant_value_t state, ant_value_t reader) { 385 + ant_value_t next_p = rs_default_reader_read(js, reader); 386 + ant_value_t fulfill = js_heavy_mkfun(js, stream_body_read, state); 387 + ant_value_t reject = js_heavy_mkfun(js, stream_body_rejected, state); 388 + ant_value_t then_result = js_promise_then(js, next_p, fulfill, reject); 389 + promise_mark_handled(then_result); 390 + } 391 + 392 + static ant_value_t stream_body_read(ant_t *js, ant_value_t *args, int nargs) { 393 + ant_value_t state = js_get_slot(js->current_func, SLOT_DATA); 394 + ant_value_t result = (nargs > 0) ? args[0] : js_mkundef(); 395 + ant_value_t promise = js_get(js, state, "promise"); 396 + ant_value_t reader = js_get(js, state, "reader"); 397 + ant_value_t chunks = js_get(js, state, "chunks"); 398 + int mode = (int)js_getnum(js_get(js, state, "mode")); 399 + 400 + ant_value_t done_val = js_get(js, result, "done"); 401 + ant_value_t value = js_get(js, result, "value"); 402 + 403 + if (vtype(done_val) == T_BOOL && done_val == js_true) { 404 + size_t size = 0; 405 + uint8_t *data = concat_chunks(js, chunks, &size); 406 + ant_value_t type_v = js_get(js, state, "type"); 407 + const char *body_type = (vtype(type_v) == T_STR) ? js_getstr(js, type_v, NULL) : NULL; 408 + resolve_body_promise(js, promise, data, size, body_type, mode, true); 409 + free(data); 410 + return js_mkundef(); 411 + } 412 + 413 + if (vtype(value) != T_UNDEF && vtype(value) != T_NULL) js_arr_push(js, chunks, value); 414 + stream_schedule_next_read(js, state, reader); 415 + return js_mkundef(); 416 + } 417 + 418 + static ant_value_t consume_body_from_stream( 419 + ant_t *js, ant_value_t stream, 420 + ant_value_t promise, int mode, 421 + const char *body_type 422 + ) { 423 + ant_value_t reader_args[1] = { stream }; 424 + ant_value_t saved = js->new_target; 425 + 426 + js->new_target = g_reader_proto; 427 + ant_value_t reader = js_rs_reader_ctor(js, reader_args, 1); 428 + js->new_target = saved; 429 + 430 + if (is_err(reader)) { 431 + js_reject_promise(js, promise, reader); 432 + return promise; 433 + } 434 + 435 + ant_value_t state = js_mkobj(js); 436 + js_set(js, state, "promise", promise); 437 + js_set(js, state, "reader", reader); 438 + js_set(js, state, "chunks", js_mkarr(js)); 439 + js_set(js, state, "mode", js_mknum(mode)); 440 + js_set(js, state, "type", body_type ? js_mkstr(js, body_type, strlen(body_type)) : js_mkundef()); 441 + 442 + stream_schedule_next_read(js, state, reader); 443 + return promise; 444 + } 445 + 446 + static ant_value_t consume_body(ant_t *js, int mode) { 447 + ant_value_t this = js_getthis(js); 448 + response_data_t *d = get_data(this); 449 + ant_value_t promise = js_mkpromise(js); 450 + ant_value_t stream = 0; 451 + 452 + if (!d) { 453 + js_reject_promise(js, promise, response_rejection_reason(js, 454 + js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Response object"))); 455 + return promise; 456 + } 457 + 458 + if (!d->has_body) { 459 + resolve_body_promise(js, promise, NULL, 0, response_effective_body_type(js, this, d), mode, false); 460 + return promise; 461 + } 462 + 463 + stream = js_get_slot(this, SLOT_RESPONSE_BODY_STREAM); 464 + if (d->body_used || (d->body_is_stream && rs_is_stream(stream) && rs_stream_unusable(stream))) { 465 + js_reject_promise(js, promise, response_rejection_reason(js, 466 + js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked"))); 467 + return promise; 468 + } 469 + 470 + d->body_used = true; 471 + if (rs_is_stream(stream) && d->body_is_stream) 472 + return consume_body_from_stream(js, stream, promise, mode, response_effective_body_type(js, this, d)); 473 + 474 + resolve_body_promise(js, promise, d->body_data, d->body_size, response_effective_body_type(js, this, d), mode, true); 475 + return promise; 476 + } 477 + 478 + static ant_value_t js_res_text(ant_t *js, ant_value_t *args, int nargs) { 479 + return consume_body(js, BODY_TEXT); 480 + } 481 + 482 + static ant_value_t js_res_json(ant_t *js, ant_value_t *args, int nargs) { 483 + return consume_body(js, BODY_JSON); 484 + } 485 + 486 + static ant_value_t js_res_array_buffer(ant_t *js, ant_value_t *args, int nargs) { 487 + return consume_body(js, BODY_ARRAYBUFFER); 488 + } 489 + 490 + static ant_value_t js_res_blob(ant_t *js, ant_value_t *args, int nargs) { 491 + return consume_body(js, BODY_BLOB); 492 + } 493 + 494 + static ant_value_t js_res_bytes(ant_t *js, ant_value_t *args, int nargs) { 495 + return consume_body(js, BODY_BYTES); 496 + } 497 + 498 + static ant_value_t js_res_form_data(ant_t *js, ant_value_t *args, int nargs) { 499 + return consume_body(js, BODY_FORMDATA); 500 + } 501 + 502 + static bool is_null_body_status(int status) { 503 + return status == 101 || status == 103 || status == 204 || status == 205 || status == 304; 504 + } 505 + 506 + static bool is_redirect_status(int status) { 507 + return status == 301 || status == 302 || status == 303 || status == 307 || status == 308; 508 + } 509 + 510 + static bool is_ok_status(int status) { 511 + return status >= 200 && status <= 299; 512 + } 513 + 514 + static bool is_valid_reason_phrase(const char *str, size_t len) { 515 + utf8proc_int32_t cp = 0; 516 + utf8proc_ssize_t n = 0; 517 + size_t pos = 0; 518 + 519 + while (pos < len) { 520 + n = utf8_next((const utf8proc_uint8_t *)(str + pos), (utf8proc_ssize_t)(len - pos), &cp); 521 + if (cp > 0xFF) return false; 522 + if (cp == '\r' || cp == '\n') return false; 523 + pos += (size_t)n; 524 + } 525 + 526 + return true; 527 + } 528 + 529 + static ant_value_t response_init_status(ant_t *js, ant_value_t init, response_data_t *resp) { 530 + ant_value_t status_v = js_get(js, init, "status"); 531 + double status_num = 200; 532 + 533 + if (vtype(status_v) != T_UNDEF) { 534 + status_num = (vtype(status_v) == T_NUM) ? js_getnum(status_v) : js_to_number(js, status_v); 535 + } 536 + 537 + if (status_num < 200 || status_num > 599 || status_num != (int)status_num) { 538 + return js_mkerr_typed(js, JS_ERR_RANGE, "Failed to construct 'Response': status must be in the range 200-599"); 539 + } 540 + 541 + resp->status = (int)status_num; 542 + return js_mkundef(); 543 + } 544 + 545 + static ant_value_t response_init_status_text(ant_t *js, ant_value_t init, response_data_t *resp) { 546 + ant_value_t status_text_v = js_get(js, init, "statusText"); 547 + size_t len = 0; 548 + const char *status_text = NULL; 549 + char *dup = NULL; 550 + 551 + if (vtype(status_text_v) == T_UNDEF) return js_mkundef(); 552 + if (vtype(status_text_v) != T_STR) { 553 + status_text_v = js_tostring_val(js, status_text_v); 554 + if (is_err(status_text_v)) return status_text_v; 555 + } 556 + 557 + status_text = js_getstr(js, status_text_v, &len); 558 + if (!is_valid_reason_phrase(status_text, len)) { 559 + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Response': Invalid statusText"); 560 + } 561 + 562 + dup = strdup(status_text); 563 + if (!dup) return js_mkerr(js, "out of memory"); 564 + free(resp->status_text); 565 + resp->status_text = dup; 566 + return js_mkundef(); 567 + } 568 + 569 + static ant_value_t response_apply_body( 570 + ant_t *js, ant_value_t resp_obj, ant_value_t headers, response_data_t *resp, 571 + ant_value_t body_val 572 + ) { 573 + ant_value_t body_err = js_mkundef(); 574 + ant_value_t body_stream = js_mkundef(); 575 + uint8_t *body_data = NULL; 576 + size_t body_size = 0; 577 + char *body_type = NULL; 578 + 579 + ant_value_t current_type = js_mknull(); 580 + 581 + if (vtype(body_val) == T_NULL || vtype(body_val) == T_UNDEF) return js_mkundef(); 582 + 583 + if (!extract_body(js, body_val, &body_data, &body_size, &body_type, &body_stream, &body_err)) { 584 + return is_err(body_err) ? body_err : js_mkerr(js, "Failed to extract body"); 585 + } 586 + 587 + if (is_null_body_status(resp->status)) { 588 + free(body_data); 589 + free(body_type); 590 + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Response': Response with null body status cannot have body"); 591 + } 592 + 593 + free(resp->body_data); 594 + free(resp->body_type); 595 + resp->body_data = body_data; 596 + resp->body_size = body_size; 597 + resp->body_type = body_type; 598 + resp->body_is_stream = rs_is_stream(body_stream); 599 + resp->has_body = true; 600 + 601 + if (resp->body_is_stream) js_set_slot_wb(js, resp_obj, SLOT_RESPONSE_BODY_STREAM, body_stream); 602 + current_type = headers_get_value(js, headers, "content-type"); 603 + if (body_type && !is_err(current_type) && vtype(current_type) == T_NULL) 604 + headers_append_if_missing(headers, "content-type", body_type); 605 + 606 + return js_mkundef(); 607 + } 608 + 609 + static ant_value_t response_init_common( 610 + ant_t *js, ant_value_t resp_obj, ant_value_t init, 611 + ant_value_t body_val, headers_guard_t guard 612 + ) { 613 + response_data_t *resp = get_data(resp_obj); 614 + ant_value_t headers = js_get_slot(resp_obj, SLOT_RESPONSE_HEADERS); 615 + ant_value_t step = js_mkundef(); 616 + 617 + if (!resp) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Response object"); 618 + if (vtype(init) != T_UNDEF) { 619 + ant_value_t init_headers = js_get(js, init, "headers"); 620 + step = response_init_status(js, init, resp); 621 + if (is_err(step)) return step; 622 + step = response_init_status_text(js, init, resp); 623 + if (is_err(step)) return step; 624 + if (vtype(init_headers) != T_UNDEF) { 625 + headers = headers_create_from_init(js, init_headers); 626 + if (is_err(headers)) return headers; 627 + } 628 + headers_set_guard(headers, guard); 629 + headers_apply_guard(headers); 630 + js_set_slot_wb(js, resp_obj, SLOT_RESPONSE_HEADERS, headers); 631 + } 632 + 633 + return response_apply_body(js, resp_obj, headers, resp, body_val); 634 + } 635 + 636 + static ant_value_t response_new(headers_guard_t guard) { 637 + ant_t *js = rt->js; 638 + response_data_t *resp = data_new(); 639 + ant_value_t obj = 0; 640 + ant_value_t headers = 0; 641 + 642 + if (!resp) return js_mkerr(js, "out of memory"); 643 + obj = js_mkobj(js); 644 + js_set_proto_init(obj, g_response_proto); 645 + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_RESPONSE)); 646 + js_set_slot(obj, SLOT_DATA, ANT_PTR(resp)); 647 + 648 + headers = headers_create_empty(js); 649 + if (is_err(headers)) { 650 + data_free(resp); 651 + return headers; 652 + } 653 + 654 + headers_set_guard(headers, guard); 655 + headers_apply_guard(headers); 656 + js_set_slot_wb(js, obj, SLOT_RESPONSE_HEADERS, headers); 657 + js_set_slot_wb(js, obj, SLOT_RESPONSE_BODY_STREAM, js_mkundef()); 658 + return obj; 659 + } 660 + 661 + static ant_value_t js_response_ctor(ant_t *js, ant_value_t *args, int nargs) { 662 + ant_value_t body = (nargs >= 1) ? args[0] : js_mknull(); 663 + ant_value_t init = (nargs >= 2 && vtype(args[1]) != T_UNDEF) ? args[1] : js_mkundef(); 664 + ant_value_t obj = 0; 665 + ant_value_t proto = 0; 666 + ant_value_t step = 0; 667 + 668 + if (vtype(js->new_target) == T_UNDEF) { 669 + return js_mkerr_typed(js, JS_ERR_TYPE, "Response constructor requires 'new'"); 670 + } 671 + 672 + obj = response_new(HEADERS_GUARD_RESPONSE); 673 + if (is_err(obj)) return obj; 674 + 675 + proto = js_instance_proto_from_new_target(js, g_response_proto); 676 + if (is_object_type(proto)) js_set_proto_init(obj, proto); 677 + 678 + step = response_init_common(js, obj, init, body, HEADERS_GUARD_RESPONSE); 679 + if (is_err(step)) { 680 + data_free(get_data(obj)); 681 + return step; 682 + } 683 + 684 + return obj; 685 + } 686 + 687 + static ant_value_t response_create_static( 688 + ant_t *js, const char *type, int status, const char *status_text, headers_guard_t guard 689 + ) { 690 + ant_value_t obj = response_new(guard); 691 + response_data_t *resp = NULL; 692 + 693 + if (is_err(obj)) return obj; 694 + resp = get_data(obj); 695 + 696 + free(resp->type); 697 + resp->type = strdup(type ? type : "default"); 698 + if (!resp->type) { 699 + data_free(resp); 700 + return js_mkerr(js, "out of memory"); 701 + } 702 + 703 + resp->status = status; 704 + free(resp->status_text); 705 + resp->status_text = strdup(status_text ? status_text : ""); 706 + if (!resp->status_text) { 707 + data_free(resp); 708 + return js_mkerr(js, "out of memory"); 709 + } 710 + 711 + return obj; 712 + } 713 + 714 + static ant_value_t js_response_error(ant_t *js, ant_value_t *args, int nargs) { 715 + (void)args; 716 + (void)nargs; 717 + ant_value_t obj = response_create_static(js, "error", 0, "", HEADERS_GUARD_IMMUTABLE); 718 + if (is_err(obj)) return obj; 719 + return obj; 720 + } 721 + 722 + static ant_value_t js_response_redirect(ant_t *js, ant_value_t *args, int nargs) { 723 + ant_value_t url_v = (nargs >= 1) ? args[0] : js_mkundef(); 724 + ant_value_t status_v = (nargs >= 2) ? args[1] : js_mknum(302); 725 + ant_value_t obj = 0; 726 + ant_value_t headers = 0; 727 + const char *url_str = NULL; 728 + int status = 302; 729 + url_state_t parsed = {0}; 730 + char *href = NULL; 731 + 732 + if (vtype(url_v) != T_STR) { 733 + url_v = js_tostring_val(js, url_v); 734 + if (is_err(url_v)) return url_v; 735 + } 736 + 737 + status = (vtype(status_v) == T_NUM) ? (int)js_getnum(status_v) : (int)js_to_number(js, status_v); 738 + if (!is_redirect_status(status)) { 739 + return js_mkerr_typed(js, JS_ERR_RANGE, "Response.redirect status must be 301, 302, 303, 307, or 308"); 740 + } 741 + 742 + url_str = js_getstr(js, url_v, NULL); 743 + if (parse_url_to_state(url_str, NULL, &parsed) != 0) { 744 + url_state_clear(&parsed); 745 + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Response': Invalid URL"); 746 + } 747 + 748 + href = build_href(&parsed); 749 + if (!href) { 750 + url_state_clear(&parsed); 751 + return js_mkerr(js, "out of memory"); 752 + } 753 + 754 + obj = response_create_static(js, "default", status, "", HEADERS_GUARD_IMMUTABLE); 755 + if (is_err(obj)) { 756 + free(href); 757 + url_state_clear(&parsed); 758 + return obj; 759 + } 760 + 761 + headers = js_get_slot(obj, SLOT_RESPONSE_HEADERS); 762 + headers_set_guard(headers, HEADERS_GUARD_NONE); 763 + headers_append_if_missing(headers, "location", href); 764 + headers_set_guard(headers, HEADERS_GUARD_IMMUTABLE); 765 + headers_apply_guard(headers); 766 + 767 + free(href); 768 + url_state_clear(&parsed); 769 + return obj; 770 + } 771 + 772 + static ant_value_t js_response_json_static(ant_t *js, ant_value_t *args, int nargs) { 773 + ant_value_t init = (nargs >= 2 && vtype(args[1]) != T_UNDEF) ? args[1] : js_mkundef(); 774 + ant_value_t stringify = 0; 775 + ant_value_t obj = 0; 776 + ant_value_t headers = 0; 777 + ant_value_t step = 0; 778 + bool init_has_content_type = false; 779 + 780 + if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Response.json requires 1 argument"); 781 + stringify = js_json_stringify(js, args, 1); 782 + if (is_err(stringify)) return stringify; 783 + if (vtype(stringify) == T_UNDEF) { 784 + return js_mkerr_typed(js, JS_ERR_TYPE, "Response.json data is not JSON serializable"); 785 + } 786 + 787 + init_has_content_type = 788 + vtype(init) != T_UNDEF && 789 + headers_init_has_name(js, js_get(js, init, "headers"), "content-type"); 790 + 791 + obj = response_new(HEADERS_GUARD_RESPONSE); 792 + if (is_err(obj)) return obj; 793 + 794 + step = response_init_common(js, obj, init, stringify, HEADERS_GUARD_RESPONSE); 795 + if (is_err(step)) { 796 + data_free(get_data(obj)); 797 + return step; 798 + } 799 + 800 + headers = js_get_slot(obj, SLOT_RESPONSE_HEADERS); 801 + if (!init_has_content_type) headers_set_literal(js, headers, "content-type", "application/json"); 802 + return obj; 803 + } 804 + 805 + static ant_value_t res_body_pull(ant_t *js, ant_value_t *args, int nargs) { 806 + ant_value_t resp_obj = js_get_slot(js->current_func, SLOT_DATA); 807 + response_data_t *d = get_data(resp_obj); 808 + ant_value_t ctrl = (nargs > 0) ? args[0] : js_mkundef(); 809 + 810 + if (d && d->body_data && d->body_size > 0) { 811 + ArrayBufferData *ab = create_array_buffer_data(d->body_size); 812 + if (ab) { 813 + memcpy(ab->data, d->body_data, d->body_size); 814 + rs_controller_enqueue(js, ctrl, 815 + create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, d->body_size, "Uint8Array")); 816 + } 817 + } 818 + 819 + rs_controller_close(js, ctrl); 820 + return js_mkundef(); 821 + } 822 + 823 + #define RES_GETTER_START(name) \ 824 + static ant_value_t js_res_get_##name(ant_t *js, ant_value_t *args, int nargs) { \ 825 + ant_value_t this = js_getthis(js); \ 826 + response_data_t *d = get_data(this); \ 827 + if (!d) return js_mkundef(); 828 + 829 + #define RES_GETTER_END } 830 + 831 + RES_GETTER_START(type) 832 + const char *type = d->type ? d->type : "default"; 833 + return js_mkstr(js, type, strlen(type)); 834 + RES_GETTER_END 835 + 836 + RES_GETTER_START(url) 837 + char *href = NULL; 838 + char *hash = NULL; 839 + ant_value_t ret = 0; 840 + 841 + if (!d->has_url) return js_mkstr(js, "", 0); 842 + href = build_href(&d->url); 843 + if (!href) return js_mkstr(js, "", 0); 844 + hash = strchr(href, '#'); 845 + if (hash) *hash = '\0'; 846 + ret = js_mkstr(js, href, strlen(href)); 847 + free(href); 848 + return ret; 849 + RES_GETTER_END 850 + 851 + RES_GETTER_START(redirected) 852 + return js_bool(d->url_list_size > 1); 853 + RES_GETTER_END 854 + 855 + RES_GETTER_START(status) 856 + return js_mknum(d->status); 857 + RES_GETTER_END 858 + 859 + RES_GETTER_START(ok) 860 + return js_bool(is_ok_status(d->status)); 861 + RES_GETTER_END 862 + 863 + RES_GETTER_START(status_text) 864 + const char *status_text = d->status_text ? d->status_text : ""; 865 + return js_mkstr(js, status_text, strlen(status_text)); 866 + RES_GETTER_END 867 + 868 + RES_GETTER_START(headers) 869 + return js_get_slot(this, SLOT_RESPONSE_HEADERS); 870 + RES_GETTER_END 871 + 872 + RES_GETTER_START(body) 873 + ant_value_t stored_stream = js_get_slot(this, SLOT_RESPONSE_BODY_STREAM); 874 + if (!d->has_body) return js_mknull(); 875 + if (rs_is_stream(stored_stream)) return stored_stream; 876 + if (d->body_used) return js_mknull(); 877 + ant_value_t pull = js_heavy_mkfun(js, res_body_pull, this); 878 + ant_value_t stream = rs_create_stream(js, pull, js_mkundef(), 1.0); 879 + if (!is_err(stream)) js_set_slot_wb(js, this, SLOT_RESPONSE_BODY_STREAM, stream); 880 + return stream; 881 + RES_GETTER_END 882 + 883 + RES_GETTER_START(body_used) 884 + ant_value_t stored_stream = js_get_slot(this, SLOT_RESPONSE_BODY_STREAM); 885 + bool used = d->body_used || (d->body_is_stream && rs_is_stream(stored_stream) && rs_stream_disturbed(stored_stream)); 886 + return js_bool(used); 887 + RES_GETTER_END 888 + 889 + #undef RES_GETTER_START 890 + #undef RES_GETTER_END 891 + 892 + static ant_value_t js_response_clone(ant_t *js, ant_value_t *args, int nargs) { 893 + ant_value_t this = js_getthis(js); 894 + response_data_t *d = get_data(this); 895 + response_data_t *nd = NULL; 896 + ant_value_t src_headers = 0; 897 + ant_value_t new_headers = 0; 898 + ant_value_t obj = 0; 899 + ant_value_t src_stream = 0; 900 + 901 + if (!d) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Response object"); 902 + if (d->body_used) { 903 + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot clone a Response whose body is unusable"); 904 + } 905 + 906 + nd = data_dup(d); 907 + if (!nd) return js_mkerr(js, "out of memory"); 908 + 909 + src_headers = js_get_slot(this, SLOT_RESPONSE_HEADERS); 910 + new_headers = headers_create_empty(js); 911 + if (is_err(new_headers)) { 912 + data_free(nd); 913 + return new_headers; 914 + } 915 + 916 + headers_copy_from(js, new_headers, src_headers); 917 + headers_set_guard(new_headers, headers_get_guard(src_headers)); 918 + headers_apply_guard(new_headers); 919 + 920 + obj = js_mkobj(js); 921 + js_set_proto_init(obj, g_response_proto); 922 + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_RESPONSE)); 923 + js_set_slot(obj, SLOT_DATA, ANT_PTR(nd)); 924 + js_set_slot_wb(js, obj, SLOT_RESPONSE_HEADERS, new_headers); 925 + js_set_slot_wb(js, obj, SLOT_RESPONSE_BODY_STREAM, js_mkundef()); 926 + 927 + src_stream = js_get_slot(this, SLOT_RESPONSE_BODY_STREAM); 928 + if (!rs_is_stream(src_stream)) return obj; 929 + 930 + ant_value_t branches = readable_stream_tee(js, src_stream); 931 + if (!is_err(branches) && vtype(branches) == T_ARR) { 932 + ant_value_t b1 = js_arr_get(js, branches, 0); 933 + ant_value_t b2 = js_arr_get(js, branches, 1); 934 + js_set_slot_wb(js, this, SLOT_RESPONSE_BODY_STREAM, b1); 935 + js_set_slot_wb(js, obj, SLOT_RESPONSE_BODY_STREAM, b2); 936 + } 937 + 938 + return obj; 939 + } 940 + 941 + ant_value_t response_create( 942 + ant_t *js, 943 + const char *type, 944 + int status, 945 + const char *status_text, 946 + ant_value_t headers_obj, 947 + const uint8_t *body, 948 + size_t body_len, 949 + const char *body_type, 950 + headers_guard_t guard 951 + ) { 952 + ant_value_t obj = response_new(guard); 953 + ant_value_t headers = 0; 954 + response_data_t *resp = NULL; 955 + 956 + if (is_err(obj)) return obj; 957 + resp = get_data(obj); 958 + 959 + free(resp->type); 960 + resp->type = strdup(type ? type : "default"); 961 + if (!resp->type) { 962 + data_free(resp); 963 + return js_mkerr(js, "out of memory"); 964 + } 965 + 966 + resp->status = status; 967 + free(resp->status_text); 968 + resp->status_text = strdup(status_text ? status_text : ""); 969 + if (!resp->status_text) { 970 + data_free(resp); 971 + return js_mkerr(js, "out of memory"); 972 + } 973 + 974 + if (body_len > 0) { 975 + resp->body_data = malloc(body_len); 976 + if (!resp->body_data) { 977 + data_free(resp); 978 + return js_mkerr(js, "out of memory"); 979 + } 980 + memcpy(resp->body_data, body, body_len); 981 + } 982 + 983 + resp->body_size = body_len; 984 + resp->body_type = body_type ? strdup(body_type) : NULL; 985 + resp->has_body = body || body_len > 0; 986 + resp->body_is_stream = false; 987 + 988 + headers = is_object_type(headers_obj) ? headers_obj : headers_create_empty(js); 989 + if (is_err(headers)) { 990 + data_free(resp); 991 + return headers; 992 + } 993 + 994 + headers_set_guard(headers, guard); 995 + headers_apply_guard(headers); 996 + ant_value_t current_type = headers_get_value(js, headers, "content-type"); 997 + if (body_type && !is_err(current_type) && vtype(current_type) == T_NULL) { 998 + headers_append_if_missing(headers, "content-type", body_type); 999 + } 1000 + js_set_slot_wb(js, obj, SLOT_RESPONSE_HEADERS, headers); 1001 + return obj; 1002 + } 1003 + 1004 + void init_response_module(void) { 1005 + ant_t *js = rt->js; 1006 + ant_value_t g = js_glob(js); 1007 + ant_value_t ctor = 0; 1008 + 1009 + g_response_proto = js_mkobj(js); 1010 + 1011 + js_set(js, g_response_proto, "text", js_mkfun(js_res_text)); 1012 + js_set(js, g_response_proto, "json", js_mkfun(js_res_json)); 1013 + js_set(js, g_response_proto, "arrayBuffer", js_mkfun(js_res_array_buffer)); 1014 + js_set(js, g_response_proto, "blob", js_mkfun(js_res_blob)); 1015 + js_set(js, g_response_proto, "formData", js_mkfun(js_res_form_data)); 1016 + js_set(js, g_response_proto, "bytes", js_mkfun(js_res_bytes)); 1017 + js_set(js, g_response_proto, "clone", js_mkfun(js_response_clone)); 1018 + 1019 + #define GETTER(prop, fn) \ 1020 + js_set_getter_desc(js, g_response_proto, prop, sizeof(prop) - 1, js_mkfun(js_res_get_##fn), JS_DESC_C) 1021 + GETTER("type", type); 1022 + GETTER("url", url); 1023 + GETTER("redirected", redirected); 1024 + GETTER("status", status); 1025 + GETTER("ok", ok); 1026 + GETTER("statusText", status_text); 1027 + GETTER("headers", headers); 1028 + GETTER("body", body); 1029 + GETTER("bodyUsed", body_used); 1030 + #undef GETTER 1031 + 1032 + js_set_sym(js, g_response_proto, get_toStringTag_sym(), js_mkstr(js, "Response", 8)); 1033 + ctor = js_make_ctor(js, js_response_ctor, g_response_proto, "Response", 8); 1034 + js_set(js, ctor, "error", js_mkfun(js_response_error)); 1035 + js_set(js, ctor, "redirect", js_mkfun(js_response_redirect)); 1036 + js_set(js, ctor, "json", js_mkfun(js_response_json_static)); 1037 + js_set(js, g, "Response", ctor); 1038 + js_set_descriptor(js, g, "Response", 8, JS_DESC_W | JS_DESC_C); 1039 + }