/src/civetweb/src/handle_form.inl
Line | Count | Source |
1 | | /* Copyright (c) 2016-2025 the Civetweb developers |
2 | | * |
3 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
4 | | * of this software and associated documentation files (the "Software"), to deal |
5 | | * in the Software without restriction, including without limitation the rights |
6 | | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
7 | | * copies of the Software, and to permit persons to whom the Software is |
8 | | * furnished to do so, subject to the following conditions: |
9 | | * |
10 | | * The above copyright notice and this permission notice shall be included in |
11 | | * all copies or substantial portions of the Software. |
12 | | * |
13 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
14 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
15 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
16 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
17 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
18 | | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
19 | | * THE SOFTWARE. |
20 | | */ |
21 | | |
22 | | static int |
23 | | url_encoded_field_found(const struct mg_connection *conn, |
24 | | const char *key, |
25 | | size_t key_len, |
26 | | const char *filename, |
27 | | size_t filename_len, |
28 | | char *path, |
29 | | size_t path_len, |
30 | | struct mg_form_data_handler *fdh) |
31 | 0 | { |
32 | 0 | char key_dec[1024]; |
33 | 0 | char filename_dec[1024]; |
34 | 0 | int key_dec_len; |
35 | 0 | int filename_dec_len; |
36 | 0 | int ret; |
37 | |
|
38 | 0 | key_dec_len = |
39 | 0 | mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); |
40 | |
|
41 | 0 | if (((size_t)key_dec_len >= (size_t)sizeof(key_dec)) || (key_dec_len < 0)) { |
42 | 0 | return MG_FORM_FIELD_STORAGE_ABORT; |
43 | 0 | } |
44 | | |
45 | 0 | if (filename) { |
46 | 0 | filename_dec_len = mg_url_decode(filename, |
47 | 0 | (int)filename_len, |
48 | 0 | filename_dec, |
49 | 0 | (int)sizeof(filename_dec), |
50 | 0 | 1); |
51 | |
|
52 | 0 | if (((size_t)filename_dec_len >= (size_t)sizeof(filename_dec)) |
53 | 0 | || (filename_dec_len < 0)) { |
54 | | /* Log error message and skip this field. */ |
55 | 0 | mg_cry_internal(conn, "%s: Cannot decode filename", __func__); |
56 | 0 | return MG_FORM_FIELD_STORAGE_ABORT; |
57 | 0 | } |
58 | 0 | remove_dot_segments(filename_dec); |
59 | |
|
60 | 0 | } else { |
61 | 0 | filename_dec[0] = 0; |
62 | 0 | } |
63 | | |
64 | 0 | ret = |
65 | 0 | fdh->field_found(key_dec, filename_dec, path, path_len, fdh->user_data); |
66 | |
|
67 | 0 | if ((ret & 0xF) == MG_FORM_FIELD_STORAGE_GET) { |
68 | 0 | if (fdh->field_get == NULL) { |
69 | 0 | mg_cry_internal(conn, |
70 | 0 | "%s: Function \"Get\" not available", |
71 | 0 | __func__); |
72 | 0 | return MG_FORM_FIELD_STORAGE_SKIP; |
73 | 0 | } |
74 | 0 | } |
75 | 0 | if ((ret & 0xF) == MG_FORM_FIELD_STORAGE_STORE) { |
76 | 0 | if (fdh->field_store == NULL) { |
77 | 0 | mg_cry_internal(conn, |
78 | 0 | "%s: Function \"Store\" not available", |
79 | 0 | __func__); |
80 | 0 | return MG_FORM_FIELD_STORAGE_SKIP; |
81 | 0 | } |
82 | 0 | } |
83 | | |
84 | 0 | return ret; |
85 | 0 | } |
86 | | |
87 | | static int |
88 | | url_encoded_field_get( |
89 | | const struct mg_connection *conn, |
90 | | const char *key, |
91 | | size_t key_len, |
92 | | const char *value, |
93 | | size_t *value_len, /* IN: number of bytes available in "value", OUT: number |
94 | | of bytes processed */ |
95 | | struct mg_form_data_handler *fdh) |
96 | 0 | { |
97 | 0 | char key_dec[1024]; |
98 | 0 | int key_dec_len; |
99 | |
|
100 | 0 | char *value_dec = (char *)mg_malloc_ctx(*value_len + 1, conn->phys_ctx); |
101 | 0 | int value_dec_len, ret; |
102 | |
|
103 | 0 | if (!value_dec) { |
104 | | /* Log error message and stop parsing the form data. */ |
105 | 0 | mg_cry_internal(conn, |
106 | 0 | "%s: Not enough memory (required: %lu)", |
107 | 0 | __func__, |
108 | 0 | (unsigned long)(*value_len + 1)); |
109 | 0 | return MG_FORM_FIELD_STORAGE_ABORT; |
110 | 0 | } |
111 | | |
112 | 0 | key_dec_len = |
113 | 0 | mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); |
114 | |
|
115 | 0 | if (*value_len >= 2 && value[*value_len - 2] == '%') |
116 | 0 | *value_len -= 2; |
117 | 0 | else if (*value_len >= 1 && value[*value_len - 1] == '%') |
118 | 0 | (*value_len)--; |
119 | 0 | value_dec_len = mg_url_decode( |
120 | 0 | value, (int)*value_len, value_dec, ((int)*value_len) + 1, 1); |
121 | |
|
122 | 0 | if ((key_dec_len < 0) || (value_dec_len < 0)) { |
123 | 0 | mg_free(value_dec); |
124 | 0 | return MG_FORM_FIELD_STORAGE_ABORT; |
125 | 0 | } |
126 | | |
127 | 0 | ret = fdh->field_get(key_dec, |
128 | 0 | value_dec, |
129 | 0 | (size_t)value_dec_len, |
130 | 0 | fdh->user_data); |
131 | |
|
132 | 0 | mg_free(value_dec); |
133 | |
|
134 | 0 | return ret; |
135 | 0 | } |
136 | | |
137 | | static int |
138 | | unencoded_field_get(const struct mg_connection *conn, |
139 | | const char *key, |
140 | | size_t key_len, |
141 | | const char *value, |
142 | | size_t value_len, |
143 | | struct mg_form_data_handler *fdh) |
144 | 0 | { |
145 | 0 | char key_dec[1024]; |
146 | 0 | int key_dec_len; |
147 | 0 | (void)conn; |
148 | |
|
149 | 0 | key_dec_len = |
150 | 0 | mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1); |
151 | 0 | if (key_dec_len < 0) { |
152 | 0 | return MG_FORM_FIELD_STORAGE_ABORT; |
153 | 0 | } |
154 | | |
155 | 0 | return fdh->field_get(key_dec, value, value_len, fdh->user_data); |
156 | 0 | } |
157 | | |
158 | | static int |
159 | | field_stored(const struct mg_connection *conn, |
160 | | const char *path, |
161 | | long long file_size, |
162 | | struct mg_form_data_handler *fdh) |
163 | 0 | { |
164 | | /* Equivalent to "upload" callback of "mg_upload". */ |
165 | |
|
166 | 0 | (void)conn; /* we do not need mg_cry here, so conn is currently unused */ |
167 | |
|
168 | 0 | return fdh->field_store(path, file_size, fdh->user_data); |
169 | 0 | } |
170 | | |
171 | | static const char * |
172 | | search_boundary(const char *buf, |
173 | | size_t buf_len, |
174 | | const char *boundary, |
175 | | size_t boundary_len) |
176 | 0 | { |
177 | 0 | char *boundary_start = "\r\n--"; |
178 | 0 | size_t boundary_start_len = strlen(boundary_start); |
179 | | |
180 | | /* We must do a binary search here, not a string search, since the |
181 | | * buffer may contain '\x00' bytes, if binary data is transferred. */ |
182 | 0 | int clen = (int)buf_len - (int)boundary_len - boundary_start_len; |
183 | 0 | int i; |
184 | |
|
185 | 0 | for (i = 0; i <= clen; i++) { |
186 | 0 | if (!memcmp(buf + i, boundary_start, boundary_start_len)) { |
187 | 0 | if (!memcmp(buf + i + boundary_start_len, boundary, boundary_len)) { |
188 | 0 | return buf + i; |
189 | 0 | } |
190 | 0 | } |
191 | 0 | } |
192 | 0 | return NULL; |
193 | 0 | } |
194 | | |
195 | | int |
196 | | mg_handle_form_request(struct mg_connection *conn, |
197 | | struct mg_form_data_handler *fdh) |
198 | 0 | { |
199 | 0 | const char *content_type; |
200 | 0 | char path[512]; |
201 | 0 | char buf[MG_BUF_LEN]; /* Must not be smaller than ~900 */ |
202 | 0 | int field_storage; |
203 | 0 | size_t buf_fill = 0; |
204 | 0 | int r; |
205 | 0 | int field_count = 0; |
206 | 0 | int abort_read = 0; |
207 | 0 | struct mg_file fstore = STRUCT_FILE_INITIALIZER; |
208 | 0 | int64_t file_size = 0; /* init here, to a avoid a false positive |
209 | | "uninitialized variable used" warning */ |
210 | |
|
211 | 0 | int has_body_data = |
212 | 0 | (conn->request_info.content_length > 0) || (conn->is_chunked); |
213 | | |
214 | | /* Unused without filesystems */ |
215 | 0 | (void)fstore; |
216 | 0 | (void)file_size; |
217 | | |
218 | | /* There are three ways to encode data from a HTML form: |
219 | | * 1) method: GET (default) |
220 | | * The form data is in the HTTP query string. |
221 | | * 2) method: POST, enctype: "application/x-www-form-urlencoded" |
222 | | * The form data is in the request body. |
223 | | * The body is url encoded (the default encoding for POST). |
224 | | * 3) method: POST, enctype: "multipart/form-data". |
225 | | * The form data is in the request body of a multipart message. |
226 | | * This is the typical way to handle file upload from a form. |
227 | | */ |
228 | |
|
229 | 0 | if (!has_body_data) { |
230 | 0 | const char *data; |
231 | |
|
232 | 0 | if (0 != strcmp(conn->request_info.request_method, "GET")) { |
233 | | /* No body data, but not a GET request. |
234 | | * This is not a valid form request. */ |
235 | 0 | return -1; |
236 | 0 | } |
237 | | |
238 | | /* GET request: form data is in the query string. */ |
239 | | /* The entire data has already been loaded, so there is no need to |
240 | | * call mg_read. We just need to split the query string into key-value |
241 | | * pairs. */ |
242 | 0 | data = conn->request_info.query_string; |
243 | 0 | if (!data) { |
244 | | /* No query string. */ |
245 | 0 | return -1; |
246 | 0 | } |
247 | | |
248 | | /* Split data in a=1&b=xy&c=3&c=4 ... */ |
249 | 0 | while (*data) { |
250 | 0 | const char *val = strchr(data, '='); |
251 | 0 | const char *next; |
252 | 0 | ptrdiff_t keylen, vallen; |
253 | |
|
254 | 0 | if (!val) { |
255 | 0 | break; |
256 | 0 | } |
257 | 0 | keylen = val - data; |
258 | | |
259 | | /* In every "field_found" callback we ask what to do with the |
260 | | * data ("field_storage"). This could be: |
261 | | * MG_FORM_FIELD_STORAGE_SKIP (0): |
262 | | * ignore the value of this field |
263 | | * MG_FORM_FIELD_STORAGE_GET (1): |
264 | | * read the data and call the get callback function |
265 | | * MG_FORM_FIELD_STORAGE_STORE (2): |
266 | | * store the data in a file |
267 | | * MG_FORM_FIELD_STORAGE_READ (3): |
268 | | * let the user read the data (for parsing long data on the fly) |
269 | | * MG_FORM_FIELD_STORAGE_ABORT (flag): |
270 | | * stop parsing |
271 | | */ |
272 | 0 | memset(path, 0, sizeof(path)); |
273 | 0 | field_count++; |
274 | 0 | field_storage = url_encoded_field_found(conn, |
275 | 0 | data, |
276 | 0 | (size_t)keylen, |
277 | 0 | NULL, |
278 | 0 | 0, |
279 | 0 | path, |
280 | 0 | sizeof(path) - 1, |
281 | 0 | fdh); |
282 | |
|
283 | 0 | val++; |
284 | 0 | next = strchr(val, '&'); |
285 | 0 | if (next) { |
286 | 0 | vallen = next - val; |
287 | 0 | } else { |
288 | 0 | vallen = (ptrdiff_t)strlen(val); |
289 | 0 | } |
290 | |
|
291 | 0 | if (field_storage == MG_FORM_FIELD_STORAGE_GET) { |
292 | | /* Call callback */ |
293 | 0 | r = url_encoded_field_get( |
294 | 0 | conn, data, (size_t)keylen, val, (size_t *)&vallen, fdh); |
295 | 0 | if (r == MG_FORM_FIELD_HANDLE_ABORT) { |
296 | | /* Stop request handling */ |
297 | 0 | abort_read = 1; |
298 | 0 | break; |
299 | 0 | } |
300 | 0 | if (r == MG_FORM_FIELD_HANDLE_NEXT) { |
301 | | /* Skip to next field */ |
302 | 0 | field_storage = MG_FORM_FIELD_STORAGE_SKIP; |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | 0 | if (next) { |
307 | 0 | next++; |
308 | 0 | } else { |
309 | | /* vallen may have been modified by url_encoded_field_get */ |
310 | 0 | next = val + vallen; |
311 | 0 | } |
312 | |
|
313 | 0 | #if !defined(NO_FILESYSTEMS) |
314 | 0 | if (field_storage == MG_FORM_FIELD_STORAGE_STORE) { |
315 | | /* Store the content to a file */ |
316 | 0 | if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) { |
317 | 0 | fstore.access.fp = NULL; |
318 | 0 | } |
319 | 0 | file_size = 0; |
320 | 0 | if (fstore.access.fp != NULL) { |
321 | 0 | size_t n = (size_t) |
322 | 0 | fwrite(val, 1, (size_t)vallen, fstore.access.fp); |
323 | 0 | if ((n != (size_t)vallen) || (ferror(fstore.access.fp))) { |
324 | 0 | mg_cry_internal(conn, |
325 | 0 | "%s: Cannot write file %s", |
326 | 0 | __func__, |
327 | 0 | path); |
328 | 0 | (void)mg_fclose(&fstore.access); |
329 | 0 | remove_bad_file(conn, path); |
330 | 0 | } |
331 | 0 | file_size += (int64_t)n; |
332 | |
|
333 | 0 | if (fstore.access.fp) { |
334 | 0 | r = mg_fclose(&fstore.access); |
335 | 0 | if (r == 0) { |
336 | | /* stored successfully */ |
337 | 0 | r = field_stored(conn, path, file_size, fdh); |
338 | 0 | if (r == MG_FORM_FIELD_HANDLE_ABORT) { |
339 | | /* Stop request handling */ |
340 | 0 | abort_read = 1; |
341 | 0 | break; |
342 | 0 | } |
343 | |
|
344 | 0 | } else { |
345 | 0 | mg_cry_internal(conn, |
346 | 0 | "%s: Error saving file %s", |
347 | 0 | __func__, |
348 | 0 | path); |
349 | 0 | remove_bad_file(conn, path); |
350 | 0 | } |
351 | 0 | fstore.access.fp = NULL; |
352 | 0 | } |
353 | |
|
354 | 0 | } else { |
355 | 0 | mg_cry_internal(conn, |
356 | 0 | "%s: Cannot create file %s", |
357 | 0 | __func__, |
358 | 0 | path); |
359 | 0 | } |
360 | 0 | } |
361 | 0 | #endif /* NO_FILESYSTEMS */ |
362 | | |
363 | | /* if (field_storage == MG_FORM_FIELD_STORAGE_READ) { */ |
364 | | /* The idea of "field_storage=read" is to let the API user read |
365 | | * data chunk by chunk and to some data processing on the fly. |
366 | | * This should avoid the need to store data in the server: |
367 | | * It should neither be stored in memory, like |
368 | | * "field_storage=get" does, nor in a file like |
369 | | * "field_storage=store". |
370 | | * However, for a "GET" request this does not make any much |
371 | | * sense, since the data is already stored in memory, as it is |
372 | | * part of the query string. |
373 | | */ |
374 | | /* } */ |
375 | | |
376 | 0 | if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT) |
377 | 0 | == MG_FORM_FIELD_STORAGE_ABORT) { |
378 | | /* Stop parsing the request */ |
379 | 0 | abort_read = 1; |
380 | 0 | break; |
381 | 0 | } |
382 | | |
383 | | /* Proceed to next entry */ |
384 | 0 | data = next; |
385 | 0 | } |
386 | |
|
387 | 0 | return field_count; |
388 | 0 | } |
389 | | |
390 | 0 | content_type = mg_get_header(conn, "Content-Type"); |
391 | |
|
392 | 0 | if (!content_type |
393 | 0 | || !mg_strncasecmp(content_type, |
394 | 0 | "APPLICATION/X-WWW-FORM-URLENCODED", |
395 | 0 | 33) |
396 | 0 | || !mg_strncasecmp(content_type, |
397 | 0 | "APPLICATION/WWW-FORM-URLENCODED", |
398 | 0 | 31)) { |
399 | | /* The form data is in the request body data, encoded in key/value |
400 | | * pairs. */ |
401 | 0 | int all_data_read = 0; |
402 | | |
403 | | /* Read body data and split it in keys and values. |
404 | | * The encoding is like in the "GET" case above: a=1&b&c=3&c=4. |
405 | | * Here we use "POST", and read the data from the request body. |
406 | | * The data read on the fly, so it is not required to buffer the |
407 | | * entire request in memory before processing it. */ |
408 | 0 | while (!abort_read) { |
409 | 0 | const char *val; |
410 | 0 | const char *next; |
411 | 0 | ptrdiff_t keylen, vallen; |
412 | 0 | ptrdiff_t used; |
413 | 0 | int end_of_key_value_pair_found = 0; |
414 | 0 | int get_block; |
415 | |
|
416 | 0 | if (buf_fill < (sizeof(buf) - 1)) { |
417 | |
|
418 | 0 | size_t to_read = sizeof(buf) - 1 - buf_fill; |
419 | 0 | r = mg_read(conn, buf + buf_fill, to_read); |
420 | 0 | if ((r < 0) || ((r == 0) && all_data_read)) { |
421 | | /* read error */ |
422 | 0 | return -1; |
423 | 0 | } |
424 | 0 | if (r == 0) { |
425 | | /* TODO: Create a function to get "all_data_read" from |
426 | | * the conn object. All data is read if the Content-Length |
427 | | * has been reached, or if chunked encoding is used and |
428 | | * the end marker has been read, or if the connection has |
429 | | * been closed. */ |
430 | 0 | all_data_read = (buf_fill == 0); |
431 | 0 | } |
432 | 0 | buf_fill += r; |
433 | 0 | buf[buf_fill] = 0; |
434 | 0 | if (buf_fill < 1) { |
435 | 0 | break; |
436 | 0 | } |
437 | 0 | } |
438 | | |
439 | 0 | val = strchr(buf, '='); |
440 | |
|
441 | 0 | if (!val) { |
442 | 0 | break; |
443 | 0 | } |
444 | 0 | keylen = val - buf; |
445 | 0 | val++; |
446 | | |
447 | | /* Call callback */ |
448 | 0 | memset(path, 0, sizeof(path)); |
449 | 0 | field_count++; |
450 | 0 | field_storage = url_encoded_field_found(conn, |
451 | 0 | buf, |
452 | 0 | (size_t)keylen, |
453 | 0 | NULL, |
454 | 0 | 0, |
455 | 0 | path, |
456 | 0 | sizeof(path) - 1, |
457 | 0 | fdh); |
458 | |
|
459 | 0 | if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT) |
460 | 0 | == MG_FORM_FIELD_STORAGE_ABORT) { |
461 | | /* Stop parsing the request */ |
462 | 0 | abort_read = 1; |
463 | 0 | break; |
464 | 0 | } |
465 | | |
466 | 0 | #if !defined(NO_FILESYSTEMS) |
467 | 0 | if (field_storage == MG_FORM_FIELD_STORAGE_STORE) { |
468 | 0 | if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) { |
469 | 0 | fstore.access.fp = NULL; |
470 | 0 | } |
471 | 0 | file_size = 0; |
472 | 0 | if (!fstore.access.fp) { |
473 | 0 | mg_cry_internal(conn, |
474 | 0 | "%s: Cannot create file %s", |
475 | 0 | __func__, |
476 | 0 | path); |
477 | 0 | } |
478 | 0 | } |
479 | 0 | #endif /* NO_FILESYSTEMS */ |
480 | |
|
481 | 0 | get_block = 0; |
482 | | /* Loop to read values larger than sizeof(buf)-keylen-2 */ |
483 | 0 | do { |
484 | 0 | next = strchr(val, '&'); |
485 | 0 | if (next) { |
486 | 0 | vallen = next - val; |
487 | 0 | end_of_key_value_pair_found = 1; |
488 | 0 | } else { |
489 | 0 | vallen = (ptrdiff_t)strlen(val); |
490 | 0 | end_of_key_value_pair_found = all_data_read; |
491 | 0 | if ((buf + buf_fill) > (val + vallen)) { |
492 | | /* Avoid DoS attacks by having a zero byte in the middle |
493 | | * of a request that is supposed to be URL encoded. |
494 | | * Since this request is certainly invalid, according to |
495 | | * the protocol |
496 | | * specification, stop processing it. Fixes #1348 */ |
497 | 0 | abort_read = 1; |
498 | 0 | break; |
499 | 0 | } |
500 | 0 | } |
501 | | |
502 | 0 | if (field_storage == MG_FORM_FIELD_STORAGE_GET) { |
503 | | #if 0 |
504 | | if (!end_of_key_value_pair_found && !all_data_read) { |
505 | | /* This callback will deliver partial contents */ |
506 | | } |
507 | | #endif |
508 | | |
509 | | /* Call callback */ |
510 | 0 | r = url_encoded_field_get(conn, |
511 | 0 | ((get_block > 0) ? NULL : buf), |
512 | 0 | ((get_block > 0) |
513 | 0 | ? 0 |
514 | 0 | : (size_t)keylen), |
515 | 0 | val, |
516 | 0 | (size_t *)&vallen, |
517 | 0 | fdh); |
518 | 0 | get_block++; |
519 | 0 | if (r == MG_FORM_FIELD_HANDLE_ABORT) { |
520 | | /* Stop request handling */ |
521 | 0 | abort_read = 1; |
522 | 0 | break; |
523 | 0 | } |
524 | 0 | if (r == MG_FORM_FIELD_HANDLE_NEXT) { |
525 | | /* Skip to next field */ |
526 | 0 | field_storage = MG_FORM_FIELD_STORAGE_SKIP; |
527 | 0 | } |
528 | 0 | } |
529 | | |
530 | 0 | if (next) { |
531 | 0 | next++; |
532 | 0 | } else { |
533 | | /* vallen may have been modified by url_encoded_field_get */ |
534 | 0 | next = val + vallen; |
535 | 0 | } |
536 | |
|
537 | 0 | #if !defined(NO_FILESYSTEMS) |
538 | 0 | if (fstore.access.fp) { |
539 | 0 | size_t n = (size_t) |
540 | 0 | fwrite(val, 1, (size_t)vallen, fstore.access.fp); |
541 | 0 | if ((n != (size_t)vallen) || (ferror(fstore.access.fp))) { |
542 | 0 | mg_cry_internal(conn, |
543 | 0 | "%s: Cannot write file %s", |
544 | 0 | __func__, |
545 | 0 | path); |
546 | 0 | mg_fclose(&fstore.access); |
547 | 0 | remove_bad_file(conn, path); |
548 | 0 | } |
549 | 0 | file_size += (int64_t)n; |
550 | 0 | } |
551 | 0 | #endif /* NO_FILESYSTEMS */ |
552 | |
|
553 | 0 | if (!end_of_key_value_pair_found) { |
554 | 0 | used = next - buf; |
555 | 0 | memmove(buf, |
556 | 0 | buf + (size_t)used, |
557 | 0 | sizeof(buf) - (size_t)used); |
558 | 0 | next = buf; |
559 | 0 | buf_fill -= used; |
560 | 0 | if (buf_fill < (sizeof(buf) - 1)) { |
561 | |
|
562 | 0 | size_t to_read = sizeof(buf) - 1 - buf_fill; |
563 | 0 | r = mg_read(conn, buf + buf_fill, to_read); |
564 | 0 | if ((r < 0) || ((r == 0) && all_data_read)) { |
565 | 0 | #if !defined(NO_FILESYSTEMS) |
566 | | /* read error */ |
567 | 0 | if (fstore.access.fp) { |
568 | 0 | mg_fclose(&fstore.access); |
569 | 0 | remove_bad_file(conn, path); |
570 | 0 | } |
571 | 0 | return -1; |
572 | 0 | #endif /* NO_FILESYSTEMS */ |
573 | 0 | } |
574 | 0 | if (r == 0) { |
575 | | /* TODO: Create a function to get "all_data_read" |
576 | | * from the conn object. All data is read if the |
577 | | * Content-Length has been reached, or if chunked |
578 | | * encoding is used and the end marker has been |
579 | | * read, or if the connection has been closed. */ |
580 | 0 | all_data_read = (buf_fill == 0); |
581 | 0 | } |
582 | 0 | buf_fill += r; |
583 | 0 | buf[buf_fill] = 0; |
584 | 0 | if (buf_fill < 1) { |
585 | 0 | break; |
586 | 0 | } |
587 | 0 | val = buf; |
588 | 0 | } |
589 | 0 | } |
590 | 0 | } while (!end_of_key_value_pair_found); |
591 | | |
592 | 0 | #if !defined(NO_FILESYSTEMS) |
593 | 0 | if (fstore.access.fp) { |
594 | 0 | r = mg_fclose(&fstore.access); |
595 | 0 | if (r == 0) { |
596 | | /* stored successfully */ |
597 | 0 | r = field_stored(conn, path, file_size, fdh); |
598 | 0 | if (r == MG_FORM_FIELD_HANDLE_ABORT) { |
599 | | /* Stop request handling */ |
600 | 0 | abort_read = 1; |
601 | 0 | break; |
602 | 0 | } |
603 | 0 | } else { |
604 | 0 | mg_cry_internal(conn, |
605 | 0 | "%s: Error saving file %s", |
606 | 0 | __func__, |
607 | 0 | path); |
608 | 0 | remove_bad_file(conn, path); |
609 | 0 | } |
610 | 0 | fstore.access.fp = NULL; |
611 | 0 | } |
612 | 0 | #endif /* NO_FILESYSTEMS */ |
613 | | |
614 | 0 | if ((all_data_read && (buf_fill == 0)) || abort_read) { |
615 | | /* nothing more to process */ |
616 | 0 | break; |
617 | 0 | } |
618 | | |
619 | | /* Proceed to next entry */ |
620 | 0 | used = next - buf; |
621 | 0 | memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used); |
622 | 0 | buf_fill -= used; |
623 | 0 | } |
624 | | |
625 | 0 | return field_count; |
626 | 0 | } |
627 | | |
628 | 0 | if (!mg_strncasecmp(content_type, "MULTIPART/FORM-DATA;", 20)) { |
629 | | /* The form data is in the request body data, encoded as multipart |
630 | | * content (see https://www.ietf.org/rfc/rfc1867.txt, |
631 | | * https://www.ietf.org/rfc/rfc2388.txt). */ |
632 | 0 | char *boundary; |
633 | 0 | size_t bl; |
634 | 0 | ptrdiff_t used; |
635 | 0 | struct mg_request_info part_header; |
636 | 0 | char *hbuf; |
637 | 0 | const char *content_disp, *hend, *fbeg, *fend, *nbeg, *nend; |
638 | 0 | const char *next; |
639 | 0 | unsigned part_no; |
640 | 0 | int all_data_read = 0; |
641 | |
|
642 | 0 | memset(&part_header, 0, sizeof(part_header)); |
643 | | |
644 | | /* Skip all spaces between MULTIPART/FORM-DATA; and BOUNDARY= */ |
645 | 0 | bl = 20; |
646 | 0 | while (content_type[bl] == ' ') { |
647 | 0 | bl++; |
648 | 0 | } |
649 | | |
650 | | /* There has to be a BOUNDARY definition in the Content-Type header */ |
651 | 0 | if (mg_strncasecmp(content_type + bl, "BOUNDARY=", 9)) { |
652 | | /* Malformed request */ |
653 | 0 | return -1; |
654 | 0 | } |
655 | | |
656 | | /* Copy boundary string to variable "boundary" */ |
657 | | /* fbeg is pointer to start of value of boundary */ |
658 | 0 | fbeg = content_type + bl + 9; |
659 | 0 | bl = strlen(fbeg); |
660 | 0 | boundary = (char *)mg_malloc(bl + 1); |
661 | 0 | if (!boundary) { |
662 | | /* Out of memory */ |
663 | 0 | mg_cry_internal(conn, |
664 | 0 | "%s: Cannot allocate memory for boundary [%lu]", |
665 | 0 | __func__, |
666 | 0 | (unsigned long)bl); |
667 | 0 | return -1; |
668 | 0 | } |
669 | 0 | memcpy(boundary, fbeg, bl); |
670 | 0 | boundary[bl] = 0; |
671 | | |
672 | | /* RFC 2046 permits the boundary string to be quoted. */ |
673 | | /* If the boundary is quoted, trim the quotes */ |
674 | 0 | if (boundary[0] == '"') { |
675 | 0 | hbuf = strchr(boundary + 1, '"'); |
676 | 0 | if ((!hbuf) || (*hbuf != '"')) { |
677 | | /* Malformed request */ |
678 | 0 | mg_free(boundary); |
679 | 0 | return -1; |
680 | 0 | } |
681 | 0 | *hbuf = 0; |
682 | 0 | memmove(boundary, boundary + 1, bl); |
683 | 0 | bl = strlen(boundary); |
684 | 0 | } |
685 | | |
686 | | /* Do some sanity checks for boundary lengths */ |
687 | 0 | if (bl > 70) { |
688 | | /* From RFC 2046: |
689 | | * Boundary delimiters must not appear within the |
690 | | * encapsulated material, and must be no longer |
691 | | * than 70 characters, not counting the two |
692 | | * leading hyphens. |
693 | | */ |
694 | | |
695 | | /* The algorithm can not work if bl >= sizeof(buf), or if buf |
696 | | * can not hold the multipart header plus the boundary. |
697 | | * Requests with long boundaries are not RFC compliant, maybe they |
698 | | * are intended attacks to interfere with this algorithm. */ |
699 | 0 | mg_free(boundary); |
700 | 0 | return -1; |
701 | 0 | } |
702 | 0 | if (bl < 4) { |
703 | | /* Sanity check: A boundary string of less than 4 bytes makes |
704 | | * no sense either. */ |
705 | 0 | mg_free(boundary); |
706 | 0 | return -1; |
707 | 0 | } |
708 | | |
709 | 0 | for (part_no = 0;; part_no++) { |
710 | 0 | size_t towrite, fnlen, n; |
711 | 0 | int get_block; |
712 | 0 | size_t to_read = sizeof(buf) - 1 - buf_fill; |
713 | | |
714 | | /* Unused without filesystems */ |
715 | 0 | (void)n; |
716 | |
|
717 | 0 | r = mg_read(conn, buf + buf_fill, to_read); |
718 | 0 | if ((r < 0) || ((r == 0) && all_data_read)) { |
719 | | /* read error */ |
720 | 0 | mg_free(boundary); |
721 | 0 | return -1; |
722 | 0 | } |
723 | 0 | if (r == 0) { |
724 | 0 | all_data_read = (buf_fill == 0); |
725 | 0 | } |
726 | |
|
727 | 0 | buf_fill += r; |
728 | 0 | buf[buf_fill] = 0; |
729 | 0 | if (buf_fill < 1) { |
730 | | /* No data */ |
731 | 0 | mg_free(boundary); |
732 | 0 | return -1; |
733 | 0 | } |
734 | | |
735 | | /* @see https://www.rfc-editor.org/rfc/rfc2046.html#section-5.1.1 |
736 | | * |
737 | | * multipart-body := [preamble CRLF] |
738 | | * dash-boundary transport-padding CRLF |
739 | | * body-part *encapsulation |
740 | | * close-delimiter transport-padding |
741 | | * [CRLF epilogue] |
742 | | */ |
743 | | |
744 | 0 | if (part_no == 0) { |
745 | 0 | size_t preamble_length = 0; |
746 | | /* skip over the preamble until we find a complete boundary |
747 | | * limit the preamble length to prevent abuse */ |
748 | | /* +2 for the -- preceding the boundary */ |
749 | 0 | while (preamble_length < 1024 |
750 | 0 | && (preamble_length < buf_fill - bl) |
751 | 0 | && strncmp(buf + preamble_length + 2, boundary, bl)) { |
752 | 0 | preamble_length++; |
753 | 0 | } |
754 | | /* reset the start of buf to remove the preamble */ |
755 | 0 | if (0 == strncmp(buf + preamble_length + 2, boundary, bl)) { |
756 | 0 | memmove(buf, |
757 | 0 | buf + preamble_length, |
758 | 0 | (unsigned)buf_fill - (unsigned)preamble_length); |
759 | 0 | buf_fill -= preamble_length; |
760 | 0 | buf[buf_fill] = 0; |
761 | 0 | } |
762 | 0 | } |
763 | | |
764 | | /* either it starts with a boundary and it's fine, or it's malformed |
765 | | * because: |
766 | | * - the preamble was longer than accepted |
767 | | * - couldn't find a boundary at all in the body |
768 | | * - didn't have a terminating boundary */ |
769 | 0 | if (buf_fill < (bl + 2) || strncmp(buf, "--", 2) |
770 | 0 | || strncmp(buf + 2, boundary, bl)) { |
771 | | /* Malformed request */ |
772 | 0 | mg_free(boundary); |
773 | 0 | return -1; |
774 | 0 | } |
775 | | |
776 | | /* skip the -- */ |
777 | 0 | char *boundary_start = buf + 2; |
778 | 0 | size_t transport_padding = 0; |
779 | 0 | while (boundary_start[bl + transport_padding] == ' ' |
780 | 0 | || boundary_start[bl + transport_padding] == '\t') { |
781 | 0 | transport_padding++; |
782 | 0 | } |
783 | 0 | char *boundary_end = boundary_start + bl + transport_padding; |
784 | | |
785 | | /* after the transport padding, if the boundary isn't |
786 | | * immediately followed by a \r\n then it is either... */ |
787 | 0 | if (strncmp(boundary_end, "\r\n", 2)) { |
788 | | /* ...the final boundary, and it is followed by --, (in which |
789 | | * case it's the end of the request) or it's a malformed |
790 | | * request */ |
791 | 0 | if (strncmp(boundary_end, "--", 2)) { |
792 | | /* Malformed request */ |
793 | 0 | mg_free(boundary); |
794 | 0 | return -1; |
795 | 0 | } |
796 | | /* Ingore any epilogue here */ |
797 | 0 | break; |
798 | 0 | } |
799 | | |
800 | | /* skip the \r\n */ |
801 | 0 | hbuf = boundary_end + 2; |
802 | | /* Next, we need to get the part header: Read until \r\n\r\n */ |
803 | 0 | hend = strstr(hbuf, "\r\n\r\n"); |
804 | 0 | if (!hend) { |
805 | | /* Malformed request */ |
806 | 0 | mg_free(boundary); |
807 | 0 | return -1; |
808 | 0 | } |
809 | | |
810 | 0 | part_header.num_headers = |
811 | 0 | parse_http_headers(&hbuf, part_header.http_headers); |
812 | 0 | if ((hend + 2) != hbuf) { |
813 | | /* Malformed request */ |
814 | 0 | mg_free(boundary); |
815 | 0 | return -1; |
816 | 0 | } |
817 | | |
818 | | /* Skip \r\n\r\n */ |
819 | 0 | hend += 4; |
820 | | |
821 | | /* According to the RFC, every part has to have a header field like: |
822 | | * Content-Disposition: form-data; name="..." */ |
823 | 0 | content_disp = get_header(part_header.http_headers, |
824 | 0 | part_header.num_headers, |
825 | 0 | "Content-Disposition"); |
826 | 0 | if (!content_disp) { |
827 | | /* Malformed request */ |
828 | 0 | mg_free(boundary); |
829 | 0 | return -1; |
830 | 0 | } |
831 | | |
832 | | /* Get the mandatory name="..." part of the Content-Disposition |
833 | | * header. */ |
834 | 0 | nbeg = strstr(content_disp, "name=\""); |
835 | 0 | while ((nbeg != NULL) && (strcspn(nbeg - 1, ":,; \t") != 0)) { |
836 | | /* It could be somethingname= instead of name= */ |
837 | 0 | nbeg = strstr(nbeg + 1, "name=\""); |
838 | 0 | } |
839 | | |
840 | | /* This line is not required, but otherwise some compilers |
841 | | * generate spurious warnings. */ |
842 | 0 | nend = nbeg; |
843 | | /* And others complain, the result is unused. */ |
844 | 0 | (void)nend; |
845 | | |
846 | | /* If name=" is found, search for the closing " */ |
847 | 0 | if (nbeg) { |
848 | 0 | nbeg += 6; |
849 | 0 | nend = strchr(nbeg, '\"'); |
850 | 0 | if (!nend) { |
851 | | /* Malformed request */ |
852 | 0 | mg_free(boundary); |
853 | 0 | return -1; |
854 | 0 | } |
855 | 0 | } else { |
856 | | /* name= without quotes is also allowed */ |
857 | 0 | nbeg = strstr(content_disp, "name="); |
858 | 0 | while ((nbeg != NULL) && (strcspn(nbeg - 1, ":,; \t") != 0)) { |
859 | | /* It could be somethingname= instead of name= */ |
860 | 0 | nbeg = strstr(nbeg + 1, "name="); |
861 | 0 | } |
862 | 0 | if (!nbeg) { |
863 | | /* Malformed request */ |
864 | 0 | mg_free(boundary); |
865 | 0 | return -1; |
866 | 0 | } |
867 | 0 | nbeg += 5; |
868 | | |
869 | | /* RFC 2616 Sec. 2.2 defines a list of allowed |
870 | | * separators, but many of them make no sense |
871 | | * here, e.g. various brackets or slashes. |
872 | | * If they are used, probably someone is |
873 | | * trying to attack with curious hand made |
874 | | * requests. Only ; , space and tab seem to be |
875 | | * reasonable here. Ignore everything else. */ |
876 | 0 | nend = nbeg + strcspn(nbeg, ",; \t"); |
877 | 0 | } |
878 | | |
879 | | /* Get the optional filename="..." part of the Content-Disposition |
880 | | * header. */ |
881 | 0 | fbeg = strstr(content_disp, "filename=\""); |
882 | 0 | while ((fbeg != NULL) && (strcspn(fbeg - 1, ":,; \t") != 0)) { |
883 | | /* It could be somethingfilename= instead of filename= */ |
884 | 0 | fbeg = strstr(fbeg + 1, "filename=\""); |
885 | 0 | } |
886 | | |
887 | | /* This line is not required, but otherwise some compilers |
888 | | * generate spurious warnings. */ |
889 | 0 | fend = fbeg; |
890 | | |
891 | | /* If filename=" is found, search for the closing " */ |
892 | 0 | if (fbeg) { |
893 | 0 | fbeg += 10; |
894 | 0 | fend = strchr(fbeg, '\"'); |
895 | |
|
896 | 0 | if (!fend) { |
897 | | /* Malformed request (the filename field is optional, but if |
898 | | * it exists, it needs to be terminated correctly). */ |
899 | 0 | mg_free(boundary); |
900 | 0 | return -1; |
901 | 0 | } |
902 | | |
903 | | /* TODO: check Content-Type */ |
904 | | /* Content-Type: application/octet-stream */ |
905 | 0 | } |
906 | 0 | if (!fbeg) { |
907 | | /* Try the same without quotes */ |
908 | 0 | fbeg = strstr(content_disp, "filename="); |
909 | 0 | while ((fbeg != NULL) && (strcspn(fbeg - 1, ":,; \t") != 0)) { |
910 | | /* It could be somethingfilename= instead of filename= */ |
911 | 0 | fbeg = strstr(fbeg + 1, "filename="); |
912 | 0 | } |
913 | 0 | if (fbeg) { |
914 | 0 | fbeg += 9; |
915 | 0 | fend = fbeg + strcspn(fbeg, ",; \t"); |
916 | 0 | } |
917 | 0 | } |
918 | |
|
919 | 0 | if (!fbeg || !fend) { |
920 | 0 | fbeg = NULL; |
921 | 0 | fend = NULL; |
922 | 0 | fnlen = 0; |
923 | 0 | } else { |
924 | 0 | fnlen = (size_t)(fend - fbeg); |
925 | 0 | } |
926 | | |
927 | | /* In theory, it could be possible that someone crafts |
928 | | * a request like name=filename=xyz. Check if name and |
929 | | * filename do not overlap. */ |
930 | 0 | if (!(((ptrdiff_t)fbeg > (ptrdiff_t)nend) |
931 | 0 | || ((ptrdiff_t)nbeg > (ptrdiff_t)fend))) { |
932 | 0 | mg_free(boundary); |
933 | 0 | return -1; |
934 | 0 | } |
935 | | |
936 | | /* Call callback for new field */ |
937 | 0 | memset(path, 0, sizeof(path)); |
938 | 0 | field_count++; |
939 | 0 | field_storage = url_encoded_field_found(conn, |
940 | 0 | nbeg, |
941 | 0 | (size_t)(nend - nbeg), |
942 | 0 | ((fnlen > 0) ? fbeg : NULL), |
943 | 0 | fnlen, |
944 | 0 | path, |
945 | 0 | sizeof(path) - 1, |
946 | 0 | fdh); |
947 | | |
948 | | /* If the boundary is already in the buffer, get the address, |
949 | | * otherwise next will be NULL. */ |
950 | 0 | next = search_boundary(hbuf, |
951 | 0 | (size_t)((buf - hbuf) + buf_fill), |
952 | 0 | boundary, |
953 | 0 | bl); |
954 | |
|
955 | 0 | #if !defined(NO_FILESYSTEMS) |
956 | 0 | if (field_storage == MG_FORM_FIELD_STORAGE_STORE) { |
957 | | /* Store the content to a file */ |
958 | 0 | if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) { |
959 | 0 | fstore.access.fp = NULL; |
960 | 0 | } |
961 | 0 | file_size = 0; |
962 | |
|
963 | 0 | if (!fstore.access.fp) { |
964 | 0 | mg_cry_internal(conn, |
965 | 0 | "%s: Cannot create file %s", |
966 | 0 | __func__, |
967 | 0 | path); |
968 | 0 | } |
969 | 0 | } |
970 | 0 | #endif /* NO_FILESYSTEMS */ |
971 | |
|
972 | 0 | get_block = 0; |
973 | 0 | while (!next) { |
974 | | /* Set "towrite" to the number of bytes available |
975 | | * in the buffer */ |
976 | 0 | towrite = (size_t)(buf - hend + buf_fill); |
977 | |
|
978 | 0 | if (towrite < bl + 4) { |
979 | | /* Not enough data stored. */ |
980 | | /* Incomplete request. */ |
981 | 0 | mg_free(boundary); |
982 | 0 | return -1; |
983 | 0 | } |
984 | | |
985 | | /* Subtract the boundary length, to deal with |
986 | | * cases the boundary is only partially stored |
987 | | * in the buffer. */ |
988 | 0 | towrite -= bl + 4; |
989 | |
|
990 | 0 | if (field_storage == MG_FORM_FIELD_STORAGE_GET) { |
991 | 0 | r = unencoded_field_get(conn, |
992 | 0 | ((get_block > 0) ? NULL : nbeg), |
993 | 0 | ((get_block > 0) |
994 | 0 | ? 0 |
995 | 0 | : (size_t)(nend - nbeg)), |
996 | 0 | hend, |
997 | 0 | towrite, |
998 | 0 | fdh); |
999 | 0 | get_block++; |
1000 | 0 | if (r == MG_FORM_FIELD_HANDLE_ABORT) { |
1001 | | /* Stop request handling */ |
1002 | 0 | abort_read = 1; |
1003 | 0 | break; |
1004 | 0 | } |
1005 | 0 | if (r == MG_FORM_FIELD_HANDLE_NEXT) { |
1006 | | /* Skip to next field */ |
1007 | 0 | field_storage = MG_FORM_FIELD_STORAGE_SKIP; |
1008 | 0 | } |
1009 | 0 | } |
1010 | | |
1011 | 0 | #if !defined(NO_FILESYSTEMS) |
1012 | 0 | if (field_storage == MG_FORM_FIELD_STORAGE_STORE) { |
1013 | 0 | if (fstore.access.fp) { |
1014 | | |
1015 | | /* Store the content of the buffer. */ |
1016 | 0 | n = (size_t)fwrite(hend, 1, towrite, fstore.access.fp); |
1017 | 0 | if ((n != towrite) || (ferror(fstore.access.fp))) { |
1018 | 0 | mg_cry_internal(conn, |
1019 | 0 | "%s: Cannot write file %s", |
1020 | 0 | __func__, |
1021 | 0 | path); |
1022 | 0 | mg_fclose(&fstore.access); |
1023 | 0 | remove_bad_file(conn, path); |
1024 | 0 | } |
1025 | 0 | file_size += (int64_t)n; |
1026 | 0 | } |
1027 | 0 | } |
1028 | 0 | #endif /* NO_FILESYSTEMS */ |
1029 | |
|
1030 | 0 | memmove(buf, hend + towrite, bl + 4); |
1031 | 0 | buf_fill = bl + 4; |
1032 | 0 | hend = buf; |
1033 | | |
1034 | | /* Read new data */ |
1035 | 0 | to_read = sizeof(buf) - 1 - buf_fill; |
1036 | 0 | r = mg_read(conn, buf + buf_fill, to_read); |
1037 | 0 | if ((r < 0) || ((r == 0) && all_data_read)) { |
1038 | 0 | #if !defined(NO_FILESYSTEMS) |
1039 | | /* read error */ |
1040 | 0 | if (fstore.access.fp) { |
1041 | 0 | mg_fclose(&fstore.access); |
1042 | 0 | remove_bad_file(conn, path); |
1043 | 0 | } |
1044 | 0 | #endif /* NO_FILESYSTEMS */ |
1045 | 0 | mg_free(boundary); |
1046 | 0 | return -1; |
1047 | 0 | } |
1048 | | /* r==0 already handled, all_data_read is false here */ |
1049 | | |
1050 | 0 | buf_fill += r; |
1051 | 0 | buf[buf_fill] = 0; |
1052 | | /* buf_fill is at least 8 here */ |
1053 | | |
1054 | | /* Find boundary */ |
1055 | 0 | next = search_boundary(buf, buf_fill, boundary, bl); |
1056 | |
|
1057 | 0 | if (!next && (r == 0)) { |
1058 | | /* incomplete request */ |
1059 | 0 | all_data_read = 1; |
1060 | 0 | } |
1061 | 0 | } |
1062 | | |
1063 | 0 | towrite = (next ? (size_t)(next - hend) : 0); |
1064 | |
|
1065 | 0 | if (field_storage == MG_FORM_FIELD_STORAGE_GET) { |
1066 | | /* Call callback */ |
1067 | 0 | r = unencoded_field_get(conn, |
1068 | 0 | ((get_block > 0) ? NULL : nbeg), |
1069 | 0 | ((get_block > 0) |
1070 | 0 | ? 0 |
1071 | 0 | : (size_t)(nend - nbeg)), |
1072 | 0 | hend, |
1073 | 0 | towrite, |
1074 | 0 | fdh); |
1075 | 0 | if (r == MG_FORM_FIELD_HANDLE_ABORT) { |
1076 | | /* Stop request handling */ |
1077 | 0 | abort_read = 1; |
1078 | 0 | break; |
1079 | 0 | } |
1080 | 0 | if (r == MG_FORM_FIELD_HANDLE_NEXT) { |
1081 | | /* Skip to next field */ |
1082 | 0 | field_storage = MG_FORM_FIELD_STORAGE_SKIP; |
1083 | 0 | } |
1084 | 0 | } |
1085 | | |
1086 | 0 | #if !defined(NO_FILESYSTEMS) |
1087 | 0 | if (field_storage == MG_FORM_FIELD_STORAGE_STORE) { |
1088 | |
|
1089 | 0 | if (fstore.access.fp) { |
1090 | 0 | n = (size_t)fwrite(hend, 1, towrite, fstore.access.fp); |
1091 | 0 | if ((n != towrite) || (ferror(fstore.access.fp))) { |
1092 | 0 | mg_cry_internal(conn, |
1093 | 0 | "%s: Cannot write file %s", |
1094 | 0 | __func__, |
1095 | 0 | path); |
1096 | 0 | mg_fclose(&fstore.access); |
1097 | 0 | remove_bad_file(conn, path); |
1098 | 0 | } else { |
1099 | 0 | file_size += (int64_t)n; |
1100 | 0 | r = mg_fclose(&fstore.access); |
1101 | 0 | if (r == 0) { |
1102 | | /* stored successfully */ |
1103 | 0 | r = field_stored(conn, path, file_size, fdh); |
1104 | 0 | if (r == MG_FORM_FIELD_HANDLE_ABORT) { |
1105 | | /* Stop request handling */ |
1106 | 0 | abort_read = 1; |
1107 | 0 | break; |
1108 | 0 | } |
1109 | 0 | } else { |
1110 | 0 | mg_cry_internal(conn, |
1111 | 0 | "%s: Error saving file %s", |
1112 | 0 | __func__, |
1113 | 0 | path); |
1114 | 0 | remove_bad_file(conn, path); |
1115 | 0 | } |
1116 | 0 | } |
1117 | 0 | fstore.access.fp = NULL; |
1118 | 0 | } |
1119 | 0 | } |
1120 | 0 | #endif /* NO_FILESYSTEMS */ |
1121 | | |
1122 | 0 | if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT) |
1123 | 0 | == MG_FORM_FIELD_STORAGE_ABORT) { |
1124 | | /* Stop parsing the request */ |
1125 | 0 | abort_read = 1; |
1126 | 0 | break; |
1127 | 0 | } |
1128 | | |
1129 | | /* Remove from the buffer */ |
1130 | 0 | if (next) { |
1131 | 0 | used = next - buf + 2; |
1132 | 0 | memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used); |
1133 | 0 | buf_fill -= used; |
1134 | 0 | } else { |
1135 | 0 | buf_fill = 0; |
1136 | 0 | } |
1137 | 0 | } |
1138 | | |
1139 | | /* All parts handled */ |
1140 | 0 | mg_free(boundary); |
1141 | 0 | return field_count; |
1142 | 0 | } |
1143 | | |
1144 | | /* Unknown Content-Type */ |
1145 | 0 | return -1; |
1146 | 0 | } |
1147 | | |
1148 | | /* End of handle_form.inl */ |