diff --git a/examples/spec/formdata.js b/examples/spec/formdata.js index 0398408..fba2580 100644 --- a/examples/spec/formdata.js +++ b/examples/spec/formdata.js @@ -117,4 +117,24 @@ console.log('\ntoStringTag\n'); test('toStringTag', Object.prototype.toString.call(new FormData()), '[object FormData]'); +console.log('\nmultipart parameter parsing\n'); + +const boundary = 'spec-boundary'; +const multipartBody = + `--${boundary}\r\n` + + `Content-Disposition: form-data; name="field"\r\n\r\n` + + `value\r\n` + + `--${boundary}--\r\n`; + +const multipartReq = new Request('https://example.com/', { + method: 'POST', + headers: { + 'Content-Type': `multipart/form-data; boundaryx=wrong; boundary="${boundary}"`, + }, + body: multipartBody, +}); + +const multipartFd = await multipartReq.formData(); +test('multipart boundary lookup skips longer prefix match', multipartFd.get('field'), 'value'); + summary(); diff --git a/examples/spec/request.js b/examples/spec/request.js new file mode 100644 index 0000000..ba2d60c --- /dev/null +++ b/examples/spec/request.js @@ -0,0 +1,132 @@ +import { test, testThrows, summary } from './helpers.js'; + +console.log('Request Tests\n'); + +test('Request typeof', typeof Request, 'function'); +test('Request toStringTag', Object.prototype.toString.call(new Request('https://example.com/')), '[object Request]'); +testThrows('Request requires new', () => Request('https://example.com/')); + +const req0 = new Request('https://example.com/'); +test('Request method default', req0.method, 'GET'); +test('Request url', req0.url, 'https://example.com/'); +test('Request headers same object', req0.headers === req0.headers, true); +test('Request keepalive default', req0.keepalive, false); +test('Request duplex getter', req0.duplex, 'half'); + +const hdrReq = new Request('https://example.com/', { + headers: { + 'Accept-Charset': 'blocked', + 'X-Custom': 'ok', + }, +}); +test('Request filters forbidden headers', hdrReq.headers.get('accept-charset'), null); +test('Request keeps allowed headers', hdrReq.headers.get('x-custom'), 'ok'); + +const noCorsReq = new Request('https://example.com/', { + mode: 'no-cors', + headers: { + 'Content-Type': 'text/plain;charset=UTF-8', + 'X-Blocked': 'nope', + }, +}); +test('Request no-cors keeps safelisted content-type', noCorsReq.headers.get('content-type'), 'text/plain;charset=UTF-8'); +test('Request no-cors strips non-safelisted header', noCorsReq.headers.get('x-blocked'), null); + +testThrows( + 'Request forbids keepalive with stream body', + () => new Request('https://example.com/', { + method: 'POST', + body: new ReadableStream(), + duplex: 'half', + keepalive: true, + }) +); + +testThrows( + 'Request requires duplex for stream body', + () => new Request('https://example.com/', { + method: 'POST', + body: new ReadableStream(), + }) +); + +testThrows( + 'Request rejects duplex full', + () => new Request('https://example.com/', { + method: 'POST', + body: 'hello', + duplex: 'full', + }) +); + +testThrows( + 'Request rejects forbidden method', + () => new Request('https://example.com/', { method: 'TRACE' }) +); + +testThrows( + 'Request rejects GET with direct body', + () => new Request('https://example.com/', { method: 'GET', body: 'hello' }) +); + +const bodyReq = new Request('https://example.com/', { + method: 'POST', + body: 'hello world', +}); +const bodyStream = bodyReq.body; +test('Request body is a ReadableStream', bodyStream instanceof ReadableStream, true); +test('Request body getter is stable', bodyReq.body === bodyStream, true); +test('Request bodyUsed before consume', bodyReq.bodyUsed, false); +test('Request text after body access', await bodyReq.text(), 'hello world'); +test('Request bodyUsed after consume', bodyReq.bodyUsed, true); +test('Request body object preserved after consume', bodyReq.body === bodyStream, true); + +const srcReq = new Request('https://example.com/', { + method: 'POST', + body: 'copied body', +}); +const srcBody = srcReq.body; +const copiedReq = new Request(srcReq); +test('Request-from-Request disturbs source', srcReq.bodyUsed, true); +test('Request-from-Request keeps source body object', srcReq.body === srcBody, true); +test('Request-from-Request creates new body object', copiedReq.body === srcBody, false); +test('Request-from-Request text', await copiedReq.text(), 'copied body'); + +const failReq = new Request('https://example.com/', { + method: 'POST', + body: 'will stay unused', +}); +testThrows( + 'Request GET from Request with body throws', + () => new Request(failReq, { method: 'GET' }) +); +test('Request failed construction does not disturb source', failReq.bodyUsed, false); + +const usedReq = new Request('https://example.com/', { + method: 'POST', + body: 'replace me', +}); +await usedReq.text(); +const replacedReq = new Request(usedReq, { + body: 'replacement body', + method: 'POST', +}); +test('Request override from disturbed request succeeds', await replacedReq.text(), 'replacement body'); + +const initReq = new Request('https://example.com/', { + method: 'POST', + body: 'init body', +}); +const reqFromInitReq = new Request('https://example.com/', initReq); +test('Request init from Request copies method', reqFromInitReq.method, 'POST'); +test('Request init from Request copies body', await reqFromInitReq.text(), 'init body'); + +const emptyTypeReq = new Request('https://example.com/', { + method: 'POST', + headers: [['Content-Type', '']], + body: 'typed body', +}); +const emptyTypeBlob = await emptyTypeReq.blob(); +test('Request empty content-type preserved on blob', emptyTypeBlob.type, ''); + +summary(); diff --git a/include/ant.h b/include/ant.h index bb4540a..e3c6541 100644 --- a/include/ant.h +++ b/include/ant.h @@ -173,6 +173,8 @@ void js_set_keys(ant_value_t obj, js_keys_fn keys); void js_set_finalizer(ant_value_t obj, js_finalizer_fn fn); ant_value_t js_get_slot(ant_value_t obj, internal_slot_t slot); +ant_value_t js_promise_then(ant_t *js, ant_value_t promise, ant_value_t on_fulfilled, ant_value_t on_rejected); + void js_set_slot(ant_value_t obj, internal_slot_t slot, ant_value_t value); void js_set_slot_wb(ant_t *, ant_value_t obj, internal_slot_t slot, ant_value_t value); diff --git a/include/common.h b/include/common.h index 17cd8cd..d6474b8 100644 --- a/include/common.h +++ b/include/common.h @@ -30,6 +30,7 @@ typedef enum { SLOT_PRIMITIVE, SLOT_PROXY_REF, SLOT_BUILTIN, + SLOT_BRAND, SLOT_DATA, SLOT_CTOR, SLOT_DEFAULT, @@ -71,6 +72,10 @@ typedef enum { SLOT_WS_ABORT, SLOT_WS_READY, SLOT_WS_SIGNAL, + SLOT_HEADERS_GUARD, + SLOT_REQUEST_HEADERS, + SLOT_REQUEST_SIGNAL, + SLOT_REQUEST_BODY_STREAM, SLOT_MAX = 255 } internal_slot_t; @@ -79,6 +84,25 @@ typedef enum { BUILTIN_OBJECT = 1 } builtin_fn_id_t; +typedef enum { + BRAND_NONE = 0, + BRAND_BLOB, + BRAND_FILE, + BRAND_HEADERS, + BRAND_FORMDATA, + BRAND_URLSEARCHPARAMS, + BRAND_DATAVIEW, + BRAND_REQUEST, + BRAND_READABLE_STREAM, + BRAND_READABLE_STREAM_READER, + BRAND_READABLE_STREAM_CONTROLLER, + BRAND_WRITABLE_STREAM, + BRAND_WRITABLE_STREAM_WRITER, + BRAND_WRITABLE_STREAM_CONTROLLER, + BRAND_TRANSFORM_STREAM, + BRAND_TRANSFORM_STREAM_CONTROLLER +} object_brand_id_t; + static inline void *mantissa_chk(void *p, const char *func) { if (!p || ((uintptr_t)p >> 47) == 0) goto ok; diff --git a/include/modules/abort.h b/include/modules/abort.h index 735633c..703d888 100644 --- a/include/modules/abort.h +++ b/include/modules/abort.h @@ -12,6 +12,8 @@ void abort_signal_add_listener(ant_t *js, ant_value_t signal, ant_value_t callba bool abort_signal_is_signal(ant_value_t signal); bool abort_signal_is_aborted(ant_value_t signal); + ant_value_t abort_signal_get_reason(ant_value_t signal); +ant_value_t abort_signal_create_dependent(ant_t *js, ant_value_t source); #endif diff --git a/include/modules/blob.h b/include/modules/blob.h index 49d9d56..6cf16ed 100644 --- a/include/modules/blob.h +++ b/include/modules/blob.h @@ -14,9 +14,10 @@ typedef struct { } blob_data_t; void init_blob_module(void); +bool blob_is_blob(ant_t *js, ant_value_t obj); blob_data_t *blob_get_data(ant_value_t obj); -ant_value_t blob_create(ant_t *js, const uint8_t *data, size_t size, const char *type); +ant_value_t blob_create(ant_t *js, const uint8_t *data, size_t size, const char *type); extern ant_value_t g_blob_proto; extern ant_value_t g_file_proto; diff --git a/include/modules/buffer.h b/include/modules/buffer.h index 90bd591..12120f1 100644 --- a/include/modules/buffer.h +++ b/include/modules/buffer.h @@ -42,10 +42,11 @@ typedef struct { size_t byte_length; } DataViewData; +ant_value_t buffer_library(ant_t *js); + void init_buffer_module(void); void cleanup_buffer_module(void); void free_array_buffer_data(ArrayBufferData *data); -size_t buffer_get_external_memory(void); ArrayBufferData *create_array_buffer_data(size_t length); ant_value_t create_arraybuffer_obj(ant_t *js, ArrayBufferData *buffer); @@ -61,6 +62,8 @@ ant_value_t create_typed_array_with_buffer( const char *type_name, ant_value_t arraybuffer_obj ); -ant_value_t buffer_library(ant_t *js); +size_t buffer_get_external_memory(void); +bool buffer_is_dataview(ant_value_t obj); +bool buffer_source_get_bytes(ant_t *js, ant_value_t value, const uint8_t **out, size_t *len); #endif diff --git a/include/modules/formdata.h b/include/modules/formdata.h index f92d4a7..e6c8621 100644 --- a/include/modules/formdata.h +++ b/include/modules/formdata.h @@ -1,6 +1,31 @@ #ifndef FORMDATA_H #define FORMDATA_H +#include +#include +#include +#include "types.h" + +typedef struct fd_entry { + char *name; + bool is_file; + char *str_value; + size_t val_idx; + struct fd_entry *next; +} fd_entry_t; + +typedef struct { + fd_entry_t *head; + fd_entry_t **tail; + size_t count; +} fd_data_t; + void init_formdata_module(void); +bool formdata_is_empty(ant_value_t fd); +bool formdata_is_formdata(ant_t *js, ant_value_t obj); + +ant_value_t formdata_create_empty(ant_t *js); +ant_value_t formdata_append_string(ant_t *js, ant_value_t fd, ant_value_t name_v, ant_value_t value_v); +ant_value_t formdata_append_file(ant_t *js, ant_value_t fd, ant_value_t name_v, ant_value_t blob_v, ant_value_t filename_v); #endif diff --git a/include/modules/headers.h b/include/modules/headers.h index 2637e6a..c75631e 100644 --- a/include/modules/headers.h +++ b/include/modules/headers.h @@ -5,8 +5,27 @@ #include "modules/symbol.h" extern ant_value_t g_headers_iter_proto; +extern ant_value_t g_headers_proto; + +typedef enum { + HEADERS_GUARD_NONE = 0, + HEADERS_GUARD_REQUEST, + HEADERS_GUARD_REQUEST_NO_CORS +} headers_guard_t; + +headers_guard_t headers_get_guard(ant_value_t hdrs); void init_headers_module(void); +void headers_set_guard(ant_value_t hdrs, headers_guard_t guard); +void headers_apply_guard(ant_value_t hdrs); +void headers_append_if_missing(ant_value_t hdrs, const char *name, const char *value); + +bool headers_is_headers(ant_value_t obj); +bool headers_copy_from(ant_t *js, ant_value_t dst, ant_value_t src); bool advance_headers(ant_t *js, struct js_iter_t *it, ant_value_t *out); +ant_value_t headers_create_empty(ant_t *js); +ant_value_t headers_get_value(ant_t *js, ant_value_t hdrs, const char *name); +ant_value_t headers_append_value(ant_t *js, ant_value_t hdrs, ant_value_t name_v, ant_value_t value_v); + #endif diff --git a/include/modules/multipart.h b/include/modules/multipart.h new file mode 100644 index 0000000..fa4c1b1 --- /dev/null +++ b/include/modules/multipart.h @@ -0,0 +1,19 @@ +#ifndef MULTIPART_H +#define MULTIPART_H + +#include +#include +#include +#include "types.h" + +uint8_t *formdata_serialize_multipart( + ant_t *js, ant_value_t fd, + size_t *out_size, char **out_boundary +); + +ant_value_t formdata_parse_body( + ant_t *js, const uint8_t *data, size_t size, + const char *body_type, bool has_body +); + +#endif diff --git a/include/modules/request.h b/include/modules/request.h new file mode 100644 index 0000000..b7451d3 --- /dev/null +++ b/include/modules/request.h @@ -0,0 +1,43 @@ +#ifndef REQUEST_H +#define REQUEST_H + +#include +#include +#include +#include "types.h" +#include "modules/url.h" + +typedef struct { + char *method; + url_state_t url; + char *referrer; + char *referrer_policy; + char *mode; + char *credentials; + char *cache; + char *redirect; + char *integrity; + bool keepalive; + bool reload_navigation; + bool history_navigation; + uint8_t *body_data; + size_t body_size; + char *body_type; + bool body_is_stream; + bool has_body; + bool body_used; +} request_data_t; + +extern ant_value_t g_request_proto; +void init_request_module(void); + +request_data_t *request_get_data(ant_value_t obj); +ant_value_t request_get_headers(ant_value_t obj); +ant_value_t request_get_signal(ant_value_t obj); + +ant_value_t request_create( + ant_t *js, const char *method, const char *url, ant_value_t headers, + const uint8_t *body, size_t body_len, const char *body_type +); + +#endif diff --git a/include/modules/url.h b/include/modules/url.h index a69845a..b88ee9f 100644 --- a/include/modules/url.h +++ b/include/modules/url.h @@ -19,6 +19,7 @@ typedef struct { void init_url_module(void); void url_state_clear(url_state_t *s); void url_free_state(url_state_t *s); +bool usp_is_urlsearchparams(ant_t *js, ant_value_t obj); ant_value_t url_library(ant_t *js); url_state_t *url_get_state(ant_value_t obj); @@ -26,7 +27,6 @@ ant_value_t make_url_obj(ant_t *js, url_state_t *s); char *build_href(const url_state_t *s); char *usp_serialize(ant_t *js, ant_value_t usp); - char *form_urlencode(const char *str); char *form_urlencode_n(const char *str, size_t len); char *form_urldecode(const char *str); diff --git a/include/streams/pipes.h b/include/streams/pipes.h index 13e9ac8..377620a 100644 --- a/include/streams/pipes.h +++ b/include/streams/pipes.h @@ -5,6 +5,7 @@ #include void init_pipes_proto(ant_t *js, ant_value_t rs_proto); +ant_value_t readable_stream_tee(ant_t *js, ant_value_t source); ant_value_t readable_stream_pipe_to( ant_t *js, ant_value_t source, ant_value_t dest, diff --git a/include/streams/readable.h b/include/streams/readable.h index b587014..a4b2fb8 100644 --- a/include/streams/readable.h +++ b/include/streams/readable.h @@ -41,6 +41,9 @@ void gc_mark_readable_streams(ant_t *js, void (*mark)(ant_t *, ant_value_t)); bool rs_is_stream(ant_value_t obj); bool rs_is_reader(ant_value_t obj); bool rs_is_controller(ant_value_t obj); +bool rs_stream_locked(ant_value_t stream_obj); +bool rs_stream_disturbed(ant_value_t stream_obj); +bool rs_stream_unusable(ant_value_t stream_obj); rs_stream_t *rs_get_stream(ant_value_t obj); rs_controller_t *rs_get_controller(ant_value_t obj); diff --git a/src/ant.c b/src/ant.c index cbe527e..509677a 100644 --- a/src/ant.c +++ b/src/ant.c @@ -9675,8 +9675,6 @@ static ant_value_t builtin_atob(ant_t *js, ant_value_t *args, int nargs) { static ant_value_t builtin_resolve_internal(ant_t *js, ant_value_t *args, int nargs); static ant_value_t builtin_reject_internal(ant_t *js, ant_value_t *args, int nargs); -static void resolve_promise(ant_t *js, ant_value_t p, ant_value_t val); -static void reject_promise(ant_t *js, ant_value_t p, ant_value_t val); static size_t strpromise(ant_t *js, ant_value_t value, char *buf, size_t len) { uint32_t pid = get_promise_id(js, value); @@ -9756,7 +9754,7 @@ static ant_value_t make_data_cfunc( return func; } -static ant_value_t mkpromise(ant_t *js) { +ant_value_t js_mkpromise(ant_t *js) { ant_value_t obj = mkobj(js, 0); if (is_err(obj)) return obj; if (!get_promise_data(js, mkval(T_PROMISE, vdata(obj)), true)) @@ -9775,6 +9773,17 @@ static ant_value_t mkpromise(ant_t *js) { return mkval(T_PROMISE, vdata(obj)); } +ant_value_t js_promise_then(ant_t *js, ant_value_t promise, ant_value_t on_fulfilled, ant_value_t on_rejected) { + ant_value_t args_then[2] = { on_fulfilled, on_rejected }; + ant_value_t saved_this = js->this_val; + + js->this_val = promise; + ant_value_t result = builtin_promise_then(js, args_then, 2); + js->this_val = saved_this; + + return result; +} + static inline void trigger_handlers(ant_t *js, ant_value_t p) { queue_promise_trigger(p); } @@ -9812,11 +9821,11 @@ void js_process_promise_handlers(ant_t *js, ant_value_t promise) { js->thrown_exists = false; js->thrown_value = js_mkundef(); js->thrown_stack = js_mkundef(); - reject_promise(js, h->nextPromise, reject_val); - } else resolve_promise(js, h->nextPromise, res); + js_reject_promise(js, h->nextPromise, reject_val); + } else js_resolve_promise(js, h->nextPromise, res); } else { - if (state == 1) resolve_promise(js, h->nextPromise, val); - else reject_promise(js, h->nextPromise, val); + if (state == 1) js_resolve_promise(js, h->nextPromise, val); + else js_reject_promise(js, h->nextPromise, val); } } @@ -9825,7 +9834,7 @@ void js_process_promise_handlers(ant_t *js, ant_value_t promise) { gc_unroot_pending_promise(js_obj_ptr(promise)); } -static void resolve_promise(ant_t *js, ant_value_t p, ant_value_t val) { +void js_resolve_promise(ant_t *js, ant_value_t p, ant_value_t val) { GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, p); GC_ROOT_PIN(js, val); @@ -9840,7 +9849,7 @@ static void resolve_promise(ant_t *js, ant_value_t p, ant_value_t val) { if (vdata(js_as_obj(val)) == vdata(js_as_obj(p))) { ant_value_t err = js_mkerr(js, "TypeError: Chaining cycle"); GC_ROOT_PIN(js, err); - reject_promise(js, p, err); + js_reject_promise(js, p, err); GC_ROOT_RESTORE(js, root_mark); return; } @@ -9848,7 +9857,7 @@ static void resolve_promise(ant_t *js, ant_value_t p, ant_value_t val) { ant_value_t res_fn = make_data_cfunc(js, p, builtin_resolve_internal); GC_ROOT_PIN(js, res_fn); if (is_err(res_fn)) { - reject_promise(js, p, res_fn); + js_reject_promise(js, p, res_fn); GC_ROOT_RESTORE(js, root_mark); return; } @@ -9856,7 +9865,7 @@ static void resolve_promise(ant_t *js, ant_value_t p, ant_value_t val) { ant_value_t rej_fn = make_data_cfunc(js, p, builtin_reject_internal); GC_ROOT_PIN(js, rej_fn); if (is_err(rej_fn)) { - reject_promise(js, p, rej_fn); + js_reject_promise(js, p, rej_fn); GC_ROOT_RESTORE(js, root_mark); return; } @@ -9879,7 +9888,7 @@ static void resolve_promise(ant_t *js, ant_value_t p, ant_value_t val) { GC_ROOT_RESTORE(js, root_mark); } -static void reject_promise(ant_t *js, ant_value_t p, ant_value_t val) { +void js_reject_promise(ant_t *js, ant_value_t p, ant_value_t val) { ant_promise_state_t *pd = get_promise_data(js, p, false); if (!pd || pd->state != 0) return; @@ -9895,7 +9904,7 @@ static ant_value_t builtin_resolve_internal(ant_t *js, ant_value_t *args, int na ant_value_t me = js->current_func; ant_value_t p = get_slot(me, SLOT_DATA); if (vtype(p) != T_PROMISE) return js_mkundef(); - resolve_promise(js, p, nargs > 0 ? args[0] : js_mkundef()); + js_resolve_promise(js, p, nargs > 0 ? args[0] : js_mkundef()); return js_mkundef(); } @@ -9903,7 +9912,7 @@ static ant_value_t builtin_reject_internal(ant_t *js, ant_value_t *args, int nar ant_value_t me = js->current_func; ant_value_t p = get_slot(me, SLOT_DATA); if (vtype(p) != T_PROMISE) return js_mkundef(); - reject_promise(js, p, nargs > 0 ? args[0] : js_mkundef()); + js_reject_promise(js, p, nargs > 0 ? args[0] : js_mkundef()); return js_mkundef(); } @@ -9921,7 +9930,7 @@ static ant_value_t builtin_Promise(ant_t *js, ant_value_t *args, int nargs) { ant_value_t executor = args[0]; GC_ROOT_PIN(js, executor); - ant_value_t p = mkpromise(js); + ant_value_t p = js_mkpromise(js); if (is_err(p)) { GC_ROOT_RESTORE(js, root_mark); return p; @@ -9962,15 +9971,15 @@ static ant_value_t builtin_Promise(ant_t *js, ant_value_t *args, int nargs) { static ant_value_t builtin_Promise_resolve(ant_t *js, ant_value_t *args, int nargs) { ant_value_t val = nargs > 0 ? args[0] : js_mkundef(); if (vtype(val) == T_PROMISE) return val; - ant_value_t p = mkpromise(js); - resolve_promise(js, p, val); + ant_value_t p = js_mkpromise(js); + js_resolve_promise(js, p, val); return p; } static ant_value_t builtin_Promise_reject(ant_t *js, ant_value_t *args, int nargs) { ant_value_t val = nargs > 0 ? args[0] : js_mkundef(); - ant_value_t p = mkpromise(js); - reject_promise(js, p, val); + ant_value_t p = js_mkpromise(js); + js_reject_promise(js, p, val); return p; } @@ -10016,7 +10025,7 @@ static ant_value_t builtin_promise_then(ant_t *js, ant_value_t *args, int nargs) return err; } - ant_value_t nextP = mkpromise(js); + ant_value_t nextP = js_mkpromise(js); GC_ROOT_PIN(js, nextP); if ((vtype(species_ctor) == T_FUNC || vtype(species_ctor) == T_CFUNC) && !(vtype(species_ctor) == vtype(promise_ctor) @@ -10218,7 +10227,7 @@ static ant_value_t builtin_Promise_try(ant_t *js, ant_value_t *args, int nargs) static ant_value_t builtin_Promise_withResolvers(ant_t *js, ant_value_t *args, int nargs) { GC_ROOT_SAVE(root_mark, js); - ant_value_t p = mkpromise(js); + ant_value_t p = js_mkpromise(js); GC_ROOT_PIN(js, p); ant_value_t res_fn = make_data_cfunc(js, p, builtin_resolve_internal); @@ -10248,7 +10257,7 @@ static ant_value_t builtin_Promise_withResolvers(ant_t *js, ant_value_t *args, i static ant_value_t mkpromise_with_ctor(ant_t *js, ant_value_t ctor) { GC_ROOT_SAVE(root_mark, js); GC_ROOT_PIN(js, ctor); - ant_value_t p = mkpromise(js); + ant_value_t p = js_mkpromise(js); GC_ROOT_PIN(js, p); if (vtype(ctor) != T_FUNC && vtype(ctor) != T_CFUNC) { GC_ROOT_RESTORE(js, root_mark); @@ -10286,7 +10295,7 @@ static ant_value_t builtin_Promise_all_resolve_handler(ant_t *js, ant_value_t *a if (remaining == 0) { ant_value_t result_promise = get_slot(tracker, SLOT_DATA); - resolve_promise(js, result_promise, mkval(T_ARR, vdata(results))); + js_resolve_promise(js, result_promise, mkval(T_ARR, vdata(results))); } return js_mkundef(); @@ -10298,7 +10307,7 @@ static ant_value_t builtin_Promise_all_reject_handler(ant_t *js, ant_value_t *ar ant_value_t result_promise = get_slot(tracker, SLOT_DATA); ant_value_t reason = nargs > 0 ? args[0] : js_mkundef(); - reject_promise(js, result_promise, reason); + js_reject_promise(js, result_promise, reason); return js_mkundef(); } @@ -10413,7 +10422,7 @@ static ant_value_t builtin_Promise_all(ant_t *js, ant_value_t *args, int nargs) } if (len == 0) { - resolve_promise(js, result_promise, mkval(T_ARR, vdata(results))); + js_resolve_promise(js, result_promise, mkval(T_ARR, vdata(results))); GC_ROOT_RESTORE(js, root_mark); return result_promise; } @@ -10437,7 +10446,7 @@ static iter_action_t promise_race_iter_cb(ant_t *js, ant_value_t value, void *ct GC_ROOT_PIN(js, item); if (vtype(item) != T_PROMISE) { - resolve_promise(js, pctx->result_promise, item); + js_resolve_promise(js, pctx->result_promise, item); pctx->settled = true; GC_ROOT_RESTORE(js, root_mark); return ITER_BREAK; @@ -10445,18 +10454,17 @@ static iter_action_t promise_race_iter_cb(ant_t *js, ant_value_t value, void *ct ant_promise_state_t *pd = get_promise_data(js, item, false); if (pd) { - if (pd->state == 1) { - resolve_promise(js, pctx->result_promise, pd->value); - pctx->settled = true; - GC_ROOT_RESTORE(js, root_mark); - return ITER_BREAK; - } else if (pd->state == 2) { - reject_promise(js, pctx->result_promise, pd->value); - pctx->settled = true; - GC_ROOT_RESTORE(js, root_mark); - return ITER_BREAK; - } - } + if (pd->state == 1) { + js_resolve_promise(js, pctx->result_promise, pd->value); + pctx->settled = true; + GC_ROOT_RESTORE(js, root_mark); + return ITER_BREAK; + } else if (pd->state == 2) { + js_reject_promise(js, pctx->result_promise, pd->value); + pctx->settled = true; + GC_ROOT_RESTORE(js, root_mark); + return ITER_BREAK; + }} ant_value_t then_args[] = { pctx->resolve_fn, pctx->reject_fn }; ant_value_t saved_this = js->this_val; @@ -10543,7 +10551,7 @@ static ant_value_t mk_aggregate_error(ant_t *js, ant_value_t errors) { static bool promise_any_try_resolve(ant_t *js, ant_value_t tracker, ant_value_t value) { if (js_truthy(js, js_get(js, tracker, "resolved"))) return false; js_set(js, tracker, "resolved", js_true); - resolve_promise(js, get_slot(tracker, SLOT_DATA), value); + js_resolve_promise(js, get_slot(tracker, SLOT_DATA), value); return true; } @@ -10554,7 +10562,7 @@ static void promise_any_record_rejection(ant_t *js, ant_value_t tracker, int ind int remaining = (int)tod(js_get(js, tracker, "remaining")) - 1; js_set(js, tracker, "remaining", tov((double)remaining)); - if (remaining == 0) reject_promise(js, get_slot(tracker, SLOT_DATA), mk_aggregate_error(js, errors)); + if (remaining == 0) js_reject_promise(js, get_slot(tracker, SLOT_DATA), mk_aggregate_error(js, errors)); } static ant_value_t builtin_Promise_any_resolve_handler(ant_t *js, ant_value_t *args, int nargs) { @@ -10593,7 +10601,7 @@ static ant_value_t builtin_Promise_any(ant_t *js, ant_value_t *args, int nargs) return ret; } - ant_value_t result_promise = mkpromise(js); + ant_value_t result_promise = js_mkpromise(js); GC_ROOT_PIN(js, result_promise); ant_value_t tracker = mkobj(js, 0); GC_ROOT_PIN(js, tracker); @@ -12561,10 +12569,6 @@ void js_prop_iter_end(ant_iter_t *iter) { iter->ctx = NULL; } -ant_value_t js_mkpromise(ant_t *js) { return mkpromise(js); } -void js_resolve_promise(ant_t *js, ant_value_t promise, ant_value_t value) { resolve_promise(js, promise, value); } -void js_reject_promise(ant_t *js, ant_value_t promise, ant_value_t value) { reject_promise(js, promise, value); } - static void check_unhandled_rejections_list(ant_t *js, ant_object_t *list) { for (ant_object_t *obj = list; obj; obj = obj->next) { ant_promise_state_t *pd = obj->promise_state; diff --git a/src/main.c b/src/main.c index a008183..cf75da6 100644 --- a/src/main.c +++ b/src/main.c @@ -39,6 +39,7 @@ #include "modules/timer.h" #include "modules/json.h" #include "modules/fetch.h" +#include "modules/request.h" #include "modules/shell.h" #include "modules/process.h" #include "modules/tty.h" @@ -612,6 +613,7 @@ int main(int argc, char *argv[]) { init_fs_module(); init_atomics_module(); init_crypto_module(); + init_request_module(); init_fetch_module(); init_console_module(); init_json_module(); diff --git a/src/modules/abort.c b/src/modules/abort.c index f5f7ea2..60a01a2 100644 --- a/src/modules/abort.c +++ b/src/modules/abort.c @@ -267,6 +267,21 @@ static ant_value_t abort_signal_static_any(ant_t *js, ant_value_t *args, int nar return composite; } +ant_value_t abort_signal_create_dependent(ant_t *js, ant_value_t source) { + ant_value_t composite = make_new_signal(js); + + if (is_err(composite)) return composite; + if (vtype(source) != T_OBJ && vtype(source) != T_ARR) return composite; + + abort_signal_data_t *d = get_signal_data(source); + if (!d) return composite; + + if (d->aborted) signal_mark_aborted(js, composite, d->reason); + else utarray_push_back(d->followers, &composite); + + return composite; +} + static void abort_timeout_close_cb(uv_handle_t *h) { abort_timeout_entry_t *entry = (abort_timeout_entry_t *)h->data; if (entry) entry->closed = 1; diff --git a/src/modules/blob.c b/src/modules/blob.c index c89746f..6cb216b 100644 --- a/src/modules/blob.c +++ b/src/modules/blob.c @@ -20,6 +20,13 @@ ant_value_t g_blob_proto = 0; ant_value_t g_file_proto = 0; +bool blob_is_blob(ant_t *js, ant_value_t obj) { + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + if (vtype(brand) != T_NUM) return false; + int id = (int)js_getnum(brand); + return id == BRAND_BLOB || id == BRAND_FILE; +} + blob_data_t *blob_get_data(ant_value_t obj) { ant_value_t slot = js_get_slot(obj, SLOT_DATA); if (vtype(slot) != T_NUM) return NULL; @@ -167,9 +174,12 @@ ant_value_t blob_create(ant_t *js, const uint8_t *data, size_t size, const char blob_data_t *bd = blob_data_new(data, size, type); if (!bd) return js_mkerr(js, "out of memory"); ant_value_t obj = js_mkobj(js); + js_set_proto_init(obj, g_blob_proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_BLOB)); js_set_slot(obj, SLOT_DATA, ANT_PTR(bd)); js_set_finalizer(obj, blob_finalize); + return obj; } @@ -347,8 +357,10 @@ static ant_value_t js_blob_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t proto = js_instance_proto_from_new_target(js, g_blob_proto); if (is_object_type(proto)) js_set_proto_init(obj, proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_BLOB)); js_set_slot(obj, SLOT_DATA, ANT_PTR(bd)); js_set_finalizer(obj, blob_finalize); + return obj; } @@ -416,6 +428,7 @@ static ant_value_t js_file_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t proto = js_instance_proto_from_new_target(js, g_file_proto); if (is_object_type(proto)) js_set_proto_init(obj, proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_FILE)); js_set_slot(obj, SLOT_DATA, ANT_PTR(bd)); js_set_finalizer(obj, blob_finalize); diff --git a/src/modules/buffer.c b/src/modules/buffer.c index f4f75bf..27d54a0 100644 --- a/src/modules/buffer.c +++ b/src/modules/buffer.c @@ -27,11 +27,48 @@ static uint8_t *ta_arena = NULL; static size_t ta_arena_offset = 0; +static size_t buffer_registry_count = 0; +static size_t buffer_registry_cap = 0; + static ArrayBufferData **buffer_registry = NULL; static ant_value_t g_typedarray_iter_proto = 0; -static size_t buffer_registry_count = 0; -static size_t buffer_registry_cap = 0; +bool buffer_is_dataview(ant_value_t obj) { + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + return vtype(brand) == T_NUM && (int)js_getnum(brand) == BRAND_DATAVIEW; +} + +bool buffer_source_get_bytes(ant_t *js, ant_value_t value, const uint8_t **out, size_t *len) { + ant_value_t slot = js_get_slot(value, SLOT_BUFFER); + TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(slot); + + if (ta) { + if (!ta->buffer || ta->buffer->is_detached) { *out = NULL; *len = 0; return true; } + *out = ta->buffer->data + ta->byte_offset; + *len = ta->byte_length; + return true; + } + + if (vtype(slot) == T_NUM) { + ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(slot); + if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; } + *out = ab->data; + *len = ab->length; + return true; + } + + if (buffer_is_dataview(value)) { + ant_value_t dv_data_val = js_get_slot(value, SLOT_DATA); + if (vtype(dv_data_val) != T_NUM) return false; + DataViewData *dv = (DataViewData *)(uintptr_t)js_getnum(dv_data_val); + if (!dv || !dv->buffer || dv->buffer->is_detached) { *out = NULL; *len = 0; return true; } + *out = dv->buffer->data + dv->byte_offset; + *len = dv->byte_length; + return true; + } + + return false; +} static bool advance_typedarray(ant_t *js, js_iter_t *it, ant_value_t *out) { ant_value_t iter = it->iterator; @@ -1206,6 +1243,7 @@ static ant_value_t js_dataview_constructor(ant_t *js, ant_value_t *args, int nar ant_value_t proto = js_get_ctor_proto(js, "DataView", 8); if (is_special_object(proto)) js_set_proto_init(obj, proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_DATAVIEW)); js_set_slot(obj, SLOT_DATA, ANT_PTR(dv_data)); js_mkprop_fast(js, obj, "buffer", 6, args[0]); js_set_descriptor(js, obj, "buffer", 6, 0); diff --git a/src/modules/formdata.c b/src/modules/formdata.c index 6ac4917..bba1ad0 100644 --- a/src/modules/formdata.c +++ b/src/modules/formdata.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -14,20 +15,6 @@ #include "modules/blob.h" #include "modules/symbol.h" -typedef struct fd_entry { - char *name; - bool is_file; - char *str_value; - size_t val_idx; - struct fd_entry *next; -} fd_entry_t; - -typedef struct { - fd_entry_t *head; - fd_entry_t **tail; - size_t count; -} fd_data_t; - typedef struct { size_t index; int kind; @@ -41,6 +28,17 @@ enum { static ant_value_t g_formdata_proto = 0; static ant_value_t g_formdata_iter_proto = 0; +static fd_data_t *get_fd_data(ant_value_t obj); + +bool formdata_is_formdata(ant_t *js, ant_value_t obj) { + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + return vtype(brand) == T_NUM && (int)js_getnum(brand) == BRAND_FORMDATA; +} + +bool formdata_is_empty(ant_value_t fd) { + fd_data_t *d = get_fd_data(fd); + return d ? d->count == 0 : true; +} static fd_data_t *fd_data_new(void) { fd_data_t *d = calloc(1, sizeof(fd_data_t)); @@ -135,6 +133,22 @@ static void fd_delete_name(fd_data_t *d, const char *name) { }} } +ant_value_t formdata_create_empty(ant_t *js) { + fd_data_t *d = fd_data_new(); + if (!d) return js_mkerr(js, "out of memory"); + + ant_value_t obj = js_mkobj(js); + js_set_proto_init(obj, g_formdata_proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_FORMDATA)); + js_set_slot(obj, SLOT_DATA, ANT_PTR(d)); + + ant_value_t vals = js_mkarr(js); + js_set_slot_wb(js, obj, SLOT_ENTRIES, vals); + js_set_finalizer(obj, formdata_finalize); + + return obj; +} + static ant_value_t entry_to_js_value(ant_t *js, ant_value_t values_arr, fd_entry_t *e) { if (!e->is_file) return js_mkstr(js, e->str_value ? e->str_value : "", strlen(e->str_value ? e->str_value : "")); @@ -205,6 +219,33 @@ static ant_value_t extract_file_entry( return fd_append_file(d, name, idx) ? js_mkundef() : js_mkerr(js, "out of memory"); } +ant_value_t formdata_append_string(ant_t *js, ant_value_t fd, ant_value_t name_v, ant_value_t value_v) { + fd_data_t *d = get_fd_data(fd); + if (!d) return js_mkerr(js, "Invalid FormData object"); + + const char *name = resolve_name(js, &name_v); + if (!name) 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; + } + + return fd_append_str(d, name, js_getstr(js, value_v, NULL)) + ? js_mkundef() + : js_mkerr(js, "out of memory" + ); +} + +ant_value_t formdata_append_file(ant_t *js, ant_value_t fd, ant_value_t name_v, ant_value_t blob_v, ant_value_t filename_v) { + fd_data_t *d = get_fd_data(fd); + if (!d) return js_mkerr(js, "Invalid FormData object"); + const char *name = resolve_name(js, &name_v); + if (!name) return name_v; + ant_value_t values_arr = get_fd_values(fd); + return extract_file_entry(js, d, values_arr, name, blob_v, filename_v, false); +} + static ant_value_t js_formdata_append(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkerr_typed(js, JS_ERR_TYPE, "FormData.append requires 2 arguments"); fd_data_t *d = get_fd_data(js->this_val); @@ -419,12 +460,15 @@ static ant_value_t js_formdata_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t obj = js_mkobj(js); ant_value_t proto = js_instance_proto_from_new_target(js, g_formdata_proto); + if (is_object_type(proto)) js_set_proto_init(obj, proto); - + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_FORMDATA)); js_set_slot(obj, SLOT_DATA, ANT_PTR(d)); + ant_value_t vals = js_mkarr(js); js_set_slot_wb(js, obj, SLOT_ENTRIES, vals); js_set_finalizer(obj, formdata_finalize); + return obj; } diff --git a/src/modules/headers.c b/src/modules/headers.c index 02f57b8..75b557b 100644 --- a/src/modules/headers.c +++ b/src/modules/headers.c @@ -42,8 +42,8 @@ enum { ITER_VALUES = 2 }; -static ant_value_t g_headers_proto = 0; -ant_value_t g_headers_iter_proto = 0; +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)); @@ -69,6 +69,17 @@ static hdr_list_t *get_list(ant_value_t obj) { 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) { + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + return vtype(brand) == T_NUM && (int)js_getnum(brand) == BRAND_HEADERS; +} + static bool is_token_char(unsigned char c) { if (c > 127) return false; static const char ok[] = @@ -121,6 +132,141 @@ static char *lowercase_dup(const char *s) { 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 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_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 (is_forbidden_request_header_name(lower_name)) return false; + if (guard == HEADERS_GUARD_REQUEST_NO_CORS) return is_no_cors_safelisted_name_value(lower_name, value); + return true; +} + +static void list_apply_guard(hdr_list_t *l, headers_guard_t guard) { + if (!l || guard == HEADERS_GUARD_NONE) 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; @@ -240,6 +386,15 @@ static ant_value_t headers_append_pair(ant_t *js, hdr_list_t *l, ant_value_t nam return js_mkundef(); } +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); + if (!l) return js_mkerr(js, "Invalid Headers object"); + ant_value_t 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(); +} + 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"); @@ -336,7 +491,10 @@ 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"); - return headers_append_pair(js, l, args[0], args[1]); + 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) { @@ -363,7 +521,8 @@ static ant_value_t js_headers_set(ant_t *js, ant_value_t *args, int nargs) { if (!lower) { free(norm); return js_mkerr(js, "out of memory"); } list_delete_name(l, lower); - list_append_raw(l, lower, norm); + 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(); } @@ -556,10 +715,124 @@ static ant_value_t js_headers_ctor(ant_t *js, ant_value_t *args, int nargs) { 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); +} + +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); diff --git a/src/modules/multipart.c b/src/modules/multipart.c new file mode 100644 index 0000000..608f2ce --- /dev/null +++ b/src/modules/multipart.c @@ -0,0 +1,546 @@ +#include +#include +#include +#include +#include +#include + +#include "ant.h" +#include "errors.h" +#include "internal.h" + +#include "modules/blob.h" +#include "modules/formdata.h" +#include "modules/multipart.h" +#include "modules/url.h" + +static bool ct_is_type(const char *ct, const char *type) { + size_t type_len = 0; + + if (!ct || !type) return false; + + while (*ct == ' ' || *ct == '\t') ct++; + type_len = strlen(type); + if (strncasecmp(ct, type, type_len) != 0) return false; + + ct += type_len; + return *ct == '\0' || *ct == ';' || *ct == ' ' || *ct == '\t'; +} + +static const char *ct_find_next_param(const char *p) { +scan: + if (*p == '\0') return NULL; + if (*p == ';') return p; + if (*p != '"') { + p++; + goto scan; + } + +quoted: + p++; + if (*p == '\0') return NULL; + if (*p == '\\') goto escaped; + if (*p == '"') { + p++; + goto scan; + } + goto quoted; + +escaped: + p++; + if (*p == '\0') return NULL; + p++; + goto quoted; +} + +static char *ct_get_param_dup(const char *ct, const char *name) { + const char *p = NULL; + const char *param_name = NULL; + const char *param_name_end = NULL; + + char *buf = NULL; + size_t name_len = 0; + size_t len = 0; + size_t cap = 0; + char ch = '\0'; + + if (!ct || !name) return NULL; + + name_len = strlen(name); + if (name_len == 0) return NULL; + + p = strchr(ct, ';'); + +next_param: + if (!p) return NULL; + p++; + + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') return NULL; + + param_name = p; + while (*p && *p != '=' && *p != ';') p++; + param_name_end = p; + + while ( + param_name_end > param_name && + (param_name_end[-1] == ' ' || param_name_end[-1] == '\t') + ) param_name_end--; + + if (*p != '=') goto skip_param; + if ((size_t)(param_name_end - param_name) != name_len) goto skip_param; + if (strncasecmp(param_name, name, name_len) != 0) goto skip_param; + + p++; + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') return strdup(""); + + if (*p == '"') { + p++; + goto quoted; + } + +unquoted: + if (*p == '\0' || *p == ';' || *p == ' ' || *p == '\t') { + buf = malloc(len + 1); + if (!buf) return NULL; + if (len != 0) memcpy(buf, p - len, len); + buf[len] = '\0'; + return buf; + } + p++; + len++; + goto unquoted; + +quoted: + if (*p == '\0') goto fail; + if (*p == '"') goto done; + if (*p == '\\') goto quoted_escape; + ch = *p++; + goto append; + +quoted_escape: + p++; + if (*p == '\0') goto fail; + ch = *p++; + goto append; + +append: + if (len == cap) { + size_t next_cap = cap ? cap * 2 : 32; + char *next = realloc(buf, next_cap); + if (!next) goto fail; + buf = next; + cap = next_cap; + } + buf[len++] = ch; + goto quoted; + +done: + p++; + if (len == cap) { + size_t next_cap = cap ? cap + 1 : 1; + char *next = realloc(buf, next_cap); + if (!next) goto fail; + buf = next; + cap = next_cap; + } + buf[len] = '\0'; + return buf; + +skip_param: + p = ct_find_next_param(p); + goto next_param; + +fail: + free(buf); + return NULL; +} + +static ant_value_t parse_formdata_urlencoded(ant_t *js, const uint8_t *data, size_t size) { + ant_value_t fd = 0; + char *body = NULL; + char *cursor = NULL; + + fd = formdata_create_empty(js); + if (is_err(fd)) return fd; + if (!data || size == 0) return fd; + + body = strndup((const char *)data, size); + if (!body) return js_mkerr(js, "out of memory"); + + cursor = body; + while (cursor) { + ant_value_t r = 0; + char *amp = strchr(cursor, '&'); + char *eq = NULL; + char *raw_name = NULL; + char *raw_value = NULL; + char *name = NULL; + char *value = NULL; + + if (amp) *amp = '\0'; + + eq = strchr(cursor, '='); + raw_name = cursor; + raw_value = eq ? (eq + 1) : ""; + if (eq) *eq = '\0'; + + name = form_urldecode(raw_name); + value = form_urldecode(raw_value); + if (!name || !value) { + free(name); + free(value); + free(body); + return js_mkerr(js, "out of memory"); + } + + r = formdata_append_string( + js, fd, + js_mkstr(js, name, strlen(name)), + js_mkstr(js, value, strlen(value)) + ); + + free(name); + free(value); + + if (is_err(r)) { + free(body); + return r; + } + + cursor = amp ? (amp + 1) : NULL; + } + + free(body); + return fd; +} + +static const uint8_t *find_bytes( + const uint8_t *haystack, size_t haystack_len, + const uint8_t *needle, size_t needle_len +) { + size_t i = 0; + + if (needle_len == 0) return haystack; + if (haystack_len < needle_len) return NULL; + + for (i = 0; i + needle_len <= haystack_len; i++) { + if (memcmp(haystack + i, needle, needle_len) == 0) return haystack + i; + } + + return NULL; +} + +static ant_value_t parse_formdata_multipart( + ant_t *js, const uint8_t *data, size_t size, const char *body_type +) { + ant_value_t fd = 0; + char *boundary = NULL; + char *delim = NULL; + + const uint8_t *p = data; + const uint8_t *end = data + size; + size_t delim_len = 0; + + if (!data || size == 0) { + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData"); + } + + boundary = ct_get_param_dup(body_type, "boundary"); + if (!boundary || boundary[0] == '\0') goto invalid; + + fd = formdata_create_empty(js); + if (is_err(fd)) goto done; + + delim_len = strlen(boundary) + 2; + delim = malloc(delim_len + 1); + if (!delim) { + fd = js_mkerr(js, "out of memory"); + goto done; + } + snprintf(delim, delim_len + 1, "--%s", boundary); + + if ((size_t)(end - p) < delim_len || memcmp(p, delim, delim_len) != 0) goto invalid; + p += delim_len; + +next_part: + if (p > end) goto done; + if ((size_t)(end - p) >= 2 && memcmp(p, "--", 2) == 0) goto done; + if ((size_t)(end - p) < 2 || memcmp(p, "\r\n", 2) != 0) goto invalid; + p += 2; + + { + ant_value_t r = 0; + const uint8_t *hdr_end = find_bytes( + p, (size_t)(end - p), (const uint8_t *)"\r\n\r\n", 4 + ); + + char *headers = NULL; + char *name = NULL; + char *filename = NULL; + char *part_type = NULL; + char *marker = NULL; + const uint8_t *part_end = NULL; + + if (!hdr_end) goto invalid; + + headers = strndup((const char *)p, (size_t)(hdr_end - p)); + if (!headers) { + fd = js_mkerr(js, "out of memory"); + goto done; + } + p = hdr_end + 4; + + { + char *saveptr = NULL; + for (char *line = strtok_r(headers, "\r\n", &saveptr); + line; + line = strtok_r(NULL, "\r\n", &saveptr)) { + char *colon = strchr(line, ':'); + if (!colon) continue; + *colon = '\0'; + + char *value = colon + 1; + while (*value == ' ' || *value == '\t') value++; + + if (strcasecmp(line, "Content-Disposition") == 0) { + char *name_pos = strcasestr(value, "name="); + char *file_pos = strcasestr(value, "filename="); + + if (name_pos) { + name_pos += 5; + if (*name_pos == '"') { + char *endq = NULL; + name_pos++; + endq = strchr(name_pos, '"'); + if (endq) name = strndup(name_pos, (size_t)(endq - name_pos)); + } + } + + if (file_pos) { + file_pos += 9; + if (*file_pos == '"') { + char *endq = NULL; + file_pos++; + endq = strchr(file_pos, '"'); + if (endq) filename = strndup(file_pos, (size_t)(endq - file_pos)); + } else { + char *ende = file_pos; + while (*ende && *ende != ';') ende++; + filename = strndup(file_pos, (size_t)(ende - file_pos)); + } + } + } else if (strcasecmp(line, "Content-Type") == 0) { + part_type = strdup(value); + } + } + } + free(headers); + headers = NULL; + + if (!name) { + free(filename); + free(part_type); + goto invalid; + } + + marker = malloc(delim_len + 3); + if (!marker) { + free(name); + free(filename); + free(part_type); + fd = js_mkerr(js, "out of memory"); + goto done; + } + snprintf(marker, delim_len + 3, "\r\n%s", delim); + part_end = find_bytes( + p, (size_t)(end - p), (const uint8_t *)marker, delim_len + 2 + ); + free(marker); + marker = NULL; + + if (!part_end) { + free(name); + free(filename); + free(part_type); + goto invalid; + } + + if (filename) { + ant_value_t blob = blob_create( + js, p, (size_t)(part_end - p), part_type ? part_type : "" + ); + r = is_err(blob) + ? blob + : formdata_append_file( + js, fd, + js_mkstr(js, name, strlen(name)), + blob, + js_mkstr(js, filename, strlen(filename)) + ); + } else { + r = formdata_append_string( + js, fd, + js_mkstr(js, name, strlen(name)), + js_mkstr(js, p, (size_t)(part_end - p)) + ); + } + + free(name); + free(filename); + free(part_type); + if (is_err(r)) { + fd = r; + goto done; + } + + p = part_end + 2 + delim_len; + if ((size_t)(end - p) >= 2 && memcmp(p, "--", 2) == 0) goto done; + } + + goto next_part; + +invalid: + fd = js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData"); + +done: + free(boundary); + free(delim); + return fd; +} + +ant_value_t formdata_parse_body( + ant_t *js, const uint8_t *data, size_t size, + const char *body_type, bool has_body +) { + if (body_type && ct_is_type(body_type, "application/x-www-form-urlencoded")) { + return parse_formdata_urlencoded(js, data, size); + } + + if (body_type && ct_is_type(body_type, "multipart/form-data")) { + if (!has_body || !data || size == 0) { + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData"); + } + return parse_formdata_multipart(js, data, size, body_type); + } + + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData"); +} + +typedef struct { + uint8_t *buf; + size_t size; + size_t cap; +} mp_buf_t; + +static bool mp_grow(mp_buf_t *b, size_t need) { + size_t nc = 0; + uint8_t *nb = NULL; + + if (b->size + need <= b->cap) return true; + + nc = b->cap ? b->cap * 2 : 4096; + while (nc < b->size + need) nc *= 2; + + nb = realloc(b->buf, nc); + if (!nb) return false; + b->buf = nb; + b->cap = nc; + return true; +} + +static bool mp_append(mp_buf_t *b, const void *data, size_t len) { + if (!mp_grow(b, len)) return false; + memcpy(b->buf + b->size, data, len); + b->size += len; + return true; +} + +static bool mp_append_str(mp_buf_t *b, const char *s) { + return mp_append(b, s, strlen(s)); +} + +uint8_t *formdata_serialize_multipart( + ant_t *js, ant_value_t fd, size_t *out_size, char **out_boundary +) { + ant_value_t values_arr = js_get_slot(fd, SLOT_ENTRIES); + ant_value_t data_slot = js_get_slot(fd, SLOT_DATA); + char boundary[49]; + mp_buf_t b = {NULL, 0, 0}; + fd_data_t *d = NULL; + if (vtype(data_slot) != T_NUM) return NULL; + d = (fd_data_t *)(uintptr_t)(size_t)js_getnum(data_slot); + if (!d) return NULL; + + snprintf( + boundary, sizeof(boundary), + "----AntFormBoundary%08x%08x", (unsigned)rand(), (unsigned)rand() + ); + + if (d->count == 0) { + uint8_t *empty = malloc(1); + if (!empty) return NULL; + *out_size = 0; + *out_boundary = strdup(boundary); + if (!*out_boundary) { + free(empty); + return NULL; + } + return empty; + } + + for (fd_entry_t *e = d->head; e; e = e->next) { + if (!mp_append_str(&b, "--") || + !mp_append_str(&b, boundary) || + !mp_append_str(&b, "\r\n")) { + goto oom; + } + + if (!e->is_file) { + char disp[512]; + const char *val = e->str_value ? e->str_value : ""; + + snprintf( + disp, sizeof(disp), + "Content-Disposition: form-data; name=\"%s\"\r\n\r\n", + e->name ? e->name : "" + ); + if (!mp_append_str(&b, disp) || !mp_append_str(&b, val)) goto oom; + } else { + ant_value_t file_val = js_arr_get(js, values_arr, (ant_offset_t)e->val_idx); + blob_data_t *bd = blob_get_data(file_val); + const char *filename = (bd && bd->name) ? bd->name : "blob"; + const char *mime = (bd && bd->type && bd->type[0]) + ? bd->type + : "application/octet-stream"; + char disp[1024]; + + snprintf( + disp, sizeof(disp), + "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n" + "Content-Type: %s\r\n\r\n", + e->name ? e->name : "", filename, mime + ); + if (!mp_append_str(&b, disp)) goto oom; + if (bd && bd->data && bd->size > 0 && !mp_append(&b, bd->data, bd->size)) goto oom; + } + + if (!mp_append_str(&b, "\r\n")) goto oom; + } + + if (!mp_append_str(&b, "--") || + !mp_append_str(&b, boundary) || + !mp_append_str(&b, "--\r\n")) { + goto oom; + } + + *out_size = b.size; + *out_boundary = strdup(boundary); + if (!*out_boundary) goto oom; + return b.buf; + +oom: + free(b.buf); + return NULL; +} diff --git a/src/modules/request.c b/src/modules/request.c new file mode 100644 index 0000000..8c9a628 --- /dev/null +++ b/src/modules/request.c @@ -0,0 +1,1104 @@ +#include +#include +#include +#include +#include + +#include "ant.h" +#include "errors.h" +#include "runtime.h" +#include "internal.h" +#include "common.h" +#include "descriptors.h" + +#include "modules/blob.h" +#include "modules/buffer.h" +#include "modules/assert.h" +#include "modules/abort.h" +#include "modules/formdata.h" +#include "modules/headers.h" +#include "modules/multipart.h" +#include "modules/request.h" +#include "modules/symbol.h" +#include "modules/url.h" +#include "modules/json.h" +#include "streams/pipes.h" +#include "streams/readable.h" + +ant_value_t g_request_proto = 0; + +static request_data_t *get_data(ant_value_t obj) { + ant_value_t slot = js_get_slot(obj, SLOT_DATA); + if (vtype(slot) != T_NUM) return NULL; + return (request_data_t *)(uintptr_t)(size_t)js_getnum(slot); +} + +request_data_t *request_get_data(ant_value_t obj) { + return get_data(obj); +} + +ant_value_t request_get_headers(ant_value_t obj) { + return js_get_slot(obj, SLOT_REQUEST_HEADERS); +} + +ant_value_t request_get_signal(ant_value_t obj) { + return js_get_slot(obj, SLOT_REQUEST_SIGNAL); +} + +static void data_free(request_data_t *d) { + if (!d) return; + free(d->method); + url_state_clear(&d->url); + free(d->referrer); + free(d->referrer_policy); + free(d->mode); + free(d->credentials); + free(d->cache); + free(d->redirect); + free(d->integrity); + free(d->body_data); + free(d->body_type); + free(d); +} + +static request_data_t *data_new(void) { + request_data_t *d = calloc(1, sizeof(request_data_t)); + if (!d) return NULL; + d->method = strdup("GET"); + d->referrer = strdup("client"); + d->referrer_policy = strdup(""); + d->mode = strdup("cors"); + d->credentials = strdup("same-origin"); + d->cache = strdup("default"); + d->redirect = strdup("follow"); + d->integrity = strdup(""); + if (!d->method || !d->referrer || !d->referrer_policy || + !d->mode || !d->credentials || !d->cache || !d->redirect || !d->integrity) { + data_free(d); + return NULL; + } + return d; +} + +static request_data_t *data_dup(const request_data_t *src) { + request_data_t *d = calloc(1, sizeof(request_data_t)); + if (!d) return NULL; + +#define DUP_STR(f) do { d->f = src->f ? strdup(src->f) : NULL; } while(0) + DUP_STR(method); + DUP_STR(referrer); + DUP_STR(referrer_policy); + DUP_STR(mode); + DUP_STR(credentials); + DUP_STR(cache); + DUP_STR(redirect); + DUP_STR(integrity); + DUP_STR(body_type); +#undef DUP_STR + url_state_t *su = (url_state_t *)&src->url; + url_state_t *du = &d->url; +#define DUP_US(f) do { du->f = su->f ? strdup(su->f) : NULL; } while(0) + DUP_US(protocol); DUP_US(username); DUP_US(password); + DUP_US(hostname); DUP_US(port); DUP_US(pathname); + DUP_US(search); DUP_US(hash); +#undef DUP_US + d->keepalive = src->keepalive; + d->reload_navigation = src->reload_navigation; + d->history_navigation = src->history_navigation; + d->has_body = src->has_body; + d->body_is_stream = src->body_is_stream; + d->body_used = src->body_used; + d->body_size = src->body_size; + + if (src->body_data && src->body_size > 0) { + d->body_data = malloc(src->body_size); + if (!d->body_data) { data_free(d); return NULL; } + memcpy(d->body_data, src->body_data, src->body_size); + } + + return d; +} + +static bool is_token_char(unsigned char c) { + if (c == 0 || c > 127) return false; + static const char *delimiters = "(),/:;<=>?@[\\]{}\""; + return c > 32 && !strchr(delimiters, (char)c); +} + +static bool is_valid_method(const char *m) { + if (!m || !*m) return false; + for (const unsigned char *p = (const unsigned char *)m; *p; p++) + if (!is_token_char(*p)) return false; + return true; +} + +static bool is_forbidden_method(const char *m) { + return + strcasecmp(m, "CONNECT") == 0 || + strcasecmp(m, "TRACE") == 0 || + strcasecmp(m, "TRACK") == 0; +} + +static bool is_cors_safelisted_method(const char *m) { + return + strcasecmp(m, "GET") == 0 || + strcasecmp(m, "HEAD") == 0 || + strcasecmp(m, "POST") == 0; +} + +static void normalize_method(char *m) { + static const char *norm[] = { + "DELETE","GET","HEAD","OPTIONS","POST","PUT" + }; + + for (int i = 0; i < 6; i++) { + if (strcasecmp(m, norm[i]) == 0) { + strcpy(m, norm[i]); + return; + }} +} + +static ant_value_t request_rejection_reason(ant_t *js, ant_value_t value) { + if (!is_err(value)) return value; + ant_value_t reason = js->thrown_exists ? js->thrown_value : value; + js->thrown_exists = false; + js->thrown_value = js_mkundef(); + js->thrown_stack = js_mkundef(); + return reason; +} + +static const char *request_effective_body_type(ant_t *js, ant_value_t req_obj, request_data_t *d) { + ant_value_t headers = js_get_slot(req_obj, SLOT_REQUEST_HEADERS); + if (!headers_is_headers(headers)) return d ? d->body_type : NULL; + ant_value_t ct = headers_get_value(js, headers, "content-type"); + if (vtype(ct) == T_STR) return js_getstr(js, ct, NULL); + return d ? d->body_type : NULL; +} + +static bool extract_body( + ant_t *js, ant_value_t body_val, + uint8_t **out_data, size_t *out_size, char **out_type, + ant_value_t *out_stream, ant_value_t *err_out +) { + *out_data = NULL; + *out_size = 0; + *out_type = NULL; + *out_stream = js_mkundef(); + *err_out = js_mkundef(); + + uint8_t t = vtype(body_val); + const uint8_t *src = NULL; + size_t src_len = 0; + + if (t == T_NULL || t == T_UNDEF) return true; + + if ((t == T_TYPEDARRAY || t == T_OBJ) && buffer_source_get_bytes(js, body_val, &src, &src_len)) { + if (src_len > 0) { + uint8_t *buf = malloc(src_len); + if (!buf) { *err_out = js_mkerr(js, "out of memory"); return false; } + memcpy(buf, src, src_len); + *out_data = buf; + *out_size = src_len; + } + return true; + } + + if (t == T_OBJ) { + if (rs_is_stream(body_val)) { + if (rs_stream_unusable(body_val)) { + *err_out = js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked"); + return false; + } + *out_stream = body_val; + return true; + } + + blob_data_t *bd = blob_is_blob(js, body_val) ? blob_get_data(body_val) : NULL; + if (bd) { + if (bd->size > 0) { + uint8_t *buf = malloc(bd->size); + if (!buf) { *err_out = js_mkerr(js, "out of memory"); return false; } + memcpy(buf, bd->data, bd->size); + *out_data = buf; + *out_size = bd->size; + } + if (bd->type && bd->type[0]) *out_type = strdup(bd->type); + return true; + } + + if (usp_is_urlsearchparams(js, body_val)) { + char *serialized = usp_serialize(js, body_val); + if (serialized) { + *out_data = (uint8_t *)serialized; + *out_size = strlen(serialized); + *out_type = strdup("application/x-www-form-urlencoded;charset=UTF-8"); + } + return true; + } + + if (formdata_is_formdata(js, body_val)) { + char *boundary = NULL; + size_t mp_size = 0; + uint8_t *mp = formdata_serialize_multipart(js, body_val, &mp_size, &boundary); + + if (!mp) { *err_out = js_mkerr(js, "out of memory"); return false; } + if (mp_size > 0) *out_data = mp; + else { free(mp); *out_data = NULL; } + + *out_size = mp_size; + if (boundary) { + char ct[256]; + snprintf(ct, sizeof(ct), "multipart/form-data; boundary=%s", boundary); + free(boundary); + *out_type = strdup(ct); + } + + return true; + } + } + + if (t != T_STR) { + body_val = js_tostring_val(js, body_val); + if (is_err(body_val)) { + *err_out = body_val; + return false; + }} + + size_t len; + const char *s = js_getstr(js, body_val, &len); + + if (len > 0) { + uint8_t *buf = malloc(len); + if (!buf) { *err_out = js_mkerr(js, "out of memory"); return false; } + memcpy(buf, s, len); + *out_data = buf; + *out_size = len; + } + + *out_type = strdup("text/plain;charset=UTF-8"); + return true; +} + +enum { + BODY_TEXT = 0, + BODY_JSON, + BODY_ARRAYBUFFER, + BODY_BLOB, + BODY_BYTES, + BODY_FORMDATA +}; + +static void resolve_body_promise( + ant_t *js, ant_value_t promise, + const uint8_t *data, size_t size, + const char *body_type, int mode, bool has_body +) { + switch (mode) { + case BODY_TEXT: { + ant_value_t str = (data && size > 0) + ? js_mkstr(js, (const char *)data, size) + : js_mkstr(js, "", 0); + js_resolve_promise(js, promise, str); + break; + } + case BODY_JSON: { + ant_value_t str = (data && size > 0) + ? js_mkstr(js, (const char *)data, size) + : js_mkstr(js, "", 0); + ant_value_t parsed = js_json_parse(js, &str, 1); + if (is_err(parsed)) js_reject_promise(js, promise, request_rejection_reason(js, parsed)); + else js_resolve_promise(js, promise, parsed); + break; + } + case BODY_ARRAYBUFFER: { + ArrayBufferData *ab = create_array_buffer_data(size); + if (!ab) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); break; } + if (data && size > 0) memcpy(ab->data, data, size); + js_resolve_promise(js, promise, create_arraybuffer_obj(js, ab)); + break; + } + case BODY_BLOB: { + const char *type = body_type ? body_type : ""; + js_resolve_promise(js, promise, blob_create(js, data, size, type)); + break; + } + case BODY_BYTES: { + ArrayBufferData *ab = create_array_buffer_data(size); + if (!ab) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); break; } + if (data && size > 0) memcpy(ab->data, data, size); + js_resolve_promise(js, promise, + create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, size, "Uint8Array")); + break; + } + case BODY_FORMDATA: { + ant_value_t fd = formdata_parse_body(js, data, size, body_type, has_body); + if (is_err(fd)) js_reject_promise(js, promise, request_rejection_reason(js, fd)); + else js_resolve_promise(js, promise, fd); + break; + }} +} + +static uint8_t *concat_chunks(ant_t *js, ant_value_t chunks, size_t *out_size) { + ant_offset_t n = js_arr_len(js, chunks); + size_t total = 0; + + for (ant_offset_t i = 0; i < n; i++) { + ant_value_t chunk = js_arr_get(js, chunks, i); + if (vtype(chunk) == T_TYPEDARRAY) { + TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(chunk); + if (ta && ta->buffer && !ta->buffer->is_detached) total += ta->byte_length; + }} + + uint8_t *buf = total > 0 ? malloc(total) : NULL; + if (total > 0 && !buf) return NULL; + + size_t pos = 0; + for (ant_offset_t i = 0; i < n; i++) { + ant_value_t chunk = js_arr_get(js, chunks, i); + + if (vtype(chunk) == T_TYPEDARRAY) { + TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(chunk); + if (ta && ta->buffer && !ta->buffer->is_detached && ta->byte_length > 0) { + memcpy(buf + pos, ta->buffer->data + ta->byte_offset, ta->byte_length); + pos += ta->byte_length; + }}} + + *out_size = pos; + return buf; +} + +static ant_value_t stream_body_read(ant_t *js, ant_value_t *args, int nargs); + +static ant_value_t stream_body_rejected(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t state = js_get_slot(js->current_func, SLOT_DATA); + ant_value_t promise = js_get(js, state, "promise"); + ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef(); + js_reject_promise(js, promise, reason); + return js_mkundef(); +} + +static void stream_schedule_next_read(ant_t *js, ant_value_t state, ant_value_t read_fn, ant_value_t reader) { + ant_value_t next_p = rs_default_reader_read(js, reader); + ant_value_t fulfill = js_heavy_mkfun(js, stream_body_read, state); + ant_value_t reject = js_heavy_mkfun(js, stream_body_rejected, state); + ant_value_t then_result = js_promise_then(js, next_p, fulfill, reject); + promise_mark_handled(then_result); +} + +static ant_value_t stream_body_read(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t state = js_get_slot(js->current_func, SLOT_DATA); + ant_value_t result = (nargs > 0) ? args[0] : js_mkundef(); + ant_value_t promise = js_get(js, state, "promise"); + ant_value_t reader = js_get(js, state, "reader"); + ant_value_t chunks = js_get(js, state, "chunks"); + int mode = (int)js_getnum(js_get(js, state, "mode")); + + ant_value_t done_val = js_get(js, result, "done"); + ant_value_t value = js_get(js, result, "value"); + + if (vtype(done_val) == T_BOOL && done_val == js_true) { + size_t size = 0; + uint8_t *data = concat_chunks(js, chunks, &size); + ant_value_t type_v = js_get(js, state, "type"); + const char *body_type = (vtype(type_v) == T_STR) ? js_getstr(js, type_v, NULL) : NULL; + resolve_body_promise(js, promise, data, size, body_type, mode, true); + free(data); + return js_mkundef(); + } + + if (vtype(value) != T_UNDEF && vtype(value) != T_NULL) + js_arr_push(js, chunks, value); + + stream_schedule_next_read(js, state, js_mkundef(), reader); + return js_mkundef(); +} + +static ant_value_t consume_body_from_stream( + ant_t *js, ant_value_t stream, + ant_value_t promise, int mode, + const char *body_type +) { + ant_value_t reader_args[1] = { stream }; + ant_value_t saved = js->new_target; + + js->new_target = g_reader_proto; + ant_value_t reader = js_rs_reader_ctor(js, reader_args, 1); + js->new_target = saved; + + if (is_err(reader)) { + js_reject_promise(js, promise, reader); + return promise; + } + + ant_value_t state = js_mkobj(js); + js_set(js, state, "promise", promise); + js_set(js, state, "reader", reader); + js_set(js, state, "chunks", js_mkarr(js)); + js_set(js, state, "mode", js_mknum(mode)); + js_set(js, state, "type", body_type ? js_mkstr(js, body_type, strlen(body_type)) : js_mkundef()); + + stream_schedule_next_read(js, state, js_mkundef(), reader); + return promise; +} + +static ant_value_t consume_body(ant_t *js, int mode) { + ant_value_t this = js_getthis(js); + request_data_t *d = get_data(this); + ant_value_t promise = js_mkpromise(js); + + if (!d) { + js_reject_promise(js, promise, request_rejection_reason(js, + js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Request object"))); + return promise; + } + + if (!d->has_body) { + resolve_body_promise(js, promise, NULL, 0, request_effective_body_type(js, this, d), mode, false); + return promise; + } + + if (d->body_used) { + js_reject_promise(js, promise, request_rejection_reason(js, + js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked"))); + return promise; + } + + d->body_used = true; + ant_value_t stream = js_get_slot(this, SLOT_REQUEST_BODY_STREAM); + if (rs_is_stream(stream) && d->body_is_stream) + return consume_body_from_stream(js, stream, promise, mode, request_effective_body_type(js, this, d)); + resolve_body_promise(js, promise, d->body_data, d->body_size, request_effective_body_type(js, this, d), mode, true); + + return promise; +} + +static ant_value_t js_req_text(ant_t *js, ant_value_t *args, int nargs) { + return consume_body(js, BODY_TEXT); +} + +static ant_value_t js_req_json(ant_t *js, ant_value_t *args, int nargs) { + return consume_body(js, BODY_JSON); +} + +static ant_value_t js_req_array_buffer(ant_t *js, ant_value_t *args, int nargs) { + return consume_body(js, BODY_ARRAYBUFFER); +} + +static ant_value_t js_req_blob(ant_t *js, ant_value_t *args, int nargs) { + return consume_body(js, BODY_BLOB); +} + +static ant_value_t js_req_bytes(ant_t *js, ant_value_t *args, int nargs) { + return consume_body(js, BODY_BYTES); +} + +static ant_value_t js_req_form_data(ant_t *js, ant_value_t *args, int nargs) { + return consume_body(js, BODY_FORMDATA); +} + +#define REQ_GETTER_START(name) \ + static ant_value_t js_req_get_##name(ant_t *js, ant_value_t *args, int nargs) { \ + ant_value_t this = js_getthis(js); \ + request_data_t *d = get_data(this); \ + if (!d) return js_mkundef(); + +#define REQ_GETTER_END } + +REQ_GETTER_START(method) + return js_mkstr(js, d->method, strlen(d->method)); +REQ_GETTER_END + +REQ_GETTER_START(url) + char *href = build_href(&d->url); + if (!href) return js_mkstr(js, "", 0); + ant_value_t ret = js_mkstr(js, href, strlen(href)); + free(href); + return ret; +REQ_GETTER_END + +REQ_GETTER_START(headers) + return js_get_slot(this, SLOT_REQUEST_HEADERS); +REQ_GETTER_END + +REQ_GETTER_START(destination) + (void)d; + return js_mkstr(js, "", 0); +REQ_GETTER_END + +REQ_GETTER_START(referrer) + if (!d->referrer || strcmp(d->referrer, "no-referrer") == 0) + return js_mkstr(js, "", 0); + if (strcmp(d->referrer, "client") == 0) + return js_mkstr(js, "about:client", 12); + return js_mkstr(js, d->referrer, strlen(d->referrer)); +REQ_GETTER_END + +REQ_GETTER_START(referrer_policy) + const char *p = d->referrer_policy ? d->referrer_policy : ""; + return js_mkstr(js, p, strlen(p)); +REQ_GETTER_END + +REQ_GETTER_START(mode) + return js_mkstr(js, d->mode, strlen(d->mode)); +REQ_GETTER_END + +REQ_GETTER_START(credentials) + return js_mkstr(js, d->credentials, strlen(d->credentials)); +REQ_GETTER_END + +REQ_GETTER_START(cache) + return js_mkstr(js, d->cache, strlen(d->cache)); +REQ_GETTER_END + +REQ_GETTER_START(redirect) + return js_mkstr(js, d->redirect, strlen(d->redirect)); +REQ_GETTER_END + +REQ_GETTER_START(integrity) + const char *ig = d->integrity ? d->integrity : ""; + return js_mkstr(js, ig, strlen(ig)); +REQ_GETTER_END + +REQ_GETTER_START(keepalive) + return js_bool(d->keepalive); +REQ_GETTER_END + +REQ_GETTER_START(is_reload_navigation) + return js_bool(d->reload_navigation); +REQ_GETTER_END + +REQ_GETTER_START(is_history_navigation) + return js_bool(d->history_navigation); +REQ_GETTER_END + +REQ_GETTER_START(signal) + return js_get_slot(this, SLOT_REQUEST_SIGNAL); +REQ_GETTER_END + +REQ_GETTER_START(duplex) + return js_mkstr(js, "half", 4); +REQ_GETTER_END + +static ant_value_t req_body_pull(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t req_obj = js_get_slot(js->current_func, SLOT_DATA); + request_data_t *d = get_data(req_obj); + ant_value_t ctrl = (nargs > 0) ? args[0] : js_mkundef(); + + if (d && d->body_data && d->body_size > 0) { + ArrayBufferData *ab = create_array_buffer_data(d->body_size); + if (ab) { + memcpy(ab->data, d->body_data, d->body_size); + rs_controller_enqueue(js, ctrl, + create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, d->body_size, "Uint8Array")); + }} + + rs_controller_close(js, ctrl); + return js_mkundef(); +} + +REQ_GETTER_START(body) + if (!d->has_body) return js_mknull(); + ant_value_t stored_stream = js_get_slot(this, SLOT_REQUEST_BODY_STREAM); + if (rs_is_stream(stored_stream)) return stored_stream; + if (d->body_used) return js_mknull(); + ant_value_t pull = js_heavy_mkfun(js, req_body_pull, this); + ant_value_t stream = rs_create_stream(js, pull, js_mkundef(), 1.0); + if (!is_err(stream)) js_set_slot_wb(js, this, SLOT_REQUEST_BODY_STREAM, stream); + return stream; +REQ_GETTER_END + +REQ_GETTER_START(body_used) + return js_bool(d->body_used); +REQ_GETTER_END + +#undef REQ_GETTER_START +#undef REQ_GETTER_END + +static ant_value_t js_request_clone(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t this = js_getthis(js); + request_data_t *d = get_data(this); + + if (!d) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Request object"); + if (d->body_used) + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot clone a Request whose body is unusable"); + + request_data_t *nd = data_dup(d); + if (!nd) return js_mkerr(js, "out of memory"); + + ant_value_t src_headers = js_get_slot(this, SLOT_REQUEST_HEADERS); + ant_value_t src_signal = js_get_slot(this, SLOT_REQUEST_SIGNAL); + + ant_value_t new_headers = headers_create_empty(js); + if (is_err(new_headers)) { data_free(nd); return new_headers; } + + headers_copy_from(js, new_headers, src_headers); + headers_set_guard(new_headers, + strcmp(nd->mode, "no-cors") == 0 + ? HEADERS_GUARD_REQUEST_NO_CORS + : HEADERS_GUARD_REQUEST + ); + headers_apply_guard(new_headers); + + ant_value_t new_signal = abort_signal_create_dependent(js, src_signal); + if (is_err(new_signal)) { data_free(nd); return new_signal; } + + ant_value_t obj = js_mkobj(js); + js_set_proto_init(obj, g_request_proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST)); + js_set_slot(obj, SLOT_DATA, ANT_PTR(nd)); + + js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, new_headers); + js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, new_signal); + + ant_value_t src_stream = js_get_slot(this, SLOT_REQUEST_BODY_STREAM); + if (rs_is_stream(src_stream)) { + ant_value_t branches = readable_stream_tee(js, src_stream); + if (!is_err(branches) && vtype(branches) == T_ARR) { + ant_value_t b1 = js_arr_get(js, branches, 0); + ant_value_t b2 = js_arr_get(js, branches, 1); + js_set_slot_wb(js, this, SLOT_REQUEST_BODY_STREAM, b1); + js_set_slot_wb(js, obj, SLOT_REQUEST_BODY_STREAM, b2); + }} + + return obj; +} + +static const char *init_str(ant_t *js, ant_value_t init, const char *key, size_t klen, ant_value_t *err_out) { + ant_value_t v = js_get(js, init, key); + if (vtype(v) == T_UNDEF) return NULL; + if (vtype(v) != T_STR) { + v = js_tostring_val(js, v); + if (is_err(v)) { *err_out = v; return NULL; } + } + return js_getstr(js, v, NULL); +} + +static ant_value_t js_request_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, "Request constructor requires 'new'"); + if (nargs < 1) + return js_mkerr_typed(js, JS_ERR_TYPE, "Request constructor requires at least 1 argument"); + + ant_value_t input = args[0]; + ant_value_t init = (nargs >= 2 && vtype(args[1]) != T_UNDEF) ? args[1] : js_mkundef(); + bool init_provided = (vtype(init) == T_OBJ || vtype(init) == T_ARR); + + request_data_t *req = NULL; + ant_value_t input_signal = js_mkundef(); + + request_data_t *src = NULL; + if (vtype(input) == T_OBJ) src = get_data(input); + + if (!src) { + if (vtype(input) != T_STR) { + input = js_tostring_val(js, input); + if (is_err(input)) return input; + } + size_t ulen; + const char *url_str = js_getstr(js, input, &ulen); + url_state_t parsed = {0}; + if (parse_url_to_state(url_str, NULL, &parsed) != 0) { + url_state_clear(&parsed); + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Invalid URL"); + } + if ((parsed.username && parsed.username[0]) || (parsed.password && parsed.password[0])) { + url_state_clear(&parsed); + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': URL includes credentials"); + } + req = data_new(); + if (!req) { url_state_clear(&parsed); return js_mkerr(js, "out of memory"); } + req->url = parsed; + } else if (vtype(input) == T_OBJ) { + req = data_dup(src); + if (!req) return js_mkerr(js, "out of memory"); + req->body_used = false; + input_signal = js_get_slot(input, SLOT_REQUEST_SIGNAL); + } + + ant_value_t err = js_mkundef(); + + if (init_provided) { + ant_value_t win = js_get(js, init, "window"); + if (vtype(win) != T_UNDEF && vtype(win) != T_NULL) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': 'window' must be null"); + } + + if (strcmp(req->mode, "navigate") == 0) { + free(req->mode); req->mode = strdup("same-origin"); + } + req->reload_navigation = false; + req->history_navigation = false; + free(req->referrer); req->referrer = strdup("client"); + free(req->referrer_policy); req->referrer_policy = strdup(""); + + const char *ref = init_str(js, init, "referrer", 8, &err); + if (is_err(err)) { data_free(req); return err; } + + if (ref) { + if (ref[0] == '\0') { + free(req->referrer); + req->referrer = strdup("no-referrer"); + } else { + url_state_t rs = {0}; + if (parse_url_to_state(ref, NULL, &rs) == 0) { + char *href = build_href(&rs); + url_state_clear(&rs); + free(req->referrer); + req->referrer = href ? href : strdup("client"); + } else { + url_state_clear(&rs); + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Invalid referrer URL"); + } + }} + + const char *rp = init_str(js, init, "referrerPolicy", 14, &err); + if (is_err(err)) { data_free(req); return err; } + if (rp) { free(req->referrer_policy); req->referrer_policy = strdup(rp); } + + const char *mode_val = init_str(js, init, "mode", 4, &err); + if (is_err(err)) { data_free(req); return err; } + if (mode_val) { + if (strcmp(mode_val, "navigate") == 0) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': mode 'navigate' is not allowed"); + } + free(req->mode); req->mode = strdup(mode_val); + } + + const char *cred = init_str(js, init, "credentials", 11, &err); + if (is_err(err)) { data_free(req); return err; } + if (cred) { free(req->credentials); req->credentials = strdup(cred); } + + const char *cache_val = init_str(js, init, "cache", 5, &err); + if (is_err(err)) { data_free(req); return err; } + if (cache_val) { + free(req->cache); req->cache = strdup(cache_val); + if ( + strcmp(req->cache, "only-if-cached") == 0 && + strcmp(req->mode, "same-origin") != 0 + ) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, + "Failed to construct 'Request': cache mode 'only-if-cached' requires mode 'same-origin'"); + }} + + const char *redir = init_str(js, init, "redirect", 8, &err); + if (is_err(err)) { data_free(req); return err; } + if (redir) { free(req->redirect); req->redirect = strdup(redir); } + + const char *integ = init_str(js, init, "integrity", 9, &err); + if (is_err(err)) { data_free(req); return err; } + if (integ) { free(req->integrity); req->integrity = strdup(integ); } + + ant_value_t ka = js_get(js, init, "keepalive"); + if (vtype(ka) != T_UNDEF) req->keepalive = js_truthy(js, ka); + + const char *method_val = init_str(js, init, "method", 6, &err); + if (is_err(err)) { data_free(req); return err; } + if (method_val) { + if (!is_valid_method(method_val)) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Invalid method"); + } + if (is_forbidden_method(method_val)) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Forbidden method"); + } + free(req->method); + req->method = strdup(method_val); + normalize_method(req->method); + } + + ant_value_t sig_val = js_get(js, init, "signal"); + if (vtype(sig_val) != T_UNDEF) { + if (vtype(sig_val) == T_NULL) input_signal = js_mkundef(); + else if (abort_signal_is_signal(sig_val)) input_signal = sig_val; else { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': signal must be an AbortSignal"); + }} + } + + if ( + strcmp(req->mode, "no-cors") == 0 && + !is_cors_safelisted_method(req->method) + ) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, + "Failed to construct 'Request': method must be one of GET, HEAD, POST for no-cors mode"); + } + + ant_value_t obj = js_mkobj(js); + ant_value_t proto = js_instance_proto_from_new_target(js, g_request_proto); + + if (is_object_type(proto)) js_set_proto_init(obj, proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST)); + js_set_slot(obj, SLOT_DATA, ANT_PTR(req)); + + ant_value_t signal = abort_signal_create_dependent(js, input_signal); + if (is_err(signal)) { data_free(req); return signal; } + js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, signal); + + ant_value_t headers; + if (vtype(input) == T_OBJ) { + ant_value_t src_hdrs = js_get_slot(input, SLOT_REQUEST_HEADERS); + headers = headers_create_empty(js); + if (is_err(headers)) { data_free(req); return headers; } + headers_copy_from(js, headers, src_hdrs); + } else { + headers = headers_create_empty(js); + if (is_err(headers)) { data_free(req); return headers; } + } + + if (init_provided) { + ant_value_t init_headers = js_get(js, init, "headers"); + + if (vtype(init_headers) != T_UNDEF) { + ant_value_t new_hdrs = headers_create_empty(js); + if (is_err(new_hdrs)) { data_free(req); return new_hdrs; } + + uint8_t ht = vtype(init_headers); + if (headers_is_headers(init_headers)) { + headers_copy_from(js, new_hdrs, init_headers); + } else if (ht == T_ARR) { + ant_offset_t len = js_arr_len(js, init_headers); + + for (ant_offset_t i = 0; i < len; i++) { + ant_value_t pair = js_arr_get(js, init_headers, i); + if (js_arr_len(js, pair) < 2) continue; + ant_value_t r = headers_append_value(js, new_hdrs, js_arr_get(js, pair, 0), js_arr_get(js, pair, 1)); + if (is_err(r)) { data_free(req); return r; } + } + } else if (ht == T_OBJ) { + ant_iter_t it = js_prop_iter_begin(js, init_headers); + 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_value(js, new_hdrs, js_mkstr(js, key, key_len), val); + if (is_err(r)) { js_prop_iter_end(&it); data_free(req); return r; } + } js_prop_iter_end(&it); + } headers = new_hdrs; + }} + + headers_set_guard(headers, + strcmp(req->mode, "no-cors") == 0 + ? HEADERS_GUARD_REQUEST_NO_CORS + : HEADERS_GUARD_REQUEST + ); + headers_apply_guard(headers); + js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, headers); + + if (init_provided) { + ant_value_t duplex_val = js_get(js, init, "duplex"); + bool duplex_provided = vtype(duplex_val) != T_UNDEF; + if (duplex_provided) { + ant_value_t duplex_str_v = duplex_val; + if (vtype(duplex_str_v) != T_STR) { + duplex_str_v = js_tostring_val(js, duplex_str_v); + if (is_err(duplex_str_v)) { data_free(req); return duplex_str_v; } + } + const char *duplex_str = js_getstr(js, duplex_str_v, NULL); + if (!duplex_str || strcmp(duplex_str, "half") != 0) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, + "Failed to construct 'Request': duplex must be 'half'"); + }} + + ant_value_t body_val = js_get(js, init, "body"); + bool init_body_present = vtype(body_val) != T_UNDEF; + bool input_body_present = src && src->has_body; + bool effective_body_present = (init_body_present && vtype(body_val) != T_NULL) + + || (input_body_present && (!init_body_present || vtype(body_val) == T_NULL)); + if ((strcmp(req->method, "GET") == 0 || strcmp(req->method, "HEAD") == 0) && + effective_body_present) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, + "Failed to construct 'Request': Request with GET/HEAD method cannot have body"); + } + if (vtype(body_val) != T_UNDEF) { + if (vtype(body_val) != T_NULL) { + request_data_t *init_req = get_data(init); + ant_value_t body_err = js_mkundef(); + uint8_t *bd = NULL; size_t bs = 0; char *bt = NULL; + ant_value_t body_stream = js_mkundef(); + if (init_req && !init_req->body_used && !init_req->body_is_stream && init_req->has_body) { + bd = malloc(init_req->body_size); + if (init_req->body_size > 0 && !bd) { + data_free(req); + return js_mkerr(js, "out of memory"); + } + if (init_req->body_size > 0) memcpy(bd, init_req->body_data, init_req->body_size); + bs = init_req->body_size; + bt = init_req->body_type ? strdup(init_req->body_type) : NULL; + } else if (!extract_body(js, body_val, &bd, &bs, &bt, &body_stream, &body_err)) { + data_free(req); + return is_err(body_err) ? body_err : js_mkerr(js, "Failed to extract body"); + } + free(req->body_data); free(req->body_type); + req->body_data = bd; + req->body_size = bs; + req->body_type = bt; + req->body_is_stream = rs_is_stream(body_stream); + req->has_body = true; + if (rs_is_stream(body_stream)) { + if (req->keepalive) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, + "Failed to construct 'Request': keepalive cannot be used with a ReadableStream body"); + } + if (!duplex_provided) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, + "Failed to construct 'Request': duplex must be provided for a ReadableStream body"); + } + js_set_slot_wb(js, obj, SLOT_REQUEST_BODY_STREAM, body_stream); + } + + if (bt && bt[0]) + headers_append_if_missing(headers, "content-type", bt); + } else { + free(req->body_data); req->body_data = NULL; + req->body_size = 0; + free(req->body_type); req->body_type = NULL; + req->body_is_stream = false; + req->has_body = false; + js_set_slot_wb(js, obj, SLOT_REQUEST_BODY_STREAM, js_mkundef()); + } + } + } else if (src) { + if (src && src->body_used) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot construct Request with unusable body"); + } + ant_value_t src_stream = js_get_slot(input, SLOT_REQUEST_BODY_STREAM); + if (rs_is_stream(src_stream) && rs_stream_unusable(src_stream)) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked"); + } + if (!src->body_is_stream) { + req->body_data = malloc(src->body_size); + if (src->body_size > 0 && !req->body_data) { data_free(req); return js_mkerr(js, "out of memory"); } + if (src->body_size > 0) memcpy(req->body_data, src->body_data, src->body_size); + req->body_size = src->body_size; + req->body_type = src->body_type ? strdup(src->body_type) : NULL; + req->body_is_stream = false; + req->has_body = true; + } else if (rs_is_stream(src_stream)) { + if (rs_stream_unusable(src_stream)) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked"); + } + ant_value_t branches = readable_stream_tee(js, src_stream); + if (is_err(branches)) { data_free(req); return branches; } + if (vtype(branches) != T_ARR) { + data_free(req); + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': tee() did not return branches"); + } + ant_value_t b2 = js_arr_get(js, branches, 1); + js_set_slot_wb(js, obj, SLOT_REQUEST_BODY_STREAM, b2); + + req->body_is_stream = true; + req->has_body = true; + } + } + + if (src && src->has_body && !src->body_used) + src->body_used = true; + + return obj; +} + +ant_value_t request_create(ant_t *js, + const char *method, const char *url, + ant_value_t headers_obj, const uint8_t *body, size_t body_len, + const char *body_type) { + request_data_t *req = data_new(); + if (!req) return js_mkerr(js, "out of memory"); + + free(req->method); + req->method = strdup(method ? method : "GET"); + free(req->mode); + req->mode = strdup("same-origin"); + + url_state_t parsed = {0}; + if (url && parse_url_to_state(url, NULL, &parsed) == 0) req->url = parsed; + else url_state_clear(&parsed); + + if (body) req->has_body = true; + + if (body && body_len > 0) { + req->body_data = malloc(body_len); + if (!req->body_data) { data_free(req); return js_mkerr(js, "out of memory"); } + memcpy(req->body_data, body, body_len); + req->body_size = body_len; + req->body_type = body_type ? strdup(body_type) : NULL; + } + req->body_is_stream = false; + + ant_value_t obj = js_mkobj(js); + js_set_proto_init(obj, g_request_proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST)); + js_set_slot(obj, SLOT_DATA, ANT_PTR(req)); + + ant_value_t hdrs = is_object_type(headers_obj) + ? headers_obj + : headers_create_empty(js); + + headers_set_guard(hdrs, + strcmp(req->mode, "no-cors") == 0 + ? HEADERS_GUARD_REQUEST_NO_CORS + : HEADERS_GUARD_REQUEST + ); + + headers_apply_guard(hdrs); + js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, hdrs); + + ant_value_t sig = abort_signal_create_dependent(js, js_mkundef()); + js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, sig); + + return obj; +} + +void init_request_module(void) { + ant_t *js = rt->js; + ant_value_t g = js_glob(js); + g_request_proto = js_mkobj(js); + + js_set(js, g_request_proto, "text", js_mkfun(js_req_text)); + js_set(js, g_request_proto, "json", js_mkfun(js_req_json)); + js_set(js, g_request_proto, "arrayBuffer", js_mkfun(js_req_array_buffer)); + js_set(js, g_request_proto, "blob", js_mkfun(js_req_blob)); + js_set(js, g_request_proto, "formData", js_mkfun(js_req_form_data)); + js_set(js, g_request_proto, "bytes", js_mkfun(js_req_bytes)); + js_set(js, g_request_proto, "clone", js_mkfun(js_request_clone)); + +#define GETTER(prop, fn) \ + js_set_getter_desc(js, g_request_proto, prop, sizeof(prop)-1, js_mkfun(js_req_get_##fn), JS_DESC_C) + GETTER("method", method); + GETTER("url", url); + GETTER("headers", headers); + GETTER("destination", destination); + GETTER("referrer", referrer); + GETTER("referrerPolicy", referrer_policy); + GETTER("mode", mode); + GETTER("credentials", credentials); + GETTER("cache", cache); + GETTER("redirect", redirect); + GETTER("integrity", integrity); + GETTER("keepalive", keepalive); + GETTER("isReloadNavigation",is_reload_navigation); + GETTER("isHistoryNavigation",is_history_navigation); + GETTER("signal", signal); + GETTER("duplex", duplex); + GETTER("body", body); + GETTER("bodyUsed", body_used); +#undef GETTER + + js_set_sym(js, g_request_proto, get_toStringTag_sym(), js_mkstr(js, "Request", 7)); + ant_value_t ctor = js_make_ctor(js, js_request_ctor, g_request_proto, "Request", 7); + + js_set(js, g, "Request", ctor); + js_set_descriptor(js, g, "Request", 7, JS_DESC_W | JS_DESC_C); +} diff --git a/src/modules/textcodec.c b/src/modules/textcodec.c index 4652acb..ad79333 100644 --- a/src/modules/textcodec.c +++ b/src/modules/textcodec.c @@ -78,43 +78,6 @@ static const char *trim_label(const char *s, size_t len, size_t *out_len) { return s; } -static bool get_buffer_source(ant_t *js, ant_value_t arg, const uint8_t **out, size_t *len) { - ant_value_t slot = js_get_slot(arg, SLOT_BUFFER); - TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(slot); - if (ta) { - if (!ta->buffer || ta->buffer->is_detached) { *out = NULL; *len = 0; return true; } - *out = ta->buffer->data + ta->byte_offset; - *len = ta->byte_length; - return true; - } - - if (vtype(slot) == T_NUM) { - ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(slot); - if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; } - *out = ab->data; - *len = ab->length; - return true; - } - - ant_value_t buf_prop = js_get(js, arg, "buffer"); - if (is_object_type(buf_prop)) { - ant_value_t buf_slot = js_get_slot(buf_prop, SLOT_BUFFER); - - if (vtype(buf_slot) == T_NUM) { - ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(buf_slot); - if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; } - ant_value_t off_v = js_get(js, arg, "byteOffset"); - ant_value_t len_v = js_get(js, arg, "byteLength"); - size_t off = (vtype(off_v) == T_NUM) ? (size_t)js_getnum(off_v) : 0; - size_t blen = (vtype(len_v) == T_NUM) ? (size_t)js_getnum(len_v) : ab->length - off; - *out = ab->data + off; - *len = blen; - return true; - }} - - return false; -} - static ant_value_t js_textencoder_get_encoding(ant_t *js, ant_value_t *args, int nargs) { return js_mkstr(js, "utf-8", 5); } @@ -420,7 +383,7 @@ static ant_value_t js_textdecoder_decode(ant_t *js, ant_value_t *args, int nargs const uint8_t *input = NULL; size_t input_len = 0; if (nargs > 0 && is_object_type(args[0])) - get_buffer_source(js, args[0], &input, &input_len); + buffer_source_get_bytes(js, args[0], &input, &input_len); return td_decode(js, st, input, input_len, stream_mode); } diff --git a/src/modules/url.c b/src/modules/url.c index ccc2ed5..d4f50d5 100644 --- a/src/modules/url.c +++ b/src/modules/url.c @@ -32,6 +32,11 @@ url_state_t *url_get_state(ant_value_t obj) { return (url_state_t *)(uintptr_t)(size_t)js_getnum(slot); } +bool usp_is_urlsearchparams(ant_t *js, ant_value_t obj) { + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + return vtype(brand) == T_NUM && (int)js_getnum(brand) == BRAND_URLSEARCHPARAMS; +} + void url_state_clear(url_state_t *s) { free(s->protocol); free(s->username); free(s->password); free(s->hostname); free(s->port); free(s->pathname); @@ -441,6 +446,7 @@ static void url_sync_usp(ant_t *js, ant_value_t url_obj, const char *query) { static ant_value_t make_usp_for_url(ant_t *js, ant_value_t url_obj, const char *query) { ant_value_t usp = js_mkobj(js); js_set_proto_init(usp, g_usp_proto); + js_set_slot(usp, SLOT_BRAND, js_mknum(BRAND_URLSEARCHPARAMS)); js_set_slot_wb(js, usp, SLOT_DATA, url_obj); js_set_slot(usp, SLOT_ENTRIES, parse_query_to_arr(js, query)); return usp; @@ -1116,11 +1122,13 @@ static ant_value_t usp_values_fn(ant_t *js, ant_value_t *args, int nargs) { static ant_value_t js_URLSearchParams(ant_t *js, ant_value_t *args, int nargs) { if (is_undefined(js->new_target)) return js_mkerr_typed(js, JS_ERR_TYPE, - "Failed to construct 'URLSearchParams': Please use the 'new' operator."); + "Failed to construct 'URLSearchParams': Please use the 'new' operator."); ant_value_t obj = js_mkobj(js); js_set_proto_init(obj, g_usp_proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_URLSEARCHPARAMS)); js_set_slot(obj, SLOT_DATA, js_mkundef()); + ant_value_t entries = js_mkarr(js); js_set_slot(obj, SLOT_ENTRIES, entries); diff --git a/src/streams/codec.c b/src/streams/codec.c index 1ae0d59..dd4f2ca 100644 --- a/src/streams/codec.c +++ b/src/streams/codec.c @@ -77,42 +77,6 @@ static void tds_state_finalize(ant_t *js, ant_object_t *obj) { }} } -static bool get_chunk_bytes(ant_t *js, ant_value_t chunk, const uint8_t **out, size_t *len) { - ant_value_t slot = js_get_slot(chunk, SLOT_BUFFER); - TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(slot); - if (ta) { - if (!ta->buffer || ta->buffer->is_detached) { *out = NULL; *len = 0; return true; } - *out = ta->buffer->data + ta->byte_offset; - *len = ta->byte_length; - return true; - } - - if (vtype(slot) == T_NUM) { - ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(slot); - if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; } - *out = ab->data; - *len = ab->length; - return true; - } - - ant_value_t buf_prop = js_get(js, chunk, "buffer"); - if (is_object_type(buf_prop)) { - ant_value_t buf_slot = js_get_slot(buf_prop, SLOT_BUFFER); - if (vtype(buf_slot) == T_NUM) { - ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(buf_slot); - if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; } - ant_value_t off_v = js_get(js, chunk, "byteOffset"); - ant_value_t len_v = js_get(js, chunk, "byteLength"); - size_t off = (vtype(off_v) == T_NUM) ? (size_t)js_getnum(off_v) : 0; - size_t blen = (vtype(len_v) == T_NUM) ? (size_t)js_getnum(len_v) : ab->length - off; - *out = ab->data + off; - *len = blen; - return true; - }} - - return false; -} - static ant_value_t codec_transform_controller(ant_value_t *args, int nargs) { return (nargs > 1) ? args[1] : js_mkundef(); } @@ -293,7 +257,7 @@ static ant_value_t tds_transform(ant_t *js, ant_value_t *args, int nargs) { const uint8_t *input = NULL; size_t input_len = 0; - if (!is_object_type(chunk) || !get_chunk_bytes(js, chunk, &input, &input_len)) + if (!is_object_type(chunk) || !buffer_source_get_bytes(js, chunk, &input, &input_len)) return js_mkerr_typed(js, JS_ERR_TYPE, "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'"); ant_value_t result = td_decode(js, st, input, input_len, true); diff --git a/src/streams/compression.c b/src/streams/compression.c index 5f0f4e2..2dc5f7a 100644 --- a/src/streams/compression.c +++ b/src/streams/compression.c @@ -119,42 +119,6 @@ static void brotli_state_finalize(ant_t *js, ant_object_t *obj) { }} } -static bool get_chunk_bytes(ant_t *js, ant_value_t chunk, const uint8_t **out, size_t *len) { - ant_value_t slot = js_get_slot(chunk, SLOT_BUFFER); - TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(slot); - if (ta) { - if (!ta->buffer || ta->buffer->is_detached) { *out = NULL; *len = 0; return true; } - *out = ta->buffer->data + ta->byte_offset; - *len = ta->byte_length; - return true; - } - - if (vtype(slot) == T_NUM) { - ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(slot); - if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; } - *out = ab->data; - *len = ab->length; - return true; - } - - ant_value_t buf_prop = js_get(js, chunk, "buffer"); - if (is_object_type(buf_prop)) { - ant_value_t buf_slot = js_get_slot(buf_prop, SLOT_BUFFER); - if (vtype(buf_slot) == T_NUM) { - ArrayBufferData *ab = (ArrayBufferData *)(uintptr_t)(size_t)js_getnum(buf_slot); - if (!ab || ab->is_detached) { *out = NULL; *len = 0; return true; } - ant_value_t off_v = js_get(js, chunk, "byteOffset"); - ant_value_t len_v = js_get(js, chunk, "byteLength"); - size_t off = (vtype(off_v) == T_NUM) ? (size_t)js_getnum(off_v) : 0; - size_t blen = (vtype(len_v) == T_NUM) ? (size_t)js_getnum(len_v) : ab->length - off; - *out = ab->data + off; - *len = blen; - return true; - }} - - return false; -} - static ant_value_t enqueue_buffer(ant_t *js, ant_value_t ctrl_obj, const uint8_t *data, size_t len) { ArrayBufferData *ab = create_array_buffer_data(len); if (!ab) return js_mkerr(js, "out of memory"); @@ -178,7 +142,7 @@ static ant_value_t cs_transform(ant_t *js, ant_value_t *args, int nargs) { const uint8_t *input = NULL; size_t input_len = 0; - if (!is_object_type(chunk) || !get_chunk_bytes(js, chunk, &input, &input_len)) + if (!is_object_type(chunk) || !buffer_source_get_bytes(js, chunk, &input, &input_len)) return js_mkerr_typed(js, JS_ERR_TYPE, "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'"); if (!input || input_len == 0) return js_mkundef(); @@ -322,7 +286,7 @@ static ant_value_t ds_transform(ant_t *js, ant_value_t *args, int nargs) { const uint8_t *input = NULL; size_t input_len = 0; - if (!is_object_type(chunk) || !get_chunk_bytes(js, chunk, &input, &input_len)) + if (!is_object_type(chunk) || !buffer_source_get_bytes(js, chunk, &input, &input_len)) return js_mkerr_typed(js, JS_ERR_TYPE, "The provided value is not of type '(ArrayBuffer or ArrayBufferView)'"); if (!input || input_len == 0) return js_mkundef(); diff --git a/src/streams/pipes.c b/src/streams/pipes.c index ebd1e02..d877a03 100644 --- a/src/streams/pipes.c +++ b/src/streams/pipes.c @@ -774,6 +774,16 @@ static ant_value_t js_rs_tee(ant_t *js, ant_value_t *args, int nargs) { return result; } +ant_value_t readable_stream_tee(ant_t *js, ant_value_t source) { + ant_value_t saved_this = js->this_val; + js->this_val = source; + + ant_value_t result = js_rs_tee(js, NULL, 0); + js->this_val = saved_this; + + return result; +} + void init_pipes_proto(ant_t *js, ant_value_t rs_proto) { js_set(js, rs_proto, "pipeTo", js_mkfun(js_rs_pipe_to)); js_set_descriptor(js, rs_proto, "pipeTo", 6, JS_DESC_W | JS_DESC_C); diff --git a/src/streams/readable.c b/src/streams/readable.c index 9190fb2..1edf011 100644 --- a/src/streams/readable.c +++ b/src/streams/readable.c @@ -18,21 +18,44 @@ ant_value_t g_reader_proto; ant_value_t g_controller_proto; bool rs_is_stream(ant_value_t obj) { - return is_object_type(obj) && rs_get_stream(obj) != NULL; + if (!is_object_type(obj)) return false; + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + return vtype(brand) == T_NUM + && (int)js_getnum(brand) == BRAND_READABLE_STREAM + && rs_get_stream(obj) != NULL; } bool rs_is_reader(ant_value_t obj) { - return is_object_type(obj) + if (!is_object_type(obj)) return false; + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + return vtype(brand) == T_NUM + && (int)js_getnum(brand) == BRAND_READABLE_STREAM_READER && vtype(js_get_slot(obj, SLOT_RS_CLOSED)) == T_PROMISE && vtype(js_get_slot(obj, SLOT_BUFFER)) == T_ARR; } bool rs_is_controller(ant_value_t obj) { - return is_object_type(obj) + if (!is_object_type(obj)) return false; + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + return vtype(brand) == T_NUM + && (int)js_getnum(brand) == BRAND_READABLE_STREAM_CONTROLLER && rs_get_controller(obj) != NULL && rs_is_stream(js_get_slot(obj, SLOT_ENTRIES)); } +bool rs_stream_locked(ant_value_t stream_obj) { + return rs_is_stream(stream_obj) && rs_is_reader(rs_stream_reader(stream_obj)); +} + +bool rs_stream_disturbed(ant_value_t stream_obj) { + rs_stream_t *stream = rs_get_stream(stream_obj); + return stream && stream->disturbed; +} + +bool rs_stream_unusable(ant_value_t stream_obj) { + return rs_stream_locked(stream_obj) || rs_stream_disturbed(stream_obj); +} + rs_stream_t *rs_get_stream(ant_value_t obj) { ant_value_t s = js_get_slot(obj, SLOT_DATA); if (vtype(s) != T_NUM) return NULL; @@ -662,6 +685,7 @@ ant_value_t js_rs_reader_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t proto = js_instance_proto_from_new_target(js, g_reader_proto); if (is_object_type(proto)) js_set_proto_init(obj, proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM_READER)); js_set_slot(obj, SLOT_ENTRIES, stream_obj); js_set_slot(obj, SLOT_RS_CLOSED, closed); js_set_slot(obj, SLOT_BUFFER, js_mkarr(js)); @@ -759,6 +783,7 @@ static ant_value_t setup_default_controller( ant_value_t ctrl_obj = js_mkobj(js); js_set_proto_init(ctrl_obj, g_controller_proto); + js_set_slot(ctrl_obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM_CONTROLLER)); js_set_slot(ctrl_obj, SLOT_DATA, ANT_PTR(ctrl)); js_set_slot(ctrl_obj, SLOT_ENTRIES, stream_obj); js_set_slot(ctrl_obj, SLOT_RS_PULL, pull_fn); @@ -827,7 +852,9 @@ static ant_value_t js_rs_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t obj = js_mkobj(js); ant_value_t proto = js_instance_proto_from_new_target(js, g_rs_proto); + if (is_object_type(proto)) js_set_proto_init(obj, proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM)); js_set_slot(obj, SLOT_DATA, ANT_PTR(st)); js_set_finalizer(obj, rs_stream_finalize); @@ -907,6 +934,7 @@ ant_value_t rs_create_stream(ant_t *js, ant_value_t pull_fn, ant_value_t cancel_ ant_value_t obj = js_mkobj(js); js_set_proto_init(obj, g_rs_proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM)); js_set_slot(obj, SLOT_DATA, ANT_PTR(st)); js_set_finalizer(obj, rs_stream_finalize); diff --git a/src/streams/transform.c b/src/streams/transform.c index aa2b3c2..e6083dc 100644 --- a/src/streams/transform.c +++ b/src/streams/transform.c @@ -19,14 +19,19 @@ ant_value_t g_ts_ctrl_proto; bool ts_is_controller(ant_value_t obj) { if (!is_object_type(obj)) return false; + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + if (vtype(brand) != T_NUM || (int)js_getnum(brand) != BRAND_TRANSFORM_STREAM_CONTROLLER) + return false; ant_value_t ts_obj = js_get_slot(obj, SLOT_DATA); return is_object_type(ts_obj) - && vtype(js_get_slot(ts_obj, SLOT_DATA)) == T_NUM + && ts_is_stream(ts_obj) && js_get_slot(ts_obj, SLOT_DEFAULT) == obj; } bool ts_is_stream(ant_value_t obj) { return is_object_type(obj) + && vtype(js_get_slot(obj, SLOT_BRAND)) == T_NUM + && (int)js_getnum(js_get_slot(obj, SLOT_BRAND)) == BRAND_TRANSFORM_STREAM && vtype(js_get_slot(obj, SLOT_DATA)) == T_NUM && rs_is_stream(js_get_slot(obj, SLOT_ENTRIES)) && ws_is_stream(js_get_slot(obj, SLOT_CTOR)) @@ -919,10 +924,12 @@ ant_value_t js_ts_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t ts_obj = js_mkobj(js); ant_value_t proto = js_instance_proto_from_new_target(js, g_ts_proto); if (is_object_type(proto)) js_set_proto_init(ts_obj, proto); + js_set_slot(ts_obj, SLOT_BRAND, js_mknum(BRAND_TRANSFORM_STREAM)); js_set_slot(ts_obj, SLOT_DATA, js_mknum(0)); ant_value_t ctrl_obj = js_mkobj(js); js_set_proto_init(ctrl_obj, g_ts_ctrl_proto); + js_set_slot(ctrl_obj, SLOT_BRAND, js_mknum(BRAND_TRANSFORM_STREAM_CONTROLLER)); js_set_slot(ctrl_obj, SLOT_DATA, ts_obj); js_set_slot(ctrl_obj, SLOT_ENTRIES, transform_fn); js_set_slot(ctrl_obj, SLOT_CTOR, flush_fn); @@ -950,6 +957,7 @@ ant_value_t js_ts_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t rs_obj = js_mkobj(js); js_set_proto_init(rs_obj, g_rs_proto); + js_set_slot(rs_obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM)); js_set_slot(rs_obj, SLOT_DATA, ANT_PTR(rst)); js_set_finalizer(rs_obj, ts_rs_finalize); @@ -959,6 +967,7 @@ ant_value_t js_ts_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t rs_ctrl_obj = js_mkobj(js); js_set_proto_init(rs_ctrl_obj, g_controller_proto); + js_set_slot(rs_ctrl_obj, SLOT_BRAND, js_mknum(BRAND_READABLE_STREAM_CONTROLLER)); js_set_slot(rs_ctrl_obj, SLOT_DATA, ANT_PTR(rcc)); js_set_slot(rs_ctrl_obj, SLOT_ENTRIES, rs_obj); js_set_slot(rs_ctrl_obj, SLOT_RS_PULL, source_pull); @@ -976,6 +985,7 @@ ant_value_t js_ts_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t ws_obj = js_mkobj(js); js_set_proto_init(ws_obj, g_ws_proto); + js_set_slot(ws_obj, SLOT_BRAND, js_mknum(BRAND_WRITABLE_STREAM)); js_set_slot(ws_obj, SLOT_DATA, ANT_PTR(wst)); js_set_slot(ws_obj, SLOT_SETTLED, js_mkarr(js)); js_set_finalizer(ws_obj, ts_ws_finalize); @@ -993,6 +1003,7 @@ ant_value_t js_ts_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t ws_ctrl_obj = js_mkobj(js); js_set_proto_init(ws_ctrl_obj, g_ws_controller_proto); + js_set_slot(ws_ctrl_obj, SLOT_BRAND, js_mknum(BRAND_WRITABLE_STREAM_CONTROLLER)); js_set_slot(ws_ctrl_obj, SLOT_DATA, ANT_PTR(wc)); js_set_slot(ws_ctrl_obj, SLOT_ENTRIES, ws_obj); js_set_slot(ws_ctrl_obj, SLOT_WS_WRITE, sink_write); diff --git a/src/streams/writable.c b/src/streams/writable.c index ee533f4..2d8cc91 100644 --- a/src/streams/writable.c +++ b/src/streams/writable.c @@ -19,17 +19,27 @@ ant_value_t g_ws_controller_proto; static ant_value_t g_close_sentinel; bool ws_is_stream(ant_value_t obj) { - return is_object_type(obj) && ws_get_stream(obj) != NULL; + if (!is_object_type(obj)) return false; + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + return vtype(brand) == T_NUM + && (int)js_getnum(brand) == BRAND_WRITABLE_STREAM + && ws_get_stream(obj) != NULL; } bool ws_is_writer(ant_value_t obj) { - return is_object_type(obj) + if (!is_object_type(obj)) return false; + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + return vtype(brand) == T_NUM + && (int)js_getnum(brand) == BRAND_WRITABLE_STREAM_WRITER && vtype(js_get_slot(obj, SLOT_RS_CLOSED)) == T_PROMISE && vtype(js_get_slot(obj, SLOT_WS_READY)) == T_PROMISE; } bool ws_is_controller(ant_value_t obj) { - return is_object_type(obj) + if (!is_object_type(obj)) return false; + ant_value_t brand = js_get_slot(obj, SLOT_BRAND); + return vtype(brand) == T_NUM + && (int)js_getnum(brand) == BRAND_WRITABLE_STREAM_CONTROLLER && ws_get_controller(obj) != NULL && ws_is_stream(js_get_slot(obj, SLOT_ENTRIES)); } @@ -971,7 +981,9 @@ ant_value_t js_ws_writer_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t obj = js_mkobj(js); ant_value_t proto = js_instance_proto_from_new_target(js, g_ws_writer_proto); + if (is_object_type(proto)) js_set_proto_init(obj, proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_WRITABLE_STREAM_WRITER)); ant_value_t closed = js_mkpromise(js); ant_value_t ready = js_mkpromise(js); @@ -1072,6 +1084,7 @@ static ant_value_t setup_ws_default_controller( ant_value_t ctrl_obj = js_mkobj(js); js_set_proto_init(ctrl_obj, g_ws_controller_proto); + js_set_slot(ctrl_obj, SLOT_BRAND, js_mknum(BRAND_WRITABLE_STREAM_CONTROLLER)); js_set_slot(ctrl_obj, SLOT_DATA, ANT_PTR(ctrl)); js_set_slot(ctrl_obj, SLOT_ENTRIES, stream_obj); js_set_slot(ctrl_obj, SLOT_WS_WRITE, write_fn); @@ -1151,7 +1164,9 @@ static ant_value_t js_ws_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t obj = js_mkobj(js); ant_value_t proto = js_instance_proto_from_new_target(js, g_ws_proto); + if (is_object_type(proto)) js_set_proto_init(obj, proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_WRITABLE_STREAM)); js_set_slot(obj, SLOT_DATA, ANT_PTR(st)); js_set_slot(obj, SLOT_SETTLED, js_mkarr(js)); js_set_finalizer(obj, ws_stream_finalize);