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