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 "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}