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