MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
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}