MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1
fork

Configure Feed

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

at master 1544 lines 50 kB view raw
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}