MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at mir/inline-method 1248 lines 39 kB view raw
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}