/* Copyright (c) 2016 the Civetweb developers
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */


static int
url_encoded_field_found(const struct mg_connection *conn,
                        const char *key,
                        size_t key_len,
                        const char *filename,
                        size_t filename_len,
                        char *path,
                        size_t path_len,
                        struct mg_form_data_handler *fdh)
{
	char key_dec[1024];
	char filename_dec[1024];
	int key_dec_len;
	int filename_dec_len;
	int ret;

	key_dec_len =
	    mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);

	if (((size_t)key_dec_len >= (size_t)sizeof(key_dec)) || (key_dec_len < 0)) {
		return FORM_FIELD_STORAGE_SKIP;
	}

	if (filename) {
		filename_dec_len = mg_url_decode(filename,
		                                 (int)filename_len,
		                                 filename_dec,
		                                 (int)sizeof(filename_dec),
		                                 1);

		if (((size_t)filename_dec_len >= (size_t)sizeof(filename_dec))
		    || (filename_dec_len < 0)) {
			/* Log error message and skip this field. */
			mg_cry(conn, "%s: Cannot decode filename", __func__);
			return FORM_FIELD_STORAGE_SKIP;
		}
	} else {
		filename_dec[0] = 0;
	}

	ret =
	    fdh->field_found(key_dec, filename_dec, path, path_len, fdh->user_data);

	if ((ret & 0xF) == FORM_FIELD_STORAGE_GET) {
		if (fdh->field_get == NULL) {
			mg_cry(conn, "%s: Function \"Get\" not available", __func__);
			return FORM_FIELD_STORAGE_SKIP;
		}
	}
	if ((ret & 0xF) == FORM_FIELD_STORAGE_STORE) {
		if (fdh->field_store == NULL) {
			mg_cry(conn, "%s: Function \"Store\" not available", __func__);
			return FORM_FIELD_STORAGE_SKIP;
		}
	}

	return ret;
}


static int
url_encoded_field_get(const struct mg_connection *conn,
                      const char *key,
                      size_t key_len,
                      const char *value,
                      size_t value_len,
                      struct mg_form_data_handler *fdh)
{
	char key_dec[1024];

	char *value_dec = mg_malloc(value_len + 1);
	int value_dec_len, ret;

	if (!value_dec) {
		/* Log error message and stop parsing the form data. */
		mg_cry(conn,
		       "%s: Not enough memory (required: %lu)",
		       __func__,
		       (unsigned long)(value_len + 1));
		return FORM_FIELD_STORAGE_ABORT;
	}

	mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);

	value_dec_len =
	    mg_url_decode(value, (int)value_len, value_dec, (int)value_len + 1, 1);

	ret = fdh->field_get(key_dec,
	                     value_dec,
	                     (size_t)value_dec_len,
	                     fdh->user_data);

	mg_free(value_dec);

	return ret;
}


static int
unencoded_field_get(const struct mg_connection *conn,
                    const char *key,
                    size_t key_len,
                    const char *value,
                    size_t value_len,
                    struct mg_form_data_handler *fdh)
{
	char key_dec[1024];
	(void)conn;

	mg_url_decode(key, (int)key_len, key_dec, (int)sizeof(key_dec), 1);

	return fdh->field_get(key_dec, value, value_len, fdh->user_data);
}


static int
field_stored(const struct mg_connection *conn,
             const char *path,
             long long file_size,
             struct mg_form_data_handler *fdh)
{
	/* Equivalent to "upload" callback of "mg_upload". */

	(void)conn; /* we do not need mg_cry here, so conn is currently unused */

	return fdh->field_store(path, file_size, fdh->user_data);
}


static const char *
search_boundary(const char *buf,
                size_t buf_len,
                const char *boundary,
                size_t boundary_len)
{
	/* We must do a binary search here, not a string search, since the buffer
	 * may contain '\x00' bytes, if binary data is transferred. */
	int clen = (int)buf_len - (int)boundary_len - 4;
	int i;

	for (i = 0; i <= clen; i++) {
		if (!memcmp(buf + i, "\r\n--", 4)) {
			if (!memcmp(buf + i + 4, boundary, boundary_len)) {
				return buf + i;
			}
		}
	}
	return NULL;
}


int
mg_handle_form_request(struct mg_connection *conn,
                       struct mg_form_data_handler *fdh)
{
	const char *content_type;
	char path[512];
	char buf[1024];
	int field_storage;
	int buf_fill = 0;
	int r;
	int field_count = 0;
	struct mg_file fstore = STRUCT_FILE_INITIALIZER;
	int64_t file_size = 0; /* init here, to a avoid a false positive
	                         "uninitialized variable used" warning */

	int has_body_data =
	    (conn->request_info.content_length > 0) || (conn->is_chunked);

	/* There are three ways to encode data from a HTML form:
	 * 1) method: GET (default)
	 *    The form data is in the HTTP query string.
	 * 2) method: POST, enctype: "application/x-www-form-urlencoded"
	 *    The form data is in the request body.
	 *    The body is url encoded (the default encoding for POST).
	 * 3) method: POST, enctype: "multipart/form-data".
	 *    The form data is in the request body of a multipart message.
	 *    This is the typical way to handle file upload from a form.
	 */

	if (!has_body_data) {
		const char *data;

		if (strcmp(conn->request_info.request_method, "GET")) {
			/* No body data, but not a GET request.
			 * This is not a valid form request. */
			return -1;
		}

		/* GET request: form data is in the query string. */
		/* The entire data has already been loaded, so there is no nead to
		 * call mg_read. We just need to split the query string into key-value
		 * pairs. */
		data = conn->request_info.query_string;
		if (!data) {
			/* No query string. */
			return -1;
		}

		/* Split data in a=1&b=xy&c=3&c=4 ... */
		while (*data) {
			const char *val = strchr(data, '=');
			const char *next;
			ptrdiff_t keylen, vallen;

			if (!val) {
				break;
			}
			keylen = val - data;

			/* In every "field_found" callback we ask what to do with the
			 * data ("field_storage"). This could be:
			 * FORM_FIELD_STORAGE_SKIP (0) ... ignore the value of this field
			 * FORM_FIELD_STORAGE_GET (1) ... read the data and call the get
			 *                              callback function
			 * FORM_FIELD_STORAGE_STORE (2) ... store the data in a file
			 * FORM_FIELD_STORAGE_READ (3) ... let the user read the data
			 *                               (for parsing long data on the fly)
			 *                               (currently not implemented)
			 * FORM_FIELD_STORAGE_ABORT (flag) ... stop parsing
			 */
			memset(path, 0, sizeof(path));
			field_count++;
			field_storage = url_encoded_field_found(conn,
			                                        data,
			                                        (size_t)keylen,
			                                        NULL,
			                                        0,
			                                        path,
			                                        sizeof(path) - 1,
			                                        fdh);

			val++;
			next = strchr(val, '&');
			if (next) {
				vallen = next - val;
				next++;
			} else {
				vallen = (ptrdiff_t)strlen(val);
				next = val + vallen;
			}

			if (field_storage == FORM_FIELD_STORAGE_GET) {
				/* Call callback */
				url_encoded_field_get(
				    conn, data, (size_t)keylen, val, (size_t)vallen, fdh);
			}
			if (field_storage == FORM_FIELD_STORAGE_STORE) {
				/* Store the content to a file */
				if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) {
					fstore.access.fp = NULL;
				}
				file_size = 0;
				if (fstore.access.fp != NULL) {
					size_t n = (size_t)
					    fwrite(val, 1, (size_t)vallen, fstore.access.fp);
					if ((n != (size_t)vallen) || (ferror(fstore.access.fp))) {
						mg_cry(conn,
						       "%s: Cannot write file %s",
						       __func__,
						       path);
						(void)mg_fclose(&fstore.access);
						remove_bad_file(conn, path);
					}
					file_size += (int64_t)n;

					if (fstore.access.fp) {
						r = mg_fclose(&fstore.access);
						if (r == 0) {
							/* stored successfully */
							field_stored(conn, path, file_size, fdh);
						} else {
							mg_cry(conn,
							       "%s: Error saving file %s",
							       __func__,
							       path);
							remove_bad_file(conn, path);
						}
						fstore.access.fp = NULL;
					}

				} else {
					mg_cry(conn, "%s: Cannot create file %s", __func__, path);
				}
			}

			/* if (field_storage == FORM_FIELD_STORAGE_READ) { */
			/* The idea of "field_storage=read" is to let the API user read
			 * data chunk by chunk and to some data processing on the fly.
			 * This should avoid the need to store data in the server:
			 * It should neither be stored in memory, like
			 * "field_storage=get" does, nor in a file like
			 * "field_storage=store".
			 * However, for a "GET" request this does not make any much
			 * sense, since the data is already stored in memory, as it is
			 * part of the query string.
			 */
			/* } */

			if ((field_storage & FORM_FIELD_STORAGE_ABORT)
			    == FORM_FIELD_STORAGE_ABORT) {
				/* Stop parsing the request */
				break;
			}

			/* Proceed to next entry */
			data = next;
		}

		return field_count;
	}

	content_type = mg_get_header(conn, "Content-Type");

	if (!content_type
	    || !mg_strcasecmp(content_type, "APPLICATION/X-WWW-FORM-URLENCODED")
	    || !mg_strcasecmp(content_type, "APPLICATION/WWW-FORM-URLENCODED")) {
		/* The form data is in the request body data, encoded in key/value
		 * pairs. */
		int all_data_read = 0;

		/* Read body data and split it in keys and values.
		 * The encoding is like in the "GET" case above: a=1&b&c=3&c=4.
		 * Here we use "POST", and read the data from the request body.
		 * The data read on the fly, so it is not required to buffer the
		 * entire request in memory before processing it. */
		for (;;) {
			const char *val;
			const char *next;
			ptrdiff_t keylen, vallen;
			ptrdiff_t used;
			int end_of_key_value_pair_found = 0;
			int get_block;

			if ((size_t)buf_fill < (sizeof(buf) - 1)) {

				size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill;
				r = mg_read(conn, buf + (size_t)buf_fill, to_read);
				if (r < 0) {
					/* read error */
					return -1;
				}
				if (r != (int)to_read) {
					/* TODO: Create a function to get "all_data_read" from
					 * the conn object. All data is read if the Content-Length
					 * has been reached, or if chunked encoding is used and
					 * the end marker has been read, or if the connection has
					 * been closed. */
					all_data_read = 1;
				}
				buf_fill += r;
				buf[buf_fill] = 0;
				if (buf_fill < 1) {
					break;
				}
			}

			val = strchr(buf, '=');

			if (!val) {
				break;
			}
			keylen = val - buf;
			val++;

			/* Call callback */
			memset(path, 0, sizeof(path));
			field_count++;
			field_storage = url_encoded_field_found(conn,
			                                        buf,
			                                        (size_t)keylen,
			                                        NULL,
			                                        0,
			                                        path,
			                                        sizeof(path) - 1,
			                                        fdh);

			if ((field_storage & FORM_FIELD_STORAGE_ABORT)
			    == FORM_FIELD_STORAGE_ABORT) {
				/* Stop parsing the request */
				break;
			}

			if (field_storage == FORM_FIELD_STORAGE_STORE) {
				if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) {
					fstore.access.fp = NULL;
				}
				file_size = 0;
				if (!fstore.access.fp) {
					mg_cry(conn, "%s: Cannot create file %s", __func__, path);
				}
			}

			get_block = 0;
			/* Loop to read values larger than sizeof(buf)-keylen-2 */
			do {
				next = strchr(val, '&');
				if (next) {
					vallen = next - val;
					next++;
					end_of_key_value_pair_found = 1;
				} else {
					vallen = (ptrdiff_t)strlen(val);
					next = val + vallen;
				}

				if (field_storage == FORM_FIELD_STORAGE_GET) {
#if 0
					if (!end_of_key_value_pair_found && !all_data_read) {
						/* This callback will deliver partial contents */
					}
#else
					(void)all_data_read; /* avoid warning */
#endif

					/* Call callback */
					url_encoded_field_get(conn,
					                      ((get_block > 0) ? NULL : buf),
					                      ((get_block > 0) ? 0
					                                       : (size_t)keylen),
					                      val,
					                      (size_t)vallen,
					                      fdh);
					get_block++;
				}
				if (fstore.access.fp) {
					size_t n = (size_t)
					    fwrite(val, 1, (size_t)vallen, fstore.access.fp);
					if ((n != (size_t)vallen) || (ferror(fstore.access.fp))) {
						mg_cry(conn,
						       "%s: Cannot write file %s",
						       __func__,
						       path);
						mg_fclose(&fstore.access);
						remove_bad_file(conn, path);
					}
					file_size += (int64_t)n;
				}

				if (!end_of_key_value_pair_found) {
					used = next - buf;
					memmove(buf,
					        buf + (size_t)used,
					        sizeof(buf) - (size_t)used);
					buf_fill -= (int)used;
					if ((size_t)buf_fill < (sizeof(buf) - 1)) {

						size_t to_read = sizeof(buf) - 1 - (size_t)buf_fill;
						r = mg_read(conn, buf + (size_t)buf_fill, to_read);
						if (r < 0) {
							/* read error */
							return -1;
						}
						if (r != (int)to_read) {
							/* TODO: Create a function to get "all_data_read"
							 * from the conn object. All data is read if the
							 * Content-Length has been reached, or if chunked
							 * encoding is used and the end marker has been
							 * read, or if the connection has been closed. */
							all_data_read = 1;
						}
						buf_fill += r;
						buf[buf_fill] = 0;
						if (buf_fill < 1) {
							break;
						}
						val = buf;
					}
				}

			} while (!end_of_key_value_pair_found);

			if (fstore.access.fp) {
				r = mg_fclose(&fstore.access);
				if (r == 0) {
					/* stored successfully */
					field_stored(conn, path, file_size, fdh);
				} else {
					mg_cry(conn, "%s: Error saving file %s", __func__, path);
					remove_bad_file(conn, path);
				}
				fstore.access.fp = NULL;
			}

			/* Proceed to next entry */
			used = next - buf;
			memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used);
			buf_fill -= (int)used;
		}

		return field_count;
	}

	if (!mg_strncasecmp(content_type, "MULTIPART/FORM-DATA;", 20)) {
		/* The form data is in the request body data, encoded as multipart
		 * content (see https://www.ietf.org/rfc/rfc1867.txt,
		 * https://www.ietf.org/rfc/rfc2388.txt). */
		const char *boundary;
		size_t bl;
		ptrdiff_t used;
		struct mg_request_info part_header;
		char *hbuf, *hend, *fbeg, *fend, *nbeg, *nend;
		const char *content_disp;
		const char *next;

		memset(&part_header, 0, sizeof(part_header));

		/* Skip all spaces between MULTIPART/FORM-DATA; and BOUNDARY= */
		bl = 20;
		while (content_type[bl] == ' ') {
			bl++;
		}

		/* There has to be a BOUNDARY definition in the Content-Type header */
		if (mg_strncasecmp(content_type + bl, "BOUNDARY=", 9)) {
			/* Malformed request */
			return -1;
		}

		boundary = content_type + bl + 9;
		bl = strlen(boundary);

		if (bl + 800 > sizeof(buf)) {
			/* Sanity check:  The algorithm can not work if bl >= sizeof(buf),
			 * and it will not work effectively, if the buf is only a few byte
			 * larger than bl, or it buf can not hold the multipart header
			 * plus the boundary.
			 * Check some reasonable number here, that should be fulfilled by
			 * any reasonable request from every browser. If it is not
			 * fulfilled, it might be a hand-made request, intended to
			 * interfere with the algorithm. */
			return -1;
		}

		for (;;) {
			size_t towrite, n;
			int get_block;

			r = mg_read(conn,
			            buf + (size_t)buf_fill,
			            sizeof(buf) - 1 - (size_t)buf_fill);
			if (r < 0) {
				/* read error */
				return -1;
			}
			buf_fill += r;
			buf[buf_fill] = 0;
			if (buf_fill < 1) {
				/* No data */
				return -1;
			}

			if (buf[0] != '-' || buf[1] != '-') {
				/* Malformed request */
				return -1;
			}
			if (strncmp(buf + 2, boundary, bl)) {
				/* Malformed request */
				return -1;
			}
			if (buf[bl + 2] != '\r' || buf[bl + 3] != '\n') {
				/* Every part must end with \r\n, if there is another part.
				 * The end of the request has an extra -- */
				if (((size_t)buf_fill != (size_t)(bl + 6))
				    || (strncmp(buf + bl + 2, "--\r\n", 4))) {
					/* Malformed request */
					return -1;
				}
				/* End of the request */
				break;
			}

			/* Next, we need to get the part header: Read until \r\n\r\n */
			hbuf = buf + bl + 4;
			hend = strstr(hbuf, "\r\n\r\n");
			if (!hend) {
				/* Malformed request */
				return -1;
			}

			parse_http_headers(&hbuf, &part_header);
			if ((hend + 2) != hbuf) {
				/* Malformed request */
				return -1;
			}

			/* Skip \r\n\r\n */
			hend += 4;

			/* According to the RFC, every part has to have a header field like:
			 * Content-Disposition: form-data; name="..." */
			content_disp = get_header(&part_header, "Content-Disposition");
			if (!content_disp) {
				/* Malformed request */
				return -1;
			}

			/* Get the mandatory name="..." part of the Content-Disposition
			 * header. */
			nbeg = strstr(content_disp, "name=\"");
			if (!nbeg) {
				/* Malformed request */
				return -1;
			}
			nbeg += 6;
			nend = strchr(nbeg, '\"');
			if (!nend) {
				/* Malformed request */
				return -1;
			}

			/* Get the optional filename="..." part of the Content-Disposition
			 * header. */
			fbeg = strstr(content_disp, "filename=\"");
			if (fbeg) {
				fbeg += 10;
				fend = strchr(fbeg, '\"');
				if (!fend) {
					/* Malformed request (the filename field is optional, but if
					 * it exists, it needs to be terminated correctly). */
					return -1;
				}

				/* TODO: check Content-Type */
				/* Content-Type: application/octet-stream */

			} else {
				fend = fbeg;
			}

			memset(path, 0, sizeof(path));
			field_count++;
			field_storage = url_encoded_field_found(conn,
			                                        nbeg,
			                                        (size_t)(nend - nbeg),
			                                        fbeg,
			                                        (size_t)(fend - fbeg),
			                                        path,
			                                        sizeof(path) - 1,
			                                        fdh);

			/* If the boundary is already in the buffer, get the address,
			 * otherwise next will be NULL. */
			next = search_boundary(hbuf,
			                       (size_t)((buf - hbuf) + buf_fill),
			                       boundary,
			                       bl);

			if (field_storage == FORM_FIELD_STORAGE_STORE) {
				/* Store the content to a file */
				if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fstore) == 0) {
					fstore.access.fp = NULL;
				}
				file_size = 0;

				if (!fstore.access.fp) {
					mg_cry(conn, "%s: Cannot create file %s", __func__, path);
				}
			}

			get_block = 0;
			while (!next) {
				/* Set "towrite" to the number of bytes available
				 * in the buffer */
				towrite = (size_t)(buf - hend + buf_fill);
				/* Subtract the boundary length, to deal with
				 * cases the boundary is only partially stored
				 * in the buffer. */
				towrite -= bl + 4;

				if (field_storage == FORM_FIELD_STORAGE_GET) {
					unencoded_field_get(conn,
					                    ((get_block > 0) ? NULL : nbeg),
					                    ((get_block > 0)
					                         ? 0
					                         : (size_t)(nend - nbeg)),
					                    hend,
					                    towrite,
					                    fdh);
					get_block++;
				}

				if (field_storage == FORM_FIELD_STORAGE_STORE) {
					if (fstore.access.fp) {

						/* Store the content of the buffer. */
						n = (size_t)fwrite(hend, 1, towrite, fstore.access.fp);
						if ((n != towrite) || (ferror(fstore.access.fp))) {
							mg_cry(conn,
							       "%s: Cannot write file %s",
							       __func__,
							       path);
							mg_fclose(&fstore.access);
							remove_bad_file(conn, path);
						}
						file_size += (int64_t)n;
					}
				}

				memmove(buf, hend + towrite, bl + 4);
				buf_fill = (int)(bl + 4);
				hend = buf;

				/* Read new data */
				r = mg_read(conn,
				            buf + (size_t)buf_fill,
				            sizeof(buf) - 1 - (size_t)buf_fill);
				if (r < 0) {
					/* read error */
					return -1;
				}
				buf_fill += r;
				buf[buf_fill] = 0;
				if (buf_fill < 1) {
					/* No data */
					return -1;
				}

				/* Find boundary */
				next = search_boundary(buf, (size_t)buf_fill, boundary, bl);
			}

			towrite = (size_t)(next - hend);

			if (field_storage == FORM_FIELD_STORAGE_GET) {
				/* Call callback */
				unencoded_field_get(conn,
				                    ((get_block > 0) ? NULL : nbeg),
				                    ((get_block > 0) ? 0
				                                     : (size_t)(nend - nbeg)),
				                    hend,
				                    towrite,
				                    fdh);
			}

			if (field_storage == FORM_FIELD_STORAGE_STORE) {

				if (fstore.access.fp) {
					n = (size_t)fwrite(hend, 1, towrite, fstore.access.fp);
					if ((n != towrite) || (ferror(fstore.access.fp))) {
						mg_cry(conn,
						       "%s: Cannot write file %s",
						       __func__,
						       path);
						mg_fclose(&fstore.access);
						remove_bad_file(conn, path);
					}
					file_size += (int64_t)n;
				}
			}

			if (field_storage == FORM_FIELD_STORAGE_STORE) {

				if (fstore.access.fp) {
					r = mg_fclose(&fstore.access);
					if (r == 0) {
						/* stored successfully */
						field_stored(conn, path, file_size, fdh);
					} else {
						mg_cry(conn,
						       "%s: Error saving file %s",
						       __func__,
						       path);
						remove_bad_file(conn, path);
					}
					fstore.access.fp = NULL;
				}
			}

			if ((field_storage & FORM_FIELD_STORAGE_ABORT)
			    == FORM_FIELD_STORAGE_ABORT) {
				/* Stop parsing the request */
				break;
			}

			/* Remove from the buffer */
			used = next - buf + 2;
			memmove(buf, buf + (size_t)used, sizeof(buf) - (size_t)used);
			buf_fill -= (int)used;
		}

		/* All parts handled */
		return field_count;
	}

	/* Unknown Content-Type */
	return -1;
}
