MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <stdlib.h>
2#include <string.h>
3#include <strings.h>
4#include <ctype.h>
5#include <stdio.h>
6
7#include "ant.h"
8#include "errors.h"
9#include "runtime.h"
10#include "internal.h"
11#include "common.h"
12#include "descriptors.h"
13#include "utf8.h"
14
15#include "modules/assert.h"
16#include "modules/blob.h"
17#include "modules/buffer.h"
18#include "modules/formdata.h"
19#include "modules/headers.h"
20#include "modules/multipart.h"
21#include "modules/response.h"
22#include "modules/symbol.h"
23#include "modules/url.h"
24#include "modules/json.h"
25#include "streams/pipes.h"
26#include "streams/readable.h"
27
28ant_value_t g_response_proto = 0;
29
30static response_data_t *get_data(ant_value_t obj) {
31 ant_value_t slot = js_get_slot(obj, SLOT_DATA);
32 if (vtype(slot) != T_NUM) return NULL;
33 return (response_data_t *)(uintptr_t)(size_t)js_getnum(slot);
34}
35
36response_data_t *response_get_data(ant_value_t obj) {
37 return get_data(obj);
38}
39
40ant_value_t response_get_headers(ant_value_t obj) {
41 return js_get_slot(obj, SLOT_RESPONSE_HEADERS);
42}
43
44static void data_free(response_data_t *d) {
45 if (!d) return;
46 free(d->type);
47 url_state_clear(&d->url);
48 free(d->status_text);
49 free(d->body_data);
50 free(d->body_type);
51 free(d);
52}
53
54static response_data_t *data_new(void) {
55 response_data_t *d = calloc(1, sizeof(response_data_t));
56 if (!d) return NULL;
57 d->type = strdup("default");
58 d->status = 200;
59 d->status_text = strdup("");
60 d->url_list_size = 0;
61 if (!d->type || !d->status_text) {
62 data_free(d);
63 return NULL;
64 }
65 return d;
66}
67
68static response_data_t *data_dup(const response_data_t *src) {
69 response_data_t *d = calloc(1, sizeof(response_data_t));
70 url_state_t *su = NULL;
71 url_state_t *du = NULL;
72
73 if (!d) return NULL;
74 d->type = src->type ? strdup(src->type) : NULL;
75 d->status_text = src->status_text ? strdup(src->status_text) : NULL;
76 d->has_url = src->has_url;
77 d->url_list_size = src->url_list_size;
78 d->status = src->status;
79 d->body_is_stream = src->body_is_stream;
80 d->has_body = src->has_body;
81 d->body_used = src->body_used;
82 d->body_size = src->body_size;
83 d->body_type = src->body_type ? strdup(src->body_type) : NULL;
84
85 su = (url_state_t *)&src->url;
86 du = &d->url;
87#define DUP_US(f) do { du->f = su->f ? strdup(su->f) : NULL; } while (0)
88 DUP_US(protocol);
89 DUP_US(username);
90 DUP_US(password);
91 DUP_US(hostname);
92 DUP_US(port);
93 DUP_US(pathname);
94 DUP_US(search);
95 DUP_US(hash);
96#undef DUP_US
97
98 if (src->body_data && src->body_size > 0) {
99 d->body_data = malloc(src->body_size);
100 if (!d->body_data) {
101 data_free(d);
102 return NULL;
103 }
104 memcpy(d->body_data, src->body_data, src->body_size);
105 }
106
107 return d;
108}
109
110static ant_value_t response_rejection_reason(ant_t *js, ant_value_t value) {
111 if (!is_err(value)) return value;
112 ant_value_t reason = js->thrown_exists ? js->thrown_value : value;
113 js->thrown_exists = false;
114 js->thrown_value = js_mkundef();
115 js->thrown_stack = js_mkundef();
116 return reason;
117}
118
119static bool copy_body_bytes(
120 ant_t *js, const uint8_t *src, size_t src_len,
121 uint8_t **out_data, size_t *out_size, ant_value_t *err_out
122) {
123 uint8_t *buf = NULL;
124
125 *out_data = NULL;
126 *out_size = 0;
127 if (src_len == 0) return true;
128
129 buf = malloc(src_len);
130 if (!buf) {
131 *err_out = js_mkerr(js, "out of memory");
132 return false;
133 }
134
135 memcpy(buf, src, src_len);
136 *out_data = buf;
137 *out_size = src_len;
138 return true;
139}
140
141static bool extract_buffer_source_body(
142 ant_t *js, ant_value_t body_val,
143 uint8_t **out_data, size_t *out_size, ant_value_t *err_out
144) {
145 const uint8_t *src = NULL;
146 size_t src_len = 0;
147
148 if (!((
149 vtype(body_val) == T_TYPEDARRAY || vtype(body_val) == T_OBJ) &&
150 buffer_source_get_bytes(js, body_val, &src, &src_len))
151 ) return false;
152
153 return copy_body_bytes(js, src, src_len, out_data, out_size, err_out);
154}
155
156static bool extract_stream_body(
157 ant_t *js, ant_value_t body_val,
158 ant_value_t *out_stream, ant_value_t *err_out
159) {
160 if (!rs_is_stream(body_val)) return false;
161 if (rs_stream_unusable(body_val)) {
162 *err_out = js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked");
163 return false;
164 }
165 *out_stream = body_val;
166 return true;
167}
168
169static bool extract_blob_body(
170 ant_t *js, ant_value_t body_val,
171 uint8_t **out_data, size_t *out_size, char **out_type, ant_value_t *err_out
172) {
173 blob_data_t *bd = blob_is_blob(js, body_val) ? blob_get_data(body_val) : NULL;
174 if (!bd) return false;
175 if (!copy_body_bytes(js, bd->data, bd->size, out_data, out_size, err_out)) return false;
176 if (bd->type && bd->type[0]) *out_type = strdup(bd->type);
177 return true;
178}
179
180static bool extract_urlsearchparams_body(
181 ant_t *js, ant_value_t body_val,
182 uint8_t **out_data, size_t *out_size, char **out_type
183) {
184 char *serialized = NULL;
185 if (!usp_is_urlsearchparams(js, body_val)) return false;
186 serialized = usp_serialize(js, body_val);
187 if (!serialized) return true;
188
189 *out_data = (uint8_t *)serialized;
190 *out_size = strlen(serialized);
191 *out_type = strdup("application/x-www-form-urlencoded;charset=UTF-8");
192
193 return true;
194}
195
196static bool extract_formdata_body(
197 ant_t *js, ant_value_t body_val,
198 uint8_t **out_data, size_t *out_size, char **out_type, ant_value_t *err_out
199) {
200 char *boundary = NULL;
201 char *content_type = NULL;
202 size_t mp_size = 0;
203 uint8_t *mp = NULL;
204
205 if (!formdata_is_formdata(js, body_val)) return false;
206 mp = formdata_serialize_multipart(js, body_val, &mp_size, &boundary);
207 if (!mp) {
208 *err_out = js_mkerr(js, "out of memory");
209 return false;
210 }
211
212 if (mp_size > 0) *out_data = mp;
213 else free(mp);
214 *out_size = mp_size;
215
216 if (!boundary) return true;
217
218 size_t ct_len = snprintf(NULL, 0, "multipart/form-data; boundary=%s", boundary);
219 content_type = malloc(ct_len + 1);
220 if (!content_type) {
221 free(boundary);
222 if (mp_size > 0) free(mp);
223 *out_data = NULL;
224 *out_size = 0;
225 *err_out = js_mkerr(js, "out of memory");
226 return false;
227 }
228
229 snprintf(content_type, ct_len + 1, "multipart/form-data; boundary=%s", boundary);
230 free(boundary);
231 *out_type = content_type;
232 return true;
233}
234
235static bool extract_string_body(
236 ant_t *js, ant_value_t body_val,
237 uint8_t **out_data, size_t *out_size, char **out_type, ant_value_t *err_out
238) {
239 size_t len = 0;
240 const char *s = NULL;
241
242 if (vtype(body_val) != T_STR) {
243 body_val = js_tostring_val(js, body_val);
244 if (is_err(body_val)) {
245 *err_out = body_val;
246 return false;
247 }}
248
249 s = js_getstr(js, body_val, &len);
250 if (!copy_body_bytes(js, (const uint8_t *)s, len, out_data, out_size, err_out)) return false;
251 *out_type = strdup("text/plain;charset=UTF-8");
252
253 return true;
254}
255
256static bool extract_body(
257 ant_t *js, ant_value_t body_val,
258 uint8_t **out_data, size_t *out_size, char **out_type,
259 ant_value_t *out_stream, ant_value_t *err_out
260) {
261 *out_data = NULL;
262 *out_size = 0;
263 *out_type = NULL;
264 *out_stream = js_mkundef();
265 *err_out = js_mkundef();
266
267 if (vtype(body_val) == T_NULL || vtype(body_val) == T_UNDEF) return true;
268 if (extract_buffer_source_body(js, body_val, out_data, out_size, err_out)) return true;
269 if (vtype(body_val) == T_OBJ && rs_is_stream(body_val)) return extract_stream_body(js, body_val, out_stream, err_out);
270 if (vtype(body_val) == T_OBJ && extract_blob_body(js, body_val, out_data, out_size, out_type, err_out)) return true;
271 if (vtype(body_val) == T_OBJ && extract_urlsearchparams_body(js, body_val, out_data, out_size, out_type)) return true;
272 if (vtype(body_val) == T_OBJ && extract_formdata_body(js, body_val, out_data, out_size, out_type, err_out)) return true;
273
274 return extract_string_body(js, body_val, out_data, out_size, out_type, err_out);
275}
276
277static bool response_content_type_has_charset(const char *value) {
278 const char *p = NULL;
279
280 if (!value) return false;
281 p = strchr(value, ';');
282
283 while (p) {
284 p++;
285 while (*p == ' ' || *p == '\t') p++;
286 if (strncasecmp(p, "charset", 7) == 0) {
287 p += 7;
288 while (*p == ' ' || *p == '\t') p++;
289 if (*p == '=') return true;
290 }
291 p = strchr(p, ';');
292 }
293
294 return false;
295}
296
297static void response_maybe_normalize_text_content_type(
298 ant_t *js, ant_value_t headers, ant_value_t current_type, const char *body_type
299) {
300 const char *current = NULL;
301
302 if (!body_type || !headers_is_headers(headers)) return;
303 if (vtype(current_type) != T_STR) return;
304
305 current = js_getstr(js, current_type, NULL);
306 if (!current) return;
307 if (strncasecmp(current, "text/", 5) != 0) return;
308 if (response_content_type_has_charset(current)) return;
309 if (!response_content_type_has_charset(body_type)) return;
310
311 headers_set_literal(js, headers, "content-type", body_type);
312}
313
314enum {
315 BODY_TEXT = 0,
316 BODY_JSON,
317 BODY_ARRAYBUFFER,
318 BODY_BLOB,
319 BODY_BYTES,
320 BODY_FORMDATA
321};
322
323static const char *response_effective_body_type(ant_t *js, ant_value_t resp_obj, response_data_t *d) {
324 ant_value_t headers = js_get_slot(resp_obj, SLOT_RESPONSE_HEADERS);
325 if (!headers_is_headers(headers)) return d ? d->body_type : NULL;
326 ant_value_t ct = headers_get_value(js, headers, "content-type");
327 if (vtype(ct) == T_STR) return js_getstr(js, ct, NULL);
328 return d ? d->body_type : NULL;
329}
330
331static void strip_utf8_bom(const uint8_t **data, size_t *size) {
332 if (!data || !*data || !size || *size < 3) return;
333 if ((*data)[0] == 0xEF && (*data)[1] == 0xBB && (*data)[2] == 0xBF) { *data += 3; *size -= 3; }
334}
335
336static void resolve_body_promise(
337 ant_t *js, ant_value_t promise,
338 const uint8_t *data, size_t size,
339 const char *body_type, int mode, bool has_body
340) {
341 switch (mode) {
342 case BODY_TEXT: {
343 ant_value_t str = (data && size > 0) ? js_mkstr(js, (const char *)data, size) : js_mkstr(js, "", 0);
344 js_resolve_promise(js, promise, str);
345 break;
346 }
347 case BODY_JSON: {
348 const uint8_t *json_data = data;
349 size_t json_size = size;
350 strip_utf8_bom(&json_data, &json_size);
351
352 ant_value_t str = (json_data && json_size > 0)
353 ? js_mkstr(js, (const char *)json_data, json_size)
354 : js_mkstr(js, "", 0);
355
356 ant_value_t parsed = json_parse_value(js, str);
357 if (is_err(parsed)) js_reject_promise(js, promise, response_rejection_reason(js, parsed));
358 else js_resolve_promise(js, promise, parsed);
359
360 break;
361 }
362 case BODY_ARRAYBUFFER: {
363 ArrayBufferData *ab = create_array_buffer_data(size);
364 if (!ab) {
365 js_reject_promise(js, promise, js_mkerr(js, "out of memory"));
366 break;
367 }
368 if (data && size > 0) memcpy(ab->data, data, size);
369 js_resolve_promise(js, promise, create_arraybuffer_obj(js, ab));
370 break;
371 }
372 case BODY_BLOB: {
373 const char *type = body_type ? body_type : "";
374 js_resolve_promise(js, promise, blob_create(js, data, size, type));
375 break;
376 }
377 case BODY_BYTES: {
378 ArrayBufferData *ab = create_array_buffer_data(size);
379 if (!ab) {
380 js_reject_promise(js, promise, js_mkerr(js, "out of memory"));
381 break;
382 }
383 if (data && size > 0) memcpy(ab->data, data, size);
384 js_resolve_promise(js, promise,
385 create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, size, "Uint8Array"));
386 break;
387 }
388 case BODY_FORMDATA: {
389 ant_value_t fd = formdata_parse_body(js, data, size, body_type, has_body);
390 if (is_err(fd)) js_reject_promise(js, promise, response_rejection_reason(js, fd));
391 else js_resolve_promise(js, promise, fd);
392 break;
393 }}
394}
395
396static bool response_chunk_is_uint8_array(ant_value_t chunk, TypedArrayData **out_ta) {
397 if (!is_object_type(chunk)) return false;
398 TypedArrayData *ta = buffer_get_typedarray_data(chunk);
399 if (!ta || !ta->buffer || ta->buffer->is_detached) return false;
400 if (ta->type != TYPED_ARRAY_UINT8) return false;
401 *out_ta = ta;
402 return true;
403}
404
405static uint8_t *concat_uint8_chunks(
406 ant_t *js, ant_value_t chunks,
407 size_t *out_size, ant_value_t *err_out
408) {
409 ant_offset_t n = js_arr_len(js, chunks);
410 size_t total = 0;
411 size_t pos = 0;
412 uint8_t *buf = NULL;
413 TypedArrayData *ta = NULL;
414
415 *out_size = 0;
416 *err_out = js_mkundef();
417
418 for (ant_offset_t i = 0; i < n; i++) {
419 ant_value_t chunk = js_arr_get(js, chunks, i);
420 if (!response_chunk_is_uint8_array(chunk, &ta)) {
421 *err_out = js_mkerr_typed(js, JS_ERR_TYPE, "Response body stream chunk must be a Uint8Array");
422 return NULL;
423 }
424 total += ta->byte_length;
425 }
426
427 buf = total > 0 ? malloc(total) : NULL;
428 if (total > 0 && !buf) {
429 *err_out = js_mkerr(js, "out of memory");
430 return NULL;
431 }
432
433 for (ant_offset_t i = 0; i < n; i++) {
434 ant_value_t chunk = js_arr_get(js, chunks, i);
435 if (!response_chunk_is_uint8_array(chunk, &ta)) {
436 free(buf);
437 *err_out = js_mkerr_typed(js, JS_ERR_TYPE, "Response body stream chunk must be a Uint8Array");
438 return NULL;
439 }
440 if (ta->byte_length == 0) continue;
441 memcpy(buf + pos, ta->buffer->data + ta->byte_offset, ta->byte_length);
442 pos += ta->byte_length;
443 }
444
445 *out_size = pos;
446 return buf;
447}
448
449static ant_value_t stream_body_read(ant_t *js, ant_value_t *args, int nargs);
450static ant_value_t stream_body_rejected(ant_t *js, ant_value_t *args, int nargs) {
451 ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
452 ant_value_t promise = js_get(js, state, "promise");
453 ant_value_t reason = (nargs > 0) ? args[0] : js_mkundef();
454 js_reject_promise(js, promise, reason);
455 return js_mkundef();
456}
457
458static void stream_schedule_next_read(ant_t *js, ant_value_t state, ant_value_t reader) {
459 ant_value_t next_p = rs_default_reader_read(js, reader);
460 ant_value_t fulfill = js_heavy_mkfun(js, stream_body_read, state);
461 ant_value_t reject = js_heavy_mkfun(js, stream_body_rejected, state);
462 ant_value_t then_result = js_promise_then(js, next_p, fulfill, reject);
463 promise_mark_handled(then_result);
464}
465
466static ant_value_t stream_body_read(ant_t *js, ant_value_t *args, int nargs) {
467 ant_value_t state = js_get_slot(js->current_func, SLOT_DATA);
468 ant_value_t result = (nargs > 0) ? args[0] : js_mkundef();
469 ant_value_t promise = js_get(js, state, "promise");
470 ant_value_t reader = js_get(js, state, "reader");
471 ant_value_t chunks = js_get(js, state, "chunks");
472 int mode = (int)js_getnum(js_get(js, state, "mode"));
473
474 ant_value_t done_val = js_get(js, result, "done");
475 ant_value_t value = js_get(js, result, "value");
476
477 if (vtype(done_val) == T_BOOL && done_val == js_true) {
478 size_t size = 0;
479 ant_value_t chunk_err = js_mkundef();
480 uint8_t *data = concat_uint8_chunks(js, chunks, &size, &chunk_err);
481 if (is_err(chunk_err)) {
482 js_reject_promise(js, promise, response_rejection_reason(js, chunk_err));
483 return js_mkundef();
484 }
485 ant_value_t type_v = js_get(js, state, "type");
486 const char *body_type = (vtype(type_v) == T_STR) ? js_getstr(js, type_v, NULL) : NULL;
487 resolve_body_promise(js, promise, data, size, body_type, mode, true);
488 free(data);
489 return js_mkundef();
490 }
491
492 if (vtype(value) != T_UNDEF && vtype(value) != T_NULL) js_arr_push(js, chunks, value);
493 stream_schedule_next_read(js, state, reader);
494 return js_mkundef();
495}
496
497static ant_value_t consume_body_from_stream(
498 ant_t *js, ant_value_t stream,
499 ant_value_t promise, int mode,
500 const char *body_type
501) {
502 ant_value_t reader_args[1] = { stream };
503 ant_value_t saved = js->new_target;
504
505 js->new_target = g_reader_proto;
506 ant_value_t reader = js_rs_reader_ctor(js, reader_args, 1);
507 js->new_target = saved;
508
509 if (is_err(reader)) {
510 js_reject_promise(js, promise, reader);
511 return promise;
512 }
513
514 ant_value_t state = js_mkobj(js);
515 js_set(js, state, "promise", promise);
516 js_set(js, state, "reader", reader);
517 js_set(js, state, "chunks", js_mkarr(js));
518 js_set(js, state, "mode", js_mknum(mode));
519 js_set(js, state, "type", body_type ? js_mkstr(js, body_type, strlen(body_type)) : js_mkundef());
520
521 stream_schedule_next_read(js, state, reader);
522 return promise;
523}
524
525static ant_value_t consume_body(ant_t *js, int mode) {
526 ant_value_t this = js_getthis(js);
527 response_data_t *d = get_data(this);
528 ant_value_t promise = js_mkpromise(js);
529 ant_value_t stream = 0;
530
531 if (!d) {
532 js_reject_promise(js, promise, response_rejection_reason(js,
533 js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Response object")));
534 return promise;
535 }
536
537 if (!d->has_body) {
538 resolve_body_promise(js, promise, NULL, 0, response_effective_body_type(js, this, d), mode, false);
539 return promise;
540 }
541
542 stream = js_get_slot(this, SLOT_RESPONSE_BODY_STREAM);
543 if (d->body_used || (rs_is_stream(stream) && rs_stream_unusable(stream))) {
544 js_reject_promise(js, promise, response_rejection_reason(js,
545 js_mkerr_typed(js, JS_ERR_TYPE, "body stream is disturbed or locked")));
546 return promise;
547 }
548
549 d->body_used = true;
550 if (rs_is_stream(stream))
551 return consume_body_from_stream(js, stream, promise, mode, response_effective_body_type(js, this, d));
552
553 resolve_body_promise(js, promise, d->body_data, d->body_size, response_effective_body_type(js, this, d), mode, true);
554 return promise;
555}
556
557static ant_value_t js_res_text(ant_t *js, ant_value_t *args, int nargs) {
558 return consume_body(js, BODY_TEXT);
559}
560
561static ant_value_t js_res_json(ant_t *js, ant_value_t *args, int nargs) {
562 return consume_body(js, BODY_JSON);
563}
564
565static ant_value_t js_res_array_buffer(ant_t *js, ant_value_t *args, int nargs) {
566 return consume_body(js, BODY_ARRAYBUFFER);
567}
568
569static ant_value_t js_res_blob(ant_t *js, ant_value_t *args, int nargs) {
570 return consume_body(js, BODY_BLOB);
571}
572
573static ant_value_t js_res_bytes(ant_t *js, ant_value_t *args, int nargs) {
574 return consume_body(js, BODY_BYTES);
575}
576
577static ant_value_t js_res_form_data(ant_t *js, ant_value_t *args, int nargs) {
578 return consume_body(js, BODY_FORMDATA);
579}
580
581static bool is_null_body_status(int status) {
582 return status == 101 || status == 103 || status == 204 || status == 205 || status == 304;
583}
584
585static bool is_redirect_status(int status) {
586 return status == 301 || status == 302 || status == 303 || status == 307 || status == 308;
587}
588
589static bool is_ok_status(int status) {
590 return status >= 200 && status <= 299;
591}
592
593static bool is_valid_reason_phrase(const char *str, size_t len) {
594 utf8proc_int32_t cp = 0;
595 utf8proc_ssize_t n = 0;
596 size_t pos = 0;
597
598 while (pos < len) {
599 n = utf8_next((const utf8proc_uint8_t *)(str + pos), (utf8proc_ssize_t)(len - pos), &cp);
600 if (cp > 0xFF) return false;
601 if (cp == '\r' || cp == '\n') return false;
602 pos += (size_t)n;
603 }
604
605 return true;
606}
607
608static ant_value_t response_init_status(ant_t *js, ant_value_t init, response_data_t *resp) {
609 ant_value_t status_v = js_get(js, init, "status");
610 double status_num = 200;
611
612 if (vtype(status_v) != T_UNDEF) {
613 status_num = (vtype(status_v) == T_NUM) ? js_getnum(status_v) : js_to_number(js, status_v);
614 }
615
616 if (status_num < 200 || status_num > 599 || status_num != (int)status_num) {
617 return js_mkerr_typed(js, JS_ERR_RANGE, "Failed to construct 'Response': status must be in the range 200-599");
618 }
619
620 resp->status = (int)status_num;
621 return js_mkundef();
622}
623
624static ant_value_t response_init_status_text(ant_t *js, ant_value_t init, response_data_t *resp) {
625 ant_value_t status_text_v = js_get(js, init, "statusText");
626 size_t len = 0;
627 const char *status_text = NULL;
628 char *dup = NULL;
629
630 if (vtype(status_text_v) == T_UNDEF) return js_mkundef();
631 if (vtype(status_text_v) != T_STR) {
632 status_text_v = js_tostring_val(js, status_text_v);
633 if (is_err(status_text_v)) return status_text_v;
634 }
635
636 status_text = js_getstr(js, status_text_v, &len);
637 if (!is_valid_reason_phrase(status_text, len)) {
638 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Response': Invalid statusText");
639 }
640
641 dup = strdup(status_text);
642 if (!dup) return js_mkerr(js, "out of memory");
643 free(resp->status_text);
644 resp->status_text = dup;
645 return js_mkundef();
646}
647
648static ant_value_t response_apply_body(
649 ant_t *js, ant_value_t resp_obj, ant_value_t headers, response_data_t *resp,
650 ant_value_t body_val
651) {
652 ant_value_t body_err = js_mkundef();
653 ant_value_t body_stream = js_mkundef();
654 uint8_t *body_data = NULL;
655 size_t body_size = 0;
656 char *body_type = NULL;
657
658 ant_value_t current_type = js_mknull();
659
660 if (vtype(body_val) == T_NULL || vtype(body_val) == T_UNDEF) return js_mkundef();
661
662 if (!extract_body(js, body_val, &body_data, &body_size, &body_type, &body_stream, &body_err)) {
663 return is_err(body_err) ? body_err : js_mkerr(js, "Failed to extract body");
664 }
665
666 if (is_null_body_status(resp->status)) {
667 free(body_data);
668 free(body_type);
669 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Response': Response with null body status cannot have body");
670 }
671
672 free(resp->body_data);
673 free(resp->body_type);
674 resp->body_data = body_data;
675 resp->body_size = body_size;
676 resp->body_type = body_type;
677 resp->body_is_stream = rs_is_stream(body_stream);
678 resp->has_body = true;
679
680 if (resp->body_is_stream) js_set_slot_wb(js, resp_obj, SLOT_RESPONSE_BODY_STREAM, body_stream);
681 current_type = headers_get_value(js, headers, "content-type");
682
683 if (body_type && !is_err(current_type) && vtype(current_type) == T_NULL)
684 headers_append_if_missing(headers, "content-type", body_type);
685 else if (!is_err(current_type))
686 response_maybe_normalize_text_content_type(js, headers, current_type, body_type);
687
688 return js_mkundef();
689}
690
691static ant_value_t response_init_common(
692 ant_t *js, ant_value_t resp_obj, ant_value_t init,
693 ant_value_t body_val, headers_guard_t guard
694) {
695 response_data_t *resp = get_data(resp_obj);
696 ant_value_t headers = js_get_slot(resp_obj, SLOT_RESPONSE_HEADERS);
697 ant_value_t step = js_mkundef();
698
699 if (!resp) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Response object");
700 if (vtype(init) != T_UNDEF) {
701 ant_value_t init_headers = js_get(js, init, "headers");
702 step = response_init_status(js, init, resp);
703 if (is_err(step)) return step;
704 step = response_init_status_text(js, init, resp);
705 if (is_err(step)) return step;
706 if (vtype(init_headers) != T_UNDEF) {
707 headers = headers_create_from_init(js, init_headers);
708 if (is_err(headers)) return headers;
709 }
710 headers_set_guard(headers, guard);
711 headers_apply_guard(headers);
712 js_set_slot_wb(js, resp_obj, SLOT_RESPONSE_HEADERS, headers);
713 }
714
715 return response_apply_body(js, resp_obj, headers, resp, body_val);
716}
717
718static ant_value_t response_new(headers_guard_t guard) {
719 ant_t *js = rt->js;
720 response_data_t *resp = data_new();
721 ant_value_t obj = 0;
722 ant_value_t headers = 0;
723
724 if (!resp) return js_mkerr(js, "out of memory");
725 obj = js_mkobj(js);
726 js_set_proto_init(obj, g_response_proto);
727 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_RESPONSE));
728 js_set_slot(obj, SLOT_DATA, ANT_PTR(resp));
729
730 headers = headers_create_empty(js);
731 if (is_err(headers)) {
732 data_free(resp);
733 return headers;
734 }
735
736 headers_set_guard(headers, guard);
737 headers_apply_guard(headers);
738 js_set_slot_wb(js, obj, SLOT_RESPONSE_HEADERS, headers);
739 js_set_slot_wb(js, obj, SLOT_RESPONSE_BODY_STREAM, js_mkundef());
740 return obj;
741}
742
743static ant_value_t js_response_ctor(ant_t *js, ant_value_t *args, int nargs) {
744 ant_value_t body = (nargs >= 1) ? args[0] : js_mknull();
745 ant_value_t init = (nargs >= 2 && vtype(args[1]) != T_UNDEF) ? args[1] : js_mkundef();
746 ant_value_t obj = 0;
747 ant_value_t proto = 0;
748 ant_value_t step = 0;
749
750 if (vtype(js->new_target) == T_UNDEF) {
751 return js_mkerr_typed(js, JS_ERR_TYPE, "Response constructor requires 'new'");
752 }
753
754 obj = response_new(HEADERS_GUARD_RESPONSE);
755 if (is_err(obj)) return obj;
756
757 proto = js_instance_proto_from_new_target(js, g_response_proto);
758 if (is_object_type(proto)) js_set_proto_init(obj, proto);
759
760 step = response_init_common(js, obj, init, body, HEADERS_GUARD_RESPONSE);
761 if (is_err(step)) {
762 data_free(get_data(obj));
763 return step;
764 }
765
766 return obj;
767}
768
769static ant_value_t response_create_static(
770 ant_t *js, const char *type, int status, const char *status_text, headers_guard_t guard
771) {
772 ant_value_t obj = response_new(guard);
773 response_data_t *resp = NULL;
774
775 if (is_err(obj)) return obj;
776 resp = get_data(obj);
777
778 free(resp->type);
779 resp->type = strdup(type ? type : "default");
780 if (!resp->type) {
781 data_free(resp);
782 return js_mkerr(js, "out of memory");
783 }
784
785 resp->status = status;
786 free(resp->status_text);
787 resp->status_text = strdup(status_text ? status_text : "");
788 if (!resp->status_text) {
789 data_free(resp);
790 return js_mkerr(js, "out of memory");
791 }
792
793 return obj;
794}
795
796static ant_value_t js_response_error(ant_t *js, ant_value_t *args, int nargs) {
797 (void)args;
798 (void)nargs;
799 ant_value_t obj = response_create_static(js, "error", 0, "", HEADERS_GUARD_IMMUTABLE);
800 if (is_err(obj)) return obj;
801 return obj;
802}
803
804static ant_value_t js_response_redirect(ant_t *js, ant_value_t *args, int nargs) {
805 ant_value_t url_v = (nargs >= 1) ? args[0] : js_mkundef();
806 ant_value_t status_v = (nargs >= 2) ? args[1] : js_mknum(302);
807 ant_value_t obj = 0;
808 ant_value_t headers = 0;
809 const char *url_str = NULL;
810 int status = 302;
811 url_state_t parsed = {0};
812 char *href = NULL;
813
814 if (vtype(url_v) != T_STR) {
815 url_v = js_tostring_val(js, url_v);
816 if (is_err(url_v)) return url_v;
817 }
818
819 status = (vtype(status_v) == T_NUM) ? (int)js_getnum(status_v) : (int)js_to_number(js, status_v);
820 if (!is_redirect_status(status)) {
821 return js_mkerr_typed(js, JS_ERR_RANGE, "Response.redirect status must be 301, 302, 303, 307, or 308");
822 }
823
824 url_str = js_getstr(js, url_v, NULL);
825 if (parse_url_to_state(url_str, NULL, &parsed) != 0) {
826 url_state_clear(&parsed);
827 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'Response': Invalid URL");
828 }
829
830 href = build_href(&parsed);
831 if (!href) {
832 url_state_clear(&parsed);
833 return js_mkerr(js, "out of memory");
834 }
835
836 obj = response_create_static(js, "default", status, "", HEADERS_GUARD_IMMUTABLE);
837 if (is_err(obj)) {
838 free(href);
839 url_state_clear(&parsed);
840 return obj;
841 }
842
843 headers = js_get_slot(obj, SLOT_RESPONSE_HEADERS);
844 headers_set_guard(headers, HEADERS_GUARD_NONE);
845 headers_append_if_missing(headers, "location", href);
846 headers_set_guard(headers, HEADERS_GUARD_IMMUTABLE);
847 headers_apply_guard(headers);
848
849 free(href);
850 url_state_clear(&parsed);
851 return obj;
852}
853
854static ant_value_t js_response_json_static(ant_t *js, ant_value_t *args, int nargs) {
855 ant_value_t init = (nargs >= 2 && vtype(args[1]) != T_UNDEF) ? args[1] : js_mkundef();
856 ant_value_t stringify = 0;
857 ant_value_t obj = 0;
858 ant_value_t headers = 0;
859 ant_value_t step = 0;
860 bool init_has_content_type = false;
861
862 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "Response.json requires 1 argument");
863 stringify = json_stringify_value(js, args[0]);
864 if (is_err(stringify)) return stringify;
865 if (vtype(stringify) == T_UNDEF) {
866 return js_mkerr_typed(js, JS_ERR_TYPE, "Response.json data is not JSON serializable");
867 }
868
869 init_has_content_type =
870 vtype(init) != T_UNDEF &&
871 headers_init_has_name(js, js_get(js, init, "headers"), "content-type");
872
873 obj = response_new(HEADERS_GUARD_RESPONSE);
874 if (is_err(obj)) return obj;
875
876 step = response_init_common(js, obj, init, stringify, HEADERS_GUARD_RESPONSE);
877 if (is_err(step)) {
878 data_free(get_data(obj));
879 return step;
880 }
881
882 headers = js_get_slot(obj, SLOT_RESPONSE_HEADERS);
883 if (!init_has_content_type) headers_set_literal(js, headers, "content-type", "application/json");
884 return obj;
885}
886
887static ant_value_t res_body_pull(ant_t *js, ant_value_t *args, int nargs) {
888 ant_value_t resp_obj = js_get_slot(js->current_func, SLOT_DATA);
889 response_data_t *d = get_data(resp_obj);
890 ant_value_t ctrl = (nargs > 0) ? args[0] : js_mkundef();
891
892 if (d && d->body_data && d->body_size > 0) {
893 ArrayBufferData *ab = create_array_buffer_data(d->body_size);
894 if (ab) {
895 memcpy(ab->data, d->body_data, d->body_size);
896 rs_controller_enqueue(js, ctrl,
897 create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, d->body_size, "Uint8Array"));
898 }
899 }
900
901 rs_controller_close(js, ctrl);
902 return js_mkundef();
903}
904
905#define RES_GETTER_START(name) \
906 static ant_value_t js_res_get_##name(ant_t *js, ant_value_t *args, int nargs) { \
907 ant_value_t this = js_getthis(js); \
908 response_data_t *d = get_data(this); \
909 if (!d) return js_mkundef();
910
911#define RES_GETTER_END }
912
913RES_GETTER_START(type)
914 const char *type = d->type ? d->type : "default";
915 return js_mkstr(js, type, strlen(type));
916RES_GETTER_END
917
918RES_GETTER_START(url)
919 char *href = NULL;
920 char *hash = NULL;
921 ant_value_t ret = 0;
922
923 if (!d->has_url) return js_mkstr(js, "", 0);
924 href = build_href(&d->url);
925 if (!href) return js_mkstr(js, "", 0);
926 hash = strchr(href, '#');
927 if (hash) *hash = '\0';
928 ret = js_mkstr(js, href, strlen(href));
929 free(href);
930 return ret;
931RES_GETTER_END
932
933RES_GETTER_START(redirected)
934 return js_bool(d->url_list_size > 1);
935RES_GETTER_END
936
937RES_GETTER_START(status)
938 return js_mknum(d->status);
939RES_GETTER_END
940
941RES_GETTER_START(ok)
942 return js_bool(is_ok_status(d->status));
943RES_GETTER_END
944
945RES_GETTER_START(status_text)
946 const char *status_text = d->status_text ? d->status_text : "";
947 return js_mkstr(js, status_text, strlen(status_text));
948RES_GETTER_END
949
950RES_GETTER_START(headers)
951 return js_get_slot(this, SLOT_RESPONSE_HEADERS);
952RES_GETTER_END
953
954RES_GETTER_START(body)
955 ant_value_t stored_stream = js_get_slot(this, SLOT_RESPONSE_BODY_STREAM);
956 if (!d->has_body) return js_mknull();
957 if (rs_is_stream(stored_stream)) return stored_stream;
958 if (d->body_used) return js_mknull();
959 ant_value_t pull = js_heavy_mkfun(js, res_body_pull, this);
960 ant_value_t stream = rs_create_stream(js, pull, js_mkundef(), 1.0);
961 if (!is_err(stream)) js_set_slot_wb(js, this, SLOT_RESPONSE_BODY_STREAM, stream);
962 return stream;
963RES_GETTER_END
964
965RES_GETTER_START(body_used)
966 ant_value_t stored_stream = js_get_slot(this, SLOT_RESPONSE_BODY_STREAM);
967 bool used = d->body_used || (rs_is_stream(stored_stream) && rs_stream_disturbed(stored_stream));
968 return js_bool(used);
969RES_GETTER_END
970
971#undef RES_GETTER_START
972#undef RES_GETTER_END
973
974static ant_value_t response_inspect_finish(ant_t *js, ant_value_t this_obj, ant_value_t body_obj) {
975 ant_value_t tag_val = js_get_sym(js, this_obj, get_toStringTag_sym());
976 const char *tag = vtype(tag_val) == T_STR ? js_getstr(js, tag_val, NULL) : "Response";
977
978 js_inspect_builder_t builder;
979 if (!js_inspect_builder_init_dynamic(&builder, js, 128)) {
980 return js_mkerr(js, "out of memory");
981 }
982
983 bool ok = js_inspect_header_for(&builder, body_obj, "%s", tag);
984 if (ok) ok = js_inspect_object_body(&builder, body_obj);
985 if (ok) ok = js_inspect_close(&builder);
986
987 if (!ok) {
988 js_inspect_builder_dispose(&builder);
989 return js_mkerr(js, "out of memory");
990 }
991
992 return js_inspect_builder_result(&builder);
993}
994
995// TODO: make dry
996static bool response_inspect_set(
997 ant_t *js, ant_value_t obj, const char *key,
998 ant_value_t value, ant_value_t *err_out
999) {
1000 if (is_err(value)) {
1001 *err_out = value;
1002 return false;
1003 }
1004
1005 js_set(js, obj, key, value);
1006 return true;
1007}
1008
1009static ant_value_t response_inspect(ant_t *js, ant_value_t *args, int nargs) {
1010 ant_value_t this_obj = js_getthis(js);
1011 ant_value_t out = js_mkobj(js);
1012 ant_value_t err = 0;
1013
1014 if (!response_inspect_set(js, out, "type", js_res_get_type(js, NULL, 0), &err)) return err;
1015 if (!response_inspect_set(js, out, "url", js_res_get_url(js, NULL, 0), &err)) return err;
1016 if (!response_inspect_set(js, out, "redirected", js_res_get_redirected(js, NULL, 0), &err)) return err;
1017 if (!response_inspect_set(js, out, "status", js_res_get_status(js, NULL, 0), &err)) return err;
1018 if (!response_inspect_set(js, out, "ok", js_res_get_ok(js, NULL, 0), &err)) return err;
1019 if (!response_inspect_set(js, out, "statusText", js_res_get_status_text(js, NULL, 0), &err)) return err;
1020 if (!response_inspect_set(js, out, "headers", js_res_get_headers(js, NULL, 0), &err)) return err;
1021
1022 return response_inspect_finish(js, this_obj, out);
1023}
1024
1025static ant_value_t js_response_clone(ant_t *js, ant_value_t *args, int nargs) {
1026 ant_value_t this = js_getthis(js);
1027 response_data_t *d = get_data(this);
1028 response_data_t *nd = NULL;
1029 ant_value_t src_headers = 0;
1030 ant_value_t new_headers = 0;
1031 ant_value_t obj = 0;
1032 ant_value_t src_stream = 0;
1033
1034 if (!d) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid Response object");
1035 if (d->body_used) {
1036 return js_mkerr_typed(js, JS_ERR_TYPE, "Cannot clone a Response whose body is unusable");
1037 }
1038
1039 nd = data_dup(d);
1040 if (!nd) return js_mkerr(js, "out of memory");
1041
1042 src_headers = js_get_slot(this, SLOT_RESPONSE_HEADERS);
1043 new_headers = headers_create_empty(js);
1044 if (is_err(new_headers)) {
1045 data_free(nd);
1046 return new_headers;
1047 }
1048
1049 headers_copy_from(js, new_headers, src_headers);
1050 headers_set_guard(new_headers, headers_get_guard(src_headers));
1051 headers_apply_guard(new_headers);
1052
1053 obj = js_mkobj(js);
1054 js_set_proto_init(obj, g_response_proto);
1055 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_RESPONSE));
1056 js_set_slot(obj, SLOT_DATA, ANT_PTR(nd));
1057 js_set_slot_wb(js, obj, SLOT_RESPONSE_HEADERS, new_headers);
1058 js_set_slot_wb(js, obj, SLOT_RESPONSE_BODY_STREAM, js_mkundef());
1059
1060 src_stream = js_get_slot(this, SLOT_RESPONSE_BODY_STREAM);
1061 if (!rs_is_stream(src_stream)) return obj;
1062
1063 ant_value_t branches = readable_stream_tee(js, src_stream);
1064 if (!is_err(branches) && vtype(branches) == T_ARR) {
1065 ant_value_t b1 = js_arr_get(js, branches, 0);
1066 ant_value_t b2 = js_arr_get(js, branches, 1);
1067 js_set_slot_wb(js, this, SLOT_RESPONSE_BODY_STREAM, b1);
1068 js_set_slot_wb(js, obj, SLOT_RESPONSE_BODY_STREAM, b2);
1069 }
1070
1071 return obj;
1072}
1073
1074ant_value_t response_create(
1075 ant_t *js,
1076 const char *type,
1077 int status,
1078 const char *status_text,
1079 ant_value_t headers_obj,
1080 const uint8_t *body,
1081 size_t body_len,
1082 const char *body_type,
1083 headers_guard_t guard
1084) {
1085 ant_value_t obj = response_new(guard);
1086 ant_value_t headers = 0;
1087 response_data_t *resp = NULL;
1088
1089 if (is_err(obj)) return obj;
1090 resp = get_data(obj);
1091
1092 free(resp->type);
1093 resp->type = strdup(type ? type : "default");
1094 if (!resp->type) {
1095 data_free(resp);
1096 return js_mkerr(js, "out of memory");
1097 }
1098
1099 resp->status = status;
1100 free(resp->status_text);
1101 resp->status_text = strdup(status_text ? status_text : "");
1102 if (!resp->status_text) {
1103 data_free(resp);
1104 return js_mkerr(js, "out of memory");
1105 }
1106
1107 if (body_len > 0) {
1108 resp->body_data = malloc(body_len);
1109 if (!resp->body_data) {
1110 data_free(resp);
1111 return js_mkerr(js, "out of memory");
1112 }
1113 memcpy(resp->body_data, body, body_len);
1114 }
1115
1116 resp->body_size = body_len;
1117 resp->body_type = body_type ? strdup(body_type) : NULL;
1118 resp->has_body = body || body_len > 0;
1119 resp->body_is_stream = false;
1120
1121 headers = is_object_type(headers_obj) ? headers_obj : headers_create_empty(js);
1122 if (is_err(headers)) {
1123 data_free(resp);
1124 return headers;
1125 }
1126
1127 headers_set_guard(headers, guard);
1128 headers_apply_guard(headers);
1129
1130 ant_value_t current_type = headers_get_value(js, headers, "content-type");
1131 if (body_type && !is_err(current_type) && vtype(current_type) == T_NULL) {
1132 headers_append_if_missing(headers, "content-type", body_type);
1133 } else if (!is_err(current_type)) response_maybe_normalize_text_content_type(
1134 js, headers, current_type, body_type
1135 );
1136 js_set_slot_wb(js, obj, SLOT_RESPONSE_HEADERS, headers);
1137 return obj;
1138}
1139
1140ant_value_t response_create_fetched(
1141 ant_t *js,
1142 int status,
1143 const char *status_text,
1144 const char *url,
1145 int url_list_size,
1146 ant_value_t headers_obj,
1147 const uint8_t *body,
1148 size_t body_len,
1149 ant_value_t body_stream,
1150 const char *body_type
1151) {
1152 ant_value_t obj = response_new(HEADERS_GUARD_IMMUTABLE);
1153 ant_value_t headers = 0;
1154 response_data_t *resp = NULL;
1155 url_state_t parsed = {0};
1156
1157 if (is_err(obj)) return obj;
1158 resp = get_data(obj);
1159
1160 resp->status = status;
1161 free(resp->status_text);
1162 resp->status_text = strdup(status_text ? status_text : "");
1163 if (!resp->status_text) {
1164 data_free(resp);
1165 return js_mkerr(js, "out of memory");
1166 }
1167
1168 if (url && parse_url_to_state(url, NULL, &parsed) == 0) {
1169 url_state_clear(&resp->url);
1170 resp->url = parsed;
1171 resp->has_url = true;
1172 resp->url_list_size = url_list_size > 0 ? url_list_size : 1;
1173 } else url_state_clear(&parsed);
1174
1175 if (rs_is_stream(body_stream)) {
1176 resp->body_is_stream = true;
1177 resp->has_body = true;
1178 js_set_slot_wb(js, obj, SLOT_RESPONSE_BODY_STREAM, body_stream);
1179 } else {
1180 if (body_len > 0) {
1181 resp->body_data = malloc(body_len);
1182 if (!resp->body_data) {
1183 data_free(resp);
1184 return js_mkerr(js, "out of memory");
1185 }
1186 memcpy(resp->body_data, body, body_len);
1187 }
1188 resp->body_size = body_len;
1189 resp->body_is_stream = false;
1190 resp->has_body = body || body_len > 0;
1191 }
1192
1193 resp->body_type = body_type ? strdup(body_type) : NULL;
1194 if (body_type && !resp->body_type) {
1195 data_free(resp);
1196 return js_mkerr(js, "out of memory");
1197 }
1198
1199 headers = is_object_type(headers_obj) ? headers_obj : headers_create_empty(js);
1200 if (is_err(headers)) {
1201 data_free(resp);
1202 return headers;
1203 }
1204
1205 headers_set_guard(headers, HEADERS_GUARD_IMMUTABLE);
1206 headers_apply_guard(headers);
1207 js_set_slot_wb(js, obj, SLOT_RESPONSE_HEADERS, headers);
1208
1209 return obj;
1210}
1211
1212void init_response_module(void) {
1213 ant_t *js = rt->js;
1214 ant_value_t g = js_glob(js);
1215 ant_value_t ctor = 0;
1216
1217 g_response_proto = js_mkobj(js);
1218
1219 js_set(js, g_response_proto, "text", js_mkfun(js_res_text));
1220 js_set(js, g_response_proto, "json", js_mkfun(js_res_json));
1221 js_set(js, g_response_proto, "arrayBuffer", js_mkfun(js_res_array_buffer));
1222 js_set(js, g_response_proto, "blob", js_mkfun(js_res_blob));
1223 js_set(js, g_response_proto, "formData", js_mkfun(js_res_form_data));
1224 js_set(js, g_response_proto, "bytes", js_mkfun(js_res_bytes));
1225 js_set(js, g_response_proto, "clone", js_mkfun(js_response_clone));
1226
1227#define GETTER(prop, fn) \
1228 js_set_getter_desc(js, g_response_proto, prop, sizeof(prop) - 1, js_mkfun(js_res_get_##fn), JS_DESC_C)
1229 GETTER("type", type);
1230 GETTER("url", url);
1231 GETTER("redirected", redirected);
1232 GETTER("status", status);
1233 GETTER("ok", ok);
1234 GETTER("statusText", status_text);
1235 GETTER("headers", headers);
1236 GETTER("body", body);
1237 GETTER("bodyUsed", body_used);
1238#undef GETTER
1239
1240 js_set_sym(js, g_response_proto, get_inspect_sym(), js_mkfun(response_inspect));
1241 js_set_sym(js, g_response_proto, get_toStringTag_sym(), js_mkstr(js, "Response", 8));
1242 ctor = js_make_ctor(js, js_response_ctor, g_response_proto, "Response", 8);
1243 js_set(js, ctor, "error", js_mkfun(js_response_error));
1244 js_set(js, ctor, "redirect", js_mkfun(js_response_redirect));
1245 js_set(js, ctor, "json", js_mkfun(js_response_json_static));
1246 js_set(js, g, "Response", ctor);
1247 js_set_descriptor(js, g, "Response", 8, JS_DESC_W | JS_DESC_C);
1248}