cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
multipart.c
Go to the documentation of this file.
1
8#include <stdbool.h>
9#include <string.h>
10#include <strings.h>
11#include <stdio.h>
12#include <ctype.h>
13#include "multipart_parser.h"
14
15typedef struct {
16 char header_field[256];
17 size_t header_field_len;
18 char header_value[1024];
19 size_t header_value_len;
20 /* Set once a value was delivered for the current header, even an empty
21 * one; header_value_len alone cannot express "seen but empty". */
22 bool have_value;
23
24 char name[256];
25 char filename[256];
26 char content_type[256];
27
28 char *data;
29 size_t data_len;
30 size_t data_cap;
31
33} mp_parse_ctx;
34
35static void mp_parse_headers(mp_parse_ctx *ctx);
36
49static int mp_on_header_field(multipart_parser *p, const char *at, size_t len) {
50 mp_parse_ctx *ctx = (mp_parse_ctx *)multipart_parser_get_data(p);
51 if (ctx->have_value) {
52 /* Previous header value is complete, parse it before moving to next field. */
53 mp_parse_headers(ctx);
54 }
55 if (ctx->header_field_len + len < sizeof(ctx->header_field)) {
56 memcpy(ctx->header_field + ctx->header_field_len, at, len);
57 ctx->header_field_len += len;
58 }
59 return 0;
60}
61
73static int mp_on_header_value(multipart_parser *p, const char *at, size_t len) {
74 mp_parse_ctx *ctx = (mp_parse_ctx *)multipart_parser_get_data(p);
75 ctx->have_value = true;
76 if (ctx->header_value_len + len < sizeof(ctx->header_value)) {
77 memcpy(ctx->header_value + ctx->header_value_len, at, len);
78 ctx->header_value_len += len;
79 }
80 return 0;
81}
82
93static void mp_parse_headers(mp_parse_ctx *ctx) {
94 if (ctx->header_field_len >= sizeof(ctx->header_field))
95 ctx->header_field_len = sizeof(ctx->header_field) - 1;
96 ctx->header_field[ctx->header_field_len] = '\0';
97
98 if (ctx->header_value_len >= sizeof(ctx->header_value))
99 ctx->header_value_len = sizeof(ctx->header_value) - 1;
100 ctx->header_value[ctx->header_value_len] = '\0';
101
102 if (strcasecmp(ctx->header_field, "Content-Disposition") == 0) {
103 const char *name_tag = strstr(ctx->header_value, "name=\"");
104 if (name_tag) {
105 name_tag += 6;
106 const char *name_end = strchr(name_tag, '"');
107 if (name_end) {
108 size_t nlen = (size_t)(name_end - name_tag);
109 if (nlen < sizeof(ctx->name)) {
110 memcpy(ctx->name, name_tag, nlen);
111 ctx->name[nlen] = '\0';
112 }
113 }
114 }
115 const char *filename_tag = strstr(ctx->header_value, "filename=\"");
116 if (filename_tag) {
117 filename_tag += 10;
118 const char *filename_end = strchr(filename_tag, '"');
119 if (filename_end) {
120 size_t flen = (size_t)(filename_end - filename_tag);
121 if (flen < sizeof(ctx->filename)) {
122 memcpy(ctx->filename, filename_tag, flen);
123 ctx->filename[flen] = '\0';
124 }
125 }
126 }
127 } else if (strcasecmp(ctx->header_field, "Content-Type") == 0) {
128 size_t vlen = strlen(ctx->header_value);
129 if (vlen < sizeof(ctx->content_type)) strcpy(ctx->content_type, ctx->header_value);
130 }
131
132 ctx->header_field_len = 0;
133 ctx->header_value_len = 0;
134 ctx->have_value = false;
135}
136
146static int mp_on_headers_complete(multipart_parser *p) {
147 mp_parse_ctx *ctx = (mp_parse_ctx *)multipart_parser_get_data(p);
148 mp_parse_headers(ctx);
149 return 0;
150}
151
163static int mp_on_part_data_begin(multipart_parser *p) {
164 mp_parse_ctx *ctx = (mp_parse_ctx *)multipart_parser_get_data(p);
165 if (ctx->have_value) {
166 mp_parse_headers(ctx);
167 }
168 ctx->header_field_len = 0;
169 ctx->header_value_len = 0;
170 ctx->have_value = false;
171 ctx->name[0] = '\0';
172 ctx->filename[0] = '\0';
173 ctx->content_type[0] = '\0';
174 if (ctx->data) {
175 cwist_free(ctx->data);
176 ctx->data = NULL;
177 }
178 ctx->data_len = 0;
179 ctx->data_cap = 0;
180 return 0;
181}
182
194static int mp_on_part_data(multipart_parser *p, const char *at, size_t len) {
195 mp_parse_ctx *ctx = (mp_parse_ctx *)multipart_parser_get_data(p);
196 if (len == 0) return 0;
197 if (ctx->data_len + len > ctx->data_cap) {
198 size_t new_cap = (ctx->data_len + len) * 2;
199 if (new_cap < 256) new_cap = 256;
200 char *new_data = (char *)cwist_alloc(new_cap);
201 if (!new_data) return 1;
202 if (ctx->data) {
203 memcpy(new_data, ctx->data, ctx->data_len);
204 cwist_free(ctx->data);
205 }
206 ctx->data = new_data;
207 ctx->data_cap = new_cap;
208 }
209 memcpy(ctx->data + ctx->data_len, at, len);
210 ctx->data_len += len;
211 return 0;
212}
213
225static int mp_on_part_data_end(multipart_parser *p) {
226 mp_parse_ctx *ctx = (mp_parse_ctx *)multipart_parser_get_data(p);
227 cwist_multipart_field *field =
229 if (!field) return 1;
230 memset(field, 0, sizeof(*field));
231
232 if (ctx->name[0]) {
233 field->name = (char *)cwist_alloc(strlen(ctx->name) + 1);
234 if (field->name) strcpy(field->name, ctx->name);
235 }
236 if (ctx->filename[0]) {
237 field->filename = (char *)cwist_alloc(strlen(ctx->filename) + 1);
238 if (field->filename) strcpy(field->filename, ctx->filename);
239 }
240 if (ctx->content_type[0]) {
241 field->content_type = (char *)cwist_alloc(strlen(ctx->content_type) + 1);
242 if (field->content_type) strcpy(field->content_type, ctx->content_type);
243 }
244 field->data = ctx->data;
245 field->data_len = ctx->data_len;
246 field->next = ctx->result->fields;
247 ctx->result->fields = field;
248
249 ctx->data = NULL;
250 ctx->data_len = 0;
251 ctx->data_cap = 0;
252 return 0;
253}
254
269cwist_multipart_result *cwist_multipart_parse(const char *body, size_t body_len,
270 const char *boundary) {
271 if (!body || body_len == 0 || !boundary) return NULL;
272
273 cwist_multipart_result *result =
275 if (!result) return NULL;
276 result->fields = NULL;
277
278 mp_parse_ctx ctx = {0};
279 ctx.result = result;
280
282 .on_header_field = mp_on_header_field,
283 .on_header_value = mp_on_header_value,
284 .on_headers_complete = mp_on_headers_complete,
285 .on_part_data_begin = mp_on_part_data_begin,
286 .on_part_data = mp_on_part_data,
287 .on_part_data_end = mp_on_part_data_end,
288 };
289
290 /* multipart-parser-c expects the leading dashes in the boundary. */
291 size_t blen = strlen(boundary);
293 char *parser_boundary = (char *)cwist_scratch_alloc(&parser_boundary_s, blen + 3);
294 if (!parser_boundary) {
295 cwist_free(result);
296 return NULL;
297 }
298 memcpy(parser_boundary, "--", 2);
300 parser_boundary[blen + 2] = '\0';
301
303 if (!parser) {
304 cwist_free(result);
305 return NULL;
306 }
308 size_t consumed = multipart_parser_execute(parser, body, body_len);
310
311 /* Truncated body: on_part_data_end never fired, so release the in-flight
312 * part buffer here. */
313 if (ctx.data) {
314 cwist_free(ctx.data);
315 ctx.data = NULL;
316 }
317
318 /* The parser stops early on malformed input; report it as NULL per the
319 * documented contract. */
320 if (consumed != body_len) {
322 return NULL;
323 }
324
325 return result;
326}
327
337 if (!result) return;
339 while (curr) {
341 cwist_free(curr->name);
342 cwist_free(curr->filename);
343 cwist_free(curr->content_type);
344 cwist_free(curr->data);
346 curr = next;
347 }
348 cwist_free(result);
349}
350
363char *cwist_multipart_extract_boundary(const char *content_type) {
364 if (!content_type) return NULL;
365 const char *p = content_type;
366 while (*p) {
367 while (*p && (isspace((unsigned char)*p) || *p == ';')) p++;
368 if (strncasecmp(p, "boundary", 8) == 0) {
369 p += 8;
370 while (*p && isspace((unsigned char)*p)) p++;
371 if (*p == '=') {
372 p++;
373 while (*p && isspace((unsigned char)*p)) p++;
374 const char *start = p;
375 if (*p == '"') {
376 start = ++p;
377 while (*p && *p != '"') p++;
378 } else {
379 while (*p && *p != ';' && !isspace((unsigned char)*p)) p++;
380 }
381 size_t len = (size_t)(p - start);
382 char *boundary = (char *)cwist_alloc(len + 1);
383 if (boundary) {
384 memcpy(boundary, start, len);
385 boundary[len] = '\0';
386 }
387 return boundary;
388 }
389 }
390 while (*p && *p != ';') p++;
391 if (*p == ';') p++;
392 }
393 return NULL;
394}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
#define CWIST_SCRATCH_DEFER
Attach to a cwist_scratch_t variable's declaration so its allocation (arena- or heap-backed,...
Definition alloc.h:190
void cwist_multipart_result_destroy(cwist_multipart_result *result)
Destroy a parse result and everything it owns.
Definition multipart.c:336
char * cwist_multipart_extract_boundary(const char *content_type)
Extract the boundary parameter from a Content-Type header value.
Definition multipart.c:363
cwist_multipart_result * cwist_multipart_parse(const char *body, size_t body_len, const char *boundary)
Parse a multipart/form-data body into a field list.
Definition multipart.c:269
RFC 7578 multipart/form-data parser.
size_t cap
Definition otel.c:56
char * filename
File name (NULL for non-file fields).
Definition multipart.h:13
char * name
Form field name.
Definition multipart.h:12
size_t data_len
Length of data in bytes.
Definition multipart.h:16
struct cwist_multipart_field * next
Definition multipart.h:17
char * data
Raw part body (may contain binary data).
Definition multipart.h:15
char * content_type
Content-Type of the part (NULL if absent).
Definition multipart.h:14
cwist_multipart_field * fields
Head of the field linked list.
Definition multipart.h:21
Backing state for cwist_scratch_alloc()/CWIST_SCRATCH_DEFER: a single block-scoped allocation that pr...
Definition alloc.h:131