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 932 lines 28 kB view raw
1#include <compat.h> // IWYU pragma: keep 2 3#include <stdbool.h> 4#include <stdint.h> 5#include <stdlib.h> 6#include <string.h> 7#include <strings.h> 8#include <uv.h> 9#include <utarray.h> 10 11#include "ant.h" 12#include "ptr.h" 13#include "common.h" 14#include "errors.h" 15#include "internal.h" 16#include "runtime.h" 17#include "esm/remote.h" 18#include "gc/modules.h" 19#include "modules/abort.h" 20#include "modules/assert.h" 21#include "modules/buffer.h" 22#include "modules/fetch.h" 23#include "modules/headers.h" 24#include "modules/http.h" 25#include "modules/request.h" 26#include "modules/response.h" 27#include "modules/url.h" 28#include "streams/readable.h" 29 30typedef struct fetch_request_s { 31 ant_t *js; 32 33 ant_value_t promise; 34 ant_value_t request_obj; 35 ant_value_t response_obj; 36 ant_value_t abort_listener; 37 ant_value_t upload_reader; 38 ant_value_t upload_read_promise; 39 ant_http_request_t *http_req; 40 41 int refs; 42 int redirect_count; 43 bool settled; 44 bool aborted; 45 bool restart_pending; 46 bool response_started; 47} fetch_request_t; 48 49enum { FETCH_REQUEST_NATIVE_TAG = 0x46524551u }; // FREQ 50 51static UT_array *pending_requests = NULL; 52static const int k_fetch_max_redirects = 20; 53static void fetch_start_http(fetch_request_t *req); 54 55static void fetch_request_retain(fetch_request_t *req) { 56 if (req) req->refs++; 57} 58 59static void remove_pending_request(fetch_request_t *req) { 60 if (!req || !pending_requests) return; 61 62 fetch_request_t **p = NULL; 63 unsigned int i = 0; 64 65 while ((p = (fetch_request_t **)utarray_next(pending_requests, p))) { 66 if (*p == req) { utarray_erase(pending_requests, i, 1); return; } i++; 67 } 68} 69 70static void destroy_fetch_request(fetch_request_t *req) { 71 ant_value_t signal = 0; 72 73 if (!req) return; 74 signal = request_get_signal(req->js, req->request_obj); 75 76 if (abort_signal_is_signal(signal) && is_callable(req->abort_listener)) 77 abort_signal_remove_listener(req->js, signal, req->abort_listener); 78 79 remove_pending_request(req); 80 free(req); 81} 82 83static void fetch_request_release(fetch_request_t *req) { 84 if (!req) return; 85 if (--req->refs == 0) destroy_fetch_request(req); 86} 87 88static ant_value_t fetch_type_error(ant_t *js, const char *message) { 89 return js_mkerr_typed(js, JS_ERR_TYPE, "%s", message ? message : "fetch failed"); 90} 91 92static ant_value_t fetch_rejection_reason(ant_t *js, ant_value_t value) { 93 if (!is_err(value)) return value; 94 if (js->thrown_exists) { 95 ant_value_t reason = js->thrown_value; 96 js->thrown_exists = false; 97 js->thrown_value = js_mkundef(); 98 js->thrown_stack = js_mkundef(); 99 return reason; 100 } 101 return value; 102} 103 104static bool fetch_is_redirect_status(int status) { 105 return 106 status == 301 || 107 status == 302 || 108 status == 303 || 109 status == 307 || 110 status == 308; 111} 112 113static void fetch_cancel_request_body(fetch_request_t *req, ant_value_t reason) { 114 request_data_t *data = request_get_data(req->request_obj); 115 ant_value_t stream = js_get_slot(req->request_obj, SLOT_REQUEST_BODY_STREAM); 116 117 if (!data || !data->body_is_stream || !rs_is_stream(stream)) return; 118 readable_stream_cancel(req->js, stream, reason); 119} 120 121static void fetch_error_response_body(fetch_request_t *req, ant_value_t reason) { 122 ant_value_t stream = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM); 123 if (rs_is_stream(stream)) readable_stream_error(req->js, stream, reason); 124} 125 126static void fetch_reject(fetch_request_t *req, ant_value_t reason) { 127 if (!req) return; 128 129 if (!req->settled) { 130 req->settled = true; 131 js_reject_promise(req->js, req->promise, reason); 132 } 133 134 fetch_cancel_request_body(req, reason); 135 if (is_object_type(req->response_obj)) fetch_error_response_body(req, reason); 136} 137 138static void fetch_resolve(fetch_request_t *req, ant_value_t response_obj) { 139 if (!req || req->settled) return; 140 req->settled = true; 141 req->response_started = true; 142 req->response_obj = response_obj; 143 js_resolve_promise(req->js, req->promise, response_obj); 144} 145 146static bool fetch_is_http_url(const char *url) { 147 return strncmp(url, "http://", 7) == 0 || strncmp(url, "https://", 8) == 0; 148} 149 150static char *fetch_build_request_url(request_data_t *request) { 151 if (!request) return NULL; 152 return build_href(&request->url); 153} 154 155static const char *fetch_find_header_value(const ant_http_header_t *headers, const char *name) { 156 for (const ant_http_header_t *entry = headers; entry; entry = entry->next) { 157 if (entry->name && strcasecmp(entry->name, name) == 0) return entry->value; 158 } 159 return NULL; 160} 161 162static bool fetch_redirect_rewrites_to_get(int status, const char *method) { 163 if (!method) return false; 164 if (status == 303) return strcasecmp(method, "HEAD") != 0; 165 return (status == 301 || status == 302) && strcasecmp(method, "POST") == 0; 166} 167 168typedef struct { 169 ant_t *js; 170 ant_value_t headers; 171 bool drop_body_headers; 172 bool failed; 173} fetch_redirect_headers_ctx_t; 174 175static void fetch_copy_redirect_header(const char *name, const char *value, void *ctx) { 176 fetch_redirect_headers_ctx_t *copy = (fetch_redirect_headers_ctx_t *)ctx; 177 ant_value_t step = 0; 178 179 if (!copy || copy->failed) return; 180 if (copy->drop_body_headers && name && strcasecmp(name, "content-type") == 0) return; 181 182 step = headers_append_literal(copy->js, copy->headers, name, value); 183 if (is_err(step)) copy->failed = true; 184} 185 186static ant_value_t fetch_replace_request_headers(fetch_request_t *req, bool drop_body_headers) { 187 ant_t *js = req->js; 188 189 request_data_t *request = request_get_data(req->request_obj); 190 ant_value_t current = request_get_headers(req->request_obj); 191 ant_value_t headers = headers_create_empty(js); 192 193 fetch_redirect_headers_ctx_t ctx = { 194 .js = js, 195 .headers = headers, 196 .drop_body_headers = drop_body_headers, 197 .failed = false, 198 }; 199 200 if (is_err(headers)) return headers; 201 headers_for_each(current, fetch_copy_redirect_header, &ctx); 202 if (ctx.failed) return js_mkerr(js, "out of memory"); 203 204 headers_set_guard(headers, 205 strcmp(request->mode, "no-cors") == 0 206 ? HEADERS_GUARD_REQUEST_NO_CORS 207 : HEADERS_GUARD_REQUEST 208 ); 209 210 headers_apply_guard(headers); 211 js_set_slot_wb(js, req->request_obj, SLOT_REQUEST_HEADERS, headers); 212 213 return js_mkundef(); 214} 215 216static ant_value_t fetch_clear_redirect_request_body(fetch_request_t *req) { 217 request_data_t *request = request_get_data(req->request_obj); 218 ant_value_t headers_step = 0; 219 220 if (!request) 221 return fetch_type_error(req->js, "Invalid Request object"); 222 223 free(request->body_data); 224 free(request->body_type); 225 request->body_data = NULL; 226 request->body_size = 0; 227 request->body_type = NULL; 228 request->body_is_stream = false; 229 request->has_body = false; 230 request->body_used = false; 231 js_set_slot_wb(req->js, req->request_obj, SLOT_REQUEST_BODY_STREAM, js_mkundef()); 232 233 headers_step = fetch_replace_request_headers(req, true); 234 if (is_err(headers_step)) return headers_step; 235 236 return js_mkundef(); 237} 238 239static ant_value_t fetch_set_redirect_method(fetch_request_t *req, const char *method) { 240 request_data_t *request = request_get_data(req->request_obj); 241 char *dup = NULL; 242 243 if (!request) return fetch_type_error(req->js, "Invalid Request object"); 244 dup = strdup(method); 245 if (!dup) return js_mkerr(req->js, "out of memory"); 246 free(request->method); 247 request->method = dup; 248 return js_mkundef(); 249} 250 251static ant_value_t fetch_update_request_url(fetch_request_t *req, const char *location) { 252 request_data_t *request = request_get_data(req->request_obj); 253 url_state_t next = {0}; 254 char *base = NULL; 255 256 if (!request || !location) return fetch_type_error(req->js, "Invalid redirect URL"); 257 base = fetch_build_request_url(request); 258 if (!base) return fetch_type_error(req->js, "Invalid request URL"); 259 260 if (parse_url_to_state(location, base, &next) != 0) { 261 free(base); 262 url_state_clear(&next); 263 return fetch_type_error(req->js, "Invalid redirect URL"); 264 } 265 266 free(base); 267 url_state_clear(&request->url); 268 request->url = next; 269 return js_mkundef(); 270} 271 272static ant_value_t fetch_prepare_redirect(fetch_request_t *req, const ant_http_response_t *resp) { 273 request_data_t *request = request_get_data(req->request_obj); 274 const char *location = fetch_find_header_value(resp->headers, "location"); 275 ant_value_t step = 0; 276 bool rewrite_to_get = false; 277 278 if (!request || !location || location[0] == '\0') return js_mkundef(); 279 if (req->redirect_count >= k_fetch_max_redirects) { 280 return fetch_type_error(req->js, "fetch failed: too many redirects"); 281 } 282 283 rewrite_to_get = fetch_redirect_rewrites_to_get(resp->status, request->method); 284 if (!rewrite_to_get && request->body_is_stream) { 285 return fetch_type_error(req->js, "fetch failed: cannot follow redirect with a streamed request body"); 286 } 287 288 if (rewrite_to_get) { 289 step = fetch_set_redirect_method(req, strcasecmp(request->method, "HEAD") == 0 ? "HEAD" : "GET"); 290 if (is_err(step)) return step; 291 step = fetch_clear_redirect_request_body(req); 292 if (is_err(step)) return step; 293 } 294 295 step = fetch_update_request_url(req, location); 296 if (is_err(step)) return step; 297 298 req->redirect_count++; 299 req->restart_pending = true; 300 return js_mkundef(); 301} 302 303typedef struct { 304 ant_http_header_t *head; 305 ant_http_header_t **tail; 306 bool failed; 307 bool has_user_agent; 308 bool has_accept; 309 bool has_accept_language; 310 bool has_sec_fetch_mode; 311 bool has_accept_encoding; 312} fetch_header_builder_t; 313 314static void fetch_collect_header(const char *name, const char *value, void *ctx) { 315 fetch_header_builder_t *builder = (fetch_header_builder_t *)ctx; 316 ant_http_header_t *header = NULL; 317 318 if (!builder || builder->failed) return; 319 if (name && strcasecmp(name, "user-agent") == 0) builder->has_user_agent = true; 320 if (name && strcasecmp(name, "accept") == 0) builder->has_accept = true; 321 if (name && strcasecmp(name, "accept-language") == 0) builder->has_accept_language = true; 322 if (name && strcasecmp(name, "sec-fetch-mode") == 0) builder->has_sec_fetch_mode = true; 323 if (name && strcasecmp(name, "accept-encoding") == 0) builder->has_accept_encoding = true; 324 header = calloc(1, sizeof(ant_http_header_t)); 325 if (!header) { 326 builder->failed = true; 327 return; 328 } 329 330 header->name = strdup(name ? name : ""); 331 header->value = strdup(value ? value : ""); 332 if (!header->name || !header->value) { 333 free(header->name); 334 free(header->value); 335 free(header); 336 builder->failed = true; 337 return; 338 } 339 340 *builder->tail = header; 341 builder->tail = &header->next; 342} 343 344static bool fetch_append_header(fetch_header_builder_t *builder, const char *name, const char *value) { 345 ant_http_header_t *header = NULL; 346 347 if (!builder || builder->failed) return false; 348 header = calloc(1, sizeof(ant_http_header_t)); 349 if (!header) { 350 builder->failed = true; 351 return false; 352 } 353 354 header->name = strdup(name); 355 header->value = strdup(value); 356 if (!header->name || !header->value) { 357 free(header->name); 358 free(header->value); 359 free(header); 360 builder->failed = true; 361 return false; 362 } 363 364 *builder->tail = header; 365 builder->tail = &header->next; 366 return true; 367} 368 369static ant_http_header_t *fetch_build_http_headers(ant_value_t request_obj) { 370 fetch_header_builder_t builder = {0}; 371 char user_agent[256] = {0}; 372 373 builder.tail = &builder.head; 374 headers_for_each(request_get_headers(request_obj), fetch_collect_header, &builder); 375 376 if (builder.failed) { 377 ant_http_headers_free(builder.head); 378 return NULL; 379 } 380 381 if (!builder.has_accept && !fetch_append_header(&builder, "accept", "*/*")) { 382 ant_http_headers_free(builder.head); 383 return NULL; 384 } 385 if (!builder.has_accept_language && !fetch_append_header(&builder, "accept-language", "*")) { 386 ant_http_headers_free(builder.head); 387 return NULL; 388 } 389 if (!builder.has_sec_fetch_mode && !fetch_append_header(&builder, "sec-fetch-mode", "cors")) { 390 ant_http_headers_free(builder.head); 391 return NULL; 392 } 393 if (!builder.has_accept_encoding && !fetch_append_header(&builder, "accept-encoding", "br, gzip, deflate")) { 394 ant_http_headers_free(builder.head); 395 return NULL; 396 } 397 if (builder.has_user_agent) return builder.head; 398 399 snprintf(user_agent, sizeof(user_agent), "ant/%s", ANT_VERSION); 400 if (!fetch_append_header(&builder, "user-agent", user_agent)) { 401 ant_http_headers_free(builder.head); 402 return NULL; 403 } 404 405 return builder.head; 406} 407 408static ant_value_t fetch_headers_from_http(ant_t *js, const ant_http_header_t *headers) { 409 ant_value_t hdrs = headers_create_empty(js); 410 if (is_err(hdrs)) return hdrs; 411 412 for (const ant_http_header_t *entry = headers; entry; entry = entry->next) { 413 ant_value_t step = headers_append_value( 414 js, hdrs, 415 js_mkstr(js, entry->name, strlen(entry->name)), 416 js_mkstr(js, entry->value, strlen(entry->value)) 417 ); 418 if (is_err(step)) return step; 419 } 420 421 return hdrs; 422} 423 424static ant_value_t fetch_create_chunk(ant_t *js, const uint8_t *data, size_t len) { 425 ArrayBufferData *ab = create_array_buffer_data(len); 426 if (!ab) return js_mkerr(js, "out of memory"); 427 if (len > 0) memcpy(ab->data, data, len); 428 return create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Uint8Array"); 429} 430 431static bool fetch_get_upload_chunk(ant_value_t value, const uint8_t **out, size_t *len) { 432 TypedArrayData *ta = buffer_get_typedarray_data(value); 433 434 if (!ta || ta->type != TYPED_ARRAY_UINT8) return false; 435 if (!ta->buffer || ta->buffer->is_detached) { 436 *out = NULL; 437 *len = 0; 438 return true; 439 } 440 441 *out = ta->buffer->data + ta->byte_offset; 442 *len = ta->byte_length; 443 return true; 444} 445 446static void fetch_http_on_response(ant_http_request_t *http_req, const ant_http_response_t *resp, void *user_data) { 447 fetch_request_t *req = (fetch_request_t *)user_data; 448 449 ant_t *js = req->js; 450 request_data_t *request = request_get_data(req->request_obj); 451 452 ant_value_t headers = 0; 453 ant_value_t step = 0; 454 ant_value_t stream = 0; 455 ant_value_t response = 0; 456 457 char *url = NULL; 458 if (req->aborted) return; 459 if (!request) { 460 fetch_reject(req, fetch_type_error(js, "Invalid Request object")); 461 ant_http_request_cancel(http_req); 462 return; 463 } 464 465 if (fetch_is_redirect_status(resp->status)) { 466 const char *location = fetch_find_header_value(resp->headers, "location"); 467 const char *redirect_mode = request->redirect ? request->redirect : "follow"; 468 469 if (location && location[0] != '\0' && strcmp(redirect_mode, "error") == 0) { 470 fetch_reject(req, fetch_type_error(js, "fetch failed: redirect mode is set to error")); 471 ant_http_request_cancel(http_req); 472 return; 473 } 474 475 if (strcmp(redirect_mode, "follow") == 0) { 476 step = fetch_prepare_redirect(req, resp); 477 478 if (is_err(step)) { 479 fetch_reject(req, fetch_rejection_reason(js, step)); 480 ant_http_request_cancel(http_req); 481 return; 482 } 483 484 if (req->restart_pending) { 485 ant_http_request_cancel(http_req); 486 return; 487 }} 488 } 489 490 headers = fetch_headers_from_http(js, resp->headers); 491 if (is_err(headers)) { 492 fetch_reject(req, fetch_rejection_reason(js, headers)); 493 ant_http_request_cancel(http_req); 494 return; 495 } 496 497 stream = rs_create_stream(js, js_mkundef(), js_mkundef(), 1.0); 498 if (is_err(stream)) { 499 fetch_reject(req, fetch_rejection_reason(js, stream)); 500 ant_http_request_cancel(http_req); 501 return; 502 } 503 504 url = fetch_build_request_url(request_get_data(req->request_obj)); 505 response = response_create_fetched( 506 js, resp->status, resp->status_text, url, 507 req->redirect_count + 1, headers, NULL, 0, stream, NULL 508 ); 509 510 free(url); 511 512 if (is_err(response)) { 513 fetch_reject(req, fetch_rejection_reason(js, response)); 514 ant_http_request_cancel(http_req); 515 return; 516 } 517 518 fetch_resolve(req, response); 519} 520 521static void fetch_http_on_body(ant_http_request_t *http_req, const uint8_t *chunk, size_t len, void *user_data) { 522 fetch_request_t *req = (fetch_request_t *)user_data; 523 ant_t *js = req->js; 524 ant_value_t stream = 0; 525 ant_value_t controller = 0; 526 ant_value_t value = 0; 527 ant_value_t step = 0; 528 529 (void)http_req; 530 if (req->aborted || !is_object_type(req->response_obj)) return; 531 532 stream = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM); 533 if (!rs_is_stream(stream)) return; 534 535 controller = rs_stream_controller(js, stream); 536 value = fetch_create_chunk(js, chunk, len); 537 if (is_err(value)) { 538 fetch_error_response_body(req, fetch_rejection_reason(js, value)); 539 ant_http_request_cancel(http_req); 540 return; 541 } 542 543 step = rs_controller_enqueue(js, controller, value); 544 if (is_err(step)) { 545 fetch_error_response_body(req, fetch_rejection_reason(js, step)); 546 ant_http_request_cancel(http_req); 547 } 548} 549 550static ant_value_t fetch_transport_reason(fetch_request_t *req, ant_http_result_t result, const char *error_message) { 551 if (result == ANT_HTTP_RESULT_ABORTED && req->aborted) { 552 ant_value_t signal = request_get_signal(req->js, req->request_obj); 553 return abort_signal_get_reason(signal); 554 } 555 556 return fetch_type_error(req->js, error_message ? error_message : "fetch failed"); 557} 558 559static void fetch_http_on_complete( 560 ant_http_request_t *http_req, 561 ant_http_result_t result, 562 int error_code, const char *error_message, void *user_data 563) { 564 fetch_request_t *req = (fetch_request_t *)user_data; 565 566 ant_t *js = req->js; 567 ant_value_t stream = 0; 568 ant_value_t controller = 0; 569 ant_value_t reason = 0; 570 req->http_req = NULL; 571 572 if (req->restart_pending) { 573 req->restart_pending = false; 574 fetch_start_http(req); 575 return; 576 } 577 578 if (result != ANT_HTTP_RESULT_OK || error_code != 0) { 579 reason = fetch_transport_reason(req, result, error_message); 580 if (is_object_type(req->response_obj)) fetch_error_response_body(req, reason); 581 else fetch_reject(req, reason); 582 fetch_request_release(req); 583 return; 584 } 585 586 if (is_object_type(req->response_obj)) { 587 stream = js_get_slot(req->response_obj, SLOT_RESPONSE_BODY_STREAM); 588 if (rs_is_stream(stream)) { 589 controller = rs_stream_controller(js, stream); 590 rs_controller_close(js, controller); 591 } 592 } else fetch_reject(req, fetch_type_error(js, "fetch completed without a response")); 593 594 fetch_request_release(req); 595} 596 597static char *fetch_data_url_content_type(const char *url) { 598 const char *header = url + 5; 599 const char *comma = strchr(header, ','); 600 const char *base64 = NULL; 601 size_t len = 0; 602 603 if (!comma) return strdup("text/plain;charset=US-ASCII"); 604 base64 = strstr(header, ";base64"); 605 len = base64 && base64 < comma ? (size_t)(base64 - header) : (size_t)(comma - header); 606 if (len == 0) return strdup("text/plain;charset=US-ASCII"); 607 608 return strndup(header, len); 609} 610 611static bool fetch_handle_data_url(fetch_request_t *req) { 612 ant_t *js = req->js; 613 request_data_t *request = request_get_data(req->request_obj); 614 615 char *url = fetch_build_request_url(request); 616 size_t len = 0; 617 char *body = NULL; 618 char *content_type = NULL; 619 620 ant_value_t headers = 0; 621 ant_value_t response = 0; 622 623 if (!url || !esm_is_data_url(url)) { 624 free(url); 625 return false; 626 } 627 628 body = esm_parse_data_url(url, &len); 629 content_type = fetch_data_url_content_type(url); 630 headers = headers_create_empty(js); 631 632 if (!body || !content_type || is_err(headers)) { 633 free(url); 634 free(body); 635 free(content_type); 636 fetch_reject(req, fetch_type_error(js, "Failed to decode data URL")); 637 fetch_request_release(req); 638 return true; 639 } 640 641 headers_set_literal(js, headers, "content-type", content_type); 642 response = response_create_fetched( 643 js, 200, "OK", url, 1, headers, 644 (const uint8_t *)body, len, js_mkundef(), content_type 645 ); 646 647 free(url); 648 free(body); 649 free(content_type); 650 651 if (is_err(response)) { 652 fetch_reject(req, fetch_rejection_reason(js, response)); 653 } else fetch_resolve(req, response); 654 655 fetch_request_release(req); 656 return true; 657} 658 659static ant_value_t fetch_upload_on_reject(ant_t *js, ant_value_t *args, int nargs) { 660 fetch_request_t *req = (fetch_request_t *)js_get_native(js->current_func, FETCH_REQUEST_NATIVE_TAG); 661 ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef(); 662 663 if (!req) return js_mkundef(); 664 req->upload_read_promise = js_mkundef(); 665 666 if (!req->aborted) { 667 if (req->http_req) ant_http_request_cancel(req->http_req); 668 fetch_reject(req, reason); 669 if (!req->http_req) fetch_request_release(req); 670 } 671 672 fetch_request_release(req); 673 return js_mkundef(); 674} 675 676static void fetch_upload_schedule_next_read(fetch_request_t *req); 677static ant_value_t fetch_upload_on_read(ant_t *js, ant_value_t *args, int nargs) { 678 fetch_request_t *req = (fetch_request_t *)js_get_native(js->current_func, FETCH_REQUEST_NATIVE_TAG); 679 680 ant_value_t result = (nargs > 0) ? args[0] : js_mkundef(); 681 ant_value_t done = 0; 682 ant_value_t value = 0; 683 684 const uint8_t *chunk = NULL; 685 size_t chunk_len = 0; 686 int rc = 0; 687 688 if (!req) return js_mkundef(); 689 req->upload_read_promise = js_mkundef(); 690 691 if (req->aborted || !req->http_req) { 692 fetch_request_release(req); 693 return js_mkundef(); 694 } 695 696 done = js_get(js, result, "done"); 697 value = js_get(js, result, "value"); 698 if (done == js_true) { 699 ant_http_request_end(req->http_req); 700 fetch_request_release(req); 701 return js_mkundef(); 702 } 703 704 if (!fetch_get_upload_chunk(value, &chunk, &chunk_len)) { 705 ant_value_t reason = js_mkerr_typed(js, JS_ERR_TYPE, "fetch request body stream chunk must be a Uint8Array"); 706 ant_http_request_cancel(req->http_req); 707 fetch_reject(req, fetch_rejection_reason(js, reason)); 708 fetch_request_release(req); 709 return js_mkundef(); 710 } 711 712 rc = ant_http_request_write(req->http_req, chunk, chunk_len); 713 if (rc != 0) { 714 ant_value_t reason = fetch_type_error(js, uv_strerror(rc)); 715 ant_http_request_cancel(req->http_req); 716 fetch_reject(req, reason); 717 fetch_request_release(req); 718 return js_mkundef(); 719 } 720 721 fetch_upload_schedule_next_read(req); 722 fetch_request_release(req); 723 return js_mkundef(); 724} 725 726static void fetch_upload_schedule_next_read(fetch_request_t *req) { 727 ant_t *js = req->js; 728 729 ant_value_t next_p = 0; 730 ant_value_t fulfill = 0; 731 ant_value_t reject = 0; 732 ant_value_t then_result = 0; 733 734 if (!req || !is_object_type(req->upload_reader)) return; 735 next_p = rs_default_reader_read(js, req->upload_reader); 736 req->upload_read_promise = next_p; 737 738 fulfill = js_heavy_mkfun_native(js, fetch_upload_on_read, req, FETCH_REQUEST_NATIVE_TAG); 739 reject = js_heavy_mkfun_native(js, fetch_upload_on_reject, req, FETCH_REQUEST_NATIVE_TAG); 740 741 fetch_request_retain(req); 742 then_result = js_promise_then(js, next_p, fulfill, reject); 743 promise_mark_handled(then_result); 744} 745 746static void fetch_start_upload(fetch_request_t *req) { 747 ant_t *js = req->js; 748 749 ant_value_t stream = js_get_slot(req->request_obj, SLOT_REQUEST_BODY_STREAM); 750 ant_value_t reader_args[1] = { stream }; 751 ant_value_t saved = js->new_target; 752 ant_value_t reader = 0; 753 754 if (!rs_is_stream(stream)) return; 755 756 js->new_target = g_reader_proto; 757 reader = js_rs_reader_ctor(js, reader_args, 1); 758 js->new_target = saved; 759 760 if (is_err(reader)) { 761 if (req->http_req) ant_http_request_cancel(req->http_req); 762 fetch_reject(req, fetch_rejection_reason(js, reader)); 763 if (!req->http_req) fetch_request_release(req); 764 return; 765 } 766 767 req->upload_reader = reader; 768 fetch_upload_schedule_next_read(req); 769} 770 771static void fetch_start_http(fetch_request_t *req) { 772 request_data_t *request = request_get_data(req->request_obj); 773 ant_http_request_options_t options = {0}; 774 ant_http_header_t *headers = NULL; 775 char *url = NULL; 776 int rc = 0; 777 778 if (!request) { 779 fetch_reject(req, fetch_type_error(req->js, "Invalid Request object")); 780 fetch_request_release(req); 781 return; 782 } 783 784 url = fetch_build_request_url(request); 785 if (!url) { 786 fetch_reject(req, fetch_type_error(req->js, "Invalid request URL")); 787 fetch_request_release(req); 788 return; 789 } 790 791 if (esm_is_data_url(url)) { 792 free(url); 793 fetch_handle_data_url(req); 794 return; 795 } 796 if (!fetch_is_http_url(url)) { 797 free(url); 798 fetch_reject(req, fetch_type_error(req->js, "fetch only supports http:, https:, and data: URLs")); 799 fetch_request_release(req); 800 return; 801 } 802 803 headers = fetch_build_http_headers(req->request_obj); 804 if (!headers) { 805 free(url); 806 fetch_reject(req, fetch_type_error(req->js, "out of memory")); 807 fetch_request_release(req); 808 return; 809 } 810 811 options.method = request->method; 812 options.url = url; 813 options.headers = headers; 814 options.body = request->body_data; 815 options.body_len = request->body_size; 816 options.chunked_body = request->body_is_stream; 817 818 rc = ant_http_request_start( 819 uv_default_loop(), &options, 820 fetch_http_on_response, fetch_http_on_body, fetch_http_on_complete, 821 req, &req->http_req 822 ); 823 824 ant_http_headers_free(headers); 825 free(url); 826 827 if (rc != 0) { 828 fetch_reject(req, fetch_type_error(req->js, uv_strerror(rc))); 829 fetch_request_release(req); 830 return; 831 } 832 833 if (request->body_is_stream) fetch_start_upload(req); 834} 835 836static ant_value_t fetch_abort_listener(ant_t *js, ant_value_t *args, int nargs) { 837 fetch_request_t *req = (fetch_request_t *)js_get_native(js->current_func, FETCH_REQUEST_NATIVE_TAG); 838 ant_value_t signal = 0; 839 ant_value_t reason = 0; 840 841 if (!req || req->aborted) return js_mkundef(); 842 req->aborted = true; 843 signal = request_get_signal(js, req->request_obj); 844 reason = abort_signal_get_reason(signal); 845 846 if (req->http_req) ant_http_request_cancel(req->http_req); 847 fetch_reject(req, reason); 848 if (!req->http_req) fetch_request_release(req); 849 850 return js_mkundef(); 851} 852 853ant_value_t ant_fetch(ant_t *js, ant_value_t *args, int nargs) { 854 ant_value_t input = (nargs >= 1) ? args[0] : js_mkundef(); 855 ant_value_t init = (nargs >= 2) ? args[1] : js_mkundef(); 856 857 ant_value_t promise = js_mkpromise(js); 858 ant_value_t request_obj = 0; 859 860 request_data_t *request = NULL; 861 fetch_request_t *req = NULL; 862 ant_value_t signal = 0; 863 864 request_obj = request_create_from_input_init(js, input, init); 865 if (is_err(request_obj)) { 866 js_reject_promise(js, promise, fetch_rejection_reason(js, request_obj)); 867 return promise; 868 } 869 870 request = request_get_data(request_obj); 871 if (!request) { 872 js_reject_promise(js, promise, fetch_type_error(js, "Invalid Request object")); 873 return promise; 874 } 875 876 req = calloc(1, sizeof(fetch_request_t)); 877 if (!req) { 878 js_reject_promise(js, promise, fetch_type_error(js, "out of memory")); 879 return promise; 880 } 881 882 req->js = js; 883 req->promise = promise; 884 req->request_obj = request_obj; 885 req->response_obj = js_mkundef(); 886 req->abort_listener = js_mkundef(); 887 req->upload_reader = js_mkundef(); 888 req->upload_read_promise = js_mkundef(); 889 req->refs = 1; 890 utarray_push_back(pending_requests, &req); 891 892 signal = request_get_signal(js, request_obj); 893 if (abort_signal_is_signal(signal)) { 894 if (abort_signal_is_aborted(signal)) { 895 fetch_reject(req, abort_signal_get_reason(signal)); 896 fetch_request_release(req); 897 return promise; 898 } 899 900 req->abort_listener = js_heavy_mkfun_native(js, fetch_abort_listener, req, FETCH_REQUEST_NATIVE_TAG); 901 abort_signal_add_listener(js, signal, req->abort_listener); 902 } 903 904 if (request->has_body) request->body_used = true; 905 fetch_start_http(req); 906 return promise; 907} 908 909void init_fetch_module() { 910 utarray_new(pending_requests, &ut_ptr_icd); 911 js_set(rt->js, rt->js->global, "fetch", js_mkfun_flags(ant_fetch, CFUNC_HAS_PROTOTYPE)); 912} 913 914int has_pending_fetches(void) { 915 return pending_requests && utarray_len(pending_requests) > 0; 916} 917 918void gc_mark_fetch(ant_t *js, gc_mark_fn mark) { 919 if (!pending_requests) return; 920 unsigned int len = utarray_len(pending_requests); 921 922 for (unsigned int i = 0; i < len; i++) { 923 fetch_request_t **reqp = (fetch_request_t **)utarray_eltptr(pending_requests, i); 924 if (!reqp || !*reqp) continue; 925 mark(js, (*reqp)->promise); 926 mark(js, (*reqp)->request_obj); 927 mark(js, (*reqp)->response_obj); 928 mark(js, (*reqp)->abort_listener); 929 mark(js, (*reqp)->upload_reader); 930 mark(js, (*reqp)->upload_read_promise); 931 } 932}