Pharos 0.7.23
Modern Web Framework & Web App Hosting Service.
Loading...
Searching...
No Matches
native_dynamic_multipart.c
Go to the documentation of this file.
1/**
2 * @file
3 * @brief Pharos-owned multipart form-data parsing for in-process dynamic handling.
4 *
5 * Provides boundary extraction, part lookup, filename extraction, and
6 * file-part extraction so that the dynamic runtime can process multipart
7 * uploads without delegating to a child process or relying on app-owned
8 * shell logic.
9 *
10 * This module is the canonical Pharos-owned implementation of multipart
11 * parsing. App-owned shell code must not replicate this logic.
12 */
13
15
16#include <ctype.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20
21/* ------------------------------------------------------------------ */
22/* Internal helpers */
23/* ------------------------------------------------------------------ */
24
25/**
26 * @brief Finds a byte pattern within a buffer.
27 *
28 * @param haystack Buffer to search.
29 * @param haystack_len Byte length of @p haystack.
30 * @param needle Pattern to locate.
31 * @param needle_len Byte length of @p needle.
32 * @return Pointer to first occurrence within @p haystack, or NULL.
33 */
34static const unsigned char *multipart_memmem(
35 const unsigned char *haystack,
36 size_t haystack_len,
37 const unsigned char *needle,
38 size_t needle_len
39) {
40 size_t i;
41 if (haystack == NULL || needle == NULL || needle_len == 0 || haystack_len < needle_len) {
42 return NULL;
43 }
44 for (i = 0; i + needle_len <= haystack_len; i++) {
45 if (memcmp(haystack + i, needle, needle_len) == 0) {
46 return haystack + i;
47 }
48 }
49 return NULL;
50}
51
52/**
53 * @brief Allocates a NUL-terminated copy of a byte range.
54 *
55 * @param start Start of the range (need not be NUL-terminated).
56 * @param len Number of bytes to copy.
57 * @return Newly allocated NUL-terminated string, or NULL on allocation failure.
58 */
59static char *multipart_dup_range(const char *start, size_t len) {
60 char *out;
61 if (start == NULL || len == 0) {
62 return NULL;
63 }
64 out = (char *)malloc(len + 1);
65 if (out == NULL) {
66 return NULL;
67 }
68 memcpy(out, start, len);
69 out[len] = '\0';
70 return out;
71}
72
73/**
74 * @brief Extracts the MIME boundary from a Content-Type header value.
75 *
76 * Handles both quoted and unquoted boundary parameters.
77 *
78 * @param content_type The full Content-Type header value.
79 * @return Newly allocated boundary string (without the leading @c -- ),
80 * or NULL when the boundary cannot be determined.
81 */
82char *native_dynamic_multipart_boundary(const char *content_type) {
83 const char *boundary_start = NULL;
84 const char *boundary_end = NULL;
85 if (content_type == NULL) {
86 return NULL;
87 }
88 boundary_start = strstr(content_type, "boundary=");
89 if (boundary_start == NULL) {
90 return NULL;
91 }
92 boundary_start += 9;
93 if (*boundary_start == '"') {
94 boundary_start++;
95 boundary_end = strchr(boundary_start, '"');
96 } else {
97 boundary_end = boundary_start;
98 while (*boundary_end != '\0' && *boundary_end != ';' && !isspace((unsigned char)*boundary_end)) {
99 boundary_end++;
100 }
101 }
102 if (boundary_end == NULL || boundary_end <= boundary_start) {
103 return NULL;
104 }
105 return multipart_dup_range(boundary_start, (size_t)(boundary_end - boundary_start));
106}
107
108/**
109 * @brief Extracts a quoted parameter value from MIME part headers.
110 *
111 * @param headers_text NUL-terminated headers block for one part.
112 * @param key Parameter name (e.g. @c name or @c filename).
113 * @return Newly allocated parameter value, or NULL when absent.
114 */
115static char *multipart_header_param_dup(const char *headers_text, const char *key) {
116 char pattern[64];
117 const char *match = NULL;
118 const char *value_start = NULL;
119 const char *value_end = NULL;
120 if (headers_text == NULL || key == NULL || key[0] == '\0') {
121 return NULL;
122 }
123 snprintf(pattern, sizeof(pattern), "%s=\"", key);
124 match = strstr(headers_text, pattern);
125 if (match == NULL) {
126 return NULL;
127 }
128 value_start = match + strlen(pattern);
129 value_end = strchr(value_start, '"');
130 if (value_end == NULL || value_end < value_start) {
131 return NULL;
132 }
133 return multipart_dup_range(value_start, (size_t)(value_end - value_start));
134}
135
136/* ------------------------------------------------------------------ */
137/* Public API */
138/* ------------------------------------------------------------------ */
139
141 const unsigned char *body,
142 size_t body_len,
143 const char *content_type,
144 const char *field_name,
145 char **out_text,
146 char **out_filename,
147 const unsigned char **out_content_start,
148 size_t *out_content_len
149) {
150 char *boundary = NULL;
151 char *delimiter = NULL;
152 char *delimiter_crlf = NULL;
153 char *delimiter_lf = NULL;
154 const unsigned char *cursor = NULL;
155 size_t delimiter_len = 0;
156 int found = 0;
157
158 if (out_text != NULL) *out_text = NULL;
159 if (out_filename != NULL) *out_filename = NULL;
160 if (out_content_start != NULL) *out_content_start = NULL;
161 if (out_content_len != NULL) *out_content_len = 0;
162
163 if (body == NULL || body_len == 0 || content_type == NULL || field_name == NULL || field_name[0] == '\0') {
164 return 0;
165 }
166
167 boundary = native_dynamic_multipart_boundary(content_type);
168 if (boundary == NULL || boundary[0] == '\0') {
169 free(boundary);
170 return 0;
171 }
172
173 delimiter_len = strlen(boundary) + 2;
174 delimiter = (char *)malloc(delimiter_len + 1);
175 delimiter_crlf = (char *)malloc(delimiter_len + 3);
176 delimiter_lf = (char *)malloc(delimiter_len + 2);
177 if (delimiter == NULL || delimiter_crlf == NULL || delimiter_lf == NULL) {
178 free(boundary); free(delimiter); free(delimiter_crlf); free(delimiter_lf);
179 return 0;
180 }
181 snprintf(delimiter, delimiter_len + 1, "--%s", boundary);
182 snprintf(delimiter_crlf, delimiter_len + 3, "\r\n--%s", boundary);
183 snprintf(delimiter_lf, delimiter_len + 2, "\n--%s", boundary);
184
185 cursor = multipart_memmem(body, body_len, (const unsigned char *)delimiter, delimiter_len);
186 while (cursor != NULL && (size_t)(cursor - body) + delimiter_len <= body_len) {
187 const unsigned char *part_cursor = cursor + delimiter_len;
188 const unsigned char *header_end = NULL;
189 const unsigned char *content_start = NULL;
190 const unsigned char *next_marker = NULL;
191 size_t header_sep_len = 0;
192 size_t headers_len = 0;
193 size_t content_len = 0;
194 char *headers_text = NULL;
195 char *part_name = NULL;
196 char *filename = NULL;
197
198 /* Check for closing delimiter (--) */
199 if ((size_t)(part_cursor - body) + 2 <= body_len && part_cursor[0] == '-' && part_cursor[1] == '-') {
200 break;
201 }
202
203 /* Skip CRLF or LF after delimiter */
204 if ((size_t)(part_cursor - body) + 2 <= body_len && part_cursor[0] == '\r' && part_cursor[1] == '\n') {
205 part_cursor += 2;
206 } else if ((size_t)(part_cursor - body) + 1 <= body_len && part_cursor[0] == '\n') {
207 part_cursor += 1;
208 }
209
210 /* Find header/body separator */
211 {
212 const unsigned char *next_crlf = multipart_memmem(part_cursor, body_len - (size_t)(part_cursor - body), (const unsigned char *)"\r\n\r\n", 4);
213 const unsigned char *next_lf = multipart_memmem(part_cursor, body_len - (size_t)(part_cursor - body), (const unsigned char *)"\n\n", 2);
214 if (next_crlf != NULL && (next_lf == NULL || next_crlf <= next_lf)) {
215 header_end = next_crlf;
216 header_sep_len = 4;
217 } else if (next_lf != NULL) {
218 header_end = next_lf;
219 header_sep_len = 2;
220 } else {
221 break;
222 }
223 }
224
225 headers_len = (size_t)(header_end - part_cursor);
226 headers_text = multipart_dup_range((const char *)part_cursor, headers_len);
227 part_name = multipart_header_param_dup(headers_text, "name");
228 filename = multipart_header_param_dup(headers_text, "filename");
229 content_start = header_end + header_sep_len;
230
231 /* Find the next delimiter to bound this part's content */
232 {
233 const unsigned char *next_crlf = multipart_memmem(content_start, body_len - (size_t)(content_start - body), (const unsigned char *)delimiter_crlf, delimiter_len + 2);
234 const unsigned char *next_lf = multipart_memmem(content_start, body_len - (size_t)(content_start - body), (const unsigned char *)delimiter_lf, delimiter_len + 1);
235 if (next_crlf != NULL && (next_lf == NULL || next_crlf <= next_lf)) {
236 next_marker = next_crlf;
237 } else if (next_lf != NULL) {
238 next_marker = next_lf;
239 } else {
240 next_marker = body + body_len;
241 }
242 }
243
244 content_len = (size_t)(next_marker - content_start);
245
246 if (part_name != NULL && strcmp(part_name, field_name) == 0) {
247 if (out_text != NULL) {
248 *out_text = multipart_dup_range((const char *)content_start, content_len);
249 }
250 if (out_filename != NULL && filename != NULL) {
251 *out_filename = strdup(filename);
252 }
253 if (out_content_start != NULL) {
254 *out_content_start = content_start;
255 }
256 if (out_content_len != NULL) {
257 *out_content_len = content_len;
258 }
259 found = 1;
260 free(headers_text);
261 free(part_name);
262 free(filename);
263 break;
264 }
265
266 free(headers_text);
267 free(part_name);
268 free(filename);
269
270 if (next_marker >= body + body_len) {
271 break;
272 }
273 cursor = next_marker + (next_marker[0] == '\r' ? 2 : 1);
274 cursor = multipart_memmem(cursor, body_len - (size_t)(cursor - body), (const unsigned char *)delimiter, delimiter_len);
275 }
276
277 free(boundary);
278 free(delimiter);
279 free(delimiter_crlf);
280 free(delimiter_lf);
281 return found;
282}
283
285 const unsigned char *body,
286 size_t body_len,
287 const char *content_type,
288 const char *field_name
289) {
290 char *filename = NULL;
291 native_dynamic_multipart_find_part(body, body_len, content_type, field_name, NULL, &filename, NULL, NULL);
292 return filename;
293}
294
296 const unsigned char *body,
297 size_t body_len,
298 const char *content_type,
299 const char *field_name
300) {
301 char *text = NULL;
302 native_dynamic_multipart_find_part(body, body_len, content_type, field_name, &text, NULL, NULL, NULL);
303 return text;
304}
305
307 const unsigned char *body,
308 size_t body_len,
309 const char *content_type,
310 const char *field_name,
311 const char *output_path
312) {
313 const unsigned char *content_start = NULL;
314 size_t content_len = 0;
315 FILE *fp = NULL;
316 size_t written;
317
318 if (output_path == NULL || output_path[0] == '\0') {
319 return 64;
320 }
321
322 if (!native_dynamic_multipart_find_part(body, body_len, content_type, field_name, NULL, NULL, &content_start, &content_len)) {
323 return 1;
324 }
325
326 if (content_start == NULL || content_len == 0) {
327 return 1;
328 }
329
330 fp = fopen(output_path, "wb");
331 if (fp == NULL) {
332 return 73;
333 }
334
335 written = fwrite(content_start, 1, content_len, fp);
336 fclose(fp);
337 return written == content_len ? 0 : 74;
338}
static const unsigned char * multipart_memmem(const unsigned char *haystack, size_t haystack_len, const unsigned char *needle, size_t needle_len)
Finds a byte pattern within a buffer.
static char * multipart_header_param_dup(const char *headers_text, const char *key)
Extracts a quoted parameter value from MIME part headers.
int native_dynamic_multipart_extract_file(const unsigned char *body, size_t body_len, const char *content_type, const char *field_name, const char *output_path)
Extracts file content from a multipart file part and writes it to disk.
char * native_dynamic_multipart_extract_text(const unsigned char *body, size_t body_len, const char *content_type, const char *field_name)
Extracts a text value from a multipart form field.
int native_dynamic_multipart_find_part(const unsigned char *body, size_t body_len, const char *content_type, const char *field_name, char **out_text, char **out_filename, const unsigned char **out_content_start, size_t *out_content_len)
Finds a named part in a multipart request body.
static char * multipart_dup_range(const char *start, size_t len)
Allocates a NUL-terminated copy of a byte range.
char * native_dynamic_multipart_boundary(const char *content_type)
Extracts the MIME boundary from a Content-Type header value.
char * native_dynamic_multipart_filename(const unsigned char *body, size_t body_len, const char *content_type, const char *field_name)
Extracts the uploaded filename for a named multipart file part.
Pharos-owned multipart form-data parsing for in-process dynamic handling.
const char size_t len
Definition yyjson.h:8364