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 581 lines 14 kB view raw
1#include <compat.h> // IWYU pragma: keep 2 3#include <stdbool.h> 4#include <stdint.h> 5#include <stdio.h> 6#include <stdlib.h> 7#include <string.h> 8#include <strings.h> 9 10#include "ant.h" 11#include "errors.h" 12#include "internal.h" 13 14#include "modules/blob.h" 15#include "modules/formdata.h" 16#include "modules/multipart.h" 17#include "modules/url.h" 18 19static ant_value_t multipart_invalid(ant_t *js) { 20 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData"); 21} 22 23static bool ct_is_type(const char *ct, const char *type) { 24 size_t type_len = 0; 25 26 if (!ct || !type) return false; 27 28 while (*ct == ' ' || *ct == '\t') ct++; 29 type_len = strlen(type); 30 if (strncasecmp(ct, type, type_len) != 0) return false; 31 32 ct += type_len; 33 return *ct == '\0' || *ct == ';' || *ct == ' ' || *ct == '\t'; 34} 35 36static const char *ct_find_next_param(const char *p) { 37scan: 38 if (*p == '\0') return NULL; 39 if (*p == ';') return p; 40 if (*p != '"') { 41 p++; 42 goto scan; 43 } 44 45quoted: 46 p++; 47 if (*p == '\0') return NULL; 48 if (*p == '\\') goto escaped; 49 if (*p == '"') { 50 p++; 51 goto scan; 52 } 53 goto quoted; 54 55escaped: 56 p++; 57 if (*p == '\0') return NULL; 58 p++; 59 goto quoted; 60} 61 62static char *ct_get_param_dup(const char *ct, const char *name) { 63 const char *p = NULL; 64 const char *param_name = NULL; 65 const char *param_name_end = NULL; 66 67 char *buf = NULL; 68 size_t name_len = 0; 69 size_t len = 0; 70 size_t cap = 0; 71 char ch = '\0'; 72 73 if (!ct || !name) return NULL; 74 75 name_len = strlen(name); 76 if (name_len == 0) return NULL; 77 78 p = strchr(ct, ';'); 79 80next_param: 81 if (!p) return NULL; 82 p++; 83 84 while (*p == ' ' || *p == '\t') p++; 85 if (*p == '\0') return NULL; 86 87 param_name = p; 88 while (*p && *p != '=' && *p != ';') p++; 89 param_name_end = p; 90 91 while ( 92 param_name_end > param_name && 93 (param_name_end[-1] == ' ' || param_name_end[-1] == '\t') 94 ) param_name_end--; 95 96 if (*p != '=') goto skip_param; 97 if ((size_t)(param_name_end - param_name) != name_len) goto skip_param; 98 if (strncasecmp(param_name, name, name_len) != 0) goto skip_param; 99 100 p++; 101 while (*p == ' ' || *p == '\t') p++; 102 if (*p == '\0') return strdup(""); 103 104 if (*p == '"') { 105 p++; 106 goto quoted; 107 } 108 109unquoted: 110 if (*p == '\0' || *p == ';' || *p == ' ' || *p == '\t') { 111 buf = malloc(len + 1); 112 if (!buf) return NULL; 113 if (len != 0) memcpy(buf, p - len, len); 114 buf[len] = '\0'; 115 return buf; 116 } 117 p++; 118 len++; 119 goto unquoted; 120 121quoted: 122 if (*p == '\0') goto fail; 123 if (*p == '"') goto done; 124 if (*p == '\\') goto quoted_escape; 125 ch = *p++; 126 goto append; 127 128quoted_escape: 129 p++; 130 if (*p == '\0') goto fail; 131 ch = *p++; 132 goto append; 133 134append: 135 if (len == cap) { 136 size_t next_cap = cap ? cap * 2 : 32; 137 char *next = realloc(buf, next_cap); 138 if (!next) goto fail; 139 buf = next; 140 cap = next_cap; 141 } 142 buf[len++] = ch; 143 goto quoted; 144 145done: 146 p++; 147 if (len == cap) { 148 size_t next_cap = cap ? cap + 1 : 1; 149 char *next = realloc(buf, next_cap); 150 if (!next) goto fail; 151 buf = next; 152 cap = next_cap; 153 } 154 buf[len] = '\0'; 155 return buf; 156 157skip_param: 158 p = ct_find_next_param(p); 159 goto next_param; 160 161fail: 162 free(buf); 163 return NULL; 164} 165 166static ant_value_t parse_formdata_urlencoded(ant_t *js, const uint8_t *data, size_t size) { 167 ant_value_t fd = 0; 168 char *body = NULL; 169 char *cursor = NULL; 170 171 fd = formdata_create_empty(js); 172 if (is_err(fd)) return fd; 173 if (!data || size == 0) return fd; 174 175 body = strndup((const char *)data, size); 176 if (!body) return js_mkerr(js, "out of memory"); 177 178 cursor = body; 179 while (cursor) { 180 ant_value_t r = 0; 181 char *amp = strchr(cursor, '&'); 182 char *eq = NULL; 183 char *raw_name = NULL; 184 char *raw_value = NULL; 185 char *name = NULL; 186 char *value = NULL; 187 188 if (amp) *amp = '\0'; 189 190 eq = strchr(cursor, '='); 191 raw_name = cursor; 192 raw_value = eq ? (eq + 1) : ""; 193 if (eq) *eq = '\0'; 194 195 name = form_urldecode(raw_name); 196 value = form_urldecode(raw_value); 197 if (!name || !value) { 198 free(name); 199 free(value); 200 free(body); 201 return js_mkerr(js, "out of memory"); 202 } 203 204 r = formdata_append_string( 205 js, fd, 206 js_mkstr(js, name, strlen(name)), 207 js_mkstr(js, value, strlen(value)) 208 ); 209 210 free(name); 211 free(value); 212 213 if (is_err(r)) { 214 free(body); 215 return r; 216 } 217 218 cursor = amp ? (amp + 1) : NULL; 219 } 220 221 free(body); 222 return fd; 223} 224 225static const uint8_t *find_bytes( 226 const uint8_t *haystack, size_t haystack_len, 227 const uint8_t *needle, size_t needle_len 228) { 229 size_t i = 0; 230 231 if (needle_len == 0) return haystack; 232 if (haystack_len < needle_len) return NULL; 233 234 for (i = 0; i + needle_len <= haystack_len; i++) { 235 if (memcmp(haystack + i, needle, needle_len) == 0) return haystack + i; 236 } 237 238 return NULL; 239} 240 241typedef struct { 242 char *name; 243 char *filename; 244 char *content_type; 245} multipart_part_info_t; 246 247static void multipart_part_info_clear(multipart_part_info_t *info) { 248 if (!info) return; 249 free(info->name); 250 free(info->filename); 251 free(info->content_type); 252 info->name = NULL; 253 info->filename = NULL; 254 info->content_type = NULL; 255} 256 257static bool multipart_parse_headers( 258 ant_t *js, char *headers, multipart_part_info_t *info, ant_value_t *err_out 259) { 260 char *saveptr = NULL; 261 262 for ( 263 char *line = strtok_r(headers, "\r\n", &saveptr); 264 line; line = strtok_r(NULL, "\r\n", &saveptr) 265 ) { 266 char *colon = strchr(line, ':'); 267 char *value = NULL; 268 269 if (!colon) continue; 270 *colon = '\0'; 271 272 value = colon + 1; 273 while (*value == ' ' || *value == '\t') value++; 274 275 if (strcasecmp(line, "Content-Disposition") == 0) { 276 free(info->name); 277 free(info->filename); 278 279 info->name = ct_get_param_dup(value, "name"); 280 info->filename = ct_get_param_dup(value, "filename"); 281 282 continue; 283 } 284 285 if (strcasecmp(line, "Content-Type") == 0) { 286 free(info->content_type); 287 info->content_type = strdup(value); 288 if (!info->content_type) { 289 *err_out = js_mkerr(js, "out of memory"); 290 return false; 291 }} 292 } 293 294 if (info->name) return true; 295 *err_out = multipart_invalid(js); 296 297 return false; 298} 299 300static const uint8_t *multipart_find_part_end( 301 const uint8_t *p, const uint8_t *end, const char *delim, size_t delim_len 302) { 303 char *marker = malloc(delim_len + 3); 304 const uint8_t *part_end = NULL; 305 if (!marker) return NULL; 306 307 snprintf(marker, delim_len + 3, "\r\n%s", delim); 308 part_end = find_bytes( 309 p, (size_t)(end - p), 310 (const uint8_t *)marker, delim_len + 2 311 ); 312 313 free(marker); 314 return part_end; 315} 316 317static ant_value_t multipart_append_part( 318 ant_t *js, ant_value_t fd, const multipart_part_info_t *info, 319 const uint8_t *data, size_t size 320) { 321 if (info->filename) { 322 ant_value_t blob = blob_create( 323 js, data, size, info->content_type 324 ? info->content_type : "" 325 ); 326 327 if (is_err(blob)) return blob; 328 329 return formdata_append_file( 330 js, fd, js_mkstr(js, info->name, strlen(info->name)), 331 blob, js_mkstr(js, info->filename, strlen(info->filename)) 332 ); 333 } 334 335 return formdata_append_string( 336 js, fd, 337 js_mkstr(js, info->name, strlen(info->name)), 338 js_mkstr(js, data, size) 339 ); 340} 341 342static ant_value_t parse_formdata_multipart( 343 ant_t *js, const uint8_t *data, size_t size, const char *body_type 344) { 345 ant_value_t fd = 0; 346 char *boundary = NULL; 347 char *delim = NULL; 348 349 const uint8_t *p = data; 350 const uint8_t *end = data + size; 351 size_t delim_len = 0; 352 353 if (!data || size == 0) return multipart_invalid(js); 354 boundary = ct_get_param_dup(body_type, "boundary"); 355 if (!boundary || boundary[0] == '\0') goto invalid; 356 357 fd = formdata_create_empty(js); 358 if (is_err(fd)) goto done; 359 360 delim_len = strlen(boundary) + 2; 361 delim = malloc(delim_len + 1); 362 if (!delim) { 363 fd = js_mkerr(js, "out of memory"); 364 goto done; 365 } 366 snprintf(delim, delim_len + 1, "--%s", boundary); 367 368 if ((size_t)(end - p) < delim_len || memcmp(p, delim, delim_len) != 0) goto invalid; 369 p += delim_len; 370 371next_part: 372 if (p > end) goto done; 373 if ((size_t)(end - p) >= 2 && memcmp(p, "--", 2) == 0) goto done; 374 if ((size_t)(end - p) < 2 || memcmp(p, "\r\n", 2) != 0) goto invalid; 375 p += 2; 376 377 { 378 ant_value_t r = 0; 379 ant_value_t hdr_err = js_mkundef(); 380 const uint8_t *hdr_end = find_bytes( 381 p, (size_t)(end - p), (const uint8_t *)"\r\n\r\n", 4 382 ); 383 384 char *headers = NULL; 385 multipart_part_info_t info = {0}; 386 const uint8_t *part_end = NULL; 387 388 if (!hdr_end) goto invalid; 389 390 headers = strndup((const char *)p, (size_t)(hdr_end - p)); 391 if (!headers) { 392 fd = js_mkerr(js, "out of memory"); 393 goto done; 394 } 395 p = hdr_end + 4; 396 397 if (!multipart_parse_headers(js, headers, &info, &hdr_err)) { 398 free(headers); 399 multipart_part_info_clear(&info); 400 if (is_err(hdr_err)) { fd = hdr_err; goto done; } 401 goto invalid; 402 } 403 free(headers); 404 405 part_end = multipart_find_part_end(p, end, delim, delim_len); 406 if (!part_end) { 407 multipart_part_info_clear(&info); 408 goto invalid; 409 } 410 411 r = multipart_append_part(js, fd, &info, p, (size_t)(part_end - p)); 412 multipart_part_info_clear(&info); 413 if (is_err(r)) { fd = r; goto done; } 414 415 p = part_end + 2 + delim_len; 416 if ((size_t)(end - p) >= 2 && memcmp(p, "--", 2) == 0) goto done; 417 } 418 419 goto next_part; 420 421invalid: 422 fd = multipart_invalid(js); 423 424done: 425 free(boundary); 426 free(delim); 427 return fd; 428} 429 430ant_value_t formdata_parse_body( 431 ant_t *js, const uint8_t *data, size_t size, 432 const char *body_type, bool has_body 433) { 434 if (body_type && ct_is_type(body_type, "application/x-www-form-urlencoded")) { 435 return parse_formdata_urlencoded(js, data, size); 436 } 437 438 if (body_type && ct_is_type(body_type, "multipart/form-data")) { 439 if (!has_body || !data || size == 0) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData"); 440 return parse_formdata_multipart(js, data, size, body_type); 441 } 442 443 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData"); 444} 445 446typedef struct { 447 uint8_t *buf; 448 size_t size; 449 size_t cap; 450} mp_buf_t; 451 452static bool mp_grow(mp_buf_t *b, size_t need) { 453 size_t nc = 0; 454 uint8_t *nb = NULL; 455 456 if (b->size + need <= b->cap) return true; 457 458 nc = b->cap ? b->cap * 2 : 4096; 459 while (nc < b->size + need) nc *= 2; 460 461 nb = realloc(b->buf, nc); 462 if (!nb) return false; 463 b->buf = nb; 464 b->cap = nc; 465 return true; 466} 467 468static bool mp_append(mp_buf_t *b, const void *data, size_t len) { 469 if (!mp_grow(b, len)) return false; 470 memcpy(b->buf + b->size, data, len); 471 b->size += len; 472 return true; 473} 474 475static bool mp_append_str(mp_buf_t *b, const char *s) { 476 return mp_append(b, s, strlen(s)); 477} 478 479static bool mp_append_quoted(mp_buf_t *b, const char *s) { 480 const char *p = s ? s : ""; 481 if (!mp_append_str(b, "\"")) return false; 482 483 while (*p) { 484 if (*p == '"' || *p == '\\') if (!mp_append(b, "\\", 1)) return false; 485 if (!mp_append(b, p, 1)) return false; 486 p++; 487 } 488 489 return mp_append_str(b, "\""); 490} 491 492static bool mp_append_boundary(mp_buf_t *b, const char *boundary, bool closing) { 493 if (!mp_append_str(b, "--")) return false; 494 if (!mp_append_str(b, boundary)) return false; 495 return mp_append_str(b, closing ? "--\r\n" : "\r\n"); 496} 497 498static bool mp_append_text_part(mp_buf_t *b, const fd_entry_t *e) { 499 const char *val = e->str_value ? e->str_value : ""; 500 501 if (!mp_append_str(b, "Content-Disposition: form-data; name=")) return false; 502 if (!mp_append_quoted(b, e->name ? e->name : "")) return false; 503 if (!mp_append_str(b, "\r\n\r\n")) return false; 504 505 return mp_append_str(b, val); 506} 507 508static bool mp_append_file_part(ant_t *js, mp_buf_t *b, ant_value_t values_arr, const fd_entry_t *e) { 509 ant_value_t file_val = js_arr_get(js, values_arr, (ant_offset_t)e->val_idx); 510 blob_data_t *bd = blob_get_data(file_val); 511 512 const char *filename = (bd && bd->name) ? bd->name : "blob"; 513 const char *mime = (bd && bd->type && bd->type[0]) 514 ? bd->type 515 : "application/octet-stream"; 516 517 if (!mp_append_str(b, "Content-Disposition: form-data; name=")) return false; 518 if (!mp_append_quoted(b, e->name ? e->name : "")) return false; 519 if (!mp_append_str(b, "; filename=")) return false; 520 if (!mp_append_quoted(b, filename)) return false; 521 if (!mp_append_str(b, "\r\nContent-Type: ")) return false; 522 if (!mp_append_str(b, mime)) return false; 523 if (!mp_append_str(b, "\r\n\r\n")) return false; 524 if (!bd || !bd->data || bd->size == 0) return true; 525 526 return mp_append(b, bd->data, bd->size); 527} 528 529uint8_t *formdata_serialize_multipart( 530 ant_t *js, ant_value_t fd, size_t *out_size, char **out_boundary 531) { 532 ant_value_t values_arr = js_get_slot(fd, SLOT_ENTRIES); 533 ant_value_t data_slot = js_get_slot(fd, SLOT_DATA); 534 char boundary[49]; 535 536 mp_buf_t b = {NULL, 0, 0}; 537 fd_data_t *d = NULL; 538 539 if (vtype(data_slot) != T_NUM) return NULL; 540 d = (fd_data_t *)(uintptr_t)(size_t)js_getnum(data_slot); 541 if (!d) return NULL; 542 543 snprintf( 544 boundary, sizeof(boundary), 545 "----AntFormBoundary%08x%08x", (unsigned)rand(), (unsigned)rand() 546 ); 547 548 if (d->count == 0) { 549 uint8_t *empty = malloc(1); 550 if (!empty) return NULL; 551 552 *out_size = 0; 553 *out_boundary = strdup(boundary); 554 555 if (!*out_boundary) { 556 free(empty); 557 return NULL; 558 } 559 560 return empty; 561 } 562 563 for (fd_entry_t *e = d->head; e; e = e->next) { 564 if (!mp_append_boundary(&b, boundary, false)) goto oom; 565 if (!e->is_file && !mp_append_text_part(&b, e)) goto oom; 566 if (e->is_file && !mp_append_file_part(js, &b, values_arr, e)) goto oom; 567 if (!mp_append_str(&b, "\r\n")) goto oom; 568 } 569 570 if (!mp_append_boundary(&b, boundary, true)) goto oom; 571 572 *out_size = b.size; 573 *out_boundary = strdup(boundary); 574 575 if (!*out_boundary) goto oom; 576 return b.buf; 577 578oom: 579 free(b.buf); 580 return NULL; 581}