MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <stdbool.h>
4#include <stdint.h>
5#include <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}