Page MenuHomePhorge

multipart.c
No OneTemporary

Size
13 KB
Referenced Files
None
Subscribers
None

multipart.c

#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);
ant_value_t data_slot = js_get_slot(fd, SLOT_DATA);
char boundary[49];
mp_buf_t b = {NULL, 0, 0};
fd_data_t *d = NULL;
if (vtype(data_slot) != T_NUM) return NULL;
d = (fd_data_t *)(uintptr_t)(size_t)js_getnum(data_slot);
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;
}

File Metadata

Mime Type
text/x-c
Expires
Sat, Apr 4, 12:49 AM (1 d, 23 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
520903
Default Alt Text
multipart.c (13 KB)

Event Timeline