Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578#include <compat.h> // IWYU pragma: keep
#include <stdbool.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <strings.h>
#include "ant.h"#include "errors.h"#include "internal.h"
#include "modules/blob.h"#include "modules/formdata.h"#include "modules/multipart.h"#include "modules/url.h"
static ant_value_t multipart_invalid(ant_t *js) { return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData");}
static bool ct_is_type(const char *ct, const char *type) { size_t type_len = 0;
if (!ct || !type) return false;
while (*ct == ' ' || *ct == '\t') ct++; type_len = strlen(type); if (strncasecmp(ct, type, type_len) != 0) return false;
ct += type_len; return *ct == '\0' || *ct == ';' || *ct == ' ' || *ct == '\t';}
static const char *ct_find_next_param(const char *p) {scan: if (*p == '\0') return NULL; if (*p == ';') return p; if (*p != '"') { p++; goto scan; }
quoted: p++; if (*p == '\0') return NULL; if (*p == '\\') goto escaped; if (*p == '"') { p++; goto scan; } goto quoted;
escaped: p++; if (*p == '\0') return NULL; p++; goto quoted;}
static char *ct_get_param_dup(const char *ct, const char *name) { const char *p = NULL; const char *param_name = NULL; const char *param_name_end = NULL; char *buf = NULL; size_t name_len = 0; size_t len = 0; size_t cap = 0; char ch = '\0';
if (!ct || !name) return NULL;
name_len = strlen(name); if (name_len == 0) return NULL;
p = strchr(ct, ';');
next_param: if (!p) return NULL; p++;
while (*p == ' ' || *p == '\t') p++; if (*p == '\0') return NULL;
param_name = p; while (*p && *p != '=' && *p != ';') p++; param_name_end = p;
while ( param_name_end > param_name && (param_name_end[-1] == ' ' || param_name_end[-1] == '\t') ) param_name_end--;
if (*p != '=') goto skip_param; if ((size_t)(param_name_end - param_name) != name_len) goto skip_param; if (strncasecmp(param_name, name, name_len) != 0) goto skip_param;
p++; while (*p == ' ' || *p == '\t') p++; if (*p == '\0') return strdup("");
if (*p == '"') { p++; goto quoted; }
unquoted: if (*p == '\0' || *p == ';' || *p == ' ' || *p == '\t') { buf = malloc(len + 1); if (!buf) return NULL; if (len != 0) memcpy(buf, p - len, len); buf[len] = '\0'; return buf; } p++; len++; goto unquoted;
quoted: if (*p == '\0') goto fail; if (*p == '"') goto done; if (*p == '\\') goto quoted_escape; ch = *p++; goto append;
quoted_escape: p++; if (*p == '\0') goto fail; ch = *p++; goto append;
append: if (len == cap) { size_t next_cap = cap ? cap * 2 : 32; char *next = realloc(buf, next_cap); if (!next) goto fail; buf = next; cap = next_cap; } buf[len++] = ch; goto quoted;
done: p++; if (len == cap) { size_t next_cap = cap ? cap + 1 : 1; char *next = realloc(buf, next_cap); if (!next) goto fail; buf = next; cap = next_cap; } buf[len] = '\0'; return buf;
skip_param: p = ct_find_next_param(p); goto next_param;
fail: free(buf); return NULL;}
static ant_value_t parse_formdata_urlencoded(ant_t *js, const uint8_t *data, size_t size) { ant_value_t fd = 0; char *body = NULL; char *cursor = NULL;
fd = formdata_create_empty(js); if (is_err(fd)) return fd; if (!data || size == 0) return fd;
body = strndup((const char *)data, size); if (!body) return js_mkerr(js, "out of memory");
cursor = body; while (cursor) { ant_value_t r = 0; char *amp = strchr(cursor, '&'); char *eq = NULL; char *raw_name = NULL; char *raw_value = NULL; char *name = NULL; char *value = NULL;
if (amp) *amp = '\0';
eq = strchr(cursor, '='); raw_name = cursor; raw_value = eq ? (eq + 1) : ""; if (eq) *eq = '\0';
name = form_urldecode(raw_name); value = form_urldecode(raw_value); if (!name || !value) { free(name); free(value); free(body); return js_mkerr(js, "out of memory"); }
r = formdata_append_string( js, fd, js_mkstr(js, name, strlen(name)), js_mkstr(js, value, strlen(value)) ); free(name); free(value); if (is_err(r)) { free(body); return r; } cursor = amp ? (amp + 1) : NULL; }
free(body); return fd;}
static const uint8_t *find_bytes( const uint8_t *haystack, size_t haystack_len, const uint8_t *needle, size_t needle_len) { size_t i = 0;
if (needle_len == 0) return haystack; if (haystack_len < needle_len) return NULL;
for (i = 0; i + needle_len <= haystack_len; i++) { if (memcmp(haystack + i, needle, needle_len) == 0) return haystack + i; } return NULL;}
typedef struct { char *name; char *filename; char *content_type;} multipart_part_info_t;
static void multipart_part_info_clear(multipart_part_info_t *info) { if (!info) return; free(info->name); free(info->filename); free(info->content_type); info->name = NULL; info->filename = NULL; info->content_type = NULL;}
static bool multipart_parse_headers( ant_t *js, char *headers, multipart_part_info_t *info, ant_value_t *err_out) { char *saveptr = NULL;
for ( char *line = strtok_r(headers, "\r\n", &saveptr); line; line = strtok_r(NULL, "\r\n", &saveptr) ) { char *colon = strchr(line, ':'); char *value = NULL; if (!colon) continue; *colon = '\0'; value = colon + 1; while (*value == ' ' || *value == '\t') value++; if (strcasecmp(line, "Content-Disposition") == 0) { free(info->name); free(info->filename); info->name = ct_get_param_dup(value, "name"); info->filename = ct_get_param_dup(value, "filename"); continue; } if (strcasecmp(line, "Content-Type") == 0) { free(info->content_type); info->content_type = strdup(value); if (!info->content_type) { *err_out = js_mkerr(js, "out of memory"); return false; }} }
if (info->name) return true; *err_out = multipart_invalid(js); return false;}
static const uint8_t *multipart_find_part_end( const uint8_t *p, const uint8_t *end, const char *delim, size_t delim_len) { char *marker = malloc(delim_len + 3); const uint8_t *part_end = NULL; if (!marker) return NULL;
snprintf(marker, delim_len + 3, "\r\n%s", delim); part_end = find_bytes( p, (size_t)(end - p), (const uint8_t *)marker, delim_len + 2 ); free(marker); return part_end;}
static ant_value_t multipart_append_part( ant_t *js, ant_value_t fd, const multipart_part_info_t *info, const uint8_t *data, size_t size) { if (info->filename) { ant_value_t blob = blob_create( js, data, size, info->content_type ? info->content_type : "" ); if (is_err(blob)) return blob; return formdata_append_file( js, fd, js_mkstr(js, info->name, strlen(info->name)), blob, js_mkstr(js, info->filename, strlen(info->filename)) ); }
return formdata_append_string( js, fd, js_mkstr(js, info->name, strlen(info->name)), js_mkstr(js, data, size) );}
static ant_value_t parse_formdata_multipart( ant_t *js, const uint8_t *data, size_t size, const char *body_type) { ant_value_t fd = 0; char *boundary = NULL; char *delim = NULL; const uint8_t *p = data; const uint8_t *end = data + size; size_t delim_len = 0;
if (!data || size == 0) return multipart_invalid(js); boundary = ct_get_param_dup(body_type, "boundary"); if (!boundary || boundary[0] == '\0') goto invalid;
fd = formdata_create_empty(js); if (is_err(fd)) goto done;
delim_len = strlen(boundary) + 2; delim = malloc(delim_len + 1); if (!delim) { fd = js_mkerr(js, "out of memory"); goto done; } snprintf(delim, delim_len + 1, "--%s", boundary);
if ((size_t)(end - p) < delim_len || memcmp(p, delim, delim_len) != 0) goto invalid; p += delim_len;
next_part: if (p > end) goto done; if ((size_t)(end - p) >= 2 && memcmp(p, "--", 2) == 0) goto done; if ((size_t)(end - p) < 2 || memcmp(p, "\r\n", 2) != 0) goto invalid; p += 2;
{ ant_value_t r = 0; ant_value_t hdr_err = js_mkundef(); const uint8_t *hdr_end = find_bytes( p, (size_t)(end - p), (const uint8_t *)"\r\n\r\n", 4 ); char *headers = NULL; multipart_part_info_t info = {0}; const uint8_t *part_end = NULL;
if (!hdr_end) goto invalid;
headers = strndup((const char *)p, (size_t)(hdr_end - p)); if (!headers) { fd = js_mkerr(js, "out of memory"); goto done; } p = hdr_end + 4;
if (!multipart_parse_headers(js, headers, &info, &hdr_err)) { free(headers); multipart_part_info_clear(&info); if (is_err(hdr_err)) { fd = hdr_err; goto done; } goto invalid; } free(headers);
part_end = multipart_find_part_end(p, end, delim, delim_len); if (!part_end) { multipart_part_info_clear(&info); goto invalid; }
r = multipart_append_part(js, fd, &info, p, (size_t)(part_end - p)); multipart_part_info_clear(&info); if (is_err(r)) { fd = r; goto done; }
p = part_end + 2 + delim_len; if ((size_t)(end - p) >= 2 && memcmp(p, "--", 2) == 0) goto done; }
goto next_part;
invalid: fd = multipart_invalid(js);
done: free(boundary); free(delim); return fd;}
ant_value_t formdata_parse_body( ant_t *js, const uint8_t *data, size_t size, const char *body_type, bool has_body) { if (body_type && ct_is_type(body_type, "application/x-www-form-urlencoded")) { return parse_formdata_urlencoded(js, data, size); }
if (body_type && ct_is_type(body_type, "multipart/form-data")) { if (!has_body || !data || size == 0) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData"); return parse_formdata_multipart(js, data, size, body_type); }
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to parse body as FormData");}
typedef struct { uint8_t *buf; size_t size; size_t cap;} mp_buf_t;
static bool mp_grow(mp_buf_t *b, size_t need) { size_t nc = 0; uint8_t *nb = NULL;
if (b->size + need <= b->cap) return true;
nc = b->cap ? b->cap * 2 : 4096; while (nc < b->size + need) nc *= 2;
nb = realloc(b->buf, nc); if (!nb) return false; b->buf = nb; b->cap = nc; return true;}
static bool mp_append(mp_buf_t *b, const void *data, size_t len) { if (!mp_grow(b, len)) return false; memcpy(b->buf + b->size, data, len); b->size += len; return true;}
static bool mp_append_str(mp_buf_t *b, const char *s) { return mp_append(b, s, strlen(s));}
static bool mp_append_quoted(mp_buf_t *b, const char *s) { const char *p = s ? s : ""; if (!mp_append_str(b, "\"")) return false; while (*p) { if (*p == '"' || *p == '\\') if (!mp_append(b, "\\", 1)) return false; if (!mp_append(b, p, 1)) return false; p++; } return mp_append_str(b, "\"");}
static bool mp_append_boundary(mp_buf_t *b, const char *boundary, bool closing) { if (!mp_append_str(b, "--")) return false; if (!mp_append_str(b, boundary)) return false; return mp_append_str(b, closing ? "--\r\n" : "\r\n");}
static bool mp_append_text_part(mp_buf_t *b, const fd_entry_t *e) { const char *val = e->str_value ? e->str_value : "";
if (!mp_append_str(b, "Content-Disposition: form-data; name=")) return false; if (!mp_append_quoted(b, e->name ? e->name : "")) return false; if (!mp_append_str(b, "\r\n\r\n")) return false; return mp_append_str(b, val);}
static bool mp_append_file_part(ant_t *js, mp_buf_t *b, ant_value_t values_arr, const fd_entry_t *e) { ant_value_t file_val = js_arr_get(js, values_arr, (ant_offset_t)e->val_idx); blob_data_t *bd = blob_get_data(file_val); const char *filename = (bd && bd->name) ? bd->name : "blob"; const char *mime = (bd && bd->type && bd->type[0]) ? bd->type : "application/octet-stream"; if (!mp_append_str(b, "Content-Disposition: form-data; name=")) return false; if (!mp_append_quoted(b, e->name ? e->name : "")) return false; if (!mp_append_str(b, "; filename=")) return false; if (!mp_append_quoted(b, filename)) return false; if (!mp_append_str(b, "\r\nContent-Type: ")) return false; if (!mp_append_str(b, mime)) return false; if (!mp_append_str(b, "\r\n\r\n")) return false; if (!bd || !bd->data || bd->size == 0) return true; return mp_append(b, bd->data, bd->size);}
uint8_t *formdata_serialize_multipart( ant_t *js, ant_value_t fd, size_t *out_size, char **out_boundary) { ant_value_t values_arr = js_get_slot(fd, SLOT_ENTRIES); char boundary[49]; mp_buf_t b = {NULL, 0, 0}; fd_data_t *d = formdata_get_data(fd); if (!d) return NULL;
snprintf( boundary, sizeof(boundary), "----AntFormBoundary%08x%08x", (unsigned)rand(), (unsigned)rand() );
if (d->count == 0) { uint8_t *empty = malloc(1); if (!empty) return NULL; *out_size = 0; *out_boundary = strdup(boundary); if (!*out_boundary) { free(empty); return NULL; } return empty; }
for (fd_entry_t *e = d->head; e; e = e->next) { if (!mp_append_boundary(&b, boundary, false)) goto oom; if (!e->is_file && !mp_append_text_part(&b, e)) goto oom; if (e->is_file && !mp_append_file_part(js, &b, values_arr, e)) goto oom; if (!mp_append_str(&b, "\r\n")) goto oom; }
if (!mp_append_boundary(&b, boundary, true)) goto oom;
*out_size = b.size; *out_boundary = strdup(boundary); if (!*out_boundary) goto oom; return b.buf;
oom: free(b.buf); return NULL;}