diff mbox series

[v5,2/3] mongoose: Adapt interface for mongoose 7.6

Message ID 20220208003822.2295-2-james.hilliard1@gmail.com
State Changes Requested
Headers show
Series [v5,1/3] mongoose: Update to version 7.6 | expand

Commit Message

James Hilliard Feb. 8, 2022, 12:38 a.m. UTC
There are significant differences in the new mongoose API which
requires significant changes to the interface to support.

We need to forward-port mongoose digest auth support as it is no
longer built into mongoose.

We also need to forward port multipart stream handling.

We need to overhaul the broadcast thread handling as the current
implementation is not thread safe and can drop messages.

Note that mongoose.c and mongoose.h are unmodified from the upstream
amalgamations to simplify review, there are a few warnings that
probably need to be fixed there.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v4 -> v5:
  - update to mongoose 7.6
  - rebase/cleanup
Changes v3 -> v4:
  - size computation fixes
  - broadcast download progress
Changes v2 -> v3:
  - forward port multipart streaming handler
---
 mongoose/Makefile             |  10 +-
 mongoose/mongoose_interface.c | 641 ++++++++++++++++++++++++----------
 mongoose/mongoose_multipart.c | 353 +++++++++++++++++++
 mongoose/mongoose_multipart.h |  48 +++
 4 files changed, 862 insertions(+), 190 deletions(-)
 create mode 100644 mongoose/mongoose_multipart.c
 create mode 100644 mongoose/mongoose_multipart.h

Comments

Stefano Babic Feb. 8, 2022, 12:35 p.m. UTC | #1
Hi James,

I tested this patchset and I found the following issues:


- there is a compatibility issue with command line because the new 
version interpretes the "port" as string, while 6.18 expects an integer.

So starting with -w "-p 8100" does not work. To make working, I replace 
the port with :

"-p localhost:8100"

This works, but raises a compatibility issue, but of course, then the 
webserver listens just from localhost. We need here some kind of 
conversion, so that it works if just the port number is passed.

- from the parameter list:

-l, --listing                  : enable directory listing
-p, --port <port>              : server port number  (default: 8080)
-s, --ssl                      : enable ssl support
-C, --ssl-cert <cert>          : ssl certificate to present to clients
-K, --ssl-key <key>            : key corresponding to the ssl certificate
-r, --document-root <path>     : path to document root directory 
(default: .)
-t, --timeout                  : timeout to check if connection is lost 
(default: check disabled)
--auth-domain                  : set authentication domain if any 
(default: none)
--global-auth-file             : set authentication file if any 
(default: none)

I tested -s, -K, -C, -r : they work as expected.

You disabled -t, see later : you have commented the line that set an 
internal timer. Is it supposed to work ? The timer sets a watchdog for 
the upload of the SWU, else it is unknown if the browser will go on to 
send data. This fixed the case when connection stucks or browser 
crashes: without this fix, SWUpdate hangs forever and new updates are 
rejected because SWupdate still expects data from previous connection.

Best regards,
Stefano

On 08.02.22 01:38, James Hilliard wrote:
> There are significant differences in the new mongoose API which
> requires significant changes to the interface to support.
> 
> We need to forward-port mongoose digest auth support as it is no
> longer built into mongoose.
> 
> We also need to forward port multipart stream handling.
> 
> We need to overhaul the broadcast thread handling as the current
> implementation is not thread safe and can drop messages.
> 
> Note that mongoose.c and mongoose.h are unmodified from the upstream
> amalgamations to simplify review, there are a few warnings that
> probably need to be fixed there.
> 
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> ---
> Changes v4 -> v5:
>    - update to mongoose 7.6
>    - rebase/cleanup
> Changes v3 -> v4:
>    - size computation fixes
>    - broadcast download progress
> Changes v2 -> v3:
>    - forward port multipart streaming handler
> ---
>   mongoose/Makefile             |  10 +-
>   mongoose/mongoose_interface.c | 641 ++++++++++++++++++++++++----------
>   mongoose/mongoose_multipart.c | 353 +++++++++++++++++++
>   mongoose/mongoose_multipart.h |  48 +++
>   4 files changed, 862 insertions(+), 190 deletions(-)
>   create mode 100644 mongoose/mongoose_multipart.c
>   create mode 100644 mongoose/mongoose_multipart.h
> 
> diff --git a/mongoose/Makefile b/mongoose/Makefile
> index a6711a2..7dd9f73 100644
> --- a/mongoose/Makefile
> +++ b/mongoose/Makefile
> @@ -6,20 +6,24 @@ ifneq ($(CONFIG_WEBSERVER),)
>   ifneq ($(CONFIG_MONGOOSE),)
>   KBUILD_CFLAGS += -DMG_ENABLE_HTTP_STREAMING_MULTIPART=1
>   KBUILD_CFLAGS += -DMG_ENABLE_HTTP_WEBSOCKET=1 -DMG_ENABLE_THREADS=1
> +KBUILD_CFLAGS += -DMG_ENABLE_MD5=1
> +KBUILD_CFLAGS += -DMG_MAX_RECV_BUF_SIZE=262144
> +KBUILD_CFLAGS += -DMG_ENABLE_LOG=0
>   ifneq ($(CONFIG_MONGOOSEIPV6),)
>   KBUILD_CFLAGS += -DMG_ENABLE_IPV6=1
>   endif
>   ifneq ($(CONFIG_MONGOOSESSL),)
>   KBUILD_CFLAGS += -DMG_ENABLE_SSL=1
> +endif
>   ifeq ($(CONFIG_SSL_IMPL_OPENSSL)$(CONFIG_SSL_IMPL_WOLFSSL),y)
> -KBUILD_CFLAGS += -DMG_SSL_IF=MG_SSL_IF_OPENSSL
> +KBUILD_CFLAGS += -DMG_ENABLE_OPENSSL=1
>   endif
>   ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y)
> -KBUILD_CFLAGS += -DMG_SSL_IF=MG_SSL_IF_MBEDTLS
> -endif
> +KBUILD_CFLAGS += -DMG_ENABLE_MBEDTLS=1
>   endif
>   endif
>   endif
>   
>   lib-$(CONFIG_MONGOOSE)	+= mongoose.o \
> +			   mongoose_multipart.o \
>   			   mongoose_interface.o
> diff --git a/mongoose/mongoose_interface.c b/mongoose/mongoose_interface.c
> index 930cbf3..a2729ac 100644
> --- a/mongoose/mongoose_interface.c
> +++ b/mongoose/mongoose_interface.c
> @@ -26,17 +26,17 @@
>   #include <parselib.h>
>   #include <progress_ipc.h>
>   #include <swupdate_settings.h>
> -#include <time.h>
> +#include <pctl.h>
> +#include <pthread.h>
> +#include <progress.h>
>   
>   #include "mongoose.h"
> +#include "mongoose_multipart.h"
>   #include "util.h"
>   
>   #define MG_PORT "8080"
>   #define MG_ROOT "."
>   
> -/* in seconds. If no packet is received with this timeout, connection is broken */
> -#define MG_TIMEOUT	120
> -
>   struct mongoose_options {
>   	char *root;
>   	bool listing;
> @@ -53,12 +53,252 @@ struct file_upload_state {
>   	size_t len;
>   	int fd;
>   	bool error_report; /* if set, stop to flood with errors */
> +	uint8_t percent;
>   };
>   
>   static bool run_postupdate;
>   static unsigned int watchdog_conn = 0;
> -static struct mg_serve_http_opts s_http_server_opts;
> -static void upload_handler(struct mg_connection *nc, int ev, void *p);
> +static struct mg_http_serve_opts s_http_server_opts;
> +const char *global_auth_domain;
> +const char *global_auth_file;
> +#if MG_ENABLE_SSL
> +static bool ssl;
> +static struct mg_tls_opts tls_opts;
> +#endif
> +
> +struct ws_msg_elem {
> +	char *msg;
> +	SIMPLEQ_ENTRY(ws_msg_elem) next;
> +};
> +
> +SIMPLEQ_HEAD(ws_msglist, ws_msg_elem);
> +static struct ws_msglist ws_messages;
> +
> +static pthread_mutex_t ws_msg_lock = PTHREAD_MUTEX_INITIALIZER;
> +
> +static struct mg_connection *ws_pipe;
> +
> +static int s_signo;
> +static void signal_handler(int signo) {
> +	s_signo = signo;
> +}
> +
> +static int p_stat(const char *path, size_t *size, time_t *mtime)
> +{
> +	int flags = mg_fs_posix.stat(path, size, mtime);
> +	if (flags & MG_FS_DIR && strcmp(s_http_server_opts.root_dir, path) != 0)
> +		return 0;
> +	return flags;
> +}
> +
> +static void p_list(const char *path, void (*fn)(const char *, void *), void *userdata)
> +{
> +	(void) path, (void) fn, (void) userdata;
> +}
> +
> +static void *p_open(const char *path, int flags)
> +{
> +	return mg_fs_posix.open(path, flags);
> +}
> +
> +static void p_close(void *fp)
> +{
> +	return mg_fs_posix.close(fp);
> +}
> +
> +static size_t p_read(void *fd, void *buf, size_t len)
> +{
> +	return mg_fs_posix.read(fd, buf, len);
> +}
> +
> +static size_t p_write(void *fd, const void *buf, size_t len)
> +{
> +	return mg_fs_posix.write(fd, buf, len);
> +}
> +
> +static size_t p_seek(void *fd, size_t offset)
> +{
> +	return mg_fs_posix.seek(fd, offset);
> +}
> +
> +static bool p_rename(const char *from, const char *to)
> +{
> +	return mg_fs_posix.rename(from, to);
> +}
> +
> +static bool p_remove(const char *path)
> +{
> +	return mg_fs_posix.remove(path);
> +}
> +
> +static bool p_mkdir(const char *path)
> +{
> +	return mg_fs_posix.mkd(path);
> +}
> +
> +/* mg_fs which inhibits directory listing functionality */
> +static struct mg_fs fs_posix_no_list = {
> +		p_stat,
> +		p_list,
> +		p_open,
> +		p_close,
> +		p_read,
> +		p_write,
> +		p_seek,
> +		p_rename,
> +		p_remove,
> +		p_mkdir
> +};
> +
> +/*
> + * Minimal forward port of mongoose digest auth support.
> + */
> +static void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],
> +				   const size_t *msg_lens, uint8_t *digest)
> +{
> +	size_t i;
> +	mg_md5_ctx md5_ctx;
> +	mg_md5_init(&md5_ctx);
> +	for (i = 0; i < num_msgs; i++) {
> +		mg_md5_update(&md5_ctx, msgs[i], msg_lens[i]);
> +	}
> +	mg_md5_final(&md5_ctx, digest);
> +}
> +
> +static void cs_md5(char buf[33], ...)
> +{
> +	unsigned char hash[16];
> +	const uint8_t *msgs[20], *p;
> +	size_t msg_lens[20];
> +	size_t num_msgs = 0;
> +	va_list ap;
> +
> +	va_start(ap, buf);
> +	while ((p = va_arg(ap, const unsigned char *)) != NULL) {
> +		msgs[num_msgs] = p;
> +		msg_lens[num_msgs] = va_arg(ap, size_t);
> +		num_msgs++;
> +	}
> +	va_end(ap);
> +
> +	mg_hash_md5_v(num_msgs, msgs, msg_lens, hash);
> +	mg_hex(hash, sizeof(hash), buf);
> +}
> +
> +static void mg_mkmd5resp(struct mg_str method, struct mg_str uri, struct mg_str ha1,
> +						 struct mg_str nonce, struct mg_str nc, struct mg_str cnonce,
> +						 struct mg_str qop, char *resp)
> +{
> +	static const char colon[] = ":";
> +	static const size_t one = 1;
> +	char ha2[33];
> +	cs_md5(ha2, method.ptr, method.len, colon, one, uri.ptr, uri.len, NULL);
> +	cs_md5(resp, ha1.ptr, ha1.len, colon, one, nonce.ptr, nonce.len, colon, one, nc.ptr,
> +		   nc.len, colon, one, cnonce.ptr, cnonce.len, colon, one, qop.ptr, qop.len,
> +		   colon, one, ha2, sizeof(ha2) - 1, NULL);
> +}
> +
> +static double mg_time(void)
> +{
> +    struct timeval tv;
> +    if (gettimeofday(&tv, NULL /* tz */) != 0) return 0;
> +    return (double) tv.tv_sec + (((double) tv.tv_usec) / 1000000.0);
> +}
> +
> +/*
> + * Check for authentication timeout.
> + * Clients send time stamp encoded in nonce. Make sure it is not too old,
> + * to prevent replay attacks.
> + * Assumption: nonce is a hexadecimal number of seconds since 1970.
> + */
> +static int mg_check_nonce(struct mg_str nonce)
> +{
> +	unsigned long now = (unsigned long) mg_time();
> +	unsigned long val = (unsigned long) strtoul(nonce.ptr, NULL, 16);
> +	return (now >= val) && (now - val < 60 * 60);
> +}
> +
> +static int mg_check_digest_auth(struct mg_str method, struct mg_str uri,
> +						 struct mg_str username, struct mg_str cnonce,
> +						 struct mg_str response, struct mg_str qop,
> +						 struct mg_str nc, struct mg_str nonce,
> +						 struct mg_str auth_domain, FILE *fp)
> +{
> +	char buf[128], f_user[sizeof(buf)], f_ha1[sizeof(buf)], f_domain[sizeof(buf)];
> +	char exp_resp[33];
> +
> +	/*
> +	 * Read passwords file line by line. If should have htdigest format,
> +	 * i.e. each line should be a colon-separated sequence:
> +	 * USER_NAME:DOMAIN_NAME:HA1_HASH_OF_USER_DOMAIN_AND_PASSWORD
> +	 */
> +	while (fgets(buf, sizeof(buf), fp) != NULL) {
> +		if (sscanf(buf, "%[^:]:%[^:]:%s", f_user, f_domain, f_ha1) == 3 &&
> +			mg_vcmp(&username, f_user) == 0 &&
> +			mg_vcmp(&auth_domain, f_domain) == 0) {
> +			/* Username and domain matched, check the password */
> +			mg_mkmd5resp(method, uri, mg_str_s(f_ha1), nonce, nc, cnonce, qop, exp_resp);
> +			return mg_ncasecmp(response.ptr, exp_resp, strlen(exp_resp)) == 0;
> +		}
> +	}
> +
> +	/* None of the entries in the passwords file matched - return failure */
> +	return 0;
> +}
> +
> +static int mg_http_check_digest_auth(struct mg_http_message *hm, struct mg_str auth_domain, FILE *fp)
> +{
> +	struct mg_str *hdr;
> +	struct mg_str username, cnonce, response, qop, nc, nonce;
> +
> +	/* Parse "Authorization:" header, fail fast on parse error */
> +	if (hm == NULL ||
> +		(hdr = mg_http_get_header(hm, "Authorization")) == NULL ||
> +		(username = mg_http_get_header_var(*hdr, mg_str_n("username", 8))).len == 0 ||
> +		(cnonce = mg_http_get_header_var(*hdr, mg_str_n("cnonce", 6))).len == 0 ||
> +		(response = mg_http_get_header_var(*hdr, mg_str_n("response", 8))).len == 0 ||
> +		mg_http_get_header_var(*hdr, mg_str_n("uri", 3)).len == 0 ||
> +		(qop = mg_http_get_header_var(*hdr, mg_str_n("qop", 3))).len == 0 ||
> +		(nc = mg_http_get_header_var(*hdr, mg_str_n("nc", 2))).len == 0 ||
> +		(nonce = mg_http_get_header_var(*hdr, mg_str_n("nonce", 5))).len == 0 ||
> +		mg_check_nonce(nonce) == 0) {
> +		return 0;
> +	}
> +
> +	/* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */
> +
> +	return mg_check_digest_auth(
> +			hm->method,
> +			mg_str_n(
> +					hm->uri.ptr,
> +					hm->uri.len + (hm->query.len ? hm->query.len + 1 : 0)),
> +			username, cnonce, response, qop, nc, nonce, auth_domain, fp);
> +}
> +
> +static int mg_http_is_authorized(struct mg_http_message *hm, const char *domain, const char *passwords_file) {
> +	FILE *fp;
> +	int authorized = 1;
> +
> +	if (domain != NULL && passwords_file != NULL) {
> +		fp = fopen(passwords_file, "r");
> +		if (fp != NULL) {
> +			authorized = mg_http_check_digest_auth(hm, mg_str(domain), fp);
> +			fclose(fp);
> +		}
> +	}
> +
> +	return authorized;
> +}
> +
> +static void mg_http_send_digest_auth_request(struct mg_connection *c, const char *domain)
> +{
> +	mg_printf(c,
> +			  "HTTP/1.1 401 Unauthorized\r\n"
> +			  "WWW-Authenticate: Digest qop=\"auth\", "
> +			  "realm=\"%s\", nonce=\"%lx\"\r\n"
> +			  "Content-Length: 0\r\n\r\n",
> +			  domain, (unsigned long) mg_time());
> +}
>   
>   /*
>    * These functions are for V2 of the protocol
> @@ -100,24 +340,50 @@ static const char *get_source_string(unsigned int source)
>   	return str[source];
>   }
>   
> -static void restart_handler(struct mg_connection *nc, int ev, void *ev_data)
> +static void restart_handler(struct mg_connection *nc, void *ev_data)
>   {
> -	struct http_message *hm = (struct http_message *) ev_data;
> +	struct mg_http_message *hm = (struct mg_http_message *) ev_data;
>   	ipc_message msg = {};
>   
> -	if (ev == MG_EV_HTTP_REQUEST) {
> -		if(mg_vcasecmp(&hm->method, "POST") != 0) {
> -			mg_http_send_error(nc, 405, "Method Not Allowed");
> -			return;
> -		}
> +	if(mg_vcasecmp(&hm->method, "POST") != 0) {
> +		mg_http_reply(nc, 405, "", "%s", "Method Not Allowed\n");
> +		return;
> +	}
> +
> +	int ret = ipc_postupdate(&msg);
> +	if (ret || msg.type != ACK) {
> +		mg_http_reply(nc, 500, "", "%s", "Failed to queue command\n");
> +		return;
> +	}
>   
> -		int ret = ipc_postupdate(&msg);
> -		if (ret || msg.type != ACK) {
> -			mg_http_send_error(nc, 500, "Failed to queue command");
> -			return;
> +	mg_http_reply(nc, 201, "", "%s", "Device will reboot now.\n");
> +}
> +
> +static void broadcast_callback(struct mg_connection *nc, int ev,
> +		void __attribute__ ((__unused__)) *ev_data, void __attribute__ ((__unused__)) *fn_data)
> +{
> +	if (ev == MG_EV_READ) {
> +		struct mg_connection *t;
> +		struct ws_msg_elem *ws_msg;
> +
> +		pthread_mutex_lock(&ws_msg_lock);
> +
> +		while(!SIMPLEQ_EMPTY(&ws_messages)) {
> +			ws_msg = SIMPLEQ_FIRST(&ws_messages);
> +			SIMPLEQ_REMOVE_HEAD(&ws_messages, next);
> +
> +			pthread_mutex_unlock(&ws_msg_lock);
> +
> +			for (t = nc->mgr->conns; t != NULL; t = t->next) {
> +				if (t->label[0] != 'W') continue;
> +				mg_ws_send(t, ws_msg->msg, strlen(ws_msg->msg), WEBSOCKET_OP_TEXT);
> +			}
> +
> +			free(ws_msg);
> +			pthread_mutex_lock(&ws_msg_lock);
>   		}
>   
> -		mg_http_send_error(nc, 201, "Device will reboot now.");
> +		pthread_mutex_unlock(&ws_msg_lock);
>   	}
>   }
>   
> @@ -137,25 +403,26 @@ static int level_to_rfc_5424(int level)
>   	}
>   }
>   
> -static void broadcast_callback(struct mg_connection *nc, int ev, void *ev_data)
> +static void broadcast(char *str)
>   {
> -	char *buf = (char *) ev_data;
> +	unsigned int len = str ? strlen(str) : 0;
> +	struct ws_msg_elem *ws_msg = (struct ws_msg_elem*)calloc(1, sizeof(*ws_msg) + len + 1);
>   
> -	if (ev != MG_EV_POLL)
> +	if (!ws_msg)
>   		return;
>   
> -	if (!(nc->flags & MG_F_IS_WEBSOCKET))
> -		return;
> +	ws_msg->msg = (char *)ws_msg + sizeof(struct ws_msg_elem);
>   
> -	mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf));
> -}
> +	if (str)
> +		strncpy(ws_msg->msg, str, len);
>   
> -static void broadcast(struct mg_mgr *mgr, char *str)
> -{
> -	mg_broadcast(mgr, broadcast_callback, str, strlen(str) + 1);
> +	pthread_mutex_lock(&ws_msg_lock);
> +	SIMPLEQ_INSERT_TAIL(&ws_messages, ws_msg, next);
> +	pthread_mutex_unlock(&ws_msg_lock);
> +	mg_mgr_wakeup(ws_pipe, "", 0);
>   }
>   
> -static void *broadcast_message_thread(void *data)
> +static void *broadcast_message_thread(void __attribute__ ((__unused__)) *data)
>   {
>   	int fd = -1;
>   
> @@ -177,8 +444,8 @@ static void *broadcast_message_thread(void *data)
>   		if (ret != sizeof(msg))
>   			return NULL;
>   
> -		if (strlen(msg.data.notify.msg) != 0) {
> -			struct mg_mgr *mgr = (struct mg_mgr *) data;
> +		if (strlen(msg.data.notify.msg) != 0 &&
> +				msg.data.status.current != PROGRESS) {
>   			char text[4096];
>   			char str[4160];
>   
> @@ -193,23 +460,20 @@ static void *broadcast_message_thread(void *data)
>   					 level_to_rfc_5424(msg.data.notify.level), /* RFC 5424 */
>   					 text);
>   
> -			broadcast(mgr, str);
> +			broadcast(str);
>   		}
>   	}
> -
> -	return NULL;
>   }
>   
> -static void *broadcast_progress_thread(void *data)
> +static void *broadcast_progress_thread(void __attribute__ ((__unused__)) *data)
>   {
>   	RECOVERY_STATUS status = -1;
>   	sourcetype source = -1;
>   	unsigned int step = 0;
> -	unsigned int percent = 0;
> +	uint8_t percent = 0;
>   	int fd = -1;
>   
>   	for (;;) {
> -		struct mg_mgr *mgr = (struct mg_mgr *) data;
>   		struct progress_msg msg;
>   		char str[512];
>   		char escaped[512];
> @@ -241,7 +505,7 @@ static void *broadcast_progress_thread(void *data)
>   				"\t\"status\": \"%s\"\r\n"
>   				"}\r\n",
>   				escaped);
> -			broadcast(mgr, str);
> +			broadcast(str);
>   		}
>   
>   		if (msg.source != source) {
> @@ -253,7 +517,7 @@ static void *broadcast_progress_thread(void *data)
>   				"\t\"source\": \"%s\"\r\n"
>   				"}\r\n",
>   				get_source_string(msg.source));
> -			broadcast(mgr, str);
> +			broadcast(str);
>   		}
>   
>   		if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER && run_postupdate) {
> @@ -271,7 +535,7 @@ static void *broadcast_progress_thread(void *data)
>   				"\t\"source\": \"%s\"\r\n"
>   				"}\r\n",
>   				escaped);
> -			broadcast(mgr, str);
> +			broadcast(str);
>   		}
>   
>   		if ((msg.cur_step != step || msg.cur_percent != percent) &&
> @@ -293,147 +557,166 @@ static void *broadcast_progress_thread(void *data)
>   				msg.cur_step,
>   				escaped,
>   				msg.cur_percent);
> -			broadcast(mgr, str);
> +			broadcast(str);
>   		}
>   	}
> -
> -	return NULL;
>   }
>   
>   /*
>    * Code common to V1 and V2
>    */
> -static void upload_handler(struct mg_connection *nc, int ev, void *p)
> +static void upload_handler(struct mg_connection *nc, int ev, void *ev_data,
> +		void __attribute__ ((__unused__)) *fn_data)
>   {
> -	struct mg_http_multipart_part *mp;
> +	struct mg_http_multipart *mp;
>   	struct file_upload_state *fus;
> +	unsigned int percent;
>   	ssize_t written;
>   
>   	switch (ev) {
> -	case MG_EV_HTTP_PART_BEGIN:
> -		mp = (struct mg_http_multipart_part *) p;
> -
> -		fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
> -		if (fus == NULL) {
> -			mg_http_send_error(nc, 500, "Out of memory");
> -			break;
> -		}
> -
> -		struct swupdate_request req;
> -		swupdate_prepare_req(&req);
> -		req.len = strlen(mp->file_name);
> -		strncpy(req.info, mp->file_name, sizeof(req.info) - 1);
> -		req.source = SOURCE_WEBSERVER;
> -		fus->fd = ipc_inst_start_ext(&req, sizeof(req));
> -		if (fus->fd < 0) {
> -			mg_http_send_error(nc, 500, "Failed to queue command");
> -			free(fus);
> -			break;
> -		}
> +		case MG_EV_HTTP_PART_BEGIN:
> +			mp = (struct mg_http_multipart *) ev_data;
> +
> +			fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
> +			if (fus == NULL) {
> +				mg_http_reply(nc, 500, "", "%s", "Out of memory\n");
> +				nc->is_closing = 1;
> +				break;
> +			}
>   
> -		if (swupdate_file_setnonblock(fus->fd, true)) {
> -			WARN("IPC cannot be set in non-blocking, fallback to block mode");
> -		}
> +			struct swupdate_request req;
> +			swupdate_prepare_req(&req);
> +			req.len = mp->len;
> +			strncpy(req.info, mp->part.filename.ptr, sizeof(req.info) - 1);
> +			req.source = SOURCE_WEBSERVER;
> +			fus->fd = ipc_inst_start_ext(&req, sizeof(req));
> +			if (fus->fd < 0) {
> +				mg_http_reply(nc, 500, "", "%s", "Failed to queue command\n");
> +				nc->is_closing = 1;
> +				free(fus);
> +				break;
> +			}
>   
> -		mp->user_data = fus;
> +			swupdate_download_update(0, mp->len);
>   
> -		/*
> -		 * There is no user data for connection.
> -		 * Set the user data to the same structure to make it available
> -		 * to the MG_TIMER event
> -		 */
> -		nc->user_data = mp->user_data;
> +			if (swupdate_file_setnonblock(fus->fd, true)) {
> +				WARN("IPC cannot be set in non-blocking, fallback to block mode");
> +			}
>   
> -		if (watchdog_conn > 0) {
> -			TRACE("Setting Webserver Watchdog Timer to %d", watchdog_conn);
> -			mg_set_timer(nc, mg_time() + watchdog_conn);
> -		}
> +			mp->user_data = fus;
>   
> -		break;
> +			/*
> +			 * There is no user data for connection.
> +			 * Set the user data to the same structure to make it available
> +			 * to the MG_TIMER event
> +			 */
> +			nc->fn_data = mp->user_data;
>   
> -	case MG_EV_HTTP_PART_DATA:
> -		mp = (struct mg_http_multipart_part *) p;
> -		fus = (struct file_upload_state *) mp->user_data;
> +			if (watchdog_conn > 0) {
> +				TRACE("Setting Webserver Watchdog Timer to %d", watchdog_conn);
> +				//mg_set_timer(nc, mg_time() + watchdog_conn);
> +			}
>   
> -		if (!fus)
>   			break;
>   
> -		written = write(fus->fd, (char *) mp->data.p, mp->data.len);
> -		/*
> -		 * IPC seems to block, wait for a while
> -		 */
> -		if (written != mp->data.len) {
> -			if (written < 0) {
> -				if (errno != EAGAIN && errno != EWOULDBLOCK) {
> -					if (!fus->error_report) {
> -						ERROR("Writing to IPC fails due to %s", strerror(errno));
> -						fus->error_report = true;
> -					}
> -					/*
> -					 * Simply consumes the data to unblock the sender
> -					 */
> -					written = mp->data.len;
> -				} else
> -					written = 0;
> +		case MG_EV_HTTP_PART_DATA:
> +			mp = (struct mg_http_multipart *) ev_data;
> +			fus = (struct file_upload_state *) mp->user_data;
> +
> +			if (!fus)
> +				break;
> +
> +			written = write(fus->fd, (char *) mp->part.body.ptr, mp->part.body.len);
> +			/*
> +			 * IPC seems to block, wait for a while
> +			 */
> +			if (written != mp->part.body.len) {
> +				if (written < 0) {
> +					if (errno != EAGAIN && errno != EWOULDBLOCK) {
> +						if ((mp->part.body.len + fus->len) == mp->len) {
> +							/*
> +							 * Simply consumes the data to unblock the sender
> +							 */
> +							written = mp->part.body.len;
> +						} else if (!fus->error_report) {
> +							ERROR("Writing to IPC fails due to %s", strerror(errno));
> +							fus->error_report = true;
> +							nc->is_closing = 1;
> +						}
> +					} else
> +						written = 0;
> +				}
> +				usleep(100);
>   			}
> -			usleep(100);
> -		}
>   
> -		mp->num_data_consumed = written;
> -		fus->len += written;
> +			mp->num_data_consumed = written;
> +			fus->len += written;
> +			percent = (uint8_t)(100.0 * ((double)fus->len / (double)mp->len));
> +			if (percent != fus->percent) {
> +				fus->percent = percent;
> +				swupdate_download_update(fus->percent, mp->len);
> +			}
>   
> -		break;
> +			break;
>   
> -	case MG_EV_HTTP_PART_END:
> -		mp = (struct mg_http_multipart_part *) p;
> -		fus = (struct file_upload_state *) mp->user_data;
> +		case MG_EV_HTTP_PART_END:
> +			mp = (struct mg_http_multipart *) ev_data;
> +			fus = (struct file_upload_state *) mp->user_data;
>   
> -		if (!fus)
> -			break;
> +			if (!fus)
> +				break;
>   
> -		ipc_end(fus->fd);
> +			ipc_end(fus->fd);
>   
> -		mg_send_response_line(nc, 200,
> -			"Content-Type: text/plain\r\n"
> -			"Connection: close");
> -		mg_send(nc, "\r\n", 2);
> -		mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len);
> -		nc->flags |= MG_F_SEND_AND_CLOSE;
> +			mg_http_reply(nc, 200, "%s",
> +								  "Content-Type: text/plain\r\n"
> +								  "Connection: close");
> +			mg_send(nc, "\r\n", 2);
> +			mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->part.filename, (int) fus->len);
> +			nc->is_closing = 1;
>   
> -		mp->user_data = NULL;
> -		nc->user_data = mp->user_data;
> -		free(fus);
> -		break;
> +			mp->user_data = NULL;
> +			nc->fn_data = mp->user_data;
> +			free(fus);
> +			break;
>   	}
>   }
>   
> -static void ev_handler(struct mg_connection *nc, int ev, void *ev_data)
> +static void websocket_handler(struct mg_connection *nc, void *ev_data)
>   {
> -	time_t now;
> +	struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> +	mg_ws_upgrade(nc, hm, NULL);
> +	nc->label[0] = 'W';
> +}
>   
> -	switch (ev) {
> -	case MG_EV_HTTP_REQUEST:
> -		mg_serve_http(nc, ev_data, s_http_server_opts);
> -		break;
> -	case MG_EV_TIMER:
> -		now = (time_t) mg_time();
> -		/*
> -		 * Check if a multi-part was initiated
> -		 */
> -		if (nc->user_data && (watchdog_conn > 0) &&
> -			(difftime(now, nc->last_io_time) > watchdog_conn)) {
> -			struct file_upload_state *fus;
> -
> -		       /* Connection lost, drop data */
> -			ERROR("Connection lost, no data since %ld now %ld, closing...",
> -				nc->last_io_time, now);
> -			fus = (struct file_upload_state *) nc->user_data;
> -			ipc_end(fus->fd);
> -			nc->user_data = NULL;
> -			nc->flags |= MG_F_CLOSE_IMMEDIATELY;
> -		} else
> -			mg_set_timer(nc, mg_time() + watchdog_conn);  // Send us timer event again after 0.5 seconds
> -		break;
> +static void ev_handler(struct mg_connection *nc, int ev, void *ev_data, void *fn_data)
> +{
> +	if (ev == MG_EV_HTTP_MSG) {
> +		struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> +		if (!mg_http_is_authorized(hm, global_auth_domain, global_auth_file))
> +			mg_http_send_digest_auth_request(nc, global_auth_domain);
> +		else if (mg_http_get_header(hm, "Sec-WebSocket-Key") != NULL)
> +			websocket_handler(nc, ev_data);
> +		else if (mg_http_match_uri(hm, "/restart"))
> +			restart_handler(nc, ev_data);
> +		else
> +			mg_http_serve_dir(nc, ev_data, &s_http_server_opts);
> +	} else if (nc->label[0] != 'M' && ev == MG_EV_HTTP_CHUNK) {
> +		struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> +		if (!mg_http_is_authorized(hm, global_auth_domain, global_auth_file))
> +			mg_http_send_digest_auth_request(nc, global_auth_domain);
> +		else if (mg_http_match_uri(hm, "/upload"))
> +			multipart_upload_handler(nc, ev, ev_data, upload_handler, NULL);
> +	} else if (nc->label[0] == 'M' && (ev == MG_EV_READ || ev == MG_EV_POLL || ev == MG_EV_CLOSE)) {
> +		multipart_upload_handler(nc, ev, ev_data, upload_handler, fn_data);
> +#if MG_ENABLE_SSL
> +	} else if (ev == MG_EV_ACCEPT && ssl) {
> +		mg_tls_init(nc, &tls_opts);
> +#endif
> +	} else if (ev == MG_EV_ERROR) {
> +		ERROR("%p %s", nc->fd, (char *) ev_data);
> +	} else if (ev == MG_EV_WS_MSG) {
> +		mg_iobuf_del(&nc->recv, 0, nc->recv.len);
>   	}
>   }
>   
> @@ -521,13 +804,12 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[])
>   	struct mongoose_options opts;
>   	struct mg_mgr mgr;
>   	struct mg_connection *nc;
> -	struct mg_bind_opts bind_opts;
>   	const char *s_http_port = NULL;
> -	const char *err_str;
> +	int choice;
> +
>   #if MG_ENABLE_SSL
> -	bool ssl = false;
> +	ssl = false;
>   #endif
> -	int choice = 0;
>   
>   	memset(&opts, 0, sizeof(opts));
>   
> @@ -598,61 +880,46 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[])
>   		}
>   	}
>   
> -	s_http_server_opts.document_root =
> +	s_http_server_opts.root_dir =
>   		opts.root ? opts.root : MG_ROOT;
> -	s_http_server_opts.enable_directory_listing =
> -		opts.listing ? "yes" : "no";
> +	if (!opts.listing)
> +		s_http_server_opts.fs = &fs_posix_no_list;
>   	s_http_port = opts.port ? opts.port : MG_PORT;
> -	s_http_server_opts.global_auth_file = opts.global_auth_file;
> -	s_http_server_opts.auth_domain = opts.auth_domain;
> +	global_auth_file = opts.global_auth_file;
> +	global_auth_domain = opts.auth_domain;
>   
> -	memset(&bind_opts, 0, sizeof(bind_opts));
> -	bind_opts.error_string = &err_str;
>   #if MG_ENABLE_SSL
>   	if (ssl) {
> -		bind_opts.ssl_cert = opts.ssl_cert;
> -		bind_opts.ssl_key = opts.ssl_key;
> +		tls_opts.cert = opts.ssl_cert;
> +		tls_opts.certkey = opts.ssl_key;
>   	}
>   #endif
>   
> -	mg_mgr_init(&mgr, NULL);
> +	signal(SIGINT, signal_handler);
> +	signal(SIGTERM, signal_handler);
> +	mg_mgr_init(&mgr);
>   
> -	nc = mg_bind_opt(&mgr, s_http_port, ev_handler, bind_opts);
> +	SIMPLEQ_INIT(&ws_messages);
> +
> +	ws_pipe = mg_mkpipe(&mgr, broadcast_callback, NULL);
> +	nc = mg_http_listen(&mgr, s_http_port, ev_handler, NULL);
>   	if (nc == NULL) {
> -		ERROR("Failed to start Mongoose: %s", *bind_opts.error_string);
> +		ERROR("Failed to start Mongoose.");
>   		exit(EXIT_FAILURE);
>   	}
>   
> -	/*
> -	 * The Event Handler in Webserver will read from socket until there is data.
> -	 * This does not guarantes a flow control because data are forwarded
> -	 * to SWUpdate internal IPC. If this is not called in blocking mode,
> -	 * the Webserver should just read from socket to fill the IPC, but without
> -	 * filling all memory.
> -	 */
> -	nc->recv_mbuf_limit = 256 * 1024;
> -
> -	mg_set_protocol_http_websocket(nc);
> -	mg_register_http_endpoint(nc, "/restart", restart_handler);
> -	mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler, NULL));
> -	mg_start_thread(broadcast_message_thread, &mgr);
> -	mg_start_thread(broadcast_progress_thread, &mgr);
> +	start_thread(broadcast_message_thread, NULL);
> +	start_thread(broadcast_progress_thread, NULL);
>   
>   	INFO("Mongoose web server version %s with pid %d started on port(s) %s with web root [%s]",
>   		MG_VERSION, getpid(), s_http_port,
> -		s_http_server_opts.document_root);
> +		s_http_server_opts.root_dir);
>   
> -	for (;;) {
> +	while (s_signo == 0)
>   		mg_mgr_poll(&mgr, 100);
> -	}
>   	mg_mgr_free(&mgr);
> +	if (opts.port)
> +		free(opts.port);
>   
>   	return 0;
>   }
> -
> -#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_MBEDTLS
> -#include <mbedtls/ctr_drbg.h>
> -int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len) {
> -	return mbedtls_ctr_drbg_random(ctx, buf, len);
> -}
> -#endif
> diff --git a/mongoose/mongoose_multipart.c b/mongoose/mongoose_multipart.c
> new file mode 100644
> index 0000000..e956c6f
> --- /dev/null
> +++ b/mongoose/mongoose_multipart.c
> @@ -0,0 +1,353 @@
> +/*
> + * Copyright (c) 2004-2013 Sergey Lyubka
> + * Copyright (c) 2013-2020 Cesanta Software Limited
> + * All rights reserved
> + *
> + * SPDX-License-Identifier: GPL-2.0-only
> + *
> + * This software is dual-licensed: you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation. For the terms of this
> + * license, see <http://www.gnu.org/licenses/>.
> + *
> + * You are free to use this software under the terms of the GNU General
> + * Public License, but WITHOUT ANY WARRANTY; without even the implied
> + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> + * See the GNU General Public License for more details.
> + *
> + * Alternatively, you can license this software under a commercial
> + * license, as set out in <https://www.cesanta.com/license>.
> + */
> +
> +#include "mongoose_multipart.h"
> +
> +enum mg_http_multipart_stream_state {
> +	MPS_BEGIN,
> +	MPS_WAITING_FOR_BOUNDARY,
> +	MPS_WAITING_FOR_CHUNK,
> +	MPS_GOT_BOUNDARY,
> +	MPS_FINALIZE,
> +	MPS_FINISHED
> +};
> +
> +struct mg_http_multipart_stream {
> +	struct mg_http_part part;
> +	struct mg_str boundary;
> +	void *user_data;
> +	enum mg_http_multipart_stream_state state;
> +	int processing_part;
> +	int data_avail;
> +	size_t len;
> +};
> +
> +static void mg_http_free_proto_data_mp_stream(
> +		struct mg_http_multipart_stream *mp) {
> +	free((void *) mp->boundary.ptr);
> +	free((void *) mp->part.name.ptr);
> +	free((void *) mp->part.filename.ptr);
> +	memset(mp, 0, sizeof(*mp));
> +}
> +
> +static void mg_http_multipart_begin(struct mg_connection *nc,
> +									struct mg_http_message *hm) {
> +	struct mg_http_multipart_stream *mp_stream = nc->pfn_data;
> +	struct mg_str *ct;
> +	struct mg_iobuf *io = &nc->recv;
> +
> +	struct mg_str boundary;
> +
> +	ct = mg_http_get_header(hm, "Content-Type");
> +	if (ct == NULL) {
> +		/* We need more data - or it isn't multipart mesage */
> +		return;
> +	}
> +
> +	/* Content-type should start with "multipart" */
> +	if (ct->len < 9 || strncmp(ct->ptr, "multipart", 9) != 0) {
> +		return;
> +	}
> +
> +	boundary = mg_http_get_header_var(*ct, mg_str_n("boundary", 8));
> +	if (boundary.len == 0) {
> +		/*
> +		 * Content type is multipart, but there is no boundary,
> +		 * probably malformed request
> +		 */
> +		nc->is_closing = 1;
> +		LOG(LL_DEBUG,("invalid request"));
> +		return;
> +	}
> +
> +	/* If we reach this place - that is multipart request */
> +
> +	if (mp_stream->boundary.len != 0) {
> +		/*
> +		 * Another streaming request was in progress,
> +		 * looks like protocol error
> +		 */
> +		nc->is_closing = 1;
> +	} else {
> +		mp_stream->state = MPS_BEGIN;
> +		mp_stream->boundary = mg_strdup(boundary);
> +		mp_stream->part.name.ptr = mp_stream->part.filename.ptr = NULL;
> +		mp_stream->part.name.len = mp_stream->part.filename.len = 0;
> +		mp_stream->len = hm->body.len;
> +
> +		mg_call(nc, MG_EV_HTTP_MULTIPART_REQUEST, hm);
> +
> +		mg_iobuf_del(io, 0, hm->head.len + 2);
> +	}
> +}
> +
> +#define CONTENT_DISPOSITION "Content-Disposition: "
> +
> +static size_t mg_http_multipart_call_handler(struct mg_connection *c, int ev,
> +											 const char *data,
> +											 size_t data_len) {
> +	struct mg_http_multipart mp;
> +	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> +	memset(&mp, 0, sizeof(mp));
> +
> +	mp.part.name = mp_stream->part.name;
> +	mp.part.filename = mp_stream->part.filename;
> +	mp.user_data = mp_stream->user_data;
> +	mp.part.body.ptr = data;
> +	mp.part.body.len = data_len;
> +	mp.num_data_consumed = data_len;
> +	mp.len = mp_stream->len;
> +	mg_call(c, ev, &mp);
> +	mp_stream->user_data = mp.user_data;
> +	mp_stream->data_avail = (mp.num_data_consumed != data_len);
> +	return mp.num_data_consumed;
> +}
> +
> +static int mg_http_multipart_finalize(struct mg_connection *c) {
> +	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> +
> +	mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
> +	free((void *) mp_stream->part.filename.ptr);
> +	mp_stream->part.filename.ptr = NULL;
> +	free((void *) mp_stream->part.name.ptr);
> +	mp_stream->part.name.ptr = NULL;
> +	mg_http_multipart_call_handler(c, MG_EV_HTTP_MULTIPART_REQUEST_END, NULL, 0);
> +	mg_http_free_proto_data_mp_stream(mp_stream);
> +	mp_stream->state = MPS_FINISHED;
> +
> +	return 1;
> +}
> +
> +static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
> +	const char *boundary;
> +	struct mg_iobuf *io = &c->recv;
> +	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> +
> +	if (mp_stream->boundary.len == 0) {
> +		mp_stream->state = MPS_FINALIZE;
> +		LOG(LL_DEBUG,("Invalid request: boundary not initialized"));
> +		return 0;
> +	}
> +
> +	if ((int) io->len < mp_stream->boundary.len + 2) {
> +		return 0;
> +	}
> +
> +	boundary = mg_strstr(mg_str_n((char *) io->buf, io->len), mp_stream->boundary);
> +	if (boundary != NULL) {
> +		const char *boundary_end = (boundary + mp_stream->boundary.len);
> +		if (io->len - (boundary_end - (char *) io->buf) < 4) {
> +			return 0;
> +		}
> +		if (strncmp(boundary_end, "--\r\n", 4) == 0) {
> +			mp_stream->state = MPS_FINALIZE;
> +			mg_iobuf_del(io, 0, (boundary_end - (char *) io->buf) + 4);
> +		} else {
> +			mp_stream->state = MPS_GOT_BOUNDARY;
> +		}
> +	} else {
> +		return 0;
> +	}
> +
> +	return 1;
> +}
> +
> +static size_t mg_get_line_len(const char *buf, size_t buf_len) {
> +	size_t len = 0;
> +	while (len < buf_len && buf[len] != '\n') len++;
> +	return len == buf_len ? 0 : len + 1;
> +}
> +
> +static int mg_http_multipart_process_boundary(struct mg_connection *c) {
> +	int data_size;
> +	const char *boundary, *block_begin;
> +	struct mg_iobuf *io = &c->recv;
> +	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> +	int line_len;
> +	boundary = mg_strstr(mg_str_n((char *) io->buf, io->len), mp_stream->boundary);
> +	block_begin = boundary + mp_stream->boundary.len + 2;
> +	data_size = io->len - (block_begin - (char *) io->buf);
> +	mp_stream->len -= ((2 * mp_stream->boundary.len) + 6);
> +
> +	while (data_size > 0 &&
> +		   (line_len = mg_get_line_len(block_begin, data_size)) != 0) {
> +		mp_stream->len -= (line_len + 2);
> +		if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&
> +			mg_ncasecmp(block_begin, CONTENT_DISPOSITION,
> +						sizeof(CONTENT_DISPOSITION) - 1) == 0) {
> +			struct mg_str header;
> +
> +			header.ptr = block_begin + sizeof(CONTENT_DISPOSITION) - 1;
> +			header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;
> +
> +			mp_stream->part.name = mg_strdup(mg_http_get_header_var(header, mg_str_n("name", 4)));
> +			mp_stream->part.filename = mg_strdup(mg_http_get_header_var(header, mg_str_n("filename", 8)));
> +
> +			block_begin += line_len;
> +			data_size -= line_len;
> +
> +			continue;
> +		}
> +
> +		if (line_len == 2 && mg_ncasecmp(block_begin, "\r\n", 2) == 0) {
> +			if (mp_stream->processing_part != 0) {
> +				mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
> +			}
> +
> +			mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
> +			mp_stream->state = MPS_WAITING_FOR_CHUNK;
> +			mp_stream->processing_part++;
> +
> +			mg_iobuf_del(io, 0, block_begin - (char *) io->buf + 2);
> +			return 1;
> +		}
> +
> +		block_begin += line_len;
> +	}
> +
> +	mp_stream->state = MPS_WAITING_FOR_BOUNDARY;
> +
> +	return 0;
> +}
> +
> +static int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) {
> +	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> +	struct mg_iobuf *io = &c->recv;
> +
> +	const char *boundary;
> +	if ((int) io->len < mp_stream->boundary.len + 6 /* \r\n, --, -- */) {
> +		return 0;
> +	}
> +
> +	boundary = mg_strstr(mg_str_n((char *) io->buf, io->len), mp_stream->boundary);
> +	if (boundary == NULL) {
> +		int data_len = io->len - (mp_stream->boundary.len + 6);
> +		if (data_len > 0) {
> +			size_t consumed = mg_http_multipart_call_handler(
> +					c, MG_EV_HTTP_PART_DATA, (char *) io->buf, (size_t) data_len);
> +			mg_iobuf_del(io, 0, consumed);
> +		}
> +		return 0;
> +	} else if (boundary != NULL) {
> +		size_t data_len = io->len - (mp_stream->boundary.len + 8);
> +		size_t consumed = mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA,
> +														 (char *) io->buf, data_len);
> +		mg_iobuf_del(io, 0, consumed);
> +		if (consumed == data_len) {
> +			mg_iobuf_del(io, 0, 4);
> +			return 1;
> +		} else {
> +			return 0;
> +		}
> +	} else {
> +		return 0;
> +	}
> +}
> +
> +static void mg_http_multipart_continue(struct mg_connection *c) {
> +	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> +	while (1) {
> +		switch (mp_stream->state) {
> +			case MPS_BEGIN: {
> +				mp_stream->state = MPS_WAITING_FOR_BOUNDARY;
> +				break;
> +			}
> +			case MPS_WAITING_FOR_BOUNDARY: {
> +				if (mg_http_multipart_wait_for_boundary(c) == 0) {
> +					return;
> +				}
> +				break;
> +			}
> +			case MPS_GOT_BOUNDARY: {
> +				if (mg_http_multipart_process_boundary(c) == 0) {
> +					return;
> +				}
> +				break;
> +			}
> +			case MPS_WAITING_FOR_CHUNK: {
> +				if (mg_http_multipart_continue_wait_for_chunk(c) == 0) {
> +					return;
> +				}
> +				break;
> +			}
> +			case MPS_FINALIZE: {
> +				if (mg_http_multipart_finalize(c) == 0) {
> +					return;
> +				}
> +				break;
> +			}
> +			case MPS_FINISHED: {
> +				return;
> +			}
> +		}
> +	}
> +}
> +
> +void multipart_upload_handler(struct mg_connection *nc, int ev, void *ev_data, mg_event_handler_t fn,
> +		void __attribute__ ((__unused__)) *fn_data)
> +{
> +	struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> +	struct mg_http_multipart_stream *mp_stream = nc->pfn_data;
> +	struct mg_str *s;
> +
> +	if (mp_stream != NULL && mp_stream->boundary.len != 0) {
> +		if (ev == MG_EV_READ || ev == MG_EV_POLL) {
> +			if (ev == MG_EV_READ) {
> +				mg_http_multipart_continue(nc);
> +			} else if (mp_stream->data_avail) {
> +				/* Try re-delivering the data. */
> +				mg_http_multipart_continue(nc);
> +			}
> +			return;
> +		} else if (ev == MG_EV_CLOSE) {
> +			/*
> +			 * Multipart message is in progress, but connection is closed.
> +			 * Finish part and request with an error flag.
> +			 */
> +			struct mg_http_multipart mp;
> +			memset(&mp, 0, sizeof(mp));
> +			mp.status = -1;
> +			mp.user_data = mp_stream->user_data;
> +			mp.part.name = mp_stream->part.name;
> +			mp.part.filename = mp_stream->part.filename;
> +			mg_call(nc, MG_EV_HTTP_PART_END, &mp);
> +			mp.part.name.ptr = NULL;
> +			mp.part.filename.ptr = NULL;
> +			mg_call(nc, MG_EV_HTTP_MULTIPART_REQUEST_END, &mp);
> +			mp_stream->state = MPS_FINISHED;
> +			return;
> +		}
> +	}
> +
> +	if (hm->chunk.len >= 0 && ev == MG_EV_HTTP_CHUNK) {
> +		s = mg_http_get_header(hm, "Content-Type");
> +		if (s->len >= 9 && strncmp(s->ptr, "multipart", 9) == 0) {
> +			/* New request - new proto data */
> +			nc->label[0] = 'M';
> +
> +			nc->pfn = fn;
> +			nc->pfn_data = calloc(1, sizeof(struct mg_http_multipart_stream));
> +			mg_http_multipart_begin(nc, hm);
> +			mg_http_multipart_continue(nc);
> +			return;
> +		}
> +	}
> +}
> \ No newline at end of file
> diff --git a/mongoose/mongoose_multipart.h b/mongoose/mongoose_multipart.h
> new file mode 100644
> index 0000000..13a31dc
> --- /dev/null
> +++ b/mongoose/mongoose_multipart.h
> @@ -0,0 +1,48 @@
> +/*
> + * Copyright (c) 2004-2013 Sergey Lyubka
> + * Copyright (c) 2013-2020 Cesanta Software Limited
> + * All rights reserved
> + *
> + * SPDX-License-Identifier: GPL-2.0-only
> + *
> + * This software is dual-licensed: you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation. For the terms of this
> + * license, see <http://www.gnu.org/licenses/>.
> + *
> + * You are free to use this software under the terms of the GNU General
> + * Public License, but WITHOUT ANY WARRANTY; without even the implied
> + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> + * See the GNU General Public License for more details.
> + *
> + * Alternatively, you can license this software under a commercial
> + * license, as set out in <https://www.cesanta.com/license>.
> + */
> +
> +#include "mongoose.h"
> +
> +enum {
> +	MG_EV_HTTP_MULTIPART_REQUEST=MG_EV_USER + 1,  // struct mg_http_message *
> +	MG_EV_HTTP_PART_BEGIN,                        // struct mg_http_multipart_part *
> +	MG_EV_HTTP_PART_DATA,                         // struct mg_http_multipart_part *
> +	MG_EV_HTTP_PART_END,                          // struct mg_http_multipart_part *
> +	MG_EV_HTTP_MULTIPART_REQUEST_END              // struct mg_http_multipart_part *
> +};
> +
> +/* HTTP multipart part */
> +struct mg_http_multipart {
> +	struct mg_http_part part;
> +	int status; /* <0 on error */
> +	void *user_data;
> +	/*
> +	 * User handler can indicate how much of the data was consumed
> +	 * by setting this variable. By default, it is assumed that all
> +	 * data has been consumed by the handler.
> +	 * If not all data was consumed, user's handler will be invoked again later
> +	 * with the remainder.
> +	 */
> +	size_t num_data_consumed;
> +	size_t len;
> +};
> +
> +void multipart_upload_handler(struct mg_connection *nc, int ev, void *ev_data, mg_event_handler_t fn, void *fn_data);
James Hilliard Feb. 8, 2022, 7:31 p.m. UTC | #2
On Tue, Feb 8, 2022 at 5:35 AM Stefano Babic <sbabic@denx.de> wrote:
>
> Hi James,
>
> I tested this patchset and I found the following issues:
>
>
> - there is a compatibility issue with command line because the new
> version interpretes the "port" as string, while 6.18 expects an integer.

Ah, actually I think the issue is that the new version expects a string while
the old version can take either a string or an integer, I've been using this
with the old version and it seems to work fine:
--port 0.0.0.0:8080

>
> So starting with -w "-p 8100" does not work. To make working, I replace
> the port with :
>
> "-p localhost:8100"
>
> This works, but raises a compatibility issue, but of course, then the
> webserver listens just from localhost. We need here some kind of
> conversion, so that it works if just the port number is passed.

I'll see if I can make it also handle the port only format.

>
> - from the parameter list:
>
> -l, --listing                  : enable directory listing
> -p, --port <port>              : server port number  (default: 8080)
> -s, --ssl                      : enable ssl support
> -C, --ssl-cert <cert>          : ssl certificate to present to clients
> -K, --ssl-key <key>            : key corresponding to the ssl certificate
> -r, --document-root <path>     : path to document root directory
> (default: .)
> -t, --timeout                  : timeout to check if connection is lost
> (default: check disabled)
> --auth-domain                  : set authentication domain if any
> (default: none)
> --global-auth-file             : set authentication file if any
> (default: none)
>
> I tested -s, -K, -C, -r : they work as expected.
>
> You disabled -t, see later : you have commented the line that set an
> internal timer. Is it supposed to work ? The timer sets a watchdog for
> the upload of the SWU, else it is unknown if the browser will go on to
> send data. This fixed the case when connection stucks or browser
> crashes: without this fix, SWUpdate hangs forever and new updates are
> rejected because SWupdate still expects data from previous connection.

Oh, I must have accidentially removed that and forgot to restore that feature
when reworking things.

>
> Best regards,
> Stefano
>
> On 08.02.22 01:38, James Hilliard wrote:
> > There are significant differences in the new mongoose API which
> > requires significant changes to the interface to support.
> >
> > We need to forward-port mongoose digest auth support as it is no
> > longer built into mongoose.
> >
> > We also need to forward port multipart stream handling.
> >
> > We need to overhaul the broadcast thread handling as the current
> > implementation is not thread safe and can drop messages.
> >
> > Note that mongoose.c and mongoose.h are unmodified from the upstream
> > amalgamations to simplify review, there are a few warnings that
> > probably need to be fixed there.
> >
> > Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
> > ---
> > Changes v4 -> v5:
> >    - update to mongoose 7.6
> >    - rebase/cleanup
> > Changes v3 -> v4:
> >    - size computation fixes
> >    - broadcast download progress
> > Changes v2 -> v3:
> >    - forward port multipart streaming handler
> > ---
> >   mongoose/Makefile             |  10 +-
> >   mongoose/mongoose_interface.c | 641 ++++++++++++++++++++++++----------
> >   mongoose/mongoose_multipart.c | 353 +++++++++++++++++++
> >   mongoose/mongoose_multipart.h |  48 +++
> >   4 files changed, 862 insertions(+), 190 deletions(-)
> >   create mode 100644 mongoose/mongoose_multipart.c
> >   create mode 100644 mongoose/mongoose_multipart.h
> >
> > diff --git a/mongoose/Makefile b/mongoose/Makefile
> > index a6711a2..7dd9f73 100644
> > --- a/mongoose/Makefile
> > +++ b/mongoose/Makefile
> > @@ -6,20 +6,24 @@ ifneq ($(CONFIG_WEBSERVER),)
> >   ifneq ($(CONFIG_MONGOOSE),)
> >   KBUILD_CFLAGS += -DMG_ENABLE_HTTP_STREAMING_MULTIPART=1
> >   KBUILD_CFLAGS += -DMG_ENABLE_HTTP_WEBSOCKET=1 -DMG_ENABLE_THREADS=1
> > +KBUILD_CFLAGS += -DMG_ENABLE_MD5=1
> > +KBUILD_CFLAGS += -DMG_MAX_RECV_BUF_SIZE=262144
> > +KBUILD_CFLAGS += -DMG_ENABLE_LOG=0
> >   ifneq ($(CONFIG_MONGOOSEIPV6),)
> >   KBUILD_CFLAGS += -DMG_ENABLE_IPV6=1
> >   endif
> >   ifneq ($(CONFIG_MONGOOSESSL),)
> >   KBUILD_CFLAGS += -DMG_ENABLE_SSL=1
> > +endif
> >   ifeq ($(CONFIG_SSL_IMPL_OPENSSL)$(CONFIG_SSL_IMPL_WOLFSSL),y)
> > -KBUILD_CFLAGS += -DMG_SSL_IF=MG_SSL_IF_OPENSSL
> > +KBUILD_CFLAGS += -DMG_ENABLE_OPENSSL=1
> >   endif
> >   ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y)
> > -KBUILD_CFLAGS += -DMG_SSL_IF=MG_SSL_IF_MBEDTLS
> > -endif
> > +KBUILD_CFLAGS += -DMG_ENABLE_MBEDTLS=1
> >   endif
> >   endif
> >   endif
> >
> >   lib-$(CONFIG_MONGOOSE)      += mongoose.o \
> > +                        mongoose_multipart.o \
> >                          mongoose_interface.o
> > diff --git a/mongoose/mongoose_interface.c b/mongoose/mongoose_interface.c
> > index 930cbf3..a2729ac 100644
> > --- a/mongoose/mongoose_interface.c
> > +++ b/mongoose/mongoose_interface.c
> > @@ -26,17 +26,17 @@
> >   #include <parselib.h>
> >   #include <progress_ipc.h>
> >   #include <swupdate_settings.h>
> > -#include <time.h>
> > +#include <pctl.h>
> > +#include <pthread.h>
> > +#include <progress.h>
> >
> >   #include "mongoose.h"
> > +#include "mongoose_multipart.h"
> >   #include "util.h"
> >
> >   #define MG_PORT "8080"
> >   #define MG_ROOT "."
> >
> > -/* in seconds. If no packet is received with this timeout, connection is broken */
> > -#define MG_TIMEOUT   120
> > -
> >   struct mongoose_options {
> >       char *root;
> >       bool listing;
> > @@ -53,12 +53,252 @@ struct file_upload_state {
> >       size_t len;
> >       int fd;
> >       bool error_report; /* if set, stop to flood with errors */
> > +     uint8_t percent;
> >   };
> >
> >   static bool run_postupdate;
> >   static unsigned int watchdog_conn = 0;
> > -static struct mg_serve_http_opts s_http_server_opts;
> > -static void upload_handler(struct mg_connection *nc, int ev, void *p);
> > +static struct mg_http_serve_opts s_http_server_opts;
> > +const char *global_auth_domain;
> > +const char *global_auth_file;
> > +#if MG_ENABLE_SSL
> > +static bool ssl;
> > +static struct mg_tls_opts tls_opts;
> > +#endif
> > +
> > +struct ws_msg_elem {
> > +     char *msg;
> > +     SIMPLEQ_ENTRY(ws_msg_elem) next;
> > +};
> > +
> > +SIMPLEQ_HEAD(ws_msglist, ws_msg_elem);
> > +static struct ws_msglist ws_messages;
> > +
> > +static pthread_mutex_t ws_msg_lock = PTHREAD_MUTEX_INITIALIZER;
> > +
> > +static struct mg_connection *ws_pipe;
> > +
> > +static int s_signo;
> > +static void signal_handler(int signo) {
> > +     s_signo = signo;
> > +}
> > +
> > +static int p_stat(const char *path, size_t *size, time_t *mtime)
> > +{
> > +     int flags = mg_fs_posix.stat(path, size, mtime);
> > +     if (flags & MG_FS_DIR && strcmp(s_http_server_opts.root_dir, path) != 0)
> > +             return 0;
> > +     return flags;
> > +}
> > +
> > +static void p_list(const char *path, void (*fn)(const char *, void *), void *userdata)
> > +{
> > +     (void) path, (void) fn, (void) userdata;
> > +}
> > +
> > +static void *p_open(const char *path, int flags)
> > +{
> > +     return mg_fs_posix.open(path, flags);
> > +}
> > +
> > +static void p_close(void *fp)
> > +{
> > +     return mg_fs_posix.close(fp);
> > +}
> > +
> > +static size_t p_read(void *fd, void *buf, size_t len)
> > +{
> > +     return mg_fs_posix.read(fd, buf, len);
> > +}
> > +
> > +static size_t p_write(void *fd, const void *buf, size_t len)
> > +{
> > +     return mg_fs_posix.write(fd, buf, len);
> > +}
> > +
> > +static size_t p_seek(void *fd, size_t offset)
> > +{
> > +     return mg_fs_posix.seek(fd, offset);
> > +}
> > +
> > +static bool p_rename(const char *from, const char *to)
> > +{
> > +     return mg_fs_posix.rename(from, to);
> > +}
> > +
> > +static bool p_remove(const char *path)
> > +{
> > +     return mg_fs_posix.remove(path);
> > +}
> > +
> > +static bool p_mkdir(const char *path)
> > +{
> > +     return mg_fs_posix.mkd(path);
> > +}
> > +
> > +/* mg_fs which inhibits directory listing functionality */
> > +static struct mg_fs fs_posix_no_list = {
> > +             p_stat,
> > +             p_list,
> > +             p_open,
> > +             p_close,
> > +             p_read,
> > +             p_write,
> > +             p_seek,
> > +             p_rename,
> > +             p_remove,
> > +             p_mkdir
> > +};
> > +
> > +/*
> > + * Minimal forward port of mongoose digest auth support.
> > + */
> > +static void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],
> > +                                const size_t *msg_lens, uint8_t *digest)
> > +{
> > +     size_t i;
> > +     mg_md5_ctx md5_ctx;
> > +     mg_md5_init(&md5_ctx);
> > +     for (i = 0; i < num_msgs; i++) {
> > +             mg_md5_update(&md5_ctx, msgs[i], msg_lens[i]);
> > +     }
> > +     mg_md5_final(&md5_ctx, digest);
> > +}
> > +
> > +static void cs_md5(char buf[33], ...)
> > +{
> > +     unsigned char hash[16];
> > +     const uint8_t *msgs[20], *p;
> > +     size_t msg_lens[20];
> > +     size_t num_msgs = 0;
> > +     va_list ap;
> > +
> > +     va_start(ap, buf);
> > +     while ((p = va_arg(ap, const unsigned char *)) != NULL) {
> > +             msgs[num_msgs] = p;
> > +             msg_lens[num_msgs] = va_arg(ap, size_t);
> > +             num_msgs++;
> > +     }
> > +     va_end(ap);
> > +
> > +     mg_hash_md5_v(num_msgs, msgs, msg_lens, hash);
> > +     mg_hex(hash, sizeof(hash), buf);
> > +}
> > +
> > +static void mg_mkmd5resp(struct mg_str method, struct mg_str uri, struct mg_str ha1,
> > +                                              struct mg_str nonce, struct mg_str nc, struct mg_str cnonce,
> > +                                              struct mg_str qop, char *resp)
> > +{
> > +     static const char colon[] = ":";
> > +     static const size_t one = 1;
> > +     char ha2[33];
> > +     cs_md5(ha2, method.ptr, method.len, colon, one, uri.ptr, uri.len, NULL);
> > +     cs_md5(resp, ha1.ptr, ha1.len, colon, one, nonce.ptr, nonce.len, colon, one, nc.ptr,
> > +                nc.len, colon, one, cnonce.ptr, cnonce.len, colon, one, qop.ptr, qop.len,
> > +                colon, one, ha2, sizeof(ha2) - 1, NULL);
> > +}
> > +
> > +static double mg_time(void)
> > +{
> > +    struct timeval tv;
> > +    if (gettimeofday(&tv, NULL /* tz */) != 0) return 0;
> > +    return (double) tv.tv_sec + (((double) tv.tv_usec) / 1000000.0);
> > +}
> > +
> > +/*
> > + * Check for authentication timeout.
> > + * Clients send time stamp encoded in nonce. Make sure it is not too old,
> > + * to prevent replay attacks.
> > + * Assumption: nonce is a hexadecimal number of seconds since 1970.
> > + */
> > +static int mg_check_nonce(struct mg_str nonce)
> > +{
> > +     unsigned long now = (unsigned long) mg_time();
> > +     unsigned long val = (unsigned long) strtoul(nonce.ptr, NULL, 16);
> > +     return (now >= val) && (now - val < 60 * 60);
> > +}
> > +
> > +static int mg_check_digest_auth(struct mg_str method, struct mg_str uri,
> > +                                              struct mg_str username, struct mg_str cnonce,
> > +                                              struct mg_str response, struct mg_str qop,
> > +                                              struct mg_str nc, struct mg_str nonce,
> > +                                              struct mg_str auth_domain, FILE *fp)
> > +{
> > +     char buf[128], f_user[sizeof(buf)], f_ha1[sizeof(buf)], f_domain[sizeof(buf)];
> > +     char exp_resp[33];
> > +
> > +     /*
> > +      * Read passwords file line by line. If should have htdigest format,
> > +      * i.e. each line should be a colon-separated sequence:
> > +      * USER_NAME:DOMAIN_NAME:HA1_HASH_OF_USER_DOMAIN_AND_PASSWORD
> > +      */
> > +     while (fgets(buf, sizeof(buf), fp) != NULL) {
> > +             if (sscanf(buf, "%[^:]:%[^:]:%s", f_user, f_domain, f_ha1) == 3 &&
> > +                     mg_vcmp(&username, f_user) == 0 &&
> > +                     mg_vcmp(&auth_domain, f_domain) == 0) {
> > +                     /* Username and domain matched, check the password */
> > +                     mg_mkmd5resp(method, uri, mg_str_s(f_ha1), nonce, nc, cnonce, qop, exp_resp);
> > +                     return mg_ncasecmp(response.ptr, exp_resp, strlen(exp_resp)) == 0;
> > +             }
> > +     }
> > +
> > +     /* None of the entries in the passwords file matched - return failure */
> > +     return 0;
> > +}
> > +
> > +static int mg_http_check_digest_auth(struct mg_http_message *hm, struct mg_str auth_domain, FILE *fp)
> > +{
> > +     struct mg_str *hdr;
> > +     struct mg_str username, cnonce, response, qop, nc, nonce;
> > +
> > +     /* Parse "Authorization:" header, fail fast on parse error */
> > +     if (hm == NULL ||
> > +             (hdr = mg_http_get_header(hm, "Authorization")) == NULL ||
> > +             (username = mg_http_get_header_var(*hdr, mg_str_n("username", 8))).len == 0 ||
> > +             (cnonce = mg_http_get_header_var(*hdr, mg_str_n("cnonce", 6))).len == 0 ||
> > +             (response = mg_http_get_header_var(*hdr, mg_str_n("response", 8))).len == 0 ||
> > +             mg_http_get_header_var(*hdr, mg_str_n("uri", 3)).len == 0 ||
> > +             (qop = mg_http_get_header_var(*hdr, mg_str_n("qop", 3))).len == 0 ||
> > +             (nc = mg_http_get_header_var(*hdr, mg_str_n("nc", 2))).len == 0 ||
> > +             (nonce = mg_http_get_header_var(*hdr, mg_str_n("nonce", 5))).len == 0 ||
> > +             mg_check_nonce(nonce) == 0) {
> > +             return 0;
> > +     }
> > +
> > +     /* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */
> > +
> > +     return mg_check_digest_auth(
> > +                     hm->method,
> > +                     mg_str_n(
> > +                                     hm->uri.ptr,
> > +                                     hm->uri.len + (hm->query.len ? hm->query.len + 1 : 0)),
> > +                     username, cnonce, response, qop, nc, nonce, auth_domain, fp);
> > +}
> > +
> > +static int mg_http_is_authorized(struct mg_http_message *hm, const char *domain, const char *passwords_file) {
> > +     FILE *fp;
> > +     int authorized = 1;
> > +
> > +     if (domain != NULL && passwords_file != NULL) {
> > +             fp = fopen(passwords_file, "r");
> > +             if (fp != NULL) {
> > +                     authorized = mg_http_check_digest_auth(hm, mg_str(domain), fp);
> > +                     fclose(fp);
> > +             }
> > +     }
> > +
> > +     return authorized;
> > +}
> > +
> > +static void mg_http_send_digest_auth_request(struct mg_connection *c, const char *domain)
> > +{
> > +     mg_printf(c,
> > +                       "HTTP/1.1 401 Unauthorized\r\n"
> > +                       "WWW-Authenticate: Digest qop=\"auth\", "
> > +                       "realm=\"%s\", nonce=\"%lx\"\r\n"
> > +                       "Content-Length: 0\r\n\r\n",
> > +                       domain, (unsigned long) mg_time());
> > +}
> >
> >   /*
> >    * These functions are for V2 of the protocol
> > @@ -100,24 +340,50 @@ static const char *get_source_string(unsigned int source)
> >       return str[source];
> >   }
> >
> > -static void restart_handler(struct mg_connection *nc, int ev, void *ev_data)
> > +static void restart_handler(struct mg_connection *nc, void *ev_data)
> >   {
> > -     struct http_message *hm = (struct http_message *) ev_data;
> > +     struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> >       ipc_message msg = {};
> >
> > -     if (ev == MG_EV_HTTP_REQUEST) {
> > -             if(mg_vcasecmp(&hm->method, "POST") != 0) {
> > -                     mg_http_send_error(nc, 405, "Method Not Allowed");
> > -                     return;
> > -             }
> > +     if(mg_vcasecmp(&hm->method, "POST") != 0) {
> > +             mg_http_reply(nc, 405, "", "%s", "Method Not Allowed\n");
> > +             return;
> > +     }
> > +
> > +     int ret = ipc_postupdate(&msg);
> > +     if (ret || msg.type != ACK) {
> > +             mg_http_reply(nc, 500, "", "%s", "Failed to queue command\n");
> > +             return;
> > +     }
> >
> > -             int ret = ipc_postupdate(&msg);
> > -             if (ret || msg.type != ACK) {
> > -                     mg_http_send_error(nc, 500, "Failed to queue command");
> > -                     return;
> > +     mg_http_reply(nc, 201, "", "%s", "Device will reboot now.\n");
> > +}
> > +
> > +static void broadcast_callback(struct mg_connection *nc, int ev,
> > +             void __attribute__ ((__unused__)) *ev_data, void __attribute__ ((__unused__)) *fn_data)
> > +{
> > +     if (ev == MG_EV_READ) {
> > +             struct mg_connection *t;
> > +             struct ws_msg_elem *ws_msg;
> > +
> > +             pthread_mutex_lock(&ws_msg_lock);
> > +
> > +             while(!SIMPLEQ_EMPTY(&ws_messages)) {
> > +                     ws_msg = SIMPLEQ_FIRST(&ws_messages);
> > +                     SIMPLEQ_REMOVE_HEAD(&ws_messages, next);
> > +
> > +                     pthread_mutex_unlock(&ws_msg_lock);
> > +
> > +                     for (t = nc->mgr->conns; t != NULL; t = t->next) {
> > +                             if (t->label[0] != 'W') continue;
> > +                             mg_ws_send(t, ws_msg->msg, strlen(ws_msg->msg), WEBSOCKET_OP_TEXT);
> > +                     }
> > +
> > +                     free(ws_msg);
> > +                     pthread_mutex_lock(&ws_msg_lock);
> >               }
> >
> > -             mg_http_send_error(nc, 201, "Device will reboot now.");
> > +             pthread_mutex_unlock(&ws_msg_lock);
> >       }
> >   }
> >
> > @@ -137,25 +403,26 @@ static int level_to_rfc_5424(int level)
> >       }
> >   }
> >
> > -static void broadcast_callback(struct mg_connection *nc, int ev, void *ev_data)
> > +static void broadcast(char *str)
> >   {
> > -     char *buf = (char *) ev_data;
> > +     unsigned int len = str ? strlen(str) : 0;
> > +     struct ws_msg_elem *ws_msg = (struct ws_msg_elem*)calloc(1, sizeof(*ws_msg) + len + 1);
> >
> > -     if (ev != MG_EV_POLL)
> > +     if (!ws_msg)
> >               return;
> >
> > -     if (!(nc->flags & MG_F_IS_WEBSOCKET))
> > -             return;
> > +     ws_msg->msg = (char *)ws_msg + sizeof(struct ws_msg_elem);
> >
> > -     mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf));
> > -}
> > +     if (str)
> > +             strncpy(ws_msg->msg, str, len);
> >
> > -static void broadcast(struct mg_mgr *mgr, char *str)
> > -{
> > -     mg_broadcast(mgr, broadcast_callback, str, strlen(str) + 1);
> > +     pthread_mutex_lock(&ws_msg_lock);
> > +     SIMPLEQ_INSERT_TAIL(&ws_messages, ws_msg, next);
> > +     pthread_mutex_unlock(&ws_msg_lock);
> > +     mg_mgr_wakeup(ws_pipe, "", 0);
> >   }
> >
> > -static void *broadcast_message_thread(void *data)
> > +static void *broadcast_message_thread(void __attribute__ ((__unused__)) *data)
> >   {
> >       int fd = -1;
> >
> > @@ -177,8 +444,8 @@ static void *broadcast_message_thread(void *data)
> >               if (ret != sizeof(msg))
> >                       return NULL;
> >
> > -             if (strlen(msg.data.notify.msg) != 0) {
> > -                     struct mg_mgr *mgr = (struct mg_mgr *) data;
> > +             if (strlen(msg.data.notify.msg) != 0 &&
> > +                             msg.data.status.current != PROGRESS) {
> >                       char text[4096];
> >                       char str[4160];
> >
> > @@ -193,23 +460,20 @@ static void *broadcast_message_thread(void *data)
> >                                        level_to_rfc_5424(msg.data.notify.level), /* RFC 5424 */
> >                                        text);
> >
> > -                     broadcast(mgr, str);
> > +                     broadcast(str);
> >               }
> >       }
> > -
> > -     return NULL;
> >   }
> >
> > -static void *broadcast_progress_thread(void *data)
> > +static void *broadcast_progress_thread(void __attribute__ ((__unused__)) *data)
> >   {
> >       RECOVERY_STATUS status = -1;
> >       sourcetype source = -1;
> >       unsigned int step = 0;
> > -     unsigned int percent = 0;
> > +     uint8_t percent = 0;
> >       int fd = -1;
> >
> >       for (;;) {
> > -             struct mg_mgr *mgr = (struct mg_mgr *) data;
> >               struct progress_msg msg;
> >               char str[512];
> >               char escaped[512];
> > @@ -241,7 +505,7 @@ static void *broadcast_progress_thread(void *data)
> >                               "\t\"status\": \"%s\"\r\n"
> >                               "}\r\n",
> >                               escaped);
> > -                     broadcast(mgr, str);
> > +                     broadcast(str);
> >               }
> >
> >               if (msg.source != source) {
> > @@ -253,7 +517,7 @@ static void *broadcast_progress_thread(void *data)
> >                               "\t\"source\": \"%s\"\r\n"
> >                               "}\r\n",
> >                               get_source_string(msg.source));
> > -                     broadcast(mgr, str);
> > +                     broadcast(str);
> >               }
> >
> >               if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER && run_postupdate) {
> > @@ -271,7 +535,7 @@ static void *broadcast_progress_thread(void *data)
> >                               "\t\"source\": \"%s\"\r\n"
> >                               "}\r\n",
> >                               escaped);
> > -                     broadcast(mgr, str);
> > +                     broadcast(str);
> >               }
> >
> >               if ((msg.cur_step != step || msg.cur_percent != percent) &&
> > @@ -293,147 +557,166 @@ static void *broadcast_progress_thread(void *data)
> >                               msg.cur_step,
> >                               escaped,
> >                               msg.cur_percent);
> > -                     broadcast(mgr, str);
> > +                     broadcast(str);
> >               }
> >       }
> > -
> > -     return NULL;
> >   }
> >
> >   /*
> >    * Code common to V1 and V2
> >    */
> > -static void upload_handler(struct mg_connection *nc, int ev, void *p)
> > +static void upload_handler(struct mg_connection *nc, int ev, void *ev_data,
> > +             void __attribute__ ((__unused__)) *fn_data)
> >   {
> > -     struct mg_http_multipart_part *mp;
> > +     struct mg_http_multipart *mp;
> >       struct file_upload_state *fus;
> > +     unsigned int percent;
> >       ssize_t written;
> >
> >       switch (ev) {
> > -     case MG_EV_HTTP_PART_BEGIN:
> > -             mp = (struct mg_http_multipart_part *) p;
> > -
> > -             fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
> > -             if (fus == NULL) {
> > -                     mg_http_send_error(nc, 500, "Out of memory");
> > -                     break;
> > -             }
> > -
> > -             struct swupdate_request req;
> > -             swupdate_prepare_req(&req);
> > -             req.len = strlen(mp->file_name);
> > -             strncpy(req.info, mp->file_name, sizeof(req.info) - 1);
> > -             req.source = SOURCE_WEBSERVER;
> > -             fus->fd = ipc_inst_start_ext(&req, sizeof(req));
> > -             if (fus->fd < 0) {
> > -                     mg_http_send_error(nc, 500, "Failed to queue command");
> > -                     free(fus);
> > -                     break;
> > -             }
> > +             case MG_EV_HTTP_PART_BEGIN:
> > +                     mp = (struct mg_http_multipart *) ev_data;
> > +
> > +                     fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
> > +                     if (fus == NULL) {
> > +                             mg_http_reply(nc, 500, "", "%s", "Out of memory\n");
> > +                             nc->is_closing = 1;
> > +                             break;
> > +                     }
> >
> > -             if (swupdate_file_setnonblock(fus->fd, true)) {
> > -                     WARN("IPC cannot be set in non-blocking, fallback to block mode");
> > -             }
> > +                     struct swupdate_request req;
> > +                     swupdate_prepare_req(&req);
> > +                     req.len = mp->len;
> > +                     strncpy(req.info, mp->part.filename.ptr, sizeof(req.info) - 1);
> > +                     req.source = SOURCE_WEBSERVER;
> > +                     fus->fd = ipc_inst_start_ext(&req, sizeof(req));
> > +                     if (fus->fd < 0) {
> > +                             mg_http_reply(nc, 500, "", "%s", "Failed to queue command\n");
> > +                             nc->is_closing = 1;
> > +                             free(fus);
> > +                             break;
> > +                     }
> >
> > -             mp->user_data = fus;
> > +                     swupdate_download_update(0, mp->len);
> >
> > -             /*
> > -              * There is no user data for connection.
> > -              * Set the user data to the same structure to make it available
> > -              * to the MG_TIMER event
> > -              */
> > -             nc->user_data = mp->user_data;
> > +                     if (swupdate_file_setnonblock(fus->fd, true)) {
> > +                             WARN("IPC cannot be set in non-blocking, fallback to block mode");
> > +                     }
> >
> > -             if (watchdog_conn > 0) {
> > -                     TRACE("Setting Webserver Watchdog Timer to %d", watchdog_conn);
> > -                     mg_set_timer(nc, mg_time() + watchdog_conn);
> > -             }
> > +                     mp->user_data = fus;
> >
> > -             break;
> > +                     /*
> > +                      * There is no user data for connection.
> > +                      * Set the user data to the same structure to make it available
> > +                      * to the MG_TIMER event
> > +                      */
> > +                     nc->fn_data = mp->user_data;
> >
> > -     case MG_EV_HTTP_PART_DATA:
> > -             mp = (struct mg_http_multipart_part *) p;
> > -             fus = (struct file_upload_state *) mp->user_data;
> > +                     if (watchdog_conn > 0) {
> > +                             TRACE("Setting Webserver Watchdog Timer to %d", watchdog_conn);
> > +                             //mg_set_timer(nc, mg_time() + watchdog_conn);
> > +                     }
> >
> > -             if (!fus)
> >                       break;
> >
> > -             written = write(fus->fd, (char *) mp->data.p, mp->data.len);
> > -             /*
> > -              * IPC seems to block, wait for a while
> > -              */
> > -             if (written != mp->data.len) {
> > -                     if (written < 0) {
> > -                             if (errno != EAGAIN && errno != EWOULDBLOCK) {
> > -                                     if (!fus->error_report) {
> > -                                             ERROR("Writing to IPC fails due to %s", strerror(errno));
> > -                                             fus->error_report = true;
> > -                                     }
> > -                                     /*
> > -                                      * Simply consumes the data to unblock the sender
> > -                                      */
> > -                                     written = mp->data.len;
> > -                             } else
> > -                                     written = 0;
> > +             case MG_EV_HTTP_PART_DATA:
> > +                     mp = (struct mg_http_multipart *) ev_data;
> > +                     fus = (struct file_upload_state *) mp->user_data;
> > +
> > +                     if (!fus)
> > +                             break;
> > +
> > +                     written = write(fus->fd, (char *) mp->part.body.ptr, mp->part.body.len);
> > +                     /*
> > +                      * IPC seems to block, wait for a while
> > +                      */
> > +                     if (written != mp->part.body.len) {
> > +                             if (written < 0) {
> > +                                     if (errno != EAGAIN && errno != EWOULDBLOCK) {
> > +                                             if ((mp->part.body.len + fus->len) == mp->len) {
> > +                                                     /*
> > +                                                      * Simply consumes the data to unblock the sender
> > +                                                      */
> > +                                                     written = mp->part.body.len;
> > +                                             } else if (!fus->error_report) {
> > +                                                     ERROR("Writing to IPC fails due to %s", strerror(errno));
> > +                                                     fus->error_report = true;
> > +                                                     nc->is_closing = 1;
> > +                                             }
> > +                                     } else
> > +                                             written = 0;
> > +                             }
> > +                             usleep(100);
> >                       }
> > -                     usleep(100);
> > -             }
> >
> > -             mp->num_data_consumed = written;
> > -             fus->len += written;
> > +                     mp->num_data_consumed = written;
> > +                     fus->len += written;
> > +                     percent = (uint8_t)(100.0 * ((double)fus->len / (double)mp->len));
> > +                     if (percent != fus->percent) {
> > +                             fus->percent = percent;
> > +                             swupdate_download_update(fus->percent, mp->len);
> > +                     }
> >
> > -             break;
> > +                     break;
> >
> > -     case MG_EV_HTTP_PART_END:
> > -             mp = (struct mg_http_multipart_part *) p;
> > -             fus = (struct file_upload_state *) mp->user_data;
> > +             case MG_EV_HTTP_PART_END:
> > +                     mp = (struct mg_http_multipart *) ev_data;
> > +                     fus = (struct file_upload_state *) mp->user_data;
> >
> > -             if (!fus)
> > -                     break;
> > +                     if (!fus)
> > +                             break;
> >
> > -             ipc_end(fus->fd);
> > +                     ipc_end(fus->fd);
> >
> > -             mg_send_response_line(nc, 200,
> > -                     "Content-Type: text/plain\r\n"
> > -                     "Connection: close");
> > -             mg_send(nc, "\r\n", 2);
> > -             mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len);
> > -             nc->flags |= MG_F_SEND_AND_CLOSE;
> > +                     mg_http_reply(nc, 200, "%s",
> > +                                                               "Content-Type: text/plain\r\n"
> > +                                                               "Connection: close");
> > +                     mg_send(nc, "\r\n", 2);
> > +                     mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->part.filename, (int) fus->len);
> > +                     nc->is_closing = 1;
> >
> > -             mp->user_data = NULL;
> > -             nc->user_data = mp->user_data;
> > -             free(fus);
> > -             break;
> > +                     mp->user_data = NULL;
> > +                     nc->fn_data = mp->user_data;
> > +                     free(fus);
> > +                     break;
> >       }
> >   }
> >
> > -static void ev_handler(struct mg_connection *nc, int ev, void *ev_data)
> > +static void websocket_handler(struct mg_connection *nc, void *ev_data)
> >   {
> > -     time_t now;
> > +     struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> > +     mg_ws_upgrade(nc, hm, NULL);
> > +     nc->label[0] = 'W';
> > +}
> >
> > -     switch (ev) {
> > -     case MG_EV_HTTP_REQUEST:
> > -             mg_serve_http(nc, ev_data, s_http_server_opts);
> > -             break;
> > -     case MG_EV_TIMER:
> > -             now = (time_t) mg_time();
> > -             /*
> > -              * Check if a multi-part was initiated
> > -              */
> > -             if (nc->user_data && (watchdog_conn > 0) &&
> > -                     (difftime(now, nc->last_io_time) > watchdog_conn)) {
> > -                     struct file_upload_state *fus;
> > -
> > -                    /* Connection lost, drop data */
> > -                     ERROR("Connection lost, no data since %ld now %ld, closing...",
> > -                             nc->last_io_time, now);
> > -                     fus = (struct file_upload_state *) nc->user_data;
> > -                     ipc_end(fus->fd);
> > -                     nc->user_data = NULL;
> > -                     nc->flags |= MG_F_CLOSE_IMMEDIATELY;
> > -             } else
> > -                     mg_set_timer(nc, mg_time() + watchdog_conn);  // Send us timer event again after 0.5 seconds
> > -             break;
> > +static void ev_handler(struct mg_connection *nc, int ev, void *ev_data, void *fn_data)
> > +{
> > +     if (ev == MG_EV_HTTP_MSG) {
> > +             struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> > +             if (!mg_http_is_authorized(hm, global_auth_domain, global_auth_file))
> > +                     mg_http_send_digest_auth_request(nc, global_auth_domain);
> > +             else if (mg_http_get_header(hm, "Sec-WebSocket-Key") != NULL)
> > +                     websocket_handler(nc, ev_data);
> > +             else if (mg_http_match_uri(hm, "/restart"))
> > +                     restart_handler(nc, ev_data);
> > +             else
> > +                     mg_http_serve_dir(nc, ev_data, &s_http_server_opts);
> > +     } else if (nc->label[0] != 'M' && ev == MG_EV_HTTP_CHUNK) {
> > +             struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> > +             if (!mg_http_is_authorized(hm, global_auth_domain, global_auth_file))
> > +                     mg_http_send_digest_auth_request(nc, global_auth_domain);
> > +             else if (mg_http_match_uri(hm, "/upload"))
> > +                     multipart_upload_handler(nc, ev, ev_data, upload_handler, NULL);
> > +     } else if (nc->label[0] == 'M' && (ev == MG_EV_READ || ev == MG_EV_POLL || ev == MG_EV_CLOSE)) {
> > +             multipart_upload_handler(nc, ev, ev_data, upload_handler, fn_data);
> > +#if MG_ENABLE_SSL
> > +     } else if (ev == MG_EV_ACCEPT && ssl) {
> > +             mg_tls_init(nc, &tls_opts);
> > +#endif
> > +     } else if (ev == MG_EV_ERROR) {
> > +             ERROR("%p %s", nc->fd, (char *) ev_data);
> > +     } else if (ev == MG_EV_WS_MSG) {
> > +             mg_iobuf_del(&nc->recv, 0, nc->recv.len);
> >       }
> >   }
> >
> > @@ -521,13 +804,12 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[])
> >       struct mongoose_options opts;
> >       struct mg_mgr mgr;
> >       struct mg_connection *nc;
> > -     struct mg_bind_opts bind_opts;
> >       const char *s_http_port = NULL;
> > -     const char *err_str;
> > +     int choice;
> > +
> >   #if MG_ENABLE_SSL
> > -     bool ssl = false;
> > +     ssl = false;
> >   #endif
> > -     int choice = 0;
> >
> >       memset(&opts, 0, sizeof(opts));
> >
> > @@ -598,61 +880,46 @@ int start_mongoose(const char *cfgfname, int argc, char *argv[])
> >               }
> >       }
> >
> > -     s_http_server_opts.document_root =
> > +     s_http_server_opts.root_dir =
> >               opts.root ? opts.root : MG_ROOT;
> > -     s_http_server_opts.enable_directory_listing =
> > -             opts.listing ? "yes" : "no";
> > +     if (!opts.listing)
> > +             s_http_server_opts.fs = &fs_posix_no_list;
> >       s_http_port = opts.port ? opts.port : MG_PORT;
> > -     s_http_server_opts.global_auth_file = opts.global_auth_file;
> > -     s_http_server_opts.auth_domain = opts.auth_domain;
> > +     global_auth_file = opts.global_auth_file;
> > +     global_auth_domain = opts.auth_domain;
> >
> > -     memset(&bind_opts, 0, sizeof(bind_opts));
> > -     bind_opts.error_string = &err_str;
> >   #if MG_ENABLE_SSL
> >       if (ssl) {
> > -             bind_opts.ssl_cert = opts.ssl_cert;
> > -             bind_opts.ssl_key = opts.ssl_key;
> > +             tls_opts.cert = opts.ssl_cert;
> > +             tls_opts.certkey = opts.ssl_key;
> >       }
> >   #endif
> >
> > -     mg_mgr_init(&mgr, NULL);
> > +     signal(SIGINT, signal_handler);
> > +     signal(SIGTERM, signal_handler);
> > +     mg_mgr_init(&mgr);
> >
> > -     nc = mg_bind_opt(&mgr, s_http_port, ev_handler, bind_opts);
> > +     SIMPLEQ_INIT(&ws_messages);
> > +
> > +     ws_pipe = mg_mkpipe(&mgr, broadcast_callback, NULL);
> > +     nc = mg_http_listen(&mgr, s_http_port, ev_handler, NULL);
> >       if (nc == NULL) {
> > -             ERROR("Failed to start Mongoose: %s", *bind_opts.error_string);
> > +             ERROR("Failed to start Mongoose.");
> >               exit(EXIT_FAILURE);
> >       }
> >
> > -     /*
> > -      * The Event Handler in Webserver will read from socket until there is data.
> > -      * This does not guarantes a flow control because data are forwarded
> > -      * to SWUpdate internal IPC. If this is not called in blocking mode,
> > -      * the Webserver should just read from socket to fill the IPC, but without
> > -      * filling all memory.
> > -      */
> > -     nc->recv_mbuf_limit = 256 * 1024;
> > -
> > -     mg_set_protocol_http_websocket(nc);
> > -     mg_register_http_endpoint(nc, "/restart", restart_handler);
> > -     mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler, NULL));
> > -     mg_start_thread(broadcast_message_thread, &mgr);
> > -     mg_start_thread(broadcast_progress_thread, &mgr);
> > +     start_thread(broadcast_message_thread, NULL);
> > +     start_thread(broadcast_progress_thread, NULL);
> >
> >       INFO("Mongoose web server version %s with pid %d started on port(s) %s with web root [%s]",
> >               MG_VERSION, getpid(), s_http_port,
> > -             s_http_server_opts.document_root);
> > +             s_http_server_opts.root_dir);
> >
> > -     for (;;) {
> > +     while (s_signo == 0)
> >               mg_mgr_poll(&mgr, 100);
> > -     }
> >       mg_mgr_free(&mgr);
> > +     if (opts.port)
> > +             free(opts.port);
> >
> >       return 0;
> >   }
> > -
> > -#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_MBEDTLS
> > -#include <mbedtls/ctr_drbg.h>
> > -int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len) {
> > -     return mbedtls_ctr_drbg_random(ctx, buf, len);
> > -}
> > -#endif
> > diff --git a/mongoose/mongoose_multipart.c b/mongoose/mongoose_multipart.c
> > new file mode 100644
> > index 0000000..e956c6f
> > --- /dev/null
> > +++ b/mongoose/mongoose_multipart.c
> > @@ -0,0 +1,353 @@
> > +/*
> > + * Copyright (c) 2004-2013 Sergey Lyubka
> > + * Copyright (c) 2013-2020 Cesanta Software Limited
> > + * All rights reserved
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-only
> > + *
> > + * This software is dual-licensed: you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation. For the terms of this
> > + * license, see <http://www.gnu.org/licenses/>.
> > + *
> > + * You are free to use this software under the terms of the GNU General
> > + * Public License, but WITHOUT ANY WARRANTY; without even the implied
> > + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> > + * See the GNU General Public License for more details.
> > + *
> > + * Alternatively, you can license this software under a commercial
> > + * license, as set out in <https://www.cesanta.com/license>.
> > + */
> > +
> > +#include "mongoose_multipart.h"
> > +
> > +enum mg_http_multipart_stream_state {
> > +     MPS_BEGIN,
> > +     MPS_WAITING_FOR_BOUNDARY,
> > +     MPS_WAITING_FOR_CHUNK,
> > +     MPS_GOT_BOUNDARY,
> > +     MPS_FINALIZE,
> > +     MPS_FINISHED
> > +};
> > +
> > +struct mg_http_multipart_stream {
> > +     struct mg_http_part part;
> > +     struct mg_str boundary;
> > +     void *user_data;
> > +     enum mg_http_multipart_stream_state state;
> > +     int processing_part;
> > +     int data_avail;
> > +     size_t len;
> > +};
> > +
> > +static void mg_http_free_proto_data_mp_stream(
> > +             struct mg_http_multipart_stream *mp) {
> > +     free((void *) mp->boundary.ptr);
> > +     free((void *) mp->part.name.ptr);
> > +     free((void *) mp->part.filename.ptr);
> > +     memset(mp, 0, sizeof(*mp));
> > +}
> > +
> > +static void mg_http_multipart_begin(struct mg_connection *nc,
> > +                                                                     struct mg_http_message *hm) {
> > +     struct mg_http_multipart_stream *mp_stream = nc->pfn_data;
> > +     struct mg_str *ct;
> > +     struct mg_iobuf *io = &nc->recv;
> > +
> > +     struct mg_str boundary;
> > +
> > +     ct = mg_http_get_header(hm, "Content-Type");
> > +     if (ct == NULL) {
> > +             /* We need more data - or it isn't multipart mesage */
> > +             return;
> > +     }
> > +
> > +     /* Content-type should start with "multipart" */
> > +     if (ct->len < 9 || strncmp(ct->ptr, "multipart", 9) != 0) {
> > +             return;
> > +     }
> > +
> > +     boundary = mg_http_get_header_var(*ct, mg_str_n("boundary", 8));
> > +     if (boundary.len == 0) {
> > +             /*
> > +              * Content type is multipart, but there is no boundary,
> > +              * probably malformed request
> > +              */
> > +             nc->is_closing = 1;
> > +             LOG(LL_DEBUG,("invalid request"));
> > +             return;
> > +     }
> > +
> > +     /* If we reach this place - that is multipart request */
> > +
> > +     if (mp_stream->boundary.len != 0) {
> > +             /*
> > +              * Another streaming request was in progress,
> > +              * looks like protocol error
> > +              */
> > +             nc->is_closing = 1;
> > +     } else {
> > +             mp_stream->state = MPS_BEGIN;
> > +             mp_stream->boundary = mg_strdup(boundary);
> > +             mp_stream->part.name.ptr = mp_stream->part.filename.ptr = NULL;
> > +             mp_stream->part.name.len = mp_stream->part.filename.len = 0;
> > +             mp_stream->len = hm->body.len;
> > +
> > +             mg_call(nc, MG_EV_HTTP_MULTIPART_REQUEST, hm);
> > +
> > +             mg_iobuf_del(io, 0, hm->head.len + 2);
> > +     }
> > +}
> > +
> > +#define CONTENT_DISPOSITION "Content-Disposition: "
> > +
> > +static size_t mg_http_multipart_call_handler(struct mg_connection *c, int ev,
> > +                                                                                      const char *data,
> > +                                                                                      size_t data_len) {
> > +     struct mg_http_multipart mp;
> > +     struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> > +     memset(&mp, 0, sizeof(mp));
> > +
> > +     mp.part.name = mp_stream->part.name;
> > +     mp.part.filename = mp_stream->part.filename;
> > +     mp.user_data = mp_stream->user_data;
> > +     mp.part.body.ptr = data;
> > +     mp.part.body.len = data_len;
> > +     mp.num_data_consumed = data_len;
> > +     mp.len = mp_stream->len;
> > +     mg_call(c, ev, &mp);
> > +     mp_stream->user_data = mp.user_data;
> > +     mp_stream->data_avail = (mp.num_data_consumed != data_len);
> > +     return mp.num_data_consumed;
> > +}
> > +
> > +static int mg_http_multipart_finalize(struct mg_connection *c) {
> > +     struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> > +
> > +     mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
> > +     free((void *) mp_stream->part.filename.ptr);
> > +     mp_stream->part.filename.ptr = NULL;
> > +     free((void *) mp_stream->part.name.ptr);
> > +     mp_stream->part.name.ptr = NULL;
> > +     mg_http_multipart_call_handler(c, MG_EV_HTTP_MULTIPART_REQUEST_END, NULL, 0);
> > +     mg_http_free_proto_data_mp_stream(mp_stream);
> > +     mp_stream->state = MPS_FINISHED;
> > +
> > +     return 1;
> > +}
> > +
> > +static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
> > +     const char *boundary;
> > +     struct mg_iobuf *io = &c->recv;
> > +     struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> > +
> > +     if (mp_stream->boundary.len == 0) {
> > +             mp_stream->state = MPS_FINALIZE;
> > +             LOG(LL_DEBUG,("Invalid request: boundary not initialized"));
> > +             return 0;
> > +     }
> > +
> > +     if ((int) io->len < mp_stream->boundary.len + 2) {
> > +             return 0;
> > +     }
> > +
> > +     boundary = mg_strstr(mg_str_n((char *) io->buf, io->len), mp_stream->boundary);
> > +     if (boundary != NULL) {
> > +             const char *boundary_end = (boundary + mp_stream->boundary.len);
> > +             if (io->len - (boundary_end - (char *) io->buf) < 4) {
> > +                     return 0;
> > +             }
> > +             if (strncmp(boundary_end, "--\r\n", 4) == 0) {
> > +                     mp_stream->state = MPS_FINALIZE;
> > +                     mg_iobuf_del(io, 0, (boundary_end - (char *) io->buf) + 4);
> > +             } else {
> > +                     mp_stream->state = MPS_GOT_BOUNDARY;
> > +             }
> > +     } else {
> > +             return 0;
> > +     }
> > +
> > +     return 1;
> > +}
> > +
> > +static size_t mg_get_line_len(const char *buf, size_t buf_len) {
> > +     size_t len = 0;
> > +     while (len < buf_len && buf[len] != '\n') len++;
> > +     return len == buf_len ? 0 : len + 1;
> > +}
> > +
> > +static int mg_http_multipart_process_boundary(struct mg_connection *c) {
> > +     int data_size;
> > +     const char *boundary, *block_begin;
> > +     struct mg_iobuf *io = &c->recv;
> > +     struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> > +     int line_len;
> > +     boundary = mg_strstr(mg_str_n((char *) io->buf, io->len), mp_stream->boundary);
> > +     block_begin = boundary + mp_stream->boundary.len + 2;
> > +     data_size = io->len - (block_begin - (char *) io->buf);
> > +     mp_stream->len -= ((2 * mp_stream->boundary.len) + 6);
> > +
> > +     while (data_size > 0 &&
> > +                (line_len = mg_get_line_len(block_begin, data_size)) != 0) {
> > +             mp_stream->len -= (line_len + 2);
> > +             if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&
> > +                     mg_ncasecmp(block_begin, CONTENT_DISPOSITION,
> > +                                             sizeof(CONTENT_DISPOSITION) - 1) == 0) {
> > +                     struct mg_str header;
> > +
> > +                     header.ptr = block_begin + sizeof(CONTENT_DISPOSITION) - 1;
> > +                     header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;
> > +
> > +                     mp_stream->part.name = mg_strdup(mg_http_get_header_var(header, mg_str_n("name", 4)));
> > +                     mp_stream->part.filename = mg_strdup(mg_http_get_header_var(header, mg_str_n("filename", 8)));
> > +
> > +                     block_begin += line_len;
> > +                     data_size -= line_len;
> > +
> > +                     continue;
> > +             }
> > +
> > +             if (line_len == 2 && mg_ncasecmp(block_begin, "\r\n", 2) == 0) {
> > +                     if (mp_stream->processing_part != 0) {
> > +                             mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
> > +                     }
> > +
> > +                     mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
> > +                     mp_stream->state = MPS_WAITING_FOR_CHUNK;
> > +                     mp_stream->processing_part++;
> > +
> > +                     mg_iobuf_del(io, 0, block_begin - (char *) io->buf + 2);
> > +                     return 1;
> > +             }
> > +
> > +             block_begin += line_len;
> > +     }
> > +
> > +     mp_stream->state = MPS_WAITING_FOR_BOUNDARY;
> > +
> > +     return 0;
> > +}
> > +
> > +static int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) {
> > +     struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> > +     struct mg_iobuf *io = &c->recv;
> > +
> > +     const char *boundary;
> > +     if ((int) io->len < mp_stream->boundary.len + 6 /* \r\n, --, -- */) {
> > +             return 0;
> > +     }
> > +
> > +     boundary = mg_strstr(mg_str_n((char *) io->buf, io->len), mp_stream->boundary);
> > +     if (boundary == NULL) {
> > +             int data_len = io->len - (mp_stream->boundary.len + 6);
> > +             if (data_len > 0) {
> > +                     size_t consumed = mg_http_multipart_call_handler(
> > +                                     c, MG_EV_HTTP_PART_DATA, (char *) io->buf, (size_t) data_len);
> > +                     mg_iobuf_del(io, 0, consumed);
> > +             }
> > +             return 0;
> > +     } else if (boundary != NULL) {
> > +             size_t data_len = io->len - (mp_stream->boundary.len + 8);
> > +             size_t consumed = mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA,
> > +                                                                                                              (char *) io->buf, data_len);
> > +             mg_iobuf_del(io, 0, consumed);
> > +             if (consumed == data_len) {
> > +                     mg_iobuf_del(io, 0, 4);
> > +                     return 1;
> > +             } else {
> > +                     return 0;
> > +             }
> > +     } else {
> > +             return 0;
> > +     }
> > +}
> > +
> > +static void mg_http_multipart_continue(struct mg_connection *c) {
> > +     struct mg_http_multipart_stream *mp_stream = c->pfn_data;
> > +     while (1) {
> > +             switch (mp_stream->state) {
> > +                     case MPS_BEGIN: {
> > +                             mp_stream->state = MPS_WAITING_FOR_BOUNDARY;
> > +                             break;
> > +                     }
> > +                     case MPS_WAITING_FOR_BOUNDARY: {
> > +                             if (mg_http_multipart_wait_for_boundary(c) == 0) {
> > +                                     return;
> > +                             }
> > +                             break;
> > +                     }
> > +                     case MPS_GOT_BOUNDARY: {
> > +                             if (mg_http_multipart_process_boundary(c) == 0) {
> > +                                     return;
> > +                             }
> > +                             break;
> > +                     }
> > +                     case MPS_WAITING_FOR_CHUNK: {
> > +                             if (mg_http_multipart_continue_wait_for_chunk(c) == 0) {
> > +                                     return;
> > +                             }
> > +                             break;
> > +                     }
> > +                     case MPS_FINALIZE: {
> > +                             if (mg_http_multipart_finalize(c) == 0) {
> > +                                     return;
> > +                             }
> > +                             break;
> > +                     }
> > +                     case MPS_FINISHED: {
> > +                             return;
> > +                     }
> > +             }
> > +     }
> > +}
> > +
> > +void multipart_upload_handler(struct mg_connection *nc, int ev, void *ev_data, mg_event_handler_t fn,
> > +             void __attribute__ ((__unused__)) *fn_data)
> > +{
> > +     struct mg_http_message *hm = (struct mg_http_message *) ev_data;
> > +     struct mg_http_multipart_stream *mp_stream = nc->pfn_data;
> > +     struct mg_str *s;
> > +
> > +     if (mp_stream != NULL && mp_stream->boundary.len != 0) {
> > +             if (ev == MG_EV_READ || ev == MG_EV_POLL) {
> > +                     if (ev == MG_EV_READ) {
> > +                             mg_http_multipart_continue(nc);
> > +                     } else if (mp_stream->data_avail) {
> > +                             /* Try re-delivering the data. */
> > +                             mg_http_multipart_continue(nc);
> > +                     }
> > +                     return;
> > +             } else if (ev == MG_EV_CLOSE) {
> > +                     /*
> > +                      * Multipart message is in progress, but connection is closed.
> > +                      * Finish part and request with an error flag.
> > +                      */
> > +                     struct mg_http_multipart mp;
> > +                     memset(&mp, 0, sizeof(mp));
> > +                     mp.status = -1;
> > +                     mp.user_data = mp_stream->user_data;
> > +                     mp.part.name = mp_stream->part.name;
> > +                     mp.part.filename = mp_stream->part.filename;
> > +                     mg_call(nc, MG_EV_HTTP_PART_END, &mp);
> > +                     mp.part.name.ptr = NULL;
> > +                     mp.part.filename.ptr = NULL;
> > +                     mg_call(nc, MG_EV_HTTP_MULTIPART_REQUEST_END, &mp);
> > +                     mp_stream->state = MPS_FINISHED;
> > +                     return;
> > +             }
> > +     }
> > +
> > +     if (hm->chunk.len >= 0 && ev == MG_EV_HTTP_CHUNK) {
> > +             s = mg_http_get_header(hm, "Content-Type");
> > +             if (s->len >= 9 && strncmp(s->ptr, "multipart", 9) == 0) {
> > +                     /* New request - new proto data */
> > +                     nc->label[0] = 'M';
> > +
> > +                     nc->pfn = fn;
> > +                     nc->pfn_data = calloc(1, sizeof(struct mg_http_multipart_stream));
> > +                     mg_http_multipart_begin(nc, hm);
> > +                     mg_http_multipart_continue(nc);
> > +                     return;
> > +             }
> > +     }
> > +}
> > \ No newline at end of file
> > diff --git a/mongoose/mongoose_multipart.h b/mongoose/mongoose_multipart.h
> > new file mode 100644
> > index 0000000..13a31dc
> > --- /dev/null
> > +++ b/mongoose/mongoose_multipart.h
> > @@ -0,0 +1,48 @@
> > +/*
> > + * Copyright (c) 2004-2013 Sergey Lyubka
> > + * Copyright (c) 2013-2020 Cesanta Software Limited
> > + * All rights reserved
> > + *
> > + * SPDX-License-Identifier: GPL-2.0-only
> > + *
> > + * This software is dual-licensed: you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation. For the terms of this
> > + * license, see <http://www.gnu.org/licenses/>.
> > + *
> > + * You are free to use this software under the terms of the GNU General
> > + * Public License, but WITHOUT ANY WARRANTY; without even the implied
> > + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
> > + * See the GNU General Public License for more details.
> > + *
> > + * Alternatively, you can license this software under a commercial
> > + * license, as set out in <https://www.cesanta.com/license>.
> > + */
> > +
> > +#include "mongoose.h"
> > +
> > +enum {
> > +     MG_EV_HTTP_MULTIPART_REQUEST=MG_EV_USER + 1,  // struct mg_http_message *
> > +     MG_EV_HTTP_PART_BEGIN,                        // struct mg_http_multipart_part *
> > +     MG_EV_HTTP_PART_DATA,                         // struct mg_http_multipart_part *
> > +     MG_EV_HTTP_PART_END,                          // struct mg_http_multipart_part *
> > +     MG_EV_HTTP_MULTIPART_REQUEST_END              // struct mg_http_multipart_part *
> > +};
> > +
> > +/* HTTP multipart part */
> > +struct mg_http_multipart {
> > +     struct mg_http_part part;
> > +     int status; /* <0 on error */
> > +     void *user_data;
> > +     /*
> > +      * User handler can indicate how much of the data was consumed
> > +      * by setting this variable. By default, it is assumed that all
> > +      * data has been consumed by the handler.
> > +      * If not all data was consumed, user's handler will be invoked again later
> > +      * with the remainder.
> > +      */
> > +     size_t num_data_consumed;
> > +     size_t len;
> > +};
> > +
> > +void multipart_upload_handler(struct mg_connection *nc, int ev, void *ev_data, mg_event_handler_t fn, void *fn_data);
>
>
> --
> =====================================================================
> DENX Software Engineering GmbH,      Managing Director: Wolfgang Denk
> HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
> Phone: +49-8142-66989-53 Fax: +49-8142-66989-80 Email: sbabic@denx.de
> =====================================================================
diff mbox series

Patch

diff --git a/mongoose/Makefile b/mongoose/Makefile
index a6711a2..7dd9f73 100644
--- a/mongoose/Makefile
+++ b/mongoose/Makefile
@@ -6,20 +6,24 @@  ifneq ($(CONFIG_WEBSERVER),)
 ifneq ($(CONFIG_MONGOOSE),)
 KBUILD_CFLAGS += -DMG_ENABLE_HTTP_STREAMING_MULTIPART=1
 KBUILD_CFLAGS += -DMG_ENABLE_HTTP_WEBSOCKET=1 -DMG_ENABLE_THREADS=1
+KBUILD_CFLAGS += -DMG_ENABLE_MD5=1
+KBUILD_CFLAGS += -DMG_MAX_RECV_BUF_SIZE=262144
+KBUILD_CFLAGS += -DMG_ENABLE_LOG=0
 ifneq ($(CONFIG_MONGOOSEIPV6),)
 KBUILD_CFLAGS += -DMG_ENABLE_IPV6=1
 endif
 ifneq ($(CONFIG_MONGOOSESSL),)
 KBUILD_CFLAGS += -DMG_ENABLE_SSL=1
+endif
 ifeq ($(CONFIG_SSL_IMPL_OPENSSL)$(CONFIG_SSL_IMPL_WOLFSSL),y)
-KBUILD_CFLAGS += -DMG_SSL_IF=MG_SSL_IF_OPENSSL
+KBUILD_CFLAGS += -DMG_ENABLE_OPENSSL=1
 endif
 ifeq ($(CONFIG_SSL_IMPL_MBEDTLS),y)
-KBUILD_CFLAGS += -DMG_SSL_IF=MG_SSL_IF_MBEDTLS
-endif
+KBUILD_CFLAGS += -DMG_ENABLE_MBEDTLS=1
 endif
 endif
 endif
 
 lib-$(CONFIG_MONGOOSE)	+= mongoose.o \
+			   mongoose_multipart.o \
 			   mongoose_interface.o
diff --git a/mongoose/mongoose_interface.c b/mongoose/mongoose_interface.c
index 930cbf3..a2729ac 100644
--- a/mongoose/mongoose_interface.c
+++ b/mongoose/mongoose_interface.c
@@ -26,17 +26,17 @@ 
 #include <parselib.h>
 #include <progress_ipc.h>
 #include <swupdate_settings.h>
-#include <time.h>
+#include <pctl.h>
+#include <pthread.h>
+#include <progress.h>
 
 #include "mongoose.h"
+#include "mongoose_multipart.h"
 #include "util.h"
 
 #define MG_PORT "8080"
 #define MG_ROOT "."
 
-/* in seconds. If no packet is received with this timeout, connection is broken */
-#define MG_TIMEOUT	120
-
 struct mongoose_options {
 	char *root;
 	bool listing;
@@ -53,12 +53,252 @@  struct file_upload_state {
 	size_t len;
 	int fd;
 	bool error_report; /* if set, stop to flood with errors */
+	uint8_t percent;
 };
 
 static bool run_postupdate;
 static unsigned int watchdog_conn = 0;
-static struct mg_serve_http_opts s_http_server_opts;
-static void upload_handler(struct mg_connection *nc, int ev, void *p);
+static struct mg_http_serve_opts s_http_server_opts;
+const char *global_auth_domain;
+const char *global_auth_file;
+#if MG_ENABLE_SSL
+static bool ssl;
+static struct mg_tls_opts tls_opts;
+#endif
+
+struct ws_msg_elem {
+	char *msg;
+	SIMPLEQ_ENTRY(ws_msg_elem) next;
+};
+
+SIMPLEQ_HEAD(ws_msglist, ws_msg_elem);
+static struct ws_msglist ws_messages;
+
+static pthread_mutex_t ws_msg_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static struct mg_connection *ws_pipe;
+
+static int s_signo;
+static void signal_handler(int signo) {
+	s_signo = signo;
+}
+
+static int p_stat(const char *path, size_t *size, time_t *mtime)
+{
+	int flags = mg_fs_posix.stat(path, size, mtime);
+	if (flags & MG_FS_DIR && strcmp(s_http_server_opts.root_dir, path) != 0)
+		return 0;
+	return flags;
+}
+
+static void p_list(const char *path, void (*fn)(const char *, void *), void *userdata)
+{
+	(void) path, (void) fn, (void) userdata;
+}
+
+static void *p_open(const char *path, int flags)
+{
+	return mg_fs_posix.open(path, flags);
+}
+
+static void p_close(void *fp)
+{
+	return mg_fs_posix.close(fp);
+}
+
+static size_t p_read(void *fd, void *buf, size_t len)
+{
+	return mg_fs_posix.read(fd, buf, len);
+}
+
+static size_t p_write(void *fd, const void *buf, size_t len)
+{
+	return mg_fs_posix.write(fd, buf, len);
+}
+
+static size_t p_seek(void *fd, size_t offset)
+{
+	return mg_fs_posix.seek(fd, offset);
+}
+
+static bool p_rename(const char *from, const char *to)
+{
+	return mg_fs_posix.rename(from, to);
+}
+
+static bool p_remove(const char *path)
+{
+	return mg_fs_posix.remove(path);
+}
+
+static bool p_mkdir(const char *path)
+{
+	return mg_fs_posix.mkd(path);
+}
+
+/* mg_fs which inhibits directory listing functionality */
+static struct mg_fs fs_posix_no_list = {
+		p_stat,
+		p_list,
+		p_open,
+		p_close,
+		p_read,
+		p_write,
+		p_seek,
+		p_rename,
+		p_remove,
+		p_mkdir
+};
+
+/*
+ * Minimal forward port of mongoose digest auth support.
+ */
+static void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],
+				   const size_t *msg_lens, uint8_t *digest)
+{
+	size_t i;
+	mg_md5_ctx md5_ctx;
+	mg_md5_init(&md5_ctx);
+	for (i = 0; i < num_msgs; i++) {
+		mg_md5_update(&md5_ctx, msgs[i], msg_lens[i]);
+	}
+	mg_md5_final(&md5_ctx, digest);
+}
+
+static void cs_md5(char buf[33], ...)
+{
+	unsigned char hash[16];
+	const uint8_t *msgs[20], *p;
+	size_t msg_lens[20];
+	size_t num_msgs = 0;
+	va_list ap;
+
+	va_start(ap, buf);
+	while ((p = va_arg(ap, const unsigned char *)) != NULL) {
+		msgs[num_msgs] = p;
+		msg_lens[num_msgs] = va_arg(ap, size_t);
+		num_msgs++;
+	}
+	va_end(ap);
+
+	mg_hash_md5_v(num_msgs, msgs, msg_lens, hash);
+	mg_hex(hash, sizeof(hash), buf);
+}
+
+static void mg_mkmd5resp(struct mg_str method, struct mg_str uri, struct mg_str ha1,
+						 struct mg_str nonce, struct mg_str nc, struct mg_str cnonce,
+						 struct mg_str qop, char *resp)
+{
+	static const char colon[] = ":";
+	static const size_t one = 1;
+	char ha2[33];
+	cs_md5(ha2, method.ptr, method.len, colon, one, uri.ptr, uri.len, NULL);
+	cs_md5(resp, ha1.ptr, ha1.len, colon, one, nonce.ptr, nonce.len, colon, one, nc.ptr,
+		   nc.len, colon, one, cnonce.ptr, cnonce.len, colon, one, qop.ptr, qop.len,
+		   colon, one, ha2, sizeof(ha2) - 1, NULL);
+}
+
+static double mg_time(void)
+{
+    struct timeval tv;
+    if (gettimeofday(&tv, NULL /* tz */) != 0) return 0;
+    return (double) tv.tv_sec + (((double) tv.tv_usec) / 1000000.0);
+}
+
+/*
+ * Check for authentication timeout.
+ * Clients send time stamp encoded in nonce. Make sure it is not too old,
+ * to prevent replay attacks.
+ * Assumption: nonce is a hexadecimal number of seconds since 1970.
+ */
+static int mg_check_nonce(struct mg_str nonce)
+{
+	unsigned long now = (unsigned long) mg_time();
+	unsigned long val = (unsigned long) strtoul(nonce.ptr, NULL, 16);
+	return (now >= val) && (now - val < 60 * 60);
+}
+
+static int mg_check_digest_auth(struct mg_str method, struct mg_str uri,
+						 struct mg_str username, struct mg_str cnonce,
+						 struct mg_str response, struct mg_str qop,
+						 struct mg_str nc, struct mg_str nonce,
+						 struct mg_str auth_domain, FILE *fp)
+{
+	char buf[128], f_user[sizeof(buf)], f_ha1[sizeof(buf)], f_domain[sizeof(buf)];
+	char exp_resp[33];
+
+	/*
+	 * Read passwords file line by line. If should have htdigest format,
+	 * i.e. each line should be a colon-separated sequence:
+	 * USER_NAME:DOMAIN_NAME:HA1_HASH_OF_USER_DOMAIN_AND_PASSWORD
+	 */
+	while (fgets(buf, sizeof(buf), fp) != NULL) {
+		if (sscanf(buf, "%[^:]:%[^:]:%s", f_user, f_domain, f_ha1) == 3 &&
+			mg_vcmp(&username, f_user) == 0 &&
+			mg_vcmp(&auth_domain, f_domain) == 0) {
+			/* Username and domain matched, check the password */
+			mg_mkmd5resp(method, uri, mg_str_s(f_ha1), nonce, nc, cnonce, qop, exp_resp);
+			return mg_ncasecmp(response.ptr, exp_resp, strlen(exp_resp)) == 0;
+		}
+	}
+
+	/* None of the entries in the passwords file matched - return failure */
+	return 0;
+}
+
+static int mg_http_check_digest_auth(struct mg_http_message *hm, struct mg_str auth_domain, FILE *fp)
+{
+	struct mg_str *hdr;
+	struct mg_str username, cnonce, response, qop, nc, nonce;
+
+	/* Parse "Authorization:" header, fail fast on parse error */
+	if (hm == NULL ||
+		(hdr = mg_http_get_header(hm, "Authorization")) == NULL ||
+		(username = mg_http_get_header_var(*hdr, mg_str_n("username", 8))).len == 0 ||
+		(cnonce = mg_http_get_header_var(*hdr, mg_str_n("cnonce", 6))).len == 0 ||
+		(response = mg_http_get_header_var(*hdr, mg_str_n("response", 8))).len == 0 ||
+		mg_http_get_header_var(*hdr, mg_str_n("uri", 3)).len == 0 ||
+		(qop = mg_http_get_header_var(*hdr, mg_str_n("qop", 3))).len == 0 ||
+		(nc = mg_http_get_header_var(*hdr, mg_str_n("nc", 2))).len == 0 ||
+		(nonce = mg_http_get_header_var(*hdr, mg_str_n("nonce", 5))).len == 0 ||
+		mg_check_nonce(nonce) == 0) {
+		return 0;
+	}
+
+	/* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */
+
+	return mg_check_digest_auth(
+			hm->method,
+			mg_str_n(
+					hm->uri.ptr,
+					hm->uri.len + (hm->query.len ? hm->query.len + 1 : 0)),
+			username, cnonce, response, qop, nc, nonce, auth_domain, fp);
+}
+
+static int mg_http_is_authorized(struct mg_http_message *hm, const char *domain, const char *passwords_file) {
+	FILE *fp;
+	int authorized = 1;
+
+	if (domain != NULL && passwords_file != NULL) {
+		fp = fopen(passwords_file, "r");
+		if (fp != NULL) {
+			authorized = mg_http_check_digest_auth(hm, mg_str(domain), fp);
+			fclose(fp);
+		}
+	}
+
+	return authorized;
+}
+
+static void mg_http_send_digest_auth_request(struct mg_connection *c, const char *domain)
+{
+	mg_printf(c,
+			  "HTTP/1.1 401 Unauthorized\r\n"
+			  "WWW-Authenticate: Digest qop=\"auth\", "
+			  "realm=\"%s\", nonce=\"%lx\"\r\n"
+			  "Content-Length: 0\r\n\r\n",
+			  domain, (unsigned long) mg_time());
+}
 
 /*
  * These functions are for V2 of the protocol
@@ -100,24 +340,50 @@  static const char *get_source_string(unsigned int source)
 	return str[source];
 }
 
-static void restart_handler(struct mg_connection *nc, int ev, void *ev_data)
+static void restart_handler(struct mg_connection *nc, void *ev_data)
 {
-	struct http_message *hm = (struct http_message *) ev_data;
+	struct mg_http_message *hm = (struct mg_http_message *) ev_data;
 	ipc_message msg = {};
 
-	if (ev == MG_EV_HTTP_REQUEST) {
-		if(mg_vcasecmp(&hm->method, "POST") != 0) {
-			mg_http_send_error(nc, 405, "Method Not Allowed");
-			return;
-		}
+	if(mg_vcasecmp(&hm->method, "POST") != 0) {
+		mg_http_reply(nc, 405, "", "%s", "Method Not Allowed\n");
+		return;
+	}
+
+	int ret = ipc_postupdate(&msg);
+	if (ret || msg.type != ACK) {
+		mg_http_reply(nc, 500, "", "%s", "Failed to queue command\n");
+		return;
+	}
 
-		int ret = ipc_postupdate(&msg);
-		if (ret || msg.type != ACK) {
-			mg_http_send_error(nc, 500, "Failed to queue command");
-			return;
+	mg_http_reply(nc, 201, "", "%s", "Device will reboot now.\n");
+}
+
+static void broadcast_callback(struct mg_connection *nc, int ev,
+		void __attribute__ ((__unused__)) *ev_data, void __attribute__ ((__unused__)) *fn_data)
+{
+	if (ev == MG_EV_READ) {
+		struct mg_connection *t;
+		struct ws_msg_elem *ws_msg;
+
+		pthread_mutex_lock(&ws_msg_lock);
+
+		while(!SIMPLEQ_EMPTY(&ws_messages)) {
+			ws_msg = SIMPLEQ_FIRST(&ws_messages);
+			SIMPLEQ_REMOVE_HEAD(&ws_messages, next);
+
+			pthread_mutex_unlock(&ws_msg_lock);
+
+			for (t = nc->mgr->conns; t != NULL; t = t->next) {
+				if (t->label[0] != 'W') continue;
+				mg_ws_send(t, ws_msg->msg, strlen(ws_msg->msg), WEBSOCKET_OP_TEXT);
+			}
+
+			free(ws_msg);
+			pthread_mutex_lock(&ws_msg_lock);
 		}
 
-		mg_http_send_error(nc, 201, "Device will reboot now.");
+		pthread_mutex_unlock(&ws_msg_lock);
 	}
 }
 
@@ -137,25 +403,26 @@  static int level_to_rfc_5424(int level)
 	}
 }
 
-static void broadcast_callback(struct mg_connection *nc, int ev, void *ev_data)
+static void broadcast(char *str)
 {
-	char *buf = (char *) ev_data;
+	unsigned int len = str ? strlen(str) : 0;
+	struct ws_msg_elem *ws_msg = (struct ws_msg_elem*)calloc(1, sizeof(*ws_msg) + len + 1);
 
-	if (ev != MG_EV_POLL)
+	if (!ws_msg)
 		return;
 
-	if (!(nc->flags & MG_F_IS_WEBSOCKET))
-		return;
+	ws_msg->msg = (char *)ws_msg + sizeof(struct ws_msg_elem);
 
-	mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, buf, strlen(buf));
-}
+	if (str)
+		strncpy(ws_msg->msg, str, len);
 
-static void broadcast(struct mg_mgr *mgr, char *str)
-{
-	mg_broadcast(mgr, broadcast_callback, str, strlen(str) + 1);
+	pthread_mutex_lock(&ws_msg_lock);
+	SIMPLEQ_INSERT_TAIL(&ws_messages, ws_msg, next);
+	pthread_mutex_unlock(&ws_msg_lock);
+	mg_mgr_wakeup(ws_pipe, "", 0);
 }
 
-static void *broadcast_message_thread(void *data)
+static void *broadcast_message_thread(void __attribute__ ((__unused__)) *data)
 {
 	int fd = -1;
 
@@ -177,8 +444,8 @@  static void *broadcast_message_thread(void *data)
 		if (ret != sizeof(msg))
 			return NULL;
 
-		if (strlen(msg.data.notify.msg) != 0) {
-			struct mg_mgr *mgr = (struct mg_mgr *) data;
+		if (strlen(msg.data.notify.msg) != 0 &&
+				msg.data.status.current != PROGRESS) {
 			char text[4096];
 			char str[4160];
 
@@ -193,23 +460,20 @@  static void *broadcast_message_thread(void *data)
 					 level_to_rfc_5424(msg.data.notify.level), /* RFC 5424 */
 					 text);
 
-			broadcast(mgr, str);
+			broadcast(str);
 		}
 	}
-
-	return NULL;
 }
 
-static void *broadcast_progress_thread(void *data)
+static void *broadcast_progress_thread(void __attribute__ ((__unused__)) *data)
 {
 	RECOVERY_STATUS status = -1;
 	sourcetype source = -1;
 	unsigned int step = 0;
-	unsigned int percent = 0;
+	uint8_t percent = 0;
 	int fd = -1;
 
 	for (;;) {
-		struct mg_mgr *mgr = (struct mg_mgr *) data;
 		struct progress_msg msg;
 		char str[512];
 		char escaped[512];
@@ -241,7 +505,7 @@  static void *broadcast_progress_thread(void *data)
 				"\t\"status\": \"%s\"\r\n"
 				"}\r\n",
 				escaped);
-			broadcast(mgr, str);
+			broadcast(str);
 		}
 
 		if (msg.source != source) {
@@ -253,7 +517,7 @@  static void *broadcast_progress_thread(void *data)
 				"\t\"source\": \"%s\"\r\n"
 				"}\r\n",
 				get_source_string(msg.source));
-			broadcast(mgr, str);
+			broadcast(str);
 		}
 
 		if (msg.status == SUCCESS && msg.source == SOURCE_WEBSERVER && run_postupdate) {
@@ -271,7 +535,7 @@  static void *broadcast_progress_thread(void *data)
 				"\t\"source\": \"%s\"\r\n"
 				"}\r\n",
 				escaped);
-			broadcast(mgr, str);
+			broadcast(str);
 		}
 
 		if ((msg.cur_step != step || msg.cur_percent != percent) &&
@@ -293,147 +557,166 @@  static void *broadcast_progress_thread(void *data)
 				msg.cur_step,
 				escaped,
 				msg.cur_percent);
-			broadcast(mgr, str);
+			broadcast(str);
 		}
 	}
-
-	return NULL;
 }
 
 /*
  * Code common to V1 and V2
  */
-static void upload_handler(struct mg_connection *nc, int ev, void *p)
+static void upload_handler(struct mg_connection *nc, int ev, void *ev_data,
+		void __attribute__ ((__unused__)) *fn_data)
 {
-	struct mg_http_multipart_part *mp;
+	struct mg_http_multipart *mp;
 	struct file_upload_state *fus;
+	unsigned int percent;
 	ssize_t written;
 
 	switch (ev) {
-	case MG_EV_HTTP_PART_BEGIN:
-		mp = (struct mg_http_multipart_part *) p;
-
-		fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
-		if (fus == NULL) {
-			mg_http_send_error(nc, 500, "Out of memory");
-			break;
-		}
-
-		struct swupdate_request req;
-		swupdate_prepare_req(&req);
-		req.len = strlen(mp->file_name);
-		strncpy(req.info, mp->file_name, sizeof(req.info) - 1);
-		req.source = SOURCE_WEBSERVER;
-		fus->fd = ipc_inst_start_ext(&req, sizeof(req));
-		if (fus->fd < 0) {
-			mg_http_send_error(nc, 500, "Failed to queue command");
-			free(fus);
-			break;
-		}
+		case MG_EV_HTTP_PART_BEGIN:
+			mp = (struct mg_http_multipart *) ev_data;
+
+			fus = (struct file_upload_state *) calloc(1, sizeof(*fus));
+			if (fus == NULL) {
+				mg_http_reply(nc, 500, "", "%s", "Out of memory\n");
+				nc->is_closing = 1;
+				break;
+			}
 
-		if (swupdate_file_setnonblock(fus->fd, true)) {
-			WARN("IPC cannot be set in non-blocking, fallback to block mode");
-		}
+			struct swupdate_request req;
+			swupdate_prepare_req(&req);
+			req.len = mp->len;
+			strncpy(req.info, mp->part.filename.ptr, sizeof(req.info) - 1);
+			req.source = SOURCE_WEBSERVER;
+			fus->fd = ipc_inst_start_ext(&req, sizeof(req));
+			if (fus->fd < 0) {
+				mg_http_reply(nc, 500, "", "%s", "Failed to queue command\n");
+				nc->is_closing = 1;
+				free(fus);
+				break;
+			}
 
-		mp->user_data = fus;
+			swupdate_download_update(0, mp->len);
 
-		/*
-		 * There is no user data for connection.
-		 * Set the user data to the same structure to make it available
-		 * to the MG_TIMER event
-		 */
-		nc->user_data = mp->user_data;
+			if (swupdate_file_setnonblock(fus->fd, true)) {
+				WARN("IPC cannot be set in non-blocking, fallback to block mode");
+			}
 
-		if (watchdog_conn > 0) {
-			TRACE("Setting Webserver Watchdog Timer to %d", watchdog_conn);
-			mg_set_timer(nc, mg_time() + watchdog_conn);
-		}
+			mp->user_data = fus;
 
-		break;
+			/*
+			 * There is no user data for connection.
+			 * Set the user data to the same structure to make it available
+			 * to the MG_TIMER event
+			 */
+			nc->fn_data = mp->user_data;
 
-	case MG_EV_HTTP_PART_DATA:
-		mp = (struct mg_http_multipart_part *) p;
-		fus = (struct file_upload_state *) mp->user_data;
+			if (watchdog_conn > 0) {
+				TRACE("Setting Webserver Watchdog Timer to %d", watchdog_conn);
+				//mg_set_timer(nc, mg_time() + watchdog_conn);
+			}
 
-		if (!fus)
 			break;
 
-		written = write(fus->fd, (char *) mp->data.p, mp->data.len);
-		/*
-		 * IPC seems to block, wait for a while
-		 */
-		if (written != mp->data.len) {
-			if (written < 0) {
-				if (errno != EAGAIN && errno != EWOULDBLOCK) {
-					if (!fus->error_report) {
-						ERROR("Writing to IPC fails due to %s", strerror(errno));
-						fus->error_report = true;
-					}
-					/*
-					 * Simply consumes the data to unblock the sender
-					 */
-					written = mp->data.len;
-				} else
-					written = 0;
+		case MG_EV_HTTP_PART_DATA:
+			mp = (struct mg_http_multipart *) ev_data;
+			fus = (struct file_upload_state *) mp->user_data;
+
+			if (!fus)
+				break;
+
+			written = write(fus->fd, (char *) mp->part.body.ptr, mp->part.body.len);
+			/*
+			 * IPC seems to block, wait for a while
+			 */
+			if (written != mp->part.body.len) {
+				if (written < 0) {
+					if (errno != EAGAIN && errno != EWOULDBLOCK) {
+						if ((mp->part.body.len + fus->len) == mp->len) {
+							/*
+							 * Simply consumes the data to unblock the sender
+							 */
+							written = mp->part.body.len;
+						} else if (!fus->error_report) {
+							ERROR("Writing to IPC fails due to %s", strerror(errno));
+							fus->error_report = true;
+							nc->is_closing = 1;
+						}
+					} else
+						written = 0;
+				}
+				usleep(100);
 			}
-			usleep(100);
-		}
 
-		mp->num_data_consumed = written;
-		fus->len += written;
+			mp->num_data_consumed = written;
+			fus->len += written;
+			percent = (uint8_t)(100.0 * ((double)fus->len / (double)mp->len));
+			if (percent != fus->percent) {
+				fus->percent = percent;
+				swupdate_download_update(fus->percent, mp->len);
+			}
 
-		break;
+			break;
 
-	case MG_EV_HTTP_PART_END:
-		mp = (struct mg_http_multipart_part *) p;
-		fus = (struct file_upload_state *) mp->user_data;
+		case MG_EV_HTTP_PART_END:
+			mp = (struct mg_http_multipart *) ev_data;
+			fus = (struct file_upload_state *) mp->user_data;
 
-		if (!fus)
-			break;
+			if (!fus)
+				break;
 
-		ipc_end(fus->fd);
+			ipc_end(fus->fd);
 
-		mg_send_response_line(nc, 200,
-			"Content-Type: text/plain\r\n"
-			"Connection: close");
-		mg_send(nc, "\r\n", 2);
-		mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->file_name, (int) fus->len);
-		nc->flags |= MG_F_SEND_AND_CLOSE;
+			mg_http_reply(nc, 200, "%s",
+								  "Content-Type: text/plain\r\n"
+								  "Connection: close");
+			mg_send(nc, "\r\n", 2);
+			mg_printf(nc, "Ok, %s - %d bytes.\r\n", mp->part.filename, (int) fus->len);
+			nc->is_closing = 1;
 
-		mp->user_data = NULL;
-		nc->user_data = mp->user_data;
-		free(fus);
-		break;
+			mp->user_data = NULL;
+			nc->fn_data = mp->user_data;
+			free(fus);
+			break;
 	}
 }
 
-static void ev_handler(struct mg_connection *nc, int ev, void *ev_data)
+static void websocket_handler(struct mg_connection *nc, void *ev_data)
 {
-	time_t now;
+	struct mg_http_message *hm = (struct mg_http_message *) ev_data;
+	mg_ws_upgrade(nc, hm, NULL);
+	nc->label[0] = 'W';
+}
 
-	switch (ev) {
-	case MG_EV_HTTP_REQUEST:
-		mg_serve_http(nc, ev_data, s_http_server_opts);
-		break;
-	case MG_EV_TIMER:
-		now = (time_t) mg_time();
-		/*
-		 * Check if a multi-part was initiated
-		 */
-		if (nc->user_data && (watchdog_conn > 0) &&
-			(difftime(now, nc->last_io_time) > watchdog_conn)) {
-			struct file_upload_state *fus;
-
-		       /* Connection lost, drop data */
-			ERROR("Connection lost, no data since %ld now %ld, closing...",
-				nc->last_io_time, now);
-			fus = (struct file_upload_state *) nc->user_data;
-			ipc_end(fus->fd);
-			nc->user_data = NULL;
-			nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-		} else
-			mg_set_timer(nc, mg_time() + watchdog_conn);  // Send us timer event again after 0.5 seconds
-		break;
+static void ev_handler(struct mg_connection *nc, int ev, void *ev_data, void *fn_data)
+{
+	if (ev == MG_EV_HTTP_MSG) {
+		struct mg_http_message *hm = (struct mg_http_message *) ev_data;
+		if (!mg_http_is_authorized(hm, global_auth_domain, global_auth_file))
+			mg_http_send_digest_auth_request(nc, global_auth_domain);
+		else if (mg_http_get_header(hm, "Sec-WebSocket-Key") != NULL)
+			websocket_handler(nc, ev_data);
+		else if (mg_http_match_uri(hm, "/restart"))
+			restart_handler(nc, ev_data);
+		else
+			mg_http_serve_dir(nc, ev_data, &s_http_server_opts);
+	} else if (nc->label[0] != 'M' && ev == MG_EV_HTTP_CHUNK) {
+		struct mg_http_message *hm = (struct mg_http_message *) ev_data;
+		if (!mg_http_is_authorized(hm, global_auth_domain, global_auth_file))
+			mg_http_send_digest_auth_request(nc, global_auth_domain);
+		else if (mg_http_match_uri(hm, "/upload"))
+			multipart_upload_handler(nc, ev, ev_data, upload_handler, NULL);
+	} else if (nc->label[0] == 'M' && (ev == MG_EV_READ || ev == MG_EV_POLL || ev == MG_EV_CLOSE)) {
+		multipart_upload_handler(nc, ev, ev_data, upload_handler, fn_data);
+#if MG_ENABLE_SSL
+	} else if (ev == MG_EV_ACCEPT && ssl) {
+		mg_tls_init(nc, &tls_opts);
+#endif
+	} else if (ev == MG_EV_ERROR) {
+		ERROR("%p %s", nc->fd, (char *) ev_data);
+	} else if (ev == MG_EV_WS_MSG) {
+		mg_iobuf_del(&nc->recv, 0, nc->recv.len);
 	}
 }
 
@@ -521,13 +804,12 @@  int start_mongoose(const char *cfgfname, int argc, char *argv[])
 	struct mongoose_options opts;
 	struct mg_mgr mgr;
 	struct mg_connection *nc;
-	struct mg_bind_opts bind_opts;
 	const char *s_http_port = NULL;
-	const char *err_str;
+	int choice;
+
 #if MG_ENABLE_SSL
-	bool ssl = false;
+	ssl = false;
 #endif
-	int choice = 0;
 
 	memset(&opts, 0, sizeof(opts));
 
@@ -598,61 +880,46 @@  int start_mongoose(const char *cfgfname, int argc, char *argv[])
 		}
 	}
 
-	s_http_server_opts.document_root =
+	s_http_server_opts.root_dir =
 		opts.root ? opts.root : MG_ROOT;
-	s_http_server_opts.enable_directory_listing =
-		opts.listing ? "yes" : "no";
+	if (!opts.listing)
+		s_http_server_opts.fs = &fs_posix_no_list;
 	s_http_port = opts.port ? opts.port : MG_PORT;
-	s_http_server_opts.global_auth_file = opts.global_auth_file;
-	s_http_server_opts.auth_domain = opts.auth_domain;
+	global_auth_file = opts.global_auth_file;
+	global_auth_domain = opts.auth_domain;
 
-	memset(&bind_opts, 0, sizeof(bind_opts));
-	bind_opts.error_string = &err_str;
 #if MG_ENABLE_SSL
 	if (ssl) {
-		bind_opts.ssl_cert = opts.ssl_cert;
-		bind_opts.ssl_key = opts.ssl_key;
+		tls_opts.cert = opts.ssl_cert;
+		tls_opts.certkey = opts.ssl_key;
 	}
 #endif
 
-	mg_mgr_init(&mgr, NULL);
+	signal(SIGINT, signal_handler);
+	signal(SIGTERM, signal_handler);
+	mg_mgr_init(&mgr);
 
-	nc = mg_bind_opt(&mgr, s_http_port, ev_handler, bind_opts);
+	SIMPLEQ_INIT(&ws_messages);
+
+	ws_pipe = mg_mkpipe(&mgr, broadcast_callback, NULL);
+	nc = mg_http_listen(&mgr, s_http_port, ev_handler, NULL);
 	if (nc == NULL) {
-		ERROR("Failed to start Mongoose: %s", *bind_opts.error_string);
+		ERROR("Failed to start Mongoose.");
 		exit(EXIT_FAILURE);
 	}
 
-	/*
-	 * The Event Handler in Webserver will read from socket until there is data.
-	 * This does not guarantes a flow control because data are forwarded
-	 * to SWUpdate internal IPC. If this is not called in blocking mode,
-	 * the Webserver should just read from socket to fill the IPC, but without
-	 * filling all memory.
-	 */
-	nc->recv_mbuf_limit = 256 * 1024;
-
-	mg_set_protocol_http_websocket(nc);
-	mg_register_http_endpoint(nc, "/restart", restart_handler);
-	mg_register_http_endpoint(nc, "/upload", MG_CB(upload_handler, NULL));
-	mg_start_thread(broadcast_message_thread, &mgr);
-	mg_start_thread(broadcast_progress_thread, &mgr);
+	start_thread(broadcast_message_thread, NULL);
+	start_thread(broadcast_progress_thread, NULL);
 
 	INFO("Mongoose web server version %s with pid %d started on port(s) %s with web root [%s]",
 		MG_VERSION, getpid(), s_http_port,
-		s_http_server_opts.document_root);
+		s_http_server_opts.root_dir);
 
-	for (;;) {
+	while (s_signo == 0)
 		mg_mgr_poll(&mgr, 100);
-	}
 	mg_mgr_free(&mgr);
+	if (opts.port)
+		free(opts.port);
 
 	return 0;
 }
-
-#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_MBEDTLS
-#include <mbedtls/ctr_drbg.h>
-int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len) {
-	return mbedtls_ctr_drbg_random(ctx, buf, len);
-}
-#endif
diff --git a/mongoose/mongoose_multipart.c b/mongoose/mongoose_multipart.c
new file mode 100644
index 0000000..e956c6f
--- /dev/null
+++ b/mongoose/mongoose_multipart.c
@@ -0,0 +1,353 @@ 
+/*
+ * Copyright (c) 2004-2013 Sergey Lyubka
+ * Copyright (c) 2013-2020 Cesanta Software Limited
+ * All rights reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively, you can license this software under a commercial
+ * license, as set out in <https://www.cesanta.com/license>.
+ */
+
+#include "mongoose_multipart.h"
+
+enum mg_http_multipart_stream_state {
+	MPS_BEGIN,
+	MPS_WAITING_FOR_BOUNDARY,
+	MPS_WAITING_FOR_CHUNK,
+	MPS_GOT_BOUNDARY,
+	MPS_FINALIZE,
+	MPS_FINISHED
+};
+
+struct mg_http_multipart_stream {
+	struct mg_http_part part;
+	struct mg_str boundary;
+	void *user_data;
+	enum mg_http_multipart_stream_state state;
+	int processing_part;
+	int data_avail;
+	size_t len;
+};
+
+static void mg_http_free_proto_data_mp_stream(
+		struct mg_http_multipart_stream *mp) {
+	free((void *) mp->boundary.ptr);
+	free((void *) mp->part.name.ptr);
+	free((void *) mp->part.filename.ptr);
+	memset(mp, 0, sizeof(*mp));
+}
+
+static void mg_http_multipart_begin(struct mg_connection *nc,
+									struct mg_http_message *hm) {
+	struct mg_http_multipart_stream *mp_stream = nc->pfn_data;
+	struct mg_str *ct;
+	struct mg_iobuf *io = &nc->recv;
+
+	struct mg_str boundary;
+
+	ct = mg_http_get_header(hm, "Content-Type");
+	if (ct == NULL) {
+		/* We need more data - or it isn't multipart mesage */
+		return;
+	}
+
+	/* Content-type should start with "multipart" */
+	if (ct->len < 9 || strncmp(ct->ptr, "multipart", 9) != 0) {
+		return;
+	}
+
+	boundary = mg_http_get_header_var(*ct, mg_str_n("boundary", 8));
+	if (boundary.len == 0) {
+		/*
+		 * Content type is multipart, but there is no boundary,
+		 * probably malformed request
+		 */
+		nc->is_closing = 1;
+		LOG(LL_DEBUG,("invalid request"));
+		return;
+	}
+
+	/* If we reach this place - that is multipart request */
+
+	if (mp_stream->boundary.len != 0) {
+		/*
+		 * Another streaming request was in progress,
+		 * looks like protocol error
+		 */
+		nc->is_closing = 1;
+	} else {
+		mp_stream->state = MPS_BEGIN;
+		mp_stream->boundary = mg_strdup(boundary);
+		mp_stream->part.name.ptr = mp_stream->part.filename.ptr = NULL;
+		mp_stream->part.name.len = mp_stream->part.filename.len = 0;
+		mp_stream->len = hm->body.len;
+
+		mg_call(nc, MG_EV_HTTP_MULTIPART_REQUEST, hm);
+
+		mg_iobuf_del(io, 0, hm->head.len + 2);
+	}
+}
+
+#define CONTENT_DISPOSITION "Content-Disposition: "
+
+static size_t mg_http_multipart_call_handler(struct mg_connection *c, int ev,
+											 const char *data,
+											 size_t data_len) {
+	struct mg_http_multipart mp;
+	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
+	memset(&mp, 0, sizeof(mp));
+
+	mp.part.name = mp_stream->part.name;
+	mp.part.filename = mp_stream->part.filename;
+	mp.user_data = mp_stream->user_data;
+	mp.part.body.ptr = data;
+	mp.part.body.len = data_len;
+	mp.num_data_consumed = data_len;
+	mp.len = mp_stream->len;
+	mg_call(c, ev, &mp);
+	mp_stream->user_data = mp.user_data;
+	mp_stream->data_avail = (mp.num_data_consumed != data_len);
+	return mp.num_data_consumed;
+}
+
+static int mg_http_multipart_finalize(struct mg_connection *c) {
+	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
+
+	mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
+	free((void *) mp_stream->part.filename.ptr);
+	mp_stream->part.filename.ptr = NULL;
+	free((void *) mp_stream->part.name.ptr);
+	mp_stream->part.name.ptr = NULL;
+	mg_http_multipart_call_handler(c, MG_EV_HTTP_MULTIPART_REQUEST_END, NULL, 0);
+	mg_http_free_proto_data_mp_stream(mp_stream);
+	mp_stream->state = MPS_FINISHED;
+
+	return 1;
+}
+
+static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
+	const char *boundary;
+	struct mg_iobuf *io = &c->recv;
+	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
+
+	if (mp_stream->boundary.len == 0) {
+		mp_stream->state = MPS_FINALIZE;
+		LOG(LL_DEBUG,("Invalid request: boundary not initialized"));
+		return 0;
+	}
+
+	if ((int) io->len < mp_stream->boundary.len + 2) {
+		return 0;
+	}
+
+	boundary = mg_strstr(mg_str_n((char *) io->buf, io->len), mp_stream->boundary);
+	if (boundary != NULL) {
+		const char *boundary_end = (boundary + mp_stream->boundary.len);
+		if (io->len - (boundary_end - (char *) io->buf) < 4) {
+			return 0;
+		}
+		if (strncmp(boundary_end, "--\r\n", 4) == 0) {
+			mp_stream->state = MPS_FINALIZE;
+			mg_iobuf_del(io, 0, (boundary_end - (char *) io->buf) + 4);
+		} else {
+			mp_stream->state = MPS_GOT_BOUNDARY;
+		}
+	} else {
+		return 0;
+	}
+
+	return 1;
+}
+
+static size_t mg_get_line_len(const char *buf, size_t buf_len) {
+	size_t len = 0;
+	while (len < buf_len && buf[len] != '\n') len++;
+	return len == buf_len ? 0 : len + 1;
+}
+
+static int mg_http_multipart_process_boundary(struct mg_connection *c) {
+	int data_size;
+	const char *boundary, *block_begin;
+	struct mg_iobuf *io = &c->recv;
+	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
+	int line_len;
+	boundary = mg_strstr(mg_str_n((char *) io->buf, io->len), mp_stream->boundary);
+	block_begin = boundary + mp_stream->boundary.len + 2;
+	data_size = io->len - (block_begin - (char *) io->buf);
+	mp_stream->len -= ((2 * mp_stream->boundary.len) + 6);
+
+	while (data_size > 0 &&
+		   (line_len = mg_get_line_len(block_begin, data_size)) != 0) {
+		mp_stream->len -= (line_len + 2);
+		if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&
+			mg_ncasecmp(block_begin, CONTENT_DISPOSITION,
+						sizeof(CONTENT_DISPOSITION) - 1) == 0) {
+			struct mg_str header;
+
+			header.ptr = block_begin + sizeof(CONTENT_DISPOSITION) - 1;
+			header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;
+
+			mp_stream->part.name = mg_strdup(mg_http_get_header_var(header, mg_str_n("name", 4)));
+			mp_stream->part.filename = mg_strdup(mg_http_get_header_var(header, mg_str_n("filename", 8)));
+
+			block_begin += line_len;
+			data_size -= line_len;
+
+			continue;
+		}
+
+		if (line_len == 2 && mg_ncasecmp(block_begin, "\r\n", 2) == 0) {
+			if (mp_stream->processing_part != 0) {
+				mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
+			}
+
+			mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
+			mp_stream->state = MPS_WAITING_FOR_CHUNK;
+			mp_stream->processing_part++;
+
+			mg_iobuf_del(io, 0, block_begin - (char *) io->buf + 2);
+			return 1;
+		}
+
+		block_begin += line_len;
+	}
+
+	mp_stream->state = MPS_WAITING_FOR_BOUNDARY;
+
+	return 0;
+}
+
+static int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) {
+	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
+	struct mg_iobuf *io = &c->recv;
+
+	const char *boundary;
+	if ((int) io->len < mp_stream->boundary.len + 6 /* \r\n, --, -- */) {
+		return 0;
+	}
+
+	boundary = mg_strstr(mg_str_n((char *) io->buf, io->len), mp_stream->boundary);
+	if (boundary == NULL) {
+		int data_len = io->len - (mp_stream->boundary.len + 6);
+		if (data_len > 0) {
+			size_t consumed = mg_http_multipart_call_handler(
+					c, MG_EV_HTTP_PART_DATA, (char *) io->buf, (size_t) data_len);
+			mg_iobuf_del(io, 0, consumed);
+		}
+		return 0;
+	} else if (boundary != NULL) {
+		size_t data_len = io->len - (mp_stream->boundary.len + 8);
+		size_t consumed = mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA,
+														 (char *) io->buf, data_len);
+		mg_iobuf_del(io, 0, consumed);
+		if (consumed == data_len) {
+			mg_iobuf_del(io, 0, 4);
+			return 1;
+		} else {
+			return 0;
+		}
+	} else {
+		return 0;
+	}
+}
+
+static void mg_http_multipart_continue(struct mg_connection *c) {
+	struct mg_http_multipart_stream *mp_stream = c->pfn_data;
+	while (1) {
+		switch (mp_stream->state) {
+			case MPS_BEGIN: {
+				mp_stream->state = MPS_WAITING_FOR_BOUNDARY;
+				break;
+			}
+			case MPS_WAITING_FOR_BOUNDARY: {
+				if (mg_http_multipart_wait_for_boundary(c) == 0) {
+					return;
+				}
+				break;
+			}
+			case MPS_GOT_BOUNDARY: {
+				if (mg_http_multipart_process_boundary(c) == 0) {
+					return;
+				}
+				break;
+			}
+			case MPS_WAITING_FOR_CHUNK: {
+				if (mg_http_multipart_continue_wait_for_chunk(c) == 0) {
+					return;
+				}
+				break;
+			}
+			case MPS_FINALIZE: {
+				if (mg_http_multipart_finalize(c) == 0) {
+					return;
+				}
+				break;
+			}
+			case MPS_FINISHED: {
+				return;
+			}
+		}
+	}
+}
+
+void multipart_upload_handler(struct mg_connection *nc, int ev, void *ev_data, mg_event_handler_t fn,
+		void __attribute__ ((__unused__)) *fn_data)
+{
+	struct mg_http_message *hm = (struct mg_http_message *) ev_data;
+	struct mg_http_multipart_stream *mp_stream = nc->pfn_data;
+	struct mg_str *s;
+
+	if (mp_stream != NULL && mp_stream->boundary.len != 0) {
+		if (ev == MG_EV_READ || ev == MG_EV_POLL) {
+			if (ev == MG_EV_READ) {
+				mg_http_multipart_continue(nc);
+			} else if (mp_stream->data_avail) {
+				/* Try re-delivering the data. */
+				mg_http_multipart_continue(nc);
+			}
+			return;
+		} else if (ev == MG_EV_CLOSE) {
+			/*
+			 * Multipart message is in progress, but connection is closed.
+			 * Finish part and request with an error flag.
+			 */
+			struct mg_http_multipart mp;
+			memset(&mp, 0, sizeof(mp));
+			mp.status = -1;
+			mp.user_data = mp_stream->user_data;
+			mp.part.name = mp_stream->part.name;
+			mp.part.filename = mp_stream->part.filename;
+			mg_call(nc, MG_EV_HTTP_PART_END, &mp);
+			mp.part.name.ptr = NULL;
+			mp.part.filename.ptr = NULL;
+			mg_call(nc, MG_EV_HTTP_MULTIPART_REQUEST_END, &mp);
+			mp_stream->state = MPS_FINISHED;
+			return;
+		}
+	}
+
+	if (hm->chunk.len >= 0 && ev == MG_EV_HTTP_CHUNK) {
+		s = mg_http_get_header(hm, "Content-Type");
+		if (s->len >= 9 && strncmp(s->ptr, "multipart", 9) == 0) {
+			/* New request - new proto data */
+			nc->label[0] = 'M';
+
+			nc->pfn = fn;
+			nc->pfn_data = calloc(1, sizeof(struct mg_http_multipart_stream));
+			mg_http_multipart_begin(nc, hm);
+			mg_http_multipart_continue(nc);
+			return;
+		}
+	}
+}
\ No newline at end of file
diff --git a/mongoose/mongoose_multipart.h b/mongoose/mongoose_multipart.h
new file mode 100644
index 0000000..13a31dc
--- /dev/null
+++ b/mongoose/mongoose_multipart.h
@@ -0,0 +1,48 @@ 
+/*
+ * Copyright (c) 2004-2013 Sergey Lyubka
+ * Copyright (c) 2013-2020 Cesanta Software Limited
+ * All rights reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively, you can license this software under a commercial
+ * license, as set out in <https://www.cesanta.com/license>.
+ */
+
+#include "mongoose.h"
+
+enum {
+	MG_EV_HTTP_MULTIPART_REQUEST=MG_EV_USER + 1,  // struct mg_http_message *
+	MG_EV_HTTP_PART_BEGIN,                        // struct mg_http_multipart_part *
+	MG_EV_HTTP_PART_DATA,                         // struct mg_http_multipart_part *
+	MG_EV_HTTP_PART_END,                          // struct mg_http_multipart_part *
+	MG_EV_HTTP_MULTIPART_REQUEST_END              // struct mg_http_multipart_part *
+};
+
+/* HTTP multipart part */
+struct mg_http_multipart {
+	struct mg_http_part part;
+	int status; /* <0 on error */
+	void *user_data;
+	/*
+	 * User handler can indicate how much of the data was consumed
+	 * by setting this variable. By default, it is assumed that all
+	 * data has been consumed by the handler.
+	 * If not all data was consumed, user's handler will be invoked again later
+	 * with the remainder.
+	 */
+	size_t num_data_consumed;
+	size_t len;
+};
+
+void multipart_upload_handler(struct mg_connection *nc, int ev, void *ev_data, mg_event_handler_t fn, void *fn_data);