1 /* Copyright (c) 2016-2021 the Civetweb developers
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:
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
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
23 url_encoded_field_found(const struct mg_connection *conn,
30 struct mg_form_data_handler *fdh)
33 char filename_dec[1024];
39 mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
41 if (((size_t)key_dec_len >= (size_t)sizeof(key_dec)) || (key_dec_len < 0)) {
42 return MG_FORM_FIELD_STORAGE_SKIP;
46 filename_dec_len = mg_url_decode(filename,
49 (int)sizeof(filename_dec),
52 if (((size_t)filename_dec_len >= (size_t)sizeof(filename_dec))
53 || (filename_dec_len < 0)) {
54 /* Log error message and skip this field. */
55 mg_cry_internal(conn, "%s: Cannot decode filename", __func__);
56 return MG_FORM_FIELD_STORAGE_SKIP;
58 remove_dot_segments(filename_dec);
65 fdh->field_found(key_dec, filename_dec, path, path_len, fdh->user_data);
67 if ((ret & 0xF) == MG_FORM_FIELD_STORAGE_GET) {
68 if (fdh->field_get == NULL) {
70 "%s: Function \"Get\" not available",
72 return MG_FORM_FIELD_STORAGE_SKIP;
75 if ((ret & 0xF) == MG_FORM_FIELD_STORAGE_STORE) {
76 if (fdh->field_store == NULL) {
78 "%s: Function \"Store\" not available",
80 return MG_FORM_FIELD_STORAGE_SKIP;
88 url_encoded_field_get(
89 const struct mg_connection *conn,
93 size_t *value_len, /* IN: number of bytes available in "value", OUT: number
95 struct mg_form_data_handler *fdh)
99 char *value_dec = (char *)mg_malloc_ctx(*value_len + 1, conn->phys_ctx);
100 int value_dec_len, ret;
103 /* Log error message and stop parsing the form data. */
104 mg_cry_internal(conn,
105 "%s: Not enough memory (required: %lu)",
107 (unsigned long)(*value_len + 1));
108 return MG_FORM_FIELD_STORAGE_ABORT;
111 mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
113 if (*value_len >= 2 && value[*value_len - 2] == '%')
115 else if (*value_len >= 1 && value[*value_len - 1] == '%')
117 value_dec_len = mg_url_decode(
118 value, (int)*value_len, value_dec, ((int)*value_len) + 1, 1);
120 ret = fdh->field_get(key_dec,
122 (size_t)value_dec_len,
131 unencoded_field_get(const struct mg_connection *conn,
136 struct mg_form_data_handler *fdh)
141 mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);
143 return fdh->field_get(key_dec, value, value_len, fdh->user_data);
147 field_stored(const struct mg_connection *conn,
150 struct mg_form_data_handler *fdh)
152 /* Equivalent to "upload" callback of "mg_upload". */
154 (void)conn; /* we do not need mg_cry here, so conn is currently unused */
156 return fdh->field_store(path, file_size, fdh->user_data);
160 search_boundary(const char *buf,
162 const char *boundary,
165 /* We must do a binary search here, not a string search, since the buffer
166 * may contain '\x00' bytes, if binary data is transferred. */
167 int clen = (int)buf_len - (int)boundary_len - 4;
170 for (i = 0; i <= clen; i++) {
171 if (!memcmp(buf + i, "\r\n--", 4)) {
172 if (!memcmp(buf + i + 4, boundary, boundary_len)) {
181 mg_handle_form_request(struct mg_connection *conn,
182 struct mg_form_data_handler *fdh)
184 const char *content_type;
186 char buf[MG_BUF_LEN]; /* Must not be smaller than ~900 */
191 struct mg_file fstore = STRUCT_FILE_INITIALIZER;
192 int64_t file_size = 0; /* init here, to a avoid a false positive
193 "uninitialized variable used" warning */
196 (conn->request_info.content_length > 0) || (conn->is_chunked);
198 /* Unused without filesystems */
202 /* There are three ways to encode data from a HTML form:
203 * 1) method: GET (default)
204 * The form data is in the HTTP query string.
205 * 2) method: POST, enctype: "application/x-www-form-urlencoded"
206 * The form data is in the request body.
207 * The body is url encoded (the default encoding for POST).
208 * 3) method: POST, enctype: "multipart/form-data".
209 * The form data is in the request body of a multipart message.
210 * This is the typical way to handle file upload from a form.
213 if (!has_body_data) {
216 if (0 != strcmp(conn->request_info.request_method, "GET")) {
217 /* No body data, but not a GET request.
218 * This is not a valid form request. */
222 /* GET request: form data is in the query string. */
223 /* The entire data has already been loaded, so there is no nead to
224 * call mg_read. We just need to split the query string into key-value
226 data = conn->request_info.query_string;
228 /* No query string. */
232 /* Split data in a=1&b=xy&c=3&c=4 ... */
234 const char *val = strchr(data, '=');
236 ptrdiff_t keylen, vallen;
243 /* In every "field_found" callback we ask what to do with the
244 * data ("field_storage"). This could be:
245 * MG_FORM_FIELD_STORAGE_SKIP (0):
246 * ignore the value of this field
247 * MG_FORM_FIELD_STORAGE_GET (1):
248 * read the data and call the get callback function
249 * MG_FORM_FIELD_STORAGE_STORE (2):
250 * store the data in a file
251 * MG_FORM_FIELD_STORAGE_READ (3):
252 * let the user read the data (for parsing long data on the fly)
253 * MG_FORM_FIELD_STORAGE_ABORT (flag):
256 memset(path, 0, sizeof(path));
258 field_storage = url_encoded_field_found(conn,
268 next = strchr(val, '&');
272 vallen = (ptrdiff_t)strlen(val);
275 if (field_storage == MG_FORM_FIELD_STORAGE_GET) {
277 r = url_encoded_field_get(
278 conn, data, (size_t)keylen, val, (size_t *)&vallen, fdh);
279 if (r == MG_FORM_FIELD_HANDLE_ABORT) {
280 /* Stop request handling */
283 if (r == MG_FORM_FIELD_HANDLE_NEXT) {
284 /* Skip to next field */
285 field_storage = MG_FORM_FIELD_STORAGE_SKIP;
292 /* vallen may have been modified by url_encoded_field_get */
296 #if !defined(NO_FILESYSTEMS)
297 if (field_storage == MG_FORM_FIELD_STORAGE_STORE) {
298 /* Store the content to a file */
299 if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) {
300 fstore.access.fp = NULL;
303 if (fstore.access.fp != NULL) {
305 fwrite(val, 1, (size_t)vallen, fstore.access.fp);
306 if ((n != (size_t)vallen) || (ferror(fstore.access.fp))) {
307 mg_cry_internal(conn,
308 "%s: Cannot write file %s",
311 (void)mg_fclose(&fstore.access);
312 remove_bad_file(conn, path);
314 file_size += (int64_t)n;
316 if (fstore.access.fp) {
317 r = mg_fclose(&fstore.access);
319 /* stored successfully */
320 r = field_stored(conn, path, file_size, fdh);
321 if (r == MG_FORM_FIELD_HANDLE_ABORT) {
322 /* Stop request handling */
327 mg_cry_internal(conn,
328 "%s: Error saving file %s",
331 remove_bad_file(conn, path);
333 fstore.access.fp = NULL;
337 mg_cry_internal(conn,
338 "%s: Cannot create file %s",
343 #endif /* NO_FILESYSTEMS */
345 /* if (field_storage == MG_FORM_FIELD_STORAGE_READ) { */
346 /* The idea of "field_storage=read" is to let the API user read
347 * data chunk by chunk and to some data processing on the fly.
348 * This should avoid the need to store data in the server:
349 * It should neither be stored in memory, like
350 * "field_storage=get" does, nor in a file like
351 * "field_storage=store".
352 * However, for a "GET" request this does not make any much
353 * sense, since the data is already stored in memory, as it is
354 * part of the query string.
358 if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
359 == MG_FORM_FIELD_STORAGE_ABORT) {
360 /* Stop parsing the request */
364 /* Proceed to next entry */
371 content_type = mg_get_header(conn, "Content-Type");
374 || !mg_strncasecmp(content_type,
375 "APPLICATION/X-WWW-FORM-URLENCODED",
377 || !mg_strncasecmp(content_type,
378 "APPLICATION/WWW-FORM-URLENCODED",
380 /* The form data is in the request body data, encoded in key/value
382 int all_data_read = 0;
384 /* Read body data and split it in keys and values.
385 * The encoding is like in the "GET" case above: a=1&b&c=3&c=4.
386 * Here we use "POST", and read the data from the request body.
387 * The data read on the fly, so it is not required to buffer the
388 * entire request in memory before processing it. */
392 ptrdiff_t keylen, vallen;
394 int end_of_key_value_pair_found = 0;
397 if ((size_t)buf_fill < (sizeof(buf) - 1)) {
399 size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill;
400 r = mg_read(conn, buf + (size_t)buf_fill, to_read);
401 if ((r < 0) || ((r == 0) && all_data_read)) {
406 /* TODO: Create a function to get "all_data_read" from
407 * the conn object. All data is read if the Content-Length
408 * has been reached, or if chunked encoding is used and
409 * the end marker has been read, or if the connection has
411 all_data_read = (buf_fill == 0);
420 val = strchr(buf, '=');
429 memset(path, 0, sizeof(path));
431 field_storage = url_encoded_field_found(conn,
440 if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
441 == MG_FORM_FIELD_STORAGE_ABORT) {
442 /* Stop parsing the request */
446 #if !defined(NO_FILESYSTEMS)
447 if (field_storage == MG_FORM_FIELD_STORAGE_STORE) {
448 if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) {
449 fstore.access.fp = NULL;
452 if (!fstore.access.fp) {
453 mg_cry_internal(conn,
454 "%s: Cannot create file %s",
459 #endif /* NO_FILESYSTEMS */
462 /* Loop to read values larger than sizeof(buf)-keylen-2 */
464 next = strchr(val, '&');
467 end_of_key_value_pair_found = 1;
469 vallen = (ptrdiff_t)strlen(val);
470 end_of_key_value_pair_found = all_data_read;
473 if (field_storage == MG_FORM_FIELD_STORAGE_GET) {
475 if (!end_of_key_value_pair_found && !all_data_read) {
476 /* This callback will deliver partial contents */
481 r = url_encoded_field_get(conn,
482 ((get_block > 0) ? NULL : buf),
490 if (r == MG_FORM_FIELD_HANDLE_ABORT) {
491 /* Stop request handling */
494 if (r == MG_FORM_FIELD_HANDLE_NEXT) {
495 /* Skip to next field */
496 field_storage = MG_FORM_FIELD_STORAGE_SKIP;
503 /* vallen may have been modified by url_encoded_field_get */
507 #if !defined(NO_FILESYSTEMS)
508 if (fstore.access.fp) {
510 fwrite(val, 1, (size_t)vallen, fstore.access.fp);
511 if ((n != (size_t)vallen) || (ferror(fstore.access.fp))) {
512 mg_cry_internal(conn,
513 "%s: Cannot write file %s",
516 mg_fclose(&fstore.access);
517 remove_bad_file(conn, path);
519 file_size += (int64_t)n;
521 #endif /* NO_FILESYSTEMS */
523 if (!end_of_key_value_pair_found) {
527 sizeof(buf) - (size_t)used);
529 buf_fill -= (int)used;
530 if ((size_t)buf_fill < (sizeof(buf) - 1)) {
532 size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill;
533 r = mg_read(conn, buf + (size_t)buf_fill, to_read);
534 if ((r < 0) || ((r == 0) && all_data_read)) {
535 #if !defined(NO_FILESYSTEMS)
537 if (fstore.access.fp) {
538 mg_fclose(&fstore.access);
539 remove_bad_file(conn, path);
542 #endif /* NO_FILESYSTEMS */
545 /* TODO: Create a function to get "all_data_read"
546 * from the conn object. All data is read if the
547 * Content-Length has been reached, or if chunked
548 * encoding is used and the end marker has been
549 * read, or if the connection has been closed. */
550 all_data_read = (buf_fill == 0);
561 } while (!end_of_key_value_pair_found);
563 #if !defined(NO_FILESYSTEMS)
564 if (fstore.access.fp) {
565 r = mg_fclose(&fstore.access);
567 /* stored successfully */
568 r = field_stored(conn, path, file_size, fdh);
569 if (r == MG_FORM_FIELD_HANDLE_ABORT) {
570 /* Stop request handling */
574 mg_cry_internal(conn,
575 "%s: Error saving file %s",
578 remove_bad_file(conn, path);
580 fstore.access.fp = NULL;
582 #endif /* NO_FILESYSTEMS */
584 if (all_data_read && (buf_fill == 0)) {
585 /* nothing more to process */
589 /* Proceed to next entry */
591 memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used);
592 buf_fill -= (int)used;
598 if (!mg_strncasecmp(content_type, "MULTIPART/FORM-DATA;", 20)) {
599 /* The form data is in the request body data, encoded as multipart
600 * content (see https://www.ietf.org/rfc/rfc1867.txt,
601 * https://www.ietf.org/rfc/rfc2388.txt). */
605 struct mg_request_info part_header;
607 const char *content_disp, *hend, *fbeg, *fend, *nbeg, *nend;
610 int all_data_read = 0;
612 memset(&part_header, 0, sizeof(part_header));
614 /* Skip all spaces between MULTIPART/FORM-DATA; and BOUNDARY= */
616 while (content_type[bl] == ' ') {
620 /* There has to be a BOUNDARY definition in the Content-Type header */
621 if (mg_strncasecmp(content_type + bl, "BOUNDARY=", 9)) {
622 /* Malformed request */
626 /* Copy boundary string to variable "boundary" */
627 fbeg = content_type + bl + 9;
629 boundary = (char *)mg_malloc(bl + 1);
632 mg_cry_internal(conn,
633 "%s: Cannot allocate memory for boundary [%lu]",
638 memcpy(boundary, fbeg, bl);
641 /* RFC 2046 permits the boundary string to be quoted. */
642 /* If the boundary is quoted, trim the quotes */
643 if (boundary[0] == '"') {
644 hbuf = strchr(boundary + 1, '"');
645 if ((!hbuf) || (*hbuf != '"')) {
646 /* Malformed request */
651 memmove(boundary, boundary + 1, bl);
652 bl = strlen(boundary);
655 /* Do some sanity checks for boundary lengths */
658 * Boundary delimiters must not appear within the
659 * encapsulated material, and must be no longer
660 * than 70 characters, not counting the two
664 /* The algorithm can not work if bl >= sizeof(buf), or if buf
665 * can not hold the multipart header plus the boundary.
666 * Requests with long boundaries are not RFC compliant, maybe they
667 * are intended attacks to interfere with this algorithm. */
672 /* Sanity check: A boundary string of less than 4 bytes makes
673 * no sense either. */
678 for (part_no = 0;; part_no++) {
679 size_t towrite, fnlen, n;
681 size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill;
683 /* Unused without filesystems */
686 r = mg_read(conn, buf + (size_t)buf_fill, to_read);
687 if ((r < 0) || ((r == 0) && all_data_read)) {
693 all_data_read = (buf_fill == 0);
706 while ((d < buf_fill) && (buf[d] != '-')) {
709 if ((d > 0) && (buf[d] == '-')) {
710 memmove(buf, buf + d, (unsigned)buf_fill - (unsigned)d);
716 if (buf[0] != '-' || buf[1] != '-') {
717 /* Malformed request */
721 if (0 != strncmp(buf + 2, boundary, bl)) {
722 /* Malformed request */
726 if (buf[bl + 2] != '\r' || buf[bl + 3] != '\n') {
727 /* Every part must end with \r\n, if there is another part.
728 * The end of the request has an extra -- */
729 if (((size_t)buf_fill != (size_t)(bl + 6))
730 || (strncmp(buf + bl + 2, "--\r\n", 4))) {
731 /* Malformed request */
735 /* End of the request */
739 /* Next, we need to get the part header: Read until \r\n\r\n */
741 hend = strstr(hbuf, "\r\n\r\n");
743 /* Malformed request */
748 part_header.num_headers =
749 parse_http_headers(&hbuf, part_header.http_headers);
750 if ((hend + 2) != hbuf) {
751 /* Malformed request */
759 /* According to the RFC, every part has to have a header field like:
760 * Content-Disposition: form-data; name="..." */
761 content_disp = get_header(part_header.http_headers,
762 part_header.num_headers,
763 "Content-Disposition");
765 /* Malformed request */
770 /* Get the mandatory name="..." part of the Content-Disposition
772 nbeg = strstr(content_disp, "name=\"");
773 while ((nbeg != NULL) && (strcspn(nbeg - 1, ":,; \t") != 0)) {
774 /* It could be somethingname= instead of name= */
775 nbeg = strstr(nbeg + 1, "name=\"");
778 /* This line is not required, but otherwise some compilers
779 * generate spurious warnings. */
781 /* And others complain, the result is unused. */
784 /* If name=" is found, search for the closing " */
787 nend = strchr(nbeg, '\"');
789 /* Malformed request */
794 /* name= without quotes is also allowed */
795 nbeg = strstr(content_disp, "name=");
796 while ((nbeg != NULL) && (strcspn(nbeg - 1, ":,; \t") != 0)) {
797 /* It could be somethingname= instead of name= */
798 nbeg = strstr(nbeg + 1, "name=");
801 /* Malformed request */
807 /* RFC 2616 Sec. 2.2 defines a list of allowed
808 * separators, but many of them make no sense
809 * here, e.g. various brackets or slashes.
810 * If they are used, probably someone is
811 * trying to attack with curious hand made
812 * requests. Only ; , space and tab seem to be
813 * reasonable here. Ignore everything else. */
814 nend = nbeg + strcspn(nbeg, ",; \t");
817 /* Get the optional filename="..." part of the Content-Disposition
819 fbeg = strstr(content_disp, "filename=\"");
820 while ((fbeg != NULL) && (strcspn(fbeg - 1, ":,; \t") != 0)) {
821 /* It could be somethingfilename= instead of filename= */
822 fbeg = strstr(fbeg + 1, "filename=\"");
825 /* This line is not required, but otherwise some compilers
826 * generate spurious warnings. */
829 /* If filename=" is found, search for the closing " */
832 fend = strchr(fbeg, '\"');
835 /* Malformed request (the filename field is optional, but if
836 * it exists, it needs to be terminated correctly). */
841 /* TODO: check Content-Type */
842 /* Content-Type: application/octet-stream */
845 /* Try the same without quotes */
846 fbeg = strstr(content_disp, "filename=");
847 while ((fbeg != NULL) && (strcspn(fbeg - 1, ":,; \t") != 0)) {
848 /* It could be somethingfilename= instead of filename= */
849 fbeg = strstr(fbeg + 1, "filename=");
853 fend = fbeg + strcspn(fbeg, ",; \t");
857 if (!fbeg || !fend) {
862 fnlen = (size_t)(fend - fbeg);
865 /* In theory, it could be possible that someone crafts
866 * a request like name=filename=xyz. Check if name and
867 * filename do not overlap. */
868 if (!(((ptrdiff_t)fbeg > (ptrdiff_t)nend)
869 || ((ptrdiff_t)nbeg > (ptrdiff_t)fend))) {
874 /* Call callback for new field */
875 memset(path, 0, sizeof(path));
877 field_storage = url_encoded_field_found(conn,
879 (size_t)(nend - nbeg),
880 ((fnlen > 0) ? fbeg : NULL),
886 /* If the boundary is already in the buffer, get the address,
887 * otherwise next will be NULL. */
888 next = search_boundary(hbuf,
889 (size_t)((buf - hbuf) + buf_fill),
893 #if !defined(NO_FILESYSTEMS)
894 if (field_storage == MG_FORM_FIELD_STORAGE_STORE) {
895 /* Store the content to a file */
896 if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) {
897 fstore.access.fp = NULL;
901 if (!fstore.access.fp) {
902 mg_cry_internal(conn,
903 "%s: Cannot create file %s",
908 #endif /* NO_FILESYSTEMS */
912 /* Set "towrite" to the number of bytes available
914 towrite = (size_t)(buf - hend + buf_fill);
916 if (towrite < bl + 4) {
917 /* Not enough data stored. */
918 /* Incomplete request. */
923 /* Subtract the boundary length, to deal with
924 * cases the boundary is only partially stored
928 if (field_storage == MG_FORM_FIELD_STORAGE_GET) {
929 r = unencoded_field_get(conn,
930 ((get_block > 0) ? NULL : nbeg),
933 : (size_t)(nend - nbeg)),
938 if (r == MG_FORM_FIELD_HANDLE_ABORT) {
939 /* Stop request handling */
942 if (r == MG_FORM_FIELD_HANDLE_NEXT) {
943 /* Skip to next field */
944 field_storage = MG_FORM_FIELD_STORAGE_SKIP;
948 #if !defined(NO_FILESYSTEMS)
949 if (field_storage == MG_FORM_FIELD_STORAGE_STORE) {
950 if (fstore.access.fp) {
952 /* Store the content of the buffer. */
953 n = (size_t)fwrite(hend, 1, towrite, fstore.access.fp);
954 if ((n != towrite) || (ferror(fstore.access.fp))) {
955 mg_cry_internal(conn,
956 "%s: Cannot write file %s",
959 mg_fclose(&fstore.access);
960 remove_bad_file(conn, path);
962 file_size += (int64_t)n;
965 #endif /* NO_FILESYSTEMS */
967 memmove(buf, hend + towrite, bl + 4);
968 buf_fill = (int)(bl + 4);
972 to_read = sizeof(buf) - 1 - (size_t)buf_fill;
973 r = mg_read(conn, buf + (size_t)buf_fill, to_read);
974 if ((r < 0) || ((r == 0) && all_data_read)) {
975 #if !defined(NO_FILESYSTEMS)
977 if (fstore.access.fp) {
978 mg_fclose(&fstore.access);
979 remove_bad_file(conn, path);
981 #endif /* NO_FILESYSTEMS */
985 /* r==0 already handled, all_data_read is false here */
989 /* buf_fill is at least 8 here */
992 next = search_boundary(buf, (size_t)buf_fill, boundary, bl);
994 if (!next && (r == 0)) {
995 /* incomplete request */
1000 towrite = (next ? (size_t)(next - hend) : 0);
1002 if (field_storage == MG_FORM_FIELD_STORAGE_GET) {
1004 r = unencoded_field_get(conn,
1005 ((get_block > 0) ? NULL : nbeg),
1008 : (size_t)(nend - nbeg)),
1012 if (r == MG_FORM_FIELD_HANDLE_ABORT) {
1013 /* Stop request handling */
1016 if (r == MG_FORM_FIELD_HANDLE_NEXT) {
1017 /* Skip to next field */
1018 field_storage = MG_FORM_FIELD_STORAGE_SKIP;
1022 #if !defined(NO_FILESYSTEMS)
1023 if (field_storage == MG_FORM_FIELD_STORAGE_STORE) {
1025 if (fstore.access.fp) {
1026 n = (size_t)fwrite(hend, 1, towrite, fstore.access.fp);
1027 if ((n != towrite) || (ferror(fstore.access.fp))) {
1028 mg_cry_internal(conn,
1029 "%s: Cannot write file %s",
1032 mg_fclose(&fstore.access);
1033 remove_bad_file(conn, path);
1035 file_size += (int64_t)n;
1036 r = mg_fclose(&fstore.access);
1038 /* stored successfully */
1039 r = field_stored(conn, path, file_size, fdh);
1040 if (r == MG_FORM_FIELD_HANDLE_ABORT) {
1041 /* Stop request handling */
1045 mg_cry_internal(conn,
1046 "%s: Error saving file %s",
1049 remove_bad_file(conn, path);
1052 fstore.access.fp = NULL;
1055 #endif /* NO_FILESYSTEMS */
1057 if ((field_storage & MG_FORM_FIELD_STORAGE_ABORT)
1058 == MG_FORM_FIELD_STORAGE_ABORT) {
1059 /* Stop parsing the request */
1063 /* Remove from the buffer */
1065 used = next - buf + 2;
1066 memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used);
1067 buf_fill -= (int)used;
1073 /* All parts handled */
1078 /* Unknown Content-Type */
1082 /* End of handle_form.inl */