MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3#include <strings.h>
4#include <ctype.h>
5#include <stdio.h>
6
7#include "ant.h"
8#include "ptr.h"
9#include "errors.h"
10#include "runtime.h"
11#include "internal.h"
12#include "common.h"
13#include "descriptors.h"
14
15#include "modules/blob.h"
16#include "modules/buffer.h"
17#include "modules/assert.h"
18#include "modules/abort.h"
19#include "modules/formdata.h"
20#include "modules/headers.h"
21#include "modules/multipart.h"
22#include "modules/request.h"
23#include "modules/symbol.h"
24#include "modules/url.h"
25#include "modules/json.h"
26#include "streams/pipes.h"
27#include "streams/readable.h"
28
29ant_value_t g_request_proto = 0;
30
31enum { REQUEST_NATIVE_TAG = 0x52455153u }; // REQS
32
33static request_data_t *get_data(ant_value_t obj) {
34 return (request_data_t *)js_get_native(obj, REQUEST_NATIVE_TAG);
35}
36
37request_data_t *request_get_data(ant_value_t obj) {
38 return get_data(obj);
39}
40
41ant_value_t request_get_headers(ant_value_t obj) {
42 return js_get_slot(obj, SLOT_REQUEST_HEADERS);
43}
44
45ant_value_t request_get_signal(ant_t *js, ant_value_t obj) {
46 ant_value_t signal = js_get_slot(obj, SLOT_REQUEST_SIGNAL);
47
48 if (vtype(signal) != T_UNDEF) return signal;
49 signal = abort_signal_create_dependent(js, js_mkundef());
50
51 if (is_err(signal)) return signal;
52 ant_value_t abort_reason = js_get_slot(obj, SLOT_REQUEST_ABORT_REASON);
53
54 if (vtype(abort_reason) != T_UNDEF) signal_do_abort(js, signal, abort_reason);
55 js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, signal);
56
57 return signal;
58}
59
60static void data_free(request_data_t *d) {
61 if (!d) return;
62 free(d->method);
63 url_state_clear(&d->url);
64 free(d->referrer);
65 free(d->referrer_policy);
66 free(d->mode);
67 free(d->credentials);
68 free(d->cache);
69 free(d->redirect);
70 free(d->integrity);
71 free(d->body_data);
72 free(d->body_type);
73 free(d);
74}
75
76static void request_finalize(ant_t *js, ant_object_t *obj) {
77 ant_value_t value = js_obj_from_ptr(obj);
78 request_data_t *data = get_data(value);
79 data_free(data);
80 js_clear_native(value, REQUEST_NATIVE_TAG);
81}
82
83static void request_clear_and_free(ant_value_t obj, request_data_t *data) {
84 if (obj) js_clear_native(obj, REQUEST_NATIVE_TAG);
85 data_free(data);
86}
87
88static request_data_t *data_new_with(const char *method, const char *mode) {
89 request_data_t *d = calloc(1, sizeof(request_data_t));
90 if (!d) return NULL;
91
92 d->method = strdup(method ? method : "GET");
93 d->referrer = strdup("client");
94 d->referrer_policy = strdup("");
95 d->mode = strdup(mode);
96 d->credentials = strdup("same-origin");
97 d->cache = strdup("default");
98 d->redirect = strdup("follow");
99 d->integrity = strdup("");
100
101 if (!d->method
102 || !d->referrer
103 || !d->referrer_policy
104 || !d->mode
105 || !d->credentials
106 || !d->cache
107 || !d->redirect
108 || !d->integrity
109 ) { data_free(d); return NULL; }
110
111 return d;
112}
113
114static request_data_t *data_new(void) { return data_new_with("GET", "cors"); }
115static request_data_t *data_new_server(const char *method) { return data_new_with(method, "same-origin"); }
116
117static ant_value_t request_create_object(ant_t *js, request_data_t *req, ant_value_t headers_obj, bool create_signal) {
118 ant_value_t obj = js_mkobj(js);
119
120 ant_value_t hdrs = is_object_type(headers_obj)
121 ? headers_obj
122 : headers_create_empty(js);
123
124 if (is_err(hdrs)) {
125 data_free(req);
126 return hdrs;
127 }
128
129 js_set_proto_init(obj, g_request_proto);
130 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST));
131 js_set_native(obj, req, REQUEST_NATIVE_TAG);
132 js_set_finalizer(obj, request_finalize);
133
134 headers_set_guard(hdrs,
135 strcmp(req->mode, "no-cors") == 0
136 ? HEADERS_GUARD_REQUEST_NO_CORS
137 : HEADERS_GUARD_REQUEST
138 );
139
140 headers_apply_guard(hdrs);
141 js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, hdrs);
142 js_set_slot(obj, SLOT_REQUEST_ABORT_REASON, js_mkundef());
143
144 js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, create_signal
145 ? abort_signal_create_dependent(js, js_mkundef())
146 : js_mkundef()
147 );
148
149 return obj;
150}
151
152static char *request_build_server_base_url(const char *host, const char *server_hostname, int server_port) {
153 const char *authority = (host && host[0]) ? host : server_hostname;
154 size_t authority_len = authority ? strlen(authority) : 0;
155 bool include_port = (!host || !host[0]) && server_port > 0 && server_port != 80;
156 size_t cap = sizeof("http://") - 1 + authority_len + 1 + 16 + 1;
157
158 char *base = malloc(cap);
159 int written = 0;
160
161 if (!base) return NULL;
162 if (include_port) written = snprintf(base, cap, "http://%s:%d/", authority ? authority : "", server_port);
163 else written = snprintf(base, cap, "http://%s/", authority ? authority : "");
164
165 if (written < 0 || (size_t)written >= cap) {
166 free(base);
167 return NULL;
168 }
169
170 return base;
171}
172
173static int request_parse_server_url(
174 const char *target,
175 bool absolute_target,
176 const char *host,
177 const char *server_hostname,
178 int server_port,
179 url_state_t *out
180) {
181 char *base = NULL;
182 int rc = 0;
183
184 if (absolute_target)
185 return parse_url_to_state(target, NULL, out);
186
187 base = request_build_server_base_url(host, server_hostname, server_port);
188 if (!base) return -1;
189
190 rc = parse_url_to_state(target, base, out);
191 free(base);
192
193 return rc;
194}
195
196static request_data_t *data_dup(const request_data_t *src) {
197 request_data_t *d = calloc(1, sizeof(request_data_t));
198 if (!d) return NULL;
199
200#define DUP_STR(f) do { d->f = src->f ? strdup(src->f) : NULL; } while(0)
201 DUP_STR(method);
202 DUP_STR(referrer);
203 DUP_STR(referrer_policy);
204 DUP_STR(mode);
205 DUP_STR(credentials);
206 DUP_STR(cache);
207 DUP_STR(redirect);
208 DUP_STR(integrity);
209 DUP_STR(body_type);
210#undef DUP_STR
211 url_state_t *su = (url_state_t *)&src->url;
212 url_state_t *du = &d->url;
213#define DUP_US(f) do { du->f = su->f ? strdup(su->f) : NULL; } while(0)
214 DUP_US(protocol); DUP_US(username); DUP_US(password);
215 DUP_US(hostname); DUP_US(port); DUP_US(pathname);
216 DUP_US(search); DUP_US(hash);
217#undef DUP_US
218 d->keepalive = src->keepalive;
219 d->reload_navigation = src->reload_navigation;
220 d->history_navigation = src->history_navigation;
221 d->has_body = src->has_body;
222 d->body_is_stream = src->body_is_stream;
223 d->body_used = src->body_used;
224 d->body_size = src->body_size;
225
226 if (src->body_data && src->body_size > 0) {
227 d->body_data = malloc(src->body_size);
228 if (!d->body_data) { data_free(d); return NULL; }
229 memcpy(d->body_data, src->body_data, src->body_size);
230 }
231
232 return d;
233}
234
235static bool is_token_char(unsigned char c) {
236 if (c == 0 || c > 127) return false;
237 static const char *delimiters = "(),/:;<=>?@[\\]{}\"";
238 return c > 32 && !strchr(delimiters, (char)c);
239}
240
241static bool is_valid_method(const char *m) {
242 if (!m || !*m) return false;
243 for (const unsigned char *p = (const unsigned char *)m; *p; p++)
244 if (!is_token_char(*p)) return false;
245 return true;
246}
247
248static bool is_forbidden_method(const char *m) {
249 return
250 strcasecmp(m, "CONNECT") == 0 ||
251 strcasecmp(m, "TRACE") == 0 ||
252 strcasecmp(m, "TRACK") == 0;
253}
254
255static bool is_cors_safelisted_method(const char *m) {
256 return
257 strcasecmp(m, "GET") == 0 ||
258 strcasecmp(m, "HEAD") == 0 ||
259 strcasecmp(m, "POST") == 0;
260}
261
262static void normalize_method(char *m) {
263 static const char *norm[] = {
264 "DELETE","GET","HEAD","OPTIONS","POST","PUT"
265 };
266
267 for (int i = 0; i < 6; i++) {
268 if (strcasecmp(m, norm[i]) == 0) {
269 strcpy(m, norm[i]);
270 return;
271 }}
272}
273
274static ant_value_t request_rejection_reason(ant_t *js, ant_value_t value) {
275 if (!is_err(value)) return value;
276 ant_value_t reason = js->thrown_exists ? js->thrown_value : value;
277 js->thrown_exists = false;
278 js->thrown_value = js_mkundef();
279 js->thrown_stack = js_mkundef();
280 return reason;
281}
282
283static const char *request_effective_body_type(ant_t *js, ant_value_t req_obj, request_data_t *d) {
284 ant_value_t headers = js_get_slot(req_obj, SLOT_REQUEST_HEADERS);
285 if (!headers_is_headers(headers)) return d ? d->body_type : NULL;
286 ant_value_t ct = headers_get_value(js, headers, "content-type");
287 if (vtype(ct) == T_STR) return js_getstr(js, ct, NULL);
288 return d ? d->body_type : NULL;
289}
290
291static bool copy_body_bytes(
292 ant_t *js, const uint8_t *src, size_t src_len,
293 uint8_t **out_data, size_t *out_size, ant_value_t *err_out
294) {
295 uint8_t *buf = NULL;
296
297 *out_data = NULL;
298 *out_size = 0;
299 if (src_len == 0) return true;
300
301 buf = malloc(src_len);
302 if (!buf) {
303 *err_out = js_mkerr(js, "out of memory");
304 return false;
305 }
306
307 memcpy(buf, src, src_len);
308 *out_data = buf;
309 *out_size = src_len;
310 return true;
311}
312
313static bool extract_buffer_source_body(
314 ant_t *js, ant_value_t body_val,
315 uint8_t **out_data, size_t *out_size, ant_value_t *err_out
316) {
317 const uint8_t *src = NULL;
318 size_t src_len = 0;
319
320 if (!((
321 vtype(body_val) == T_TYPEDARRAY || vtype(body_val) == T_OBJ) &&
322 buffer_source_get_bytes(js, body_val, &src, &src_len))
323 ) return false;
324
325 return copy_body_bytes(js, src, src_len, out_data, out_size, err_out);
326}
327
328static bool extract_stream_body(
329 ant_t *js, ant_value_t body_val,
330 ant_value_t *out_stream, ant_value_t *err_out
331) {
332 if (!rs_is_stream(body_val)) return false;
333 if (rs_stream_unusable(body_val)) {
334 *err_out = js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked");
335 return false;
336 }
337
338 *out_stream = body_val;
339 return true;
340}
341
342static bool extract_blob_body(
343 ant_t *js, ant_value_t body_val,
344 uint8_t **out_data, size_t *out_size, char **out_type, ant_value_t *err_out
345) {
346 blob_data_t *bd = blob_is_blob(js, body_val) ? blob_get_data(body_val) : NULL;
347 if (!bd) return false;
348
349 if (!copy_body_bytes(js, bd->data, bd->size, out_data, out_size, err_out)) return false;
350 if (bd->type && bd->type[0]) *out_type = strdup(bd->type);
351
352 return true;
353}
354
355static bool extract_urlsearchparams_body(
356 ant_t *js, ant_value_t body_val,
357 uint8_t **out_data, size_t *out_size, char **out_type
358) {
359 char *serialized = NULL;
360 if (!usp_is_urlsearchparams(js, body_val)) return false;
361
362 serialized = usp_serialize(js, body_val);
363 if (serialized) {
364 *out_data = (uint8_t *)serialized;
365 *out_size = strlen(serialized);
366 *out_type = strdup("application/x-www-form-urlencoded;charset=UTF-8");
367 }
368
369 return true;
370}
371
372static bool extract_formdata_body(
373 ant_t *js, ant_value_t body_val,
374 uint8_t **out_data, size_t *out_size, char **out_type, ant_value_t *err_out
375) {
376 char *boundary = NULL;
377 char *content_type = NULL;
378 size_t mp_size = 0;
379 uint8_t *mp = NULL;
380
381 if (!formdata_is_formdata(js, body_val)) return false;
382 mp = formdata_serialize_multipart(js, body_val, &mp_size, &boundary);
383
384 if (!mp) {
385 *err_out = js_mkerr(js, "out of memory");
386 return false;
387 }
388
389 if (mp_size > 0) *out_data = mp;
390 else free(mp);
391
392 *out_size = mp_size;
393 if (boundary) {
394 size_t ct_len = snprintf(NULL, 0, "multipart/form-data; boundary=%s", boundary);
395 content_type = malloc(ct_len + 1);
396 if (!content_type) {
397 free(boundary);
398 if (mp_size > 0) free(mp);
399 *out_data = NULL;
400 *out_size = 0;
401 *err_out = js_mkerr(js, "out of memory");
402 return false;
403 }
404 snprintf(content_type, ct_len + 1, "multipart/form-data; boundary=%s", boundary);
405 free(boundary);
406 *out_type = content_type;
407 }
408
409 return true;
410}
411
412static bool extract_string_body(
413 ant_t *js, ant_value_t body_val,
414 uint8_t **out_data, size_t *out_size, char **out_type, ant_value_t *err_out
415) {
416 size_t len = 0;
417 const char *s = NULL;
418
419 if (vtype(body_val) != T_STR) {
420 body_val = js_tostring_val(js, body_val);
421 if (is_err(body_val)) {
422 *err_out = body_val;
423 return false;
424 }}
425
426 s = js_getstr(js, body_val, &len);
427 if (!copy_body_bytes(js, (const uint8_t *)s, len, out_data, out_size, err_out)) return false;
428 *out_type = strdup("text/plain;charset=UTF-8");
429
430 return true;
431}
432
433static bool extract_body(
434 ant_t *js, ant_value_t body_val,
435 uint8_t **out_data, size_t *out_size, char **out_type,
436 ant_value_t *out_stream, ant_value_t *err_out
437) {
438 *out_data = NULL;
439 *out_size = 0;
440 *out_type = NULL;
441 *out_stream = js_mkundef();
442 *err_out = js_mkundef();
443
444 if (vtype(body_val) == T_NULL || vtype(body_val) == T_UNDEF) return true;
445 if (extract_buffer_source_body(js, body_val, out_data, out_size, err_out)) return true;
446 if (vtype(body_val) == T_OBJ && rs_is_stream(body_val)) return extract_stream_body(js, body_val, out_stream, err_out);
447 if (vtype(body_val) == T_OBJ && extract_blob_body(js, body_val, out_data, out_size, out_type, err_out)) return true;
448 if (vtype(body_val) == T_OBJ && extract_urlsearchparams_body(js, body_val, out_data, out_size, out_type)) return true;
449 if (vtype(body_val) == T_OBJ && extract_formdata_body(js, body_val, out_data, out_size, out_type, err_out)) return true;
450
451 return extract_string_body(js, body_val, out_data, out_size, out_type, err_out);
452}
453
454enum {
455 BODY_TEXT = 0,
456 BODY_JSON,
457 BODY_ARRAYBUFFER,
458 BODY_BLOB,
459 BODY_BYTES,
460 BODY_FORMDATA
461};
462
463static void resolve_body_promise(
464 ant_t *js, ant_value_t promise,
465 const uint8_t *data, size_t size,
466 const char *body_type, int mode, bool has_body
467) {
468 switch (mode) {
469 case BODY_TEXT: {
470 ant_value_t str = (data && size > 0)
471 ? js_mkstr(js, (const char *)data, size)
472 : js_mkstr(js, "", 0);
473 js_resolve_promise(js, promise, str);
474 break;
475 }
476 case BODY_JSON: {
477 ant_value_t str = (data && size > 0)
478 ? js_mkstr(js, (const char *)data, size)
479 : js_mkstr(js, "", 0);
480 ant_value_t parsed = json_parse_value(js, str);
481 if (is_err(parsed)) js_reject_promise(js, promise, request_rejection_reason(js, parsed));
482 else js_resolve_promise(js, promise, parsed);
483 break;
484 }
485 case BODY_ARRAYBUFFER: {
486 ArrayBufferData *ab = create_array_buffer_data(size);
487 if (!ab) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); break; }
488 if (data && size > 0) memcpy(ab->data, data, size);
489 js_resolve_promise(js, promise, create_arraybuffer_obj(js, ab));
490 break;
491 }
492 case BODY_BLOB: {
493 const char *type = body_type ? body_type : "";
494 js_resolve_promise(js, promise, blob_create(js, data, size, type));
495 break;
496 }
497 case BODY_BYTES: {
498 ArrayBufferData *ab = create_array_buffer_data(size);
499 if (!ab) { js_reject_promise(js, promise, js_mkerr(js, "out of memory")); break; }
500 if (data && size > 0) memcpy(ab->data, data, size);
501 js_resolve_promise(js, promise,
502 create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, size, "Uint8Array"));
503 break;
504 }
505 case BODY_FORMDATA: {
506 ant_value_t fd = formdata_parse_body(js, data, size, body_type, has_body);
507 if (is_err(fd)) js_reject_promise(js, promise, request_rejection_reason(js, fd));
508 else js_resolve_promise(js, promise, fd);
509 break;
510 }}
511}
512
513static uint8_t *concat_chunks(ant_t *js, ant_value_t chunks, size_t *out_size) {
514 ant_offset_t n = js_arr_len(js, chunks);
515 size_t total = 0;
516
517 for (ant_offset_t i = 0; i < n; i++) {
518 ant_value_t chunk = js_arr_get(js, chunks, i);
519 if (vtype(chunk) == T_TYPEDARRAY) {
520 TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(chunk);
521 if (ta && ta->buffer && !ta->buffer->is_detached) total += ta->byte_length;
522 }}
523
524 uint8_t *buf = total > 0 ? malloc(total) : NULL;
525 if (total > 0 && !buf) return NULL;
526
527 size_t pos = 0;
528 for (ant_offset_t i = 0; i < n; i++) {
529 ant_value_t chunk = js_arr_get(js, chunks, i);
530
531 if (vtype(chunk) == T_TYPEDARRAY) {
532 TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(chunk);
533 if (ta && ta->buffer && !ta->buffer->is_detached && ta->byte_length > 0) {
534 memcpy(buf + pos, ta->buffer->data + ta->byte_offset, ta->byte_length);
535 pos += ta->byte_length;
536 }}}
537
538 *out_size = pos;
539 return buf;
540}
541
542static ant_value_t stream_body_read(ant_t *js, ant_value_t *args, int nargs);
543
544static ant_value_t stream_body_rejected(ant_t *js, ant_value_t *args, int nargs) {
545 ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
546 ant_value_t promise = js_get(js, state, "promise");
547 ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
548 js_reject_promise(js, promise, reason);
549 return js_mkundef();
550}
551
552static void stream_schedule_next_read(ant_t *js, ant_value_t state, ant_value_t read_fn, ant_value_t reader) {
553 ant_value_t next_p = rs_default_reader_read(js, reader);
554 ant_value_t fulfill = js_heavy_mkfun(js, stream_body_read, state);
555 ant_value_t reject = js_heavy_mkfun(js, stream_body_rejected, state);
556 ant_value_t then_result = js_promise_then(js, next_p, fulfill, reject);
557 promise_mark_handled(then_result);
558}
559
560static ant_value_t stream_body_read(ant_t *js, ant_value_t *args, int nargs) {
561 ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
562 ant_value_t result = (nargs > 0) ? args[0] : js_mkundef();
563 ant_value_t promise = js_get(js, state, "promise");
564 ant_value_t reader = js_get(js, state, "reader");
565 ant_value_t chunks = js_get(js, state, "chunks");
566 int mode = (int)js_getnum(js_get(js, state, "mode"));
567
568 ant_value_t done_val = js_get(js, result, "done");
569 ant_value_t value = js_get(js, result, "value");
570
571 if (vtype(done_val) == T_BOOL && done_val == js_true) {
572 size_t size = 0;
573 uint8_t *data = concat_chunks(js, chunks, &size);
574 ant_value_t type_v = js_get(js, state, "type");
575 const char *body_type = (vtype(type_v) == T_STR) ? js_getstr(js, type_v, NULL) : NULL;
576 resolve_body_promise(js, promise, data, size, body_type, mode, true);
577 free(data);
578 return js_mkundef();
579 }
580
581 if (vtype(value) != T_UNDEF && vtype(value) != T_NULL)
582 js_arr_push(js, chunks, value);
583
584 stream_schedule_next_read(js, state, js_mkundef(), reader);
585 return js_mkundef();
586}
587
588static ant_value_t consume_body_from_stream(
589 ant_t *js, ant_value_t stream,
590 ant_value_t promise, int mode,
591 const char *body_type
592) {
593 ant_value_t reader_args[1] = { stream };
594 ant_value_t saved = js->new_target;
595
596 js->new_target = g_reader_proto;
597 ant_value_t reader = js_rs_reader_ctor(js, reader_args, 1);
598 js->new_target = saved;
599
600 if (is_err(reader)) {
601 js_reject_promise(js, promise, reader);
602 return promise;
603 }
604
605 ant_value_t state = js_mkobj(js);
606 js_set(js, state, "promise", promise);
607 js_set(js, state, "reader", reader);
608 js_set(js, state, "chunks", js_mkarr(js));
609 js_set(js, state, "mode", js_mknum(mode));
610 js_set(js, state, "type", body_type ? js_mkstr(js, body_type, strlen(body_type)) : js_mkundef());
611
612 stream_schedule_next_read(js, state, js_mkundef(), reader);
613 return promise;
614}
615
616static ant_value_t consume_body(ant_t *js, int mode) {
617 ant_value_t this = js_getthis(js);
618 request_data_t *d = get_data(this);
619 ant_value_t promise = js_mkpromise(js);
620
621 if (!d) {
622 js_reject_promise(js, promise, request_rejection_reason(js,
623 js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Request object")));
624 return promise;
625 }
626
627 if (!d->has_body) {
628 resolve_body_promise(js, promise, NULL, 0, request_effective_body_type(js, this, d), mode, false);
629 return promise;
630 }
631
632 if (d->body_used) {
633 js_reject_promise(js, promise, request_rejection_reason(js,
634 js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked")));
635 return promise;
636 }
637
638 d->body_used = true;
639 ant_value_t stream = js_get_slot(this, SLOT_REQUEST_BODY_STREAM);
640 if (rs_is_stream(stream) && d->body_is_stream)
641 return consume_body_from_stream(js, stream, promise, mode, request_effective_body_type(js, this, d));
642 resolve_body_promise(js, promise, d->body_data, d->body_size, request_effective_body_type(js, this, d), mode, true);
643
644 return promise;
645}
646
647static ant_value_t js_req_text(ant_t *js, ant_value_t *args, int nargs) {
648 return consume_body(js, BODY_TEXT);
649}
650
651static ant_value_t js_req_json(ant_t *js, ant_value_t *args, int nargs) {
652 return consume_body(js, BODY_JSON);
653}
654
655static ant_value_t js_req_array_buffer(ant_t *js, ant_value_t *args, int nargs) {
656 return consume_body(js, BODY_ARRAYBUFFER);
657}
658
659static ant_value_t js_req_blob(ant_t *js, ant_value_t *args, int nargs) {
660 return consume_body(js, BODY_BLOB);
661}
662
663static ant_value_t js_req_bytes(ant_t *js, ant_value_t *args, int nargs) {
664 return consume_body(js, BODY_BYTES);
665}
666
667static ant_value_t js_req_form_data(ant_t *js, ant_value_t *args, int nargs) {
668 return consume_body(js, BODY_FORMDATA);
669}
670
671static ant_value_t request_set_extracted_body(
672 ant_t *js, ant_value_t req_obj, ant_value_t headers, request_data_t *req,
673 uint8_t *body_data, size_t body_size, char *body_type,
674 ant_value_t body_stream, bool duplex_provided
675) {
676 free(req->body_data);
677 free(req->body_type);
678
679 req->body_data = body_data;
680 req->body_size = body_size;
681 req->body_type = body_type;
682 req->body_is_stream = rs_is_stream(body_stream);
683 req->has_body = true;
684
685 if (!req->body_is_stream) {
686 if (body_type && body_type[0]) headers_append_if_missing(headers, "content-type", body_type);
687 return js_mkundef();
688 }
689
690 if (req->keepalive) {
691 return js_mkerr_typed(js, JS_ERR_TYPE,
692 "Failed to construct 'Request': keepalive cannot be used with a ReadableStream body");
693 }
694 if (!duplex_provided) {
695 return js_mkerr_typed(js, JS_ERR_TYPE,
696 "Failed to construct 'Request': duplex must be provided for a ReadableStream body");
697 }
698
699 js_set_slot_wb(js, req_obj, SLOT_REQUEST_BODY_STREAM, body_stream);
700 if (body_type && body_type[0]) headers_append_if_missing(headers, "content-type", body_type);
701 return js_mkundef();
702}
703
704static void request_clear_body(ant_t *js, ant_value_t req_obj, request_data_t *req) {
705 free(req->body_data);
706 free(req->body_type);
707 req->body_data = NULL;
708 req->body_size = 0;
709 req->body_type = NULL;
710 req->body_is_stream = false;
711 req->has_body = false;
712 js_set_slot_wb(js, req_obj, SLOT_REQUEST_BODY_STREAM, js_mkundef());
713}
714
715static ant_value_t request_copy_source_body(ant_t *js, ant_value_t req_obj, ant_value_t input, request_data_t *req, request_data_t *src) {
716 ant_value_t src_stream = js_get_slot(input, SLOT_REQUEST_BODY_STREAM);
717
718 if (src->body_used) {
719 return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot construct Request with unusable body");
720 }
721
722 if (rs_is_stream(src_stream) && rs_stream_unusable(src_stream)) {
723 return js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked");
724 }
725
726 if (!src->body_is_stream) {
727 req->body_data = malloc(src->body_size);
728 if (src->body_size > 0 && !req->body_data) return js_mkerr(js, "out of memory");
729 if (src->body_size > 0) memcpy(req->body_data, src->body_data, src->body_size);
730 req->body_size = src->body_size;
731 req->body_type = src->body_type ? strdup(src->body_type) : NULL;
732 req->body_is_stream = false;
733 req->has_body = true;
734 return js_mkundef();
735 }
736
737 if (!rs_is_stream(src_stream)) return js_mkundef();
738 ant_value_t branches = readable_stream_tee(js, src_stream);
739
740 if (is_err(branches)) return branches;
741 if (vtype(branches) != T_ARR) {
742 return js_mkerr_typed(js, JS_ERR_TYPE,
743 "Failed to construct 'Request': tee() did not return branches");
744 }
745
746 js_set_slot_wb(js, req_obj, SLOT_REQUEST_BODY_STREAM, js_arr_get(js, branches, 1));
747 req->body_is_stream = true;
748 req->has_body = true;
749
750 return js_mkundef();
751}
752
753#define REQ_GETTER_START(name) \
754 static ant_value_t js_req_get_##name(ant_t *js, ant_value_t *args, int nargs) { \
755 ant_value_t this = js_getthis(js); \
756 request_data_t *d = get_data(this); \
757 if (!d) return js_mkundef();
758
759#define REQ_GETTER_END }
760
761REQ_GETTER_START(method)
762 return js_mkstr(js, d->method, strlen(d->method));
763REQ_GETTER_END
764
765REQ_GETTER_START(url)
766 char *href = build_href(&d->url);
767 if (!href) return js_mkstr(js, "", 0);
768 ant_value_t ret = js_mkstr(js, href, strlen(href));
769 free(href);
770 return ret;
771REQ_GETTER_END
772
773REQ_GETTER_START(headers)
774 return js_get_slot(this, SLOT_REQUEST_HEADERS);
775REQ_GETTER_END
776
777REQ_GETTER_START(destination)
778 (void)d;
779 return js_mkstr(js, "", 0);
780REQ_GETTER_END
781
782REQ_GETTER_START(referrer)
783 if (!d->referrer || strcmp(d->referrer, "no-referrer") == 0)
784 return js_mkstr(js, "", 0);
785 if (strcmp(d->referrer, "client") == 0)
786 return js_mkstr(js, "about:client", 12);
787 return js_mkstr(js, d->referrer, strlen(d->referrer));
788REQ_GETTER_END
789
790REQ_GETTER_START(referrer_policy)
791 const char *p = d->referrer_policy ? d->referrer_policy : "";
792 return js_mkstr(js, p, strlen(p));
793REQ_GETTER_END
794
795REQ_GETTER_START(mode)
796 return js_mkstr(js, d->mode, strlen(d->mode));
797REQ_GETTER_END
798
799REQ_GETTER_START(credentials)
800 return js_mkstr(js, d->credentials, strlen(d->credentials));
801REQ_GETTER_END
802
803REQ_GETTER_START(cache)
804 return js_mkstr(js, d->cache, strlen(d->cache));
805REQ_GETTER_END
806
807REQ_GETTER_START(redirect)
808 return js_mkstr(js, d->redirect, strlen(d->redirect));
809REQ_GETTER_END
810
811REQ_GETTER_START(integrity)
812 const char *ig = d->integrity ? d->integrity : "";
813 return js_mkstr(js, ig, strlen(ig));
814REQ_GETTER_END
815
816REQ_GETTER_START(keepalive)
817 return js_bool(d->keepalive);
818REQ_GETTER_END
819
820REQ_GETTER_START(is_reload_navigation)
821 return js_bool(d->reload_navigation);
822REQ_GETTER_END
823
824REQ_GETTER_START(is_history_navigation)
825 return js_bool(d->history_navigation);
826REQ_GETTER_END
827
828REQ_GETTER_START(signal)
829 return request_get_signal(js, this);
830REQ_GETTER_END
831
832REQ_GETTER_START(duplex)
833 return js_mkstr(js, "half", 4);
834REQ_GETTER_END
835
836static ant_value_t req_body_pull(ant_t *js, ant_value_t *args, int nargs) {
837 ant_value_t req_obj = js_get_slot(js->current_func, SLOT_DATA);
838 request_data_t *d = get_data(req_obj);
839 ant_value_t ctrl = (nargs > 0) ? args[0] : js_mkundef();
840
841 if (d && d->body_data && d->body_size > 0) {
842 ArrayBufferData *ab = create_array_buffer_data(d->body_size);
843 if (ab) {
844 memcpy(ab->data, d->body_data, d->body_size);
845 rs_controller_enqueue(js, ctrl,
846 create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, d->body_size, "Uint8Array"));
847 }}
848
849 rs_controller_close(js, ctrl);
850 return js_mkundef();
851}
852
853REQ_GETTER_START(body)
854 if (!d->has_body) return js_mknull();
855 ant_value_t stored_stream = js_get_slot(this, SLOT_REQUEST_BODY_STREAM);
856 if (rs_is_stream(stored_stream)) return stored_stream;
857 if (d->body_used) return js_mknull();
858 ant_value_t pull = js_heavy_mkfun(js, req_body_pull, this);
859 ant_value_t stream = rs_create_stream(js, pull, js_mkundef(), 1.0);
860 if (!is_err(stream)) js_set_slot_wb(js, this, SLOT_REQUEST_BODY_STREAM, stream);
861 return stream;
862REQ_GETTER_END
863
864REQ_GETTER_START(body_used)
865 return js_bool(d->body_used);
866REQ_GETTER_END
867
868#undef REQ_GETTER_START
869#undef REQ_GETTER_END
870
871static ant_value_t request_inspect_finish(ant_t *js, ant_value_t this_obj, ant_value_t body_obj) {
872 ant_value_t tag_val = js_get_sym(js, this_obj, get_toStringTag_sym());
873 const char *tag = vtype(tag_val) == T_STR ? js_getstr(js, tag_val, NULL) : "Request";
874
875 js_inspect_builder_t builder;
876 if (!js_inspect_builder_init_dynamic(&builder, js, 128)) {
877 return js_mkerr(js, "out of memory");
878 }
879
880 bool ok = js_inspect_header_for(&builder, body_obj, "%s", tag);
881 if (ok) ok = js_inspect_object_body(&builder, body_obj);
882 if (ok) ok = js_inspect_close(&builder);
883
884 if (!ok) {
885 js_inspect_builder_dispose(&builder);
886 return js_mkerr(js, "out of memory");
887 }
888
889 return js_inspect_builder_result(&builder);
890}
891
892// TODO: make dry
893static bool request_inspect_set(
894 ant_t *js, ant_value_t obj, const char *key,
895 ant_value_t value, ant_value_t *err_out
896) {
897 if (is_err(value)) {
898 *err_out = value;
899 return false;
900 }
901
902 js_set(js, obj, key, value);
903 return true;
904}
905
906static ant_value_t request_inspect(ant_t *js, ant_value_t *args, int nargs) {
907 ant_value_t this_obj = js_getthis(js);
908 ant_value_t out = js_mkobj(js);
909 ant_value_t err = 0;
910
911 if (!request_inspect_set(js, out, "method", js_req_get_method(js, NULL, 0), &err)) return err;
912 if (!request_inspect_set(js, out, "url", js_req_get_url(js, NULL, 0), &err)) return err;
913 if (!request_inspect_set(js, out, "headers", js_req_get_headers(js, NULL, 0), &err)) return err;
914 if (!request_inspect_set(js, out, "destination", js_req_get_destination(js, NULL, 0), &err)) return err;
915 if (!request_inspect_set(js, out, "referrer", js_req_get_referrer(js, NULL, 0), &err)) return err;
916 if (!request_inspect_set(js, out, "referrerPolicy", js_req_get_referrer_policy(js, NULL, 0), &err)) return err;
917 if (!request_inspect_set(js, out, "mode", js_req_get_mode(js, NULL, 0), &err)) return err;
918 if (!request_inspect_set(js, out, "credentials", js_req_get_credentials(js, NULL, 0), &err)) return err;
919 if (!request_inspect_set(js, out, "cache", js_req_get_cache(js, NULL, 0), &err)) return err;
920 if (!request_inspect_set(js, out, "redirect", js_req_get_redirect(js, NULL, 0), &err)) return err;
921 if (!request_inspect_set(js, out, "integrity", js_req_get_integrity(js, NULL, 0), &err)) return err;
922 if (!request_inspect_set(js, out, "keepalive", js_req_get_keepalive(js, NULL, 0), &err)) return err;
923 if (!request_inspect_set(js, out, "isReloadNavigation", js_req_get_is_reload_navigation(js, NULL, 0), &err)) return err;
924 if (!request_inspect_set(js, out, "isHistoryNavigation", js_req_get_is_history_navigation(js, NULL, 0), &err)) return err;
925 if (!request_inspect_set(js, out, "signal", js_req_get_signal(js, NULL, 0), &err)) return err;
926
927 return request_inspect_finish(js, this_obj, out);
928}
929
930static ant_value_t js_request_clone(ant_t *js, ant_value_t *args, int nargs) {
931 ant_value_t this = js_getthis(js);
932 request_data_t *d = get_data(this);
933
934 if (!d) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Request object");
935 if (d->body_used)
936 return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot clone a Request whose body is unusable");
937
938 request_data_t *nd = data_dup(d);
939 if (!nd) return js_mkerr(js, "out of memory");
940
941 ant_value_t src_headers = js_get_slot(this, SLOT_REQUEST_HEADERS);
942 ant_value_t src_signal = request_get_signal(js, this);
943
944 ant_value_t new_headers = headers_create_empty(js);
945 if (is_err(new_headers)) { data_free(nd); return new_headers; }
946
947 headers_copy_from(js, new_headers, src_headers);
948 headers_set_guard(new_headers,
949 strcmp(nd->mode, "no-cors") == 0
950 ? HEADERS_GUARD_REQUEST_NO_CORS
951 : HEADERS_GUARD_REQUEST
952 );
953 headers_apply_guard(new_headers);
954
955 ant_value_t new_signal = abort_signal_create_dependent(js, src_signal);
956 if (is_err(new_signal)) { data_free(nd); return new_signal; }
957
958 ant_value_t obj = js_mkobj(js);
959 js_set_proto_init(obj, g_request_proto);
960 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST));
961 js_set_native(obj, nd, REQUEST_NATIVE_TAG);
962 js_set_finalizer(obj, request_finalize);
963
964 js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, new_headers);
965 js_set_slot(obj, SLOT_REQUEST_ABORT_REASON, js_mkundef());
966 js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, new_signal);
967
968 ant_value_t src_stream = js_get_slot(this, SLOT_REQUEST_BODY_STREAM);
969 if (rs_is_stream(src_stream)) {
970 ant_value_t branches = readable_stream_tee(js, src_stream);
971 if (!is_err(branches) && vtype(branches) == T_ARR) {
972 ant_value_t b1 = js_arr_get(js, branches, 0);
973 ant_value_t b2 = js_arr_get(js, branches, 1);
974 js_set_slot_wb(js, this, SLOT_REQUEST_BODY_STREAM, b1);
975 js_set_slot_wb(js, obj, SLOT_REQUEST_BODY_STREAM, b2);
976 }}
977
978 return obj;
979}
980
981static const char *init_str(ant_t *js, ant_value_t init, const char *key, size_t klen, ant_value_t *err_out) {
982 ant_value_t v = js_get(js, init, key);
983 if (vtype(v) == T_UNDEF) return NULL;
984 if (vtype(v) != T_STR) {
985 v = js_tostring_val(js, v);
986 if (is_err(v)) { *err_out = v; return NULL; }
987 }
988 return js_getstr(js, v, NULL);
989}
990
991static ant_value_t request_new_from_input(
992 ant_t *js, ant_value_t input,
993 request_data_t **out_req, request_data_t **out_src,
994 ant_value_t *out_input_signal
995) {
996 request_data_t *req = NULL;
997 request_data_t *src = NULL;
998
999 *out_req = NULL;
1000 *out_src = NULL;
1001 *out_input_signal = js_mkundef();
1002
1003 if (
1004 vtype(input) == T_OBJ &&
1005 js_check_brand(input, BRAND_REQUEST)
1006 ) src = get_data(input);
1007
1008 if (!src) {
1009 size_t ulen = 0;
1010 const char *url_str = NULL;
1011 url_state_t parsed = {0};
1012
1013 if (vtype(input) != T_STR) {
1014 input = js_tostring_val(js, input);
1015 if (is_err(input)) return input;
1016 }
1017
1018 url_str = js_getstr(js, input, &ulen);
1019 if (parse_url_to_state(url_str, NULL, &parsed) != 0) {
1020 url_state_clear(&parsed);
1021 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Invalid URL");
1022 }
1023
1024 if ((parsed.username && parsed.username[0]) || (parsed.password && parsed.password[0])) {
1025 url_state_clear(&parsed);
1026 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': URL includes credentials");
1027 }
1028
1029 req = data_new();
1030 if (!req) {
1031 url_state_clear(&parsed);
1032 return js_mkerr(js, "out of memory");
1033 }
1034
1035 req->url = parsed;
1036 } else {
1037 req = data_dup(src);
1038 if (!req) return js_mkerr(js, "out of memory");
1039 req->body_used = false;
1040 *out_input_signal = request_get_signal(js, input);
1041 }
1042
1043 *out_req = req;
1044 *out_src = src;
1045 return js_mkundef();
1046}
1047
1048static ant_value_t request_apply_init_options(
1049 ant_t *js, ant_value_t init, request_data_t *req, ant_value_t *input_signal
1050) {
1051 ant_value_t err = js_mkundef();
1052 ant_value_t win = js_get(js, init, "window");
1053
1054 const char *ref = NULL;
1055 const char *rp = NULL;
1056 const char *mode_val = NULL;
1057 const char *cred = NULL;
1058 const char *cache_val = NULL;
1059 const char *redir = NULL;
1060 const char *integ = NULL;
1061 const char *method_val = NULL;
1062
1063 if (vtype(win) != T_UNDEF && vtype(win) != T_NULL) {
1064 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': 'window' must be null");
1065 }
1066
1067 if (strcmp(req->mode, "navigate") == 0) {
1068 free(req->mode);
1069 req->mode = strdup("same-origin");
1070 }
1071
1072 req->reload_navigation = false;
1073 req->history_navigation = false;
1074 free(req->referrer);
1075 req->referrer = strdup("client");
1076 free(req->referrer_policy);
1077 req->referrer_policy = strdup("");
1078
1079 ref = init_str(js, init, "referrer", 8, &err);
1080 if (is_err(err)) return err;
1081
1082 if (ref) {
1083 if (ref[0] == '\0') {
1084 free(req->referrer);
1085 req->referrer = strdup("no-referrer");
1086 } else {
1087 url_state_t rs = {0};
1088 if (parse_url_to_state(ref, NULL, &rs) != 0) {
1089 url_state_clear(&rs);
1090 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Invalid referrer URL");
1091 }
1092 free(req->referrer);
1093 req->referrer = build_href(&rs);
1094 url_state_clear(&rs);
1095 if (!req->referrer) req->referrer = strdup("client");
1096 }}
1097
1098 rp = init_str(js, init, "referrerPolicy", 14, &err);
1099 if (is_err(err)) return err;
1100 if (rp) {
1101 free(req->referrer_policy);
1102 req->referrer_policy = strdup(rp);
1103 }
1104
1105 mode_val = init_str(js, init, "mode", 4, &err);
1106 if (is_err(err)) return err;
1107 if (mode_val) {
1108 if (strcmp(mode_val, "navigate") == 0) {
1109 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': mode 'navigate' is not allowed");
1110 }
1111 free(req->mode);
1112 req->mode = strdup(mode_val);
1113 }
1114
1115 cred = init_str(js, init, "credentials", 11, &err);
1116 if (is_err(err)) return err;
1117 if (cred) {
1118 free(req->credentials);
1119 req->credentials = strdup(cred);
1120 }
1121
1122 cache_val = init_str(js, init, "cache", 5, &err);
1123 if (is_err(err)) return err;
1124
1125 if (cache_val) {
1126 free(req->cache);
1127 req->cache = strdup(cache_val);
1128 if (
1129 strcmp(req->cache, "only-if-cached") == 0 &&
1130 strcmp(req->mode, "same-origin") != 0
1131 ) return js_mkerr_typed(js, JS_ERR_TYPE,
1132 "Failed to construct 'Request': cache mode 'only-if-cached' requires mode 'same-origin'");
1133 }
1134
1135 redir = init_str(js, init, "redirect", 8, &err);
1136 if (is_err(err)) return err;
1137
1138 if (redir) {
1139 free(req->redirect);
1140 req->redirect = strdup(redir);
1141 }
1142
1143 integ = init_str(js, init, "integrity", 9, &err);
1144 if (is_err(err)) return err;
1145
1146 if (integ) {
1147 free(req->integrity);
1148 req->integrity = strdup(integ);
1149 }
1150
1151 ant_value_t ka = js_get(js, init, "keepalive");
1152 if (vtype(ka) != T_UNDEF) req->keepalive = js_truthy(js, ka);
1153
1154 method_val = init_str(js, init, "method", 6, &err);
1155 if (is_err(err)) return err;
1156
1157 if (method_val) {
1158 if (!is_valid_method(method_val)) {
1159 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Invalid method");
1160 }
1161 if (is_forbidden_method(method_val)) {
1162 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': Forbidden method");
1163 }
1164
1165 free(req->method);
1166 req->method = strdup(method_val);
1167 normalize_method(req->method);
1168 }
1169
1170 ant_value_t sig_val = js_get(js, init, "signal");
1171 if (vtype(sig_val) == T_UNDEF) return js_mkundef();
1172
1173 if (vtype(sig_val) == T_NULL) {
1174 *input_signal = js_mkundef();
1175 return js_mkundef();
1176 }
1177
1178 if (abort_signal_is_signal(sig_val)) {
1179 *input_signal = sig_val;
1180 return js_mkundef();
1181 }
1182
1183 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Request': signal must be an AbortSignal");
1184}
1185
1186static ant_value_t request_create_ctor_headers(ant_t *js, ant_value_t input) {
1187 ant_value_t headers = headers_create_empty(js);
1188 if (is_err(headers)) return headers;
1189 if (vtype(input) != T_OBJ) return headers;
1190
1191 ant_value_t src_hdrs = js_get_slot(input, SLOT_REQUEST_HEADERS);
1192 headers_copy_from(js, headers, src_hdrs);
1193 return headers;
1194}
1195
1196static ant_value_t request_apply_init_headers(ant_t *js, ant_value_t init, ant_value_t headers) {
1197 ant_value_t init_headers = js_get(js, init, "headers");
1198 if (vtype(init_headers) == T_UNDEF) return headers;
1199 return headers_create_from_init(js, init_headers);
1200}
1201
1202static ant_value_t request_parse_duplex(ant_t *js, ant_value_t init, bool *out_duplex_provided) {
1203 ant_value_t duplex_val = js_get(js, init, "duplex");
1204 ant_value_t duplex_str_v = duplex_val;
1205 const char *duplex_str = NULL;
1206
1207 *out_duplex_provided = vtype(duplex_val) != T_UNDEF;
1208 if (!*out_duplex_provided) return js_mkundef();
1209
1210 if (vtype(duplex_str_v) != T_STR) {
1211 duplex_str_v = js_tostring_val(js, duplex_str_v);
1212 if (is_err(duplex_str_v)) return duplex_str_v;
1213 }
1214
1215 duplex_str = js_getstr(js, duplex_str_v, NULL);
1216 if (duplex_str && strcmp(duplex_str, "half") == 0) return js_mkundef();
1217
1218 return js_mkerr_typed(js, JS_ERR_TYPE,
1219 "Failed to construct 'Request': duplex must be 'half'");
1220}
1221
1222static ant_value_t request_apply_ctor_body(
1223 ant_t *js, ant_value_t req_obj, ant_value_t input, ant_value_t init,
1224 bool init_provided, bool duplex_provided,
1225 request_data_t *req, request_data_t *src, ant_value_t headers
1226) {
1227 if (init_provided) {
1228 ant_value_t body_val = js_get(js, init, "body");
1229 bool init_body_present = vtype(body_val) != T_UNDEF;
1230 bool input_body_present = src && src->has_body;
1231 bool effective_body_present =
1232 (init_body_present && vtype(body_val) != T_NULL) ||
1233 (input_body_present && (!init_body_present || vtype(body_val) == T_NULL));
1234
1235 if ((strcmp(req->method, "GET") == 0 || strcmp(req->method, "HEAD") == 0) && effective_body_present) {
1236 return js_mkerr_typed(js, JS_ERR_TYPE,
1237 "Failed to construct 'Request': Request with GET/HEAD method cannot have body");
1238 }
1239
1240 if (vtype(body_val) == T_UNDEF) return js_mkundef();
1241 if (vtype(body_val) == T_NULL) {
1242 request_clear_body(js, req_obj, req);
1243 return js_mkundef();
1244 }
1245
1246 request_data_t *init_req = get_data(init);
1247 ant_value_t body_err = js_mkundef();
1248 ant_value_t body_stream = js_mkundef();
1249
1250 uint8_t *bd = NULL;
1251 size_t bs = 0;
1252 char *bt = NULL;
1253
1254 if (init_req && !init_req->body_used && !init_req->body_is_stream && init_req->has_body) {
1255 bd = malloc(init_req->body_size);
1256 if (init_req->body_size > 0 && !bd) return js_mkerr(js, "out of memory");
1257 if (init_req->body_size > 0) memcpy(bd, init_req->body_data, init_req->body_size);
1258 bs = init_req->body_size;
1259 bt = init_req->body_type ? strdup(init_req->body_type) : NULL;
1260 } else if (!extract_body(js, body_val, &bd, &bs, &bt, &body_stream, &body_err))
1261 return is_err(body_err) ? body_err : js_mkerr(js, "Failed to extract body");
1262
1263 return request_set_extracted_body(
1264 js, req_obj, headers, req, bd,
1265 bs, bt, body_stream, duplex_provided
1266 );
1267 }
1268
1269 if (!src) return js_mkundef();
1270 return request_copy_source_body(js, req_obj, input, req, src);
1271}
1272
1273static ant_value_t js_request_ctor(ant_t *js, ant_value_t *args, int nargs) {
1274 ant_value_t init = (nargs >= 2 && vtype(args[1]) != T_UNDEF) ? args[1] : js_mkundef();
1275
1276 if (vtype(js->new_target) == T_UNDEF)
1277 return js_mkerr_typed(js, JS_ERR_TYPE, "Request constructor requires 'new'");
1278 if (nargs < 1)
1279 return js_mkerr_typed(js, JS_ERR_TYPE, "Request constructor requires at least 1 argument");
1280
1281 ant_value_t input = args[0];
1282 ant_value_t obj = 0;
1283 ant_value_t proto = 0;
1284 bool init_provided = false;
1285
1286 request_data_t *req = NULL;
1287 request_data_t *src = NULL;
1288
1289 ant_value_t input_signal = js_mkundef();
1290 ant_value_t step = js_mkundef();
1291 ant_value_t signal = 0;
1292 ant_value_t headers = 0;
1293
1294 bool duplex_provided = false;
1295 init_provided = (vtype(init) == T_OBJ || vtype(init) == T_ARR);
1296
1297 step = request_new_from_input(js, input, &req, &src, &input_signal);
1298 if (is_err(step)) return step;
1299
1300 if (init_provided) {
1301 step = request_apply_init_options(js, init, req, &input_signal);
1302 if (is_err(step)) { data_free(req); return step; }
1303 }
1304
1305 if (
1306 strcmp(req->mode, "no-cors") == 0 &&
1307 !is_cors_safelisted_method(req->method)
1308 ) {
1309 data_free(req);
1310 return js_mkerr_typed(js, JS_ERR_TYPE,
1311 "Failed to construct 'Request': method must be one of GET, HEAD, POST for no-cors mode");
1312 }
1313
1314 obj = js_mkobj(js);
1315 proto = js_instance_proto_from_new_target(js, g_request_proto);
1316
1317 if (is_object_type(proto)) js_set_proto_init(obj, proto);
1318 else js_set_proto_init(obj, g_request_proto);
1319
1320 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST));
1321 js_set_native(obj, req, REQUEST_NATIVE_TAG);
1322 js_set_finalizer(obj, request_finalize);
1323 js_set_slot(obj, SLOT_REQUEST_ABORT_REASON, js_mkundef());
1324
1325 signal = abort_signal_create_dependent(js, input_signal);
1326 if (is_err(signal)) { request_clear_and_free(obj, req); return signal; }
1327 js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, signal);
1328
1329 headers = request_create_ctor_headers(js, input);
1330 if (is_err(headers)) { request_clear_and_free(obj, req); return headers; }
1331
1332 if (init_provided) {
1333 headers = request_apply_init_headers(js, init, headers);
1334 if (is_err(headers)) { request_clear_and_free(obj, req); return headers; }
1335 }
1336
1337 headers_set_guard(headers,
1338 strcmp(req->mode, "no-cors") == 0
1339 ? HEADERS_GUARD_REQUEST_NO_CORS
1340 : HEADERS_GUARD_REQUEST
1341 );
1342
1343 headers_apply_guard(headers);
1344 js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, headers);
1345
1346 if (init_provided) {
1347 step = request_parse_duplex(js, init, &duplex_provided);
1348 if (is_err(step)) { request_clear_and_free(obj, req); return step; }
1349 }
1350
1351 step = request_apply_ctor_body(
1352 js, obj, input, init, init_provided,
1353 duplex_provided, req, src, headers
1354 );
1355
1356 if (is_err(step)) {
1357 request_clear_and_free(obj, req);
1358 return step;
1359 }
1360
1361 if (src && src->has_body && !src->body_used)
1362 src->body_used = true;
1363
1364 return obj;
1365}
1366
1367ant_value_t request_create_from_input_init(ant_t *js, ant_value_t input, ant_value_t init) {
1368 bool init_provided = (vtype(init) == T_OBJ || vtype(init) == T_ARR);
1369
1370 request_data_t *req = NULL;
1371 request_data_t *src = NULL;
1372
1373 ant_value_t input_signal = js_mkundef();
1374 ant_value_t step = js_mkundef();
1375 ant_value_t obj = 0;
1376 ant_value_t signal = 0;
1377 ant_value_t headers = 0;
1378
1379 bool duplex_provided = false;
1380 step = request_new_from_input(js, input, &req, &src, &input_signal);
1381 if (is_err(step)) return step;
1382
1383 if (init_provided) {
1384 step = request_apply_init_options(js, init, req, &input_signal);
1385 if (is_err(step)) { data_free(req); return step; }
1386 }
1387
1388 if (
1389 strcmp(req->mode, "no-cors") == 0 &&
1390 !is_cors_safelisted_method(req->method)
1391 ) {
1392 data_free(req);
1393 return js_mkerr_typed(js, JS_ERR_TYPE,
1394 "Failed to construct 'Request': method must be one of GET, HEAD, POST for no-cors mode");
1395 }
1396
1397 obj = js_mkobj(js);
1398 js_set_proto_init(obj, g_request_proto);
1399 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST));
1400 js_set_native(obj, req, REQUEST_NATIVE_TAG);
1401 js_set_finalizer(obj, request_finalize);
1402 js_set_slot(obj, SLOT_REQUEST_ABORT_REASON, js_mkundef());
1403
1404 signal = abort_signal_create_dependent(js, input_signal);
1405 if (is_err(signal)) { request_clear_and_free(obj, req); return signal; }
1406 js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, signal);
1407
1408 headers = request_create_ctor_headers(js, input);
1409 if (is_err(headers)) { request_clear_and_free(obj, req); return headers; }
1410
1411 if (init_provided) {
1412 headers = request_apply_init_headers(js, init, headers);
1413 if (is_err(headers)) { request_clear_and_free(obj, req); return headers; }
1414 }
1415
1416 headers_set_guard(headers,
1417 strcmp(req->mode, "no-cors") == 0
1418 ? HEADERS_GUARD_REQUEST_NO_CORS
1419 : HEADERS_GUARD_REQUEST
1420 );
1421 headers_apply_guard(headers);
1422 js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, headers);
1423
1424 if (init_provided) {
1425 step = request_parse_duplex(js, init, &duplex_provided);
1426 if (is_err(step)) { request_clear_and_free(obj, req); return step; }
1427 }
1428
1429 step = request_apply_ctor_body(js, obj, input, init, init_provided, duplex_provided, req, src, headers);
1430 if (is_err(step)) {
1431 request_clear_and_free(obj, req);
1432 return step;
1433 }
1434
1435 if (src && src->has_body && !src->body_used)
1436 src->body_used = true;
1437
1438 return obj;
1439}
1440
1441ant_value_t request_create(ant_t *js,
1442 const char *method, const char *url,
1443 ant_value_t headers_obj, const uint8_t *body, size_t body_len,
1444 const char *body_type) {
1445 request_data_t *req = data_new();
1446 if (!req) return js_mkerr(js, "out of memory");
1447
1448 free(req->method);
1449 req->method = strdup(method ? method : "GET");
1450 free(req->mode);
1451 req->mode = strdup("same-origin");
1452
1453 url_state_t parsed = {0};
1454 if (url && parse_url_to_state(url, NULL, &parsed) == 0) req->url = parsed;
1455 else url_state_clear(&parsed);
1456
1457 if (body) req->has_body = true;
1458
1459 if (body && body_len > 0) {
1460 req->body_data = malloc(body_len);
1461 if (!req->body_data) { data_free(req); return js_mkerr(js, "out of memory"); }
1462 memcpy(req->body_data, body, body_len);
1463 req->body_size = body_len;
1464 req->body_type = body_type ? strdup(body_type) : NULL;
1465 }
1466 req->body_is_stream = false;
1467 return request_create_object(js, req, headers_obj, true);
1468}
1469
1470ant_value_t request_create_server(
1471 ant_t *js,
1472 const char *method,
1473 const char *target,
1474 bool absolute_target,
1475 const char *host,
1476 const char *server_hostname,
1477 int server_port,
1478 ant_value_t headers_obj,
1479 const uint8_t *body,
1480 size_t body_len,
1481 const char *body_type
1482) {
1483 request_data_t *req = data_new_server(method);
1484 if (!req) return js_mkerr(js, "out of memory");
1485
1486 if (target && request_parse_server_url(target, absolute_target, host, server_hostname, server_port, &req->url) != 0)
1487 url_state_clear(&req->url);
1488
1489 if (body) req->has_body = true;
1490
1491 if (body && body_len > 0) {
1492 req->body_data = malloc(body_len);
1493 if (!req->body_data) { data_free(req); return js_mkerr(js, "out of memory"); }
1494 memcpy(req->body_data, body, body_len);
1495 req->body_size = body_len;
1496 req->body_type = body_type ? strdup(body_type) : NULL;
1497 }
1498 req->body_is_stream = false;
1499
1500 return request_create_object(js, req, headers_obj, false);
1501}
1502
1503void init_request_module(void) {
1504 ant_t *js = rt->js;
1505 ant_value_t g = js_glob(js);
1506 g_request_proto = js_mkobj(js);
1507
1508 js_set(js, g_request_proto, "text", js_mkfun(js_req_text));
1509 js_set(js, g_request_proto, "json", js_mkfun(js_req_json));
1510 js_set(js, g_request_proto, "arrayBuffer", js_mkfun(js_req_array_buffer));
1511 js_set(js, g_request_proto, "blob", js_mkfun(js_req_blob));
1512 js_set(js, g_request_proto, "formData", js_mkfun(js_req_form_data));
1513 js_set(js, g_request_proto, "bytes", js_mkfun(js_req_bytes));
1514 js_set(js, g_request_proto, "clone", js_mkfun(js_request_clone));
1515
1516#define GETTER(prop, fn) \
1517 js_set_getter_desc(js, g_request_proto, prop, sizeof(prop)-1, js_mkfun(js_req_get_##fn), JS_DESC_C)
1518 GETTER("method", method);
1519 GETTER("url", url);
1520 GETTER("headers", headers);
1521 GETTER("destination", destination);
1522 GETTER("referrer", referrer);
1523 GETTER("referrerPolicy", referrer_policy);
1524 GETTER("mode", mode);
1525 GETTER("credentials", credentials);
1526 GETTER("cache", cache);
1527 GETTER("redirect", redirect);
1528 GETTER("integrity", integrity);
1529 GETTER("keepalive", keepalive);
1530 GETTER("isReloadNavigation",is_reload_navigation);
1531 GETTER("isHistoryNavigation",is_history_navigation);
1532 GETTER("signal", signal);
1533 GETTER("duplex", duplex);
1534 GETTER("body", body);
1535 GETTER("bodyUsed", body_used);
1536#undef GETTER
1537
1538 js_set_sym(js, g_request_proto, get_inspect_sym(), js_mkfun(request_inspect));
1539 js_set_sym(js, g_request_proto, get_toStringTag_sym(), js_mkstr(js, "Request", 7));
1540 ant_value_t ctor = js_make_ctor(js, js_request_ctor, g_request_proto, "Request", 7);
1541
1542 js_set(js, g, "Request", ctor);
1543 js_set_descriptor(js, g, "Request", 7, JS_DESC_W | JS_DESC_C);
1544}