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