summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMateusz Łukasik <mati75@linuxmint.pl>2018-06-02 22:47:14 +0200
committerMateusz Łukasik <mati75@linuxmint.pl>2018-06-02 22:47:14 +0200
commit2ae19bc2ded3c7a9b1a11647d2fbc52a707ed287 (patch)
treef916f2e8e4079e358bf56e80e04f0d74f21029fc
parentbcd1eba97dbb085deeafc55151d2ed6bcca90a36 (diff)
Add debian/patches/03-update-mongoose-to-6.11.patch
-rw-r--r--debian/changelog4
-rw-r--r--debian/patches/03-update-mongoose-to-6.11.patch12281
-rw-r--r--debian/patches/series1
3 files changed, 12286 insertions, 0 deletions
diff --git a/debian/changelog b/debian/changelog
index 370f3e9..3410776 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,6 +1,10 @@
smplayer (18.4.0~ds0-1) UNRELEASED; urgency=medium
* New upstream release.
+ * Add debian/patches/03-update-mongoose-to-6.11.patch:
+ - Fix CVE-2017-2891, CVE-2017-2892, CVE-2017-2893, CVE-2017-2894,
+ CVE-2017-2895, CVE-2017-2909, CVE-2017-2921, CVE-2017-2922. (Closes: #898943)
+ - Fix FTBFS with gcc-8. (Closes: #897863)
-- Mateusz Łukasik <mati75@linuxmint.pl> Thu, 17 May 2018 21:03:10 +0200
diff --git a/debian/patches/03-update-mongoose-to-6.11.patch b/debian/patches/03-update-mongoose-to-6.11.patch
new file mode 100644
index 0000000..aa1d6c4
--- /dev/null
+++ b/debian/patches/03-update-mongoose-to-6.11.patch
@@ -0,0 +1,12281 @@
+Description: <short summary of the patch>
+ TODO: Put a short summary on the line above and replace this paragraph
+ with a longer explanation of this change. Complete the meta-information
+ with other relevant fields (see below for details). To make it easier, the
+ information below has been extracted from the changelog. Adjust it or drop
+ it.
+ .
+ smplayer (18.4.0~ds0-1) UNRELEASED; urgency=medium
+ .
+ * New upstream release.
+Author: Mateusz Łukasik <mati75@linuxmint.pl>
+
+---
+The information above should follow the Patch Tagging Guidelines, please
+checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here
+are templates for supplementary fields that you might want to add:
+
+Origin: <vendor|upstream|other>, <url of original patch>
+Bug: <url in upstream bugtracker>
+Bug-Debian: https://bugs.debian.org/<bugnumber>
+Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber>
+Forwarded: <no|not-needed|url proving that it has been forwarded>
+Reviewed-By: <name and email of someone who approved the patch>
+Last-Update: 2018-05-17
+
+--- smplayer-18.4.0~ds0.orig/webserver/mongoose.c
++++ smplayer-18.4.0~ds0/webserver/mongoose.c
+@@ -1,6 +1,6 @@
+ #include "mongoose.h"
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/internal.h"
++#line 1 "mongoose/src/mg_internal.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -10,21 +10,7 @@
+ #ifndef CS_MONGOOSE_SRC_INTERNAL_H_
+ #define CS_MONGOOSE_SRC_INTERNAL_H_
+
+-#ifndef MG_MALLOC
+-#define MG_MALLOC malloc
+-#endif
+-
+-#ifndef MG_CALLOC
+-#define MG_CALLOC calloc
+-#endif
+-
+-#ifndef MG_REALLOC
+-#define MG_REALLOC realloc
+-#endif
+-
+-#ifndef MG_FREE
+-#define MG_FREE free
+-#endif
++/* Amalgamated: #include "common/mg_mem.h" */
+
+ #ifndef MBUF_REALLOC
+ #define MBUF_REALLOC MG_REALLOC
+@@ -48,9 +34,9 @@
+ #define MG_DISABLE_PFS
+ #endif
+
+-/* Amalgamated: #include "mongoose/src/net.h" */
+-/* Amalgamated: #include "mongoose/src/http.h" */
+ /* Amalgamated: #include "common/cs_dbg.h" */
++/* Amalgamated: #include "mg_http.h" */
++/* Amalgamated: #include "mg_net.h" */
+
+ #define MG_CTL_MSG_MESSAGE_SIZE 8192
+
+@@ -62,7 +48,8 @@ MG_INTERNAL struct mg_connection *mg_do_
+ MG_INTERNAL int mg_parse_address(const char *str, union socket_address *sa,
+ int *proto, char *host, size_t host_len);
+ MG_INTERNAL void mg_call(struct mg_connection *nc,
+- mg_event_handler_t ev_handler, int ev, void *ev_data);
++ mg_event_handler_t ev_handler, void *user_data, int ev,
++ void *ev_data);
+ void mg_forward(struct mg_connection *from, struct mg_connection *to);
+ MG_INTERNAL void mg_add_conn(struct mg_mgr *mgr, struct mg_connection *c);
+ MG_INTERNAL void mg_remove_conn(struct mg_connection *c);
+@@ -81,6 +68,10 @@ struct ctl_msg {
+
+ #if MG_ENABLE_MQTT
+ struct mg_mqtt_message;
++
++#define MG_MQTT_ERROR_INCOMPLETE_MSG -1
++#define MG_MQTT_ERROR_MALFORMED_MSG -2
++
+ MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm);
+ #endif
+
+@@ -110,11 +101,6 @@ MG_INTERNAL size_t mg_handle_chunked(str
+ struct http_message *hm, char *buf,
+ size_t blen);
+
+-MG_INTERNAL int mg_http_common_url_parse(const char *url, const char *schema,
+- const char *schema_tls, int *use_ssl,
+- char **user, char **pass, char **addr,
+- int *port_i, const char **path);
+-
+ #if MG_ENABLE_FILESYSTEM
+ MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
+ const struct mg_serve_http_opts *opts,
+@@ -155,9 +141,11 @@ MG_INTERNAL void mg_handle_put(struct mg
+ struct http_message *hm);
+ #endif
+ #if MG_ENABLE_HTTP_WEBSOCKET
+-MG_INTERNAL void mg_ws_handler(struct mg_connection *nc, int ev, void *ev_data);
++MG_INTERNAL void mg_ws_handler(struct mg_connection *nc, int ev,
++ void *ev_data MG_UD_ARG(void *user_data));
+ MG_INTERNAL void mg_ws_handshake(struct mg_connection *nc,
+- const struct mg_str *key);
++ const struct mg_str *key,
++ struct http_message *);
+ #endif
+ #endif /* MG_ENABLE_HTTP */
+
+@@ -165,11 +153,6 @@ MG_INTERNAL int mg_get_errno(void);
+
+ MG_INTERNAL void mg_close_conn(struct mg_connection *conn);
+
+-MG_INTERNAL int mg_http_common_url_parse(const char *url, const char *schema,
+- const char *schema_tls, int *use_ssl,
+- char **user, char **pass, char **addr,
+- int *port_i, const char **path);
+-
+ #if MG_ENABLE_SNTP
+ MG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len,
+ struct mg_sntp_message *msg);
+@@ -177,160 +160,43 @@ MG_INTERNAL int mg_sntp_parse_reply(cons
+
+ #endif /* CS_MONGOOSE_SRC_INTERNAL_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "common/cs_dbg.h"
++#line 1 "common/mg_mem.h"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+-#ifndef CS_COMMON_CS_DBG_H_
+-#define CS_COMMON_CS_DBG_H_
+-
+-/* Amalgamated: #include "common/platform.h" */
+-
+-#if CS_ENABLE_STDIO
+-#include <stdio.h>
+-#endif
+-
+-#ifndef CS_ENABLE_DEBUG
+-#define CS_ENABLE_DEBUG 0
+-#endif
+-
+-#ifndef CS_LOG_ENABLE_TS_DIFF
+-#define CS_LOG_ENABLE_TS_DIFF 0
+-#endif
++#ifndef CS_COMMON_MG_MEM_H_
++#define CS_COMMON_MG_MEM_H_
+
+ #ifdef __cplusplus
+ extern "C" {
+-#endif /* __cplusplus */
+-
+-enum cs_log_level {
+- LL_NONE = -1,
+- LL_ERROR = 0,
+- LL_WARN = 1,
+- LL_INFO = 2,
+- LL_DEBUG = 3,
+- LL_VERBOSE_DEBUG = 4,
+-
+- _LL_MIN = -2,
+- _LL_MAX = 5,
+-};
+-
+-void cs_log_set_level(enum cs_log_level level);
+-
+-#if CS_ENABLE_STDIO
+-
+-void cs_log_set_file(FILE *file);
+-extern enum cs_log_level cs_log_level;
+-void cs_log_print_prefix(const char *func);
+-void cs_log_printf(const char *fmt, ...);
+-
+-#define LOG(l, x) \
+- do { \
+- if (cs_log_level >= l) { \
+- cs_log_print_prefix(__func__); \
+- cs_log_printf x; \
+- } \
+- } while (0)
+-
+-#ifndef CS_NDEBUG
+-
+-#define DBG(x) \
+- do { \
+- if (cs_log_level >= LL_VERBOSE_DEBUG) { \
+- cs_log_print_prefix(__func__); \
+- cs_log_printf x; \
+- } \
+- } while (0)
+-
+-#else /* NDEBUG */
+-
+-#define DBG(x)
+-
+ #endif
+
+-#else /* CS_ENABLE_STDIO */
+-
+-#define LOG(l, x)
+-#define DBG(x)
+-
+-#endif
+-
+-#ifdef __cplusplus
+-}
+-#endif /* __cplusplus */
+-
+-#endif /* CS_COMMON_CS_DBG_H_ */
+-#ifdef MG_MODULE_LINES
+-#line 1 "common/cs_dbg.c"
++#ifndef MG_MALLOC
++#define MG_MALLOC malloc
+ #endif
+-/*
+- * Copyright (c) 2014-2016 Cesanta Software Limited
+- * All rights reserved
+- */
+-
+-/* Amalgamated: #include "common/cs_dbg.h" */
+-
+-#include <stdarg.h>
+-#include <stdio.h>
+-
+-/* Amalgamated: #include "common/cs_time.h" */
+
+-enum cs_log_level cs_log_level WEAK =
+-#if CS_ENABLE_DEBUG
+- LL_VERBOSE_DEBUG;
+-#else
+- LL_ERROR;
++#ifndef MG_CALLOC
++#define MG_CALLOC calloc
+ #endif
+
+-#if CS_ENABLE_STDIO
+-
+-FILE *cs_log_file WEAK = NULL;
+-
+-#if CS_LOG_ENABLE_TS_DIFF
+-double cs_log_ts WEAK;
++#ifndef MG_REALLOC
++#define MG_REALLOC realloc
+ #endif
+
+-void cs_log_print_prefix(const char *func) WEAK;
+-void cs_log_print_prefix(const char *func) {
+- if (cs_log_file == NULL) cs_log_file = stderr;
+- fprintf(cs_log_file, "%-20s ", func);
+-#if CS_LOG_ENABLE_TS_DIFF
+- {
+- double now = cs_time();
+- fprintf(cs_log_file, "%7u ", (unsigned int) ((now - cs_log_ts) * 1000000));
+- cs_log_ts = now;
+- }
++#ifndef MG_FREE
++#define MG_FREE free
+ #endif
+-}
+-
+-void cs_log_printf(const char *fmt, ...) WEAK;
+-void cs_log_printf(const char *fmt, ...) {
+- va_list ap;
+- va_start(ap, fmt);
+- vfprintf(cs_log_file, fmt, ap);
+- va_end(ap);
+- fputc('\n', cs_log_file);
+- fflush(cs_log_file);
+-}
+
+-void cs_log_set_file(FILE *file) WEAK;
+-void cs_log_set_file(FILE *file) {
+- cs_log_file = file;
++#ifdef __cplusplus
+ }
+-
+-#endif /* CS_ENABLE_STDIO */
+-
+-void cs_log_set_level(enum cs_log_level level) WEAK;
+-void cs_log_set_level(enum cs_log_level level) {
+- cs_log_level = level;
+-#if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO
+- cs_log_ts = cs_time();
+ #endif
+-}
++
++#endif /* CS_COMMON_MG_MEM_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "common/base64.c"
++#line 1 "common/cs_base64.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -339,7 +205,7 @@ void cs_log_set_level(enum cs_log_level
+
+ #ifndef EXCLUDE_COMMON
+
+-/* Amalgamated: #include "common/base64.h" */
++/* Amalgamated: #include "common/cs_base64.h" */
+
+ #include <string.h>
+
+@@ -538,64 +404,301 @@ int cs_base64_decode(const unsigned char
+
+ #endif /* EXCLUDE_COMMON */
+ #ifdef MG_MODULE_LINES
+-#line 1 "common/cs_dirent.h"
++#line 1 "common/cs_dbg.h"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+-#ifndef CS_COMMON_CS_DIRENT_H_
+-#define CS_COMMON_CS_DIRENT_H_
++#ifndef CS_COMMON_CS_DBG_H_
++#define CS_COMMON_CS_DBG_H_
+
+ /* Amalgamated: #include "common/platform.h" */
+
++#if CS_ENABLE_STDIO
++#include <stdio.h>
++#endif
++
++#ifndef CS_ENABLE_DEBUG
++#define CS_ENABLE_DEBUG 0
++#endif
++
++#ifndef CS_LOG_ENABLE_TS_DIFF
++#define CS_LOG_ENABLE_TS_DIFF 0
++#endif
++
+ #ifdef __cplusplus
+ extern "C" {
+ #endif /* __cplusplus */
+
+-#ifndef CS_ENABLE_SPIFFS
+-#define CS_ENABLE_SPIFFS 0
++/*
++ * Log level; `LL_INFO` is the default. Use `cs_log_set_level()` to change it.
++ */
++enum cs_log_level {
++ LL_NONE = -1,
++ LL_ERROR = 0,
++ LL_WARN = 1,
++ LL_INFO = 2,
++ LL_DEBUG = 3,
++ LL_VERBOSE_DEBUG = 4,
++
++ _LL_MIN = -2,
++ _LL_MAX = 5,
++};
++
++/*
++ * Set max log level to print; messages with the level above the given one will
++ * not be printed.
++ */
++void cs_log_set_level(enum cs_log_level level);
++
++/*
++ * Set log filter. NULL (a default) logs everything.
++ * Otherwise, function name and file name will be tested against the given
++ * pattern, and only matching messages will be printed.
++ *
++ * For the pattern syntax, refer to `mg_match_prefix()` in `str_util.h`.
++ *
++ * Example:
++ * ```c
++ * void foo(void) {
++ * LOG(LL_INFO, ("hello from foo"));
++ * }
++ *
++ * void bar(void) {
++ * LOG(LL_INFO, ("hello from bar"));
++ * }
++ *
++ * void test(void) {
++ * cs_log_set_filter(NULL);
++ * foo();
++ * bar();
++ *
++ * cs_log_set_filter("f*");
++ * foo();
++ * bar(); // Will NOT print anything
++ *
++ * cs_log_set_filter("bar");
++ * foo(); // Will NOT print anything
++ * bar();
++ * }
++ * ```
++ */
++void cs_log_set_filter(const char *pattern);
++
++/*
++ * Helper function which prints message prefix with the given `level`, function
++ * name `func` and `filename`. If message should be printed (accordingly to the
++ * current log level and filter), prints the prefix and returns 1, otherwise
++ * returns 0.
++ *
++ * Clients should typically just use `LOG()` macro.
++ */
++int cs_log_print_prefix(enum cs_log_level level, const char *func,
++ const char *filename);
++
++extern enum cs_log_level cs_log_threshold;
++
++#if CS_ENABLE_STDIO
++
++/*
++ * Set file to write logs into. If `NULL`, logs go to `stderr`.
++ */
++void cs_log_set_file(FILE *file);
++
++/*
++ * Prints log to the current log file, appends "\n" in the end and flushes the
++ * stream.
++ */
++void cs_log_printf(const char *fmt, ...)
++#ifdef __GNUC__
++ __attribute__((format(printf, 1, 2)))
+ #endif
++ ;
+
+-#if CS_ENABLE_SPIFFS
++/*
++ * Format and print message `x` with the given level `l`. Example:
++ *
++ * ```c
++ * LOG(LL_INFO, ("my info message: %d", 123));
++ * LOG(LL_DEBUG, ("my debug message: %d", 123));
++ * ```
++ */
++#define LOG(l, x) \
++ do { \
++ if (cs_log_print_prefix(l, __func__, __FILE__)) cs_log_printf x; \
++ } while (0)
+
+-#include <spiffs.h>
++#ifndef CS_NDEBUG
+
+-typedef struct {
+- spiffs_DIR dh;
+- struct spiffs_dirent de;
+-} DIR;
++/*
++ * Shortcut for `LOG(LL_VERBOSE_DEBUG, (...))`
++ */
++#define DBG(x) LOG(LL_VERBOSE_DEBUG, x)
+
+-#define d_name name
+-#define dirent spiffs_dirent
++#else /* NDEBUG */
+
+-int rmdir(const char *path);
+-int mkdir(const char *path, mode_t mode);
++#define DBG(x)
+
+ #endif
+
+-#if defined(_WIN32)
+-struct dirent {
+- char d_name[MAX_PATH];
+-};
++#else /* CS_ENABLE_STDIO */
+
+-typedef struct DIR {
+- HANDLE handle;
+- WIN32_FIND_DATAW info;
+- struct dirent result;
+-} DIR;
++#define LOG(l, x)
++#define DBG(x)
++
++#endif
++
++#ifdef __cplusplus
++}
++#endif /* __cplusplus */
++
++#endif /* CS_COMMON_CS_DBG_H_ */
++#ifdef MG_MODULE_LINES
++#line 1 "common/cs_dbg.c"
++#endif
++/*
++ * Copyright (c) 2014-2016 Cesanta Software Limited
++ * All rights reserved
++ */
++
++/* Amalgamated: #include "common/cs_dbg.h" */
++
++#include <stdarg.h>
++#include <stdio.h>
++#include <string.h>
++
++/* Amalgamated: #include "common/cs_time.h" */
++/* Amalgamated: #include "common/str_util.h" */
++
++enum cs_log_level cs_log_threshold WEAK =
++#if CS_ENABLE_DEBUG
++ LL_VERBOSE_DEBUG;
++#else
++ LL_ERROR;
++#endif
++
++static char *s_filter_pattern = NULL;
++static size_t s_filter_pattern_len;
++
++void cs_log_set_filter(const char *pattern) WEAK;
++
++#if CS_ENABLE_STDIO
++
++FILE *cs_log_file WEAK = NULL;
++
++#if CS_LOG_ENABLE_TS_DIFF
++double cs_log_ts WEAK;
++#endif
++
++enum cs_log_level cs_log_cur_msg_level WEAK = LL_NONE;
++
++void cs_log_set_filter(const char *pattern) {
++ free(s_filter_pattern);
++ if (pattern != NULL) {
++ s_filter_pattern = strdup(pattern);
++ s_filter_pattern_len = strlen(pattern);
++ } else {
++ s_filter_pattern = NULL;
++ s_filter_pattern_len = 0;
++ }
++}
++
++int cs_log_print_prefix(enum cs_log_level, const char *, const char *) WEAK;
++int cs_log_print_prefix(enum cs_log_level level, const char *func,
++ const char *filename) {
++ char prefix[21];
++
++ if (level > cs_log_threshold) return 0;
++ if (s_filter_pattern != NULL &&
++ mg_match_prefix(s_filter_pattern, s_filter_pattern_len, func) == 0 &&
++ mg_match_prefix(s_filter_pattern, s_filter_pattern_len, filename) == 0) {
++ return 0;
++ }
++
++ strncpy(prefix, func, 20);
++ prefix[20] = '\0';
++ if (cs_log_file == NULL) cs_log_file = stderr;
++ cs_log_cur_msg_level = level;
++ fprintf(cs_log_file, "%-20s ", prefix);
++#if CS_LOG_ENABLE_TS_DIFF
++ {
++ double now = cs_time();
++ fprintf(cs_log_file, "%7u ", (unsigned int) ((now - cs_log_ts) * 1000000));
++ cs_log_ts = now;
++ }
++#endif
++ return 1;
++}
++
++void cs_log_printf(const char *fmt, ...) WEAK;
++void cs_log_printf(const char *fmt, ...) {
++ va_list ap;
++ va_start(ap, fmt);
++ vfprintf(cs_log_file, fmt, ap);
++ va_end(ap);
++ fputc('\n', cs_log_file);
++ fflush(cs_log_file);
++ cs_log_cur_msg_level = LL_NONE;
++}
++
++void cs_log_set_file(FILE *file) WEAK;
++void cs_log_set_file(FILE *file) {
++ cs_log_file = file;
++}
++
++#else
++
++void cs_log_set_filter(const char *pattern) {
++ (void) pattern;
++}
++
++#endif /* CS_ENABLE_STDIO */
++
++void cs_log_set_level(enum cs_log_level level) WEAK;
++void cs_log_set_level(enum cs_log_level level) {
++ cs_log_threshold = level;
++#if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO
++ cs_log_ts = cs_time();
+ #endif
++}
++#ifdef MG_MODULE_LINES
++#line 1 "common/cs_dirent.h"
++#endif
++/*
++ * Copyright (c) 2014-2016 Cesanta Software Limited
++ * All rights reserved
++ */
++
++#ifndef CS_COMMON_CS_DIRENT_H_
++#define CS_COMMON_CS_DIRENT_H_
++
++#include <limits.h>
+
+-#if CS_ENABLE_SPIFFS
+-extern spiffs *cs_spiffs_get_fs(void);
++/* Amalgamated: #include "common/platform.h" */
++
++#ifdef __cplusplus
++extern "C" {
++#endif /* __cplusplus */
++
++#ifdef CS_DEFINE_DIRENT
++typedef struct { int dummy; } DIR;
++
++struct dirent {
++ int d_ino;
++#ifdef _WIN32
++ char d_name[MAX_PATH];
++#else
++ /* TODO(rojer): Use PATH_MAX but make sure it's sane on every platform */
++ char d_name[256];
+ #endif
++};
+
+-#if defined(_WIN32) || CS_ENABLE_SPIFFS
+ DIR *opendir(const char *dir_name);
+ int closedir(DIR *dir);
+ struct dirent *readdir(DIR *dir);
+-#endif
++#endif /* CS_DEFINE_DIRENT */
+
+ #ifdef __cplusplus
+ }
+@@ -612,6 +715,7 @@ struct dirent *readdir(DIR *dir);
+
+ #ifndef EXCLUDE_COMMON
+
++/* Amalgamated: #include "common/mg_mem.h" */
+ /* Amalgamated: #include "common/cs_dirent.h" */
+
+ /*
+@@ -619,23 +723,22 @@ struct dirent *readdir(DIR *dir);
+ * for systems which do not natively support it (e.g. Windows).
+ */
+
+-#ifndef MG_FREE
+-#define MG_FREE free
+-#endif
+-
+-#ifndef MG_MALLOC
+-#define MG_MALLOC malloc
+-#endif
+-
+ #ifdef _WIN32
++struct win32_dir {
++ DIR d;
++ HANDLE handle;
++ WIN32_FIND_DATAW info;
++ struct dirent result;
++};
++
+ DIR *opendir(const char *name) {
+- DIR *dir = NULL;
++ struct win32_dir *dir = NULL;
+ wchar_t wpath[MAX_PATH];
+ DWORD attrs;
+
+ if (name == NULL) {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+- } else if ((dir = (DIR *) MG_MALLOC(sizeof(*dir))) == NULL) {
++ } else if ((dir = (struct win32_dir *) MG_MALLOC(sizeof(*dir))) == NULL) {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ } else {
+ to_wchar(name, wpath, ARRAY_SIZE(wpath));
+@@ -650,10 +753,11 @@ DIR *opendir(const char *name) {
+ }
+ }
+
+- return dir;
++ return (DIR *) dir;
+ }
+
+-int closedir(DIR *dir) {
++int closedir(DIR *d) {
++ struct win32_dir *dir = (struct win32_dir *) d;
+ int result = 0;
+
+ if (dir != NULL) {
+@@ -668,10 +772,12 @@ int closedir(DIR *dir) {
+ return result;
+ }
+
+-struct dirent *readdir(DIR *dir) {
++struct dirent *readdir(DIR *d) {
++ struct win32_dir *dir = (struct win32_dir *) d;
+ struct dirent *result = NULL;
+
+ if (dir) {
++ memset(&dir->result, 0, sizeof(dir->result));
+ if (dir->handle != INVALID_HANDLE_VALUE) {
+ result = &dir->result;
+ (void) WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, -1,
+@@ -694,52 +800,6 @@ struct dirent *readdir(DIR *dir) {
+ }
+ #endif
+
+-#if CS_ENABLE_SPIFFS
+-
+-DIR *opendir(const char *dir_name) {
+- DIR *dir = NULL;
+- spiffs *fs = cs_spiffs_get_fs();
+-
+- if (dir_name == NULL || fs == NULL ||
+- (dir = (DIR *) calloc(1, sizeof(*dir))) == NULL) {
+- return NULL;
+- }
+-
+- if (SPIFFS_opendir(fs, dir_name, &dir->dh) == NULL) {
+- free(dir);
+- dir = NULL;
+- }
+-
+- return dir;
+-}
+-
+-int closedir(DIR *dir) {
+- if (dir != NULL) {
+- SPIFFS_closedir(&dir->dh);
+- free(dir);
+- }
+- return 0;
+-}
+-
+-struct dirent *readdir(DIR *dir) {
+- return SPIFFS_readdir(&dir->dh, &dir->de);
+-}
+-
+-/* SPIFFs doesn't support directory operations */
+-int rmdir(const char *path) {
+- (void) path;
+- return ENOTSUP;
+-}
+-
+-int mkdir(const char *path, mode_t mode) {
+- (void) path;
+- (void) mode;
+- /* for spiffs supports only root dir, which comes from mongoose as '.' */
+- return (strlen(path) == 1 && *path == '.') ? 0 : ENOTSUP;
+-}
+-
+-#endif /* CS_ENABLE_SPIFFS */
+-
+ #endif /* EXCLUDE_COMMON */
+
+ /* ISO C requires a translation unit to contain at least one declaration */
+@@ -795,6 +855,41 @@ double cs_time(void) {
+ #endif /* _WIN32 */
+ return now;
+ }
++
++double cs_timegm(const struct tm *tm) {
++ /* Month-to-day offset for non-leap-years. */
++ static const int month_day[12] = {0, 31, 59, 90, 120, 151,
++ 181, 212, 243, 273, 304, 334};
++
++ /* Most of the calculation is easy; leap years are the main difficulty. */
++ int month = tm->tm_mon % 12;
++ int year = tm->tm_year + tm->tm_mon / 12;
++ int year_for_leap;
++ int64_t rt;
++
++ if (month < 0) { /* Negative values % 12 are still negative. */
++ month += 12;
++ --year;
++ }
++
++ /* This is the number of Februaries since 1900. */
++ year_for_leap = (month > 1) ? year + 1 : year;
++
++ rt =
++ tm->tm_sec /* Seconds */
++ +
++ 60 *
++ (tm->tm_min /* Minute = 60 seconds */
++ +
++ 60 * (tm->tm_hour /* Hour = 60 minutes */
++ +
++ 24 * (month_day[month] + tm->tm_mday - 1 /* Day = 24 hours */
++ + 365 * (year - 70) /* Year = 365 days */
++ + (year_for_leap - 69) / 4 /* Every 4 years is leap... */
++ - (year_for_leap - 1) / 100 /* Except centuries... */
++ + (year_for_leap + 299) / 400))); /* Except 400s. */
++ return rt < 0 ? -1 : (double) rt;
++}
+ #ifdef MG_MODULE_LINES
+ #line 1 "common/cs_endian.h"
+ #endif
+@@ -806,6 +901,10 @@ double cs_time(void) {
+ #ifndef CS_COMMON_CS_ENDIAN_H_
+ #define CS_COMMON_CS_ENDIAN_H_
+
++#ifdef __cplusplus
++extern "C" {
++#endif
++
+ /*
+ * clang with std=-c99 uses __LITTLE_ENDIAN, by default
+ * while for ex, RTOS gcc - LITTLE_ENDIAN, by default
+@@ -821,9 +920,13 @@ double cs_time(void) {
+ #endif /* BIG_ENDIAN */
+ #endif /* BYTE_ORDER */
+
++#ifdef __cplusplus
++}
++#endif
++
+ #endif /* CS_COMMON_CS_ENDIAN_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "common/md5.c"
++#line 1 "common/cs_md5.c"
+ #endif
+ /*
+ * This code implements the MD5 message-digest algorithm.
+@@ -842,11 +945,11 @@ double cs_time(void) {
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+-/* Amalgamated: #include "common/md5.h" */
++/* Amalgamated: #include "common/cs_md5.h" */
+ /* Amalgamated: #include "common/str_util.h" */
+
+ #if !defined(EXCLUDE_COMMON)
+-#if !DISABLE_MD5
++#if !CS_DISABLE_MD5
+
+ /* Amalgamated: #include "common/cs_endian.h" */
+
+@@ -877,7 +980,7 @@ static void byteReverse(unsigned char *b
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+-void MD5_Init(MD5_CTX *ctx) {
++void cs_md5_init(cs_md5_ctx *ctx) {
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+@@ -887,7 +990,7 @@ void MD5_Init(MD5_CTX *ctx) {
+ ctx->bits[1] = 0;
+ }
+
+-static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
++static void cs_md5_transform(uint32_t buf[4], uint32_t const in[16]) {
+ register uint32_t a, b, c, d;
+
+ a = buf[0];
+@@ -969,7 +1072,7 @@ static void MD5Transform(uint32_t buf[4]
+ buf[3] += d;
+ }
+
+-void MD5_Update(MD5_CTX *ctx, const unsigned char *buf, size_t len) {
++void cs_md5_update(cs_md5_ctx *ctx, const unsigned char *buf, size_t len) {
+ uint32_t t;
+
+ t = ctx->bits[0];
+@@ -988,7 +1091,7 @@ void MD5_Update(MD5_CTX *ctx, const unsi
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+- MD5Transform(ctx->buf, (uint32_t *) ctx->in);
++ cs_md5_transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += t;
+ len -= t;
+ }
+@@ -996,7 +1099,7 @@ void MD5_Update(MD5_CTX *ctx, const unsi
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+- MD5Transform(ctx->buf, (uint32_t *) ctx->in);
++ cs_md5_transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+@@ -1004,7 +1107,7 @@ void MD5_Update(MD5_CTX *ctx, const unsi
+ memcpy(ctx->in, buf, len);
+ }
+
+-void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) {
++void cs_md5_final(unsigned char digest[16], cs_md5_ctx *ctx) {
+ unsigned count;
+ unsigned char *p;
+ uint32_t *a;
+@@ -1017,7 +1120,7 @@ void MD5_Final(unsigned char digest[16],
+ if (count < 8) {
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+- MD5Transform(ctx->buf, (uint32_t *) ctx->in);
++ cs_md5_transform(ctx->buf, (uint32_t *) ctx->in);
+ memset(ctx->in, 0, 56);
+ } else {
+ memset(p, 0, count - 8);
+@@ -1028,235 +1131,23 @@ void MD5_Final(unsigned char digest[16],
+ a[14] = ctx->bits[0];
+ a[15] = ctx->bits[1];
+
+- MD5Transform(ctx->buf, (uint32_t *) ctx->in);
++ cs_md5_transform(ctx->buf, (uint32_t *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+ memcpy(digest, ctx->buf, 16);
+ memset((char *) ctx, 0, sizeof(*ctx));
+ }
+-#endif /* DISABLE_MD5 */
+-
+-char *cs_md5(char buf[33], ...) {
+- unsigned char hash[16];
+- const unsigned char *p;
+- va_list ap;
+- MD5_CTX ctx;
+-
+- MD5_Init(&ctx);
+-
+- va_start(ap, buf);
+- while ((p = va_arg(ap, const unsigned char *) ) != NULL) {
+- size_t len = va_arg(ap, size_t);
+- MD5_Update(&ctx, p, len);
+- }
+- va_end(ap);
+-
+- MD5_Final(hash, &ctx);
+- cs_to_hex(buf, hash, sizeof(hash));
+-
+- return buf;
+-}
+-
+-#endif /* EXCLUDE_COMMON */
+-#ifdef MG_MODULE_LINES
+-#line 1 "common/mbuf.c"
+-#endif
+-/*
+- * Copyright (c) 2014 Cesanta Software Limited
+- * All rights reserved
+- */
+-
+-#ifndef EXCLUDE_COMMON
+-
+-#include <assert.h>
+-#include <string.h>
+-/* Amalgamated: #include "common/mbuf.h" */
+-
+-#ifndef MBUF_REALLOC
+-#define MBUF_REALLOC realloc
+-#endif
+-
+-#ifndef MBUF_FREE
+-#define MBUF_FREE free
+-#endif
+-
+-void mbuf_init(struct mbuf *mbuf, size_t initial_size) WEAK;
+-void mbuf_init(struct mbuf *mbuf, size_t initial_size) {
+- mbuf->len = mbuf->size = 0;
+- mbuf->buf = NULL;
+- mbuf_resize(mbuf, initial_size);
+-}
+-
+-void mbuf_free(struct mbuf *mbuf) WEAK;
+-void mbuf_free(struct mbuf *mbuf) {
+- if (mbuf->buf != NULL) {
+- MBUF_FREE(mbuf->buf);
+- mbuf_init(mbuf, 0);
+- }
+-}
+-
+-void mbuf_resize(struct mbuf *a, size_t new_size) WEAK;
+-void mbuf_resize(struct mbuf *a, size_t new_size) {
+- if (new_size > a->size || (new_size < a->size && new_size >= a->len)) {
+- char *buf = (char *) MBUF_REALLOC(a->buf, new_size);
+- /*
+- * In case realloc fails, there's not much we can do, except keep things as
+- * they are. Note that NULL is a valid return value from realloc when
+- * size == 0, but that is covered too.
+- */
+- if (buf == NULL && new_size != 0) return;
+- a->buf = buf;
+- a->size = new_size;
+- }
+-}
+-
+-void mbuf_trim(struct mbuf *mbuf) WEAK;
+-void mbuf_trim(struct mbuf *mbuf) {
+- mbuf_resize(mbuf, mbuf->len);
+-}
+-
+-size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t) WEAK;
+-size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) {
+- char *p = NULL;
+-
+- assert(a != NULL);
+- assert(a->len <= a->size);
+- assert(off <= a->len);
+-
+- /* check overflow */
+- if (~(size_t) 0 - (size_t) a->buf < len) return 0;
+-
+- if (a->len + len <= a->size) {
+- memmove(a->buf + off + len, a->buf + off, a->len - off);
+- if (buf != NULL) {
+- memcpy(a->buf + off, buf, len);
+- }
+- a->len += len;
+- } else {
+- size_t new_size = (size_t)((a->len + len) * MBUF_SIZE_MULTIPLIER);
+- if ((p = (char *) MBUF_REALLOC(a->buf, new_size)) != NULL) {
+- a->buf = p;
+- memmove(a->buf + off + len, a->buf + off, a->len - off);
+- if (buf != NULL) memcpy(a->buf + off, buf, len);
+- a->len += len;
+- a->size = new_size;
+- } else {
+- len = 0;
+- }
+- }
+-
+- return len;
+-}
+-
+-size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) WEAK;
+-size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) {
+- return mbuf_insert(a, a->len, buf, len);
+-}
+-
+-void mbuf_remove(struct mbuf *mb, size_t n) WEAK;
+-void mbuf_remove(struct mbuf *mb, size_t n) {
+- if (n > 0 && n <= mb->len) {
+- memmove(mb->buf, mb->buf + n, mb->len - n);
+- mb->len -= n;
+- }
+-}
+
++#endif /* CS_DISABLE_MD5 */
+ #endif /* EXCLUDE_COMMON */
+ #ifdef MG_MODULE_LINES
+-#line 1 "common/mg_str.c"
+-#endif
+-/*
+- * Copyright (c) 2014-2016 Cesanta Software Limited
+- * All rights reserved
+- */
+-
+-/* Amalgamated: #include "common/mg_str.h" */
+-
+-#include <stdlib.h>
+-#include <string.h>
+-
+-int mg_ncasecmp(const char *s1, const char *s2, size_t len) WEAK;
+-
+-struct mg_str mg_mk_str(const char *s) WEAK;
+-struct mg_str mg_mk_str(const char *s) {
+- struct mg_str ret = {s, 0};
+- if (s != NULL) ret.len = strlen(s);
+- return ret;
+-}
+-
+-struct mg_str mg_mk_str_n(const char *s, size_t len) WEAK;
+-struct mg_str mg_mk_str_n(const char *s, size_t len) {
+- struct mg_str ret = {s, len};
+- return ret;
+-}
+-
+-int mg_vcmp(const struct mg_str *str1, const char *str2) WEAK;
+-int mg_vcmp(const struct mg_str *str1, const char *str2) {
+- size_t n2 = strlen(str2), n1 = str1->len;
+- int r = memcmp(str1->p, str2, (n1 < n2) ? n1 : n2);
+- if (r == 0) {
+- return n1 - n2;
+- }
+- return r;
+-}
+-
+-int mg_vcasecmp(const struct mg_str *str1, const char *str2) WEAK;
+-int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
+- size_t n2 = strlen(str2), n1 = str1->len;
+- int r = mg_ncasecmp(str1->p, str2, (n1 < n2) ? n1 : n2);
+- if (r == 0) {
+- return n1 - n2;
+- }
+- return r;
+-}
+-
+-struct mg_str mg_strdup(const struct mg_str s) WEAK;
+-struct mg_str mg_strdup(const struct mg_str s) {
+- struct mg_str r = {NULL, 0};
+- if (s.len > 0 && s.p != NULL) {
+- r.p = (char *) malloc(s.len);
+- if (r.p != NULL) {
+- memcpy((char *) r.p, s.p, s.len);
+- r.len = s.len;
+- }
+- }
+- return r;
+-}
+-
+-int mg_strcmp(const struct mg_str str1, const struct mg_str str2) WEAK;
+-int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
+- size_t i = 0;
+- while (i < str1.len && i < str2.len) {
+- if (str1.p[i] < str2.p[i]) return -1;
+- if (str1.p[i] > str2.p[i]) return 1;
+- i++;
+- }
+- if (i < str1.len) return 1;
+- if (i < str2.len) return -1;
+- return 0;
+-}
+-
+-int mg_strncmp(const struct mg_str, const struct mg_str, size_t n) WEAK;
+-int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) {
+- struct mg_str s1 = str1;
+- struct mg_str s2 = str2;
+-
+- if (s1.len > n) {
+- s1.len = n;
+- }
+- if (s2.len > n) {
+- s2.len = n;
+- }
+- return mg_strcmp(s1, s2);
+-}
+-#ifdef MG_MODULE_LINES
+-#line 1 "common/sha1.c"
++#line 1 "common/cs_sha1.c"
+ #endif
+ /* Copyright(c) By Steve Reid <steve@edmweb.com> */
+ /* 100% Public Domain */
+
+-/* Amalgamated: #include "common/sha1.h" */
++/* Amalgamated: #include "common/cs_sha1.h" */
+
+-#if !DISABLE_SHA1 && !defined(EXCLUDE_COMMON)
++#if !CS_DISABLE_SHA1 && !defined(EXCLUDE_COMMON)
+
+ /* Amalgamated: #include "common/cs_endian.h" */
+
+@@ -1505,6 +1396,233 @@ void cs_hmac_sha1(const unsigned char *k
+
+ #endif /* EXCLUDE_COMMON */
+ #ifdef MG_MODULE_LINES
++#line 1 "common/mbuf.c"
++#endif
++/*
++ * Copyright (c) 2014 Cesanta Software Limited
++ * All rights reserved
++ */
++
++#ifndef EXCLUDE_COMMON
++
++#include <assert.h>
++#include <string.h>
++/* Amalgamated: #include "common/mbuf.h" */
++
++#ifndef MBUF_REALLOC
++#define MBUF_REALLOC realloc
++#endif
++
++#ifndef MBUF_FREE
++#define MBUF_FREE free
++#endif
++
++void mbuf_init(struct mbuf *mbuf, size_t initial_size) WEAK;
++void mbuf_init(struct mbuf *mbuf, size_t initial_size) {
++ mbuf->len = mbuf->size = 0;
++ mbuf->buf = NULL;
++ mbuf_resize(mbuf, initial_size);
++}
++
++void mbuf_free(struct mbuf *mbuf) WEAK;
++void mbuf_free(struct mbuf *mbuf) {
++ if (mbuf->buf != NULL) {
++ MBUF_FREE(mbuf->buf);
++ mbuf_init(mbuf, 0);
++ }
++}
++
++void mbuf_resize(struct mbuf *a, size_t new_size) WEAK;
++void mbuf_resize(struct mbuf *a, size_t new_size) {
++ if (new_size > a->size || (new_size < a->size && new_size >= a->len)) {
++ char *buf = (char *) MBUF_REALLOC(a->buf, new_size);
++ /*
++ * In case realloc fails, there's not much we can do, except keep things as
++ * they are. Note that NULL is a valid return value from realloc when
++ * size == 0, but that is covered too.
++ */
++ if (buf == NULL && new_size != 0) return;
++ a->buf = buf;
++ a->size = new_size;
++ }
++}
++
++void mbuf_trim(struct mbuf *mbuf) WEAK;
++void mbuf_trim(struct mbuf *mbuf) {
++ mbuf_resize(mbuf, mbuf->len);
++}
++
++size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t) WEAK;
++size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) {
++ char *p = NULL;
++
++ assert(a != NULL);
++ assert(a->len <= a->size);
++ assert(off <= a->len);
++
++ /* check overflow */
++ if (~(size_t) 0 - (size_t) a->buf < len) return 0;
++
++ if (a->len + len <= a->size) {
++ memmove(a->buf + off + len, a->buf + off, a->len - off);
++ if (buf != NULL) {
++ memcpy(a->buf + off, buf, len);
++ }
++ a->len += len;
++ } else {
++ size_t new_size = (size_t)((a->len + len) * MBUF_SIZE_MULTIPLIER);
++ if ((p = (char *) MBUF_REALLOC(a->buf, new_size)) != NULL) {
++ a->buf = p;
++ memmove(a->buf + off + len, a->buf + off, a->len - off);
++ if (buf != NULL) memcpy(a->buf + off, buf, len);
++ a->len += len;
++ a->size = new_size;
++ } else {
++ len = 0;
++ }
++ }
++
++ return len;
++}
++
++size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) WEAK;
++size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) {
++ return mbuf_insert(a, a->len, buf, len);
++}
++
++void mbuf_remove(struct mbuf *mb, size_t n) WEAK;
++void mbuf_remove(struct mbuf *mb, size_t n) {
++ if (n > 0 && n <= mb->len) {
++ memmove(mb->buf, mb->buf + n, mb->len - n);
++ mb->len -= n;
++ }
++}
++
++#endif /* EXCLUDE_COMMON */
++#ifdef MG_MODULE_LINES
++#line 1 "common/mg_str.c"
++#endif
++/*
++ * Copyright (c) 2014-2016 Cesanta Software Limited
++ * All rights reserved
++ */
++
++/* Amalgamated: #include "common/mg_mem.h" */
++/* Amalgamated: #include "common/mg_str.h" */
++
++#include <stdlib.h>
++#include <string.h>
++
++int mg_ncasecmp(const char *s1, const char *s2, size_t len) WEAK;
++
++struct mg_str mg_mk_str(const char *s) WEAK;
++struct mg_str mg_mk_str(const char *s) {
++ struct mg_str ret = {s, 0};
++ if (s != NULL) ret.len = strlen(s);
++ return ret;
++}
++
++struct mg_str mg_mk_str_n(const char *s, size_t len) WEAK;
++struct mg_str mg_mk_str_n(const char *s, size_t len) {
++ struct mg_str ret = {s, len};
++ return ret;
++}
++
++int mg_vcmp(const struct mg_str *str1, const char *str2) WEAK;
++int mg_vcmp(const struct mg_str *str1, const char *str2) {
++ size_t n2 = strlen(str2), n1 = str1->len;
++ int r = strncmp(str1->p, str2, (n1 < n2) ? n1 : n2);
++ if (r == 0) {
++ return n1 - n2;
++ }
++ return r;
++}
++
++int mg_vcasecmp(const struct mg_str *str1, const char *str2) WEAK;
++int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
++ size_t n2 = strlen(str2), n1 = str1->len;
++ int r = mg_ncasecmp(str1->p, str2, (n1 < n2) ? n1 : n2);
++ if (r == 0) {
++ return n1 - n2;
++ }
++ return r;
++}
++
++static struct mg_str mg_strdup_common(const struct mg_str s,
++ int nul_terminate) {
++ struct mg_str r = {NULL, 0};
++ if (s.len > 0 && s.p != NULL) {
++ char *sc = (char *) MG_MALLOC(s.len + (nul_terminate ? 1 : 0));
++ if (sc != NULL) {
++ memcpy(sc, s.p, s.len);
++ if (nul_terminate) sc[s.len] = '\0';
++ r.p = sc;
++ r.len = s.len;
++ }
++ }
++ return r;
++}
++
++struct mg_str mg_strdup(const struct mg_str s) WEAK;
++struct mg_str mg_strdup(const struct mg_str s) {
++ return mg_strdup_common(s, 0 /* NUL-terminate */);
++}
++
++struct mg_str mg_strdup_nul(const struct mg_str s) WEAK;
++struct mg_str mg_strdup_nul(const struct mg_str s) {
++ return mg_strdup_common(s, 1 /* NUL-terminate */);
++}
++
++const char *mg_strchr(const struct mg_str s, int c) WEAK;
++const char *mg_strchr(const struct mg_str s, int c) {
++ size_t i;
++ for (i = 0; i < s.len; i++) {
++ if (s.p[i] == c) return &s.p[i];
++ }
++ return NULL;
++}
++
++int mg_strcmp(const struct mg_str str1, const struct mg_str str2) WEAK;
++int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
++ size_t i = 0;
++ while (i < str1.len && i < str2.len) {
++ if (str1.p[i] < str2.p[i]) return -1;
++ if (str1.p[i] > str2.p[i]) return 1;
++ i++;
++ }
++ if (i < str1.len) return 1;
++ if (i < str2.len) return -1;
++ return 0;
++}
++
++int mg_strncmp(const struct mg_str, const struct mg_str, size_t n) WEAK;
++int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) {
++ struct mg_str s1 = str1;
++ struct mg_str s2 = str2;
++
++ if (s1.len > n) {
++ s1.len = n;
++ }
++ if (s2.len > n) {
++ s2.len = n;
++ }
++ return mg_strcmp(s1, s2);
++}
++
++const char *mg_strstr(const struct mg_str haystack,
++ const struct mg_str needle) WEAK;
++const char *mg_strstr(const struct mg_str haystack,
++ const struct mg_str needle) {
++ size_t i;
++ if (needle.len > haystack.len) return NULL;
++ for (i = 0; i <= haystack.len - needle.len; i++) {
++ if (memcmp(haystack.p + i, needle.p, needle.len) == 0) {
++ return haystack.p + i;
++ }
++ }
++ return NULL;
++}
++#ifdef MG_MODULE_LINES
+ #line 1 "common/str_util.c"
+ #endif
+ /*
+@@ -1514,20 +1632,15 @@ void cs_hmac_sha1(const unsigned char *k
+
+ #ifndef EXCLUDE_COMMON
+
+-/* Amalgamated: #include "common/platform.h" */
+ /* Amalgamated: #include "common/str_util.h" */
++/* Amalgamated: #include "common/mg_mem.h" */
++/* Amalgamated: #include "common/platform.h" */
+
+ #ifndef C_DISABLE_BUILTIN_SNPRINTF
+ #define C_DISABLE_BUILTIN_SNPRINTF 0
+ #endif
+
+-#ifndef MG_MALLOC
+-#define MG_MALLOC malloc
+-#endif
+-
+-#ifndef MG_FREE
+-#define MG_FREE free
+-#endif
++/* Amalgamated: #include "common/mg_mem.h" */
+
+ size_t c_strnlen(const char *s, size_t maxlen) WEAK;
+ size_t c_strnlen(const char *s, size_t maxlen) {
+@@ -1793,7 +1906,7 @@ const char *c_strnstr(const char *s, con
+ char *strdup(const char *src) WEAK;
+ char *strdup(const char *src) {
+ size_t len = strlen(src) + 1;
+- char *ret = malloc(len);
++ char *ret = MG_MALLOC(len);
+ if (ret != NULL) {
+ strcpy(ret, src);
+ }
+@@ -1898,12 +2011,24 @@ int mg_avprintf(char **buf, size_t size,
+ *buf = NULL; /* LCOV_EXCL_START */
+ while (len < 0) {
+ MG_FREE(*buf);
++ if (size == 0) {
++ size = 5;
++ }
+ size *= 2;
+- if ((*buf = (char *) MG_MALLOC(size)) == NULL) break;
++ if ((*buf = (char *) MG_MALLOC(size)) == NULL) {
++ len = -1;
++ break;
++ }
+ va_copy(ap_copy, ap);
+- len = vsnprintf(*buf, size, fmt, ap_copy);
++ len = vsnprintf(*buf, size - 1, fmt, ap_copy);
+ va_end(ap_copy);
+ }
++
++ /*
++ * Microsoft version of vsnprintf() is not always null-terminated, so put
++ * the terminator manually
++ */
++ (*buf)[len] = 0;
+ /* LCOV_EXCL_STOP */
+ } else if (len >= (int) size) {
+ /* Standard-compliant code path. Allocate a buffer that is large enough. */
+@@ -1919,95 +2044,109 @@ int mg_avprintf(char **buf, size_t size,
+ return len;
+ }
+
+-#endif /* EXCLUDE_COMMON */
+-#ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/tun.h"
+-#endif
+-/*
+- * Copyright (c) 2014-2016 Cesanta Software Limited
+- * All rights reserved
+- */
+-
+-#ifndef CS_MONGOOSE_SRC_TUN_H_
+-#define CS_MONGOOSE_SRC_TUN_H_
+-
+-#if MG_ENABLE_TUN
+-
+-/* Amalgamated: #include "mongoose/src/net.h" */
+-/* Amalgamated: #include "common/mg_str.h" */
+-
+-#ifndef MG_TUN_RECONNECT_INTERVAL
+-#define MG_TUN_RECONNECT_INTERVAL 1
+-#endif
+-
+-#define MG_TUN_PROTO_NAME "mg_tun"
+-
+-#define MG_TUN_DATA_FRAME 0x0
+-#define MG_TUN_F_END_STREAM 0x1
+-
+-/*
+- * MG TUN frame format is loosely based on HTTP/2.
+- * However since the communication happens via WebSocket
+- * there is no need to encode the frame length, since that's
+- * solved by WebSocket framing.
+- *
+- * TODO(mkm): Detailed description of the protocol.
+- */
+-struct mg_tun_frame {
+- uint8_t type;
+- uint8_t flags;
+- uint32_t stream_id; /* opaque stream identifier */
+- struct mg_str body;
+-};
+-
+-struct mg_tun_ssl_opts {
+-#if MG_ENABLE_SSL
+- const char *ssl_cert;
+- const char *ssl_key;
+- const char *ssl_ca_cert;
+-#else
+- int dummy; /* some compilers don't like empty structs */
+-#endif
+-};
+-
+-struct mg_tun_client {
+- struct mg_mgr *mgr;
+- struct mg_iface *iface;
+- const char *disp_url;
+- struct mg_tun_ssl_opts ssl;
+-
+- uint32_t last_stream_id; /* stream id of most recently accepted connection */
++const char *mg_next_comma_list_entry(const char *, struct mg_str *,
++ struct mg_str *) WEAK;
++const char *mg_next_comma_list_entry(const char *list, struct mg_str *val,
++ struct mg_str *eq_val) {
++ struct mg_str ret = mg_next_comma_list_entry_n(mg_mk_str(list), val, eq_val);
++ return ret.p;
++}
+
+- struct mg_connection *disp;
+- struct mg_connection *listener;
+- struct mg_connection *reconnect;
+-};
++struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val,
++ struct mg_str *eq_val) WEAK;
++struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val,
++ struct mg_str *eq_val) {
++ if (list.len == 0) {
++ /* End of the list */
++ list = mg_mk_str(NULL);
++ } else {
++ const char *chr = NULL;
++ *val = list;
+
+-#ifdef __cplusplus
+-extern "C" {
+-#endif /* __cplusplus */
++ if ((chr = mg_strchr(*val, ',')) != NULL) {
++ /* Comma found. Store length and shift the list ptr */
++ val->len = chr - val->p;
++ chr++;
++ list.len -= (chr - list.p);
++ list.p = chr;
++ } else {
++ /* This value is the last one */
++ list = mg_mk_str_n(list.p + list.len, 0);
++ }
+
+-struct mg_connection *mg_tun_bind_opt(struct mg_mgr *mgr,
+- const char *dispatcher,
+- mg_event_handler_t handler,
+- struct mg_bind_opts opts);
++ if (eq_val != NULL) {
++ /* Value has form "x=y", adjust pointers and lengths */
++ /* so that val points to "x", and eq_val points to "y". */
++ eq_val->len = 0;
++ eq_val->p = (const char *) memchr(val->p, '=', val->len);
++ if (eq_val->p != NULL) {
++ eq_val->p++; /* Skip over '=' character */
++ eq_val->len = val->p + val->len - eq_val->p;
++ val->len = (eq_val->p - val->p) - 1;
++ }
++ }
++ }
+
+-int mg_tun_parse_frame(void *data, size_t len, struct mg_tun_frame *frame);
++ return list;
++}
+
+-void mg_tun_send_frame(struct mg_connection *ws, uint32_t stream_id,
+- uint8_t type, uint8_t flags, struct mg_str msg);
++size_t mg_match_prefix_n(const struct mg_str, const struct mg_str) WEAK;
++size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) {
++ const char *or_str;
++ size_t res = 0, len = 0, i = 0, j = 0;
+
+-void mg_tun_destroy_client(struct mg_tun_client *client);
++ if ((or_str = (const char *) memchr(pattern.p, '|', pattern.len)) != NULL ||
++ (or_str = (const char *) memchr(pattern.p, ',', pattern.len)) != NULL) {
++ struct mg_str pstr = {pattern.p, (size_t)(or_str - pattern.p)};
++ res = mg_match_prefix_n(pstr, str);
++ if (res > 0) return res;
++ pstr.p = or_str + 1;
++ pstr.len = (pattern.p + pattern.len) - (or_str + 1);
++ return mg_match_prefix_n(pstr, str);
++ }
+
+-#ifdef __cplusplus
++ for (; i < pattern.len && j < str.len; i++, j++) {
++ if (pattern.p[i] == '?') {
++ continue;
++ } else if (pattern.p[i] == '*') {
++ i++;
++ if (i < pattern.len && pattern.p[i] == '*') {
++ i++;
++ len = str.len - j;
++ } else {
++ len = 0;
++ while (j + len < str.len && str.p[j + len] != '/') len++;
++ }
++ if (i == pattern.len || (pattern.p[i] == '$' && i == pattern.len - 1))
++ return j + len;
++ do {
++ const struct mg_str pstr = {pattern.p + i, pattern.len - i};
++ const struct mg_str sstr = {str.p + j + len, str.len - j - len};
++ res = mg_match_prefix_n(pstr, sstr);
++ } while (res == 0 && len != 0 && len-- > 0);
++ return res == 0 ? 0 : j + res + len;
++ } else if (str_util_lowercase(&pattern.p[i]) !=
++ str_util_lowercase(&str.p[j])) {
++ break;
++ }
++ }
++ if (i < pattern.len && pattern.p[i] == '$') {
++ return j == str.len ? str.len : 0;
++ }
++ return i == pattern.len ? j : 0;
+ }
+-#endif /* __cplusplus */
+
+-#endif /* MG_ENABLE_TUN */
++size_t mg_match_prefix(const char *, int, const char *) WEAK;
++size_t mg_match_prefix(const char *pattern, int pattern_len, const char *str) {
++ const struct mg_str pstr = {pattern, (size_t) pattern_len};
++ struct mg_str s = {str, 0};
++ if (str != NULL) s.len = strlen(str);
++ return mg_match_prefix_n(pstr, s);
++}
+
+-#endif /* CS_MONGOOSE_SRC_TUN_H_ */
++#endif /* EXCLUDE_COMMON */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/net.c"
++#line 1 "mongoose/src/mg_net.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -2028,11 +2167,10 @@ void mg_tun_destroy_client(struct mg_tun
+ */
+
+ /* Amalgamated: #include "common/cs_time.h" */
+-/* Amalgamated: #include "mongoose/src/dns.h" */
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/resolv.h" */
+-/* Amalgamated: #include "mongoose/src/util.h" */
+-/* Amalgamated: #include "mongoose/src/tun.h" */
++/* Amalgamated: #include "mg_dns.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_resolv.h" */
++/* Amalgamated: #include "mg_util.h" */
+
+ #define MG_MAX_HOST_LEN 200
+
+@@ -2069,11 +2207,15 @@ MG_INTERNAL void mg_remove_conn(struct m
+ if (conn->prev == NULL) conn->mgr->active_connections = conn->next;
+ if (conn->prev) conn->prev->next = conn->next;
+ if (conn->next) conn->next->prev = conn->prev;
++ conn->prev = conn->next = NULL;
+ conn->iface->vtable->remove_conn(conn);
+ }
+
+ MG_INTERNAL void mg_call(struct mg_connection *nc,
+- mg_event_handler_t ev_handler, int ev, void *ev_data) {
++ mg_event_handler_t ev_handler, void *user_data, int ev,
++ void *ev_data) {
++ static int nesting_level = 0;
++ nesting_level++;
+ if (ev_handler == NULL) {
+ /*
+ * If protocol handler is specified, call it. Otherwise, call user-specified
+@@ -2088,29 +2230,25 @@ MG_INTERNAL void mg_call(struct mg_conne
+ }
+
+ #if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+- /* LCOV_EXCL_START */
+- if (nc->mgr->hexdump_file != NULL && ev != MG_EV_POLL &&
++ if (nc->mgr->hexdump_file != NULL && ev != MG_EV_POLL && ev != MG_EV_RECV &&
+ ev != MG_EV_SEND /* handled separately */) {
+- if (ev == MG_EV_RECV) {
+- mg_hexdump_connection(nc, nc->mgr->hexdump_file, nc->recv_mbuf.buf,
+- *(int *) ev_data, ev);
+- } else {
+- mg_hexdump_connection(nc, nc->mgr->hexdump_file, NULL, 0, ev);
+- }
++ mg_hexdump_connection(nc, nc->mgr->hexdump_file, NULL, 0, ev);
+ }
+-/* LCOV_EXCL_STOP */
+ #endif
+ if (ev_handler != NULL) {
+ unsigned long flags_before = nc->flags;
+ size_t recv_mbuf_before = nc->recv_mbuf.len, recved;
+- ev_handler(nc, ev, ev_data);
++ ev_handler(nc, ev, ev_data MG_UD_ARG(user_data));
+ recved = (recv_mbuf_before - nc->recv_mbuf.len);
+ /* Prevent user handler from fiddling with system flags. */
+ if (ev_handler == nc->handler && nc->flags != flags_before) {
+ nc->flags = (flags_before & ~_MG_CALLBACK_MODIFIABLE_FLAGS_MASK) |
+ (nc->flags & _MG_CALLBACK_MODIFIABLE_FLAGS_MASK);
+ }
+- if (recved > 0 && !(nc->flags & MG_F_UDP)) {
++ /* It's important to not double-count recved bytes, and since mg_call can be
++ * called recursively (e.g. proto_handler invokes user handler), we keep
++ * track of recursion and only report received bytes at the top level. */
++ if (nesting_level == 1 && recved > 0 && !(nc->flags & MG_F_UDP)) {
+ nc->iface->vtable->recved(nc, recved);
+ }
+ }
+@@ -2119,29 +2257,27 @@ MG_INTERNAL void mg_call(struct mg_conne
+ ev_handler == nc->handler ? "user" : "proto", nc->flags,
+ (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
+ }
++ nesting_level--;
++#if !MG_ENABLE_CALLBACK_USERDATA
++ (void) user_data;
++#endif
+ }
+
+ void mg_if_timer(struct mg_connection *c, double now) {
+ if (c->ev_timer_time > 0 && now >= c->ev_timer_time) {
+ double old_value = c->ev_timer_time;
+- mg_call(c, NULL, MG_EV_TIMER, &now);
+- /*
+- * To prevent timer firing all the time, reset the timer after delivery.
+- * However, in case user sets it to new value, do not reset.
+- */
+- if (c->ev_timer_time == old_value) {
+- c->ev_timer_time = 0;
+- }
++ c->ev_timer_time = 0;
++ mg_call(c, NULL, c->user_data, MG_EV_TIMER, &old_value);
+ }
+ }
+
+ void mg_if_poll(struct mg_connection *nc, time_t now) {
+ if (!(nc->flags & MG_F_SSL) || (nc->flags & MG_F_SSL_HANDSHAKE_DONE)) {
+- mg_call(nc, NULL, MG_EV_POLL, &now);
++ mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now);
+ }
+ }
+
+-static void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
++void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
+ if (destroy_if) conn->iface->vtable->destroy_conn(conn);
+ if (conn->proto_data != NULL && conn->proto_data_destructor != NULL) {
+ conn->proto_data_destructor(conn->proto_data);
+@@ -2158,9 +2294,14 @@ static void mg_destroy_conn(struct mg_co
+
+ void mg_close_conn(struct mg_connection *conn) {
+ DBG(("%p %lu %d", conn, conn->flags, conn->sock));
++#if MG_ENABLE_SSL
++ if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) {
++ mg_ssl_if_conn_close_notify(conn);
++ }
++#endif
+ mg_remove_conn(conn);
+ conn->iface->vtable->destroy_conn(conn);
+- mg_call(conn, NULL, MG_EV_CLOSE, NULL);
++ mg_call(conn, NULL, conn->user_data, MG_EV_CLOSE, NULL);
+ mg_destroy_conn(conn, 0 /* destroy_if */);
+ }
+
+@@ -2215,36 +2356,13 @@ void mg_mgr_init_opt(struct mg_mgr *m, v
+ m->ifaces[i]->vtable->init(m->ifaces[i]);
+ }
+ }
++ if (opts.nameserver != NULL) {
++ m->nameserver = strdup(opts.nameserver);
++ }
+ DBG(("=================================="));
+ DBG(("init mgr=%p", m));
+ }
+
+-#if MG_ENABLE_JAVASCRIPT
+-static enum v7_err mg_send_js(struct v7 *v7, v7_val_t *res) {
+- v7_val_t arg0 = v7_arg(v7, 0);
+- v7_val_t arg1 = v7_arg(v7, 1);
+- struct mg_connection *c = (struct mg_connection *) v7_get_ptr(v7, arg0);
+- size_t len = 0;
+-
+- if (v7_is_string(arg1)) {
+- const char *data = v7_get_string(v7, &arg1, &len);
+- mg_send(c, data, len);
+- }
+-
+- *res = v7_mk_number(v7, len);
+-
+- return V7_OK;
+-}
+-
+-enum v7_err mg_enable_javascript(struct mg_mgr *m, struct v7 *v7,
+- const char *init_file_name) {
+- v7_val_t v;
+- m->v7 = v7;
+- v7_set_method(v7, v7_get_global(v7), "mg_send", mg_send_js);
+- return v7_exec_file(v7, init_file_name, &v);
+-}
+-#endif
+-
+ void mg_mgr_free(struct mg_mgr *m) {
+ struct mg_connection *conn, *tmp_conn;
+
+@@ -2272,6 +2390,8 @@ void mg_mgr_free(struct mg_mgr *m) {
+ }
+ MG_FREE(m->ifaces);
+ }
++
++ MG_FREE((char *) m->nameserver);
+ }
+
+ time_t mg_mgr_poll(struct mg_mgr *m, int timeout_ms) {
+@@ -2397,7 +2517,7 @@ MG_INTERNAL struct mg_connection *mg_cre
+ * Address format: [PROTO://][HOST]:PORT
+ *
+ * HOST could be IPv4/IPv6 address or a host name.
+- * `host` is a destination buffer to hold parsed HOST part. Shoud be at least
++ * `host` is a destination buffer to hold parsed HOST part. Should be at least
+ * MG_MAX_HOST_LEN bytes long.
+ * `proto` is a returned socket type, either SOCK_STREAM or SOCK_DGRAM
+ *
+@@ -2504,7 +2624,7 @@ void mg_if_accept_tcp_cb(struct mg_conne
+ size_t sa_len) {
+ (void) sa_len;
+ nc->sa = *sa;
+- mg_call(nc, NULL, MG_EV_ACCEPT, &nc->sa);
++ mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+ }
+
+ void mg_send(struct mg_connection *nc, const void *buf, int len) {
+@@ -2514,23 +2634,35 @@ void mg_send(struct mg_connection *nc, c
+ } else {
+ nc->iface->vtable->tcp_send(nc, buf, len);
+ }
++}
++
++void mg_if_sent_cb(struct mg_connection *nc, int num_sent) {
++ DBG(("%p %d", nc, num_sent));
+ #if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+ if (nc->mgr && nc->mgr->hexdump_file != NULL) {
+- mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, len, MG_EV_SEND);
++ char *buf = nc->send_mbuf.buf;
++ mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, num_sent, MG_EV_SEND);
+ }
+ #endif
+-}
+-
+-void mg_if_sent_cb(struct mg_connection *nc, int num_sent) {
+ if (num_sent < 0) {
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ } else {
++ mbuf_remove(&nc->send_mbuf, num_sent);
++ mbuf_trim(&nc->send_mbuf);
+ }
+- mg_call(nc, NULL, MG_EV_SEND, &num_sent);
++ mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &num_sent);
+ }
+
+ MG_INTERNAL void mg_recv_common(struct mg_connection *nc, void *buf, int len,
+ int own) {
+ DBG(("%p %d %u", nc, len, (unsigned int) nc->recv_mbuf.len));
++
++#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
++ if (nc->mgr && nc->mgr->hexdump_file != NULL) {
++ mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, len, MG_EV_RECV);
++ }
++#endif
++
+ if (nc->flags & MG_F_CLOSE_IMMEDIATELY) {
+ DBG(("%p discarded %d bytes", nc, len));
+ /*
+@@ -2554,7 +2686,7 @@ MG_INTERNAL void mg_recv_common(struct m
+ mbuf_append(&nc->recv_mbuf, buf, len);
+ MG_FREE(buf);
+ }
+- mg_call(nc, NULL, MG_EV_RECV, &len);
++ mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &len);
+ }
+
+ void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own) {
+@@ -2602,7 +2734,7 @@ void mg_if_recv_udp_cb(struct mg_connect
+ */
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ mg_add_conn(lc->mgr, nc);
+- mg_call(nc, NULL, MG_EV_ACCEPT, &nc->sa);
++ mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+ } else {
+ DBG(("OOM"));
+ /* No return here, we still need to drop on the floor */
+@@ -2614,7 +2746,6 @@ void mg_if_recv_udp_cb(struct mg_connect
+ } else {
+ /* Drop on the floor. */
+ MG_FREE(buf);
+- nc->iface->vtable->recved(nc, len);
+ }
+ }
+
+@@ -2646,7 +2777,7 @@ void mg_if_connect_cb(struct mg_connecti
+ if (err != 0) {
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ }
+- mg_call(nc, NULL, MG_EV_CONNECT, &err);
++ mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+ }
+
+ #if MG_ENABLE_ASYNC_RESOLVER
+@@ -2684,27 +2815,29 @@ static void resolve_cb(struct mg_dns_mes
+
+ if (e == MG_RESOLVE_TIMEOUT) {
+ double now = mg_time();
+- mg_call(nc, NULL, MG_EV_TIMER, &now);
++ mg_call(nc, NULL, nc->user_data, MG_EV_TIMER, &now);
+ }
+
+ /*
+ * If we get there was no MG_DNS_A_RECORD in the answer
+ */
+- mg_call(nc, NULL, MG_EV_CONNECT, &failure);
+- mg_call(nc, NULL, MG_EV_CLOSE, NULL);
++ mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &failure);
++ mg_call(nc, NULL, nc->user_data, MG_EV_CLOSE, NULL);
+ mg_destroy_conn(nc, 1 /* destroy_if */);
+ }
+ #endif
+
+ struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *address,
+- mg_event_handler_t callback) {
++ MG_CB(mg_event_handler_t callback,
++ void *user_data)) {
+ struct mg_connect_opts opts;
+ memset(&opts, 0, sizeof(opts));
+- return mg_connect_opt(mgr, address, callback, opts);
++ return mg_connect_opt(mgr, address, MG_CB(callback, user_data), opts);
+ }
+
+ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
+- mg_event_handler_t callback,
++ MG_CB(mg_event_handler_t callback,
++ void *user_data),
+ struct mg_connect_opts opts) {
+ struct mg_connection *nc = NULL;
+ int proto, rc;
+@@ -2727,14 +2860,19 @@ struct mg_connection *mg_connect_opt(str
+
+ nc->flags |= opts.flags & _MG_ALLOWED_CONNECT_FLAGS_MASK;
+ nc->flags |= (proto == SOCK_DGRAM) ? MG_F_UDP : 0;
++#if MG_ENABLE_CALLBACK_USERDATA
++ nc->user_data = user_data;
++#else
+ nc->user_data = opts.user_data;
++#endif
+
+ #if MG_ENABLE_SSL
+ DBG(("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"),
+ (opts.ssl_key ? opts.ssl_key : "-"),
+ (opts.ssl_ca_cert ? opts.ssl_ca_cert : "-")));
+
+- if (opts.ssl_cert != NULL || opts.ssl_ca_cert != NULL) {
++ if (opts.ssl_cert != NULL || opts.ssl_ca_cert != NULL ||
++ opts.ssl_psk_identity != NULL) {
+ const char *err_msg = NULL;
+ struct mg_ssl_if_conn_params params;
+ if (nc->flags & MG_F_UDP) {
+@@ -2746,6 +2884,9 @@ struct mg_connection *mg_connect_opt(str
+ params.cert = opts.ssl_cert;
+ params.key = opts.ssl_key;
+ params.ca_cert = opts.ssl_ca_cert;
++ params.cipher_suites = opts.ssl_cipher_suites;
++ params.psk_identity = opts.ssl_psk_identity;
++ params.psk_key = opts.ssl_psk_key;
+ if (opts.ssl_ca_cert != NULL) {
+ if (opts.ssl_server_name != NULL) {
+ if (strcmp(opts.ssl_server_name, "*") != 0) {
+@@ -2774,6 +2915,7 @@ struct mg_connection *mg_connect_opt(str
+ struct mg_resolve_async_opts o;
+ memset(&o, 0, sizeof(o));
+ o.dns_conn = &dns_conn;
++ o.nameserver = opts.nameserver;
+ if (mg_resolve_async_opt(nc->mgr, host, MG_DNS_A_RECORD, resolve_cb, nc,
+ o) != 0) {
+ MG_SET_PTRPTR(opts.error_string, "cannot schedule DNS lookup");
+@@ -2795,14 +2937,16 @@ struct mg_connection *mg_connect_opt(str
+ }
+
+ struct mg_connection *mg_bind(struct mg_mgr *srv, const char *address,
+- mg_event_handler_t event_handler) {
++ MG_CB(mg_event_handler_t event_handler,
++ void *user_data)) {
+ struct mg_bind_opts opts;
+ memset(&opts, 0, sizeof(opts));
+- return mg_bind_opt(srv, address, event_handler, opts);
++ return mg_bind_opt(srv, address, MG_CB(event_handler, user_data), opts);
+ }
+
+ struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
+- mg_event_handler_t callback,
++ MG_CB(mg_event_handler_t callback,
++ void *user_data),
+ struct mg_bind_opts opts) {
+ union socket_address sa;
+ struct mg_connection *nc = NULL;
+@@ -2810,14 +2954,16 @@ struct mg_connection *mg_bind_opt(struct
+ struct mg_add_sock_opts add_sock_opts;
+ char host[MG_MAX_HOST_LEN];
+
+- MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);
++#if MG_ENABLE_CALLBACK_USERDATA
++ opts.user_data = user_data;
++#endif
+
+-#if MG_ENABLE_TUN
+- if (mg_strncmp(mg_mk_str(address), mg_mk_str("ws://"), 5) == 0 ||
+- mg_strncmp(mg_mk_str(address), mg_mk_str("wss://"), 6) == 0) {
+- return mg_tun_bind_opt(mgr, address, callback, opts);
++ if (callback == NULL) {
++ MG_SET_PTRPTR(opts.error_string, "handler is required");
++ return NULL;
+ }
+-#endif
++
++ MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);
+
+ if (mg_parse_address(address, &sa, &proto, host, sizeof(host)) <= 0) {
+ MG_SET_PTRPTR(opts.error_string, "cannot parse address");
+@@ -2850,6 +2996,7 @@ struct mg_connection *mg_bind_opt(struct
+ params.cert = opts.ssl_cert;
+ params.key = opts.ssl_key;
+ params.ca_cert = opts.ssl_ca_cert;
++ params.cipher_suites = opts.ssl_cipher_suites;
+ if (mg_ssl_if_conn_init(nc, &params, &err_msg) != MG_SSL_OK) {
+ MG_SET_PTRPTR(opts.error_string, err_msg);
+ mg_destroy_conn(nc, 1 /* destroy_if */);
+@@ -2945,7 +3092,7 @@ int mg_check_ip_acl(const char *acl, uin
+ }
+ }
+
+- DBG(("%08x %c", remote_ip, allowed));
++ DBG(("%08x %c", (unsigned int) remote_ip, allowed));
+ return allowed == '+';
+ }
+
+@@ -2963,7 +3110,7 @@ double mg_set_timer(struct mg_connection
+ * connections, so not processed yet. It has a DNS resolver connection
+ * linked to it. Set up a timer for the DNS connection.
+ */
+- DBG(("%p %p %d -> %lu", c, c->priv_2, c->flags & MG_F_RESOLVING,
++ DBG(("%p %p %d -> %lu", c, c->priv_2, (c->flags & MG_F_RESOLVING ? 1 : 0),
+ (unsigned long) timestamp));
+ if ((c->flags & MG_F_RESOLVING) && c->priv_2 != NULL) {
+ ((struct mg_connection *) c->priv_2)->ev_timer_time = timestamp;
+@@ -2983,8 +3130,13 @@ void mg_if_get_conn_addr(struct mg_conne
+ }
+
+ struct mg_connection *mg_add_sock_opt(struct mg_mgr *s, sock_t sock,
+- mg_event_handler_t callback,
++ MG_CB(mg_event_handler_t callback,
++ void *user_data),
+ struct mg_add_sock_opts opts) {
++#if MG_ENABLE_CALLBACK_USERDATA
++ opts.user_data = user_data;
++#endif
++
+ struct mg_connection *nc = mg_create_connection_base(s, callback, opts);
+ if (nc != NULL) {
+ mg_sock_set(nc, sock);
+@@ -2994,17 +3146,18 @@ struct mg_connection *mg_add_sock_opt(st
+ }
+
+ struct mg_connection *mg_add_sock(struct mg_mgr *s, sock_t sock,
+- mg_event_handler_t callback) {
++ MG_CB(mg_event_handler_t callback,
++ void *user_data)) {
+ struct mg_add_sock_opts opts;
+ memset(&opts, 0, sizeof(opts));
+- return mg_add_sock_opt(s, sock, callback, opts);
++ return mg_add_sock_opt(s, sock, MG_CB(callback, user_data), opts);
+ }
+
+ double mg_time(void) {
+ return cs_time();
+ }
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/net_if_socket.h"
++#line 1 "mongoose/src/mg_net_if_socket.h"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+@@ -3014,7 +3167,7 @@ double mg_time(void) {
+ #ifndef CS_MONGOOSE_SRC_NET_IF_SOCKET_H_
+ #define CS_MONGOOSE_SRC_NET_IF_SOCKET_H_
+
+-/* Amalgamated: #include "mongoose/src/net_if.h" */
++/* Amalgamated: #include "mg_net_if.h" */
+
+ #ifdef __cplusplus
+ extern "C" {
+@@ -3024,7 +3177,7 @@ extern "C" {
+ #define MG_ENABLE_NET_IF_SOCKET MG_NET_IF == MG_NET_IF_SOCKET
+ #endif
+
+-extern struct mg_iface_vtable mg_socket_iface_vtable;
++extern const struct mg_iface_vtable mg_socket_iface_vtable;
+
+ #ifdef __cplusplus
+ }
+@@ -3032,58 +3185,46 @@ extern struct mg_iface_vtable mg_socket_
+
+ #endif /* CS_MONGOOSE_SRC_NET_IF_SOCKET_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/net_if_tun.h"
++#line 1 "mongoose/src/mg_net_if_socks.h"
+ #endif
+ /*
+- * Copyright (c) 2014-2016 Cesanta Software Limited
+- * All rights reserved
+- */
+-
+-#ifndef CS_MONGOOSE_SRC_NET_IF_TUN_H_
+-#define CS_MONGOOSE_SRC_NET_IF_TUN_H_
+-
+-#if MG_ENABLE_TUN
++* Copyright (c) 2014-2017 Cesanta Software Limited
++* All rights reserved
++*/
+
+-/* Amalgamated: #include "mongoose/src/net_if.h" */
++#ifndef CS_MONGOOSE_SRC_NET_IF_SOCKS_H_
++#define CS_MONGOOSE_SRC_NET_IF_SOCKS_H_
+
+-struct mg_tun_client;
++#if MG_ENABLE_SOCKS
++/* Amalgamated: #include "mg_net_if.h" */
+
+ #ifdef __cplusplus
+ extern "C" {
+ #endif /* __cplusplus */
+
+-extern struct mg_iface_vtable mg_tun_iface_vtable;
+-
+-struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client,
+- uint32_t stream_id);
++extern const struct mg_iface_vtable mg_socks_iface_vtable;
+
+ #ifdef __cplusplus
+ }
+ #endif /* __cplusplus */
+-
+-#endif /* MG_ENABLE_TUN */
+-
+-#endif /* CS_MONGOOSE_SRC_NET_IF_TUN_H_ */
++#endif /* MG_ENABLE_SOCKS */
++#endif /* CS_MONGOOSE_SRC_NET_IF_SOCKS_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/net_if.c"
++#line 1 "mongoose/src/mg_net_if.c"
+ #endif
+-/* Amalgamated: #include "mongoose/src/net_if.h" */
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/net_if_socket.h" */
+-/* Amalgamated: #include "mongoose/src/net_if_tun.h" */
++/* Amalgamated: #include "mg_net_if.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_net_if_socket.h" */
+
+-extern struct mg_iface_vtable mg_default_iface_vtable;
++extern const struct mg_iface_vtable mg_default_iface_vtable;
+
+-#if MG_ENABLE_TUN
+-struct mg_iface_vtable *mg_ifaces[] = {&mg_default_iface_vtable,
+- &mg_tun_iface_vtable};
+-#else
+-struct mg_iface_vtable *mg_ifaces[] = {&mg_default_iface_vtable};
+-#endif
++const struct mg_iface_vtable *mg_ifaces[] = {
++ &mg_default_iface_vtable,
++};
+
+ int mg_num_ifaces = (int) (sizeof(mg_ifaces) / sizeof(mg_ifaces[0]));
+
+-struct mg_iface *mg_if_create_iface(struct mg_iface_vtable *vtable,
++struct mg_iface *mg_if_create_iface(const struct mg_iface_vtable *vtable,
+ struct mg_mgr *mgr) {
+ struct mg_iface *iface = (struct mg_iface *) MG_CALLOC(1, sizeof(*iface));
+ iface->mgr = mgr;
+@@ -3093,7 +3234,7 @@ struct mg_iface *mg_if_create_iface(stru
+ }
+
+ struct mg_iface *mg_find_iface(struct mg_mgr *mgr,
+- struct mg_iface_vtable *vtable,
++ const struct mg_iface_vtable *vtable,
+ struct mg_iface *from) {
+ int i = 0;
+ if (from != NULL) {
+@@ -3113,7 +3254,7 @@ struct mg_iface *mg_find_iface(struct mg
+ return NULL;
+ }
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/net_if_socket.c"
++#line 1 "mongoose/src/mg_net_if_socket.c"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+@@ -3122,9 +3263,9 @@ struct mg_iface *mg_find_iface(struct mg
+
+ #if MG_ENABLE_NET_IF_SOCKET
+
+-/* Amalgamated: #include "mongoose/src/net_if_socket.h" */
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/util.h" */
++/* Amalgamated: #include "mg_net_if_socket.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_util.h" */
+
+ #define MG_TCP_RECV_BUFFER_SIZE 1024
+ #define MG_UDP_RECV_BUFFER_SIZE 1500
+@@ -3145,17 +3286,16 @@ void mg_set_non_blocking_mode(sock_t soc
+ #endif
+ }
+
+-static int mg_is_error(int n) {
++static int mg_is_error(void) {
+ int err = mg_get_errno();
+- return (n < 0 && err != EINPROGRESS && err != EWOULDBLOCK
++ return err != EINPROGRESS && err != EWOULDBLOCK
+ #ifndef WINCE
+- && err != EAGAIN && err != EINTR
++ && err != EAGAIN && err != EINTR
+ #endif
+ #ifdef _WIN32
+- && WSAGetLastError() != WSAEINTR &&
+- WSAGetLastError() != WSAEWOULDBLOCK
++ && WSAGetLastError() != WSAEINTR && WSAGetLastError() != WSAEWOULDBLOCK
+ #endif
+- );
++ ;
+ }
+
+ void mg_socket_if_connect_tcp(struct mg_connection *nc,
+@@ -3170,7 +3310,7 @@ void mg_socket_if_connect_tcp(struct mg_
+ mg_set_non_blocking_mode(nc->sock);
+ #endif
+ rc = connect(nc->sock, &sa->sa, sizeof(sa->sin));
+- nc->err = mg_is_error(rc) ? mg_get_errno() : 0;
++ nc->err = rc < 0 && mg_is_error() ? mg_get_errno() : 0;
+ DBG(("%p sock %d rc %d errno %d err %d", nc, nc->sock, rc, mg_get_errno(),
+ nc->err));
+ }
+@@ -3183,8 +3323,11 @@ void mg_socket_if_connect_udp(struct mg_
+ }
+ if (nc->flags & MG_F_ENABLE_BROADCAST) {
+ int optval = 1;
+- setsockopt(nc->sock, SOL_SOCKET, SO_BROADCAST, (const char *) &optval,
+- sizeof(optval));
++ if (setsockopt(nc->sock, SOL_SOCKET, SO_BROADCAST, (const char *) &optval,
++ sizeof(optval)) < 0) {
++ nc->err = mg_get_errno() ? mg_get_errno() : 1;
++ return;
++ }
+ }
+ nc->err = 0;
+ }
+@@ -3246,7 +3389,7 @@ static int mg_accept_conn(struct mg_conn
+ /* NOTE(lsm): on Windows, sock is always > FD_SETSIZE */
+ sock_t sock = accept(lc->sock, &sa.sa, &sa_len);
+ if (sock == INVALID_SOCKET) {
+- if (mg_is_error(-1)) DBG(("%p: failed to accept: %d", lc, mg_get_errno()));
++ if (mg_is_error()) DBG(("%p: failed to accept: %d", lc, mg_get_errno()));
+ return 0;
+ }
+ nc = mg_if_accept_new_conn(lc);
+@@ -3331,10 +3474,7 @@ static void mg_write_to_socket(struct mg
+ sendto(nc->sock, io->buf, io->len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
+ DBG(("%p %d %d %d %s:%hu", nc, nc->sock, n, mg_get_errno(),
+ inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port)));
+- if (n > 0) {
+- mbuf_remove(io, n);
+- mg_if_sent_cb(nc, n);
+- }
++ mg_if_sent_cb(nc, n);
+ return;
+ }
+
+@@ -3361,17 +3501,9 @@ static void mg_write_to_socket(struct mg
+ {
+ n = (int) MG_SEND_FUNC(nc->sock, io->buf, io->len, 0);
+ DBG(("%p %d bytes -> %d", nc, n, nc->sock));
+- if (n < 0 && mg_is_error(n)) {
+- /* Something went wrong, drop the connection. */
+- nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+- return;
+- }
+ }
+
+- if (n > 0) {
+- mbuf_remove(io, n);
+- mg_if_sent_cb(nc, n);
+- }
++ mg_if_sent_cb(nc, n);
+ }
+
+ MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
+@@ -3426,7 +3558,7 @@ static void mg_handle_tcp_read(struct mg
+ if (n == 0) {
+ /* Orderly shutdown of the socket, try flushing output. */
+ conn->flags |= MG_F_SEND_AND_CLOSE;
+- } else if (mg_is_error(n)) {
++ } else if (n < 0 && mg_is_error()) {
+ conn->flags |= MG_F_CLOSE_IMMEDIATELY;
+ }
+ }
+@@ -3574,7 +3706,8 @@ static void mg_mgr_handle_ctl_sock(struc
+ if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) {
+ struct mg_connection *nc;
+ for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
+- ctl_msg.callback(nc, MG_EV_POLL, ctl_msg.message);
++ ctl_msg.callback(nc, MG_EV_POLL,
++ ctl_msg.message MG_UD_ARG(nc->user_data));
+ }
+ }
+ }
+@@ -3592,9 +3725,7 @@ void mg_socket_if_init(struct mg_iface *
+ (void) iface;
+ DBG(("%p using select()", iface->mgr));
+ #if MG_ENABLE_BROADCAST
+- do {
+- mg_socketpair(iface->mgr->ctl, SOCK_DGRAM);
+- } while (iface->mgr->ctl[0] == INVALID_SOCKET);
++ mg_socketpair(iface->mgr->ctl, SOCK_DGRAM);
+ #endif
+ }
+
+@@ -3613,7 +3744,7 @@ void mg_socket_if_remove_conn(struct mg_
+ void mg_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {
+ if (sock != INVALID_SOCKET
+ #ifdef __unix__
+- && sock < FD_SETSIZE
++ && sock < (sock_t) FD_SETSIZE
+ #endif
+ ) {
+ FD_SET(sock, set);
+@@ -3656,12 +3787,18 @@ time_t mg_socket_if_poll(struct mg_iface
+
+ #ifdef __unix__
+ /* A hack to make sure all our file descriptos fit into FD_SETSIZE. */
+- if (nc->sock >= FD_SETSIZE && try_dup) {
++ if (nc->sock >= (sock_t) FD_SETSIZE && try_dup) {
+ int new_sock = dup(nc->sock);
+- if (new_sock >= 0 && new_sock < FD_SETSIZE) {
+- closesocket(nc->sock);
+- DBG(("new sock %d -> %d", nc->sock, new_sock));
+- nc->sock = new_sock;
++ if (new_sock >= 0) {
++ if (new_sock < (sock_t) FD_SETSIZE) {
++ closesocket(nc->sock);
++ DBG(("new sock %d -> %d", nc->sock, new_sock));
++ nc->sock = new_sock;
++ } else {
++ closesocket(new_sock);
++ DBG(("new sock is still larger than FD_SETSIZE, disregard"));
++ try_dup = 0;
++ }
+ } else {
+ try_dup = 0;
+ }
+@@ -3752,6 +3889,26 @@ time_t mg_socket_if_poll(struct mg_iface
+ }
+
+ #if MG_ENABLE_BROADCAST
++MG_INTERNAL void mg_socketpair_close(sock_t *sock) {
++ while (1) {
++ if (closesocket(*sock) == -1 && errno == EINTR) continue;
++ break;
++ }
++ *sock = INVALID_SOCKET;
++}
++
++MG_INTERNAL sock_t
++mg_socketpair_accept(sock_t sock, union socket_address *sa, socklen_t sa_len) {
++ sock_t rc;
++ while (1) {
++ if ((rc = accept(sock, &sa->sa, &sa_len)) == INVALID_SOCKET &&
++ errno == EINTR)
++ continue;
++ break;
++ }
++ return rc;
++}
++
+ int mg_socketpair(sock_t sp[2], int sock_type) {
+ union socket_address sa;
+ sock_t sock;
+@@ -3774,21 +3931,20 @@ int mg_socketpair(sock_t sp[2], int sock
+ } else if (sock_type == SOCK_DGRAM &&
+ (getsockname(sp[0], &sa.sa, &len) != 0 ||
+ connect(sock, &sa.sa, len) != 0)) {
+- } else if ((sp[1] = (sock_type == SOCK_DGRAM ? sock
+- : accept(sock, &sa.sa, &len))) ==
++ } else if ((sp[1] = (sock_type == SOCK_DGRAM ? sock : mg_socketpair_accept(
++ sock, &sa, len))) ==
+ INVALID_SOCKET) {
+ } else {
+ mg_set_close_on_exec(sp[0]);
+ mg_set_close_on_exec(sp[1]);
+- if (sock_type == SOCK_STREAM) closesocket(sock);
++ if (sock_type == SOCK_STREAM) mg_socketpair_close(&sock);
+ ret = 1;
+ }
+
+ if (!ret) {
+- if (sp[0] != INVALID_SOCKET) closesocket(sp[0]);
+- if (sp[1] != INVALID_SOCKET) closesocket(sp[1]);
+- if (sock != INVALID_SOCKET) closesocket(sock);
+- sock = sp[0] = sp[1] = INVALID_SOCKET;
++ if (sp[0] != INVALID_SOCKET) mg_socketpair_close(&sp[0]);
++ if (sp[1] != INVALID_SOCKET) mg_socketpair_close(&sp[1]);
++ if (sock != INVALID_SOCKET) mg_socketpair_close(&sock);
+ }
+
+ return ret;
+@@ -3814,6 +3970,10 @@ void mg_sock_to_str(sock_t sock, char *b
+
+ void mg_socket_if_get_conn_addr(struct mg_connection *nc, int remote,
+ union socket_address *sa) {
++ if ((nc->flags & MG_F_UDP) && remote) {
++ memcpy(sa, &nc->sa, sizeof(*sa));
++ return;
++ }
+ mg_sock_get_addr(nc->sock, remote, sa);
+ }
+
+@@ -3839,187 +3999,226 @@ void mg_socket_if_get_conn_addr(struct m
+ }
+ /* clang-format on */
+
+-struct mg_iface_vtable mg_socket_iface_vtable = MG_SOCKET_IFACE_VTABLE;
++const struct mg_iface_vtable mg_socket_iface_vtable = MG_SOCKET_IFACE_VTABLE;
+ #if MG_NET_IF == MG_NET_IF_SOCKET
+-struct mg_iface_vtable mg_default_iface_vtable = MG_SOCKET_IFACE_VTABLE;
++const struct mg_iface_vtable mg_default_iface_vtable = MG_SOCKET_IFACE_VTABLE;
+ #endif
+
+ #endif /* MG_ENABLE_NET_IF_SOCKET */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/net_if_tun.c"
++#line 1 "mongoose/src/mg_net_if_socks.c"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+-#if MG_ENABLE_TUN
++#if MG_ENABLE_SOCKS
+
+-/* Amalgamated: #include "common/cs_dbg.h" */
+-/* Amalgamated: #include "common/cs_time.h" */
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/net_if_tun.h" */
+-/* Amalgamated: #include "mongoose/src/tun.h" */
+-/* Amalgamated: #include "mongoose/src/util.h" */
++struct socksdata {
++ char *proxy_addr; /* HOST:PORT of the socks5 proxy server */
++ struct mg_connection *s; /* Respective connection to the server */
++ struct mg_connection *c; /* Connection to the client */
++ struct mbuf tmp; /* Temporary buffer for sent data */
++};
+
+-#define MG_TCP_RECV_BUFFER_SIZE 1024
+-#define MG_UDP_RECV_BUFFER_SIZE 1500
++static void socks_if_disband(struct socksdata *d) {
++ LOG(LL_DEBUG, ("disbanding proxy %p %p", d->c, d->s));
++ if (d->c) d->c->flags |= MG_F_SEND_AND_CLOSE;
++ if (d->s) d->s->flags |= MG_F_SEND_AND_CLOSE;
++ d->c = d->s = NULL;
++}
++
++static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {
++ struct socksdata *d = (struct socksdata *) c->user_data;
++ if (ev == MG_EV_CONNECT) {
++ int res = *(int *) ev_data;
++ if (res == 0) {
++ /* Send handshake to the proxy server */
++ unsigned char buf[] = {MG_SOCKS_VERSION, 1, MG_SOCKS_HANDSHAKE_NOAUTH};
++ mg_send(d->s, buf, sizeof(buf));
++ LOG(LL_DEBUG, ("Sent handshake to %s", d->proxy_addr));
++ } else {
++ LOG(LL_ERROR, ("Cannot connect to %s: %d", d->proxy_addr, res));
++ d->c->flags |= MG_F_CLOSE_IMMEDIATELY;
++ }
++ } else if (ev == MG_EV_CLOSE) {
++ socks_if_disband(d);
++ } else if (ev == MG_EV_RECV) {
++ /* Handle handshake reply */
++ if (!(c->flags & MG_SOCKS_HANDSHAKE_DONE)) {
++ /* TODO(lsm): process IPv6 too */
++ unsigned char buf[10] = {MG_SOCKS_VERSION, MG_SOCKS_CMD_CONNECT, 0,
++ MG_SOCKS_ADDR_IPV4};
++ if (c->recv_mbuf.len < 2) return;
++ if ((unsigned char) c->recv_mbuf.buf[1] == MG_SOCKS_HANDSHAKE_FAILURE) {
++ LOG(LL_ERROR, ("Server kicked us out"));
++ socks_if_disband(d);
++ return;
++ }
++ mbuf_remove(&c->recv_mbuf, 2);
++ c->flags |= MG_SOCKS_HANDSHAKE_DONE;
+
+-void mg_tun_if_connect_tcp(struct mg_connection *nc,
+- const union socket_address *sa) {
+- (void) nc;
++ /* Send connect request */
++ memcpy(buf + 4, &d->c->sa.sin.sin_addr, 4);
++ memcpy(buf + 8, &d->c->sa.sin.sin_port, 2);
++ mg_send(c, buf, sizeof(buf));
++ }
++ /* Process connect request */
++ if ((c->flags & MG_SOCKS_HANDSHAKE_DONE) &&
++ !(c->flags & MG_SOCKS_CONNECT_DONE)) {
++ if (c->recv_mbuf.len < 10) return;
++ if (c->recv_mbuf.buf[1] != MG_SOCKS_SUCCESS) {
++ LOG(LL_ERROR, ("Socks connection error: %d", c->recv_mbuf.buf[1]));
++ socks_if_disband(d);
++ return;
++ }
++ mbuf_remove(&c->recv_mbuf, 10);
++ c->flags |= MG_SOCKS_CONNECT_DONE;
++ /* Connected. Move sent data from client, if any, to server */
++ if (d->s && d->c) {
++ mbuf_append(&d->s->send_mbuf, d->tmp.buf, d->tmp.len);
++ mbuf_free(&d->tmp);
++ }
++ }
++ /* All flags are set, we're in relay mode */
++ if ((c->flags & MG_SOCKS_CONNECT_DONE) && d->c && d->s) {
++ mbuf_append(&d->c->recv_mbuf, d->s->recv_mbuf.buf, d->s->recv_mbuf.len);
++ mbuf_remove(&d->s->recv_mbuf, d->s->recv_mbuf.len);
++ }
++ }
++}
++
++static void mg_socks_if_connect_tcp(struct mg_connection *c,
++ const union socket_address *sa) {
++ struct socksdata *d = (struct socksdata *) c->iface->data;
++ d->c = c;
++ d->s = mg_connect(c->mgr, d->proxy_addr, socks_if_handler);
++ d->s->user_data = d;
++ LOG(LL_DEBUG, ("%p %s", c, d->proxy_addr));
+ (void) sa;
+ }
+
+-void mg_tun_if_connect_udp(struct mg_connection *nc) {
+- (void) nc;
++static void mg_socks_if_connect_udp(struct mg_connection *c) {
++ (void) c;
+ }
+
+-int mg_tun_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
+- (void) nc;
++static int mg_socks_if_listen_tcp(struct mg_connection *c,
++ union socket_address *sa) {
++ (void) c;
+ (void) sa;
+ return 0;
+ }
+
+-int mg_tun_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
+- (void) nc;
++static int mg_socks_if_listen_udp(struct mg_connection *c,
++ union socket_address *sa) {
++ (void) c;
+ (void) sa;
+ return -1;
+ }
+
+-void mg_tun_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {
+- struct mg_tun_client *client = (struct mg_tun_client *) nc->iface->data;
+- uint32_t stream_id = (uint32_t)(uintptr_t) nc->mgr_data;
+- struct mg_str msg = {(char *) buf, len};
+-#if MG_ENABLE_HEXDUMP
+- char hex[512];
+- mg_hexdump(buf, len, hex, sizeof(hex));
+- LOG(LL_DEBUG, ("sending to stream %zu:\n%s", stream_id, hex));
+-#endif
+-
+- mg_tun_send_frame(client->disp, stream_id, MG_TUN_DATA_FRAME, 0, msg);
++static void mg_socks_if_tcp_send(struct mg_connection *c, const void *buf,
++ size_t len) {
++ struct socksdata *d = (struct socksdata *) c->iface->data;
++ LOG(LL_DEBUG, ("%p -> %p %d %d", c, buf, (int) len, (int) c->send_mbuf.len));
++ if (d && d->s && d->s->flags & MG_SOCKS_CONNECT_DONE) {
++ mbuf_append(&d->s->send_mbuf, d->tmp.buf, d->tmp.len);
++ mbuf_append(&d->s->send_mbuf, buf, len);
++ mbuf_free(&d->tmp);
++ } else {
++ mbuf_append(&d->tmp, buf, len);
++ }
+ }
+
+-void mg_tun_if_udp_send(struct mg_connection *nc, const void *buf, size_t len) {
+- (void) nc;
++static void mg_socks_if_udp_send(struct mg_connection *c, const void *buf,
++ size_t len) {
++ (void) c;
+ (void) buf;
+ (void) len;
+ }
+
+-void mg_tun_if_recved(struct mg_connection *nc, size_t len) {
+- (void) nc;
++static void mg_socks_if_recved(struct mg_connection *c, size_t len) {
++ (void) c;
+ (void) len;
+ }
+
+-int mg_tun_if_create_conn(struct mg_connection *nc) {
+- (void) nc;
++static int mg_socks_if_create_conn(struct mg_connection *c) {
++ (void) c;
+ return 1;
+ }
+
+-void mg_tun_if_destroy_conn(struct mg_connection *nc) {
+- struct mg_tun_client *client = (struct mg_tun_client *) nc->iface->data;
+-
+- if (nc->flags & MG_F_LISTENING) {
+- mg_tun_destroy_client(client);
+- } else if (client->disp) {
+- uint32_t stream_id = (uint32_t)(uintptr_t) nc->mgr_data;
+- struct mg_str msg = {NULL, 0};
+-
+- LOG(LL_DEBUG, ("closing %zu:", stream_id));
+- mg_tun_send_frame(client->disp, stream_id, MG_TUN_DATA_FRAME,
+- MG_TUN_F_END_STREAM, msg);
+- }
++static void mg_socks_if_destroy_conn(struct mg_connection *c) {
++ c->iface->vtable->free(c->iface);
++ MG_FREE(c->iface);
++ c->iface = NULL;
++ LOG(LL_DEBUG, ("%p", c));
+ }
+
+-/* Associate a socket to a connection. */
+-void mg_tun_if_sock_set(struct mg_connection *nc, sock_t sock) {
+- (void) nc;
++static void mg_socks_if_sock_set(struct mg_connection *c, sock_t sock) {
++ (void) c;
+ (void) sock;
+ }
+
+-void mg_tun_if_init(struct mg_iface *iface) {
++static void mg_socks_if_init(struct mg_iface *iface) {
+ (void) iface;
+ }
+
+-void mg_tun_if_free(struct mg_iface *iface) {
+- (void) iface;
++static void mg_socks_if_free(struct mg_iface *iface) {
++ struct socksdata *d = (struct socksdata *) iface->data;
++ LOG(LL_DEBUG, ("%p", iface));
++ if (d != NULL) {
++ socks_if_disband(d);
++ mbuf_free(&d->tmp);
++ MG_FREE(d->proxy_addr);
++ MG_FREE(d);
++ iface->data = NULL;
++ }
+ }
+
+-void mg_tun_if_add_conn(struct mg_connection *nc) {
+- nc->sock = INVALID_SOCKET;
++static void mg_socks_if_add_conn(struct mg_connection *c) {
++ c->sock = INVALID_SOCKET;
+ }
+
+-void mg_tun_if_remove_conn(struct mg_connection *nc) {
+- (void) nc;
++static void mg_socks_if_remove_conn(struct mg_connection *c) {
++ (void) c;
+ }
+
+-time_t mg_tun_if_poll(struct mg_iface *iface, int timeout_ms) {
++static time_t mg_socks_if_poll(struct mg_iface *iface, int timeout_ms) {
++ LOG(LL_DEBUG, ("%p", iface));
+ (void) iface;
+ (void) timeout_ms;
+ return (time_t) cs_time();
+ }
+
+-void mg_tun_if_get_conn_addr(struct mg_connection *nc, int remote,
+- union socket_address *sa) {
+- (void) nc;
++static void mg_socks_if_get_conn_addr(struct mg_connection *c, int remote,
++ union socket_address *sa) {
++ LOG(LL_DEBUG, ("%p", c));
++ (void) c;
+ (void) remote;
+ (void) sa;
+ }
+
+-struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client,
+- uint32_t stream_id) {
+- struct mg_connection *nc = NULL;
+-
+- for (nc = client->mgr->active_connections; nc != NULL; nc = nc->next) {
+- if (nc->iface != client->iface || (nc->flags & MG_F_LISTENING)) {
+- continue;
+- }
+- if (stream_id == (uint32_t)(uintptr_t) nc->mgr_data) {
+- return nc;
+- }
+- }
+-
+- if (stream_id > client->last_stream_id) {
+- /* create a new connection */
+- LOG(LL_DEBUG, ("new stream 0x%lx, accepting", stream_id));
+- nc = mg_if_accept_new_conn(client->listener);
+- nc->mgr_data = (void *) (uintptr_t) stream_id;
+- client->last_stream_id = stream_id;
+- } else {
+- LOG(LL_DEBUG, ("Ignoring stream 0x%lx (last_stream_id 0x%lx)", stream_id,
+- client->last_stream_id));
+- }
++const struct mg_iface_vtable mg_socks_iface_vtable = {
++ mg_socks_if_init, mg_socks_if_free,
++ mg_socks_if_add_conn, mg_socks_if_remove_conn,
++ mg_socks_if_poll, mg_socks_if_listen_tcp,
++ mg_socks_if_listen_udp, mg_socks_if_connect_tcp,
++ mg_socks_if_connect_udp, mg_socks_if_tcp_send,
++ mg_socks_if_udp_send, mg_socks_if_recved,
++ mg_socks_if_create_conn, mg_socks_if_destroy_conn,
++ mg_socks_if_sock_set, mg_socks_if_get_conn_addr,
++};
+
+- return nc;
++struct mg_iface *mg_socks_mk_iface(struct mg_mgr *mgr, const char *proxy_addr) {
++ struct mg_iface *iface = mg_if_create_iface(&mg_socks_iface_vtable, mgr);
++ iface->data = MG_CALLOC(1, sizeof(struct socksdata));
++ ((struct socksdata *) iface->data)->proxy_addr = strdup(proxy_addr);
++ return iface;
+ }
+
+-/* clang-format off */
+-#define MG_TUN_IFACE_VTABLE \
+- { \
+- mg_tun_if_init, \
+- mg_tun_if_free, \
+- mg_tun_if_add_conn, \
+- mg_tun_if_remove_conn, \
+- mg_tun_if_poll, \
+- mg_tun_if_listen_tcp, \
+- mg_tun_if_listen_udp, \
+- mg_tun_if_connect_tcp, \
+- mg_tun_if_connect_udp, \
+- mg_tun_if_tcp_send, \
+- mg_tun_if_udp_send, \
+- mg_tun_if_recved, \
+- mg_tun_if_create_conn, \
+- mg_tun_if_destroy_conn, \
+- mg_tun_if_sock_set, \
+- mg_tun_if_get_conn_addr, \
+- }
+-/* clang-format on */
+-
+-struct mg_iface_vtable mg_tun_iface_vtable = MG_TUN_IFACE_VTABLE;
+-
+-#endif /* MG_ENABLE_TUN */
++#endif
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/ssl_if_openssl.c"
++#line 1 "mongoose/src/mg_ssl_if_openssl.c"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+@@ -4037,6 +4236,8 @@ struct mg_iface_vtable mg_tun_iface_vtab
+ struct mg_ssl_if_ctx {
+ SSL *ssl;
+ SSL_CTX *ssl_ctx;
++ struct mbuf psk;
++ size_t identity_len;
+ };
+
+ void mg_ssl_if_init() {
+@@ -4060,7 +4261,10 @@ enum mg_ssl_if_result mg_ssl_if_conn_acc
+ static enum mg_ssl_if_result mg_use_cert(SSL_CTX *ctx, const char *cert,
+ const char *key, const char **err_msg);
+ static enum mg_ssl_if_result mg_use_ca_cert(SSL_CTX *ctx, const char *cert);
+-static enum mg_ssl_if_result mg_set_cipher_list(SSL_CTX *ctx);
++static enum mg_ssl_if_result mg_set_cipher_list(SSL_CTX *ctx, const char *cl);
++static enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,
++ const char *identity,
++ const char *key_str);
+
+ enum mg_ssl_if_result mg_ssl_if_conn_init(
+ struct mg_connection *nc, const struct mg_ssl_if_conn_params *params,
+@@ -4085,6 +4289,21 @@ enum mg_ssl_if_result mg_ssl_if_conn_ini
+ return MG_SSL_ERROR;
+ }
+
++#ifndef KR_VERSION
++ /* Disable deprecated protocols. */
++ SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2);
++ SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv3);
++ SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1);
++#ifdef MG_SSL_OPENSSL_NO_COMPRESSION
++ SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_COMPRESSION);
++#endif
++#ifdef MG_SSL_OPENSSL_CIPHER_SERVER_PREFERENCE
++ SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
++#endif
++#else
++/* Krypton only supports TLSv1.2 anyway. */
++#endif
++
+ if (params->cert != NULL &&
+ mg_use_cert(ctx->ssl_ctx, params->cert, params->key, err_msg) !=
+ MG_SSL_OK) {
+@@ -4105,7 +4324,17 @@ enum mg_ssl_if_result mg_ssl_if_conn_ini
+ #endif
+ }
+
+- mg_set_cipher_list(ctx->ssl_ctx);
++ if (mg_set_cipher_list(ctx->ssl_ctx, params->cipher_suites) != MG_SSL_OK) {
++ MG_SET_PTRPTR(err_msg, "Invalid cipher suite list");
++ return MG_SSL_ERROR;
++ }
++
++ mbuf_init(&ctx->psk, 0);
++ if (mg_ssl_if_ossl_set_psk(ctx, params->psk_identity, params->psk_key) !=
++ MG_SSL_OK) {
++ MG_SET_PTRPTR(err_msg, "Invalid PSK settings");
++ return MG_SSL_ERROR;
++ }
+
+ if (!(nc->flags & MG_F_LISTENING) &&
+ (ctx->ssl = SSL_new(ctx->ssl_ctx)) == NULL) {
+@@ -4159,12 +4388,19 @@ int mg_ssl_if_write(struct mg_connection
+ return n;
+ }
+
++void mg_ssl_if_conn_close_notify(struct mg_connection *nc) {
++ struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
++ if (ctx == NULL) return;
++ SSL_shutdown(ctx->ssl);
++}
++
+ void mg_ssl_if_conn_free(struct mg_connection *nc) {
+ struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
+ if (ctx == NULL) return;
+ nc->ssl_if_data = NULL;
+ if (ctx->ssl != NULL) SSL_free(ctx->ssl);
+ if (ctx->ssl_ctx != NULL && nc->listener == NULL) SSL_CTX_free(ctx->ssl_ctx);
++ mbuf_free(&ctx->psk);
+ memset(ctx, 0, sizeof(*ctx));
+ MG_FREE(ctx);
+ }
+@@ -4287,11 +4523,83 @@ static enum mg_ssl_if_result mg_use_cert
+ return MG_SSL_OK;
+ }
+
+-static enum mg_ssl_if_result mg_set_cipher_list(SSL_CTX *ctx) {
+- return (SSL_CTX_set_cipher_list(ctx, mg_s_cipher_list) == 1 ? MG_SSL_OK
+- : MG_SSL_ERROR);
++static enum mg_ssl_if_result mg_set_cipher_list(SSL_CTX *ctx, const char *cl) {
++ return (SSL_CTX_set_cipher_list(ctx, cl ? cl : mg_s_cipher_list) == 1
++ ? MG_SSL_OK
++ : MG_SSL_ERROR);
+ }
+
++#ifndef KR_VERSION
++static unsigned int mg_ssl_if_ossl_psk_cb(SSL *ssl, const char *hint,
++ char *identity,
++ unsigned int max_identity_len,
++ unsigned char *psk,
++ unsigned int max_psk_len) {
++ struct mg_ssl_if_ctx *ctx =
++ (struct mg_ssl_if_ctx *) SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl));
++ size_t key_len = ctx->psk.len - ctx->identity_len - 1;
++ DBG(("hint: '%s'", (hint ? hint : "")));
++ if (ctx->identity_len + 1 > max_identity_len) {
++ DBG(("identity too long"));
++ return 0;
++ }
++ if (key_len > max_psk_len) {
++ DBG(("key too long"));
++ return 0;
++ }
++ memcpy(identity, ctx->psk.buf, ctx->identity_len + 1);
++ memcpy(psk, ctx->psk.buf + ctx->identity_len + 1, key_len);
++ (void) ssl;
++ return key_len;
++}
++
++static enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,
++ const char *identity,
++ const char *key_str) {
++ unsigned char key[32];
++ size_t key_len;
++ size_t i = 0;
++ if (identity == NULL && key_str == NULL) return MG_SSL_OK;
++ if (identity == NULL || key_str == NULL) return MG_SSL_ERROR;
++ key_len = strlen(key_str);
++ if (key_len != 32 && key_len != 64) return MG_SSL_ERROR;
++ memset(key, 0, sizeof(key));
++ key_len = 0;
++ for (i = 0; key_str[i] != '\0'; i++) {
++ unsigned char c;
++ char hc = tolower((int) key_str[i]);
++ if (hc >= '0' && hc <= '9') {
++ c = hc - '0';
++ } else if (hc >= 'a' && hc <= 'f') {
++ c = hc - 'a' + 0xa;
++ } else {
++ return MG_SSL_ERROR;
++ }
++ key_len = i / 2;
++ key[key_len] <<= 4;
++ key[key_len] |= c;
++ }
++ key_len++;
++ DBG(("identity = '%s', key = (%u)", identity, (unsigned int) key_len));
++ ctx->identity_len = strlen(identity);
++ mbuf_append(&ctx->psk, identity, ctx->identity_len + 1);
++ mbuf_append(&ctx->psk, key, key_len);
++ SSL_CTX_set_psk_client_callback(ctx->ssl_ctx, mg_ssl_if_ossl_psk_cb);
++ SSL_CTX_set_app_data(ctx->ssl_ctx, ctx);
++ return MG_SSL_OK;
++}
++#else
++static enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,
++ const char *identity,
++ const char *key_str) {
++ (void) ctx;
++ (void) identity;
++ (void) key_str;
++ /* Krypton does not support PSK. */
++ return MG_SSL_ERROR;
++}
++#endif /* defined(KR_VERSION) */
++
+ const char *mg_set_ssl(struct mg_connection *nc, const char *cert,
+ const char *ca_cert) {
+ const char *err_msg = NULL;
+@@ -4307,7 +4615,7 @@ const char *mg_set_ssl(struct mg_connect
+
+ #endif /* MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_OPENSSL */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/ssl_if_mbedtls.c"
++#line 1 "mongoose/src/mg_ssl_if_mbedtls.c"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+@@ -4348,6 +4656,7 @@ struct mg_ssl_if_ctx {
+ mbedtls_x509_crt *cert;
+ mbedtls_pk_context *key;
+ mbedtls_x509_crt *ca_cert;
++ struct mbuf cipher_suites;
+ };
+
+ /* Must be provided by the platform. ctx is struct mg_connection. */
+@@ -4363,7 +4672,7 @@ enum mg_ssl_if_result mg_ssl_if_conn_acc
+ struct mg_ssl_if_ctx *lc_ctx = (struct mg_ssl_if_ctx *) lc->ssl_if_data;
+ nc->ssl_if_data = ctx;
+ if (ctx == NULL || lc_ctx == NULL) return MG_SSL_ERROR;
+- ctx->ssl = MG_CALLOC(1, sizeof(*ctx->ssl));
++ ctx->ssl = (mbedtls_ssl_context *) MG_CALLOC(1, sizeof(*ctx->ssl));
+ if (mbedtls_ssl_setup(ctx->ssl, lc_ctx->conf) != 0) {
+ return MG_SSL_ERROR;
+ }
+@@ -4377,6 +4686,9 @@ static enum mg_ssl_if_result mg_use_ca_c
+ const char *cert);
+ static enum mg_ssl_if_result mg_set_cipher_list(struct mg_ssl_if_ctx *ctx,
+ const char *ciphers);
++static enum mg_ssl_if_result mg_ssl_if_mbed_set_psk(struct mg_ssl_if_ctx *ctx,
++ const char *identity,
++ const char *key);
+
+ enum mg_ssl_if_result mg_ssl_if_conn_init(
+ struct mg_connection *nc, const struct mg_ssl_if_conn_params *params,
+@@ -4392,7 +4704,8 @@ enum mg_ssl_if_result mg_ssl_if_conn_ini
+ return MG_SSL_ERROR;
+ }
+ nc->ssl_if_data = ctx;
+- ctx->conf = MG_CALLOC(1, sizeof(*ctx->conf));
++ ctx->conf = (mbedtls_ssl_config *) MG_CALLOC(1, sizeof(*ctx->conf));
++ mbuf_init(&ctx->cipher_suites, 0);
+ mbedtls_ssl_config_init(ctx->conf);
+ mbedtls_ssl_conf_dbg(ctx->conf, mg_ssl_mbed_log, nc);
+ if (mbedtls_ssl_config_defaults(
+@@ -4402,6 +4715,7 @@ enum mg_ssl_if_result mg_ssl_if_conn_ini
+ MG_SET_PTRPTR(err_msg, "Failed to init SSL config");
+ return MG_SSL_ERROR;
+ }
++
+ /* TLS 1.2 and up */
+ mbedtls_ssl_conf_min_version(ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
+ MBEDTLS_SSL_MINOR_VERSION_3);
+@@ -4418,10 +4732,19 @@ enum mg_ssl_if_result mg_ssl_if_conn_ini
+ return MG_SSL_ERROR;
+ }
+
+- mg_set_cipher_list(ctx, NULL);
++ if (mg_set_cipher_list(ctx, params->cipher_suites) != MG_SSL_OK) {
++ MG_SET_PTRPTR(err_msg, "Invalid cipher suite list");
++ return MG_SSL_ERROR;
++ }
++
++ if (mg_ssl_if_mbed_set_psk(ctx, params->psk_identity, params->psk_key) !=
++ MG_SSL_OK) {
++ MG_SET_PTRPTR(err_msg, "Invalid PSK settings");
++ return MG_SSL_ERROR;
++ }
+
+ if (!(nc->flags & MG_F_LISTENING)) {
+- ctx->ssl = MG_CALLOC(1, sizeof(*ctx->ssl));
++ ctx->ssl = (mbedtls_ssl_context *) MG_CALLOC(1, sizeof(*ctx->ssl));
+ mbedtls_ssl_init(ctx->ssl);
+ if (mbedtls_ssl_setup(ctx->ssl, ctx->conf) != 0) {
+ MG_SET_PTRPTR(err_msg, "Failed to create SSL session");
+@@ -4433,6 +4756,24 @@ enum mg_ssl_if_result mg_ssl_if_conn_ini
+ }
+ }
+
++#ifdef MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN
++ if (mbedtls_ssl_conf_max_frag_len(ctx->conf,
++#if MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN == 512
++ MBEDTLS_SSL_MAX_FRAG_LEN_512
++#elif MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN == 1024
++ MBEDTLS_SSL_MAX_FRAG_LEN_1024
++#elif MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN == 2048
++ MBEDTLS_SSL_MAX_FRAG_LEN_2048
++#elif MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN == 4096
++ MBEDTLS_SSL_MAX_FRAG_LEN_4096
++#else
++#error Invalid MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN
++#endif
++ ) != 0) {
++ return MG_SSL_ERROR;
++ }
++#endif
++
+ nc->flags |= MG_F_SSL;
+
+ return MG_SSL_OK;
+@@ -4445,7 +4786,7 @@ int ssl_socket_recv(void *ctx, unsigned
+ static int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len) {
+ struct mg_connection *nc = (struct mg_connection *) ctx;
+ int n = (int) MG_SEND_FUNC(nc->sock, buf, len, 0);
+- DBG(("%p %d -> %d", nc, (int) len, n));
++ LOG(LL_DEBUG, ("%p %d -> %d", nc, (int) len, n));
+ if (n >= 0) return n;
+ n = mg_get_errno();
+ return ((n == EAGAIN || n == EINPROGRESS) ? MBEDTLS_ERR_SSL_WANT_WRITE : -1);
+@@ -4454,7 +4795,7 @@ static int ssl_socket_send(void *ctx, co
+ static int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len) {
+ struct mg_connection *nc = (struct mg_connection *) ctx;
+ int n = (int) MG_RECV_FUNC(nc->sock, buf, len, 0);
+- DBG(("%p %d <- %d", nc, (int) len, n));
++ LOG(LL_DEBUG, ("%p %d <- %d", nc, (int) len, n));
+ if (n >= 0) return n;
+ n = mg_get_errno();
+ return ((n == EAGAIN || n == EINPROGRESS) ? MBEDTLS_ERR_SSL_WANT_READ : -1);
+@@ -4485,6 +4826,12 @@ static void mg_ssl_if_mbed_free_certs_an
+ }
+ if (ctx->ca_cert != NULL) {
+ mbedtls_ssl_conf_ca_chain(ctx->conf, NULL, NULL);
++#ifdef MBEDTLS_X509_CA_CHAIN_ON_DISK
++ if (ctx->ca_cert->ca_chain_file != NULL) {
++ MG_FREE((void *) ctx->ca_cert->ca_chain_file);
++ ctx->ca_cert->ca_chain_file = NULL;
++ }
++#endif
+ mbedtls_x509_crt_free(ctx->ca_cert);
+ MG_FREE(ctx->ca_cert);
+ ctx->ca_cert = NULL;
+@@ -4524,7 +4871,7 @@ enum mg_ssl_if_result mg_ssl_if_handshak
+
+ int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t buf_size) {
+ struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
+- int n = mbedtls_ssl_read(ctx->ssl, buf, buf_size);
++ int n = mbedtls_ssl_read(ctx->ssl, (unsigned char *) buf, buf_size);
+ DBG(("%p %d -> %d", nc, (int) buf_size, n));
+ if (n < 0) return mg_ssl_if_mbed_err(nc, n);
+ if (n == 0) nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+@@ -4533,12 +4880,18 @@ int mg_ssl_if_read(struct mg_connection
+
+ int mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len) {
+ struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
+- int n = mbedtls_ssl_write(ctx->ssl, data, len);
++ int n = mbedtls_ssl_write(ctx->ssl, (const unsigned char *) data, len);
+ DBG(("%p %d -> %d", nc, (int) len, n));
+ if (n < 0) return mg_ssl_if_mbed_err(nc, n);
+ return n;
+ }
+
++void mg_ssl_if_conn_close_notify(struct mg_connection *nc) {
++ struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
++ if (ctx == NULL) return;
++ mbedtls_ssl_close_notify(ctx->ssl);
++}
++
+ void mg_ssl_if_conn_free(struct mg_connection *nc) {
+ struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
+ if (ctx == NULL) return;
+@@ -4552,6 +4905,7 @@ void mg_ssl_if_conn_free(struct mg_conne
+ mbedtls_ssl_config_free(ctx->conf);
+ MG_FREE(ctx->conf);
+ }
++ mbuf_free(&ctx->cipher_suites);
+ memset(ctx, 0, sizeof(*ctx));
+ MG_FREE(ctx);
+ }
+@@ -4559,13 +4913,21 @@ void mg_ssl_if_conn_free(struct mg_conne
+ static enum mg_ssl_if_result mg_use_ca_cert(struct mg_ssl_if_ctx *ctx,
+ const char *ca_cert) {
+ if (ca_cert == NULL || strcmp(ca_cert, "*") == 0) {
++ mbedtls_ssl_conf_authmode(ctx->conf, MBEDTLS_SSL_VERIFY_NONE);
+ return MG_SSL_OK;
+ }
+- ctx->ca_cert = MG_CALLOC(1, sizeof(*ctx->ca_cert));
++ ctx->ca_cert = (mbedtls_x509_crt *) MG_CALLOC(1, sizeof(*ctx->ca_cert));
+ mbedtls_x509_crt_init(ctx->ca_cert);
++#ifdef MBEDTLS_X509_CA_CHAIN_ON_DISK
++ ca_cert = strdup(ca_cert);
++ if (mbedtls_x509_crt_set_ca_chain_file(ctx->ca_cert, ca_cert) != 0) {
++ return MG_SSL_ERROR;
++ }
++#else
+ if (mbedtls_x509_crt_parse_file(ctx->ca_cert, ca_cert) != 0) {
+ return MG_SSL_ERROR;
+ }
++#endif
+ mbedtls_ssl_conf_ca_chain(ctx->conf, ctx->ca_cert, NULL);
+ mbedtls_ssl_conf_authmode(ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
+ return MG_SSL_OK;
+@@ -4578,9 +4940,9 @@ static enum mg_ssl_if_result mg_use_cert
+ if (cert == NULL || cert[0] == '\0' || key == NULL || key[0] == '\0') {
+ return MG_SSL_OK;
+ }
+- ctx->cert = MG_CALLOC(1, sizeof(*ctx->cert));
++ ctx->cert = (mbedtls_x509_crt *) MG_CALLOC(1, sizeof(*ctx->cert));
+ mbedtls_x509_crt_init(ctx->cert);
+- ctx->key = MG_CALLOC(1, sizeof(*ctx->key));
++ ctx->key = (mbedtls_pk_context *) MG_CALLOC(1, sizeof(*ctx->key));
+ mbedtls_pk_init(ctx->key);
+ if (mbedtls_x509_crt_parse_file(ctx->cert, cert) != 0) {
+ MG_SET_PTRPTR(err_msg, "Invalid SSL cert");
+@@ -4598,8 +4960,11 @@ static enum mg_ssl_if_result mg_use_cert
+ }
+
+ static const int mg_s_cipher_list[] = {
++#if CS_PLATFORM != CS_P_ESP8266
+ MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
++ MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+ MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
++ MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+ MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+ MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
+ MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
+@@ -4610,7 +4975,29 @@ static const int mg_s_cipher_list[] = {
+ MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
+ MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256,
+ MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256,
+- MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA, 0};
++ MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA,
++#else
++ /*
++ * ECDHE is way too slow on ESP8266 w/o cryptochip, this sometimes results
++ * in WiFi STA deauths. Use weaker but faster cipher suites. Sad but true.
++ * Disable DHE completely because it's just hopelessly slow.
++ */
++ MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256,
++ MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256,
++ MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
++ MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
++ MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
++ MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
++ MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
++ MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
++ MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
++ MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
++ MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
++ MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
++ MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA,
++#endif /* CS_PLATFORM != CS_P_ESP8266 */
++ 0,
++};
+
+ /*
+ * Ciphers can be specified as a colon-separated list of cipher suite names.
+@@ -4621,27 +5008,70 @@ static const int mg_s_cipher_list[] = {
+ static enum mg_ssl_if_result mg_set_cipher_list(struct mg_ssl_if_ctx *ctx,
+ const char *ciphers) {
+ if (ciphers != NULL) {
+- int ids[50], n = 0, l, id;
+- const char *s = ciphers;
+- char *e, tmp[50];
+- while (s != NULL && n < (int) (sizeof(ids) / sizeof(ids[0])) - 1) {
++ int l, id;
++ const char *s = ciphers, *e;
++ char tmp[50];
++ while (s != NULL) {
+ e = strchr(s, ':');
+ l = (e != NULL ? (e - s) : (int) strlen(s));
+ strncpy(tmp, s, l);
++ tmp[l] = '\0';
+ id = mbedtls_ssl_get_ciphersuite_id(tmp);
+- DBG(("%s -> %d", tmp, id));
+- if (id != 0) ids[n++] = id;
++ DBG(("%s -> %04x", tmp, id));
++ if (id != 0) {
++ mbuf_append(&ctx->cipher_suites, &id, sizeof(id));
++ }
+ s = (e != NULL ? e + 1 : NULL);
+ }
+- if (n == 0) return MG_SSL_ERROR;
+- ids[n] = 0;
+- mbedtls_ssl_conf_ciphersuites(ctx->conf, ids);
++ if (ctx->cipher_suites.len == 0) return MG_SSL_ERROR;
++ id = 0;
++ mbuf_append(&ctx->cipher_suites, &id, sizeof(id));
++ mbuf_trim(&ctx->cipher_suites);
++ mbedtls_ssl_conf_ciphersuites(ctx->conf,
++ (const int *) ctx->cipher_suites.buf);
+ } else {
+ mbedtls_ssl_conf_ciphersuites(ctx->conf, mg_s_cipher_list);
+ }
+ return MG_SSL_OK;
+ }
+
++static enum mg_ssl_if_result mg_ssl_if_mbed_set_psk(struct mg_ssl_if_ctx *ctx,
++ const char *identity,
++ const char *key_str) {
++ unsigned char key[32];
++ size_t key_len;
++ if (identity == NULL && key_str == NULL) return MG_SSL_OK;
++ if (identity == NULL || key_str == NULL) return MG_SSL_ERROR;
++ key_len = strlen(key_str);
++ if (key_len != 32 && key_len != 64) return MG_SSL_ERROR;
++ size_t i = 0;
++ memset(key, 0, sizeof(key));
++ key_len = 0;
++ for (i = 0; key_str[i] != '\0'; i++) {
++ unsigned char c;
++ char hc = tolower((int) key_str[i]);
++ if (hc >= '0' && hc <= '9') {
++ c = hc - '0';
++ } else if (hc >= 'a' && hc <= 'f') {
++ c = hc - 'a' + 0xa;
++ } else {
++ return MG_SSL_ERROR;
++ }
++ key_len = i / 2;
++ key[key_len] <<= 4;
++ key[key_len] |= c;
++ }
++ key_len++;
++ DBG(("identity = '%s', key = (%u)", identity, (unsigned int) key_len));
++ /* mbedTLS makes copies of psk and identity. */
++ if (mbedtls_ssl_conf_psk(ctx->conf, (const unsigned char *) key, key_len,
++ (const unsigned char *) identity,
++ strlen(identity)) != 0) {
++ return MG_SSL_ERROR;
++ }
++ return MG_SSL_OK;
++}
++
+ const char *mg_set_ssl(struct mg_connection *nc, const char *cert,
+ const char *ca_cert) {
+ const char *err_msg = NULL;
+@@ -4666,167 +5096,37 @@ int mg_ssl_if_mbed_random(void *ctx, uns
+
+ #endif /* MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_MBEDTLS */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/multithreading.c"
+-#endif
+-/*
+- * Copyright (c) 2014 Cesanta Software Limited
+- * All rights reserved
+- */
+-
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/util.h" */
+-
+-#if MG_ENABLE_THREADS
+-
+-static void multithreaded_ev_handler(struct mg_connection *c, int ev, void *p);
+-
+-/*
+- * This thread function executes user event handler.
+- * It runs an event manager that has only one connection, until that
+- * connection is alive.
+- */
+-static void *per_connection_thread_function(void *param) {
+- struct mg_connection *c = (struct mg_connection *) param;
+- struct mg_mgr m;
+- /* mgr_data can be used subsequently, store its value */
+- int poll_timeout = (intptr_t) c->mgr_data;
+-
+- mg_mgr_init(&m, NULL);
+- mg_add_conn(&m, c);
+- mg_call(c, NULL, MG_EV_ACCEPT, &c->sa);
+-
+- while (m.active_connections != NULL) {
+- mg_mgr_poll(&m, poll_timeout ? poll_timeout : 1000);
+- }
+- mg_mgr_free(&m);
+-
+- return param;
+-}
+-
+-static void link_conns(struct mg_connection *c1, struct mg_connection *c2) {
+- c1->priv_2 = c2;
+- c2->priv_2 = c1;
+-}
+-
+-static void unlink_conns(struct mg_connection *c) {
+- struct mg_connection *peer = (struct mg_connection *) c->priv_2;
+- if (peer != NULL) {
+- peer->flags |= MG_F_SEND_AND_CLOSE;
+- peer->priv_2 = NULL;
+- }
+- c->priv_2 = NULL;
+-}
+-
+-static void forwarder_ev_handler(struct mg_connection *c, int ev, void *p) {
+- (void) p;
+- if (ev == MG_EV_RECV && c->priv_2) {
+- mg_forward(c, (struct mg_connection *) c->priv_2);
+- } else if (ev == MG_EV_CLOSE) {
+- unlink_conns(c);
+- }
+-}
+-
+-static void spawn_handling_thread(struct mg_connection *nc) {
+- struct mg_mgr dummy;
+- sock_t sp[2];
+- struct mg_connection *c[2];
+- int poll_timeout;
+- /*
+- * Create a socket pair, and wrap each socket into the connection with
+- * dummy event manager.
+- * c[0] stays in this thread, c[1] goes to another thread.
+- */
+- mg_mgr_init(&dummy, NULL);
+- mg_socketpair(sp, SOCK_STREAM);
+-
+- c[0] = mg_add_sock(&dummy, sp[0], forwarder_ev_handler);
+- c[1] = mg_add_sock(&dummy, sp[1], nc->listener->priv_1.f);
+-
+- /* link_conns replaces priv_2, storing its value */
+- poll_timeout = (intptr_t) nc->priv_2;
+-
+- /* Interlink client connection with c[0] */
+- link_conns(c[0], nc);
+-
+- /*
+- * Switch c[0] manager from the dummy one to the real one. c[1] manager
+- * will be set in another thread, allocated on stack of that thread.
+- */
+- mg_add_conn(nc->mgr, c[0]);
+-
+- /*
+- * Dress c[1] as nc.
+- * TODO(lsm): code in accept_conn() looks similar. Refactor.
+- */
+- c[1]->listener = nc->listener;
+- c[1]->proto_handler = nc->proto_handler;
+- c[1]->user_data = nc->user_data;
+- c[1]->sa = nc->sa;
+- c[1]->flags = nc->flags;
+-
+- /* priv_2 is used, so, put timeout to mgr_data */
+- c[1]->mgr_data = (void *) (intptr_t) poll_timeout;
+-
+- mg_start_thread(per_connection_thread_function, c[1]);
+-}
+-
+-static void multithreaded_ev_handler(struct mg_connection *c, int ev, void *p) {
+- (void) p;
+- if (ev == MG_EV_ACCEPT) {
+- spawn_handling_thread(c);
+- c->handler = forwarder_ev_handler;
+- }
+-}
+-
+-void mg_enable_multithreading_opt(struct mg_connection *nc,
+- struct mg_multithreading_opts opts) {
+- /* Wrap user event handler into our multithreaded_ev_handler */
+- nc->priv_1.f = nc->handler;
+- /*
+- * We put timeout to `priv_2` member of the main
+- * (listening) connection, mt is not enabled yet,
+- * and this member is not used
+- */
+- nc->priv_2 = (void *) (intptr_t) opts.poll_timeout;
+- nc->handler = multithreaded_ev_handler;
+-}
+-
+-void mg_enable_multithreading(struct mg_connection *nc) {
+- struct mg_multithreading_opts opts;
+- memset(&opts, 0, sizeof(opts));
+- mg_enable_multithreading_opt(nc, opts);
+-}
+-
+-#endif
+-#ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/uri.c"
++#line 1 "mongoose/src/mg_uri.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/uri.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_uri.h" */
+
+ /*
+- * scan string until `sep`, keeping track of component boundaries in `res`.
++ * scan string until encountering one of `seps`, keeping track of component
++ * boundaries in `res`.
+ *
+ * `p` will point to the char after the separator or it will be `end`.
+ */
+-static void parse_uri_component(const char **p, const char *end, char sep,
+- struct mg_str *res) {
++static void parse_uri_component(const char **p, const char *end,
++ const char *seps, struct mg_str *res) {
++ const char *q;
+ res->p = *p;
+ for (; *p < end; (*p)++) {
+- if (**p == sep) {
+- break;
++ for (q = seps; *q != '\0'; q++) {
++ if (**p == *q) break;
+ }
++ if (*q != '\0') break;
+ }
+ res->len = (*p) - res->p;
+ if (*p < end) (*p)++;
+ }
+
+-int mg_parse_uri(struct mg_str uri, struct mg_str *scheme,
++int mg_parse_uri(const struct mg_str uri, struct mg_str *scheme,
+ struct mg_str *user_info, struct mg_str *host,
+ unsigned int *port, struct mg_str *path, struct mg_str *query,
+ struct mg_str *fragment) {
+@@ -4850,8 +5150,13 @@ int mg_parse_uri(struct mg_str uri, stru
+ * expecting on of:
+ * - `scheme://xxxx`
+ * - `xxxx:port`
++ * - `[a:b:c]:port`
+ * - `xxxx/path`
+ */
++ if (*p == '[') {
++ state = P_HOST;
++ break;
++ }
+ for (; p < end; p++) {
+ if (*p == ':') {
+ state = P_SCHEME_OR_PORT;
+@@ -4871,7 +5176,7 @@ int mg_parse_uri(struct mg_str uri, stru
+ rscheme.p = uri.p;
+ rscheme.len = p - uri.p;
+ state = P_USER_INFO;
+- p += 2; /* point to last separator char */
++ p += 3;
+ } else {
+ rhost.p = uri.p;
+ rhost.len = p - uri.p;
+@@ -4879,27 +5184,35 @@ int mg_parse_uri(struct mg_str uri, stru
+ }
+ break;
+ case P_USER_INFO:
+- p++;
+ ruser_info.p = p;
+ for (; p < end; p++) {
+- if (*p == '@') {
+- state = P_HOST;
+- break;
+- } else if (*p == '/') {
++ if (*p == '@' || *p == '[' || *p == '/') {
+ break;
+ }
+ }
+- if (p == end || *p == '/') {
++ if (p == end || *p == '/' || *p == '[') {
+ /* backtrack and parse as host */
+- state = P_HOST;
+ p = ruser_info.p;
+ }
+ ruser_info.len = p - ruser_info.p;
++ state = P_HOST;
+ break;
+ case P_HOST:
+ if (*p == '@') p++;
+ rhost.p = p;
+- for (; p < end; p++) {
++ if (*p == '[') {
++ int found = 0;
++ for (; !found && p < end; p++) {
++ found = (*p == ']');
++ }
++ if (!found) return -1;
++ } else {
++ for (; p < end; p++) {
++ if (*p == ':' || *p == '/') break;
++ }
++ }
++ rhost.len = p - rhost.p;
++ if (p < end) {
+ if (*p == ':') {
+ state = P_PORT;
+ break;
+@@ -4908,7 +5221,6 @@ int mg_parse_uri(struct mg_str uri, stru
+ break;
+ }
+ }
+- rhost.len = p - rhost.p;
+ break;
+ case P_PORT:
+ p++;
+@@ -4923,9 +5235,11 @@ int mg_parse_uri(struct mg_str uri, stru
+ break;
+ case P_REST:
+ /* `p` points to separator. `path` includes the separator */
+- parse_uri_component(&p, end, '?', &rpath);
+- parse_uri_component(&p, end, '#', &rquery);
+- parse_uri_component(&p, end, '\0', &rfragment);
++ parse_uri_component(&p, end, "?#", &rpath);
++ if (p < end && *(p - 1) == '?') {
++ parse_uri_component(&p, end, "#", &rquery);
++ }
++ parse_uri_component(&p, end, "", &rfragment);
+ break;
+ }
+ }
+@@ -4956,7 +5270,7 @@ int mg_normalize_uri_path(const struct m
+ while (s < se) {
+ const char *next = s;
+ struct mg_str component;
+- parse_uri_component(&next, se, '/', &component);
++ parse_uri_component(&next, se, "/", &component);
+ if (mg_vcmp(&component, ".") == 0) {
+ /* Yum. */
+ } else if (mg_vcmp(&component, "..") == 0) {
+@@ -4975,8 +5289,78 @@ int mg_normalize_uri_path(const struct m
+ out->len = d - cp;
+ return 1;
+ }
++
++int mg_assemble_uri(const struct mg_str *scheme, const struct mg_str *user_info,
++ const struct mg_str *host, unsigned int port,
++ const struct mg_str *path, const struct mg_str *query,
++ const struct mg_str *fragment, int normalize_path,
++ struct mg_str *uri) {
++ int result = -1;
++ struct mbuf out;
++ mbuf_init(&out, 0);
++
++ if (scheme != NULL && scheme->len > 0) {
++ mbuf_append(&out, scheme->p, scheme->len);
++ mbuf_append(&out, "://", 3);
++ }
++
++ if (user_info != NULL && user_info->len > 0) {
++ mbuf_append(&out, user_info->p, user_info->len);
++ mbuf_append(&out, "@", 1);
++ }
++
++ if (host != NULL && host->len > 0) {
++ mbuf_append(&out, host->p, host->len);
++ }
++
++ if (port != 0) {
++ char port_str[20];
++ int port_str_len = sprintf(port_str, ":%u", port);
++ mbuf_append(&out, port_str, port_str_len);
++ }
++
++ if (path != NULL && path->len > 0) {
++ if (normalize_path) {
++ struct mg_str npath = mg_strdup(*path);
++ if (npath.len != path->len) goto out;
++ if (!mg_normalize_uri_path(path, &npath)) {
++ free((void *) npath.p);
++ goto out;
++ }
++ mbuf_append(&out, npath.p, npath.len);
++ free((void *) npath.p);
++ } else {
++ mbuf_append(&out, path->p, path->len);
++ }
++ } else if (normalize_path) {
++ mbuf_append(&out, "/", 1);
++ }
++
++ if (query != NULL && query->len > 0) {
++ mbuf_append(&out, "?", 1);
++ mbuf_append(&out, query->p, query->len);
++ }
++
++ if (fragment != NULL && fragment->len > 0) {
++ mbuf_append(&out, "#", 1);
++ mbuf_append(&out, fragment->p, fragment->len);
++ }
++
++ result = 0;
++
++out:
++ if (result == 0) {
++ uri->p = out.buf;
++ uri->len = out.len;
++ } else {
++ mbuf_free(&out);
++ uri->p = NULL;
++ uri->len = 0;
++ }
++ return result;
++}
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/http.c"
++#line 1 "mongoose/src/mg_http.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -4985,10 +5369,89 @@ int mg_normalize_uri_path(const struct m
+
+ #if MG_ENABLE_HTTP
+
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/util.h" */
+-/* Amalgamated: #include "common/sha1.h" */
+-/* Amalgamated: #include "common/md5.h" */
++/* Amalgamated: #include "common/cs_md5.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_util.h" */
++
++/* altbuf {{{ */
++
++/*
++ * Alternate buffer: fills the client-provided buffer with data; and if it's
++ * not large enough, allocates another buffer (via mbuf), similar to asprintf.
++ */
++struct altbuf {
++ struct mbuf m;
++ char *user_buf;
++ size_t len;
++ size_t user_buf_size;
++};
++
++/*
++ * Initializes altbuf; `buf`, `buf_size` is the client-provided buffer.
++ */
++MG_INTERNAL void altbuf_init(struct altbuf *ab, char *buf, size_t buf_size) {
++ mbuf_init(&ab->m, 0);
++ ab->user_buf = buf;
++ ab->user_buf_size = buf_size;
++ ab->len = 0;
++}
++
++/*
++ * Appends a single char to the altbuf.
++ */
++MG_INTERNAL void altbuf_append(struct altbuf *ab, char c) {
++ if (ab->len < ab->user_buf_size) {
++ /* The data fits into the original buffer */
++ ab->user_buf[ab->len++] = c;
++ } else {
++ /* The data can't fit into the original buffer, so write it to mbuf. */
++
++ /*
++ * First of all, see if that's the first byte which overflows the original
++ * buffer: if so, copy the existing data from there to a newly allocated
++ * mbuf.
++ */
++ if (ab->len > 0 && ab->m.len == 0) {
++ mbuf_append(&ab->m, ab->user_buf, ab->len);
++ }
++
++ mbuf_append(&ab->m, &c, 1);
++ ab->len = ab->m.len;
++ }
++}
++
++/*
++ * Resets any data previously appended to altbuf.
++ */
++MG_INTERNAL void altbuf_reset(struct altbuf *ab) {
++ mbuf_free(&ab->m);
++ ab->len = 0;
++}
++
++/*
++ * Returns whether the additional buffer was allocated (and thus the data
++ * is in the mbuf, not the client-provided buffer)
++ */
++MG_INTERNAL int altbuf_reallocated(struct altbuf *ab) {
++ return ab->len > ab->user_buf_size;
++}
++
++/*
++ * Returns the actual buffer with data, either the client-provided or a newly
++ * allocated one. If `trim` is non-zero, mbuf-backed buffer is trimmed first.
++ */
++MG_INTERNAL char *altbuf_get_buf(struct altbuf *ab, int trim) {
++ if (altbuf_reallocated(ab)) {
++ if (trim) {
++ mbuf_trim(&ab->m);
++ }
++ return ab->m.buf;
++ } else {
++ return ab->user_buf;
++ }
++}
++
++/* }}} */
+
+ static const char *mg_version_header = "Mongoose/" MG_VERSION;
+
+@@ -5014,9 +5477,14 @@ struct mg_http_proto_data_chuncked {
+
+ struct mg_http_endpoint {
+ struct mg_http_endpoint *next;
+- const char *name;
+- size_t name_len;
++ struct mg_str uri_pattern; /* owned */
++ char *auth_domain; /* owned */
++ char *auth_file; /* owned */
++
+ mg_event_handler_t handler;
++#if MG_ENABLE_CALLBACK_USERDATA
++ void *user_data;
++#endif
+ };
+
+ enum mg_http_multipart_stream_state {
+@@ -5044,6 +5512,16 @@ struct mg_reverse_proxy_data {
+ struct mg_connection *linked_conn;
+ };
+
++struct mg_ws_proto_data {
++ /*
++ * Defragmented size of the frame so far.
++ *
++ * First byte of nc->recv_mbuf.buf is an op, the rest of the data is
++ * defragmented data.
++ */
++ size_t reass_len;
++};
++
+ struct mg_http_proto_data {
+ #if MG_ENABLE_FILESYSTEM
+ struct mg_http_proto_data_file file;
+@@ -5054,17 +5532,22 @@ struct mg_http_proto_data {
+ #if MG_ENABLE_HTTP_STREAMING_MULTIPART
+ struct mg_http_multipart_stream mp_stream;
+ #endif
++#if MG_ENABLE_HTTP_WEBSOCKET
++ struct mg_ws_proto_data ws_data;
++#endif
+ struct mg_http_proto_data_chuncked chunk;
+ struct mg_http_endpoint *endpoints;
+ mg_event_handler_t endpoint_handler;
+ struct mg_reverse_proxy_data reverse_proxy_data;
++ size_t rcvd; /* How many bytes we have received. */
+ };
+
+ static void mg_http_conn_destructor(void *proto_data);
+ struct mg_connection *mg_connect_http_base(
+- struct mg_mgr *mgr, mg_event_handler_t ev_handler,
+- struct mg_connect_opts opts, const char *schema, const char *schema_ssl,
+- const char *url, const char **path, char **user, char **pass, char **addr);
++ struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
++ struct mg_connect_opts opts, const char *scheme1, const char *scheme2,
++ const char *scheme_ssl1, const char *scheme_ssl2, const char *url,
++ struct mg_str *path, struct mg_str *user_info, struct mg_str *host);
+
+ static struct mg_http_proto_data *mg_http_get_proto_data(
+ struct mg_connection *c) {
+@@ -5079,9 +5562,9 @@ static struct mg_http_proto_data *mg_htt
+ #if MG_ENABLE_HTTP_STREAMING_MULTIPART
+ static void mg_http_free_proto_data_mp_stream(
+ struct mg_http_multipart_stream *mp) {
+- free((void *) mp->boundary);
+- free((void *) mp->var_name);
+- free((void *) mp->file_name);
++ MG_FREE((void *) mp->boundary);
++ MG_FREE((void *) mp->var_name);
++ MG_FREE((void *) mp->file_name);
+ memset(mp, 0, sizeof(*mp));
+ }
+ #endif
+@@ -5102,8 +5585,10 @@ static void mg_http_free_proto_data_endp
+
+ while (current != NULL) {
+ struct mg_http_endpoint *tmp = current->next;
+- free((void *) current->name);
+- free(current);
++ MG_FREE((void *) current->uri_pattern.p);
++ MG_FREE((void *) current->auth_domain);
++ MG_FREE((void *) current->auth_file);
++ MG_FREE(current);
+ current = tmp;
+ }
+
+@@ -5139,7 +5624,7 @@ static void mg_http_conn_destructor(void
+ #endif
+ mg_http_free_proto_data_endpoints(&pd->endpoints);
+ mg_http_free_reverse_proxy_data(&pd->reverse_proxy_data);
+- free(proto_data);
++ MG_FREE(proto_data);
+ }
+
+ #if MG_ENABLE_FILESYSTEM
+@@ -5380,28 +5865,33 @@ static void mg_http_transfer_file_data(s
+
+ if (pd->file.type == DATA_FILE) {
+ struct mbuf *io = &nc->send_mbuf;
+- if (io->len < sizeof(buf)) {
+- to_read = sizeof(buf) - io->len;
++ if (io->len >= MG_MAX_HTTP_SEND_MBUF) {
++ to_read = 0;
++ } else {
++ to_read = MG_MAX_HTTP_SEND_MBUF - io->len;
+ }
+-
+- if (left > 0 && to_read > left) {
++ if (to_read > left) {
+ to_read = left;
+ }
+-
+- if (to_read == 0) {
+- /* Rate limiting. send_mbuf is too full, wait until it's drained. */
+- } else if (pd->file.sent < pd->file.cl &&
+- (n = fread(buf, 1, to_read, pd->file.fp)) > 0) {
+- mg_send(nc, buf, n);
+- pd->file.sent += n;
++ if (to_read > 0) {
++ n = mg_fread(buf, 1, to_read, pd->file.fp);
++ if (n > 0) {
++ mg_send(nc, buf, n);
++ pd->file.sent += n;
++ DBG(("%p sent %d (total %d)", nc, (int) n, (int) pd->file.sent));
++ }
+ } else {
++ /* Rate-limited */
++ }
++ if (pd->file.sent >= pd->file.cl) {
++ LOG(LL_DEBUG, ("%p done, %d bytes", nc, (int) pd->file.sent));
+ if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE;
+ mg_http_free_proto_data_file(&pd->file);
+ }
+ } else if (pd->file.type == DATA_PUT) {
+ struct mbuf *io = &nc->recv_mbuf;
+ size_t to_write = left <= 0 ? 0 : left < io->len ? (size_t) left : io->len;
+- size_t n = fwrite(io->buf, 1, to_write, pd->file.fp);
++ size_t n = mg_fwrite(io->buf, 1, to_write, pd->file.fp);
+ if (n > 0) {
+ mbuf_remove(io, n);
+ pd->file.sent += n;
+@@ -5498,7 +5988,7 @@ MG_INTERNAL size_t mg_handle_chunked(str
+
+ /* Send MG_EV_HTTP_CHUNK event */
+ nc->flags &= ~MG_F_DELETE_CHUNK;
+- mg_call(nc, nc->handler, MG_EV_HTTP_CHUNK, hm);
++ mg_call(nc, nc->handler, nc->user_data, MG_EV_HTTP_CHUNK, hm);
+
+ /* Delete processed data if user set MG_F_DELETE_CHUNK flag */
+ if (nc->flags & MG_F_DELETE_CHUNK) {
+@@ -5510,17 +6000,19 @@ MG_INTERNAL size_t mg_handle_chunked(str
+ }
+
+ if (zero_chunk_received) {
+- hm->message.len = (size_t) pd->chunk.body_len + blen - i;
++ /* Total message size is len(body) + len(headers) */
++ hm->message.len =
++ (size_t) pd->chunk.body_len + blen - i + (hm->body.p - hm->message.p);
+ }
+ }
+
+ return body_len;
+ }
+
+-static mg_event_handler_t mg_http_get_endpoint_handler(
+- struct mg_connection *nc, struct mg_str *uri_path) {
++struct mg_http_endpoint *mg_http_get_endpoint_handler(struct mg_connection *nc,
++ struct mg_str *uri_path) {
+ struct mg_http_proto_data *pd;
+- mg_event_handler_t ret = NULL;
++ struct mg_http_endpoint *ret = NULL;
+ int matched, matched_max = 0;
+ struct mg_http_endpoint *ep;
+
+@@ -5532,11 +6024,10 @@ static mg_event_handler_t mg_http_get_en
+
+ ep = pd->endpoints;
+ while (ep != NULL) {
+- const struct mg_str name_s = {ep->name, ep->name_len};
+- if ((matched = mg_match_prefix_n(name_s, *uri_path)) != -1) {
++ if ((matched = mg_match_prefix_n(ep->uri_pattern, *uri_path)) > 0) {
+ if (matched > matched_max) {
+ /* Looking for the longest suitable handler */
+- ret = ep->handler;
++ ret = ep;
+ matched_max = matched;
+ }
+ }
+@@ -5547,20 +6038,6 @@ static mg_event_handler_t mg_http_get_en
+ return ret;
+ }
+
+-static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev,
+- struct http_message *hm) {
+- struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+-
+- if (pd->endpoint_handler == NULL || ev == MG_EV_HTTP_REQUEST) {
+- pd->endpoint_handler =
+- ev == MG_EV_HTTP_REQUEST
+- ? mg_http_get_endpoint_handler(nc->listener, &hm->uri)
+- : NULL;
+- }
+- mg_call(nc, pd->endpoint_handler ? pd->endpoint_handler : nc->handler, ev,
+- hm);
+-}
+-
+ #if MG_ENABLE_HTTP_STREAMING_MULTIPART
+ static void mg_http_multipart_continue(struct mg_connection *nc);
+
+@@ -5569,26 +6046,42 @@ static void mg_http_multipart_begin(stru
+
+ #endif
+
++static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev,
++ struct http_message *hm);
++
++static void deliver_chunk(struct mg_connection *c, struct http_message *hm,
++ int req_len) {
++ /* Incomplete message received. Send MG_EV_HTTP_CHUNK event */
++ hm->body.len = c->recv_mbuf.len - req_len;
++ c->flags &= ~MG_F_DELETE_CHUNK;
++ mg_call(c, c->handler, c->user_data, MG_EV_HTTP_CHUNK, hm);
++ /* Delete processed data if user set MG_F_DELETE_CHUNK flag */
++ if (c->flags & MG_F_DELETE_CHUNK) c->recv_mbuf.len = req_len;
++}
++
+ /*
+ * lx106 compiler has a bug (TODO(mkm) report and insert tracking bug here)
+ * If a big structure is declared in a big function, lx106 gcc will make it
+ * even bigger (round up to 4k, from 700 bytes of actual size).
+ */
+ #ifdef __xtensa__
+-static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data,
++static void mg_http_handler2(struct mg_connection *nc, int ev,
++ void *ev_data MG_UD_ARG(void *user_data),
+ struct http_message *hm) __attribute__((noinline));
+
+-void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
++void mg_http_handler(struct mg_connection *nc, int ev,
++ void *ev_data MG_UD_ARG(void *user_data)) {
+ struct http_message hm;
+- mg_http_handler2(nc, ev, ev_data, &hm);
++ mg_http_handler2(nc, ev, ev_data MG_UD_ARG(user_data), &hm);
+ }
+
+-static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data,
++static void mg_http_handler2(struct mg_connection *nc, int ev,
++ void *ev_data MG_UD_ARG(void *user_data),
+ struct http_message *hm) {
+ #else /* !__XTENSA__ */
+-void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
+- struct http_message shm;
+- struct http_message *hm = &shm;
++void mg_http_handler(struct mg_connection *nc, int ev,
++ void *ev_data MG_UD_ARG(void *user_data)) {
++ struct http_message shm, *hm = &shm;
+ #endif /* __XTENSA__ */
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+ struct mbuf *io = &nc->recv_mbuf;
+@@ -5598,6 +6091,13 @@ void mg_http_handler(struct mg_connectio
+ struct mg_str *vec;
+ #endif
+ if (ev == MG_EV_CLOSE) {
++#if MG_ENABLE_HTTP_CGI
++ /* Close associated CGI forwarder connection */
++ if (pd->cgi.cgi_nc != NULL) {
++ pd->cgi.cgi_nc->user_data = NULL;
++ pd->cgi.cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ }
++#endif
+ #if MG_ENABLE_HTTP_STREAMING_MULTIPART
+ if (pd->mp_stream.boundary != NULL) {
+ /*
+@@ -5610,14 +6110,15 @@ void mg_http_handler(struct mg_connectio
+ mp.var_name = pd->mp_stream.var_name;
+ mp.file_name = pd->mp_stream.file_name;
+ mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler),
+- MG_EV_HTTP_PART_END, &mp);
++ nc->user_data, MG_EV_HTTP_PART_END, &mp);
+ mp.var_name = NULL;
+ mp.file_name = NULL;
+ mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler),
+- MG_EV_HTTP_MULTIPART_REQUEST_END, &mp);
++ nc->user_data, MG_EV_HTTP_MULTIPART_REQUEST_END, &mp);
+ } else
+ #endif
+- if (io->len > 0 && mg_parse_http(io->buf, io->len, hm, is_req) > 0) {
++ if (io->len > 0 &&
++ (req_len = mg_parse_http(io->buf, io->len, hm, is_req)) > 0) {
+ /*
+ * For HTTP messages without Content-Length, always send HTTP message
+ * before MG_EV_CLOSE message.
+@@ -5625,8 +6126,10 @@ void mg_http_handler(struct mg_connectio
+ int ev2 = is_req ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
+ hm->message.len = io->len;
+ hm->body.len = io->buf + io->len - hm->body.p;
++ deliver_chunk(nc, hm, req_len);
+ mg_http_call_endpoint_handler(nc, ev2, hm);
+ }
++ pd->rcvd = 0;
+ }
+
+ #if MG_ENABLE_FILESYSTEM
+@@ -5635,10 +6138,11 @@ void mg_http_handler(struct mg_connectio
+ }
+ #endif
+
+- mg_call(nc, nc->handler, ev, ev_data);
++ mg_call(nc, nc->handler, nc->user_data, ev, ev_data);
+
+ if (ev == MG_EV_RECV) {
+ struct mg_str *s;
++ pd->rcvd += *(int *) ev_data;
+
+ #if MG_ENABLE_HTTP_STREAMING_MULTIPART
+ if (pd->mp_stream.boundary != NULL) {
+@@ -5680,11 +6184,12 @@ void mg_http_handler(struct mg_connectio
+ mbuf_remove(io, req_len);
+ nc->proto_handler = mg_ws_handler;
+ nc->flags |= MG_F_IS_WEBSOCKET;
+- mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL);
+- mg_ws_handler(nc, MG_EV_RECV, ev_data);
++ mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,
++ NULL);
++ mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data));
+ } else if (nc->listener != NULL &&
+ (vec = mg_get_http_header(hm, "Sec-WebSocket-Key")) != NULL) {
+- mg_event_handler_t handler;
++ struct mg_http_endpoint *ep;
+
+ /* This is a websocket request. Switch protocol handlers. */
+ mbuf_remove(io, req_len);
+@@ -5696,79 +6201,52 @@ void mg_http_handler(struct mg_connectio
+ * deliver subsequent websocket events to this handler after the
+ * protocol switch.
+ */
+- handler = mg_http_get_endpoint_handler(nc->listener, &hm->uri);
+- if (handler != NULL) {
+- nc->handler = handler;
++ ep = mg_http_get_endpoint_handler(nc->listener, &hm->uri);
++ if (ep != NULL) {
++ nc->handler = ep->handler;
++#if MG_ENABLE_CALLBACK_USERDATA
++ nc->user_data = ep->user_data;
++#endif
+ }
+
+ /* Send handshake */
+- mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_REQUEST, hm);
++ mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_REQUEST,
++ hm);
+ if (!(nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_SEND_AND_CLOSE))) {
+ if (nc->send_mbuf.len == 0) {
+- mg_ws_handshake(nc, vec);
++ mg_ws_handshake(nc, vec, hm);
+ }
+- mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL);
+- mg_ws_handler(nc, MG_EV_RECV, ev_data);
++ mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,
++ NULL);
++ mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data));
+ }
+ }
+ #endif /* MG_ENABLE_HTTP_WEBSOCKET */
+- else if (hm->message.len <= io->len) {
+- int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
+-
+-/* Whole HTTP message is fully buffered, call event handler */
+-
+-#if MG_ENABLE_JAVASCRIPT
+- v7_val_t v1, v2, headers, req, args, res;
+- struct v7 *v7 = nc->mgr->v7;
+- const char *ev_name = trigger_ev == MG_EV_HTTP_REPLY ? "onsnd" : "onrcv";
+- int i, js_callback_handled_request = 0;
+-
+- if (v7 != NULL) {
+- /* Lookup JS callback */
+- v1 = v7_get(v7, v7_get_global(v7), "Http", ~0);
+- v2 = v7_get(v7, v1, ev_name, ~0);
+-
+- /* Create callback params. TODO(lsm): own/disown those */
+- args = v7_mk_array(v7);
+- req = v7_mk_object(v7);
+- headers = v7_mk_object(v7);
+-
+- /* Populate request object */
+- v7_set(v7, req, "method", ~0,
+- v7_mk_string(v7, hm->method.p, hm->method.len, 1));
+- v7_set(v7, req, "uri", ~0, v7_mk_string(v7, hm->uri.p, hm->uri.len, 1));
+- v7_set(v7, req, "body", ~0,
+- v7_mk_string(v7, hm->body.p, hm->body.len, 1));
+- v7_set(v7, req, "headers", ~0, headers);
+- for (i = 0; hm->header_names[i].len > 0; i++) {
+- const struct mg_str *name = &hm->header_names[i];
+- const struct mg_str *value = &hm->header_values[i];
+- v7_set(v7, headers, name->p, name->len,
+- v7_mk_string(v7, value->p, value->len, 1));
+- }
+-
+- /* Invoke callback. TODO(lsm): report errors */
+- v7_array_push(v7, args, v7_mk_foreign(v7, nc));
+- v7_array_push(v7, args, req);
+- if (v7_apply(v7, v2, V7_UNDEFINED, args, &res) == V7_OK &&
+- v7_is_truthy(v7, res)) {
+- js_callback_handled_request++;
+- }
+- }
+-
+- /* If JS callback returns true, stop request processing */
+- if (js_callback_handled_request) {
+- nc->flags |= MG_F_SEND_AND_CLOSE;
+- } else {
+- mg_http_call_endpoint_handler(nc, trigger_ev, hm);
++ else if (hm->message.len > pd->rcvd) {
++ /* Not yet received all HTTP body, deliver MG_EV_HTTP_CHUNK */
++ deliver_chunk(nc, hm, req_len);
++ if (nc->recv_mbuf_limit > 0 && nc->recv_mbuf.len >= nc->recv_mbuf_limit) {
++ LOG(LL_ERROR, ("%p recv buffer (%lu bytes) exceeds the limit "
++ "%lu bytes, and not drained, closing",
++ nc, (unsigned long) nc->recv_mbuf.len,
++ (unsigned long) nc->recv_mbuf_limit));
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ }
+-#else
++ } else {
++ /* We did receive all HTTP body. */
++ int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
++ char addr[32];
++ mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
++ MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
++ DBG(("%p %s %.*s %.*s", nc, addr, (int) hm->method.len, hm->method.p,
++ (int) hm->uri.len, hm->uri.p));
++ deliver_chunk(nc, hm, req_len);
++ /* Whole HTTP message is fully buffered, call event handler */
+ mg_http_call_endpoint_handler(nc, trigger_ev, hm);
+-#endif
+ mbuf_remove(io, hm->message.len);
++ pd->rcvd = 0;
+ }
+ }
+- (void) pd;
+ }
+
+ static size_t mg_get_line_len(const char *buf, size_t buf_len) {
+@@ -5784,14 +6262,10 @@ static void mg_http_multipart_begin(stru
+ struct mg_str *ct;
+ struct mbuf *io = &nc->recv_mbuf;
+
+- char boundary[100];
++ char boundary_buf[100];
++ char *boundary = boundary_buf;
+ int boundary_len;
+
+- if (nc->listener == NULL) {
+- /* No streaming for replies now */
+- goto exit_mp;
+- }
+-
+ ct = mg_get_http_header(hm, "Content-Type");
+ if (ct == NULL) {
+ /* We need more data - or it isn't multipart mesage */
+@@ -5804,7 +6278,7 @@ static void mg_http_multipart_begin(stru
+ }
+
+ boundary_len =
+- mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary));
++ mg_http_parse_header2(ct, "boundary", &boundary, sizeof(boundary_buf));
+ if (boundary_len == 0) {
+ /*
+ * Content type is multipart, but there is no boundary,
+@@ -5824,22 +6298,24 @@ static void mg_http_multipart_begin(stru
+ */
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ } else {
++ struct mg_http_endpoint *ep = NULL;
+ pd->mp_stream.state = MPS_BEGIN;
+ pd->mp_stream.boundary = strdup(boundary);
+ pd->mp_stream.boundary_len = strlen(boundary);
+ pd->mp_stream.var_name = pd->mp_stream.file_name = NULL;
++ pd->endpoint_handler = nc->handler;
+
+- pd->endpoint_handler = mg_http_get_endpoint_handler(nc->listener, &hm->uri);
+- if (pd->endpoint_handler == NULL) {
+- pd->endpoint_handler = nc->handler;
++ ep = mg_http_get_endpoint_handler(nc->listener, &hm->uri);
++ if (ep != NULL) {
++ pd->endpoint_handler = ep->handler;
+ }
+
+- mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_MULTIPART_REQUEST, hm);
++ mg_http_call_endpoint_handler(nc, MG_EV_HTTP_MULTIPART_REQUEST, hm);
+
+ mbuf_remove(io, req_len);
+ }
+ exit_mp:
+- ;
++ if (boundary != boundary_buf) MG_FREE(boundary);
+ }
+
+ #define CONTENT_DISPOSITION "Content-Disposition: "
+@@ -5855,7 +6331,7 @@ static void mg_http_multipart_call_handl
+ mp.user_data = pd->mp_stream.user_data;
+ mp.data.p = data;
+ mp.data.len = data_len;
+- mg_call(c, pd->endpoint_handler, ev, &mp);
++ mg_call(c, pd->endpoint_handler, c->user_data, ev, &mp);
+ pd->mp_stream.user_data = mp.user_data;
+ }
+
+@@ -5876,9 +6352,9 @@ static int mg_http_multipart_finalize(st
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+
+ mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
+- free((void *) pd->mp_stream.file_name);
++ MG_FREE((void *) pd->mp_stream.file_name);
+ pd->mp_stream.file_name = NULL;
+- free((void *) pd->mp_stream.var_name);
++ MG_FREE((void *) pd->mp_stream.var_name);
+ pd->mp_stream.var_name = NULL;
+ mg_http_multipart_call_handler(c, MG_EV_HTTP_MULTIPART_REQUEST_END, NULL, 0);
+ mg_http_free_proto_data_mp_stream(&pd->mp_stream);
+@@ -5892,6 +6368,12 @@ static int mg_http_multipart_wait_for_bo
+ struct mbuf *io = &c->recv_mbuf;
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+
++ if (pd->mp_stream.boundary == NULL) {
++ pd->mp_stream.state = MPS_FINALIZE;
++ DBG(("Invalid request: boundary not initialized"));
++ return 0;
++ }
++
+ if ((int) io->len < pd->mp_stream.boundary_len + 2) {
+ return 0;
+ }
+@@ -5915,17 +6397,24 @@ static int mg_http_multipart_wait_for_bo
+ return 1;
+ }
+
++static void mg_http_parse_header_internal(struct mg_str *hdr,
++ const char *var_name,
++ struct altbuf *ab);
++
+ static int mg_http_multipart_process_boundary(struct mg_connection *c) {
+ int data_size;
+ const char *boundary, *block_begin;
+ struct mbuf *io = &c->recv_mbuf;
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+- char file_name[100], var_name[100];
++ struct altbuf ab_file_name, ab_var_name;
+ int line_len;
+ boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
+ block_begin = boundary + pd->mp_stream.boundary_len + 2;
+ data_size = io->len - (block_begin - io->buf);
+
++ altbuf_init(&ab_file_name, NULL, 0);
++ altbuf_init(&ab_var_name, NULL, 0);
++
+ while (data_size > 0 &&
+ (line_len = mg_get_line_len(block_begin, data_size)) != 0) {
+ if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&
+@@ -5935,11 +6424,16 @@ static int mg_http_multipart_process_bou
+
+ header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1;
+ header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;
+- mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2);
+- mg_http_parse_header(&header, "filename", file_name,
+- sizeof(file_name) - 2);
++
++ altbuf_reset(&ab_var_name);
++ mg_http_parse_header_internal(&header, "name", &ab_var_name);
++
++ altbuf_reset(&ab_file_name);
++ mg_http_parse_header_internal(&header, "filename", &ab_file_name);
++
+ block_begin += line_len;
+ data_size -= line_len;
++
+ continue;
+ }
+
+@@ -5950,10 +6444,16 @@ static int mg_http_multipart_process_bou
+ mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
+ }
+
+- free((void *) pd->mp_stream.file_name);
+- pd->mp_stream.file_name = strdup(file_name);
+- free((void *) pd->mp_stream.var_name);
+- pd->mp_stream.var_name = strdup(var_name);
++ /* Reserve 2 bytes for "\r\n" in file_name and var_name */
++ altbuf_append(&ab_file_name, '\0');
++ altbuf_append(&ab_file_name, '\0');
++ altbuf_append(&ab_var_name, '\0');
++ altbuf_append(&ab_var_name, '\0');
++
++ MG_FREE((void *) pd->mp_stream.file_name);
++ pd->mp_stream.file_name = altbuf_get_buf(&ab_file_name, 1 /* trim */);
++ MG_FREE((void *) pd->mp_stream.var_name);
++ pd->mp_stream.var_name = altbuf_get_buf(&ab_var_name, 1 /* trim */);
+
+ mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
+ pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
+@@ -5966,6 +6466,9 @@ static int mg_http_multipart_process_bou
+
+ pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
+
++ altbuf_reset(&ab_var_name);
++ altbuf_reset(&ab_file_name);
++
+ return 0;
+ }
+
+@@ -6238,7 +6741,7 @@ void mg_send_head(struct mg_connection *
+ void mg_http_send_error(struct mg_connection *nc, int code,
+ const char *reason) {
+ if (!reason) reason = mg_status_message(code);
+- DBG(("%p %d %s", nc, code, reason));
++ LOG(LL_DEBUG, ("%p %d %s", nc, code, reason));
+ mg_send_head(nc, code, strlen(reason),
+ "Content-Type: text/plain\r\nConnection: close");
+ mg_send(nc, reason, strlen(reason));
+@@ -6282,7 +6785,7 @@ void mg_http_serve_file(struct mg_connec
+ const struct mg_str extra_headers) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+ cs_stat_t st;
+- DBG(("%p [%s] %.*s", nc, path, (int) mime_type.len, mime_type.p));
++ LOG(LL_DEBUG, ("%p [%s] %.*s", nc, path, (int) mime_type.len, mime_type.p));
+ if (mg_stat(path, &st) != 0 || (pd->file.fp = mg_fopen(path, "rb")) == NULL) {
+ int code, err = mg_get_errno();
+ switch (err) {
+@@ -6423,6 +6926,14 @@ int mg_get_http_var(const struct mg_str
+ size_t name_len;
+ int len;
+
++ /*
++ * According to the documentation function returns negative
++ * value in case of error. For debug purposes it returns:
++ * -1 - src is wrong (NUUL)
++ * -2 - dst is wrong (NULL)
++ * -3 - failed to decode url or dst is to small
++ * -4 - name does not exist
++ */
+ if (dst == NULL || dst_len == 0) {
+ len = -2;
+ } else if (buf->p == NULL || name == NULL || buf->len == 0) {
+@@ -6431,7 +6942,7 @@ int mg_get_http_var(const struct mg_str
+ } else {
+ name_len = strlen(name);
+ e = buf->p + buf->len;
+- len = -1;
++ len = -4;
+ dst[0] = '\0';
+
+ for (p = buf->p; p + name_len < e; p++) {
+@@ -6443,8 +6954,9 @@ int mg_get_http_var(const struct mg_str
+ s = e;
+ }
+ len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
++ /* -1 means: failed to decode or dst is too small */
+ if (len == -1) {
+- len = -2;
++ len = -3;
+ }
+ break;
+ }
+@@ -6511,18 +7023,16 @@ void mg_printf_html_escape(struct mg_con
+ /* LCOV_EXCL_STOP */
+ }
+
+-int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
+- size_t buf_size) {
+- int ch = ' ', ch1 = ',', len = 0, n = strlen(var_name);
++static void mg_http_parse_header_internal(struct mg_str *hdr,
++ const char *var_name,
++ struct altbuf *ab) {
++ int ch = ' ', ch1 = ',', n = strlen(var_name);
+ const char *p, *end = hdr ? hdr->p + hdr->len : NULL, *s = NULL;
+
+- if (buf != NULL && buf_size > 0) buf[0] = '\0';
+- if (hdr == NULL) return 0;
+-
+ /* Find where variable starts */
+ for (s = hdr->p; s != NULL && s + n < end; s++) {
+- if ((s == hdr->p || s[-1] == ch || s[-1] == ch1) && s[n] == '=' &&
+- !strncmp(s, var_name, n))
++ if ((s == hdr->p || s[-1] == ch || s[-1] == ch1 || s[-1] == ';') &&
++ s[n] == '=' && !strncmp(s, var_name, n))
+ break;
+ }
+
+@@ -6532,19 +7042,51 @@ int mg_http_parse_header(struct mg_str *
+ ch = ch1 = *s++;
+ }
+ p = s;
+- while (p < end && p[0] != ch && p[0] != ch1 && len < (int) buf_size) {
++ while (p < end && p[0] != ch && p[0] != ch1) {
+ if (ch != ' ' && p[0] == '\\' && p[1] == ch) p++;
+- buf[len++] = *p++;
++ altbuf_append(ab, *p++);
+ }
+- if (len >= (int) buf_size || (ch != ' ' && *p != ch)) {
+- len = 0;
+- } else {
+- if (len > 0 && s[len - 1] == ',') len--;
+- if (len > 0 && s[len - 1] == ';') len--;
+- buf[len] = '\0';
++
++ if (ch != ' ' && *p != ch) {
++ altbuf_reset(ab);
+ }
+ }
+
++ /* If there is some data, append a NUL. */
++ if (ab->len > 0) {
++ altbuf_append(ab, '\0');
++ }
++}
++
++int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,
++ size_t buf_size) {
++ struct altbuf ab;
++ altbuf_init(&ab, *buf, buf_size);
++ if (hdr == NULL) return 0;
++ if (*buf != NULL && buf_size > 0) *buf[0] = '\0';
++
++ mg_http_parse_header_internal(hdr, var_name, &ab);
++
++ /*
++ * Get a (trimmed) buffer, and return a len without a NUL byte which might
++ * have been added.
++ */
++ *buf = altbuf_get_buf(&ab, 1 /* trim */);
++ return ab.len > 0 ? ab.len - 1 : 0;
++}
++
++int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
++ size_t buf_size) {
++ char *buf2 = buf;
++
++ int len = mg_http_parse_header2(hdr, var_name, &buf2, buf_size);
++
++ if (buf2 != buf) {
++ /* Buffer was not enough and was reallocated: free it and just return 0 */
++ MG_FREE(buf2);
++ return 0;
++ }
++
+ return len;
+ }
+
+@@ -6591,12 +7133,47 @@ static int mg_is_file_hidden(const char
+ }
+
+ return (exclude_specials && (!strcmp(path, ".") || !strcmp(path, ".."))) ||
+- (p1 != NULL &&
+- mg_match_prefix(p1, strlen(p1), path) == (int) strlen(p1)) ||
++ (p1 != NULL && mg_match_prefix(p1, strlen(p1), path) == strlen(p1)) ||
+ (p2 != NULL && mg_match_prefix(p2, strlen(p2), path) > 0);
+ }
+
+ #if !MG_DISABLE_HTTP_DIGEST_AUTH
++
++#ifndef MG_EXT_MD5
++void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],
++ const size_t *msg_lens, uint8_t *digest) {
++ size_t i;
++ cs_md5_ctx md5_ctx;
++ cs_md5_init(&md5_ctx);
++ for (i = 0; i < num_msgs; i++) {
++ cs_md5_update(&md5_ctx, msgs[i], msg_lens[i]);
++ }
++ cs_md5_final(digest, &md5_ctx);
++}
++#else
++extern void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],
++ const size_t *msg_lens, uint8_t *digest);
++#endif
++
++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);
++ cs_to_hex(buf, hash, sizeof(hash));
++}
++
+ static void mg_mkmd5resp(const char *method, size_t method_len, const char *uri,
+ size_t uri_len, const char *ha1, size_t ha1_len,
+ const char *nonce, size_t nonce_len, const char *nc,
+@@ -6605,7 +7182,6 @@ static void mg_mkmd5resp(const char *met
+ static const char colon[] = ":";
+ static const size_t one = 1;
+ char ha2[33];
+-
+ cs_md5(ha2, method, method_len, colon, one, uri, uri_len, NULL);
+ cs_md5(resp, ha1, ha1_len, colon, one, nonce, nonce_len, colon, one, nc,
+ nc_len, colon, one, cnonce, cnonce_len, colon, one, qop, qop_len,
+@@ -6615,23 +7191,23 @@ static void mg_mkmd5resp(const char *met
+ int mg_http_create_digest_auth_header(char *buf, size_t buf_len,
+ const char *method, const char *uri,
+ const char *auth_domain, const char *user,
+- const char *passwd) {
++ const char *passwd, const char *nonce) {
+ static const char colon[] = ":", qop[] = "auth";
+ static const size_t one = 1;
+ char ha1[33], resp[33], cnonce[40];
+
+- snprintf(cnonce, sizeof(cnonce), "%x", (unsigned int) mg_time());
++ snprintf(cnonce, sizeof(cnonce), "%lx", (unsigned long) mg_time());
+ cs_md5(ha1, user, (size_t) strlen(user), colon, one, auth_domain,
+ (size_t) strlen(auth_domain), colon, one, passwd,
+ (size_t) strlen(passwd), NULL);
+ mg_mkmd5resp(method, strlen(method), uri, strlen(uri), ha1, sizeof(ha1) - 1,
+- cnonce, strlen(cnonce), "1", one, cnonce, strlen(cnonce), qop,
++ nonce, strlen(nonce), "1", one, cnonce, strlen(cnonce), qop,
+ sizeof(qop) - 1, resp);
+ return snprintf(buf, buf_len,
+ "Authorization: Digest username=\"%s\","
+ "realm=\"%s\",uri=\"%s\",qop=%s,nc=1,cnonce=%s,"
+ "nonce=%s,response=%s\r\n",
+- user, auth_domain, uri, qop, cnonce, cnonce, resp);
++ user, auth_domain, uri, qop, cnonce, nonce, resp);
+ }
+
+ /*
+@@ -6643,30 +7219,67 @@ int mg_http_create_digest_auth_header(ch
+ static int mg_check_nonce(const char *nonce) {
+ unsigned long now = (unsigned long) mg_time();
+ unsigned long val = (unsigned long) strtoul(nonce, NULL, 16);
+- return now < val || now - val < 3600;
++ return (now >= val) && (now - val < 60 * 60);
+ }
+
+ int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
+ FILE *fp) {
++ int ret = 0;
+ struct mg_str *hdr;
+- char buf[128], f_user[sizeof(buf)], f_ha1[sizeof(buf)], f_domain[sizeof(buf)];
+- char user[50], cnonce[33], response[40], uri[200], qop[20], nc[20], nonce[30];
+- char expected_response[33];
++ char username_buf[50], cnonce_buf[64], response_buf[40], uri_buf[200],
++ qop_buf[20], nc_buf[20], nonce_buf[16];
++
++ char *username = username_buf, *cnonce = cnonce_buf, *response = response_buf,
++ *uri = uri_buf, *qop = qop_buf, *nc = nc_buf, *nonce = nonce_buf;
+
+ /* Parse "Authorization:" header, fail fast on parse error */
+ if (hm == NULL || fp == NULL ||
+ (hdr = mg_get_http_header(hm, "Authorization")) == NULL ||
+- mg_http_parse_header(hdr, "username", user, sizeof(user)) == 0 ||
+- mg_http_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce)) == 0 ||
+- mg_http_parse_header(hdr, "response", response, sizeof(response)) == 0 ||
+- mg_http_parse_header(hdr, "uri", uri, sizeof(uri)) == 0 ||
+- mg_http_parse_header(hdr, "qop", qop, sizeof(qop)) == 0 ||
+- mg_http_parse_header(hdr, "nc", nc, sizeof(nc)) == 0 ||
+- mg_http_parse_header(hdr, "nonce", nonce, sizeof(nonce)) == 0 ||
++ mg_http_parse_header2(hdr, "username", &username, sizeof(username_buf)) ==
++ 0 ||
++ mg_http_parse_header2(hdr, "cnonce", &cnonce, sizeof(cnonce_buf)) == 0 ||
++ mg_http_parse_header2(hdr, "response", &response, sizeof(response_buf)) ==
++ 0 ||
++ mg_http_parse_header2(hdr, "uri", &uri, sizeof(uri_buf)) == 0 ||
++ mg_http_parse_header2(hdr, "qop", &qop, sizeof(qop_buf)) == 0 ||
++ mg_http_parse_header2(hdr, "nc", &nc, sizeof(nc_buf)) == 0 ||
++ mg_http_parse_header2(hdr, "nonce", &nonce, sizeof(nonce_buf)) == 0 ||
+ mg_check_nonce(nonce) == 0) {
+- return 0;
++ ret = 0;
++ goto clean;
+ }
+
++ /* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */
++
++ ret = mg_check_digest_auth(
++ hm->method,
++ mg_mk_str_n(
++ hm->uri.p,
++ hm->uri.len + (hm->query_string.len ? hm->query_string.len + 1 : 0)),
++ mg_mk_str(username), mg_mk_str(cnonce), mg_mk_str(response),
++ mg_mk_str(qop), mg_mk_str(nc), mg_mk_str(nonce), mg_mk_str(auth_domain),
++ fp);
++
++clean:
++ if (username != username_buf) MG_FREE(username);
++ if (cnonce != cnonce_buf) MG_FREE(cnonce);
++ if (response != response_buf) MG_FREE(response);
++ if (uri != uri_buf) MG_FREE(uri);
++ if (qop != qop_buf) MG_FREE(qop);
++ if (nc != nc_buf) MG_FREE(nc);
++ if (nonce != nonce_buf) MG_FREE(nonce);
++
++ return ret;
++}
++
++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 expected_response[33];
++
+ /*
+ * Read passwords file line by line. If should have htdigest format,
+ * i.e. each line should be a colon-separated sequence:
+@@ -6674,16 +7287,16 @@ int mg_http_check_digest_auth(struct htt
+ */
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (sscanf(buf, "%[^:]:%[^:]:%s", f_user, f_domain, f_ha1) == 3 &&
+- strcmp(user, f_user) == 0 &&
+- /* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */
+- strcmp(auth_domain, f_domain) == 0) {
+- /* User and domain matched, check the password */
+- mg_mkmd5resp(
+- hm->method.p, hm->method.len, hm->uri.p,
+- hm->uri.len + (hm->query_string.len ? hm->query_string.len + 1 : 0),
+- f_ha1, strlen(f_ha1), nonce, strlen(nonce), nc, strlen(nc), cnonce,
+- strlen(cnonce), qop, strlen(qop), expected_response);
+- return mg_casecmp(response, expected_response) == 0;
++ mg_vcmp(&username, f_user) == 0 &&
++ mg_vcmp(&auth_domain, f_domain) == 0) {
++ /* Username and domain matched, check the password */
++ mg_mkmd5resp(method.p, method.len, uri.p, uri.len, f_ha1, strlen(f_ha1),
++ nonce.p, nonce.len, nc.p, nc.len, cnonce.p, cnonce.len,
++ qop.p, qop.len, expected_response);
++ LOG(LL_DEBUG,
++ ("%.*s %s %.*s %s", (int) username.len, username.p, f_domain,
++ (int) response.len, response.p, expected_response));
++ return mg_ncasecmp(response.p, expected_response, response.len) == 0;
+ }
+ }
+
+@@ -6691,25 +7304,25 @@ int mg_http_check_digest_auth(struct htt
+ return 0;
+ }
+
+-static int mg_is_authorized(struct http_message *hm, const char *path,
+- int is_directory, const char *domain,
+- const char *passwords_file,
+- int is_global_pass_file) {
++int mg_http_is_authorized(struct http_message *hm, struct mg_str path,
++ const char *domain, const char *passwords_file,
++ int flags) {
+ char buf[MG_MAX_PATH];
+ const char *p;
+ FILE *fp;
+ int authorized = 1;
+
+ if (domain != NULL && passwords_file != NULL) {
+- if (is_global_pass_file) {
++ if (flags & MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE) {
+ fp = mg_fopen(passwords_file, "r");
+- } else if (is_directory) {
+- snprintf(buf, sizeof(buf), "%s%c%s", path, DIRSEP, passwords_file);
++ } else if (flags & MG_AUTH_FLAG_IS_DIRECTORY) {
++ snprintf(buf, sizeof(buf), "%.*s%c%s", (int) path.len, path.p, DIRSEP,
++ passwords_file);
+ fp = mg_fopen(buf, "r");
+ } else {
+- p = strrchr(path, DIRSEP);
+- if (p == NULL) p = path;
+- snprintf(buf, sizeof(buf), "%.*s%c%s", (int) (p - path), path, DIRSEP,
++ p = strrchr(path.p, DIRSEP);
++ if (p == NULL) p = path.p;
++ snprintf(buf, sizeof(buf), "%.*s%c%s", (int) (p - path.p), path.p, DIRSEP,
+ passwords_file);
+ fp = mg_fopen(buf, "r");
+ }
+@@ -6717,51 +7330,29 @@ static int mg_is_authorized(struct http_
+ if (fp != NULL) {
+ authorized = mg_http_check_digest_auth(hm, domain, fp);
+ fclose(fp);
++ } else if (!(flags & MG_AUTH_FLAG_ALLOW_MISSING_FILE)) {
++ authorized = 0;
+ }
+ }
+
+- DBG(("%s %s %d %d", path, passwords_file ? passwords_file : "",
+- is_global_pass_file, authorized));
++ LOG(LL_DEBUG, ("%.*s %s %x %d", (int) path.len, path.p,
++ passwords_file ? passwords_file : "", flags, authorized));
+ return authorized;
+ }
+ #else
+-static int mg_is_authorized(struct http_message *hm, const char *path,
+- int is_directory, const char *domain,
+- const char *passwords_file,
+- int is_global_pass_file) {
++int mg_http_is_authorized(struct http_message *hm, const struct mg_str path,
++ const char *domain, const char *passwords_file,
++ int flags) {
+ (void) hm;
+ (void) path;
+- (void) is_directory;
+ (void) domain;
+ (void) passwords_file;
+- (void) is_global_pass_file;
++ (void) flags;
+ return 1;
+ }
+ #endif
+
+ #if MG_ENABLE_DIRECTORY_LISTING
+-static size_t mg_url_encode(const char *src, size_t s_len, char *dst,
+- size_t dst_len) {
+- static const char *dont_escape = "._-$,;~()/";
+- static const char *hex = "0123456789abcdef";
+- size_t i = 0, j = 0;
+-
+- for (i = j = 0; dst_len > 0 && i < s_len && j + 2 < dst_len - 1; i++, j++) {
+- if (isalnum(*(const unsigned char *) (src + i)) ||
+- strchr(dont_escape, *(const unsigned char *) (src + i)) != NULL) {
+- dst[j] = src[i];
+- } else if (j + 3 < dst_len) {
+- dst[j] = '%';
+- dst[j + 1] = hex[(*(const unsigned char *) (src + i)) >> 4];
+- dst[j + 2] = hex[(*(const unsigned char *) (src + i)) & 0xf];
+- j += 2;
+- }
+- }
+-
+- dst[j] = '\0';
+- return j;
+-}
+-
+ static void mg_escape(const char *src, char *dst, size_t dst_len) {
+ size_t n = 0;
+ while (*src != '\0' && n + 5 < dst_len) {
+@@ -6777,10 +7368,11 @@ static void mg_escape(const char *src, c
+
+ static void mg_print_dir_entry(struct mg_connection *nc, const char *file_name,
+ cs_stat_t *stp) {
+- char size[64], mod[64], href[MAX_PATH_SIZE * 3], path[MAX_PATH_SIZE];
++ char size[64], mod[64], path[MG_MAX_PATH];
+ int64_t fsize = stp->st_size;
+ int is_dir = S_ISDIR(stp->st_mode);
+ const char *slash = is_dir ? "/" : "";
++ struct mg_str href;
+
+ if (is_dir) {
+ snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
+@@ -6801,24 +7393,25 @@ static void mg_print_dir_entry(struct mg
+ }
+ strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&stp->st_mtime));
+ mg_escape(file_name, path, sizeof(path));
+- mg_url_encode(file_name, strlen(file_name), href, sizeof(href));
++ href = mg_url_encode(mg_mk_str(file_name));
+ mg_printf_http_chunk(nc,
+ "<tr><td><a href=\"%s%s\">%s%s</a></td>"
+ "<td>%s</td><td name=%" INT64_FMT ">%s</td></tr>\n",
+- href, slash, path, slash, mod, is_dir ? -1 : fsize,
++ href.p, slash, path, slash, mod, is_dir ? -1 : fsize,
+ size);
++ free((void *) href.p);
+ }
+
+ static void mg_scan_directory(struct mg_connection *nc, const char *dir,
+ const struct mg_serve_http_opts *opts,
+ void (*func)(struct mg_connection *, const char *,
+ cs_stat_t *)) {
+- char path[MAX_PATH_SIZE];
++ char path[MG_MAX_PATH];
+ cs_stat_t st;
+ struct dirent *dp;
+ DIR *dirp;
+
+- DBG(("%p [%s]", nc, dir));
++ LOG(LL_DEBUG, ("%p [%s]", nc, dir));
+ if ((dirp = (opendir(dir))) != NULL) {
+ while ((dp = readdir(dirp)) != NULL) {
+ /* Do not show current dir and hidden files */
+@@ -6832,7 +7425,7 @@ static void mg_scan_directory(struct mg_
+ }
+ closedir(dirp);
+ } else {
+- DBG(("%p opendir(%s) -> %d", nc, dir, mg_get_errno()));
++ LOG(LL_DEBUG, ("%p opendir(%s) -> %d", nc, dir, mg_get_errno()));
+ }
+ }
+
+@@ -6929,7 +7522,7 @@ MG_INTERNAL void mg_find_index_file(cons
+ MG_FREE(*index_file);
+ *index_file = NULL;
+ }
+- DBG(("[%s] [%s]", path, (*index_file ? *index_file : "")));
++ LOG(LL_DEBUG, ("[%s] [%s]", path, (*index_file ? *index_file : "")));
+ }
+
+ #if MG_ENABLE_HTTP_URL_REWRITES
+@@ -6957,7 +7550,7 @@ static int mg_http_send_port_based_redir
+ }
+
+ static void mg_reverse_proxy_handler(struct mg_connection *nc, int ev,
+- void *ev_data) {
++ void *ev_data MG_UD_ARG(void *user_data)) {
+ struct http_message *hm = (struct http_message *) ev_data;
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+
+@@ -6983,6 +7576,10 @@ static void mg_reverse_proxy_handler(str
+ pd->reverse_proxy_data.linked_conn->flags |= MG_F_SEND_AND_CLOSE;
+ break;
+ }
++
++#if MG_ENABLE_CALLBACK_USERDATA
++ (void) user_data;
++#endif
+ }
+
+ void mg_http_reverse_proxy(struct mg_connection *nc,
+@@ -6990,22 +7587,21 @@ void mg_http_reverse_proxy(struct mg_con
+ struct mg_str upstream) {
+ struct mg_connection *be;
+ char burl[256], *purl = burl;
+- char *addr = NULL;
+- const char *path = NULL;
+ int i;
+ const char *error;
+ struct mg_connect_opts opts;
++ struct mg_str path = MG_NULL_STR, user_info = MG_NULL_STR, host = MG_NULL_STR;
+ memset(&opts, 0, sizeof(opts));
+ opts.error_string = &error;
+
+ mg_asprintf(&purl, sizeof(burl), "%.*s%.*s", (int) upstream.len, upstream.p,
+ (int) (hm->uri.len - mount.len), hm->uri.p + mount.len);
+
+- be = mg_connect_http_base(nc->mgr, mg_reverse_proxy_handler, opts, "http://",
+- "https://", purl, &path, NULL /* user */,
+- NULL /* pass */, &addr);
+- DBG(("Proxying %.*s to %s (rule: %.*s)", (int) hm->uri.len, hm->uri.p, purl,
+- (int) mount.len, mount.p));
++ be = mg_connect_http_base(nc->mgr, MG_CB(mg_reverse_proxy_handler, NULL),
++ opts, "http", NULL, "https", NULL, purl, &path,
++ &user_info, &host);
++ LOG(LL_DEBUG, ("Proxying %.*s to %s (rule: %.*s)", (int) hm->uri.len,
++ hm->uri.p, purl, (int) mount.len, mount.p));
+
+ if (be == NULL) {
+ LOG(LL_ERROR, ("Error connecting to %s: %s", purl, error));
+@@ -7018,10 +7614,10 @@ void mg_http_reverse_proxy(struct mg_con
+ mg_http_get_proto_data(nc)->reverse_proxy_data.linked_conn = be;
+
+ /* send request upstream */
+- mg_printf(be, "%.*s %s HTTP/1.1\r\n", (int) hm->method.len, hm->method.p,
+- path);
++ mg_printf(be, "%.*s %.*s HTTP/1.1\r\n", (int) hm->method.len, hm->method.p,
++ (int) path.len, path.p);
+
+- mg_printf(be, "Host: %s\r\n", addr);
++ mg_printf(be, "Host: %.*s\r\n", (int) host.len, host.p);
+ for (i = 0; i < MG_MAX_HTTP_HEADERS && hm->header_names[i].len > 0; i++) {
+ struct mg_str hn = hm->header_names[i];
+ struct mg_str hv = hm->header_values[i];
+@@ -7071,7 +7667,7 @@ static int mg_http_handle_forwarding(str
+
+ return 0;
+ }
+-#endif
++#endif /* MG_ENABLE_FILESYSTEM */
+
+ MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
+ const struct mg_serve_http_opts *opts,
+@@ -7105,7 +7701,7 @@ MG_INTERNAL int mg_uri_to_local_path(str
+ }
+ } else {
+ /* Regular rewrite, URI=directory */
+- int match_len = mg_match_prefix_n(a, hm->uri);
++ size_t match_len = mg_match_prefix_n(a, hm->uri);
+ if (match_len > 0) {
+ file_uri_start = hm->uri.p + match_len;
+ if (*file_uri_start == '/' || file_uri_start == cp_end) {
+@@ -7169,7 +7765,7 @@ MG_INTERNAL int mg_uri_to_local_path(str
+ }
+ }
+ if (u >= cp_end) break;
+- parse_uri_component((const char **) &next, cp_end, '/', &component);
++ parse_uri_component((const char **) &next, cp_end, "/", &component);
+ if (component.len > 0) {
+ int len;
+ memmove(p + 1, component.p, component.len);
+@@ -7225,7 +7821,8 @@ MG_INTERNAL int mg_uri_to_local_path(str
+ }
+
+ out:
+- DBG(("'%.*s' -> '%s' + '%.*s'", (int) hm->uri.len, hm->uri.p,
++ LOG(LL_DEBUG,
++ ("'%.*s' -> '%s' + '%.*s'", (int) hm->uri.len, hm->uri.p,
+ *local_path ? *local_path : "", (int) remainder->len, remainder->p));
+ return ok;
+ }
+@@ -7284,12 +7881,12 @@ MG_INTERNAL int mg_is_not_modified(struc
+ }
+ }
+
+-static void mg_http_send_digest_auth_request(struct mg_connection *c,
+- const char *domain) {
++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=\"%lu\"\r\n"
++ "realm=\"%s\", nonce=\"%lx\"\r\n"
+ "Content-Length: 0\r\n\r\n",
+ domain, (unsigned long) mg_time());
+ }
+@@ -7331,7 +7928,8 @@ MG_INTERNAL void mg_send_http_file(struc
+ (mg_match_prefix(opts->cgi_file_pattern, strlen(opts->cgi_file_pattern),
+ index_file ? index_file : path) > 0);
+
+- DBG(("%p %.*s [%s] exists=%d is_dir=%d is_dav=%d is_cgi=%d index=%s", nc,
++ LOG(LL_DEBUG,
++ ("%p %.*s [%s] exists=%d is_dir=%d is_dav=%d is_cgi=%d index=%s", nc,
+ (int) hm->method.len, hm->method.p, path, exists, is_directory, is_dav,
+ is_cgi, index_file ? index_file : ""));
+
+@@ -7353,10 +7951,16 @@ MG_INTERNAL void mg_send_http_file(struc
+
+ if (is_dav && opts->dav_document_root == NULL) {
+ mg_http_send_error(nc, 501, NULL);
+- } else if (!mg_is_authorized(hm, path, is_directory, opts->auth_domain,
+- opts->global_auth_file, 1) ||
+- !mg_is_authorized(hm, path, is_directory, opts->auth_domain,
+- opts->per_directory_auth_file, 0)) {
++ } else if (!mg_http_is_authorized(
++ hm, mg_mk_str(path), opts->auth_domain, opts->global_auth_file,
++ ((is_directory ? MG_AUTH_FLAG_IS_DIRECTORY : 0) |
++ MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE |
++ MG_AUTH_FLAG_ALLOW_MISSING_FILE)) ||
++ !mg_http_is_authorized(
++ hm, mg_mk_str(path), opts->auth_domain,
++ opts->per_directory_auth_file,
++ ((is_directory ? MG_AUTH_FLAG_IS_DIRECTORY : 0) |
++ MG_AUTH_FLAG_ALLOW_MISSING_FILE))) {
+ mg_http_send_digest_auth_request(nc, opts->auth_domain);
+ } else if (is_cgi) {
+ #if MG_ENABLE_HTTP_CGI
+@@ -7375,8 +7979,11 @@ MG_INTERNAL void mg_send_http_file(struc
+ } else if (is_dav &&
+ (opts->dav_auth_file == NULL ||
+ (strcmp(opts->dav_auth_file, "-") != 0 &&
+- !mg_is_authorized(hm, path, is_directory, opts->auth_domain,
+- opts->dav_auth_file, 1)))) {
++ !mg_http_is_authorized(
++ hm, mg_mk_str(path), opts->auth_domain, opts->dav_auth_file,
++ ((is_directory ? MG_AUTH_FLAG_IS_DIRECTORY : 0) |
++ MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE |
++ MG_AUTH_FLAG_ALLOW_MISSING_FILE))))) {
+ mg_http_send_digest_auth_request(nc, opts->auth_domain);
+ #endif
+ } else if (!mg_vcmp(&hm->method, "MKCOL")) {
+@@ -7479,16 +8086,16 @@ void mg_serve_http(struct mg_connection
+
+ #if MG_ENABLE_HTTP_STREAMING_MULTIPART
+ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
+- mg_fu_fname_fn local_name_fn) {
++ mg_fu_fname_fn local_name_fn
++ MG_UD_ARG(void *user_data)) {
+ switch (ev) {
+ case MG_EV_HTTP_PART_BEGIN: {
+ struct mg_http_multipart_part *mp =
+ (struct mg_http_multipart_part *) ev_data;
+ struct file_upload_state *fus =
+- (struct file_upload_state *) calloc(1, sizeof(*fus));
+- mp->user_data = NULL;
+-
++ (struct file_upload_state *) MG_CALLOC(1, sizeof(*fus));
+ struct mg_str lfn = local_name_fn(nc, mg_mk_str(mp->file_name));
++ mp->user_data = NULL;
+ if (lfn.p == NULL || lfn.len == 0) {
+ LOG(LL_ERROR, ("%p Not allowed to upload %s", nc, mp->file_name));
+ mg_printf(nc,
+@@ -7500,10 +8107,10 @@ void mg_file_upload_handler(struct mg_co
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ return;
+ }
+- fus->lfn = (char *) malloc(lfn.len + 1);
++ fus->lfn = (char *) MG_MALLOC(lfn.len + 1);
+ memcpy(fus->lfn, lfn.p, lfn.len);
+ fus->lfn[lfn.len] = '\0';
+- if (lfn.p != mp->file_name) free((char *) lfn.p);
++ if (lfn.p != mp->file_name) MG_FREE((char *) lfn.p);
+ LOG(LL_DEBUG,
+ ("%p Receiving file %s -> %s", nc, mp->file_name, fus->lfn));
+ fus->fp = mg_fopen(fus->lfn, "w");
+@@ -7527,7 +8134,7 @@ void mg_file_upload_handler(struct mg_co
+ struct file_upload_state *fus =
+ (struct file_upload_state *) mp->user_data;
+ if (fus == NULL || fus->fp == NULL) break;
+- if (fwrite(mp->data.p, 1, mp->data.len, fus->fp) != mp->data.len) {
++ if (mg_fwrite(mp->data.p, 1, mp->data.len, fus->fp) != mp->data.len) {
+ LOG(LL_ERROR, ("Failed to write to %s: %d, wrote %d", fus->lfn,
+ mg_get_errno(), (int) fus->num_recd));
+ if (mg_get_errno() == ENOSPC
+@@ -7585,115 +8192,60 @@ void mg_file_upload_handler(struct mg_co
+ */
+ }
+ if (fus->fp != NULL) fclose(fus->fp);
+- free(fus->lfn);
+- free(fus);
++ MG_FREE(fus->lfn);
++ MG_FREE(fus);
+ mp->user_data = NULL;
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ break;
+ }
+ }
++
++#if MG_ENABLE_CALLBACK_USERDATA
++ (void) user_data;
++#endif
+ }
+
+ #endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
+ #endif /* MG_ENABLE_FILESYSTEM */
+
+-/* returns 0 on success, -1 on error */
+-MG_INTERNAL int mg_http_common_url_parse(const char *url, const char *schema,
+- const char *schema_tls, int *use_ssl,
+- char **user, char **pass, char **addr,
+- int *port_i, const char **path) {
+- int addr_len = 0;
+- int auth_sep_pos = -1;
+- int user_sep_pos = -1;
+- int port_pos = -1;
+- (void) user;
+- (void) pass;
+-
+- if (strncmp(url, schema, strlen(schema)) == 0) {
+- url += strlen(schema);
+- } else if (strncmp(url, schema_tls, strlen(schema_tls)) == 0) {
+- url += strlen(schema_tls);
+- *use_ssl = 1;
+-#if !MG_ENABLE_SSL
+- return -1; /* SSL is not enabled, cannot do HTTPS URLs */
+-#endif
+- }
+-
+- while (*url != '\0') {
+- *addr = (char *) MG_REALLOC(*addr, addr_len + 6 /* space for port too. */);
+- if (*addr == NULL) {
+- DBG(("OOM"));
+- return -1;
+- }
+- if (*url == '/') {
+- break;
+- }
+- if (*url == '@') {
+- auth_sep_pos = addr_len;
+- user_sep_pos = port_pos;
+- port_pos = -1;
+- }
+- if (*url == ':') port_pos = addr_len;
+- (*addr)[addr_len++] = *url;
+- (*addr)[addr_len] = '\0';
+- url++;
+- }
+-
+- if (addr_len == 0) goto cleanup;
+- if (port_pos < 0) {
+- *port_i = addr_len;
+- addr_len += sprintf(*addr + addr_len, ":%d", *use_ssl ? 443 : 80);
+- } else {
+- *port_i = -1;
+- }
+-
+- if (*path == NULL) *path = url;
+-
+- if (**path == '\0') *path = "/";
+-
+- if (user != NULL && pass != NULL) {
+- if (auth_sep_pos == -1) {
+- *user = NULL;
+- *pass = NULL;
+- } else {
+- /* user is from 0 to user_sep_pos */
+- *user = (char *) MG_MALLOC(user_sep_pos + 1);
+- memcpy(*user, *addr, user_sep_pos);
+- (*user)[user_sep_pos] = '\0';
+- /* pass is from user_sep_pos + 1 to auth_sep_pos */
+- *pass = (char *) MG_MALLOC(auth_sep_pos - user_sep_pos - 1 + 1);
+- memcpy(*pass, *addr + user_sep_pos + 1, auth_sep_pos - user_sep_pos - 1);
+- (*pass)[auth_sep_pos - user_sep_pos - 1] = '\0';
+-
+- /* move address proper to the front */
+- memmove(*addr, *addr + auth_sep_pos + 1, addr_len - auth_sep_pos);
+- }
+- }
+-
+- DBG(("%s %s", *addr, *path));
+-
+- return 0;
+-
+-cleanup:
+- MG_FREE(*addr);
+- return -1;
+-}
+-
+ struct mg_connection *mg_connect_http_base(
+- struct mg_mgr *mgr, mg_event_handler_t ev_handler,
+- struct mg_connect_opts opts, const char *schema, const char *schema_ssl,
+- const char *url, const char **path, char **user, char **pass, char **addr) {
++ struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
++ struct mg_connect_opts opts, const char *scheme1, const char *scheme2,
++ const char *scheme_ssl1, const char *scheme_ssl2, const char *url,
++ struct mg_str *path, struct mg_str *user_info, struct mg_str *host) {
+ struct mg_connection *nc = NULL;
+- int port_i = -1;
++ unsigned int port_i = 0;
+ int use_ssl = 0;
++ struct mg_str scheme, query, fragment;
++ char conn_addr_buf[2];
++ char *conn_addr = conn_addr_buf;
+
+- if (mg_http_common_url_parse(url, schema, schema_ssl, &use_ssl, user, pass,
+- addr, &port_i, path) < 0) {
++ if (mg_parse_uri(mg_mk_str(url), &scheme, user_info, host, &port_i, path,
++ &query, &fragment) != 0) {
+ MG_SET_PTRPTR(opts.error_string, "cannot parse url");
+- return NULL;
++ goto out;
++ }
++
++ /* If query is present, do not strip it. Pass to the caller. */
++ if (query.len > 0) path->len += query.len + 1;
++
++ if (scheme.len == 0 || mg_vcmp(&scheme, scheme1) == 0 ||
++ (scheme2 != NULL && mg_vcmp(&scheme, scheme2) == 0)) {
++ use_ssl = 0;
++ if (port_i == 0) port_i = 80;
++ } else if (mg_vcmp(&scheme, scheme_ssl1) == 0 ||
++ (scheme2 != NULL && mg_vcmp(&scheme, scheme_ssl2) == 0)) {
++ use_ssl = 1;
++ if (port_i == 0) port_i = 443;
++ } else {
++ goto out;
+ }
+
+- LOG(LL_DEBUG, ("%s use_ssl? %d", url, use_ssl));
++ mg_asprintf(&conn_addr, sizeof(conn_addr_buf), "tcp://%.*s:%u",
++ (int) host->len, host->p, port_i);
++ if (conn_addr == NULL) goto out;
++
++ LOG(LL_DEBUG, ("%s use_ssl? %d %s", url, use_ssl, conn_addr));
+ if (use_ssl) {
+ #if MG_ENABLE_SSL
+ /*
+@@ -7706,68 +8258,62 @@ struct mg_connection *mg_connect_http_ba
+ }
+ #else
+ MG_SET_PTRPTR(opts.error_string, "ssl is disabled");
+- if (user != NULL) MG_FREE(*user);
+- if (pass != NULL) MG_FREE(*pass);
+- MG_FREE(*addr);
+- return NULL;
++ goto out;
+ #endif
+ }
+
+- if ((nc = mg_connect_opt(mgr, *addr, ev_handler, opts)) != NULL) {
++ if ((nc = mg_connect_opt(mgr, conn_addr, MG_CB(ev_handler, user_data),
++ opts)) != NULL) {
+ mg_set_protocol_http_websocket(nc);
+- /* If the port was addred by us, restore the original host. */
+- if (port_i >= 0) (*addr)[port_i] = '\0';
+ }
+
++out:
++ if (conn_addr != NULL && conn_addr != conn_addr_buf) MG_FREE(conn_addr);
+ return nc;
+ }
+
+-struct mg_connection *mg_connect_http_opt(struct mg_mgr *mgr,
+- mg_event_handler_t ev_handler,
+- struct mg_connect_opts opts,
+- const char *url,
+- const char *extra_headers,
+- const char *post_data) {
+- char *user = NULL, *pass = NULL, *addr = NULL;
+- const char *path = NULL;
++struct mg_connection *mg_connect_http_opt(
++ struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
++ struct mg_connect_opts opts, const char *url, const char *extra_headers,
++ const char *post_data) {
++ struct mg_str user = MG_NULL_STR, null_str = MG_NULL_STR;
++ struct mg_str host = MG_NULL_STR, path = MG_NULL_STR;
+ struct mbuf auth;
+ struct mg_connection *nc =
+- mg_connect_http_base(mgr, ev_handler, opts, "http://", "https://", url,
+- &path, &user, &pass, &addr);
++ mg_connect_http_base(mgr, MG_CB(ev_handler, user_data), opts, "http",
++ NULL, "https", NULL, url, &path, &user, &host);
+
+ if (nc == NULL) {
+ return NULL;
+ }
+
+ mbuf_init(&auth, 0);
+- if (user != NULL) {
+- mg_basic_auth_header(user, pass, &auth);
++ if (user.len > 0) {
++ mg_basic_auth_header(user, null_str, &auth);
+ }
+
+- mg_printf(nc, "%s %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %" SIZE_T_FMT
++ if (post_data == NULL) post_data = "";
++ if (extra_headers == NULL) extra_headers = "";
++ if (path.len == 0) path = mg_mk_str("/");
++ if (host.len == 0) host = mg_mk_str("");
++
++ mg_printf(nc, "%s %.*s HTTP/1.1\r\nHost: %.*s\r\nContent-Length: %" SIZE_T_FMT
+ "\r\n%.*s%s\r\n%s",
+- post_data == NULL ? "GET" : "POST", path, addr,
+- post_data == NULL ? 0 : strlen(post_data), (int) auth.len,
+- (auth.buf == NULL ? "" : auth.buf),
+- extra_headers == NULL ? "" : extra_headers,
+- post_data == NULL ? "" : post_data);
++ (post_data[0] == '\0' ? "GET" : "POST"), (int) path.len, path.p,
++ (int) (path.p - host.p), host.p, strlen(post_data), (int) auth.len,
++ (auth.buf == NULL ? "" : auth.buf), extra_headers, post_data);
+
+ mbuf_free(&auth);
+- MG_FREE(user);
+- MG_FREE(pass);
+- MG_FREE(addr);
+ return nc;
+ }
+
+-struct mg_connection *mg_connect_http(struct mg_mgr *mgr,
+- mg_event_handler_t ev_handler,
+- const char *url,
+- const char *extra_headers,
+- const char *post_data) {
++struct mg_connection *mg_connect_http(
++ struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
++ const char *url, const char *extra_headers, const char *post_data) {
+ struct mg_connect_opts opts;
+ memset(&opts, 0, sizeof(opts));
+- return mg_connect_http_opt(mgr, ev_handler, opts, url, extra_headers,
+- post_data);
++ return mg_connect_http_opt(mgr, MG_CB(ev_handler, user_data), opts, url,
++ extra_headers, post_data);
+ }
+
+ size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
+@@ -7776,9 +8322,11 @@ size_t mg_parse_multipart(const char *bu
+ size_t *data_len) {
+ static const char cd[] = "Content-Disposition: ";
+ size_t hl, bl, n, ll, pos, cdl = sizeof(cd) - 1;
++ int shl;
+
+ if (buf == NULL || buf_len <= 0) return 0;
+- if ((hl = mg_http_get_request_len(buf, buf_len)) <= 0) return 0;
++ if ((shl = mg_http_get_request_len(buf, buf_len)) <= 0) return 0;
++ hl = shl;
+ if (buf[0] != '-' || buf[1] != '-' || buf[2] == '\n') return 0;
+
+ /* Get boundary length */
+@@ -7791,8 +8339,24 @@ size_t mg_parse_multipart(const char *bu
+ struct mg_str header;
+ header.p = buf + n + cdl;
+ header.len = ll - (cdl + 2);
+- mg_http_parse_header(&header, "name", var_name, var_name_len);
+- mg_http_parse_header(&header, "filename", file_name, file_name_len);
++ {
++ char *var_name2 = var_name;
++ mg_http_parse_header2(&header, "name", &var_name2, var_name_len);
++ /* TODO: handle reallocated buffer correctly */
++ if (var_name2 != var_name) {
++ MG_FREE(var_name2);
++ var_name[0] = '\0';
++ }
++ }
++ {
++ char *file_name2 = file_name;
++ mg_http_parse_header2(&header, "filename", &file_name2, file_name_len);
++ /* TODO: handle reallocated buffer correctly */
++ if (file_name2 != file_name) {
++ MG_FREE(file_name2);
++ file_name[0] = '\0';
++ }
++ }
+ }
+ }
+
+@@ -7808,32 +8372,85 @@ size_t mg_parse_multipart(const char *bu
+ return 0;
+ }
+
+-void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
+- mg_event_handler_t handler) {
++void mg_register_http_endpoint_opt(struct mg_connection *nc,
++ const char *uri_path,
++ mg_event_handler_t handler,
++ struct mg_http_endpoint_opts opts) {
+ struct mg_http_proto_data *pd = NULL;
+ struct mg_http_endpoint *new_ep = NULL;
+
+ if (nc == NULL) return;
+- new_ep = (struct mg_http_endpoint *) calloc(1, sizeof(*new_ep));
++ new_ep = (struct mg_http_endpoint *) MG_CALLOC(1, sizeof(*new_ep));
+ if (new_ep == NULL) return;
+
+ pd = mg_http_get_proto_data(nc);
+- new_ep->name = strdup(uri_path);
+- new_ep->name_len = strlen(new_ep->name);
++ new_ep->uri_pattern = mg_strdup(mg_mk_str(uri_path));
++ if (opts.auth_domain != NULL && opts.auth_file != NULL) {
++ new_ep->auth_domain = strdup(opts.auth_domain);
++ new_ep->auth_file = strdup(opts.auth_file);
++ }
+ new_ep->handler = handler;
++#if MG_ENABLE_CALLBACK_USERDATA
++ new_ep->user_data = opts.user_data;
++#endif
+ new_ep->next = pd->endpoints;
+ pd->endpoints = new_ep;
+ }
+
++static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev,
++ struct http_message *hm) {
++ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
++ void *user_data = nc->user_data;
++
++ if (ev == MG_EV_HTTP_REQUEST
++#if MG_ENABLE_HTTP_STREAMING_MULTIPART
++ || ev == MG_EV_HTTP_MULTIPART_REQUEST
++#endif
++ ) {
++ struct mg_http_endpoint *ep =
++ mg_http_get_endpoint_handler(nc->listener, &hm->uri);
++ if (ep != NULL) {
++#if MG_ENABLE_FILESYSTEM && !MG_DISABLE_HTTP_DIGEST_AUTH
++ if (!mg_http_is_authorized(hm, hm->uri, ep->auth_domain, ep->auth_file,
++ MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE)) {
++ mg_http_send_digest_auth_request(nc, ep->auth_domain);
++ return;
++ }
++#endif
++ pd->endpoint_handler = ep->handler;
++#if MG_ENABLE_CALLBACK_USERDATA
++ user_data = ep->user_data;
++#endif
++ }
++ }
++ mg_call(nc, pd->endpoint_handler ? pd->endpoint_handler : nc->handler,
++ user_data, ev, hm);
++}
++
++void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
++ MG_CB(mg_event_handler_t handler,
++ void *user_data)) {
++ struct mg_http_endpoint_opts opts;
++ memset(&opts, 0, sizeof(opts));
++#if MG_ENABLE_CALLBACK_USERDATA
++ opts.user_data = user_data;
++#endif
++ mg_register_http_endpoint_opt(nc, uri_path, handler, opts);
++}
++
+ #endif /* MG_ENABLE_HTTP */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/http_cgi.c"
++#line 1 "mongoose/src/mg_http_cgi.c"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
++#ifndef _WIN32
++#include <signal.h>
++#endif
++
+ #if MG_ENABLE_HTTP && MG_ENABLE_HTTP_CGI
+
+ #ifndef MG_MAX_CGI_ENVIR_VARS
+@@ -7844,6 +8461,8 @@ void mg_register_http_endpoint(struct mg
+ #define MG_ENV_EXPORT_TO_CGI "MONGOOSE_CGI"
+ #endif
+
++#define MG_F_HTTP_CGI_PARSE_HEADERS MG_F_USER_1
++
+ /*
+ * This structure helps to create an environment for the spawned CGI program.
+ * Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
+@@ -7926,7 +8545,7 @@ static void mg_spawn_stdio_thread(sock_t
+ }
+
+ static void mg_abs_path(const char *utf8_path, char *abs_path, size_t len) {
+- wchar_t buf[MAX_PATH_SIZE], buf2[MAX_PATH_SIZE];
++ wchar_t buf[MG_MAX_PATH], buf2[MG_MAX_PATH];
+ to_wchar(utf8_path, buf, ARRAY_SIZE(buf));
+ GetFullPathNameW(buf, ARRAY_SIZE(buf2), buf2, NULL);
+ WideCharToMultiByte(CP_UTF8, 0, buf2, wcslen(buf2) + 1, abs_path, len, 0, 0);
+@@ -7938,9 +8557,9 @@ static int mg_start_process(const char *
+ STARTUPINFOW si;
+ PROCESS_INFORMATION pi;
+ HANDLE a[2], b[2], me = GetCurrentProcess();
+- wchar_t wcmd[MAX_PATH_SIZE], full_dir[MAX_PATH_SIZE];
+- char buf[MAX_PATH_SIZE], buf2[MAX_PATH_SIZE], buf5[MAX_PATH_SIZE],
+- buf4[MAX_PATH_SIZE], cmdline[MAX_PATH_SIZE];
++ wchar_t wcmd[MG_MAX_PATH], full_dir[MG_MAX_PATH];
++ char buf[MG_MAX_PATH], buf2[MG_MAX_PATH], buf5[MG_MAX_PATH],
++ buf4[MG_MAX_PATH], cmdline[MG_MAX_PATH];
+ DWORD flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS;
+ FILE *fp;
+
+@@ -8044,7 +8663,7 @@ static int mg_start_process(const char *
+ interp == NULL ? "" : interp, interp == NULL ? "" : " ", cmd,
+ strerror(errno));
+ send(1, buf, strlen(buf), 0);
+- exit(EXIT_FAILURE); /* exec call failed */
++ _exit(EXIT_FAILURE); /* exec call failed */
+ }
+
+ return (pid != 0);
+@@ -8097,6 +8716,7 @@ static void mg_prepare_cgi_environment(s
+ char *p;
+ size_t i;
+ char buf[100];
++ size_t path_info_len = path_info != NULL ? path_info->len : 0;
+
+ blk->len = blk->nvars = 0;
+ blk->nc = nc;
+@@ -8128,7 +8748,7 @@ static void mg_prepare_cgi_environment(s
+ mg_conn_addr_to_str(nc, buf, sizeof(buf), MG_SOCK_STRINGIFY_PORT);
+ mg_addenv(blk, "SERVER_PORT=%s", buf);
+
+- s = hm->uri.p + hm->uri.len - path_info->len - 1;
++ s = hm->uri.p + hm->uri.len - path_info_len - 1;
+ if (*s == '/') {
+ const char *base_name = strrchr(prog, DIRSEP);
+ mg_addenv(blk, "SCRIPT_NAME=%.*s/%s", (int) (s - hm->uri.p), hm->uri.p,
+@@ -8201,11 +8821,18 @@ static void mg_prepare_cgi_environment(s
+ }
+
+ static void mg_cgi_ev_handler(struct mg_connection *cgi_nc, int ev,
+- void *ev_data) {
+- struct mg_connection *nc = (struct mg_connection *) cgi_nc->user_data;
++ void *ev_data MG_UD_ARG(void *user_data)) {
++#if !MG_ENABLE_CALLBACK_USERDATA
++ void *user_data = cgi_nc->user_data;
++#endif
++ struct mg_connection *nc = (struct mg_connection *) user_data;
+ (void) ev_data;
+
+- if (nc == NULL) return;
++ if (nc == NULL) {
++ /* The corresponding network connection was closed. */
++ cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ return;
++ }
+
+ switch (ev) {
+ case MG_EV_RECV:
+@@ -8222,7 +8849,7 @@ static void mg_cgi_ev_handler(struct mg_
+ * been received, send appropriate reply line, and forward all
+ * received headers to the client.
+ */
+- if (nc->flags & MG_F_USER_1) {
++ if (nc->flags & MG_F_HTTP_CGI_PARSE_HEADERS) {
+ struct mbuf *io = &cgi_nc->recv_mbuf;
+ int len = mg_http_get_request_len(io->buf, io->len);
+
+@@ -8242,14 +8869,15 @@ static void mg_cgi_ev_handler(struct mg_
+ mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\n");
+ }
+ }
+- nc->flags &= ~MG_F_USER_1;
++ nc->flags &= ~MG_F_HTTP_CGI_PARSE_HEADERS;
+ }
+- if (!(nc->flags & MG_F_USER_1)) {
++ if (!(nc->flags & MG_F_HTTP_CGI_PARSE_HEADERS)) {
+ mg_forward(cgi_nc, nc);
+ }
+ break;
+ case MG_EV_CLOSE:
+- mg_http_free_proto_data_cgi(&mg_http_get_proto_data(cgi_nc)->cgi);
++ DBG(("%p CLOSE", cgi_nc));
++ mg_http_free_proto_data_cgi(&mg_http_get_proto_data(nc)->cgi);
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ break;
+ }
+@@ -8260,7 +8888,7 @@ MG_INTERNAL void mg_handle_cgi(struct mg
+ const struct http_message *hm,
+ const struct mg_serve_http_opts *opts) {
+ struct mg_cgi_env_block blk;
+- char dir[MAX_PATH_SIZE];
++ char dir[MG_MAX_PATH];
+ const char *p;
+ sock_t fds[2];
+
+@@ -8278,24 +8906,31 @@ MG_INTERNAL void mg_handle_cgi(struct mg
+ prog = p + 1;
+ }
+
+- /*
+- * Try to create socketpair in a loop until success. mg_socketpair()
+- * can be interrupted by a signal and fail.
+- * TODO(lsm): use sigaction to restart interrupted syscall
+- */
+- do {
+- mg_socketpair(fds, SOCK_STREAM);
+- } while (fds[0] == INVALID_SOCKET);
++ if (!mg_socketpair(fds, SOCK_STREAM)) {
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ return;
++ }
++
++#ifndef _WIN32
++ struct sigaction sa;
++
++ sigemptyset(&sa.sa_mask);
++ sa.sa_handler = SIG_IGN;
++ sa.sa_flags = 0;
++ sigaction(SIGCHLD, &sa, NULL);
++#endif
+
+ if (mg_start_process(opts->cgi_interpreter, prog, blk.buf, blk.vars, dir,
+ fds[1]) != 0) {
+ size_t n = nc->recv_mbuf.len - (hm->message.len - hm->body.len);
+ struct mg_connection *cgi_nc =
+- mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler);
+- struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(cgi_nc);
++ mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler MG_UD_ARG(nc));
++ struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(nc);
+ cgi_pd->cgi.cgi_nc = cgi_nc;
++#if !MG_ENABLE_CALLBACK_USERDATA
+ cgi_pd->cgi.cgi_nc->user_data = nc;
+- nc->flags |= MG_F_USER_1;
++#endif
++ nc->flags |= MG_F_HTTP_CGI_PARSE_HEADERS;
+ /* Push POST data to the CGI */
+ if (n > 0 && n < nc->recv_mbuf.len) {
+ mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, n);
+@@ -8312,15 +8947,17 @@ MG_INTERNAL void mg_handle_cgi(struct mg
+ }
+
+ MG_INTERNAL void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d) {
+- if (d != NULL) {
+- if (d->cgi_nc != NULL) d->cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+- memset(d, 0, sizeof(struct mg_http_proto_data_cgi));
++ if (d == NULL) return;
++ if (d->cgi_nc != NULL) {
++ d->cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ d->cgi_nc->user_data = NULL;
+ }
++ memset(d, 0, sizeof(*d));
+ }
+
+ #endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_CGI */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/http_ssi.c"
++#line 1 "mongoose/src/mg_http_ssi.c"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+@@ -8336,7 +8973,7 @@ static void mg_send_ssi_file(struct mg_c
+ static void mg_send_file_data(struct mg_connection *nc, FILE *fp) {
+ char buf[BUFSIZ];
+ size_t n;
+- while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
++ while ((n = mg_fread(buf, 1, sizeof(buf), fp)) > 0) {
+ mg_send(nc, buf, n);
+ }
+ }
+@@ -8344,7 +8981,7 @@ static void mg_send_file_data(struct mg_
+ static void mg_do_ssi_include(struct mg_connection *nc, struct http_message *hm,
+ const char *ssi, char *tag, int include_level,
+ const struct mg_serve_http_opts *opts) {
+- char file_name[BUFSIZ], path[MAX_PATH_SIZE], *p;
++ char file_name[MG_MAX_PATH], path[MG_MAX_PATH], *p;
+ FILE *fp;
+
+ /*
+@@ -8447,9 +9084,9 @@ static void mg_send_ssi_file(struct mg_c
+ cctx.req = hm;
+ cctx.file = mg_mk_str(path);
+ cctx.arg = mg_mk_str(p + d_call.len + 1);
+- mg_call(nc, NULL, MG_EV_SSI_CALL,
++ mg_call(nc, NULL, nc->user_data, MG_EV_SSI_CALL,
+ (void *) cctx.arg.p); /* NUL added above */
+- mg_call(nc, NULL, MG_EV_SSI_CALL_CTX, &cctx);
++ mg_call(nc, NULL, nc->user_data, MG_EV_SSI_CALL_CTX, &cctx);
+ #if MG_ENABLE_HTTP_SSI_EXEC
+ } else if (strncmp(p, d_exec.p, d_exec.len) == 0) {
+ do_ssi_exec(nc, p + d_exec.len + 1);
+@@ -8516,7 +9153,7 @@ MG_INTERNAL void mg_handle_ssi_request(s
+
+ #endif /* MG_ENABLE_HTTP_SSI && MG_ENABLE_HTTP && MG_ENABLE_FILESYSTEM */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/http_webdav.c"
++#line 1 "mongoose/src/mg_http_webdav.c"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+@@ -8560,10 +9197,10 @@ static int mg_mkdir(const char *path, ui
+
+ static void mg_print_props(struct mg_connection *nc, const char *name,
+ cs_stat_t *stp) {
+- char mtime[64], buf[MAX_PATH_SIZE * 3];
++ char mtime[64];
+ time_t t = stp->st_mtime; /* store in local variable for NDK compile */
++ struct mg_str name_esc = mg_url_encode(mg_mk_str(name));
+ mg_gmt_time_string(mtime, sizeof(mtime), &t);
+- mg_url_encode(name, strlen(name), buf, sizeof(buf));
+ mg_printf(nc,
+ "<d:response>"
+ "<d:href>%s</d:href>"
+@@ -8577,8 +9214,9 @@ static void mg_print_props(struct mg_con
+ "<d:status>HTTP/1.1 200 OK</d:status>"
+ "</d:propstat>"
+ "</d:response>\n",
+- buf, S_ISDIR(stp->st_mode) ? "<d:collection/>" : "",
++ name_esc.p, S_ISDIR(stp->st_mode) ? "<d:collection/>" : "",
+ (int64_t) stp->st_size, mtime);
++ free((void *) name_esc.p);
+ }
+
+ MG_INTERNAL void mg_handle_propfind(struct mg_connection *nc, const char *path,
+@@ -8598,7 +9236,7 @@ MG_INTERNAL void mg_handle_propfind(stru
+ strcmp(opts->enable_directory_listing, "yes") != 0) {
+ mg_printf(nc, "%s", "HTTP/1.1 403 Directory Listing Denied\r\n\r\n");
+ } else {
+- char uri[MAX_PATH_SIZE];
++ char uri[MG_MAX_PATH];
+ mg_send(nc, header, sizeof(header) - 1);
+ snprintf(uri, sizeof(uri), "%.*s", (int) hm->uri.len, hm->uri.p);
+ mg_print_props(nc, uri, stp);
+@@ -8665,7 +9303,7 @@ MG_INTERNAL void mg_handle_mkcol(struct
+
+ static int mg_remove_directory(const struct mg_serve_http_opts *opts,
+ const char *dir) {
+- char path[MAX_PATH_SIZE];
++ char path[MG_MAX_PATH];
+ struct dirent *dp;
+ cs_stat_t st;
+ DIR *dirp;
+@@ -8700,7 +9338,7 @@ MG_INTERNAL void mg_handle_move(struct m
+ const char *p = (char *) memchr(dest->p, '/', dest->len);
+ if (p != NULL && p[1] == '/' &&
+ (p = (char *) memchr(p + 2, '/', dest->p + dest->len - p)) != NULL) {
+- char buf[MAX_PATH_SIZE];
++ char buf[MG_MAX_PATH];
+ snprintf(buf, sizeof(buf), "%s%.*s", opts->dav_document_root,
+ (int) (dest->p + dest->len - p), p);
+ if (rename(path, buf) == 0) {
+@@ -8737,7 +9375,7 @@ static int mg_create_itermediate_directo
+ /* Create intermediate directories if they do not exist */
+ for (s = path + 1; *s != '\0'; s++) {
+ if (*s == '/') {
+- char buf[MAX_PATH_SIZE];
++ char buf[MG_MAX_PATH];
+ cs_stat_t st;
+ snprintf(buf, sizeof(buf), "%.*s", (int) (s - path), path);
+ buf[sizeof(buf) - 1] = '\0';
+@@ -8787,7 +9425,7 @@ MG_INTERNAL void mg_handle_put(struct mg
+
+ #endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBDAV */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/http_websocket.c"
++#line 1 "mongoose/src/mg_http_websocket.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -8796,109 +9434,205 @@ MG_INTERNAL void mg_handle_put(struct mg
+
+ #if MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBSOCKET
+
++/* Amalgamated: #include "common/cs_sha1.h" */
++
+ #ifndef MG_WEBSOCKET_PING_INTERVAL_SECONDS
+ #define MG_WEBSOCKET_PING_INTERVAL_SECONDS 5
+ #endif
+
+-#define MG_WS_NO_HOST_HEADER_MAGIC ((char *) 0x1)
++#define FLAGS_MASK_FIN (1 << 7)
++#define FLAGS_MASK_OP 0x0f
+
+ static int mg_is_ws_fragment(unsigned char flags) {
+- return (flags & 0x80) == 0 || (flags & 0x0f) == 0;
++ return (flags & FLAGS_MASK_FIN) == 0 ||
++ (flags & FLAGS_MASK_OP) == WEBSOCKET_OP_CONTINUE;
+ }
+
+ static int mg_is_ws_first_fragment(unsigned char flags) {
+- return (flags & 0x80) == 0 && (flags & 0x0f) != 0;
++ return (flags & FLAGS_MASK_FIN) == 0 &&
++ (flags & FLAGS_MASK_OP) != WEBSOCKET_OP_CONTINUE;
++}
++
++static int mg_is_ws_control_frame(unsigned char flags) {
++ unsigned char op = (flags & FLAGS_MASK_OP);
++ return op == WEBSOCKET_OP_CLOSE || op == WEBSOCKET_OP_PING ||
++ op == WEBSOCKET_OP_PONG;
+ }
+
+ static void mg_handle_incoming_websocket_frame(struct mg_connection *nc,
+ struct websocket_message *wsm) {
+ if (wsm->flags & 0x8) {
+- mg_call(nc, nc->handler, MG_EV_WEBSOCKET_CONTROL_FRAME, wsm);
++ mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_CONTROL_FRAME, wsm);
+ } else {
+- mg_call(nc, nc->handler, MG_EV_WEBSOCKET_FRAME, wsm);
++ mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_FRAME, wsm);
++ }
++}
++
++static struct mg_ws_proto_data *mg_ws_get_proto_data(struct mg_connection *nc) {
++ struct mg_http_proto_data *htd = mg_http_get_proto_data(nc);
++ return (htd != NULL ? &htd->ws_data : NULL);
++}
++
++/*
++ * Sends a Close websocket frame with the given data, and closes the underlying
++ * connection. If `len` is ~0, strlen(data) is used.
++ */
++static void mg_ws_close(struct mg_connection *nc, const void *data,
++ size_t len) {
++ if ((int) len == ~0) {
++ len = strlen((const char *) data);
+ }
++ mg_send_websocket_frame(nc, WEBSOCKET_OP_CLOSE, data, len);
++ nc->flags |= MG_F_SEND_AND_CLOSE;
+ }
+
+ static int mg_deliver_websocket_data(struct mg_connection *nc) {
+ /* Using unsigned char *, cause of integer arithmetic below */
+- uint64_t i, data_len = 0, frame_len = 0, buf_len = nc->recv_mbuf.len, len,
+- mask_len = 0, header_len = 0;
+- unsigned char *p = (unsigned char *) nc->recv_mbuf.buf, *buf = p,
+- *e = p + buf_len;
+- unsigned *sizep = (unsigned *) &p[1]; /* Size ptr for defragmented frames */
+- int ok, reass = buf_len > 0 && mg_is_ws_fragment(p[0]) &&
+- !(nc->flags & MG_F_WEBSOCKET_NO_DEFRAG);
+-
+- /* If that's a continuation frame that must be reassembled, handle it */
+- if (reass && !mg_is_ws_first_fragment(p[0]) &&
+- buf_len >= 1 + sizeof(*sizep) && buf_len >= 1 + sizeof(*sizep) + *sizep) {
+- buf += 1 + sizeof(*sizep) + *sizep;
+- buf_len -= 1 + sizeof(*sizep) + *sizep;
+- }
+-
+- if (buf_len >= 2) {
+- len = buf[1] & 127;
+- mask_len = buf[1] & 128 ? 4 : 0;
+- if (len < 126 && buf_len >= mask_len) {
++ uint64_t i, data_len = 0, frame_len = 0, new_data_len = nc->recv_mbuf.len,
++ len, mask_len = 0, header_len = 0;
++ struct mg_ws_proto_data *wsd = mg_ws_get_proto_data(nc);
++ unsigned char *new_data = (unsigned char *) nc->recv_mbuf.buf,
++ *e = (unsigned char *) nc->recv_mbuf.buf + nc->recv_mbuf.len;
++ uint8_t flags;
++ int ok, reass;
++
++ if (wsd->reass_len > 0) {
++ /*
++ * We already have some previously received data which we need to
++ * reassemble and deliver to the client code when we get the final
++ * fragment.
++ *
++ * NOTE: it doesn't mean that the current message must be a continuation:
++ * it might be a control frame (Close, Ping or Pong), which should be
++ * handled without breaking the fragmented message.
++ */
++
++ size_t existing_len = wsd->reass_len;
++ assert(new_data_len >= existing_len);
++
++ new_data += existing_len;
++ new_data_len -= existing_len;
++ }
++
++ flags = new_data[0];
++
++ reass = new_data_len > 0 && mg_is_ws_fragment(flags) &&
++ !(nc->flags & MG_F_WEBSOCKET_NO_DEFRAG);
++
++ if (reass && mg_is_ws_control_frame(flags)) {
++ /*
++ * Control frames can't be fragmented, so if we encounter fragmented
++ * control frame, close connection immediately.
++ */
++ mg_ws_close(nc, "fragmented control frames are illegal", ~0);
++ return 0;
++ } else if (new_data_len > 0 && !reass && !mg_is_ws_control_frame(flags) &&
++ wsd->reass_len > 0) {
++ /*
++ * When in the middle of a fragmented message, only the continuations
++ * and control frames are allowed.
++ */
++ mg_ws_close(nc, "non-continuation in the middle of a fragmented message",
++ ~0);
++ return 0;
++ }
++
++ if (new_data_len >= 2) {
++ len = new_data[1] & 0x7f;
++ mask_len = new_data[1] & FLAGS_MASK_FIN ? 4 : 0;
++ if (len < 126 && new_data_len >= mask_len) {
+ data_len = len;
+ header_len = 2 + mask_len;
+- } else if (len == 126 && buf_len >= 4 + mask_len) {
++ } else if (len == 126 && new_data_len >= 4 + mask_len) {
+ header_len = 4 + mask_len;
+- data_len = ntohs(*(uint16_t *) &buf[2]);
+- } else if (buf_len >= 10 + mask_len) {
++ data_len = ntohs(*(uint16_t *) &new_data[2]);
++ } else if (new_data_len >= 10 + mask_len) {
+ header_len = 10 + mask_len;
+- data_len = (((uint64_t) ntohl(*(uint32_t *) &buf[2])) << 32) +
+- ntohl(*(uint32_t *) &buf[6]);
++ data_len = (((uint64_t) ntohl(*(uint32_t *) &new_data[2])) << 32) +
++ ntohl(*(uint32_t *) &new_data[6]);
+ }
+ }
+
+ frame_len = header_len + data_len;
+- ok = frame_len > 0 && frame_len <= buf_len;
++ ok = (frame_len > 0 && frame_len <= new_data_len);
++
++ /* Check for overflow */
++ if (frame_len < header_len || frame_len < data_len) {
++ ok = 0;
++ mg_ws_close(nc, "overflowed message", ~0);
++ }
+
+ if (ok) {
++ size_t cleanup_len = 0;
+ struct websocket_message wsm;
+
+ wsm.size = (size_t) data_len;
+- wsm.data = buf + header_len;
+- wsm.flags = buf[0];
++ wsm.data = new_data + header_len;
++ wsm.flags = flags;
+
+ /* Apply mask if necessary */
+ if (mask_len > 0) {
+ for (i = 0; i < data_len; i++) {
+- buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4];
++ new_data[i + header_len] ^= (new_data + header_len - mask_len)[i % 4];
+ }
+ }
+
+ if (reass) {
+- /* On first fragmented frame, nullify size */
+- if (mg_is_ws_first_fragment(wsm.flags)) {
+- mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.size + sizeof(*sizep));
+- p[0] &= ~0x0f; /* Next frames will be treated as continuation */
+- buf = p + 1 + sizeof(*sizep);
+- *sizep = 0; /* TODO(lsm): fix. this can stomp over frame data */
++ /* This is a message fragment */
++
++ if (mg_is_ws_first_fragment(flags)) {
++ /*
++ * On the first fragmented frame, skip the first byte (op) and also
++ * reset size to 1 (op), it'll be incremented with the data len below.
++ */
++ new_data += 1;
++ wsd->reass_len = 1 /* op */;
+ }
+
+ /* Append this frame to the reassembled buffer */
+- memmove(buf, wsm.data, e - wsm.data);
+- (*sizep) += wsm.size;
+- nc->recv_mbuf.len -= wsm.data - buf;
+-
+- /* On last fragmented frame - call user handler and remove data */
+- if (wsm.flags & 0x80) {
+- wsm.data = p + 1 + sizeof(*sizep);
+- wsm.size = *sizep;
++ memmove(new_data, wsm.data, e - wsm.data);
++ wsd->reass_len += wsm.size;
++ nc->recv_mbuf.len -= wsm.data - new_data;
++
++ if (flags & FLAGS_MASK_FIN) {
++ /* On last fragmented frame - call user handler and remove data */
++ wsm.flags = FLAGS_MASK_FIN | nc->recv_mbuf.buf[0];
++ wsm.data = (unsigned char *) nc->recv_mbuf.buf + 1 /* op */;
++ wsm.size = wsd->reass_len - 1 /* op */;
++ cleanup_len = wsd->reass_len;
++ wsd->reass_len = 0;
++
++ /* Pass reassembled message to the client code. */
+ mg_handle_incoming_websocket_frame(nc, &wsm);
+- mbuf_remove(&nc->recv_mbuf, 1 + sizeof(*sizep) + *sizep);
++ mbuf_remove(&nc->recv_mbuf, cleanup_len); /* Cleanup frame */
+ }
+ } else {
+- /* TODO(lsm): properly handle OOB control frames during defragmentation */
++ /*
++ * This is a complete message, not a fragment. It might happen in between
++ * of a fragmented message (in this case, WebSocket protocol requires
++ * current message to be a control frame).
++ */
++ cleanup_len = (size_t) frame_len;
++
++ /* First of all, check if we need to react on a control frame. */
++ switch (flags & FLAGS_MASK_OP) {
++ case WEBSOCKET_OP_PING:
++ mg_send_websocket_frame(nc, WEBSOCKET_OP_PONG, wsm.data, wsm.size);
++ break;
++
++ case WEBSOCKET_OP_CLOSE:
++ mg_ws_close(nc, wsm.data, wsm.size);
++ break;
++ }
++
++ /* Pass received message to the client code. */
+ mg_handle_incoming_websocket_frame(nc, &wsm);
+- mbuf_remove(&nc->recv_mbuf, (size_t) frame_len); /* Cleanup frame */
+- }
+
+- /* If client closes, close too */
+- if ((buf[0] & 0x0f) == WEBSOCKET_OP_CLOSE) {
+- nc->flags |= MG_F_SEND_AND_CLOSE;
++ /* Cleanup frame */
++ memmove(nc->recv_mbuf.buf + wsd->reass_len,
++ nc->recv_mbuf.buf + wsd->reass_len + cleanup_len,
++ nc->recv_mbuf.len - wsd->reass_len - cleanup_len);
++ nc->recv_mbuf.len -= cleanup_len;
+ }
+ }
+
+@@ -8943,7 +9677,8 @@ static void mg_send_ws_header(struct mg_
+ int header_len;
+ unsigned char header[10];
+
+- header[0] = (op & WEBSOCKET_DONT_FIN ? 0x0 : 0x80) + (op & 0x0f);
++ header[0] =
++ (op & WEBSOCKET_DONT_FIN ? 0x0 : FLAGS_MASK_FIN) | (op & FLAGS_MASK_OP);
+ if (len < 126) {
+ header[1] = (unsigned char) len;
+ header_len = 2;
+@@ -9037,8 +9772,8 @@ void mg_printf_websocket_frame(struct mg
+ }
+
+ MG_INTERNAL void mg_ws_handler(struct mg_connection *nc, int ev,
+- void *ev_data) {
+- mg_call(nc, nc->handler, ev, ev_data);
++ void *ev_data MG_UD_ARG(void *user_data)) {
++ mg_call(nc, nc->handler, nc->user_data, ev, ev_data);
+
+ switch (ev) {
+ case MG_EV_RECV:
+@@ -9058,11 +9793,14 @@ MG_INTERNAL void mg_ws_handler(struct mg
+ default:
+ break;
+ }
++#if MG_ENABLE_CALLBACK_USERDATA
++ (void) user_data;
++#endif
+ }
+
+ #ifndef MG_EXT_SHA1
+-static void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],
+- const size_t *msg_lens, uint8_t *digest) {
++void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],
++ const size_t *msg_lens, uint8_t *digest) {
+ size_t i;
+ cs_sha1_ctx sha_ctx;
+ cs_sha1_init(&sha_ctx);
+@@ -9077,21 +9815,28 @@ extern void mg_hash_sha1_v(size_t num_ms
+ #endif
+
+ MG_INTERNAL void mg_ws_handshake(struct mg_connection *nc,
+- const struct mg_str *key) {
++ const struct mg_str *key,
++ struct http_message *hm) {
+ static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ const uint8_t *msgs[2] = {(const uint8_t *) key->p, (const uint8_t *) magic};
+ const size_t msg_lens[2] = {key->len, 36};
+ unsigned char sha[20];
+ char b64_sha[30];
++ struct mg_str *s;
+
+ mg_hash_sha1_v(2, msgs, msg_lens, sha);
+ mg_base64_encode(sha, sizeof(sha), b64_sha);
+- mg_printf(nc, "%s%s%s",
++ mg_printf(nc, "%s",
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+- "Connection: Upgrade\r\n"
+- "Sec-WebSocket-Accept: ",
+- b64_sha, "\r\n\r\n");
++ "Connection: Upgrade\r\n");
++
++ s = mg_get_http_header(hm, "Sec-WebSocket-Protocol");
++ if (s != NULL) {
++ mg_printf(nc, "Sec-WebSocket-Protocol: %.*s\r\n", (int) s->len, s->p);
++ }
++ mg_printf(nc, "Sec-WebSocket-Accept: %s%s", b64_sha, "\r\n\r\n");
++
+ DBG(("%p %.*s %s", nc, (int) key->len, key->p, b64_sha));
+ }
+
+@@ -9106,6 +9851,18 @@ void mg_send_websocket_handshake3(struct
+ const char *host, const char *protocol,
+ const char *extra_headers, const char *user,
+ const char *pass) {
++ mg_send_websocket_handshake3v(nc, mg_mk_str(path), mg_mk_str(host),
++ mg_mk_str(protocol), mg_mk_str(extra_headers),
++ mg_mk_str(user), mg_mk_str(pass));
++}
++
++void mg_send_websocket_handshake3v(struct mg_connection *nc,
++ const struct mg_str path,
++ const struct mg_str host,
++ const struct mg_str protocol,
++ const struct mg_str extra_headers,
++ const struct mg_str user,
++ const struct mg_str pass) {
+ struct mbuf auth;
+ char key[25];
+ uint32_t nonce[4];
+@@ -9116,7 +9873,7 @@ void mg_send_websocket_handshake3(struct
+ mg_base64_encode((unsigned char *) &nonce, sizeof(nonce), key);
+
+ mbuf_init(&auth, 0);
+- if (user != NULL) {
++ if (user.len > 0) {
+ mg_basic_auth_header(user, pass, &auth);
+ }
+
+@@ -9127,23 +9884,26 @@ void mg_send_websocket_handshake3(struct
+ * because it handles NULL specially (and incorrectly).
+ */
+ mg_printf(nc,
+- "GET %s HTTP/1.1\r\n"
++ "GET %.*s HTTP/1.1\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "%.*s"
+ "Sec-WebSocket-Version: 13\r\n"
+ "Sec-WebSocket-Key: %s\r\n",
+- path, (int) auth.len, (auth.buf == NULL ? "" : auth.buf), key);
++ (int) path.len, path.p, (int) auth.len,
++ (auth.buf == NULL ? "" : auth.buf), key);
+
+ /* TODO(mkm): take default hostname from http proto data if host == NULL */
+- if (host != MG_WS_NO_HOST_HEADER_MAGIC) {
+- mg_printf(nc, "Host: %s\r\n", host);
+- }
+- if (protocol != NULL) {
+- mg_printf(nc, "Sec-WebSocket-Protocol: %s\r\n", protocol);
++ if (host.len > 0) {
++ int host_len = (int) (path.p - host.p); /* Account for possible :PORT */
++ mg_printf(nc, "Host: %.*s\r\n", host_len, host.p);
++ }
++ if (protocol.len > 0) {
++ mg_printf(nc, "Sec-WebSocket-Protocol: %.*s\r\n", (int) protocol.len,
++ protocol.p);
+ }
+- if (extra_headers != NULL) {
+- mg_printf(nc, "%s", extra_headers);
++ if (extra_headers.len > 0) {
++ mg_printf(nc, "%.*s", (int) extra_headers.len, extra_headers.p);
+ }
+ mg_printf(nc, "\r\n");
+
+@@ -9152,52 +9912,49 @@ void mg_send_websocket_handshake3(struct
+
+ void mg_send_websocket_handshake(struct mg_connection *nc, const char *path,
+ const char *extra_headers) {
+- mg_send_websocket_handshake2(nc, path, MG_WS_NO_HOST_HEADER_MAGIC, NULL,
+- extra_headers);
++ struct mg_str null_str = MG_NULL_STR;
++ mg_send_websocket_handshake3v(
++ nc, mg_mk_str(path), null_str /* host */, null_str /* protocol */,
++ mg_mk_str(extra_headers), null_str /* user */, null_str /* pass */);
+ }
+
+-struct mg_connection *mg_connect_ws_opt(struct mg_mgr *mgr,
+- mg_event_handler_t ev_handler,
+- struct mg_connect_opts opts,
+- const char *url, const char *protocol,
+- const char *extra_headers) {
+- char *user = NULL, *pass = NULL, *addr = NULL;
+- const char *path = NULL;
++struct mg_connection *mg_connect_ws_opt(
++ struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
++ struct mg_connect_opts opts, const char *url, const char *protocol,
++ const char *extra_headers) {
++ struct mg_str null_str = MG_NULL_STR;
++ struct mg_str host = MG_NULL_STR, path = MG_NULL_STR, user_info = MG_NULL_STR;
+ struct mg_connection *nc =
+- mg_connect_http_base(mgr, ev_handler, opts, "ws://", "wss://", url, &path,
+- &user, &pass, &addr);
+-
++ mg_connect_http_base(mgr, MG_CB(ev_handler, user_data), opts, "http",
++ "ws", "https", "wss", url, &path, &user_info, &host);
+ if (nc != NULL) {
+- mg_send_websocket_handshake3(nc, path, addr, protocol, extra_headers, user,
+- pass);
++ mg_send_websocket_handshake3v(nc, path, host, mg_mk_str(protocol),
++ mg_mk_str(extra_headers), user_info,
++ null_str);
+ }
+-
+- MG_FREE(addr);
+- MG_FREE(user);
+- MG_FREE(pass);
+ return nc;
+ }
+
+-struct mg_connection *mg_connect_ws(struct mg_mgr *mgr,
+- mg_event_handler_t ev_handler,
+- const char *url, const char *protocol,
+- const char *extra_headers) {
++struct mg_connection *mg_connect_ws(
++ struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
++ const char *url, const char *protocol, const char *extra_headers) {
+ struct mg_connect_opts opts;
+ memset(&opts, 0, sizeof(opts));
+- return mg_connect_ws_opt(mgr, ev_handler, opts, url, protocol, extra_headers);
++ return mg_connect_ws_opt(mgr, MG_CB(ev_handler, user_data), opts, url,
++ protocol, extra_headers);
+ }
+ #endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBSOCKET */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/util.c"
++#line 1 "mongoose/src/mg_util.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+-/* Amalgamated: #include "common/base64.h" */
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/util.h" */
++/* Amalgamated: #include "common/cs_base64.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_util.h" */
+
+ /* For platforms with limited libc */
+ #ifndef MAX
+@@ -9213,14 +9970,10 @@ const char *mg_skip(const char *s, const
+ return s;
+ }
+
+-static int lowercase(const char *s) {
+- return tolower(*(const unsigned char *) s);
+-}
+-
+-#if MG_ENABLE_FILESYSTEM
++#if MG_ENABLE_FILESYSTEM && !defined(MG_USER_FILE_FUNCTIONS)
+ int mg_stat(const char *path, cs_stat_t *st) {
+ #ifdef _WIN32
+- wchar_t wpath[MAX_PATH_SIZE];
++ wchar_t wpath[MG_MAX_PATH];
+ to_wchar(path, wpath, ARRAY_SIZE(wpath));
+ DBG(("[%ls] -> %d", wpath, _wstati64(wpath, st)));
+ return _wstati64(wpath, st);
+@@ -9231,7 +9984,7 @@ int mg_stat(const char *path, cs_stat_t
+
+ FILE *mg_fopen(const char *path, const char *mode) {
+ #ifdef _WIN32
+- wchar_t wpath[MAX_PATH_SIZE], wmode[10];
++ wchar_t wpath[MG_MAX_PATH], wmode[10];
+ to_wchar(path, wpath, ARRAY_SIZE(wpath));
+ to_wchar(mode, wmode, ARRAY_SIZE(wmode));
+ return _wfopen(wpath, wmode);
+@@ -9242,13 +9995,21 @@ FILE *mg_fopen(const char *path, const c
+
+ int mg_open(const char *path, int flag, int mode) { /* LCOV_EXCL_LINE */
+ #if defined(_WIN32) && !defined(WINCE)
+- wchar_t wpath[MAX_PATH_SIZE];
++ wchar_t wpath[MG_MAX_PATH];
+ to_wchar(path, wpath, ARRAY_SIZE(wpath));
+ return _wopen(wpath, flag, mode);
+ #else
+ return open(path, flag, mode); /* LCOV_EXCL_LINE */
+ #endif
+ }
++
++size_t mg_fread(void *ptr, size_t size, size_t count, FILE *f) {
++ return fread(ptr, size, count, f);
++}
++
++size_t mg_fwrite(const void *ptr, size_t size, size_t count, FILE *f) {
++ return fwrite(ptr, size, count, f);
++}
+ #endif
+
+ void mg_base64_encode(const unsigned char *src, int src_len, char *dst) {
+@@ -9295,11 +10056,11 @@ void mg_set_close_on_exec(sock_t sock) {
+ #endif
+ }
+
+-void mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,
+- int flags) {
++int mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,
++ int flags) {
+ int is_v6;
+- if (buf == NULL || len <= 0) return;
+- buf[0] = '\0';
++ if (buf == NULL || len <= 0) return 0;
++ memset(buf, 0, len);
+ #if MG_ENABLE_IPV6
+ is_v6 = sa->sa.sa_family == AF_INET6;
+ #else
+@@ -9321,36 +10082,51 @@ void mg_sock_addr_to_str(const union soc
+ }
+ }
+ if (inet_ntop(sa->sa.sa_family, addr, start, capacity) == NULL) {
+- *buf = '\0';
++ goto cleanup;
+ }
+ #elif defined(_WIN32) || MG_LWIP || (MG_NET_IF == MG_NET_IF_PIC32)
+ /* Only Windoze Vista (and newer) have inet_ntop() */
+- strncpy(buf, inet_ntoa(sa->sin.sin_addr), len);
++ char *addr_str = inet_ntoa(sa->sin.sin_addr);
++ if (addr_str != NULL) {
++ strncpy(buf, inet_ntoa(sa->sin.sin_addr), len - 1);
++ } else {
++ goto cleanup;
++ }
+ #else
+- inet_ntop(AF_INET, (void *) &sa->sin.sin_addr, buf, len);
++ if (inet_ntop(AF_INET, (void *) &sa->sin.sin_addr, buf, len - 1) == NULL) {
++ goto cleanup;
++ }
+ #endif
+ }
+ if (flags & MG_SOCK_STRINGIFY_PORT) {
+ int port = ntohs(sa->sin.sin_port);
+ if (flags & MG_SOCK_STRINGIFY_IP) {
+- snprintf(buf + strlen(buf), len - (strlen(buf) + 1), "%s:%d",
+- (is_v6 ? "]" : ""), port);
++ int buf_len = strlen(buf);
++ snprintf(buf + buf_len, len - (buf_len + 1), "%s:%d", (is_v6 ? "]" : ""),
++ port);
+ } else {
+ snprintf(buf, len, "%d", port);
+ }
+ }
++
++ return strlen(buf);
++
++cleanup:
++ *buf = '\0';
++ return 0;
+ }
+
+-void mg_conn_addr_to_str(struct mg_connection *nc, char *buf, size_t len,
+- int flags) {
++int mg_conn_addr_to_str(struct mg_connection *nc, char *buf, size_t len,
++ int flags) {
+ union socket_address sa;
+ memset(&sa, 0, sizeof(sa));
+ mg_if_get_conn_addr(nc, flags & MG_SOCK_STRINGIFY_REMOTE, &sa);
+- mg_sock_addr_to_str(&sa, buf, len, flags);
++ return mg_sock_addr_to_str(&sa, buf, len, flags);
+ }
+
+ #if MG_ENABLE_HEXDUMP
+-int mg_hexdump(const void *buf, int len, char *dst, int dst_len) {
++static int mg_hexdump_n(const void *buf, int len, char *dst, int dst_len,
++ int offset) {
+ const unsigned char *p = (const unsigned char *) buf;
+ char ascii[17] = "";
+ int i, idx, n = 0;
+@@ -9359,7 +10135,7 @@ int mg_hexdump(const void *buf, int len,
+ idx = i % 16;
+ if (idx == 0) {
+ if (i > 0) n += snprintf(dst + n, MAX(dst_len - n, 0), " %s\n", ascii);
+- n += snprintf(dst + n, MAX(dst_len - n, 0), "%04x ", i);
++ n += snprintf(dst + n, MAX(dst_len - n, 0), "%04x ", i + offset);
+ }
+ if (dst_len - n < 0) {
+ return n;
+@@ -9370,16 +10146,50 @@ int mg_hexdump(const void *buf, int len,
+ }
+
+ while (i++ % 16) n += snprintf(dst + n, MAX(dst_len - n, 0), "%s", " ");
+- n += snprintf(dst + n, MAX(dst_len - n, 0), " %s\n\n", ascii);
++ n += snprintf(dst + n, MAX(dst_len - n, 0), " %s\n", ascii);
+
+ return n;
+ }
+
++int mg_hexdump(const void *buf, int len, char *dst, int dst_len) {
++ return mg_hexdump_n(buf, len, dst, dst_len, 0);
++}
++
++void mg_hexdumpf(FILE *fp, const void *buf, int len) {
++ char tmp[80];
++ int offset = 0, n;
++ while (len > 0) {
++ n = (len < 16 ? len : 16);
++ mg_hexdump_n(((const char *) buf) + offset, n, tmp, sizeof(tmp), offset);
++ fputs(tmp, fp);
++ offset += n;
++ len -= n;
++ }
++}
++
+ void mg_hexdump_connection(struct mg_connection *nc, const char *path,
+ const void *buf, int num_bytes, int ev) {
+ FILE *fp = NULL;
+- char *hexbuf, src[60], dst[60];
+- int buf_size = num_bytes * 5 + 100;
++ char src[60], dst[60];
++ const char *tag = NULL;
++ switch (ev) {
++ case MG_EV_RECV:
++ tag = "<-";
++ break;
++ case MG_EV_SEND:
++ tag = "->";
++ break;
++ case MG_EV_ACCEPT:
++ tag = "<A";
++ break;
++ case MG_EV_CONNECT:
++ tag = "C>";
++ break;
++ case MG_EV_CLOSE:
++ tag = "XX";
++ break;
++ }
++ if (tag == NULL) return; /* Don't log MG_EV_TIMER, etc */
+
+ if (strcmp(path, "-") == 0) {
+ fp = stdout;
+@@ -9397,20 +10207,12 @@ void mg_hexdump_connection(struct mg_con
+ mg_conn_addr_to_str(nc, dst, sizeof(dst), MG_SOCK_STRINGIFY_IP |
+ MG_SOCK_STRINGIFY_PORT |
+ MG_SOCK_STRINGIFY_REMOTE);
+- fprintf(
+- fp, "%lu %p %s %s %s %d\n", (unsigned long) mg_time(), (void *) nc, src,
+- ev == MG_EV_RECV ? "<-" : ev == MG_EV_SEND
+- ? "->"
+- : ev == MG_EV_ACCEPT
+- ? "<A"
+- : ev == MG_EV_CONNECT ? "C>" : "XX",
+- dst, num_bytes);
+- if (num_bytes > 0 && (hexbuf = (char *) MG_MALLOC(buf_size)) != NULL) {
+- mg_hexdump(buf, num_bytes, hexbuf, buf_size);
+- fprintf(fp, "%s", hexbuf);
+- MG_FREE(hexbuf);
++ fprintf(fp, "%lu %p %s %s %s %d\n", (unsigned long) mg_time(), (void *) nc,
++ src, tag, dst, (int) num_bytes);
++ if (num_bytes > 0) {
++ mg_hexdumpf(fp, buf, num_bytes);
+ }
+- if (fp != stdin && fp != stdout) fclose(fp);
++ if (fp != stdout && fp != stderr) fclose(fp);
+ }
+ #endif
+
+@@ -9420,90 +10222,6 @@ int mg_is_big_endian(void) {
+ return ((char *) &n)[0] == 0;
+ }
+
+-const char *mg_next_comma_list_entry(const char *list, struct mg_str *val,
+- struct mg_str *eq_val) {
+- if (list == NULL || *list == '\0') {
+- /* End of the list */
+- list = NULL;
+- } else {
+- val->p = list;
+- if ((list = strchr(val->p, ',')) != NULL) {
+- /* Comma found. Store length and shift the list ptr */
+- val->len = list - val->p;
+- list++;
+- } else {
+- /* This value is the last one */
+- list = val->p + strlen(val->p);
+- val->len = list - val->p;
+- }
+-
+- if (eq_val != NULL) {
+- /* Value has form "x=y", adjust pointers and lengths */
+- /* so that val points to "x", and eq_val points to "y". */
+- eq_val->len = 0;
+- eq_val->p = (const char *) memchr(val->p, '=', val->len);
+- if (eq_val->p != NULL) {
+- eq_val->p++; /* Skip over '=' character */
+- eq_val->len = val->p + val->len - eq_val->p;
+- val->len = (eq_val->p - val->p) - 1;
+- }
+- }
+- }
+-
+- return list;
+-}
+-
+-int mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) {
+- const char *or_str;
+- size_t len, i = 0, j = 0;
+- int res;
+-
+- if ((or_str = (const char *) memchr(pattern.p, '|', pattern.len)) != NULL) {
+- struct mg_str pstr = {pattern.p, (size_t)(or_str - pattern.p)};
+- res = mg_match_prefix_n(pstr, str);
+- if (res > 0) return res;
+- pstr.p = or_str + 1;
+- pstr.len = (pattern.p + pattern.len) - (or_str + 1);
+- return mg_match_prefix_n(pstr, str);
+- }
+-
+- for (; i < pattern.len; i++, j++) {
+- if (pattern.p[i] == '?' && j != str.len) {
+- continue;
+- } else if (pattern.p[i] == '$') {
+- return j == str.len ? (int) j : -1;
+- } else if (pattern.p[i] == '*') {
+- i++;
+- if (pattern.p[i] == '*') {
+- i++;
+- len = str.len - j;
+- } else {
+- len = 0;
+- while (j + len != str.len && str.p[j + len] != '/') {
+- len++;
+- }
+- }
+- if (i == pattern.len) {
+- return j + len;
+- }
+- do {
+- const struct mg_str pstr = {pattern.p + i, pattern.len - i};
+- const struct mg_str sstr = {str.p + j + len, str.len - j - len};
+- res = mg_match_prefix_n(pstr, sstr);
+- } while (res == -1 && len-- > 0);
+- return res == -1 ? -1 : (int) (j + res + len);
+- } else if (lowercase(&pattern.p[i]) != lowercase(&str.p[j])) {
+- return -1;
+- }
+- }
+- return j;
+-}
+-
+-int mg_match_prefix(const char *pattern, int pattern_len, const char *str) {
+- const struct mg_str pstr = {pattern, (size_t) pattern_len};
+- return mg_match_prefix_n(pstr, mg_mk_str(str));
+-}
+-
+ DO_NOT_WARN_UNUSED MG_INTERNAL int mg_get_errno(void) {
+ #ifndef WINCE
+ return errno;
+@@ -9525,7 +10243,7 @@ void mg_mbuf_append_base64(struct mbuf *
+ cs_base64_finish(&ctx);
+ }
+
+-void mg_basic_auth_header(const char *user, const char *pass,
++void mg_basic_auth_header(const struct mg_str user, const struct mg_str pass,
+ struct mbuf *buf) {
+ const char *header_prefix = "Authorization: Basic ";
+ const char *header_suffix = "\r\n";
+@@ -9535,16 +10253,38 @@ void mg_basic_auth_header(const char *us
+
+ mbuf_append(buf, header_prefix, strlen(header_prefix));
+
+- cs_base64_update(&ctx, user, strlen(user));
+- if (pass != NULL) {
++ cs_base64_update(&ctx, user.p, user.len);
++ if (pass.len > 0) {
+ cs_base64_update(&ctx, ":", 1);
+- cs_base64_update(&ctx, pass, strlen(pass));
++ cs_base64_update(&ctx, pass.p, pass.len);
+ }
+ cs_base64_finish(&ctx);
+ mbuf_append(buf, header_suffix, strlen(header_suffix));
+ }
++
++struct mg_str mg_url_encode(const struct mg_str src) {
++ static const char *dont_escape = "._-$,;~()/";
++ static const char *hex = "0123456789abcdef";
++ size_t i = 0;
++ struct mbuf mb;
++ mbuf_init(&mb, src.len);
++
++ for (i = 0; i < src.len; i++) {
++ const unsigned char c = *((const unsigned char *) src.p + i);
++ if (isalnum(c) || strchr(dont_escape, c) != NULL) {
++ mbuf_append(&mb, &c, 1);
++ } else {
++ mbuf_append(&mb, "%", 1);
++ mbuf_append(&mb, &hex[c >> 4], 1);
++ mbuf_append(&mb, &hex[c & 15], 1);
++ }
++ }
++ mbuf_append(&mb, "", 1);
++ mbuf_trim(&mb);
++ return mg_mk_str_n(mb.buf, mb.len - 1);
++}
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/mqtt.c"
++#line 1 "mongoose/src/mg_mqtt.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -9555,8 +10295,8 @@ void mg_basic_auth_header(const char *us
+
+ #include <string.h>
+
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/mqtt.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_mqtt.h" */
+
+ static uint16_t getu16(const char *p) {
+ const uint8_t *up = (const uint8_t *) p;
+@@ -9571,22 +10311,29 @@ static const char *scanto(const char *p,
+
+ MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {
+ uint8_t header;
+- size_t len = 0;
++ size_t len = 0, len_len = 0;
++ const char *p, *end;
++ unsigned char lc = 0;
+ int cmd;
+- const char *p = &io->buf[1], *end;
+
+- if (io->len < 2) return -1;
++ if (io->len < 2) return MG_MQTT_ERROR_INCOMPLETE_MSG;
+ header = io->buf[0];
+ cmd = header >> 4;
+
+ /* decode mqtt variable length */
+- do {
+- len += (*p & 127) << 7 * (p - &io->buf[1]);
+- } while ((*p++ & 128) != 0 && ((size_t)(p - io->buf) <= io->len));
++ len = len_len = 0;
++ p = io->buf + 1;
++ while ((size_t)(p - io->buf) < io->len) {
++ lc = *((const unsigned char *) p++);
++ len += (lc & 0x7f) << 7 * len_len;
++ len_len++;
++ if (!(lc & 0x80)) break;
++ if (len_len > 4) return MG_MQTT_ERROR_MALFORMED_MSG;
++ }
+
+ end = p + len;
+- if (end > io->buf + io->len + 1) {
+- return -1;
++ if (lc & 0x80 || len > (io->len - (p - io->buf))) {
++ return MG_MQTT_ERROR_INCOMPLETE_MSG;
+ }
+
+ mm->cmd = cmd;
+@@ -9595,24 +10342,36 @@ MG_INTERNAL int parse_mqtt(struct mbuf *
+ switch (cmd) {
+ case MG_MQTT_CMD_CONNECT: {
+ p = scanto(p, &mm->protocol_name);
++ if (p > end - 4) return MG_MQTT_ERROR_MALFORMED_MSG;
+ mm->protocol_version = *(uint8_t *) p++;
+ mm->connect_flags = *(uint8_t *) p++;
+ mm->keep_alive_timer = getu16(p);
+ p += 2;
+- if (p < end) p = scanto(p, &mm->client_id);
+- if (p < end && (mm->connect_flags & MG_MQTT_HAS_WILL))
++ if (p >= end) return MG_MQTT_ERROR_MALFORMED_MSG;
++ p = scanto(p, &mm->client_id);
++ if (p > end) return MG_MQTT_ERROR_MALFORMED_MSG;
++ if (mm->connect_flags & MG_MQTT_HAS_WILL) {
++ if (p >= end) return MG_MQTT_ERROR_MALFORMED_MSG;
+ p = scanto(p, &mm->will_topic);
+- if (p < end && (mm->connect_flags & MG_MQTT_HAS_WILL))
++ }
++ if (mm->connect_flags & MG_MQTT_HAS_WILL) {
++ if (p >= end) return MG_MQTT_ERROR_MALFORMED_MSG;
+ p = scanto(p, &mm->will_message);
+- if (p < end && (mm->connect_flags & MG_MQTT_HAS_USER_NAME))
++ }
++ if (mm->connect_flags & MG_MQTT_HAS_USER_NAME) {
++ if (p >= end) return MG_MQTT_ERROR_MALFORMED_MSG;
+ p = scanto(p, &mm->user_name);
+- if (p < end && (mm->connect_flags & MG_MQTT_HAS_PASSWORD))
++ }
++ if (mm->connect_flags & MG_MQTT_HAS_PASSWORD) {
++ if (p >= end) return MG_MQTT_ERROR_MALFORMED_MSG;
+ p = scanto(p, &mm->password);
++ }
++ if (p != end) return MG_MQTT_ERROR_MALFORMED_MSG;
+
+ LOG(LL_DEBUG,
+ ("%d %2x %d proto [%.*s] client_id [%.*s] will_topic [%.*s] "
+ "will_msg [%.*s] user_name [%.*s] password [%.*s]",
+- len, (int) mm->connect_flags, (int) mm->keep_alive_timer,
++ (int) len, (int) mm->connect_flags, (int) mm->keep_alive_timer,
+ (int) mm->protocol_name.len, mm->protocol_name.p,
+ (int) mm->client_id.len, mm->client_id.p, (int) mm->will_topic.len,
+ mm->will_topic.p, (int) mm->will_message.len, mm->will_message.p,
+@@ -9621,6 +10380,7 @@ MG_INTERNAL int parse_mqtt(struct mbuf *
+ break;
+ }
+ case MG_MQTT_CMD_CONNACK:
++ if (end - p < 2) return MG_MQTT_ERROR_MALFORMED_MSG;
+ mm->connack_ret_code = p[1];
+ break;
+ case MG_MQTT_CMD_PUBACK:
+@@ -9631,17 +10391,19 @@ MG_INTERNAL int parse_mqtt(struct mbuf *
+ mm->message_id = getu16(p);
+ break;
+ case MG_MQTT_CMD_PUBLISH: {
+- if (MG_MQTT_GET_QOS(header) > 0) {
++ p = scanto(p, &mm->topic);
++ if (p > end) return MG_MQTT_ERROR_MALFORMED_MSG;
++ if (mm->qos > 0) {
++ if (end - p < 2) return MG_MQTT_ERROR_MALFORMED_MSG;
+ mm->message_id = getu16(p);
+ p += 2;
+ }
+- p = scanto(p, &mm->topic);
+-
+ mm->payload.p = p;
+ mm->payload.len = end - p;
+ break;
+ }
+ case MG_MQTT_CMD_SUBSCRIBE:
++ if (end - p < 2) return MG_MQTT_ERROR_MALFORMED_MSG;
+ mm->message_id = getu16(p);
+ p += 2;
+ /*
+@@ -9656,24 +10418,65 @@ MG_INTERNAL int parse_mqtt(struct mbuf *
+ break;
+ }
+
+- return end - io->buf;
++ mm->len = end - io->buf;
++ return mm->len;
+ }
+
+-static void mqtt_handler(struct mg_connection *nc, int ev, void *ev_data) {
+- int len;
++static void mqtt_handler(struct mg_connection *nc, int ev,
++ void *ev_data MG_UD_ARG(void *user_data)) {
+ struct mbuf *io = &nc->recv_mbuf;
+ struct mg_mqtt_message mm;
+ memset(&mm, 0, sizeof(mm));
+
+- nc->handler(nc, ev, ev_data);
++ nc->handler(nc, ev, ev_data MG_UD_ARG(user_data));
+
+ switch (ev) {
+- case MG_EV_RECV:
+- len = parse_mqtt(io, &mm);
+- if (len == -1) break; /* not fully buffered */
+- nc->handler(nc, MG_MQTT_EVENT_BASE + mm.cmd, &mm);
+- mbuf_remove(io, len);
++ case MG_EV_ACCEPT:
++ if (nc->proto_data == NULL) mg_set_protocol_mqtt(nc);
++ break;
++ case MG_EV_RECV: {
++ /* There can be multiple messages in the buffer, process them all. */
++ while (1) {
++ int len = parse_mqtt(io, &mm);
++ if (len < 0) {
++ if (len == MG_MQTT_ERROR_MALFORMED_MSG) {
++ /* Protocol error. */
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ } else if (len == MG_MQTT_ERROR_INCOMPLETE_MSG) {
++ /* Not fully buffered, let's check if we have a chance to get more
++ * data later */
++ if (nc->recv_mbuf_limit > 0 &&
++ nc->recv_mbuf.len >= nc->recv_mbuf_limit) {
++ LOG(LL_ERROR, ("%p recv buffer (%lu bytes) exceeds the limit "
++ "%lu bytes, and not drained, closing",
++ nc, (unsigned long) nc->recv_mbuf.len,
++ (unsigned long) nc->recv_mbuf_limit));
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ }
++ } else {
++ /* Should never be here */
++ LOG(LL_ERROR, ("%p invalid len: %d, closing", nc, len));
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ }
++ break;
++ }
++
++ nc->handler(nc, MG_MQTT_EVENT_BASE + mm.cmd, &mm MG_UD_ARG(user_data));
++ mbuf_remove(io, len);
++ }
+ break;
++ }
++ case MG_EV_POLL: {
++ struct mg_mqtt_proto_data *pd =
++ (struct mg_mqtt_proto_data *) nc->proto_data;
++ double now = mg_time();
++ if (pd->keep_alive > 0 && pd->last_control_time > 0 &&
++ (now - pd->last_control_time) > pd->keep_alive) {
++ LOG(LL_DEBUG, ("Send PINGREQ"));
++ mg_mqtt_ping(nc);
++ }
++ break;
++ }
+ }
+ }
+
+@@ -9681,12 +10484,63 @@ static void mg_mqtt_proto_data_destructo
+ MG_FREE(proto_data);
+ }
+
++int mg_mqtt_match_topic_expression(struct mg_str exp, struct mg_str topic) {
++ /* TODO(mkm): implement real matching */
++ if (memchr(exp.p, '#', exp.len)) {
++ /* exp `foo/#` will become `foo/` */
++ exp.len -= 1;
++ /*
++ * topic should be longer than the expression: e.g. topic `foo/bar` does
++ * match `foo/#`, but neither `foo` nor `foo/` do.
++ */
++ if (topic.len <= exp.len) {
++ return 0;
++ }
++
++ /* Truncate topic so that it'll pass the next length check */
++ topic.len = exp.len;
++ }
++ if (topic.len != exp.len) {
++ return 0;
++ }
++ return strncmp(topic.p, exp.p, exp.len) == 0;
++}
++
++int mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic) {
++ return mg_mqtt_match_topic_expression(mg_mk_str(exp), topic);
++}
++
+ void mg_set_protocol_mqtt(struct mg_connection *nc) {
+ nc->proto_handler = mqtt_handler;
+ nc->proto_data = MG_CALLOC(1, sizeof(struct mg_mqtt_proto_data));
+ nc->proto_data_destructor = mg_mqtt_proto_data_destructor;
+ }
+
++static void mg_mqtt_prepend_header(struct mg_connection *nc, uint8_t cmd,
++ uint8_t flags, size_t len) {
++ struct mg_mqtt_proto_data *pd = (struct mg_mqtt_proto_data *) nc->proto_data;
++ size_t off = nc->send_mbuf.len - len;
++ uint8_t header = cmd << 4 | (uint8_t) flags;
++
++ uint8_t buf[1 + sizeof(size_t)];
++ uint8_t *vlen = &buf[1];
++
++ assert(nc->send_mbuf.len >= len);
++
++ buf[0] = header;
++
++ /* mqtt variable length encoding */
++ do {
++ *vlen = len % 0x80;
++ len /= 0x80;
++ if (len > 0) *vlen |= 0x80;
++ vlen++;
++ } while (len > 0);
++
++ mbuf_insert(&nc->send_mbuf, off, buf, vlen - buf);
++ pd->last_control_time = mg_time();
++}
++
+ void mg_send_mqtt_handshake(struct mg_connection *nc, const char *client_id) {
+ static struct mg_send_mqtt_handshake_opts opts;
+ mg_send_mqtt_handshake_opt(nc, client_id, opts);
+@@ -9694,98 +10548,74 @@ void mg_send_mqtt_handshake(struct mg_co
+
+ void mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id,
+ struct mg_send_mqtt_handshake_opts opts) {
+- uint8_t header = MG_MQTT_CMD_CONNECT << 4;
+- uint8_t rem_len;
+- uint16_t keep_alive;
+- uint16_t len;
++ uint16_t hlen, nlen, rem_len = 0;
+ struct mg_mqtt_proto_data *pd = (struct mg_mqtt_proto_data *) nc->proto_data;
+
+- /*
+- * 9: version_header(len, magic_string, version_number), 1: flags, 2:
+- * keep-alive timer,
+- * 2: client_identifier_len, n: client_id
+- */
+- rem_len = 9 + 1 + 2 + 2 + (uint8_t) strlen(client_id);
++ mg_send(nc, "\00\04MQTT\04", 7);
++ rem_len += 7;
+
+ if (opts.user_name != NULL) {
+ opts.flags |= MG_MQTT_HAS_USER_NAME;
+- rem_len += (uint8_t) strlen(opts.user_name) + 2;
+ }
+ if (opts.password != NULL) {
+ opts.flags |= MG_MQTT_HAS_PASSWORD;
+- rem_len += (uint8_t) strlen(opts.password) + 2;
+ }
+ if (opts.will_topic != NULL && opts.will_message != NULL) {
+ opts.flags |= MG_MQTT_HAS_WILL;
+- rem_len += (uint8_t) strlen(opts.will_topic) + 2;
+- rem_len += (uint8_t) strlen(opts.will_message) + 2;
+ }
+-
+- mg_send(nc, &header, 1);
+- mg_send(nc, &rem_len, 1);
+- mg_send(nc, "\00\06MQIsdp\03", 9);
+- mg_send(nc, &opts.flags, 1);
+-
+ if (opts.keep_alive == 0) {
+ opts.keep_alive = 60;
+ }
+
+- keep_alive = htons(opts.keep_alive);
+- mg_send(nc, &keep_alive, 2);
++ mg_send(nc, &opts.flags, 1);
++ rem_len += 1;
+
+- len = htons((uint16_t) strlen(client_id));
+- mg_send(nc, &len, 2);
+- mg_send(nc, client_id, strlen(client_id));
++ nlen = htons(opts.keep_alive);
++ mg_send(nc, &nlen, 2);
++ rem_len += 2;
++
++ hlen = strlen(client_id);
++ nlen = htons((uint16_t) hlen);
++ mg_send(nc, &nlen, 2);
++ mg_send(nc, client_id, hlen);
++ rem_len += 2 + hlen;
+
+ if (opts.flags & MG_MQTT_HAS_WILL) {
+- len = htons((uint16_t) strlen(opts.will_topic));
+- mg_send(nc, &len, 2);
+- mg_send(nc, opts.will_topic, strlen(opts.will_topic));
+-
+- len = htons((uint16_t) strlen(opts.will_message));
+- mg_send(nc, &len, 2);
+- mg_send(nc, opts.will_message, strlen(opts.will_message));
++ hlen = strlen(opts.will_topic);
++ nlen = htons((uint16_t) hlen);
++ mg_send(nc, &nlen, 2);
++ mg_send(nc, opts.will_topic, hlen);
++ rem_len += 2 + hlen;
++
++ hlen = strlen(opts.will_message);
++ nlen = htons((uint16_t) hlen);
++ mg_send(nc, &nlen, 2);
++ mg_send(nc, opts.will_message, hlen);
++ rem_len += 2 + hlen;
+ }
+
+ if (opts.flags & MG_MQTT_HAS_USER_NAME) {
+- len = htons((uint16_t) strlen(opts.user_name));
+- mg_send(nc, &len, 2);
+- mg_send(nc, opts.user_name, strlen(opts.user_name));
++ hlen = strlen(opts.user_name);
++ nlen = htons((uint16_t) hlen);
++ mg_send(nc, &nlen, 2);
++ mg_send(nc, opts.user_name, hlen);
++ rem_len += 2 + hlen;
+ }
+ if (opts.flags & MG_MQTT_HAS_PASSWORD) {
+- len = htons((uint16_t) strlen(opts.password));
+- mg_send(nc, &len, 2);
+- mg_send(nc, opts.password, strlen(opts.password));
++ hlen = strlen(opts.password);
++ nlen = htons((uint16_t) hlen);
++ mg_send(nc, &nlen, 2);
++ mg_send(nc, opts.password, hlen);
++ rem_len += 2 + hlen;
+ }
+
++ mg_mqtt_prepend_header(nc, MG_MQTT_CMD_CONNECT, 0, rem_len);
++
+ if (pd != NULL) {
+ pd->keep_alive = opts.keep_alive;
+ }
+ }
+
+-static void mg_mqtt_prepend_header(struct mg_connection *nc, uint8_t cmd,
+- uint8_t flags, size_t len) {
+- size_t off = nc->send_mbuf.len - len;
+- uint8_t header = cmd << 4 | (uint8_t) flags;
+-
+- uint8_t buf[1 + sizeof(size_t)];
+- uint8_t *vlen = &buf[1];
+-
+- assert(nc->send_mbuf.len >= len);
+-
+- buf[0] = header;
+-
+- /* mqtt variable length encoding */
+- do {
+- *vlen = len % 0x80;
+- len /= 0x80;
+- if (len > 0) *vlen |= 0x80;
+- vlen++;
+- } while (len > 0);
+-
+- mbuf_insert(&nc->send_mbuf, off, buf, vlen - buf);
+-}
+-
+ void mg_mqtt_publish(struct mg_connection *nc, const char *topic,
+ uint16_t message_id, int flags, const void *data,
+ size_t len) {
+@@ -9828,15 +10658,16 @@ void mg_mqtt_subscribe(struct mg_connect
+ int mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg,
+ struct mg_str *topic, uint8_t *qos, int pos) {
+ unsigned char *buf = (unsigned char *) msg->payload.p + pos;
++ int new_pos;
+
+- if ((size_t) pos >= msg->payload.len) {
+- return -1;
+- }
++ if ((size_t) pos >= msg->payload.len) return -1;
+
+ topic->len = buf[0] << 8 | buf[1];
+ topic->p = (char *) buf + 2;
++ new_pos = pos + 2 + topic->len + 1;
++ if ((size_t) new_pos > msg->payload.len) return -1;
+ *qos = buf[2 + topic->len];
+- return pos + 2 + topic->len + 1;
++ return new_pos;
+ }
+
+ void mg_mqtt_unsubscribe(struct mg_connection *nc, char **topics,
+@@ -9872,8 +10703,9 @@ void mg_mqtt_connack(struct mg_connectio
+ static void mg_send_mqtt_short_command(struct mg_connection *nc, uint8_t cmd,
+ uint16_t message_id) {
+ uint16_t message_id_net = htons(message_id);
++ uint8_t flags = (cmd == MG_MQTT_CMD_PUBREL ? 2 : 0);
+ mg_send(nc, &message_id_net, 2);
+- mg_mqtt_prepend_header(nc, cmd, MG_MQTT_QOS(1), 2);
++ mg_mqtt_prepend_header(nc, cmd, flags, 2 /* len */);
+ }
+
+ void mg_mqtt_puback(struct mg_connection *nc, uint16_t message_id) {
+@@ -9921,15 +10753,15 @@ void mg_mqtt_disconnect(struct mg_connec
+
+ #endif /* MG_ENABLE_MQTT */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/mqtt_server.c"
++#line 1 "mongoose/src/mg_mqtt_server.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/mqtt-server.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_mqtt_server.h" */
+
+ #if MG_ENABLE_MQTT_BROKER
+
+@@ -9971,7 +10803,8 @@ void mg_mqtt_broker_init(struct mg_mqtt_
+
+ static void mg_mqtt_broker_handle_connect(struct mg_mqtt_broker *brk,
+ struct mg_connection *nc) {
+- struct mg_mqtt_session *s = (struct mg_mqtt_session *) calloc(1, sizeof *s);
++ struct mg_mqtt_session *s =
++ (struct mg_mqtt_session *) MG_CALLOC(1, sizeof *s);
+ if (s == NULL) {
+ /* LCOV_EXCL_START */
+ mg_mqtt_connack(nc, MG_EV_MQTT_CONNACK_SERVER_UNAVAILABLE);
+@@ -9982,8 +10815,7 @@ static void mg_mqtt_broker_handle_connec
+ /* TODO(mkm): check header (magic and version) */
+
+ mg_mqtt_session_init(brk, s, nc);
+- s->user_data = nc->user_data;
+- nc->user_data = s;
++ nc->priv_2 = s;
+ mg_mqtt_add_session(s);
+
+ mg_mqtt_connack(nc, MG_EV_MQTT_CONNACK_ACCEPTED);
+@@ -9991,9 +10823,9 @@ static void mg_mqtt_broker_handle_connec
+
+ static void mg_mqtt_broker_handle_subscribe(struct mg_connection *nc,
+ struct mg_mqtt_message *msg) {
+- struct mg_mqtt_session *ss = (struct mg_mqtt_session *) nc->user_data;
+- uint8_t qoss[512];
+- size_t qoss_len = 0;
++ struct mg_mqtt_session *ss = (struct mg_mqtt_session *) nc->priv_2;
++ uint8_t qoss[MG_MQTT_MAX_SESSION_SUBSCRIPTIONS];
++ size_t num_subs = 0;
+ struct mg_str topic;
+ uint8_t qos;
+ int pos;
+@@ -10001,41 +10833,42 @@ static void mg_mqtt_broker_handle_subscr
+
+ for (pos = 0;
+ (pos = mg_mqtt_next_subscribe_topic(msg, &topic, &qos, pos)) != -1;) {
+- qoss[qoss_len++] = qos;
++ if (num_subs >= sizeof(MG_MQTT_MAX_SESSION_SUBSCRIPTIONS) ||
++ (ss->num_subscriptions + num_subs >=
++ MG_MQTT_MAX_SESSION_SUBSCRIPTIONS)) {
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ return;
++ }
++ qoss[num_subs++] = qos;
+ }
+
+- ss->subscriptions = (struct mg_mqtt_topic_expression *) realloc(
+- ss->subscriptions, sizeof(*ss->subscriptions) * qoss_len);
+- for (pos = 0;
+- (pos = mg_mqtt_next_subscribe_topic(msg, &topic, &qos, pos)) != -1;
+- ss->num_subscriptions++) {
+- te = &ss->subscriptions[ss->num_subscriptions];
+- te->topic = (char *) malloc(topic.len + 1);
+- te->qos = qos;
+- strncpy((char *) te->topic, topic.p, topic.len + 1);
++ if (num_subs > 0) {
++ te = (struct mg_mqtt_topic_expression *) MG_REALLOC(
++ ss->subscriptions,
++ sizeof(*ss->subscriptions) * (ss->num_subscriptions + num_subs));
++ if (te == NULL) {
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ return;
++ }
++ ss->subscriptions = te;
++ for (pos = 0;
++ pos < (int) msg->payload.len &&
++ (pos = mg_mqtt_next_subscribe_topic(msg, &topic, &qos, pos)) != -1;
++ ss->num_subscriptions++) {
++ te = &ss->subscriptions[ss->num_subscriptions];
++ te->topic = (char *) MG_MALLOC(topic.len + 1);
++ te->qos = qos;
++ memcpy((char *) te->topic, topic.p, topic.len);
++ ((char *) te->topic)[topic.len] = '\0';
++ }
+ }
+
+- mg_mqtt_suback(nc, qoss, qoss_len, msg->message_id);
+-}
+-
+-/*
+- * Matches a topic against a topic expression
+- *
+- * See http://goo.gl/iWk21X
+- *
+- * Returns 1 if it matches; 0 otherwise.
+- */
+-static int mg_mqtt_match_topic_expression(const char *exp,
+- const struct mg_str *topic) {
+- /* TODO(mkm): implement real matching */
+- size_t len = strlen(exp);
+- if (strchr(exp, '#')) {
+- len -= 2;
+- if (topic->len < len) {
+- len = topic->len;
+- }
++ if (pos == (int) msg->payload.len) {
++ mg_mqtt_suback(nc, qoss, num_subs, msg->message_id);
++ } else {
++ /* We did not fully parse the payload, something must be wrong. */
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ }
+- return strncmp(topic->p, exp, len) == 0;
+ }
+
+ static void mg_mqtt_broker_handle_publish(struct mg_mqtt_broker *brk,
+@@ -10045,8 +10878,8 @@ static void mg_mqtt_broker_handle_publis
+
+ for (s = mg_mqtt_next(brk, NULL); s != NULL; s = mg_mqtt_next(brk, s)) {
+ for (i = 0; i < s->num_subscriptions; i++) {
+- if (mg_mqtt_match_topic_expression(s->subscriptions[i].topic,
+- &msg->topic)) {
++ if (mg_mqtt_vmatch_topic_expression(s->subscriptions[i].topic,
++ msg->topic)) {
+ char buf[100], *p = buf;
+ mg_asprintf(&p, sizeof(buf), "%.*s", (int) msg->topic.len,
+ msg->topic.p);
+@@ -10068,28 +10901,43 @@ void mg_mqtt_broker(struct mg_connection
+ struct mg_mqtt_broker *brk;
+
+ if (nc->listener) {
+- brk = (struct mg_mqtt_broker *) nc->listener->user_data;
++ brk = (struct mg_mqtt_broker *) nc->listener->priv_2;
+ } else {
+- brk = (struct mg_mqtt_broker *) nc->user_data;
++ brk = (struct mg_mqtt_broker *) nc->priv_2;
+ }
+
+ switch (ev) {
+ case MG_EV_ACCEPT:
+- mg_set_protocol_mqtt(nc);
+- nc->user_data = NULL; /* Clear up the inherited pointer to broker */
++ if (nc->proto_data == NULL) mg_set_protocol_mqtt(nc);
++ nc->priv_2 = NULL; /* Clear up the inherited pointer to broker */
+ break;
+ case MG_EV_MQTT_CONNECT:
+- mg_mqtt_broker_handle_connect(brk, nc);
++ if (nc->priv_2 == NULL) {
++ mg_mqtt_broker_handle_connect(brk, nc);
++ } else {
++ /* Repeated CONNECT */
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ }
+ break;
+ case MG_EV_MQTT_SUBSCRIBE:
+- mg_mqtt_broker_handle_subscribe(nc, msg);
++ if (nc->priv_2 != NULL) {
++ mg_mqtt_broker_handle_subscribe(nc, msg);
++ } else {
++ /* Subscribe before CONNECT */
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ }
+ break;
+ case MG_EV_MQTT_PUBLISH:
+- mg_mqtt_broker_handle_publish(brk, msg);
++ if (nc->priv_2 != NULL) {
++ mg_mqtt_broker_handle_publish(brk, msg);
++ } else {
++ /* Publish before CONNECT */
++ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ }
+ break;
+ case MG_EV_CLOSE:
+- if (nc->listener && nc->user_data != NULL) {
+- mg_mqtt_close_session((struct mg_mqtt_session *) nc->user_data);
++ if (nc->listener && nc->priv_2 != NULL) {
++ mg_mqtt_close_session((struct mg_mqtt_session *) nc->priv_2);
+ }
+ break;
+ }
+@@ -10102,7 +10950,7 @@ struct mg_mqtt_session *mg_mqtt_next(str
+
+ #endif /* MG_ENABLE_MQTT_BROKER */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/dns.c"
++#line 1 "mongoose/src/mg_dns.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -10111,8 +10959,8 @@ struct mg_mqtt_session *mg_mqtt_next(str
+
+ #if MG_ENABLE_DNS
+
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/dns.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_dns.h" */
+
+ static int mg_dns_tid = 0xa0;
+
+@@ -10390,7 +11238,7 @@ int mg_parse_dns(const char *buf, int le
+
+ size_t mg_dns_uncompress_name(struct mg_dns_message *msg, struct mg_str *name,
+ char *dst, int dst_len) {
+- int chunk_len;
++ int chunk_len, num_ptrs = 0;
+ char *old_dst = dst;
+ const unsigned char *data = (unsigned char *) name->p;
+ const unsigned char *end = (unsigned char *) msg->pkt.p + msg->pkt.len;
+@@ -10405,14 +11253,21 @@ size_t mg_dns_uncompress_name(struct mg_
+ return 0;
+ }
+
+- if (chunk_len & 0xc0) {
++ if ((chunk_len & 0xc0) == 0xc0) {
+ uint16_t off = (data[-1] & (~0xc0)) << 8 | data[0];
+ if (off >= msg->pkt.len) {
+ return 0;
+ }
++ /* Basic circular loop avoidance: allow up to 16 pointer hops. */
++ if (++num_ptrs > 15) {
++ return 0;
++ }
+ data = (unsigned char *) msg->pkt.p + off;
+ continue;
+ }
++ if (chunk_len > 63) {
++ return 0;
++ }
+ if (chunk_len > leeway) {
+ chunk_len = leeway;
+ }
+@@ -10437,12 +11292,13 @@ size_t mg_dns_uncompress_name(struct mg_
+ return dst - old_dst;
+ }
+
+-static void dns_handler(struct mg_connection *nc, int ev, void *ev_data) {
++static void dns_handler(struct mg_connection *nc, int ev,
++ void *ev_data MG_UD_ARG(void *user_data)) {
+ struct mbuf *io = &nc->recv_mbuf;
+ struct mg_dns_message msg;
+
+ /* Pass low-level events to the user handler */
+- nc->handler(nc, ev, ev_data);
++ nc->handler(nc, ev, ev_data MG_UD_ARG(user_data));
+
+ switch (ev) {
+ case MG_EV_RECV:
+@@ -10461,7 +11317,7 @@ static void dns_handler(struct mg_connec
+ mg_send(nc, io->buf, io->len);
+ } else {
+ /* Call user handler with parsed message */
+- nc->handler(nc, MG_DNS_MESSAGE, &msg);
++ nc->handler(nc, MG_DNS_MESSAGE, &msg MG_UD_ARG(user_data));
+ }
+ mbuf_remove(io, io->len);
+ break;
+@@ -10474,7 +11330,7 @@ void mg_set_protocol_dns(struct mg_conne
+
+ #endif /* MG_ENABLE_DNS */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/dns_server.c"
++#line 1 "mongoose/src/mg_dns_server.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -10483,8 +11339,8 @@ void mg_set_protocol_dns(struct mg_conne
+
+ #if MG_ENABLE_DNS_SERVER
+
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/dns-server.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "dns-server.h" */
+
+ struct mg_dns_reply mg_dns_create_reply(struct mbuf *io,
+ struct mg_dns_message *msg) {
+@@ -10548,7 +11404,7 @@ int mg_dns_reply_record(struct mg_dns_re
+
+ #endif /* MG_ENABLE_DNS_SERVER */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/resolv.c"
++#line 1 "mongoose/src/mg_resolv.c"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -10557,17 +11413,13 @@ int mg_dns_reply_record(struct mg_dns_re
+
+ #if MG_ENABLE_ASYNC_RESOLVER
+
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/resolv.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_resolv.h" */
+
+ #ifndef MG_DEFAULT_NAMESERVER
+ #define MG_DEFAULT_NAMESERVER "8.8.8.8"
+ #endif
+
+-static const char *mg_default_dns_server = "udp://" MG_DEFAULT_NAMESERVER ":53";
+-
+-MG_INTERNAL char mg_dns_server[256];
+-
+ struct mg_resolve_async_request {
+ char name[1024];
+ int query;
+@@ -10609,10 +11461,12 @@ static int mg_get_ip_address_of_nameserv
+ break;
+ }
+ if (RegOpenKeyExW(hKey, subkey, 0, KEY_READ, &hSub) == ERROR_SUCCESS &&
+- (RegQueryValueExW(hSub, L"NameServer", 0, &type, (void *) value,
+- &len) == ERROR_SUCCESS ||
+- RegQueryValueExW(hSub, L"DhcpNameServer", 0, &type, (void *) value,
+- &len) == ERROR_SUCCESS)) {
++ ((RegQueryValueExW(hSub, L"NameServer", 0, &type, (void *) value,
++ &len) == ERROR_SUCCESS &&
++ value[0] != '\0') ||
++ (RegQueryValueExW(hSub, L"DhcpNameServer", 0, &type, (void *) value,
++ &len) == ERROR_SUCCESS &&
++ value[0] != '\0'))) {
+ /*
+ * See https://github.com/cesanta/mongoose/issues/176
+ * The value taken from the registry can be empty, a single
+@@ -10621,13 +11475,11 @@ static int mg_get_ip_address_of_nameserv
+ * If it's multiple IP addresses, take the first one.
+ */
+ wchar_t *comma = wcschr(value, ',');
+- if (value[0] == '\0') {
+- continue;
+- }
+ if (comma != NULL) {
+ *comma = '\0';
+ }
+- snprintf(name, name_len, "udp://%S:53", value);
++ /* %S will convert wchar_t -> char */
++ snprintf(name, name_len, "%S", value);
+ ret = 0;
+ RegCloseKey(hSub);
+ break;
+@@ -10635,18 +11487,18 @@ static int mg_get_ip_address_of_nameserv
+ }
+ RegCloseKey(hKey);
+ }
+-#elif MG_ENABLE_FILESYSTEM
++#elif MG_ENABLE_FILESYSTEM && defined(MG_RESOLV_CONF_FILE_NAME)
+ FILE *fp;
+ char line[512];
+
+- if ((fp = mg_fopen("/etc/resolv.conf", "r")) == NULL) {
++ if ((fp = mg_fopen(MG_RESOLV_CONF_FILE_NAME, "r")) == NULL) {
+ ret = -1;
+ } else {
+ /* Try to figure out what nameserver to use */
+ for (ret = -1; fgets(line, sizeof(line), fp) != NULL;) {
+ unsigned int a, b, c, d;
+ if (sscanf(line, "nameserver %u.%u.%u.%u", &a, &b, &c, &d) == 4) {
+- snprintf(name, name_len, "udp://%u.%u.%u.%u:53", a, b, c, d);
++ snprintf(name, name_len, "%u.%u.%u.%u", a, b, c, d);
+ ret = 0;
+ break;
+ }
+@@ -10654,14 +11506,14 @@ static int mg_get_ip_address_of_nameserv
+ (void) fclose(fp);
+ }
+ #else
+- snprintf(name, name_len, "%s", mg_default_dns_server);
++ snprintf(name, name_len, "%s", MG_DEFAULT_NAMESERVER);
+ #endif /* _WIN32 */
+
+ return ret;
+ }
+
+ int mg_resolve_from_hosts_file(const char *name, union socket_address *usa) {
+-#if MG_ENABLE_FILESYSTEM
++#if MG_ENABLE_FILESYSTEM && defined(MG_HOSTS_FILE_NAME)
+ /* TODO(mkm) cache /etc/hosts */
+ FILE *fp;
+ char line[1024];
+@@ -10670,7 +11522,7 @@ int mg_resolve_from_hosts_file(const cha
+ unsigned int a, b, c, d;
+ int len = 0;
+
+- if ((fp = mg_fopen("/etc/hosts", "r")) == NULL) {
++ if ((fp = mg_fopen(MG_HOSTS_FILE_NAME, "r")) == NULL) {
+ return -1;
+ }
+
+@@ -10699,15 +11551,19 @@ int mg_resolve_from_hosts_file(const cha
+ return -1;
+ }
+
+-static void mg_resolve_async_eh(struct mg_connection *nc, int ev, void *data) {
++static void mg_resolve_async_eh(struct mg_connection *nc, int ev,
++ void *data MG_UD_ARG(void *user_data)) {
+ time_t now = (time_t) mg_time();
+ struct mg_resolve_async_request *req;
+ struct mg_dns_message *msg;
+ int first = 0;
++#if !MG_ENABLE_CALLBACK_USERDATA
++ void *user_data = nc->user_data;
++#endif
+
+- DBG(("ev=%d user_data=%p", ev, nc->user_data));
++ if (ev != MG_EV_POLL) DBG(("ev=%d user_data=%p", ev, user_data));
+
+- req = (struct mg_resolve_async_request *) nc->user_data;
++ req = (struct mg_resolve_async_request *) user_data;
+
+ if (req == NULL) {
+ return;
+@@ -10758,6 +11614,11 @@ static void mg_resolve_async_eh(struct m
+ case MG_EV_CLOSE:
+ /* If we got here with request still not done, fire an error callback. */
+ if (req != NULL) {
++ char addr[32];
++ mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), MG_SOCK_STRINGIFY_IP);
++#ifdef MG_LOG_DNS_FAILURES
++ LOG(LL_ERROR, ("Failed to resolve '%s', server %s", req->name, addr));
++#endif
+ req->callback(NULL, req->data, req->err);
+ nc->user_data = NULL;
+ MG_FREE(req);
+@@ -10778,7 +11639,12 @@ int mg_resolve_async_opt(struct mg_mgr *
+ struct mg_resolve_async_opts opts) {
+ struct mg_resolve_async_request *req;
+ struct mg_connection *dns_nc;
+- const char *nameserver = opts.nameserver_url;
++ const char *nameserver = opts.nameserver;
++ char dns_server_buff[17], nameserver_url[26];
++
++ if (nameserver == NULL) {
++ nameserver = mgr->nameserver;
++ }
+
+ DBG(("%s %d %p", name, query, opts.dns_conn));
+
+@@ -10789,6 +11655,8 @@ int mg_resolve_async_opt(struct mg_mgr *
+ }
+
+ strncpy(req->name, name, sizeof(req->name));
++ req->name[sizeof(req->name) - 1] = '\0';
++
+ req->query = query;
+ req->callback = cb;
+ req->data = data;
+@@ -10797,19 +11665,20 @@ int mg_resolve_async_opt(struct mg_mgr *
+ req->timeout = opts.timeout ? opts.timeout : 5;
+
+ /* Lazily initialize dns server */
+- if (nameserver == NULL && mg_dns_server[0] == '\0' &&
+- mg_get_ip_address_of_nameserver(mg_dns_server, sizeof(mg_dns_server)) ==
+- -1) {
+- strncpy(mg_dns_server, mg_default_dns_server, sizeof(mg_dns_server));
+- }
+-
+ if (nameserver == NULL) {
+- nameserver = mg_dns_server;
++ if (mg_get_ip_address_of_nameserver(dns_server_buff,
++ sizeof(dns_server_buff)) != -1) {
++ nameserver = dns_server_buff;
++ } else {
++ nameserver = MG_DEFAULT_NAMESERVER;
++ }
+ }
+
+- dns_nc = mg_connect(mgr, nameserver, mg_resolve_async_eh);
++ snprintf(nameserver_url, sizeof(nameserver_url), "udp://%s:53", nameserver);
++
++ dns_nc = mg_connect(mgr, nameserver_url, MG_CB(mg_resolve_async_eh, NULL));
+ if (dns_nc == NULL) {
+- free(req);
++ MG_FREE(req);
+ return -1;
+ }
+ dns_nc->user_data = req;
+@@ -10820,9 +11689,17 @@ int mg_resolve_async_opt(struct mg_mgr *
+ return 0;
+ }
+
++void mg_set_nameserver(struct mg_mgr *mgr, const char *nameserver) {
++ MG_FREE((char *) mgr->nameserver);
++ mgr->nameserver = NULL;
++ if (nameserver != NULL) {
++ mgr->nameserver = strdup(nameserver);
++ }
++}
++
+ #endif /* MG_ENABLE_ASYNC_RESOLVER */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/coap.c"
++#line 1 "mongoose/src/mg_coap.c"
+ #endif
+ /*
+ * Copyright (c) 2015 Cesanta Software Limited
+@@ -10841,8 +11718,8 @@ int mg_resolve_async_opt(struct mg_mgr *
+ * license, as set out in <https://www.cesanta.com/license>.
+ */
+
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/coap.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_coap.h" */
+
+ #if MG_ENABLE_COAP
+
+@@ -11285,7 +12162,7 @@ uint32_t mg_coap_compose(struct mg_coap_
+
+ /* saving previous lenght to handle non-empty mbuf */
+ prev_io_len = io->len;
+- mbuf_append(io, NULL, packet_size);
++ if (mbuf_append(io, NULL, packet_size) == 0) return MG_COAP_ERROR;
+ ptr = io->buf + prev_io_len;
+
+ /*
+@@ -11369,14 +12246,15 @@ uint32_t mg_coap_send_ack(struct mg_conn
+ return mg_coap_send_message(nc, &cm);
+ }
+
+-static void coap_handler(struct mg_connection *nc, int ev, void *ev_data) {
++static void coap_handler(struct mg_connection *nc, int ev,
++ void *ev_data MG_UD_ARG(void *user_data)) {
+ struct mbuf *io = &nc->recv_mbuf;
+ struct mg_coap_message cm;
+ uint32_t parse_res;
+
+ memset(&cm, 0, sizeof(cm));
+
+- nc->handler(nc, ev, ev_data);
++ nc->handler(nc, ev, ev_data MG_UD_ARG(user_data));
+
+ switch (ev) {
+ case MG_EV_RECV:
+@@ -11389,7 +12267,8 @@ static void coap_handler(struct mg_conne
+ */
+ cm.flags |= MG_COAP_FORMAT_ERROR; /* LCOV_EXCL_LINE */
+ } /* LCOV_EXCL_LINE */
+- nc->handler(nc, MG_COAP_EVENT_BASE + cm.msg_type, &cm);
++ nc->handler(nc, MG_COAP_EVENT_BASE + cm.msg_type,
++ &cm MG_UD_ARG(user_data));
+ }
+
+ mg_coap_free_options(&cm);
+@@ -11420,316 +12299,16 @@ int mg_set_protocol_coap(struct mg_conne
+
+ #endif /* MG_ENABLE_COAP */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/tun.c"
+-#endif
+-/*
+- * Copyright (c) 2014 Cesanta Software Limited
+- * All rights reserved
+- */
+-
+-#if MG_ENABLE_TUN
+-
+-/* Amalgamated: #include "common/cs_dbg.h" */
+-/* Amalgamated: #include "mongoose/src/http.h" */
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/net.h" */
+-/* Amalgamated: #include "mongoose/src/net_if_tun.h" */
+-/* Amalgamated: #include "mongoose/src/tun.h" */
+-/* Amalgamated: #include "mongoose/src/util.h" */
+-
+-static void mg_tun_reconnect(struct mg_tun_client *client, int timeout);
+-
+-static void mg_tun_init_client(struct mg_tun_client *client, struct mg_mgr *mgr,
+- struct mg_iface *iface, const char *dispatcher,
+- struct mg_tun_ssl_opts ssl) {
+- client->mgr = mgr;
+- client->iface = iface;
+- client->disp_url = dispatcher;
+- client->last_stream_id = 0;
+- client->ssl = ssl;
+-
+- client->disp = NULL; /* will be set by mg_tun_reconnect */
+- client->listener = NULL; /* will be set by mg_do_bind */
+- client->reconnect = NULL; /* will be set by mg_tun_reconnect */
+-}
+-
+-void mg_tun_log_frame(struct mg_tun_frame *frame) {
+- LOG(LL_DEBUG, ("Got TUN frame: type=0x%x, flags=0x%x stream_id=0x%lx, "
+- "len=%zu",
+- frame->type, frame->flags, frame->stream_id, frame->body.len));
+-#if MG_ENABLE_HEXDUMP
+- {
+- char hex[512];
+- mg_hexdump(frame->body.p, frame->body.len, hex, sizeof(hex) - 1);
+- hex[sizeof(hex) - 1] = '\0';
+- LOG(LL_DEBUG, ("body:\n%s", hex));
+- }
+-#else
+- LOG(LL_DEBUG, ("body: '%.*s'", (int) frame->body.len, frame->body.p));
+-#endif
+-}
+-
+-static void mg_tun_close_all(struct mg_tun_client *client) {
+- struct mg_connection *nc;
+- for (nc = client->mgr->active_connections; nc != NULL; nc = nc->next) {
+- if (nc->iface == client->iface && !(nc->flags & MG_F_LISTENING)) {
+- LOG(LL_DEBUG, ("Closing tunneled connection %p", nc));
+- nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+- /* mg_close_conn(nc); */
+- }
+- }
+-}
+-
+-static void mg_tun_client_handler(struct mg_connection *nc, int ev,
+- void *ev_data) {
+- struct mg_tun_client *client = (struct mg_tun_client *) nc->user_data;
+-
+- switch (ev) {
+- case MG_EV_CONNECT: {
+- int err = *(int *) ev_data;
+-
+- if (err) {
+- LOG(LL_ERROR, ("Cannot connect to the tunnel dispatcher: %d", err));
+- } else {
+- LOG(LL_INFO, ("Connected to the tunnel dispatcher"));
+- }
+- break;
+- }
+- case MG_EV_HTTP_REPLY: {
+- struct http_message *hm = (struct http_message *) ev_data;
+-
+- if (hm->resp_code != 200) {
+- LOG(LL_ERROR,
+- ("Tunnel dispatcher reply non-OK status code %d", hm->resp_code));
+- }
+- break;
+- }
+- case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
+- LOG(LL_INFO, ("Tunnel dispatcher handshake done"));
+- break;
+- }
+- case MG_EV_WEBSOCKET_FRAME: {
+- struct websocket_message *wm = (struct websocket_message *) ev_data;
+- struct mg_connection *tc;
+- struct mg_tun_frame frame;
+-
+- if (mg_tun_parse_frame(wm->data, wm->size, &frame) == -1) {
+- LOG(LL_ERROR, ("Got invalid tun frame dropping", wm->size));
+- break;
+- }
+-
+- mg_tun_log_frame(&frame);
+-
+- tc = mg_tun_if_find_conn(client, frame.stream_id);
+- if (tc == NULL) {
+- if (frame.body.len > 0) {
+- LOG(LL_DEBUG, ("Got frame after receiving end has been closed"));
+- }
+- break;
+- }
+- if (frame.body.len > 0) {
+- mg_if_recv_tcp_cb(tc, (void *) frame.body.p, frame.body.len,
+- 0 /* own */);
+- }
+- if (frame.flags & MG_TUN_F_END_STREAM) {
+- LOG(LL_DEBUG, ("Closing tunneled connection because got end of stream "
+- "from other end"));
+- tc->flags |= MG_F_CLOSE_IMMEDIATELY;
+- mg_close_conn(tc);
+- }
+- break;
+- }
+- case MG_EV_CLOSE: {
+- LOG(LL_DEBUG, ("Closing all tunneled connections"));
+- /*
+- * The client might have been already freed when the listening socket is
+- * closed.
+- */
+- if (client != NULL) {
+- mg_tun_close_all(client);
+- client->disp = NULL;
+- LOG(LL_INFO, ("Dispatcher connection is no more, reconnecting"));
+- /* TODO(mkm): implement exp back off */
+- mg_tun_reconnect(client, MG_TUN_RECONNECT_INTERVAL);
+- }
+- break;
+- }
+- default:
+- break;
+- }
+-}
+-
+-static void mg_tun_do_reconnect(struct mg_tun_client *client) {
+- struct mg_connection *dc;
+- struct mg_connect_opts opts;
+- memset(&opts, 0, sizeof(opts));
+-#if MG_ENABLE_SSL
+- opts.ssl_cert = client->ssl.ssl_cert;
+- opts.ssl_key = client->ssl.ssl_key;
+- opts.ssl_ca_cert = client->ssl.ssl_ca_cert;
+-#endif
+- /* HTTP/Websocket listener */
+- if ((dc = mg_connect_ws_opt(client->mgr, mg_tun_client_handler, opts,
+- client->disp_url, MG_TUN_PROTO_NAME, NULL)) ==
+- NULL) {
+- LOG(LL_ERROR,
+- ("Cannot connect to WS server on addr [%s]\n", client->disp_url));
+- return;
+- }
+-
+- client->disp = dc;
+- dc->user_data = client;
+-}
+-
+-void mg_tun_reconnect_ev_handler(struct mg_connection *nc, int ev,
+- void *ev_data) {
+- struct mg_tun_client *client = (struct mg_tun_client *) nc->user_data;
+- (void) ev_data;
+-
+- switch (ev) {
+- case MG_EV_TIMER:
+- if (!(client->listener->flags & MG_F_TUN_DO_NOT_RECONNECT)) {
+- mg_tun_do_reconnect(client);
+- } else {
+- /* Reconnecting is suppressed, we'll check again at the next poll */
+- mg_tun_reconnect(client, 0);
+- }
+- break;
+- }
+-}
+-
+-static void mg_tun_reconnect(struct mg_tun_client *client, int timeout) {
+- if (client->reconnect == NULL) {
+- client->reconnect =
+- mg_add_sock(client->mgr, INVALID_SOCKET, mg_tun_reconnect_ev_handler);
+- client->reconnect->user_data = client;
+- }
+- client->reconnect->ev_timer_time = mg_time() + timeout;
+-}
+-
+-static struct mg_tun_client *mg_tun_create_client(struct mg_mgr *mgr,
+- const char *dispatcher,
+- struct mg_tun_ssl_opts ssl) {
+- struct mg_tun_client *client = NULL;
+- struct mg_iface *iface = mg_find_iface(mgr, &mg_tun_iface_vtable, NULL);
+- if (iface == NULL) {
+- LOG(LL_ERROR, ("The tun feature requires the manager to have a tun "
+- "interface enabled"));
+- return NULL;
+- }
+-
+- client = (struct mg_tun_client *) MG_MALLOC(sizeof(*client));
+- mg_tun_init_client(client, mgr, iface, dispatcher, ssl);
+- iface->data = client;
+-
+- /*
+- * We need to give application a chance to set MG_F_TUN_DO_NOT_RECONNECT on a
+- * listening connection right after mg_tun_bind_opt() returned it, so we
+- * should use mg_tun_reconnect() here, instead of mg_tun_do_reconnect()
+- */
+- mg_tun_reconnect(client, 0);
+- return client;
+-}
+-
+-void mg_tun_destroy_client(struct mg_tun_client *client) {
+- /*
+- * NOTE:
+- * `client` is NULL in case of OOM
+- * `client->disp` is NULL if connection failed
+- * `client->iface is NULL is `mg_find_iface` failed
+- */
+-
+- if (client != NULL && client->disp != NULL) {
+- /* the dispatcher connection handler will in turn close all tunnels */
+- client->disp->flags |= MG_F_CLOSE_IMMEDIATELY;
+- /* this is used as a signal to other tun handlers that the party is over */
+- client->disp->user_data = NULL;
+- }
+-
+- if (client != NULL && client->reconnect != NULL) {
+- client->reconnect->flags |= MG_F_CLOSE_IMMEDIATELY;
+- }
+-
+- if (client != NULL && client->iface != NULL) {
+- client->iface->data = NULL;
+- }
+-
+- MG_FREE(client);
+-}
+-
+-static struct mg_connection *mg_tun_do_bind(struct mg_tun_client *client,
+- mg_event_handler_t handler,
+- struct mg_bind_opts opts) {
+- struct mg_connection *lc;
+- opts.iface = client->iface;
+- lc = mg_bind_opt(client->mgr, ":1234" /* dummy port */, handler, opts);
+- client->listener = lc;
+- return lc;
+-}
+-
+-struct mg_connection *mg_tun_bind_opt(struct mg_mgr *mgr,
+- const char *dispatcher,
+- mg_event_handler_t handler,
+- struct mg_bind_opts opts) {
+-#if MG_ENABLE_SSL
+- struct mg_tun_ssl_opts ssl = {opts.ssl_cert, opts.ssl_key, opts.ssl_ca_cert};
+-#else
+- struct mg_tun_ssl_opts ssl = {0};
+-#endif
+- struct mg_tun_client *client = mg_tun_create_client(mgr, dispatcher, ssl);
+- if (client == NULL) {
+- return NULL;
+- }
+-#if MG_ENABLE_SSL
+- /* these options don't make sense in the local mouth of the tunnel */
+- opts.ssl_cert = NULL;
+- opts.ssl_key = NULL;
+- opts.ssl_ca_cert = NULL;
+-#endif
+- return mg_tun_do_bind(client, handler, opts);
+-}
+-
+-int mg_tun_parse_frame(void *data, size_t len, struct mg_tun_frame *frame) {
+- const size_t header_size = sizeof(uint32_t) + sizeof(uint8_t) * 2;
+- if (len < header_size) {
+- return -1;
+- }
+-
+- frame->type = *(uint8_t *) (data);
+- frame->flags = *(uint8_t *) ((char *) data + 1);
+- memcpy(&frame->stream_id, (char *) data + 2, sizeof(uint32_t));
+- frame->stream_id = ntohl(frame->stream_id);
+- frame->body.p = (char *) data + header_size;
+- frame->body.len = len - header_size;
+- return 0;
+-}
+-
+-void mg_tun_send_frame(struct mg_connection *ws, uint32_t stream_id,
+- uint8_t type, uint8_t flags, struct mg_str msg) {
+- stream_id = htonl(stream_id);
+- {
+- struct mg_str parts[] = {
+- {(char *) &type, sizeof(type)},
+- {(char *) &flags, sizeof(flags)},
+- {(char *) &stream_id, sizeof(stream_id)},
+- {msg.p, msg.len} /* vc6 doesn't like just `msg` here */};
+- mg_send_websocket_framev(ws, WEBSOCKET_OP_BINARY, parts,
+- sizeof(parts) / sizeof(parts[0]));
+- }
+-}
+-
+-#endif /* MG_ENABLE_TUN */
+-#ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/sntp.c"
++#line 1 "mongoose/src/mg_sntp.c"
+ #endif
+ /*
+ * Copyright (c) 2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+-/* Amalgamated: #include "mongoose/src/internal.h" */
+-/* Amalgamated: #include "mongoose/src/sntp.h" */
+-/* Amalgamated: #include "mongoose/src/util.h" */
++/* Amalgamated: #include "mg_internal.h" */
++/* Amalgamated: #include "mg_sntp.h" */
++/* Amalgamated: #include "mg_util.h" */
+
+ #if MG_ENABLE_SNTP
+
+@@ -11773,7 +12352,7 @@ static void mg_get_ntp_ts(const char *nt
+ }
+
+ void mg_sntp_send_request(struct mg_connection *c) {
+- char buf[48] = {0};
++ uint8_t buf[48] = {0};
+ /*
+ * header - 8 bit:
+ * LI (2 bit) - 3 (not in sync), VN (3 bit) - 4 (version),
+@@ -11801,16 +12380,16 @@ void mg_sntp_send_request(struct mg_conn
+ * but if local clock is absolutely broken (and doesn't work even
+ * as simple timer), it is better to disable it
+ */
+-#ifndef MG_SNMP_NO_DELAY_CORRECTION
++#ifndef MG_SNTP_NO_DELAY_CORRECTION
+ uint32_t sec;
+- sec = htonl(mg_time() + SNTP_TIME_OFFSET);
++ sec = htonl((uint32_t)(mg_time() + SNTP_TIME_OFFSET));
+ memcpy(&buf[40], &sec, sizeof(sec));
+ #endif
+
+ mg_send(c, buf, sizeof(buf));
+ }
+
+-#ifndef MG_SNMP_NO_DELAY_CORRECTION
++#ifndef MG_SNTP_NO_DELAY_CORRECTION
+ static uint64_t mg_calculate_delay(uint64_t t1, uint64_t t2, uint64_t t3) {
+ /* roundloop delay = (T4 - T1) - (T3 - T2) */
+ uint64_t d1 = ((mg_time() + SNTP_TIME_OFFSET) * 1000000) -
+@@ -11825,12 +12404,10 @@ static uint64_t mg_calculate_delay(uint6
+ MG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len,
+ struct mg_sntp_message *msg) {
+ uint8_t hdr;
+- uint64_t orig_ts_T1, recv_ts_T2, trsm_ts_T3, delay = 0;
++ uint64_t trsm_ts_T3, delay = 0;
+ int mode;
+ struct timeval tv;
+
+- (void) orig_ts_T1;
+- (void) recv_ts_T2;
+ if (len < 48) {
+ return -1;
+ }
+@@ -11854,10 +12431,13 @@ MG_INTERNAL int mg_sntp_parse_reply(cons
+
+ mg_get_ntp_ts(&buf[40], &trsm_ts_T3);
+
+-#ifndef MG_SNMP_NO_DELAY_CORRECTION
+- mg_get_ntp_ts(&buf[24], &orig_ts_T1);
+- mg_get_ntp_ts(&buf[32], &recv_ts_T2);
+- delay = mg_calculate_delay(orig_ts_T1, recv_ts_T2, trsm_ts_T3);
++#ifndef MG_SNTP_NO_DELAY_CORRECTION
++ {
++ uint64_t orig_ts_T1, recv_ts_T2;
++ mg_get_ntp_ts(&buf[24], &orig_ts_T1);
++ mg_get_ntp_ts(&buf[32], &recv_ts_T2);
++ delay = mg_calculate_delay(orig_ts_T1, recv_ts_T2, trsm_ts_T3);
++ }
+ #endif
+
+ mg_ntp_to_tv(trsm_ts_T3, &tv);
+@@ -11867,19 +12447,20 @@ MG_INTERNAL int mg_sntp_parse_reply(cons
+ return 0;
+ }
+
+-static void mg_sntp_handler(struct mg_connection *c, int ev, void *ev_data) {
++static void mg_sntp_handler(struct mg_connection *c, int ev,
++ void *ev_data MG_UD_ARG(void *user_data)) {
+ struct mbuf *io = &c->recv_mbuf;
+ struct mg_sntp_message msg;
+
+- c->handler(c, ev, ev_data);
++ c->handler(c, ev, ev_data MG_UD_ARG(user_data));
+
+ switch (ev) {
+ case MG_EV_RECV: {
+ if (mg_sntp_parse_reply(io->buf, io->len, &msg) < 0) {
+ DBG(("Invalid SNTP packet received (%d)", (int) io->len));
+- c->handler(c, MG_SNTP_MALFORMED_REPLY, NULL);
++ c->handler(c, MG_SNTP_MALFORMED_REPLY, NULL MG_UD_ARG(user_data));
+ } else {
+- c->handler(c, MG_SNTP_REPLY, (void *) &msg);
++ c->handler(c, MG_SNTP_REPLY, (void *) &msg MG_UD_ARG(user_data));
+ }
+
+ mbuf_remove(io, io->len);
+@@ -11899,7 +12480,8 @@ int mg_set_protocol_sntp(struct mg_conne
+ }
+
+ struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr,
+- mg_event_handler_t event_handler,
++ MG_CB(mg_event_handler_t event_handler,
++ void *user_data),
+ const char *sntp_server_name) {
+ struct mg_connection *c = NULL;
+ char url[100], *p_url = url;
+@@ -11922,7 +12504,7 @@ struct mg_connection *mg_sntp_connect(st
+
+ mg_asprintf(&p_url, sizeof(url), "%s%s%s", proto, sntp_server_name, port);
+
+- c = mg_connect(mgr, p_url, event_handler);
++ c = mg_connect(mgr, p_url, event_handler MG_UD_ARG(user_data));
+
+ if (c == NULL) {
+ goto cleanup;
+@@ -11944,13 +12526,16 @@ struct sntp_data {
+ };
+
+ static void mg_sntp_util_ev_handler(struct mg_connection *c, int ev,
+- void *ev_data) {
+- struct sntp_data *sd = (struct sntp_data *) c->user_data;
++ void *ev_data MG_UD_ARG(void *user_data)) {
++#if !MG_ENABLE_CALLBACK_USERDATA
++ void *user_data = c->user_data;
++#endif
++ struct sntp_data *sd = (struct sntp_data *) user_data;
+
+ switch (ev) {
+ case MG_EV_CONNECT:
+ if (*(int *) ev_data != 0) {
+- mg_call(c, sd->hander, MG_SNTP_FAILED, NULL);
++ mg_call(c, sd->hander, c->user_data, MG_SNTP_FAILED, NULL);
+ break;
+ }
+ /* fallthrough */
+@@ -11960,20 +12545,20 @@ static void mg_sntp_util_ev_handler(stru
+ mg_set_timer(c, mg_time() + 10);
+ sd->count++;
+ } else {
+- mg_call(c, sd->hander, MG_SNTP_FAILED, NULL);
++ mg_call(c, sd->hander, c->user_data, MG_SNTP_FAILED, NULL);
+ c->flags |= MG_F_CLOSE_IMMEDIATELY;
+ }
+ break;
+ case MG_SNTP_MALFORMED_REPLY:
+- mg_call(c, sd->hander, MG_SNTP_FAILED, NULL);
++ mg_call(c, sd->hander, c->user_data, MG_SNTP_FAILED, NULL);
+ c->flags |= MG_F_CLOSE_IMMEDIATELY;
+ break;
+ case MG_SNTP_REPLY:
+- mg_call(c, sd->hander, MG_SNTP_REPLY, ev_data);
++ mg_call(c, sd->hander, c->user_data, MG_SNTP_REPLY, ev_data);
+ c->flags |= MG_F_CLOSE_IMMEDIATELY;
+ break;
+ case MG_EV_CLOSE:
+- MG_FREE(c->user_data);
++ MG_FREE(user_data);
+ c->user_data = NULL;
+ break;
+ }
+@@ -11988,20 +12573,185 @@ struct mg_connection *mg_sntp_get_time(s
+ return NULL;
+ }
+
+- c = mg_sntp_connect(mgr, mg_sntp_util_ev_handler, sntp_server_name);
++ c = mg_sntp_connect(mgr, MG_CB(mg_sntp_util_ev_handler, sd),
++ sntp_server_name);
+ if (c == NULL) {
+ MG_FREE(sd);
+ return NULL;
+ }
+
+ sd->hander = event_handler;
++#if !MG_ENABLE_CALLBACK_USERDATA
+ c->user_data = sd;
++#endif
+
+ return c;
+ }
+
+ #endif /* MG_ENABLE_SNTP */
+ #ifdef MG_MODULE_LINES
++#line 1 "mongoose/src/mg_socks.c"
++#endif
++/*
++ * Copyright (c) 2017 Cesanta Software Limited
++ * All rights reserved
++ */
++
++#if MG_ENABLE_SOCKS
++
++/* Amalgamated: #include "mg_socks.h" */
++/* Amalgamated: #include "mg_internal.h" */
++
++/*
++ * https://www.ietf.org/rfc/rfc1928.txt paragraph 3, handle client handshake
++ *
++ * +----+----------+----------+
++ * |VER | NMETHODS | METHODS |
++ * +----+----------+----------+
++ * | 1 | 1 | 1 to 255 |
++ * +----+----------+----------+
++ */
++static void mg_socks5_handshake(struct mg_connection *c) {
++ struct mbuf *r = &c->recv_mbuf;
++ if (r->buf[0] != MG_SOCKS_VERSION) {
++ c->flags |= MG_F_CLOSE_IMMEDIATELY;
++ } else if (r->len > 2 && (size_t) r->buf[1] + 2 <= r->len) {
++ /* https://www.ietf.org/rfc/rfc1928.txt paragraph 3 */
++ unsigned char reply[2] = {MG_SOCKS_VERSION, MG_SOCKS_HANDSHAKE_FAILURE};
++ int i;
++ for (i = 2; i < r->buf[1] + 2; i++) {
++ /* TODO(lsm): support other auth methods */
++ if (r->buf[i] == MG_SOCKS_HANDSHAKE_NOAUTH) reply[1] = r->buf[i];
++ }
++ mbuf_remove(r, 2 + r->buf[1]);
++ mg_send(c, reply, sizeof(reply));
++ c->flags |= MG_SOCKS_HANDSHAKE_DONE; /* Mark handshake done */
++ }
++}
++
++static void disband(struct mg_connection *c) {
++ struct mg_connection *c2 = (struct mg_connection *) c->user_data;
++ if (c2 != NULL) {
++ c2->flags |= MG_F_SEND_AND_CLOSE;
++ c2->user_data = NULL;
++ }
++ c->flags |= MG_F_SEND_AND_CLOSE;
++ c->user_data = NULL;
++}
++
++static void relay_data(struct mg_connection *c) {
++ struct mg_connection *c2 = (struct mg_connection *) c->user_data;
++ if (c2 != NULL) {
++ mg_send(c2, c->recv_mbuf.buf, c->recv_mbuf.len);
++ mbuf_remove(&c->recv_mbuf, c->recv_mbuf.len);
++ } else {
++ c->flags |= MG_F_SEND_AND_CLOSE;
++ }
++}
++
++static void serv_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
++ if (ev == MG_EV_CLOSE) {
++ disband(c);
++ } else if (ev == MG_EV_RECV) {
++ relay_data(c);
++ } else if (ev == MG_EV_CONNECT) {
++ int res = *(int *) ev_data;
++ if (res != 0) LOG(LL_ERROR, ("connect error: %d", res));
++ }
++}
++
++static void mg_socks5_connect(struct mg_connection *c, const char *addr) {
++ struct mg_connection *serv = mg_connect(c->mgr, addr, serv_ev_handler);
++ serv->user_data = c;
++ c->user_data = serv;
++}
++
++/*
++ * Request, https://www.ietf.org/rfc/rfc1928.txt paragraph 4
++ *
++ * +----+-----+-------+------+----------+----------+
++ * |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
++ * +----+-----+-------+------+----------+----------+
++ * | 1 | 1 | X'00' | 1 | Variable | 2 |
++ * +----+-----+-------+------+----------+----------+
++ */
++static void mg_socks5_handle_request(struct mg_connection *c) {
++ struct mbuf *r = &c->recv_mbuf;
++ unsigned char *p = (unsigned char *) r->buf;
++ unsigned char addr_len = 4, reply = MG_SOCKS_SUCCESS;
++ int ver, cmd, atyp;
++ char addr[300];
++
++ if (r->len < 8) return; /* return if not fully buffered. min DST.ADDR is 2 */
++ ver = p[0];
++ cmd = p[1];
++ atyp = p[3];
++
++ /* TODO(lsm): support other commands */
++ if (ver != MG_SOCKS_VERSION || cmd != MG_SOCKS_CMD_CONNECT) {
++ reply = MG_SOCKS_CMD_NOT_SUPPORTED;
++ } else if (atyp == MG_SOCKS_ADDR_IPV4) {
++ addr_len = 4;
++ if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */
++ snprintf(addr, sizeof(addr), "%d.%d.%d.%d:%d", p[4], p[5], p[6], p[7],
++ p[8] << 8 | p[9]);
++ mg_socks5_connect(c, addr);
++ } else if (atyp == MG_SOCKS_ADDR_IPV6) {
++ addr_len = 16;
++ if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */
++ snprintf(addr, sizeof(addr), "[%x:%x:%x:%x:%x:%x:%x:%x]:%d",
++ p[4] << 8 | p[5], p[6] << 8 | p[7], p[8] << 8 | p[9],
++ p[10] << 8 | p[11], p[12] << 8 | p[13], p[14] << 8 | p[15],
++ p[16] << 8 | p[17], p[18] << 8 | p[19], p[20] << 8 | p[21]);
++ mg_socks5_connect(c, addr);
++ } else if (atyp == MG_SOCKS_ADDR_DOMAIN) {
++ addr_len = p[4] + 1;
++ if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */
++ snprintf(addr, sizeof(addr), "%.*s:%d", p[4], p + 5,
++ p[4 + addr_len] << 8 | p[4 + addr_len + 1]);
++ mg_socks5_connect(c, addr);
++ } else {
++ reply = MG_SOCKS_ADDR_NOT_SUPPORTED;
++ }
++
++ /*
++ * Reply, https://www.ietf.org/rfc/rfc1928.txt paragraph 5
++ *
++ * +----+-----+-------+------+----------+----------+
++ * |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
++ * +----+-----+-------+------+----------+----------+
++ * | 1 | 1 | X'00' | 1 | Variable | 2 |
++ * +----+-----+-------+------+----------+----------+
++ */
++ {
++ unsigned char buf[] = {MG_SOCKS_VERSION, reply, 0};
++ mg_send(c, buf, sizeof(buf));
++ }
++ mg_send(c, r->buf + 3, addr_len + 1 + 2);
++
++ mbuf_remove(r, 6 + addr_len); /* Remove request from the input stream */
++ c->flags |= MG_SOCKS_CONNECT_DONE; /* Mark ourselves as connected */
++}
++
++static void socks_handler(struct mg_connection *c, int ev, void *ev_data) {
++ if (ev == MG_EV_RECV) {
++ if (!(c->flags & MG_SOCKS_HANDSHAKE_DONE)) mg_socks5_handshake(c);
++ if (c->flags & MG_SOCKS_HANDSHAKE_DONE &&
++ !(c->flags & MG_SOCKS_CONNECT_DONE)) {
++ mg_socks5_handle_request(c);
++ }
++ if (c->flags & MG_SOCKS_CONNECT_DONE) relay_data(c);
++ } else if (ev == MG_EV_CLOSE) {
++ disband(c);
++ }
++ (void) ev_data;
++}
++
++void mg_set_protocol_socks(struct mg_connection *c) {
++ c->proto_handler = socks_handler;
++}
++#endif
++#ifdef MG_MODULE_LINES
+ #line 1 "common/platforms/cc3200/cc3200_libc.c"
+ #endif
+ /*
+@@ -12011,6 +12761,7 @@ struct mg_connection *mg_sntp_get_time(s
+
+ #if CS_PLATFORM == CS_P_CC3200
+
++/* Amalgamated: #include "common/mg_mem.h" */
+ #include <stdio.h>
+ #include <string.h>
+
+@@ -12036,7 +12787,7 @@ int asprintf(char **strp, const char *fm
+ va_list ap;
+ int len;
+
+- *strp = malloc(BUFSIZ);
++ *strp = MG_MALLOC(BUFSIZ);
+ if (*strp == NULL) return -1;
+
+ va_start(ap, fmt);
+@@ -12044,7 +12795,7 @@ int asprintf(char **strp, const char *fm
+ va_end(ap);
+
+ if (len > 0) {
+- *strp = realloc(*strp, len + 1);
++ *strp = MG_REALLOC(*strp, len + 1);
+ if (*strp == NULL) return -1;
+ }
+
+@@ -12067,25 +12818,6 @@ time_t HOSTtime() {
+
+ #endif /* __TI_COMPILER_VERSION__ */
+
+-#ifndef __TI_COMPILER_VERSION__
+-int _gettimeofday_r(struct _reent *r, struct timeval *tp, void *tzp) {
+-#else
+-int gettimeofday(struct timeval *tp, void *tzp) {
+-#endif
+- unsigned long long r1 = 0, r2;
+- /* Achieve two consecutive reads of the same value. */
+- do {
+- r2 = r1;
+- r1 = PRCMSlowClkCtrFastGet();
+- } while (r1 != r2);
+- /* This is a 32768 Hz counter. */
+- tp->tv_sec = (r1 >> 15);
+- /* 1/32768-th of a second is 30.517578125 microseconds, approx. 31,
+- * but we round down so it doesn't overflow at 32767 */
+- tp->tv_usec = (r1 & 0x7FFF) * 30;
+- return 0;
+-}
+-
+ void fprint_str(FILE *fp, const char *str) {
+ while (*str != '\0') {
+ if (*str == '\n') MAP_UARTCharPut(CONSOLE_UART, '\r');
+@@ -12219,14 +12951,25 @@ void fs_slfs_set_new_file_size(const cha
+ #if CS_PLATFORM == CS_P_CC3200
+ #include <inc/hw_types.h>
+ #endif
+-#include <simplelink/include/simplelink.h>
+-#include <simplelink/include/fs.h>
+
+ /* Amalgamated: #include "common/cs_dbg.h" */
++/* Amalgamated: #include "common/mg_mem.h" */
++
++#if SL_MAJOR_VERSION_NUM < 2
++int slfs_open(const unsigned char *fname, uint32_t flags) {
++ _i32 fh;
++ _i32 r = sl_FsOpen(fname, flags, NULL /* token */, &fh);
++ return (r < 0 ? r : fh);
++}
++#else /* SL_MAJOR_VERSION_NUM >= 2 */
++int slfs_open(const unsigned char *fname, uint32_t flags) {
++ return sl_FsOpen(fname, flags, NULL /* token */);
++}
++#endif
+
+ /* From sl_fs.c */
+-extern int set_errno(int e);
+-static const char *drop_dir(const char *fname, bool *is_slfs);
++int set_errno(int e);
++const char *drop_dir(const char *fname, bool *is_slfs);
+
+ /*
+ * With SLFS, you have to pre-declare max file size. Yes. Really.
+@@ -12255,18 +12998,18 @@ static int sl_fs_to_errno(_i32 r) {
+ switch (r) {
+ case SL_FS_OK:
+ return 0;
+- case SL_FS_FILE_NAME_EXIST:
++ case SL_ERROR_FS_FILE_NAME_EXIST:
+ return EEXIST;
+- case SL_FS_WRONG_FILE_NAME:
++ case SL_ERROR_FS_WRONG_FILE_NAME:
+ return EINVAL;
+- case SL_FS_ERR_NO_AVAILABLE_NV_INDEX:
+- case SL_FS_ERR_NO_AVAILABLE_BLOCKS:
++ case SL_ERROR_FS_NO_AVAILABLE_NV_INDEX:
++ case SL_ERROR_FS_NOT_ENOUGH_STORAGE_SPACE:
+ return ENOSPC;
+- case SL_FS_ERR_FAILED_TO_ALLOCATE_MEM:
++ case SL_ERROR_FS_FAILED_TO_ALLOCATE_MEM:
+ return ENOMEM;
+- case SL_FS_ERR_FILE_NOT_EXISTS:
++ case SL_ERROR_FS_FILE_NOT_EXISTS:
+ return ENOENT;
+- case SL_FS_ERR_NOT_SUPPORTED:
++ case SL_ERROR_FS_NOT_SUPPORTED:
+ return ENOTSUP;
+ }
+ return ENXIO;
+@@ -12289,13 +13032,14 @@ int fs_slfs_open(const char *pathname, i
+ _u32 am = 0;
+ fi->size = (size_t) -1;
+ int rw = (flags & 3);
++ size_t new_size = FS_SLFS_MAX_FILE_SIZE;
+ if (rw == O_RDONLY) {
+ SlFsFileInfo_t sl_fi;
+ _i32 r = sl_FsGetInfo((const _u8 *) pathname, 0, &sl_fi);
+ if (r == SL_FS_OK) {
+- fi->size = sl_fi.FileLen;
++ fi->size = SL_FI_FILE_SIZE(sl_fi);
+ }
+- am = FS_MODE_OPEN_READ;
++ am = SL_FS_READ;
+ } else {
+ if (!(flags & O_TRUNC) || (flags & O_APPEND)) {
+ // FailFS files cannot be opened for append and will be truncated
+@@ -12303,31 +13047,30 @@ int fs_slfs_open(const char *pathname, i
+ return set_errno(ENOTSUP);
+ }
+ if (flags & O_CREAT) {
+- size_t i, size = FS_SLFS_MAX_FILE_SIZE;
++ size_t i;
+ for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) {
+ if (s_sl_file_size_hints[i].name != NULL &&
+ strcmp(s_sl_file_size_hints[i].name, pathname) == 0) {
+- size = s_sl_file_size_hints[i].size;
+- free(s_sl_file_size_hints[i].name);
++ new_size = s_sl_file_size_hints[i].size;
++ MG_FREE(s_sl_file_size_hints[i].name);
+ s_sl_file_size_hints[i].name = NULL;
+ break;
+ }
+ }
+- DBG(("creating %s with max size %d", pathname, (int) size));
+- am = FS_MODE_OPEN_CREATE(size, 0);
++ am = FS_MODE_OPEN_CREATE(new_size, 0);
+ } else {
+- am = FS_MODE_OPEN_WRITE;
++ am = SL_FS_WRITE;
+ }
+ }
+- _i32 r = sl_FsOpen((_u8 *) pathname, am, NULL, &fi->fh);
+- DBG(("sl_FsOpen(%s, 0x%x) = %d, %d", pathname, (int) am, (int) r,
+- (int) fi->fh));
+- if (r == SL_FS_OK) {
++ fi->fh = slfs_open((_u8 *) pathname, am);
++ LOG(LL_DEBUG, ("sl_FsOpen(%s, 0x%x) sz %u = %d", pathname, (int) am,
++ (unsigned int) new_size, (int) fi->fh));
++ int r;
++ if (fi->fh >= 0) {
+ fi->pos = 0;
+ r = fd;
+ } else {
+- fi->fh = -1;
+- r = set_errno(sl_fs_to_errno(r));
++ r = set_errno(sl_fs_to_errno(fi->fh));
+ }
+ return r;
+ }
+@@ -12336,7 +13079,7 @@ int fs_slfs_close(int fd) {
+ struct sl_fd_info *fi = &s_sl_fds[fd];
+ if (fi->fh <= 0) return set_errno(EBADF);
+ _i32 r = sl_FsClose(fi->fh, NULL, NULL, 0);
+- DBG(("sl_FsClose(%d) = %d", (int) fi->fh, (int) r));
++ LOG(LL_DEBUG, ("sl_FsClose(%d) = %d", (int) fi->fh, (int) r));
+ s_sl_fds[fd].fh = -1;
+ return set_errno(sl_fs_to_errno(r));
+ }
+@@ -12381,7 +13124,7 @@ int fs_slfs_stat(const char *pathname, s
+ if (r == SL_FS_OK) {
+ s->st_mode = S_IFREG | 0666;
+ s->st_nlink = 1;
+- s->st_size = sl_fi.FileLen;
++ s->st_size = SL_FI_FILE_SIZE(sl_fi);
+ return 0;
+ }
+ return set_errno(sl_fs_to_errno(r));
+@@ -12449,6 +13192,32 @@ void fs_slfs_set_new_file_size(const cha
+ #if MG_NET_IF == MG_NET_IF_SIMPLELINK && \
+ (defined(MG_FS_SLFS) || defined(MG_FS_SPIFFS))
+
++int set_errno(int e) {
++ errno = e;
++ return (e == 0 ? 0 : -1);
++}
++
++const char *drop_dir(const char *fname, bool *is_slfs) {
++ if (is_slfs != NULL) {
++ *is_slfs = (strncmp(fname, "SL:", 3) == 0);
++ if (*is_slfs) fname += 3;
++ }
++ /* Drop "./", if any */
++ if (fname[0] == '.' && fname[1] == '/') {
++ fname += 2;
++ }
++ /*
++ * Drop / if it is the only one in the path.
++ * This allows use of /pretend/directories but serves /file.txt as normal.
++ */
++ if (fname[0] == '/' && strchr(fname + 1, '/') == NULL) {
++ fname++;
++ }
++ return fname;
++}
++
++#if !defined(MG_FS_NO_VFS)
++
+ #include <errno.h>
+ #include <stdbool.h>
+ #include <stdio.h>
+@@ -12473,7 +13242,7 @@ void fs_slfs_set_new_file_size(const cha
+ #define SPIFFS_FD_BASE 10
+ #define SLFS_FD_BASE 100
+
+-#ifndef MG_UART_CHAR_PUT
++#if !defined(MG_UART_CHAR_PUT) && !defined(MG_UART_WRITE)
+ #if CS_PLATFORM == CS_P_CC3200
+ #include <inc/hw_types.h>
+ #include <inc/hw_memmap.h>
+@@ -12482,34 +13251,10 @@ void fs_slfs_set_new_file_size(const cha
+ #include <driverlib/uart.h>
+ #define MG_UART_CHAR_PUT(fd, c) MAP_UARTCharPut(UARTA0_BASE, c);
+ #else
+-#define MG_UART_CHAR_PUT(fd, c)
++#define MG_UART_WRITE(fd, buf, len)
+ #endif /* CS_PLATFORM == CS_P_CC3200 */
+ #endif /* !MG_UART_CHAR_PUT */
+
+-int set_errno(int e) {
+- errno = e;
+- return (e == 0 ? 0 : -1);
+-}
+-
+-static const char *drop_dir(const char *fname, bool *is_slfs) {
+- if (is_slfs != NULL) {
+- *is_slfs = (strncmp(fname, "SL:", 3) == 0);
+- if (*is_slfs) fname += 3;
+- }
+- /* Drop "./", if any */
+- if (fname[0] == '.' && fname[1] == '/') {
+- fname += 2;
+- }
+- /*
+- * Drop / if it is the only one in the path.
+- * This allows use of /pretend/directories but serves /file.txt as normal.
+- */
+- if (fname[0] == '/' && strchr(fname + 1, '/') == NULL) {
+- fname++;
+- }
+- return fname;
+-}
+-
+ enum fd_type {
+ FD_INVALID,
+ FD_SYS,
+@@ -12711,7 +13456,6 @@ int write(int fd, const char *buf, unsig
+ ssize_t _write(int fd, const void *buf, size_t count) {
+ #endif
+ int r = -1;
+- size_t i = 0;
+ switch (fd_type(fd)) {
+ case FD_INVALID:
+ r = set_errno(EBADF);
+@@ -12721,11 +13465,18 @@ ssize_t _write(int fd, const void *buf,
+ r = set_errno(EACCES);
+ break;
+ }
+- for (i = 0; i < count; i++) {
+- const char c = ((const char *) buf)[i];
+- if (c == '\n') MG_UART_CHAR_PUT(fd, '\r');
+- MG_UART_CHAR_PUT(fd, c);
++#ifdef MG_UART_WRITE
++ MG_UART_WRITE(fd, buf, count);
++#elif defined(MG_UART_CHAR_PUT)
++ {
++ size_t i;
++ for (i = 0; i < count; i++) {
++ const char c = ((const char *) buf)[i];
++ if (c == '\n') MG_UART_CHAR_PUT(fd, '\r');
++ MG_UART_CHAR_PUT(fd, c);
++ }
+ }
++#endif
+ r = count;
+ break;
+ }
+@@ -12841,6 +13592,7 @@ int sl_fs_init(void) {
+ return ret;
+ }
+
++#endif /* !defined(MG_FS_NO_VFS) */
+ #endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK && (defined(MG_FS_SLFS) || \
+ defined(MG_FS_SPIFFS)) */
+ #ifdef MG_MODULE_LINES
+@@ -12862,7 +13614,7 @@ const char *inet_ntop(int af, const void
+ int res;
+ struct in_addr *in = (struct in_addr *) src;
+ if (af != AF_INET) {
+- errno = EAFNOSUPPORT;
++ errno = ENOTSUP;
+ return NULL;
+ }
+ res = snprintf(dst, size, "%lu.%lu.%lu.%lu", SL_IPV4_BYTE(in->s_addr, 0),
+@@ -12880,7 +13632,7 @@ int inet_pton(int af, const char *src, v
+ uint32_t a0, a1, a2, a3;
+ uint8_t *db = (uint8_t *) dst;
+ if (af != AF_INET) {
+- errno = EAFNOSUPPORT;
++ errno = ENOTSUP;
+ return 0;
+ }
+ if (sscanf(src, "%lu.%lu.%lu.%lu", &a0, &a1, &a2, &a3) != 4) {
+@@ -12970,7 +13722,7 @@ extern "C" {
+ #define MG_ENABLE_NET_IF_SIMPLELINK MG_NET_IF == MG_NET_IF_SIMPLELINK
+ #endif
+
+-extern struct mg_iface_vtable mg_simplelink_iface_vtable;
++extern const struct mg_iface_vtable mg_simplelink_iface_vtable;
+
+ #ifdef __cplusplus
+ }
+@@ -12995,19 +13747,22 @@ extern struct mg_iface_vtable mg_simplel
+ #define MG_TCP_RECV_BUFFER_SIZE 1024
+ #define MG_UDP_RECV_BUFFER_SIZE 1500
+
+-static sock_t mg_open_listening_socket(union socket_address *sa, int type,
++static sock_t mg_open_listening_socket(struct mg_connection *nc,
++ union socket_address *sa, int type,
+ int proto);
+
+-int sl_set_ssl_opts(struct mg_connection *nc);
+-
+ void mg_set_non_blocking_mode(sock_t sock) {
+ SlSockNonblocking_t opt;
++#if SL_MAJOR_VERSION_NUM < 2
+ opt.NonblockingEnabled = 1;
++#else
++ opt.NonBlockingEnabled = 1;
++#endif
+ sl_SetSockOpt(sock, SL_SOL_SOCKET, SL_SO_NONBLOCKING, &opt, sizeof(opt));
+ }
+
+ static int mg_is_error(int n) {
+- return (n < 0 && n != SL_EALREADY && n != SL_EAGAIN);
++ return (n < 0 && n != SL_ERROR_BSD_EALREADY && n != SL_ERROR_BSD_EAGAIN);
+ }
+
+ void mg_sl_if_connect_tcp(struct mg_connection *nc,
+@@ -13021,7 +13776,7 @@ void mg_sl_if_connect_tcp(struct mg_conn
+ }
+ mg_sock_set(nc, sock);
+ #if MG_ENABLE_SSL
+- nc->err = sl_set_ssl_opts(nc);
++ nc->err = sl_set_ssl_opts(sock, nc);
+ if (nc->err != 0) goto out;
+ #endif
+ nc->err = sl_Connect(sock, &sa->sa, sizeof(sa->sin));
+@@ -13043,18 +13798,14 @@ void mg_sl_if_connect_udp(struct mg_conn
+ int mg_sl_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
+ int proto = 0;
+ if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET;
+- sock_t sock = mg_open_listening_socket(sa, SOCK_STREAM, proto);
++ sock_t sock = mg_open_listening_socket(nc, sa, SOCK_STREAM, proto);
+ if (sock < 0) return sock;
+ mg_sock_set(nc, sock);
+-#if MG_ENABLE_SSL
+- return sl_set_ssl_opts(nc);
+-#else
+ return 0;
+-#endif
+ }
+
+ int mg_sl_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
+- sock_t sock = mg_open_listening_socket(sa, SOCK_DGRAM, 0);
++ sock_t sock = mg_open_listening_socket(nc, sa, SOCK_DGRAM, 0);
+ if (sock == INVALID_SOCKET) return (errno ? errno : 1);
+ mg_sock_set(nc, sock);
+ return 0;
+@@ -13110,22 +13861,27 @@ static int mg_accept_conn(struct mg_conn
+ }
+
+ /* 'sa' must be an initialized address to bind to */
+-static sock_t mg_open_listening_socket(union socket_address *sa, int type,
++static sock_t mg_open_listening_socket(struct mg_connection *nc,
++ union socket_address *sa, int type,
+ int proto) {
+ int r;
+ socklen_t sa_len =
+ (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6);
+ sock_t sock = sl_Socket(sa->sa.sa_family, type, proto);
+ if (sock < 0) return sock;
+- if ((r = sl_Bind(sock, &sa->sa, sa_len)) < 0) {
+- sl_Close(sock);
+- return r;
++ if ((r = sl_Bind(sock, &sa->sa, sa_len)) < 0) goto clean;
++ if (type != SOCK_DGRAM) {
++#if MG_ENABLE_SSL
++ if ((r = sl_set_ssl_opts(sock, nc)) < 0) goto clean;
++#endif
++ if ((r = sl_Listen(sock, SOMAXCONN)) < 0) goto clean;
+ }
+- if (type != SOCK_DGRAM && (r = sl_Listen(sock, SOMAXCONN)) < 0) {
++ mg_set_non_blocking_mode(sock);
++clean:
++ if (r < 0) {
+ sl_Close(sock);
+- return r;
++ sock = r;
+ }
+- mg_set_non_blocking_mode(sock);
+ return sock;
+ }
+
+@@ -13144,7 +13900,6 @@ static void mg_write_to_socket(struct mg
+ }
+
+ if (n > 0) {
+- mbuf_remove(io, n);
+ mg_if_sent_cb(nc, n);
+ } else if (n < 0 && mg_is_error(n)) {
+ /* Something went wrong, drop the connection. */
+@@ -13209,7 +13964,7 @@ void mg_mgr_handle_conn(struct mg_connec
+ fd_flags, nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
+
+ if (nc->flags & MG_F_CONNECTING) {
+- if (nc->flags & MG_F_UDP || nc->err != SL_EALREADY) {
++ if (nc->flags & MG_F_UDP || nc->err != SL_ERROR_BSD_EALREADY) {
+ mg_if_connect_cb(nc, nc->err);
+ } else {
+ /* In SimpleLink, to get status of non-blocking connect() we need to wait
+@@ -13218,9 +13973,17 @@ void mg_mgr_handle_conn(struct mg_connec
+ if (fd_flags & _MG_F_FD_CAN_WRITE) {
+ nc->err = sl_Connect(nc->sock, &nc->sa.sa, sizeof(nc->sa.sin));
+ DBG(("%p conn res=%d", nc, nc->err));
+- if (nc->err == SL_ESECSNOVERIFY ||
++ if (nc->err == SL_ERROR_BSD_ESECSNOVERIFY ||
+ /* TODO(rojer): Provide API to set the date for verification. */
+- nc->err == SL_ESECDATEERROR) {
++ nc->err == SL_ERROR_BSD_ESECDATEERROR
++#if SL_MAJOR_VERSION_NUM >= 2
++ /* Per SWRU455, this error does not mean verification failed,
++ * it only means that the cert used is not present in the trusted
++ * root CA catalog. Which is perfectly fine. */
++ ||
++ nc->err == SL_ERROR_BSD_ESECUNKNOWNROOTCA
++#endif
++ ) {
+ nc->err = 0;
+ }
+ if (nc->flags & MG_F_SSL && nc->err == 0) {
+@@ -13294,9 +14057,9 @@ time_t mg_sl_if_poll(struct mg_iface *if
+ sock_t max_fd = INVALID_SOCKET;
+ int num_fds, num_ev = 0, num_timers = 0;
+
+- SL_FD_ZERO(&read_set);
+- SL_FD_ZERO(&write_set);
+- SL_FD_ZERO(&err_set);
++ SL_SOCKET_FD_ZERO(&read_set);
++ SL_SOCKET_FD_ZERO(&write_set);
++ SL_SOCKET_FD_ZERO(&err_set);
+
+ /*
+ * Note: it is ok to have connections with sock == INVALID_SOCKET in the list,
+@@ -13312,14 +14075,14 @@ time_t mg_sl_if_poll(struct mg_iface *if
+ if (!(nc->flags & MG_F_WANT_WRITE) &&
+ nc->recv_mbuf.len < nc->recv_mbuf_limit &&
+ (!(nc->flags & MG_F_UDP) || nc->listener == NULL)) {
+- SL_FD_SET(nc->sock, &read_set);
++ SL_SOCKET_FD_SET(nc->sock, &read_set);
+ if (max_fd == INVALID_SOCKET || nc->sock > max_fd) max_fd = nc->sock;
+ }
+
+ if (((nc->flags & MG_F_CONNECTING) && !(nc->flags & MG_F_WANT_READ)) ||
+ (nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING))) {
+- SL_FD_SET(nc->sock, &write_set);
+- SL_FD_SET(nc->sock, &err_set);
++ SL_SOCKET_FD_SET(nc->sock, &write_set);
++ SL_SOCKET_FD_SET(nc->sock, &err_set);
+ if (max_fd == INVALID_SOCKET || nc->sock > max_fd) max_fd = nc->sock;
+ }
+ }
+@@ -13360,14 +14123,15 @@ time_t mg_sl_if_poll(struct mg_iface *if
+ if (nc->sock != INVALID_SOCKET) {
+ if (num_ev > 0) {
+ fd_flags =
+- (SL_FD_ISSET(nc->sock, &read_set) &&
++ (SL_SOCKET_FD_ISSET(nc->sock, &read_set) &&
+ (!(nc->flags & MG_F_UDP) || nc->listener == NULL)
+ ? _MG_F_FD_CAN_READ
+ : 0) |
+- (SL_FD_ISSET(nc->sock, &write_set) ? _MG_F_FD_CAN_WRITE : 0) |
+- (SL_FD_ISSET(nc->sock, &err_set) ? _MG_F_FD_ERROR : 0);
++ (SL_SOCKET_FD_ISSET(nc->sock, &write_set) ? _MG_F_FD_CAN_WRITE
++ : 0) |
++ (SL_SOCKET_FD_ISSET(nc->sock, &err_set) ? _MG_F_FD_ERROR : 0);
+ }
+- /* SimpleLink does not report UDP sockets as writeable. */
++ /* SimpleLink does not report UDP sockets as writable. */
+ if (nc->flags & MG_F_UDP && nc->send_mbuf.len > 0) {
+ fd_flags |= _MG_F_FD_CAN_WRITE;
+ }
+@@ -13390,7 +14154,7 @@ time_t mg_sl_if_poll(struct mg_iface *if
+ void mg_sl_if_get_conn_addr(struct mg_connection *nc, int remote,
+ union socket_address *sa) {
+ /* SimpleLink does not provide a way to get socket's peer address after
+- * accept or connect. Address hould have been preserved in the connection,
++ * accept or connect. Address should have been preserved in the connection,
+ * so we do our best here by using it. */
+ if (remote) memcpy(sa, &nc->sa, sizeof(*sa));
+ }
+@@ -13442,9 +14206,9 @@ void sl_restart_cb(struct mg_mgr *mgr) {
+ }
+ /* clang-format on */
+
+-struct mg_iface_vtable mg_simplelink_iface_vtable = MG_SL_IFACE_VTABLE;
++const struct mg_iface_vtable mg_simplelink_iface_vtable = MG_SL_IFACE_VTABLE;
+ #if MG_NET_IF == MG_NET_IF_SIMPLELINK
+-struct mg_iface_vtable mg_default_iface_vtable = MG_SL_IFACE_VTABLE;
++const struct mg_iface_vtable mg_default_iface_vtable = MG_SL_IFACE_VTABLE;
+ #endif
+
+ #endif /* MG_ENABLE_NET_IF_SIMPLELINK */
+@@ -13458,6 +14222,15 @@ struct mg_iface_vtable mg_default_iface_
+
+ #if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
+
++/* Amalgamated: #include "common/mg_mem.h" */
++
++#ifndef MG_SSL_IF_SIMPLELINK_SLFS_PREFIX
++#define MG_SSL_IF_SIMPLELINK_SLFS_PREFIX "SL:"
++#endif
++
++#define MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN \
++ (sizeof(MG_SSL_IF_SIMPLELINK_SLFS_PREFIX) - 1)
++
+ struct mg_ssl_if_ctx {
+ char *ssl_cert;
+ char *ssl_key;
+@@ -13491,12 +14264,18 @@ enum mg_ssl_if_result mg_ssl_if_conn_ini
+ if (params->ca_cert != NULL && strcmp(params->ca_cert, "*") != 0) {
+ ctx->ssl_ca_cert = strdup(params->ca_cert);
+ }
++ /* TODO(rojer): cipher_suites. */
+ if (params->server_name != NULL) {
+ ctx->ssl_server_name = strdup(params->server_name);
+ }
+ return MG_SSL_OK;
+ }
+
++void mg_ssl_if_conn_close_notify(struct mg_connection *nc) {
++ /* Nothing to do */
++ (void) nc;
++}
++
+ void mg_ssl_if_conn_free(struct mg_connection *nc) {
+ struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
+ if (ctx == NULL) return;
+@@ -13516,7 +14295,8 @@ bool pem_to_der(const char *pem_file, co
+ pf = fopen(pem_file, "r");
+ if (pf == NULL) goto clean;
+ remove(der_file);
+- fs_slfs_set_new_file_size(der_file + 3, 2048);
++ fs_slfs_set_new_file_size(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN,
++ 2048);
+ df = fopen(der_file, "w");
+ if (df == NULL) goto clean;
+ while (1) {
+@@ -13558,8 +14338,8 @@ static char *sl_pem2der(const char *pem_
+ }
+ char *der_file = NULL;
+ /* DER file must be located on SLFS, add prefix. */
+- int l = mg_asprintf(&der_file, 0, "SL:%.*s.der", (int) (pem_ext - pem_file),
+- pem_file);
++ int l = mg_asprintf(&der_file, 0, MG_SSL_IF_SIMPLELINK_SLFS_PREFIX "%.*s.der",
++ (int) (pem_ext - pem_file), pem_file);
+ if (der_file == NULL) return NULL;
+ bool result = false;
+ cs_stat_t st;
+@@ -13572,9 +14352,10 @@ static char *sl_pem2der(const char *pem_
+ }
+ if (result) {
+ /* Strip the SL: prefix we added since NWP does not expect it. */
+- memmove(der_file, der_file + 3, l - 2 /* including \0 */);
++ memmove(der_file, der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN,
++ l - 2 /* including \0 */);
+ } else {
+- free(der_file);
++ MG_FREE(der_file);
+ der_file = NULL;
+ }
+ return der_file;
+@@ -13585,9 +14366,9 @@ static char *sl_pem2der(const char *pem_
+ }
+ #endif
+
+-int sl_set_ssl_opts(struct mg_connection *nc) {
++int sl_set_ssl_opts(int sock, struct mg_connection *nc) {
+ int err;
+- struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
++ const struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
+ DBG(("%p ssl ctx: %p", nc, ctx));
+
+ if (ctx != NULL) {
+@@ -13599,45 +14380,46 @@ int sl_set_ssl_opts(struct mg_connection
+ char *ssl_cert = sl_pem2der(ctx->ssl_cert);
+ char *ssl_key = sl_pem2der(ctx->ssl_key);
+ if (ssl_cert != NULL && ssl_key != NULL) {
+- err = sl_SetSockOpt(nc->sock, SL_SOL_SOCKET,
++ err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
+ SL_SO_SECURE_FILES_CERTIFICATE_FILE_NAME, ssl_cert,
+ strlen(ssl_cert));
+ LOG(LL_INFO, ("CERTIFICATE_FILE_NAME %s -> %d", ssl_cert, err));
+- err = sl_SetSockOpt(nc->sock, SL_SOL_SOCKET,
++ err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
+ SL_SO_SECURE_FILES_PRIVATE_KEY_FILE_NAME, ssl_key,
+ strlen(ssl_key));
+ LOG(LL_INFO, ("PRIVATE_KEY_FILE_NAME %s -> %d", ssl_key, err));
+ } else {
+ err = -1;
+ }
+- free(ssl_cert);
+- free(ssl_key);
++ MG_FREE(ssl_cert);
++ MG_FREE(ssl_key);
+ if (err != 0) return err;
+ }
+ if (ctx->ssl_ca_cert != NULL) {
+ if (ctx->ssl_ca_cert[0] != '\0') {
+ char *ssl_ca_cert = sl_pem2der(ctx->ssl_ca_cert);
+ if (ssl_ca_cert != NULL) {
+- err = sl_SetSockOpt(nc->sock, SL_SOL_SOCKET,
++ err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
+ SL_SO_SECURE_FILES_CA_FILE_NAME, ssl_ca_cert,
+ strlen(ssl_ca_cert));
+ LOG(LL_INFO, ("CA_FILE_NAME %s -> %d", ssl_ca_cert, err));
+ } else {
+ err = -1;
+ }
+- free(ssl_ca_cert);
++ MG_FREE(ssl_ca_cert);
+ if (err != 0) return err;
+ }
+ }
+ if (ctx->ssl_server_name != NULL) {
+- err = sl_SetSockOpt(nc->sock, SL_SOL_SOCKET,
+- SO_SECURE_DOMAIN_NAME_VERIFICATION,
++ err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
++ SL_SO_SECURE_DOMAIN_NAME_VERIFICATION,
+ ctx->ssl_server_name, strlen(ctx->ssl_server_name));
+ DBG(("DOMAIN_NAME_VERIFICATION %s -> %d", ctx->ssl_server_name, err));
+ /* Domain name verificationw as added in a NWP service pack, older
+- * versions return SL_ENOPROTOOPT. There isn't much we can do about it,
++ * versions return SL_ERROR_BSD_ENOPROTOOPT. There isn't much we can do
++ * about it,
+ * so we ignore the error. */
+- if (err != 0 && err != SL_ENOPROTOOPT) return err;
++ if (err != 0 && err != SL_ERROR_BSD_ENOPROTOOPT) return err;
+ }
+ }
+ return 0;
+@@ -13663,10 +14445,11 @@ int sl_set_ssl_opts(struct mg_connection
+
+ #include <stdint.h>
+
+-extern struct mg_iface_vtable mg_lwip_iface_vtable;
++extern const struct mg_iface_vtable mg_lwip_iface_vtable;
+
+ struct mg_lwip_conn_state {
+ struct mg_connection *nc;
++ struct mg_connection *lc;
+ union {
+ struct tcp_pcb *tcp;
+ struct udp_pcb *udp;
+@@ -13677,14 +14460,18 @@ struct mg_lwip_conn_state {
+ size_t rx_offset; /* Offset within the first pbuf (if partially consumed) */
+ /* Last SSL write size, for retries. */
+ int last_ssl_write_size;
++ /* Whether MG_SIG_RECV is already pending for this connection */
++ int recv_pending : 1;
++ /* Whether the connection is about to close, just `rx_chain` needs to drain */
++ int draining_rx_chain : 1;
+ };
+
+ enum mg_sig_type {
+ MG_SIG_CONNECT_RESULT = 1,
+ MG_SIG_RECV = 2,
+- MG_SIG_SENT_CB = 3,
+- MG_SIG_CLOSE_CONN = 4,
+- MG_SIG_TOMBSTONE = 5,
++ MG_SIG_CLOSE_CONN = 3,
++ MG_SIG_TOMBSTONE = 4,
++ MG_SIG_ACCEPT = 5,
+ };
+
+ void mg_lwip_post_signal(enum mg_sig_type sig, struct mg_connection *nc);
+@@ -13705,14 +14492,34 @@ void mg_lwip_mgr_schedule_poll(struct mg
+
+ #if MG_ENABLE_NET_IF_LWIP_LOW_LEVEL
+
++/* Amalgamated: #include "common/mg_mem.h" */
++
++#include <lwip/init.h>
+ #include <lwip/pbuf.h>
+ #include <lwip/tcp.h>
++#include <lwip/tcpip.h>
++#if ((LWIP_VERSION_MAJOR << 8) | LWIP_VERSION_MINOR) >= 0x0105
++#include <lwip/priv/tcp_priv.h> /* For tcp_seg */
++#else
+ #include <lwip/tcp_impl.h>
++#endif
+ #include <lwip/udp.h>
+
+ /* Amalgamated: #include "common/cs_dbg.h" */
+
+ /*
++ * Newest versions of LWIP have ip_2_ip4, older have ipX_2_ip,
++ * even older have nothing.
++ */
++#ifndef ip_2_ip4
++#ifdef ipX_2_ip
++#define ip_2_ip4(addr) ipX_2_ip(addr)
++#else
++#define ip_2_ip4(addr) (addr)
++#endif
++#endif
++
++/*
+ * Depending on whether Mongoose is compiled with ipv6 support, use right
+ * lwip functions
+ */
+@@ -13729,16 +14536,12 @@ void mg_lwip_mgr_schedule_poll(struct mg
+ #define TCP_BIND tcp_bind
+ #define UDP_BIND udp_bind
+ #define IPADDR_NTOA ipaddr_ntoa
+-#define SET_ADDR(dst, src) (dst)->sin.sin_addr.s_addr = GET_IPV4(src)
++#define SET_ADDR(dst, src) (dst)->sin.sin_addr.s_addr = ip_2_ip4(src)->addr
+ #endif
+
+-/*
+- * If lwip is compiled with ipv6 support, then API changes even for ipv4
+- */
+-#if !defined(LWIP_IPV6) || !LWIP_IPV6
+-#define GET_IPV4(ipX_addr) ((ipX_addr)->addr)
+-#else
+-#define GET_IPV4(ipX_addr) ((ipX_addr)->ip4.addr)
++#if NO_SYS
++#define tcpip_callback(fn, arg) (fn)(arg)
++typedef void (*tcpip_callback_fn)(void *arg);
+ #endif
+
+ void mg_lwip_ssl_do_hs(struct mg_connection *nc);
+@@ -13751,6 +14554,16 @@ void mg_lwip_if_add_conn(struct mg_conne
+ void mg_lwip_if_remove_conn(struct mg_connection *nc);
+ time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms);
+
++#if defined(RTOS_SDK) || defined(ESP_PLATFORM)
++extern void mgos_lock();
++extern void mgos_unlock();
++#else
++#define mgos_lock()
++#define mgos_unlock()
++#endif
++
++static void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p);
++
+ #if LWIP_TCP_KEEPALIVE
+ void mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle,
+ int interval, int count) {
+@@ -13774,7 +14587,7 @@ void mg_lwip_set_keepalive_params(struct
+
+ static err_t mg_lwip_tcp_conn_cb(void *arg, struct tcp_pcb *tpcb, err_t err) {
+ struct mg_connection *nc = (struct mg_connection *) arg;
+- DBG(("%p connect to %s:%u = %d", nc, IPADDR_NTOA(&tpcb->remote_ip),
++ DBG(("%p connect to %s:%u = %d", nc, IPADDR_NTOA(ipX_2_ip(&tpcb->remote_ip)),
+ tpcb->remote_port, err));
+ if (nc == NULL) {
+ tcp_abort(tpcb);
+@@ -13808,8 +14621,17 @@ static err_t mg_lwip_tcp_recv_cb(void *a
+ struct mg_connection *nc = (struct mg_connection *) arg;
+ DBG(("%p %p %u %d", nc, tpcb, (p != NULL ? p->tot_len : 0), err));
+ if (p == NULL) {
+- if (nc != NULL) {
+- mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
++ if (nc != NULL && !(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
++ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
++ if (cs->rx_chain != NULL) {
++ /*
++ * rx_chain still contains non-consumed data, don't close the
++ * connection
++ */
++ cs->draining_rx_chain = 1;
++ } else {
++ mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
++ }
+ } else {
+ /* Tombstoned connection, do nothing. */
+ }
+@@ -13827,30 +14649,28 @@ static err_t mg_lwip_tcp_recv_cb(void *a
+ struct pbuf *q = p->next;
+ for (; q != NULL; q = q->next) pbuf_ref(q);
+ }
++ mgos_lock();
+ if (cs->rx_chain == NULL) {
+- cs->rx_chain = p;
+ cs->rx_offset = 0;
+- } else {
+- if (pbuf_clen(cs->rx_chain) >= 4) {
+- /* ESP SDK has a limited pool of 5 pbufs. We must not hog them all or RX
+- * will be completely blocked. We already have at least 4 in the chain,
+- * this one is, so we have to make a copy and release this one. */
+- struct pbuf *np = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
+- if (np != NULL) {
+- pbuf_copy(np, p);
+- pbuf_free(p);
+- p = np;
+- }
++ } else if (pbuf_clen(cs->rx_chain) >= 4) {
++ /* ESP SDK has a limited pool of 5 pbufs. We must not hog them all or RX
++ * will be completely blocked. We already have at least 4 in the chain,
++ * this one is, so we have to make a copy and release this one. */
++ struct pbuf *np = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
++ if (np != NULL) {
++ pbuf_copy(np, p);
++ pbuf_free(p);
++ p = np;
+ }
+- pbuf_chain(cs->rx_chain, p);
+ }
+- mg_lwip_post_signal(MG_SIG_RECV, nc);
++ mgos_unlock();
++ mg_lwip_recv_common(nc, p);
+ return ERR_OK;
+ }
+
+-static void mg_lwip_handle_recv(struct mg_connection *nc) {
++static void mg_lwip_consume_rx_chain_tcp(struct mg_connection *nc) {
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+-
++ if (cs->rx_chain == NULL) return;
+ #if MG_ENABLE_SSL
+ if (nc->flags & MG_F_SSL) {
+ if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
+@@ -13861,24 +14681,36 @@ static void mg_lwip_handle_recv(struct m
+ return;
+ }
+ #endif
+-
+- while (cs->rx_chain != NULL) {
++ mgos_lock();
++ while (cs->rx_chain != NULL && nc->recv_mbuf.len < nc->recv_mbuf_limit) {
+ struct pbuf *seg = cs->rx_chain;
+- size_t len = (seg->len - cs->rx_offset);
+- char *data = (char *) malloc(len);
++
++ size_t seg_len = (seg->len - cs->rx_offset);
++ size_t buf_avail = (nc->recv_mbuf_limit - nc->recv_mbuf.len);
++ size_t len = MIN(seg_len, buf_avail);
++
++ char *data = (char *) MG_MALLOC(len);
+ if (data == NULL) {
++ mgos_unlock();
+ DBG(("OOM"));
+ return;
+ }
+ pbuf_copy_partial(seg, data, len, cs->rx_offset);
+- mg_if_recv_tcp_cb(nc, data, len, 1 /* own */);
+ cs->rx_offset += len;
+ if (cs->rx_offset == cs->rx_chain->len) {
+ cs->rx_chain = pbuf_dechain(cs->rx_chain);
+ pbuf_free(seg);
+ cs->rx_offset = 0;
+ }
++ mgos_unlock();
++ mg_if_recv_tcp_cb(nc, data, len, 1 /* own */);
++ mgos_lock();
+ }
++ mgos_unlock();
++}
++
++static void mg_lwip_handle_recv_tcp(struct mg_connection *nc) {
++ mg_lwip_consume_rx_chain_tcp(nc);
+
+ if (nc->send_mbuf.len > 0) {
+ mg_lwip_mgr_schedule_poll(nc->mgr);
+@@ -13888,20 +14720,26 @@ static void mg_lwip_handle_recv(struct m
+ static err_t mg_lwip_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb,
+ u16_t num_sent) {
+ struct mg_connection *nc = (struct mg_connection *) arg;
+- DBG(("%p %p %u", nc, tpcb, num_sent));
+- if (nc == NULL) {
+- tcp_abort(tpcb);
+- return ERR_ABRT;
++ DBG(("%p %p %u %p %p", nc, tpcb, num_sent, tpcb->unsent, tpcb->unacked));
++ if (nc == NULL) return ERR_OK;
++ if ((nc->flags & MG_F_SEND_AND_CLOSE) && !(nc->flags & MG_F_WANT_WRITE) &&
++ nc->send_mbuf.len == 0 && tpcb->unsent == NULL && tpcb->unacked == NULL) {
++ mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
+ }
+- struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+- cs->num_sent += num_sent;
+-
+- mg_lwip_post_signal(MG_SIG_SENT_CB, nc);
+ return ERR_OK;
+ }
+
+-void mg_lwip_if_connect_tcp(struct mg_connection *nc,
+- const union socket_address *sa) {
++struct mg_lwip_if_connect_tcp_ctx {
++ struct mg_connection *nc;
++ const union socket_address *sa;
++};
++
++static void mg_lwip_if_connect_tcp_tcpip(void *arg) {
++ struct mg_lwip_if_connect_tcp_ctx *ctx =
++ (struct mg_lwip_if_connect_tcp_ctx *) arg;
++ struct mg_connection *nc = ctx->nc;
++ const union socket_address *sa = ctx->sa;
++
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+ struct tcp_pcb *tpcb = TCP_NEW();
+ cs->pcb.tcp = tpcb;
+@@ -13925,11 +14763,17 @@ void mg_lwip_if_connect_tcp(struct mg_co
+ }
+ }
+
++void mg_lwip_if_connect_tcp(struct mg_connection *nc,
++ const union socket_address *sa) {
++ struct mg_lwip_if_connect_tcp_ctx ctx = {.nc = nc, .sa = sa};
++ tcpip_callback(mg_lwip_if_connect_tcp_tcpip, &ctx);
++}
++
+ /*
+ * Lwip included in the SDKs for nRF5x chips has different type for the
+ * callback of `udp_recv()`
+ */
+-#if CS_PLATFORM == CS_P_NRF51 || CS_PLATFORM == CS_P_NRF52
++#if ((LWIP_VERSION_MAJOR << 8) | LWIP_VERSION_MINOR) >= 0x0105
+ static void mg_lwip_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,
+ const ip_addr_t *addr, u16_t port)
+ #else
+@@ -13938,24 +14782,70 @@ static void mg_lwip_udp_recv_cb(void *ar
+ #endif
+ {
+ struct mg_connection *nc = (struct mg_connection *) arg;
+- size_t len = p->len;
+- char *data = (char *) malloc(len);
+- union socket_address sa;
+- (void) pcb;
+- DBG(("%p %s:%u %u", nc, IPADDR_NTOA(addr), port, p->len));
+- if (data == NULL) {
+- DBG(("OOM"));
++ DBG(("%p %s:%u %p %u %u", nc, IPADDR_NTOA(addr), port, p, p->ref, p->len));
++ /* Put address in a separate pbuf and tack it onto the packet. */
++ struct pbuf *sap =
++ pbuf_alloc(PBUF_RAW, sizeof(union socket_address), PBUF_RAM);
++ if (sap == NULL) {
+ pbuf_free(p);
+ return;
+ }
+- sa.sin.sin_addr.s_addr = addr->addr;
+- sa.sin.sin_port = htons(port);
+- pbuf_copy_partial(p, data, len, 0);
+- pbuf_free(p);
+- mg_if_recv_udp_cb(nc, data, len, &sa, sizeof(sa.sin));
++ union socket_address *sa = (union socket_address *) sap->payload;
++#if ((LWIP_VERSION_MAJOR << 8) | LWIP_VERSION_MINOR) >= 0x0105
++ sa->sin.sin_addr.s_addr = ip_2_ip4(addr)->addr;
++#else
++ sa->sin.sin_addr.s_addr = addr->addr;
++#endif
++ sa->sin.sin_port = htons(port);
++ /* Logic in the recv handler requires that there be exactly one data pbuf. */
++ p = pbuf_coalesce(p, PBUF_RAW);
++ pbuf_chain(sap, p);
++ mg_lwip_recv_common(nc, sap);
++ (void) pcb;
+ }
+
+-void mg_lwip_if_connect_udp(struct mg_connection *nc) {
++static void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p) {
++ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
++ mgos_lock();
++ if (cs->rx_chain == NULL) {
++ cs->rx_chain = p;
++ } else {
++ pbuf_chain(cs->rx_chain, p);
++ }
++ if (!cs->recv_pending) {
++ cs->recv_pending = 1;
++ mg_lwip_post_signal(MG_SIG_RECV, nc);
++ }
++ mgos_unlock();
++}
++
++static void mg_lwip_handle_recv_udp(struct mg_connection *nc) {
++ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
++ /*
++ * For UDP, RX chain consists of interleaved address and packet bufs:
++ * Address pbuf followed by exactly one data pbuf (recv_cb took care of that).
++ */
++ while (cs->rx_chain != NULL) {
++ struct pbuf *sap = cs->rx_chain;
++ struct pbuf *p = sap->next;
++ cs->rx_chain = pbuf_dechain(p);
++ size_t data_len = p->len;
++ char *data = (char *) MG_MALLOC(data_len);
++ if (data != NULL) {
++ pbuf_copy_partial(p, data, data_len, 0);
++ pbuf_free(p);
++ mg_if_recv_udp_cb(nc, data, data_len,
++ (union socket_address *) sap->payload, sap->len);
++ pbuf_free(sap);
++ } else {
++ pbuf_free(p);
++ pbuf_free(sap);
++ }
++ }
++}
++
++static void mg_lwip_if_connect_udp_tcpip(void *arg) {
++ struct mg_connection *nc = (struct mg_connection *) arg;
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+ struct udp_pcb *upcb = udp_new();
+ cs->err = UDP_BIND(upcb, IP_ADDR_ANY, 0 /* any port */);
+@@ -13969,6 +14859,10 @@ void mg_lwip_if_connect_udp(struct mg_co
+ mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
+ }
+
++void mg_lwip_if_connect_udp(struct mg_connection *nc) {
++ tcpip_callback(mg_lwip_if_connect_udp_tcpip, nc);
++}
++
+ void mg_lwip_accept_conn(struct mg_connection *nc, struct tcp_pcb *tpcb) {
+ union socket_address sa;
+ SET_ADDR(&sa, &tpcb->remote_ip);
+@@ -13976,18 +14870,52 @@ void mg_lwip_accept_conn(struct mg_conne
+ mg_if_accept_tcp_cb(nc, &sa, sizeof(sa.sin));
+ }
+
++static void tcp_close_tcpip(void *arg) {
++ tcp_close((struct tcp_pcb *) arg);
++}
++
++void mg_lwip_handle_accept(struct mg_connection *nc) {
++ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
++ if (cs->pcb.tcp == NULL) return;
++#if MG_ENABLE_SSL
++ if (cs->lc->flags & MG_F_SSL) {
++ if (mg_ssl_if_conn_accept(nc, cs->lc) != MG_SSL_OK) {
++ LOG(LL_ERROR, ("SSL error"));
++ tcpip_callback(tcp_close_tcpip, cs->pcb.tcp);
++ }
++ } else
++#endif
++ {
++ mg_lwip_accept_conn(nc, cs->pcb.tcp);
++ }
++}
++
+ static err_t mg_lwip_accept_cb(void *arg, struct tcp_pcb *newtpcb, err_t err) {
+- struct mg_connection *lc = (struct mg_connection *) arg;
+- (void) err;
+- DBG(("%p conn %p from %s:%u", lc, newtpcb, IPADDR_NTOA(&newtpcb->remote_ip),
+- newtpcb->remote_port));
+- struct mg_connection *nc = mg_if_accept_new_conn(lc);
++ struct mg_connection *lc = (struct mg_connection *) arg, *nc;
++ struct mg_lwip_conn_state *lcs, *cs;
++ struct tcp_pcb_listen *lpcb;
++ LOG(LL_DEBUG,
++ ("%p conn %p from %s:%u", lc, newtpcb,
++ IPADDR_NTOA(ipX_2_ip(&newtpcb->remote_ip)), newtpcb->remote_port));
++ if (lc == NULL) {
++ tcp_abort(newtpcb);
++ return ERR_ABRT;
++ }
++ lcs = (struct mg_lwip_conn_state *) lc->sock;
++ lpcb = (struct tcp_pcb_listen *) lcs->pcb.tcp;
++#if TCP_LISTEN_BACKLOG
++ tcp_accepted(lpcb);
++#endif
++ nc = mg_if_accept_new_conn(lc);
+ if (nc == NULL) {
+ tcp_abort(newtpcb);
+ return ERR_ABRT;
+ }
+- struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
++ cs = (struct mg_lwip_conn_state *) nc->sock;
++ cs->lc = lc;
+ cs->pcb.tcp = newtpcb;
++ /* We need to set up callbacks before returning because data may start
++ * arriving immediately. */
+ tcp_arg(newtpcb, nc);
+ tcp_err(newtpcb, mg_lwip_tcp_error_cb);
+ tcp_sent(newtpcb, mg_lwip_tcp_sent_cb);
+@@ -13995,21 +14923,22 @@ static err_t mg_lwip_accept_cb(void *arg
+ #if LWIP_TCP_KEEPALIVE
+ mg_lwip_set_keepalive_params(nc, 60, 10, 6);
+ #endif
+-#if MG_ENABLE_SSL
+- if (lc->flags & MG_F_SSL) {
+- if (mg_ssl_if_conn_accept(nc, lc) != MG_SSL_OK) {
+- LOG(LL_ERROR, ("SSL error"));
+- tcp_close(newtpcb);
+- }
+- } else
+-#endif
+- {
+- mg_lwip_accept_conn(nc, newtpcb);
+- }
++ mg_lwip_post_signal(MG_SIG_ACCEPT, nc);
++ (void) err;
++ (void) lpcb;
+ return ERR_OK;
+ }
+
+-int mg_lwip_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
++struct mg_lwip_if_listen_ctx {
++ struct mg_connection *nc;
++ union socket_address *sa;
++ int ret;
++};
++
++static void mg_lwip_if_listen_tcp_tcpip(void *arg) {
++ struct mg_lwip_if_listen_ctx *ctx = (struct mg_lwip_if_listen_ctx *) arg;
++ struct mg_connection *nc = ctx->nc;
++ union socket_address *sa = ctx->sa;
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+ struct tcp_pcb *tpcb = TCP_NEW();
+ ip_addr_t *ip = (ip_addr_t *) &sa->sin.sin_addr.s_addr;
+@@ -14018,16 +14947,26 @@ int mg_lwip_if_listen_tcp(struct mg_conn
+ DBG(("%p tcp_bind(%s:%u) = %d", nc, IPADDR_NTOA(ip), port, cs->err));
+ if (cs->err != ERR_OK) {
+ tcp_close(tpcb);
+- return -1;
++ ctx->ret = -1;
++ return;
+ }
+ tcp_arg(tpcb, nc);
+ tpcb = tcp_listen(tpcb);
+ cs->pcb.tcp = tpcb;
+ tcp_accept(tpcb, mg_lwip_accept_cb);
+- return 0;
++ ctx->ret = 0;
+ }
+
+-int mg_lwip_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
++int mg_lwip_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
++ struct mg_lwip_if_listen_ctx ctx = {.nc = nc, .sa = sa};
++ tcpip_callback(mg_lwip_if_listen_tcp_tcpip, &ctx);
++ return ctx.ret;
++}
++
++static void mg_lwip_if_listen_udp_tcpip(void *arg) {
++ struct mg_lwip_if_listen_ctx *ctx = (struct mg_lwip_if_listen_ctx *) arg;
++ struct mg_connection *nc = ctx->nc;
++ union socket_address *sa = ctx->sa;
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+ struct udp_pcb *upcb = udp_new();
+ ip_addr_t *ip = (ip_addr_t *) &sa->sin.sin_addr.s_addr;
+@@ -14036,24 +14975,47 @@ int mg_lwip_if_listen_udp(struct mg_conn
+ DBG(("%p udb_bind(%s:%u) = %d", nc, IPADDR_NTOA(ip), port, cs->err));
+ if (cs->err != ERR_OK) {
+ udp_remove(upcb);
+- return -1;
++ ctx->ret = -1;
++ } else {
++ udp_recv(upcb, mg_lwip_udp_recv_cb, nc);
++ cs->pcb.udp = upcb;
++ ctx->ret = 0;
+ }
+- udp_recv(upcb, mg_lwip_udp_recv_cb, nc);
+- cs->pcb.udp = upcb;
+- return 0;
+ }
+
+-int mg_lwip_tcp_write(struct mg_connection *nc, const void *data,
+- uint16_t len) {
++int mg_lwip_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
++ struct mg_lwip_if_listen_ctx ctx = {.nc = nc, .sa = sa};
++ tcpip_callback(mg_lwip_if_listen_udp_tcpip, &ctx);
++ return ctx.ret;
++}
++
++struct mg_lwip_tcp_write_ctx {
++ struct mg_connection *nc;
++ const void *data;
++ uint16_t len;
++ int ret;
++};
++
++static void tcp_output_tcpip(void *arg) {
++ tcp_output((struct tcp_pcb *) arg);
++}
++
++static void mg_lwip_tcp_write_tcpip(void *arg) {
++ struct mg_lwip_tcp_write_ctx *ctx = (struct mg_lwip_tcp_write_ctx *) arg;
++ struct mg_connection *nc = ctx->nc;
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+ struct tcp_pcb *tpcb = cs->pcb.tcp;
+- len = MIN(tpcb->mss, MIN(len, tpcb->snd_buf));
++ size_t len = MIN(tpcb->mss, MIN(ctx->len, tpcb->snd_buf));
++ size_t unsent, unacked;
+ if (len == 0) {
+- DBG(("%p no buf avail %u %u %u %p %p", tpcb, tpcb->acked, tpcb->snd_buf,
+- tpcb->snd_queuelen, tpcb->unsent, tpcb->unacked));
+- tcp_output(tpcb);
+- return 0;
++ DBG(("%p no buf avail %u %u %p %p", tpcb, tpcb->snd_buf, tpcb->snd_queuelen,
++ tpcb->unsent, tpcb->unacked));
++ tcpip_callback(tcp_output_tcpip, tpcb);
++ ctx->ret = 0;
++ return;
+ }
++ unsent = (tpcb->unsent != NULL ? tpcb->unsent->len : 0);
++ unacked = (tpcb->unacked != NULL ? tpcb->unacked->len : 0);
+ /*
+ * On ESP8266 we only allow one TCP segment in flight at any given time.
+ * This may increase latency and reduce efficiency of tcp windowing,
+@@ -14061,75 +15023,124 @@ int mg_lwip_tcp_write(struct mg_connecti
+ * reduce footprint.
+ */
+ #if CS_PLATFORM == CS_P_ESP8266
+- if (tpcb->unacked != NULL) {
+- return 0;
+- }
+- if (tpcb->unsent != NULL) {
+- len = MIN(len, (TCP_MSS - tpcb->unsent->len));
++ if (unacked > 0) {
++ ctx->ret = 0;
++ return;
+ }
++ len = MIN(len, (TCP_MSS - unsent));
+ #endif
+- err_t err = tcp_write(tpcb, data, len, TCP_WRITE_FLAG_COPY);
+- DBG(("%p tcp_write %u = %d", tpcb, len, err));
+- if (err != ERR_OK) {
++ cs->err = tcp_write(tpcb, ctx->data, len, TCP_WRITE_FLAG_COPY);
++ unsent = (tpcb->unsent != NULL ? tpcb->unsent->len : 0);
++ unacked = (tpcb->unacked != NULL ? tpcb->unacked->len : 0);
++ DBG(("%p tcp_write %u = %d, %u %u", tpcb, len, cs->err, unsent, unacked));
++ if (cs->err != ERR_OK) {
+ /*
+ * We ignore ERR_MEM because memory will be freed up when the data is sent
+ * and we'll retry.
+ */
+- return (err == ERR_MEM ? 0 : -1);
++ ctx->ret = (cs->err == ERR_MEM ? 0 : -1);
++ return;
+ }
+- return len;
++ ctx->ret = len;
+ }
+
+-static void mg_lwip_send_more(struct mg_connection *nc) {
++static int mg_lwip_tcp_write(struct mg_connection *nc, const void *data,
++ uint16_t len) {
++ struct mg_lwip_tcp_write_ctx ctx = {.nc = nc, .data = data, .len = len};
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+- if (nc->sock == INVALID_SOCKET || cs->pcb.tcp == NULL) {
+- DBG(("%p invalid socket", nc));
+- return;
+- }
+- int num_written = mg_lwip_tcp_write(nc, nc->send_mbuf.buf, nc->send_mbuf.len);
+- DBG(("%p mg_lwip_tcp_write %u = %d", nc, nc->send_mbuf.len, num_written));
+- if (num_written == 0) return;
+- if (num_written < 0) {
+- mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
++ struct tcp_pcb *tpcb = cs->pcb.tcp;
++ if (tpcb == NULL) {
++ return -1;
+ }
+- mbuf_remove(&nc->send_mbuf, num_written);
+- mbuf_trim(&nc->send_mbuf);
++ tcpip_callback(mg_lwip_tcp_write_tcpip, &ctx);
++ return ctx.ret;
+ }
+
+-void mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf,
+- size_t len) {
+- mbuf_append(&nc->send_mbuf, buf, len);
+- mg_lwip_mgr_schedule_poll(nc->mgr);
++struct udp_sendto_ctx {
++ struct udp_pcb *upcb;
++ struct pbuf *p;
++ ip_addr_t *ip;
++ uint16_t port;
++ int ret;
++};
++
++static void udp_sendto_tcpip(void *arg) {
++ struct udp_sendto_ctx *ctx = (struct udp_sendto_ctx *) arg;
++ ctx->ret = udp_sendto(ctx->upcb, ctx->p, ctx->ip, ctx->port);
+ }
+
+-void mg_lwip_if_udp_send(struct mg_connection *nc, const void *buf,
+- size_t len) {
++static int mg_lwip_udp_send(struct mg_connection *nc, const void *data,
++ uint16_t len) {
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+- if (nc->sock == INVALID_SOCKET || cs->pcb.udp == NULL) {
++ if (cs->pcb.udp == NULL) {
+ /*
+ * In case of UDP, this usually means, what
+ * async DNS resolve is still in progress and connection
+ * is not ready yet
+ */
+ DBG(("%p socket is not connected", nc));
+- return;
++ return -1;
+ }
+ struct udp_pcb *upcb = cs->pcb.udp;
+ struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
+- ip_addr_t *ip = (ip_addr_t *) &nc->sa.sin.sin_addr.s_addr;
++#if defined(LWIP_IPV4) && LWIP_IPV4 && defined(LWIP_IPV6) && LWIP_IPV6
++ ip_addr_t ip = {.u_addr.ip4.addr = nc->sa.sin.sin_addr.s_addr, .type = 0};
++#else
++ ip_addr_t ip = {.addr = nc->sa.sin.sin_addr.s_addr};
++#endif
+ u16_t port = ntohs(nc->sa.sin.sin_port);
+- memcpy(p->payload, buf, len);
+- cs->err = udp_sendto(upcb, p, (ip_addr_t *) ip, port);
+- DBG(("%p udp_sendto = %d", nc, cs->err));
++ if (p == NULL) {
++ DBG(("OOM"));
++ return 0;
++ }
++ memcpy(p->payload, data, len);
++ struct udp_sendto_ctx ctx = {.upcb = upcb, .p = p, .ip = &ip, .port = port};
++ tcpip_callback(udp_sendto_tcpip, &ctx);
++ cs->err = ctx.ret;
+ pbuf_free(p);
+- if (cs->err != ERR_OK) {
+- mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
++ return (cs->err == ERR_OK ? len : -1);
++}
++
++static void mg_lwip_send_more(struct mg_connection *nc) {
++ int num_sent = 0;
++ if (nc->sock == INVALID_SOCKET) return;
++ if (nc->flags & MG_F_UDP) {
++ num_sent = mg_lwip_udp_send(nc, nc->send_mbuf.buf, nc->send_mbuf.len);
++ DBG(("%p mg_lwip_udp_send %u = %d", nc, nc->send_mbuf.len, num_sent));
++ } else {
++ num_sent = mg_lwip_tcp_write(nc, nc->send_mbuf.buf, nc->send_mbuf.len);
++ DBG(("%p mg_lwip_tcp_write %u = %d", nc, nc->send_mbuf.len, num_sent));
++ }
++ if (num_sent == 0) return;
++ if (num_sent > 0) {
++ mg_if_sent_cb(nc, num_sent);
+ } else {
+- cs->num_sent += len;
+- mg_lwip_post_signal(MG_SIG_SENT_CB, nc);
++ mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
+ }
+ }
+
++void mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf,
++ size_t len) {
++ mbuf_append(&nc->send_mbuf, buf, len);
++ mg_lwip_mgr_schedule_poll(nc->mgr);
++}
++
++void mg_lwip_if_udp_send(struct mg_connection *nc, const void *buf,
++ size_t len) {
++ mbuf_append(&nc->send_mbuf, buf, len);
++ mg_lwip_mgr_schedule_poll(nc->mgr);
++}
++
++struct tcp_recved_ctx {
++ struct tcp_pcb *tpcb;
++ size_t len;
++};
++
++void tcp_recved_tcpip(void *arg) {
++ struct tcp_recved_ctx *ctx = (struct tcp_recved_ctx *) arg;
++ tcp_recved(ctx->tpcb, ctx->len);
++}
++
+ void mg_lwip_if_recved(struct mg_connection *nc, size_t len) {
+ if (nc->flags & MG_F_UDP) return;
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+@@ -14137,28 +15148,35 @@ void mg_lwip_if_recved(struct mg_connect
+ DBG(("%p invalid socket", nc));
+ return;
+ }
+- DBG(("%p %p %u", nc, cs->pcb.tcp, len));
+-/* Currently SSL acknowledges data immediately.
+- * TODO(rojer): Find a way to propagate mg_lwip_if_recved. */
++ DBG(("%p %p %u %u", nc, cs->pcb.tcp, len,
++ (cs->rx_chain ? cs->rx_chain->tot_len : 0)));
++ struct tcp_recved_ctx ctx = {.tpcb = cs->pcb.tcp, .len = len};
+ #if MG_ENABLE_SSL
+ if (!(nc->flags & MG_F_SSL)) {
+- tcp_recved(cs->pcb.tcp, len);
++ tcpip_callback(tcp_recved_tcpip, &ctx);
++ } else {
++ /* Currently SSL acknowledges data immediately.
++ * TODO(rojer): Find a way to propagate mg_lwip_if_recved. */
+ }
+ #else
+- tcp_recved(cs->pcb.tcp, len);
++ tcpip_callback(tcp_recved_tcpip, &ctx);
+ #endif
+ mbuf_trim(&nc->recv_mbuf);
+ }
+
+ int mg_lwip_if_create_conn(struct mg_connection *nc) {
+ struct mg_lwip_conn_state *cs =
+- (struct mg_lwip_conn_state *) calloc(1, sizeof(*cs));
++ (struct mg_lwip_conn_state *) MG_CALLOC(1, sizeof(*cs));
+ if (cs == NULL) return 0;
+ cs->nc = nc;
+ nc->sock = (intptr_t) cs;
+ return 1;
+ }
+
++static void udp_remove_tcpip(void *arg) {
++ udp_remove((struct udp_pcb *) arg);
++}
++
+ void mg_lwip_if_destroy_conn(struct mg_connection *nc) {
+ if (nc->sock == INVALID_SOCKET) return;
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+@@ -14168,7 +15186,7 @@ void mg_lwip_if_destroy_conn(struct mg_c
+ tcp_arg(tpcb, NULL);
+ DBG(("%p tcp_close %p", nc, tpcb));
+ tcp_arg(tpcb, NULL);
+- tcp_close(tpcb);
++ tcpip_callback(tcp_close_tcpip, tpcb);
+ }
+ while (cs->rx_chain != NULL) {
+ struct pbuf *seg = cs->rx_chain;
+@@ -14176,16 +15194,16 @@ void mg_lwip_if_destroy_conn(struct mg_c
+ pbuf_free(seg);
+ }
+ memset(cs, 0, sizeof(*cs));
+- free(cs);
++ MG_FREE(cs);
+ } else if (nc->listener == NULL) {
+ /* Only close outgoing UDP pcb or listeners. */
+ struct udp_pcb *upcb = cs->pcb.udp;
+ if (upcb != NULL) {
+ DBG(("%p udp_remove %p", nc, upcb));
+- udp_remove(upcb);
++ tcpip_callback(udp_remove_tcpip, upcb);
+ }
+ memset(cs, 0, sizeof(*cs));
+- free(cs);
++ MG_FREE(cs);
+ }
+ nc->sock = INVALID_SOCKET;
+ }
+@@ -14193,22 +15211,21 @@ void mg_lwip_if_destroy_conn(struct mg_c
+ void mg_lwip_if_get_conn_addr(struct mg_connection *nc, int remote,
+ union socket_address *sa) {
+ memset(sa, 0, sizeof(*sa));
+- if (nc->sock == INVALID_SOCKET) return;
++ if (nc == NULL || nc->sock == INVALID_SOCKET) return;
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+ if (nc->flags & MG_F_UDP) {
+ struct udp_pcb *upcb = cs->pcb.udp;
+ if (remote) {
+ memcpy(sa, &nc->sa, sizeof(*sa));
+- } else {
++ } else if (upcb != NULL) {
+ sa->sin.sin_port = htons(upcb->local_port);
+ SET_ADDR(sa, &upcb->local_ip);
+ }
+ } else {
+ struct tcp_pcb *tpcb = cs->pcb.tcp;
+ if (remote) {
+- sa->sin.sin_port = htons(tpcb->remote_port);
+- SET_ADDR(sa, &tpcb->remote_ip);
+- } else {
++ memcpy(sa, &nc->sa, sizeof(*sa));
++ } else if (tpcb != NULL) {
+ sa->sin.sin_port = htons(tpcb->local_port);
+ SET_ADDR(sa, &tpcb->local_ip);
+ }
+@@ -14241,9 +15258,9 @@ void mg_lwip_if_sock_set(struct mg_conne
+ }
+ /* clang-format on */
+
+-struct mg_iface_vtable mg_lwip_iface_vtable = MG_LWIP_IFACE_VTABLE;
++const struct mg_iface_vtable mg_lwip_iface_vtable = MG_LWIP_IFACE_VTABLE;
+ #if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
+-struct mg_iface_vtable mg_default_iface_vtable = MG_LWIP_IFACE_VTABLE;
++const struct mg_iface_vtable mg_default_iface_vtable = MG_LWIP_IFACE_VTABLE;
+ #endif
+
+ #endif /* MG_ENABLE_NET_IF_LWIP_LOW_LEVEL */
+@@ -14258,7 +15275,7 @@ struct mg_iface_vtable mg_default_iface_
+ #if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
+
+ #ifndef MG_SIG_QUEUE_LEN
+-#define MG_SIG_QUEUE_LEN 16
++#define MG_SIG_QUEUE_LEN 32
+ #endif
+
+ struct mg_ev_mgr_lwip_signal {
+@@ -14275,20 +15292,32 @@ struct mg_ev_mgr_lwip_data {
+ void mg_lwip_post_signal(enum mg_sig_type sig, struct mg_connection *nc) {
+ struct mg_ev_mgr_lwip_data *md =
+ (struct mg_ev_mgr_lwip_data *) nc->iface->data;
+- if (md->sig_queue_len >= MG_SIG_QUEUE_LEN) return;
++ mgos_lock();
++ if (md->sig_queue_len >= MG_SIG_QUEUE_LEN) {
++ mgos_unlock();
++ return;
++ }
+ int end_index = (md->start_index + md->sig_queue_len) % MG_SIG_QUEUE_LEN;
+ md->sig_queue[end_index].sig = sig;
+ md->sig_queue[end_index].nc = nc;
+ md->sig_queue_len++;
++ mg_lwip_mgr_schedule_poll(nc->mgr);
++ mgos_unlock();
+ }
+
+ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
+ struct mg_ev_mgr_lwip_data *md =
+ (struct mg_ev_mgr_lwip_data *) mgr->ifaces[MG_MAIN_IFACE]->data;
+ while (md->sig_queue_len > 0) {
++ mgos_lock();
++ int sig = md->sig_queue[md->start_index].sig;
+ struct mg_connection *nc = md->sig_queue[md->start_index].nc;
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+- switch (md->sig_queue[md->start_index].sig) {
++ md->start_index = (md->start_index + 1) % MG_SIG_QUEUE_LEN;
++ md->sig_queue_len--;
++ mgos_unlock();
++ if (nc->iface == NULL || nc->mgr == NULL) continue;
++ switch (sig) {
+ case MG_SIG_CONNECT_RESULT: {
+ #if MG_ENABLE_SSL
+ if (cs->err == 0 && (nc->flags & MG_F_SSL) &&
+@@ -14302,30 +15331,32 @@ void mg_ev_mgr_lwip_process_signals(stru
+ break;
+ }
+ case MG_SIG_CLOSE_CONN: {
+- nc->flags |= MG_F_CLOSE_IMMEDIATELY;
++ nc->flags |= MG_F_SEND_AND_CLOSE;
+ mg_close_conn(nc);
+ break;
+ }
+ case MG_SIG_RECV: {
+- mg_lwip_handle_recv(nc);
++ cs->recv_pending = 0;
++ if (nc->flags & MG_F_UDP) {
++ mg_lwip_handle_recv_udp(nc);
++ } else {
++ mg_lwip_handle_recv_tcp(nc);
++ }
+ break;
+ }
+- case MG_SIG_SENT_CB: {
+- if (cs->num_sent > 0) mg_if_sent_cb(nc, cs->num_sent);
+- cs->num_sent = 0;
++ case MG_SIG_TOMBSTONE: {
+ break;
+ }
+- case MG_SIG_TOMBSTONE: {
++ case MG_SIG_ACCEPT: {
++ mg_lwip_handle_accept(nc);
+ break;
+ }
+ }
+- md->start_index = (md->start_index + 1) % MG_SIG_QUEUE_LEN;
+- md->sig_queue_len--;
+ }
+ }
+
+ void mg_lwip_if_init(struct mg_iface *iface) {
+- LOG(LL_INFO, ("%p Mongoose init"));
++ LOG(LL_INFO, ("%p Mongoose init", iface));
+ iface->data = MG_CALLOC(1, sizeof(struct mg_ev_mgr_lwip_data));
+ }
+
+@@ -14364,22 +15395,20 @@ time_t mg_lwip_if_poll(struct mg_iface *
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+ tmp = nc->next;
+ n++;
+- if (nc->flags & MG_F_CLOSE_IMMEDIATELY) {
++ if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
++ ((nc->flags & MG_F_SEND_AND_CLOSE) && (nc->flags & MG_F_UDP) &&
++ (nc->send_mbuf.len == 0))) {
+ mg_close_conn(nc);
+ continue;
+ }
+ mg_if_poll(nc, now);
+ mg_if_timer(nc, now);
+- if (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE) &&
+- !(nc->flags & MG_F_WANT_WRITE)) {
+- mg_close_conn(nc);
+- continue;
+- }
+ #if MG_ENABLE_SSL
+ if ((nc->flags & MG_F_SSL) && cs != NULL && cs->pcb.tcp != NULL &&
+ cs->pcb.tcp->state == ESTABLISHED) {
+ if (((nc->flags & MG_F_WANT_WRITE) ||
+- (nc->send_mbuf.len > 0) && (nc->flags & MG_F_SSL_HANDSHAKE_DONE)) &&
++ ((nc->send_mbuf.len > 0) &&
++ (nc->flags & MG_F_SSL_HANDSHAKE_DONE))) &&
+ cs->pcb.tcp->snd_buf > 0) {
+ /* Can write more. */
+ if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
+@@ -14398,14 +15427,14 @@ time_t mg_lwip_if_poll(struct mg_iface *
+ } else
+ #endif /* MG_ENABLE_SSL */
+ {
+- if (!(nc->flags & (MG_F_CONNECTING | MG_F_UDP))) {
+- if (nc->send_mbuf.len > 0) mg_lwip_send_more(nc);
++ if (nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING)) {
++ mg_lwip_send_more(nc);
+ }
+ }
+ if (nc->sock != INVALID_SOCKET &&
+ !(nc->flags & (MG_F_UDP | MG_F_LISTENING)) && cs->pcb.tcp != NULL &&
+ cs->pcb.tcp->unsent != NULL) {
+- tcp_output(cs->pcb.tcp);
++ tcpip_callback(tcp_output_tcpip, cs->pcb.tcp);
+ }
+ if (nc->ev_timer_time > 0) {
+ if (num_timers == 0 || nc->ev_timer_time < min_timer) {
+@@ -14413,6 +15442,19 @@ time_t mg_lwip_if_poll(struct mg_iface *
+ }
+ num_timers++;
+ }
++
++ if (nc->sock != INVALID_SOCKET) {
++ /* Try to consume data from cs->rx_chain */
++ mg_lwip_consume_rx_chain_tcp(nc);
++
++ /*
++ * If the connection is about to close, and rx_chain is finally empty,
++ * send the MG_SIG_CLOSE_CONN signal
++ */
++ if (cs->draining_rx_chain && cs->rx_chain == NULL) {
++ mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
++ }
++ }
+ }
+ #if 0
+ DBG(("end poll @%u, %d conns, %d timers (min %u), next in %d ms",
+@@ -14425,20 +15467,40 @@ time_t mg_lwip_if_poll(struct mg_iface *
+
+ uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr) {
+ struct mg_connection *nc;
+- double now = mg_time();
++ double now;
+ double min_timer = 0;
+ int num_timers = 0;
+ mg_ev_mgr_lwip_process_signals(mgr);
+ for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
++ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+ if (nc->ev_timer_time > 0) {
+ if (num_timers == 0 || nc->ev_timer_time < min_timer) {
+ min_timer = nc->ev_timer_time;
+ }
+ num_timers++;
+ }
++ if (nc->send_mbuf.len > 0
++#if MG_ENABLE_SSL
++ || (nc->flags & MG_F_WANT_WRITE)
++#endif
++ ) {
++ int can_send = 0;
++ /* We have stuff to send, but can we? */
++ if (nc->flags & MG_F_UDP) {
++ /* UDP is always ready for sending. */
++ can_send = (cs->pcb.udp != NULL);
++ } else {
++ can_send = (cs->pcb.tcp != NULL && cs->pcb.tcp->snd_buf > 0);
++ }
++ /* We want and can send, request a poll immediately. */
++ if (can_send) return 0;
++ }
+ }
+ uint32_t timeout_ms = ~0;
++ now = mg_time();
+ if (num_timers > 0) {
++ /* If we have a timer that is past due, do a poll ASAP. */
++ if (min_timer < now) return 0;
+ double timer_timeout_ms = (min_timer - now) * 1000 + 1 /* rounding */;
+ if (timer_timeout_ms < timeout_ms) {
+ timeout_ms = timer_timeout_ms;
+@@ -14458,6 +15520,7 @@ uint32_t mg_lwip_get_poll_delay_ms(struc
+
+ #if MG_ENABLE_SSL && MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
+
++/* Amalgamated: #include "common/mg_mem.h" */
+ /* Amalgamated: #include "common/cs_dbg.h" */
+
+ #include <lwip/pbuf.h>
+@@ -14467,14 +15530,6 @@ uint32_t mg_lwip_get_poll_delay_ms(struc
+ #define MG_LWIP_SSL_IO_SIZE 1024
+ #endif
+
+-/*
+- * Stop processing incoming SSL traffic when recv_mbuf.size is this big.
+- * It'a a uick solution for SSL recv pushback.
+- */
+-#ifndef MG_LWIP_SSL_RECV_MBUF_LIMIT
+-#define MG_LWIP_SSL_RECV_MBUF_LIMIT 3072
+-#endif
+-
+ #ifndef MIN
+ #define MIN(a, b) ((a) < (b) ? (a) : (b))
+ #endif
+@@ -14485,7 +15540,7 @@ void mg_lwip_ssl_do_hs(struct mg_connect
+ enum mg_ssl_if_result res;
+ if (nc->flags & MG_F_CLOSE_IMMEDIATELY) return;
+ res = mg_ssl_if_handshake(nc);
+- DBG(("%p %d %d %d", nc, nc->flags, server_side, res));
++ DBG(("%p %lu %d %d", nc, nc->flags, server_side, res));
+ if (res != MG_SSL_OK) {
+ if (res == MG_SSL_WANT_WRITE) {
+ nc->flags |= MG_F_WANT_WRITE;
+@@ -14533,10 +15588,9 @@ void mg_lwip_ssl_send(struct mg_connecti
+ len = MIN(MG_LWIP_SSL_IO_SIZE, nc->send_mbuf.len);
+ }
+ int ret = mg_ssl_if_write(nc, nc->send_mbuf.buf, len);
+- DBG(("%p SSL_write %u = %d, %d", nc, len, ret));
++ DBG(("%p SSL_write %u = %d", nc, len, ret));
+ if (ret > 0) {
+- mbuf_remove(&nc->send_mbuf, ret);
+- mbuf_trim(&nc->send_mbuf);
++ mg_if_sent_cb(nc, ret);
+ cs->last_ssl_write_size = 0;
+ } else if (ret < 0) {
+ /* This is tricky. We must remember the exact data we were sending to retry
+@@ -14556,13 +15610,13 @@ void mg_lwip_ssl_recv(struct mg_connecti
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+ /* Don't deliver data before connect callback */
+ if (nc->flags & MG_F_CONNECTING) return;
+- while (nc->recv_mbuf.len < MG_LWIP_SSL_RECV_MBUF_LIMIT) {
+- char *buf = (char *) malloc(MG_LWIP_SSL_IO_SIZE);
++ while (nc->recv_mbuf.len < nc->recv_mbuf_limit) {
++ char *buf = (char *) MG_MALLOC(MG_LWIP_SSL_IO_SIZE);
+ if (buf == NULL) return;
+ int ret = mg_ssl_if_read(nc, buf, MG_LWIP_SSL_IO_SIZE);
+ DBG(("%p %p SSL_read %u = %d", nc, cs->rx_chain, MG_LWIP_SSL_IO_SIZE, ret));
+ if (ret <= 0) {
+- free(buf);
++ MG_FREE(buf);
+ if (ret == MG_SSL_WANT_WRITE) {
+ nc->flags |= MG_F_WANT_WRITE;
+ return;
+@@ -14621,8 +15675,8 @@ int ssl_socket_send(void *ctx, const uns
+ struct mg_connection *nc = (struct mg_connection *) ctx;
+ struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+ int ret = mg_lwip_tcp_write(cs->nc, buf, len);
+- DBG(("%p mg_lwip_tcp_write %u = %d", cs->nc, len, ret));
+ if (ret == 0) ret = MBEDTLS_ERR_SSL_WANT_WRITE;
++ LOG(LL_DEBUG, ("%p %d -> %d", nc, len, ret));
+ return ret;
+ }
+
+@@ -14636,6 +15690,7 @@ int ssl_socket_recv(void *ctx, unsigned
+ }
+ size_t seg_len = (seg->len - cs->rx_offset);
+ DBG(("%u %u %u %u", len, cs->rx_chain->len, seg_len, cs->rx_chain->tot_len));
++ mgos_lock();
+ len = MIN(len, seg_len);
+ pbuf_copy_partial(seg, buf, len, cs->rx_offset);
+ cs->rx_offset += len;
+@@ -14647,6 +15702,8 @@ int ssl_socket_recv(void *ctx, unsigned
+ pbuf_free(seg);
+ cs->rx_offset = 0;
+ }
++ mgos_unlock();
++ LOG(LL_DEBUG, ("%p <- %d", nc, (int) len));
+ return len;
+ }
+
+@@ -14751,7 +15808,7 @@ extern "C" {
+ #define MG_ENABLE_NET_IF_PIC32 MG_NET_IF == MG_NET_IF_PIC32
+ #endif
+
+-extern struct mg_iface_vtable mg_pic32_iface_vtable;
++extern const struct mg_iface_vtable mg_pic32_iface_vtable;
+
+ #ifdef __cplusplus
+ }
+@@ -14919,10 +15976,7 @@ static void mg_handle_send(struct mg_con
+ }
+ }
+
+- if (bytes_written != 0) {
+- mbuf_remove(&nc->send_mbuf, bytes_written);
+- mg_if_sent_cb(nc, bytes_written);
+- }
++ mg_if_sent_cb(nc, bytes_written);
+ }
+
+ static void mg_handle_recv(struct mg_connection *nc) {
+@@ -15050,9 +16104,29 @@ void mg_pic32_if_connect_udp(struct mg_c
+ }
+ /* clang-format on */
+
+-struct mg_iface_vtable mg_pic32_iface_vtable = MG_PIC32_IFACE_VTABLE;
++const struct mg_iface_vtable mg_pic32_iface_vtable = MG_PIC32_IFACE_VTABLE;
+ #if MG_NET_IF == MG_NET_IF_PIC32
+-struct mg_iface_vtable mg_default_iface_vtable = MG_PIC32_IFACE_VTABLE;
++const struct mg_iface_vtable mg_default_iface_vtable = MG_PIC32_IFACE_VTABLE;
+ #endif
+
+ #endif /* MG_ENABLE_NET_IF_PIC32 */
++#ifdef MG_MODULE_LINES
++#line 1 "common/platforms/windows/windows_direct.c"
++#endif
++/*
++ * Copyright (c) 2017 Cesanta Software Limited
++ * All rights reserved
++ */
++
++#ifdef _WIN32
++
++int rmdir(const char *dirname) {
++ return _rmdir(dirname);
++}
++
++unsigned int sleep(unsigned int seconds) {
++ Sleep(seconds * 1000);
++ return 0;
++}
++
++#endif /* _WIN32 */
+--- smplayer-18.4.0~ds0.orig/webserver/mongoose.h
++++ smplayer-18.4.0~ds0/webserver/mongoose.h
+@@ -1,5 +1,5 @@
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/common.h"
++#line 1 "mongoose/src/mg_common.h"
+ #endif
+ /*
+ * Copyright (c) 2004-2013 Sergey Lyubka
+@@ -23,7 +23,7 @@
+ #ifndef CS_MONGOOSE_SRC_COMMON_H_
+ #define CS_MONGOOSE_SRC_COMMON_H_
+
+-#define MG_VERSION "6.6"
++#define MG_VERSION "6.11"
+
+ /* Local tweaks, applied before any of Mongoose's own headers. */
+ #ifdef MG_LOCALS
+@@ -46,9 +46,10 @@
+ #define CS_P_WINDOWS 2
+ #define CS_P_ESP32 15
+ #define CS_P_ESP8266 3
++#define CS_P_CC3100 6
+ #define CS_P_CC3200 4
++#define CS_P_CC3220 17
+ #define CS_P_MSP432 5
+-#define CS_P_CC3100 6
+ #define CS_P_TM4C129 14
+ #define CS_P_MBED 7
+ #define CS_P_WINCE 8
+@@ -57,15 +58,18 @@
+ #define CS_P_NRF51 12
+ #define CS_P_NRF52 10
+ #define CS_P_PIC32 11
+-/* Next id: 16 */
++#define CS_P_STM32 16
++/* Next id: 18 */
+
+ /* If not specified explicitly, we guess platform by defines. */
+ #ifndef CS_PLATFORM
+
+ #if defined(TARGET_IS_MSP432P4XX) || defined(__MSP432P401R__)
+ #define CS_PLATFORM CS_P_MSP432
+-#elif defined(cc3200)
++#elif defined(cc3200) || defined(TARGET_IS_CC3200)
+ #define CS_PLATFORM CS_P_CC3200
++#elif defined(cc3220) || defined(TARGET_IS_CC3220)
++#define CS_PLATFORM CS_P_CC3220
+ #elif defined(__unix__) || defined(__APPLE__)
+ #define CS_PLATFORM CS_P_UNIX
+ #elif defined(WINCE)
+@@ -87,6 +91,8 @@
+ #elif defined(TARGET_IS_TM4C129_RA0) || defined(TARGET_IS_TM4C129_RA1) || \
+ defined(TARGET_IS_TM4C129_RA2)
+ #define CS_PLATFORM CS_P_TM4C129
++#elif defined(STM32)
++#define CS_PLATFORM CS_P_STM32
+ #endif
+
+ #ifndef CS_PLATFORM
+@@ -108,8 +114,9 @@
+ /* Amalgamated: #include "common/platforms/platform_windows.h" */
+ /* Amalgamated: #include "common/platforms/platform_esp32.h" */
+ /* Amalgamated: #include "common/platforms/platform_esp8266.h" */
+-/* Amalgamated: #include "common/platforms/platform_cc3200.h" */
+ /* Amalgamated: #include "common/platforms/platform_cc3100.h" */
++/* Amalgamated: #include "common/platforms/platform_cc3200.h" */
++/* Amalgamated: #include "common/platforms/platform_cc3220.h" */
+ /* Amalgamated: #include "common/platforms/platform_mbed.h" */
+ /* Amalgamated: #include "common/platforms/platform_nrf51.h" */
+ /* Amalgamated: #include "common/platforms/platform_nrf52.h" */
+@@ -117,6 +124,7 @@
+ /* Amalgamated: #include "common/platforms/platform_nxp_lpc.h" */
+ /* Amalgamated: #include "common/platforms/platform_nxp_kinetis.h" */
+ /* Amalgamated: #include "common/platforms/platform_pic32.h" */
++/* Amalgamated: #include "common/platforms/platform_stm32.h" */
+
+ /* Common stuff */
+
+@@ -191,6 +199,7 @@
+ #include <stdlib.h>
+ #include <sys/stat.h>
+ #include <time.h>
++#include <ctype.h>
+
+ #ifdef _MSC_VER
+ #pragma comment(lib, "ws2_32.lib") /* Linking with winsock library */
+@@ -201,6 +210,12 @@
+ #include <windows.h>
+ #include <process.h>
+
++#if _MSC_VER < 1700
++typedef int bool;
++#else
++#include <stdbool.h>
++#endif
++
+ #if defined(_MSC_VER) && _MSC_VER >= 1800
+ #define strdup _strdup
+ #endif
+@@ -217,15 +232,13 @@
+ #define __func__ __FILE__ ":" STR(__LINE__)
+ #endif
+ #define snprintf _snprintf
+-#define fileno _fileno
+ #define vsnprintf _vsnprintf
+-#define sleep(x) Sleep((x) *1000)
+ #define to64(x) _atoi64(x)
+ #if !defined(__MINGW32__) && !defined(__MINGW64__)
+ #define popen(x, y) _popen((x), (y))
+ #define pclose(x) _pclose(x)
++#define fileno _fileno
+ #endif
+-#define rmdir _rmdir
+ #if defined(_MSC_VER) && _MSC_VER >= 1400
+ #define fseeko(x, y, z) _fseeki64((x), (y), (z))
+ #else
+@@ -270,6 +283,7 @@ typedef struct _stati64 cs_stat_t;
+ #define S_ISREG(x) (((x) &_S_IFMT) == _S_IFREG)
+ #endif
+ #define DIRSEP '\\'
++#define CS_DEFINE_DIRENT
+
+ #ifndef va_copy
+ #ifdef __va_copy
+@@ -315,6 +329,16 @@ typedef struct _stati64 cs_stat_t;
+ #define MG_NET_IF MG_NET_IF_SOCKET
+ #endif
+
++unsigned int sleep(unsigned int seconds);
++
++/* https://stackoverflow.com/questions/16647819/timegm-cross-platform */
++#define timegm _mkgmtime
++
++#define gmtime_r(a, b) \
++ do { \
++ *(b) = *gmtime(a); \
++ } while (0)
++
+ #endif /* CS_PLATFORM == CS_P_WINDOWS */
+ #endif /* CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_ */
+ #ifdef MG_MODULE_LINES
+@@ -363,6 +387,7 @@ typedef struct _stati64 cs_stat_t;
+ #include <pthread.h>
+ #include <signal.h>
+ #include <stdarg.h>
++#include <stdbool.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -454,6 +479,14 @@ typedef struct stat cs_stat_t;
+ #define MG_NET_IF MG_NET_IF_SOCKET
+ #endif
+
++#ifndef MG_HOSTS_FILE_NAME
++#define MG_HOSTS_FILE_NAME "/etc/hosts"
++#endif
++
++#ifndef MG_RESOLV_CONF_FILE_NAME
++#define MG_RESOLV_CONF_FILE_NAME "/etc/resolv.conf"
++#endif
++
+ #endif /* CS_PLATFORM == CS_P_UNIX */
+ #endif /* CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_ */
+ #ifdef MG_MODULE_LINES
+@@ -470,9 +503,11 @@ typedef struct stat cs_stat_t;
+
+ #include <assert.h>
+ #include <ctype.h>
++#include <dirent.h>
+ #include <fcntl.h>
+ #include <inttypes.h>
+ #include <machine/endian.h>
++#include <stdbool.h>
+ #include <stdint.h>
+ #include <string.h>
+ #include <sys/stat.h>
+@@ -516,6 +551,7 @@ typedef struct stat cs_stat_t;
+ #include <fcntl.h>
+ #include <inttypes.h>
+ #include <machine/endian.h>
++#include <stdbool.h>
+ #include <string.h>
+ #include <sys/stat.h>
+ #include <sys/time.h>
+@@ -523,13 +559,17 @@ typedef struct stat cs_stat_t;
+ #define SIZE_T_FMT "u"
+ typedef struct stat cs_stat_t;
+ #define DIRSEP '/'
++#if !defined(MGOS_VFS_DEFINE_DIRENT)
++#define CS_DEFINE_DIRENT
++#endif
++
+ #define to64(x) strtoll(x, NULL, 10)
+ #define INT64_FMT PRId64
+ #define INT64_X_FMT PRIx64
+ #define __cdecl
+ #define _FILE_OFFSET_BITS 32
+
+-#ifndef RTOS_SDK
++#if !defined(RTOS_SDK) && !defined(__cplusplus)
+ #define fileno(x) -1
+ #endif
+
+@@ -541,9 +581,9 @@ typedef struct stat cs_stat_t;
+ #ifndef MG_NET_IF
+ #include <lwip/opt.h>
+ #if LWIP_SOCKET /* RTOS SDK has LWIP sockets */
+-# define MG_NET_IF MG_NET_IF_SOCKET
++#define MG_NET_IF MG_NET_IF_SOCKET
+ #else
+-# define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL
++#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL
+ #endif
+ #endif
+
+@@ -551,6 +591,12 @@ typedef struct stat cs_stat_t;
+ #define CS_ENABLE_STDIO 1
+ #endif
+
++#define inet_ntop(af, src, dst, size) \
++ (((af) == AF_INET) ? ipaddr_ntoa_r((const ip_addr_t *) (src), (dst), (size)) \
++ : NULL)
++#define inet_pton(af, src, dst) \
++ (((af) == AF_INET) ? ipaddr_aton((src), (ip_addr_t *) (dst)) : 0)
++
+ #endif /* CS_PLATFORM == CS_P_ESP8266 */
+ #endif /* CS_COMMON_PLATFORMS_PLATFORM_ESP8266_H_ */
+ #ifdef MG_MODULE_LINES
+@@ -584,7 +630,7 @@ typedef struct stat cs_stat_t;
+
+ #include <simplelink.h>
+ #include <netapp.h>
+-#undef timeval
++#undef timeval
+
+ typedef int sock_t;
+ #define INVALID_SOCKET (-1)
+@@ -618,6 +664,7 @@ int inet_pton(int af, const char *src, v
+ #include <ctype.h>
+ #include <errno.h>
+ #include <inttypes.h>
++#include <stdbool.h>
+ #include <stdint.h>
+ #include <string.h>
+ #include <time.h>
+@@ -659,6 +706,7 @@ extern "C" {
+ struct SlTimeval_t;
+ #define timeval SlTimeval_t
+ int gettimeofday(struct timeval *t, void *tz);
++int settimeofday(const struct timeval *tv, const void *tz);
+
+ int asprintf(char **strp, const char *fmt, ...);
+
+@@ -682,7 +730,7 @@ struct stat {
+ };
+
+ int _stat(const char *pathname, struct stat *st);
+-#define stat(a, b) _stat(a, b)
++int stat(const char *pathname, struct stat *st);
+
+ #define __S_IFMT 0170000
+
+@@ -698,27 +746,13 @@ int _stat(const char *pathname, struct s
+ #define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)
+ #define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)
+
+-/* As of 5.2.7, TI compiler does not support va_copy() yet. */
++/* 5.x series compilers don't have va_copy, 16.x do. */
++#if __TI_COMPILER_VERSION__ < 16000000
+ #define va_copy(apc, ap) ((apc) = (ap))
++#endif
+
+ #endif /* __TI_COMPILER_VERSION__ */
+
+-#ifdef CC3200_FS_SPIFFS
+-#include <common/spiffs/spiffs.h>
+-
+-typedef struct {
+- spiffs_DIR dh;
+- struct spiffs_dirent de;
+-} DIR;
+-
+-#define d_name name
+-#define dirent spiffs_dirent
+-
+-DIR *opendir(const char *dir_name);
+-int closedir(DIR *dir);
+-struct dirent *readdir(DIR *dir);
+-#endif /* CC3200_FS_SPIFFS */
+-
+ #ifdef CC3200_FS_SLFS
+ #define MG_FS_SLFS
+ #endif
+@@ -726,6 +760,7 @@ struct dirent *readdir(DIR *dir);
+ #if (defined(CC3200_FS_SPIFFS) || defined(CC3200_FS_SLFS)) && \
+ !defined(MG_ENABLE_FILESYSTEM)
+ #define MG_ENABLE_FILESYSTEM 1
++#define CS_DEFINE_DIRENT
+ #endif
+
+ #ifndef CS_ENABLE_STDIO
+@@ -835,7 +870,8 @@ int _stat(const char *pathname, struct s
+ #define CS_ENABLE_STDIO 1
+ #endif
+
+-#if (defined(CC3200_FS_SPIFFS) || defined(CC3200_FS_SLFS)) && !defined(MG_ENABLE_FILESYSTEM)
++#if (defined(CC3200_FS_SPIFFS) || defined(CC3200_FS_SLFS)) && \
++ !defined(MG_ENABLE_FILESYSTEM)
+ #define MG_ENABLE_FILESYSTEM 1
+ #endif
+
+@@ -879,15 +915,15 @@ typedef struct stat cs_stat_t;
+ #define __cdecl
+
+ #ifndef MG_NET_IF
+-# include <lwip/opt.h>
+-# if LWIP_SOCKET
+-# define MG_NET_IF MG_NET_IF_SOCKET
+-# else
+-# define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL
+-# endif
+-# define MG_LWIP 1
++#include <lwip/opt.h>
++#if LWIP_SOCKET
++#define MG_NET_IF MG_NET_IF_SOCKET
++#else
++#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL
++#endif
++#define MG_LWIP 1
+ #elif MG_NET_IF == MG_NET_IF_SIMPLELINK
+-# include "common/platforms/simplelink/cs_simplelink.h"
++/* Amalgamated: #include "common/platforms/simplelink/cs_simplelink.h" */
+ #endif
+
+ #ifndef CS_ENABLE_STDIO
+@@ -1009,16 +1045,16 @@ in_addr_t inet_addr(const char *cp);
+
+ #define to64(x) strtoll(x, NULL, 10)
+
+-#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL
+-#define MG_LWIP 1
+-#define MG_ENABLE_IPV6 1
++#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL
++#define MG_LWIP 1
++#define MG_ENABLE_IPV6 1
+
+ /*
+ * For ARM C Compiler, make lwip to export `struct timeval`; for other
+ * compilers, suppress it.
+ */
+ #if !defined(__ARMCC_VERSION)
+-# define LWIP_TIMEVAL_PRIVATE 0
++#define LWIP_TIMEVAL_PRIVATE 0
+ #else
+ struct timeval;
+ int gettimeofday(struct timeval *tp, void *tzp);
+@@ -1049,18 +1085,19 @@ int gettimeofday(struct timeval *tp, voi
+ #include <ctype.h>
+ #include <errno.h>
+ #include <inttypes.h>
++#include <stdbool.h>
+ #include <stdint.h>
+ #include <string.h>
+ #include <time.h>
+
+ #define to64(x) strtoll(x, NULL, 10)
+
+-#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL
+-#define MG_LWIP 1
+-#define MG_ENABLE_IPV6 1
++#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL
++#define MG_LWIP 1
++#define MG_ENABLE_IPV6 1
+
+ #if !defined(ENOSPC)
+-# define ENOSPC 28 /* No space left on device */
++#define ENOSPC 28 /* No space left on device */
+ #endif
+
+ /*
+@@ -1068,7 +1105,7 @@ int gettimeofday(struct timeval *tp, voi
+ * compilers, suppress it.
+ */
+ #if !defined(__ARMCC_VERSION)
+-# define LWIP_TIMEVAL_PRIVATE 0
++#define LWIP_TIMEVAL_PRIVATE 0
+ #endif
+
+ #define INT64_FMT PRId64
+@@ -1092,9 +1129,10 @@ int gettimeofday(struct timeval *tp, voi
+ #ifndef CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_
+ #define CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_
+
++#if defined(MG_NET_IF) && MG_NET_IF == MG_NET_IF_SIMPLELINK
++
+ /* If simplelink.h is already included, all bets are off. */
+-#if defined(MG_NET_IF) && MG_NET_IF == MG_NET_IF_SIMPLELINK && \
+- !defined(__SIMPLELINK_H__)
++#if !defined(__SIMPLELINK_H__)
+
+ #include <stdbool.h>
+
+@@ -1108,6 +1146,12 @@ int gettimeofday(struct timeval *tp, voi
+ #undef fd_set
+ #endif
+
++#if CS_PLATFORM == CS_P_CC3220
++#include <ti/drivers/net/wifi/porting/user.h>
++#include <ti/drivers/net/wifi/simplelink.h>
++#include <ti/drivers/net/wifi/sl_socket.h>
++#include <ti/drivers/net/wifi/netapp.h>
++#else
+ /* We want to disable SL_INC_STD_BSD_API_NAMING, so we include user.h ourselves
+ * and undef it. */
+ #define PROVISIONING_API_H_
+@@ -1117,6 +1161,7 @@ int gettimeofday(struct timeval *tp, voi
+
+ #include <simplelink/include/simplelink.h>
+ #include <simplelink/include/netapp.h>
++#endif /* CS_PLATFORM == CS_P_CC3220 */
+
+ /* Now define only the subset of the BSD API that we use.
+ * Notably, close(), read() and write() are not defined. */
+@@ -1179,13 +1224,65 @@ int sl_fs_init(void);
+
+ void sl_restart_cb(struct mg_mgr *mgr);
+
+-int sl_set_ssl_opts(struct mg_connection *nc);
++int sl_set_ssl_opts(int sock, struct mg_connection *nc);
+
+ #ifdef __cplusplus
+ }
+ #endif
+
+-#endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK && !defined(__SIMPLELINK_H__) */
++#endif /* !defined(__SIMPLELINK_H__) */
++
++/* Compatibility with older versions of SimpleLink */
++#if SL_MAJOR_VERSION_NUM < 2
++
++#define SL_ERROR_BSD_EAGAIN SL_EAGAIN
++#define SL_ERROR_BSD_EALREADY SL_EALREADY
++#define SL_ERROR_BSD_ENOPROTOOPT SL_ENOPROTOOPT
++#define SL_ERROR_BSD_ESECDATEERROR SL_ESECDATEERROR
++#define SL_ERROR_BSD_ESECSNOVERIFY SL_ESECSNOVERIFY
++#define SL_ERROR_FS_FAILED_TO_ALLOCATE_MEM SL_FS_ERR_FAILED_TO_ALLOCATE_MEM
++#define SL_ERROR_FS_FILE_HAS_NOT_BEEN_CLOSE_CORRECTLY \
++ SL_FS_FILE_HAS_NOT_BEEN_CLOSE_CORRECTLY
++#define SL_ERROR_FS_FILE_NAME_EXIST SL_FS_FILE_NAME_EXIST
++#define SL_ERROR_FS_FILE_NOT_EXISTS SL_FS_ERR_FILE_NOT_EXISTS
++#define SL_ERROR_FS_NO_AVAILABLE_NV_INDEX SL_FS_ERR_NO_AVAILABLE_NV_INDEX
++#define SL_ERROR_FS_NOT_ENOUGH_STORAGE_SPACE SL_FS_ERR_NO_AVAILABLE_BLOCKS
++#define SL_ERROR_FS_NOT_SUPPORTED SL_FS_ERR_NOT_SUPPORTED
++#define SL_ERROR_FS_WRONG_FILE_NAME SL_FS_WRONG_FILE_NAME
++#define SL_ERROR_FS_INVALID_HANDLE SL_FS_ERR_INVALID_HANDLE
++#define SL_NETCFG_MAC_ADDRESS_GET SL_MAC_ADDRESS_GET
++#define SL_SOCKET_FD_ZERO SL_FD_ZERO
++#define SL_SOCKET_FD_SET SL_FD_SET
++#define SL_SOCKET_FD_ISSET SL_FD_ISSET
++#define SL_SO_SECURE_DOMAIN_NAME_VERIFICATION SO_SECURE_DOMAIN_NAME_VERIFICATION
++
++#define SL_FS_READ FS_MODE_OPEN_READ
++#define SL_FS_WRITE FS_MODE_OPEN_WRITE
++
++#define SL_FI_FILE_SIZE(fi) ((fi).FileLen)
++#define SL_FI_FILE_MAX_SIZE(fi) ((fi).AllocatedLen)
++
++#define SlDeviceVersion_t SlVersionFull
++#define sl_DeviceGet sl_DevGet
++#define SL_DEVICE_GENERAL SL_DEVICE_GENERAL_CONFIGURATION
++#define SL_LEN_TYPE _u8
++#define SL_OPT_TYPE _u8
++
++#else /* SL_MAJOR_VERSION_NUM >= 2 */
++
++#define FS_MODE_OPEN_CREATE(max_size, flag) \
++ (SL_FS_CREATE | SL_FS_CREATE_MAX_SIZE(max_size))
++#define SL_FI_FILE_SIZE(fi) ((fi).Len)
++#define SL_FI_FILE_MAX_SIZE(fi) ((fi).MaxSize)
++
++#define SL_LEN_TYPE _u16
++#define SL_OPT_TYPE _u16
++
++#endif /* SL_MAJOR_VERSION_NUM < 2 */
++
++int slfs_open(const unsigned char *fname, uint32_t flags);
++
++#endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK */
+
+ #endif /* CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_ */
+ #ifdef MG_MODULE_LINES
+@@ -1242,6 +1339,10 @@ int sl_set_ssl_opts(struct mg_connection
+ #define EWOULDBLOCK WSAEWOULDBLOCK
+ #endif
+
++#ifndef EAGAIN
++#define EAGAIN EWOULDBLOCK
++#endif
++
+ #ifndef __func__
+ #define STRX(x) #x
+ #define STR(x) STRX(x)
+@@ -1297,6 +1398,7 @@ typedef uint32_t in_addr_t;
+ #define SIZE_T_FMT "u"
+
+ #define DIRSEP '\\'
++#define CS_DEFINE_DIRENT
+
+ #ifndef va_copy
+ #ifdef __va_copy
+@@ -1367,18 +1469,18 @@ typedef struct _stati64 {
+ #endif
+
+ #ifndef _UINTPTR_T_DEFINED
+-typedef unsigned int* uintptr_t;
++typedef unsigned int *uintptr_t;
+ #endif
+
+ #define _S_IFREG 2
+ #define _S_IFDIR 4
+
+ #ifndef S_ISDIR
+-#define S_ISDIR(x) (((x) & _S_IFDIR) != 0)
++#define S_ISDIR(x) (((x) &_S_IFDIR) != 0)
+ #endif
+
+ #ifndef S_ISREG
+-#define S_ISREG(x) (((x) & _S_IFREG) != 0)
++#define S_ISREG(x) (((x) &_S_IFREG) != 0)
+ #endif
+
+ int open(const char *filename, int oflag, int pmode);
+@@ -1415,7 +1517,8 @@ typedef struct stat cs_stat_t;
+ #define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL
+
+ /*
+- * LPCXpress comes with 3 C library implementations: Newlib, NewlibNano and Redlib.
++ * LPCXpress comes with 3 C library implementations: Newlib, NewlibNano and
++ *Redlib.
+ * See https://community.nxp.com/message/630860 for more details.
+ *
+ * Redlib is the default and lacks certain things, so we provide them.
+@@ -1511,12 +1614,54 @@ typedef TCP_SOCKET sock_t;
+ #define CS_ENABLE_STDIO 1
+ #endif
+
+-char* inet_ntoa(struct in_addr in);
++char *inet_ntoa(struct in_addr in);
+
+ #endif /* CS_PLATFORM == CS_P_PIC32 */
+
+ #endif /* CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_ */
+ #ifdef MG_MODULE_LINES
++#line 1 "common/platforms/platform_stm32.h"
++#endif
++/*
++ * Copyright (c) 2014-2016 Cesanta Software Limited
++ * All rights reserved
++ */
++
++#ifndef CS_COMMON_PLATFORMS_PLATFORM_STM32_H_
++#define CS_COMMON_PLATFORMS_PLATFORM_STM32_H_
++#if CS_PLATFORM == CS_P_STM32
++
++#include <ctype.h>
++#include <errno.h>
++#include <fcntl.h>
++#include <stdint.h>
++#include <stdio.h>
++#include <string.h>
++#include <sys/stat.h>
++#include <sys/time.h>
++#include <sys/types.h>
++#include <unistd.h>
++#include <dirent.h>
++
++#include <stm32_sdk_hal.h>
++
++#define to64(x) strtoll(x, NULL, 10)
++#define INT64_FMT PRId64
++#define SIZE_T_FMT "u"
++typedef struct stat cs_stat_t;
++#define DIRSEP '/'
++
++#ifndef CS_ENABLE_STDIO
++#define CS_ENABLE_STDIO 1
++#endif
++
++#ifndef MG_ENABLE_FILESYSTEM
++#define MG_ENABLE_FILESYSTEM 1
++#endif
++
++#endif /* CS_PLATFORM == CS_P_STM32 */
++#endif /* CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ */
++#ifdef MG_MODULE_LINES
+ #line 1 "common/platforms/lwip/mg_lwip.h"
+ #endif
+ /*
+@@ -1580,10 +1725,93 @@ void mg_lwip_set_keepalive_params(struct
+ int interval, int count);
+ #endif
+
++/* For older version of LWIP */
++#ifndef ipX_2_ip
++#define ipX_2_ip(x) (x)
++#endif
++
+ #endif /* MG_LWIP */
+
+ #endif /* CS_COMMON_PLATFORMS_LWIP_MG_LWIP_H_ */
+ #ifdef MG_MODULE_LINES
++#line 1 "common/cs_md5.h"
++#endif
++/*
++ * Copyright (c) 2014 Cesanta Software Limited
++ * All rights reserved
++ */
++
++#ifndef CS_COMMON_MD5_H_
++#define CS_COMMON_MD5_H_
++
++/* Amalgamated: #include "common/platform.h" */
++
++#ifndef CS_DISABLE_MD5
++#define CS_DISABLE_MD5 0
++#endif
++
++#ifdef __cplusplus
++extern "C" {
++#endif /* __cplusplus */
++
++typedef struct {
++ uint32_t buf[4];
++ uint32_t bits[2];
++ unsigned char in[64];
++} cs_md5_ctx;
++
++void cs_md5_init(cs_md5_ctx *c);
++void cs_md5_update(cs_md5_ctx *c, const unsigned char *data, size_t len);
++void cs_md5_final(unsigned char *md, cs_md5_ctx *c);
++
++#ifdef __cplusplus
++}
++#endif /* __cplusplus */
++
++#endif /* CS_COMMON_MD5_H_ */
++#ifdef MG_MODULE_LINES
++#line 1 "common/cs_sha1.h"
++#endif
++/*
++ * Copyright (c) 2014 Cesanta Software Limited
++ * All rights reserved
++ */
++
++#ifndef CS_COMMON_SHA1_H_
++#define CS_COMMON_SHA1_H_
++
++#ifndef CS_DISABLE_SHA1
++#define CS_DISABLE_SHA1 0
++#endif
++
++#if !CS_DISABLE_SHA1
++
++/* Amalgamated: #include "common/platform.h" */
++
++#ifdef __cplusplus
++extern "C" {
++#endif /* __cplusplus */
++
++typedef struct {
++ uint32_t state[5];
++ uint32_t count[2];
++ unsigned char buffer[64];
++} cs_sha1_ctx;
++
++void cs_sha1_init(cs_sha1_ctx *);
++void cs_sha1_update(cs_sha1_ctx *, const unsigned char *data, uint32_t len);
++void cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *);
++void cs_hmac_sha1(const unsigned char *key, size_t key_len,
++ const unsigned char *text, size_t text_len,
++ unsigned char out[20]);
++#ifdef __cplusplus
++}
++#endif /* __cplusplus */
++
++#endif /* CS_DISABLE_SHA1 */
++
++#endif /* CS_COMMON_SHA1_H_ */
++#ifdef MG_MODULE_LINES
+ #line 1 "common/cs_time.h"
+ #endif
+ /*
+@@ -1594,6 +1822,8 @@ void mg_lwip_set_keepalive_params(struct
+ #ifndef CS_COMMON_CS_TIME_H_
+ #define CS_COMMON_CS_TIME_H_
+
++#include <time.h>
++
+ /* Amalgamated: #include "common/platform.h" */
+
+ #ifdef __cplusplus
+@@ -1603,6 +1833,12 @@ extern "C" {
+ /* Sub-second granularity time(). */
+ double cs_time(void);
+
++/*
++ * Similar to (non-standard) timegm, converts broken-down time into the number
++ * of seconds since Unix Epoch.
++ */
++double cs_timegm(const struct tm *tm);
++
+ #ifdef __cplusplus
+ }
+ #endif /* __cplusplus */
+@@ -1625,7 +1861,7 @@ double cs_time(void);
+
+ #ifdef __cplusplus
+ extern "C" {
+-#endif /* __cplusplus */
++#endif
+
+ /* Describes chunk of memory */
+ struct mg_str {
+@@ -1634,15 +1870,21 @@ struct mg_str {
+ };
+
+ /*
+- * Helper functions for creating mg_str struct from plain C string.
++ * Helper function for creating mg_str struct from plain C string.
+ * `NULL` is allowed and becomes `{NULL, 0}`.
+ */
+ struct mg_str mg_mk_str(const char *s);
++
++/*
++ * Like `mg_mk_str`, but takes string length explicitly.
++ */
+ struct mg_str mg_mk_str_n(const char *s, size_t len);
+
+ /* Macro for initializing mg_str. */
+ #define MG_MK_STR(str_literal) \
+ { str_literal, sizeof(str_literal) - 1 }
++#define MG_NULL_STR \
++ { NULL, 0 }
+
+ /*
+ * Cross-platform version of `strcmp()` where where first string is
+@@ -1656,13 +1898,38 @@ int mg_vcmp(const struct mg_str *str2, c
+ */
+ int mg_vcasecmp(const struct mg_str *str2, const char *str1);
+
++/* Creates a copy of s (heap-allocated). */
+ struct mg_str mg_strdup(const struct mg_str s);
++
++/*
++ * Creates a copy of s (heap-allocated).
++ * Resulting string is NUL-terminated (but NUL is not included in len).
++ */
++struct mg_str mg_strdup_nul(const struct mg_str s);
++
++/*
++ * Locates character in a string.
++ */
++const char *mg_strchr(const struct mg_str s, int c);
++
++/*
++ * Compare two `mg_str`s; return value is the same as `strcmp`.
++ */
+ int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
++
++/*
++ * Like `mg_strcmp`, but compares at most `n` characters.
++ */
+ int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n);
+
++/*
++ * Finds the first occurrence of a substring `needle` in the `haystack`.
++ */
++const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle);
++
+ #ifdef __cplusplus
+ }
+-#endif /* __cplusplus */
++#endif
+
+ #endif /* CS_COMMON_MG_STR_H_ */
+ #ifdef MG_MODULE_LINES
+@@ -1748,103 +2015,15 @@ void mbuf_trim(struct mbuf *);
+
+ #endif /* CS_COMMON_MBUF_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "common/sha1.h"
+-#endif
+-/*
+- * Copyright (c) 2014 Cesanta Software Limited
+- * All rights reserved
+- */
+-
+-#ifndef CS_COMMON_SHA1_H_
+-#define CS_COMMON_SHA1_H_
+-
+-#ifndef DISABLE_SHA1
+-#define DISABLE_SHA1 0
+-#endif
+-
+-#if !DISABLE_SHA1
+-
+-/* Amalgamated: #include "common/platform.h" */
+-
+-#ifdef __cplusplus
+-extern "C" {
+-#endif /* __cplusplus */
+-
+-typedef struct {
+- uint32_t state[5];
+- uint32_t count[2];
+- unsigned char buffer[64];
+-} cs_sha1_ctx;
+-
+-void cs_sha1_init(cs_sha1_ctx *);
+-void cs_sha1_update(cs_sha1_ctx *, const unsigned char *data, uint32_t len);
+-void cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *);
+-void cs_hmac_sha1(const unsigned char *key, size_t key_len,
+- const unsigned char *text, size_t text_len,
+- unsigned char out[20]);
+-#ifdef __cplusplus
+-}
+-#endif /* __cplusplus */
+-
+-#endif /* DISABLE_SHA1 */
+-
+-#endif /* CS_COMMON_SHA1_H_ */
+-#ifdef MG_MODULE_LINES
+-#line 1 "common/md5.h"
+-#endif
+-/*
+- * Copyright (c) 2014 Cesanta Software Limited
+- * All rights reserved
+- */
+-
+-#ifndef CS_COMMON_MD5_H_
+-#define CS_COMMON_MD5_H_
+-
+-/* Amalgamated: #include "common/platform.h" */
+-
+-#ifndef DISABLE_MD5
+-#define DISABLE_MD5 0
+-#endif
+-
+-#ifdef __cplusplus
+-extern "C" {
+-#endif /* __cplusplus */
+-
+-typedef struct MD5Context {
+- uint32_t buf[4];
+- uint32_t bits[2];
+- unsigned char in[64];
+-} MD5_CTX;
+-
+-void MD5_Init(MD5_CTX *c);
+-void MD5_Update(MD5_CTX *c, const unsigned char *data, size_t len);
+-void MD5_Final(unsigned char *md, MD5_CTX *c);
+-
+-/*
+- * Return stringified MD5 hash for NULL terminated list of pointer/length pairs.
+- * A length should be specified as size_t variable.
+- * Example:
+- *
+- * char buf[33];
+- * cs_md5(buf, "foo", (size_t) 3, "bar", (size_t) 3, NULL);
+- */
+-char *cs_md5(char buf[33], ...);
+-
+-#ifdef __cplusplus
+-}
+-#endif /* __cplusplus */
+-
+-#endif /* CS_COMMON_MD5_H_ */
+-#ifdef MG_MODULE_LINES
+-#line 1 "common/base64.h"
++#line 1 "common/cs_base64.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+-#ifndef CS_COMMON_BASE64_H_
+-#define CS_COMMON_BASE64_H_
++#ifndef CS_COMMON_CS_BASE64_H_
++#define CS_COMMON_CS_BASE64_H_
+
+ #ifndef DISABLE_BASE64
+ #define DISABLE_BASE64 0
+@@ -1883,7 +2062,7 @@ int cs_base64_decode(const unsigned char
+
+ #endif /* DISABLE_BASE64 */
+
+-#endif /* CS_COMMON_BASE64_H_ */
++#endif /* CS_COMMON_CS_BASE64_H_ */
+ #ifdef MG_MODULE_LINES
+ #line 1 "common/str_util.h"
+ #endif
+@@ -1898,6 +2077,7 @@ int cs_base64_decode(const unsigned char
+ #include <stdarg.h>
+ #include <stdlib.h>
+
++/* Amalgamated: #include "common/mg_str.h" */
+ /* Amalgamated: #include "common/platform.h" */
+
+ #ifndef CS_ENABLE_STRDUP
+@@ -1929,9 +2109,21 @@ int cs_base64_decode(const unsigned char
+ extern "C" {
+ #endif
+
++/*
++ * Equivalent of standard `strnlen()`.
++ */
+ size_t c_strnlen(const char *s, size_t maxlen);
++
++/*
++ * Equivalent of standard `snprintf()`.
++ */
+ int c_snprintf(char *buf, size_t buf_size, const char *format, ...);
++
++/*
++ * Equivalent of standard `vsnprintf()`.
++ */
+ int c_vsnprintf(char *buf, size_t buf_size, const char *format, va_list ap);
++
+ /*
+ * Find the first occurrence of find in s, where the search is limited to the
+ * first slen characters of s.
+@@ -1952,6 +2144,9 @@ void cs_to_hex(char *to, const unsigned
+ void cs_from_hex(char *to, const char *p, size_t len);
+
+ #if CS_ENABLE_STRDUP
++/*
++ * Equivalent of standard `strdup()`, defined if only `CS_ENABLE_STRDUP` is 1.
++ */
+ char *strdup(const char *src);
+ #endif
+
+@@ -1979,12 +2174,14 @@ int mg_casecmp(const char *s1, const cha
+ * enough buffer on heap and returns allocated buffer.
+ * This is a supposed use case:
+ *
++ * ```c
+ * char buf[5], *p = buf;
+ * mg_avprintf(&p, sizeof(buf), "%s", "hi there");
+ * use_p_somehow(p);
+ * if (p != buf) {
+ * free(p);
+ * }
++ * ```
+ *
+ * The purpose of this is to avoid malloc-ing if generated strings are small.
+ */
+@@ -1993,6 +2190,55 @@ int mg_asprintf(char **buf, size_t size,
+ /* Same as mg_asprintf, but takes varargs list. */
+ int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
+
++/*
++ * A helper function for traversing a comma separated list of values.
++ * It returns a list pointer shifted to the next value or NULL if the end
++ * of the list found.
++ * The value is stored in a val vector. If the value has a form "x=y", then
++ * eq_val vector is initialised to point to the "y" part, and val vector length
++ * is adjusted to point only to "x".
++ * If the list is just a comma separated list of entries, like "aa,bb,cc" then
++ * `eq_val` will contain zero-length string.
++ *
++ * The purpose of this function is to parse comma separated string without
++ * any copying/memory allocation.
++ */
++const char *mg_next_comma_list_entry(const char *list, struct mg_str *val,
++ struct mg_str *eq_val);
++
++/*
++ * Like `mg_next_comma_list_entry()`, but takes `list` as `struct mg_str`.
++ */
++struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val,
++ struct mg_str *eq_val);
++
++/*
++ * Matches 0-terminated string (mg_match_prefix) or string with given length
++ * mg_match_prefix_n against a glob pattern. Glob syntax:
++ * ```
++ * - * matches zero or more characters until a slash character /
++ * - ** matches zero or more characters
++ * - ? Matches exactly one character which is not a slash /
++ * - | or , divides alternative patterns
++ * - any other character matches itself
++ * ```
++ * Match is case-insensitive. Return number of bytes matched.
++ * Examples:
++ * ```
++ * mg_match_prefix("a*f", len, "abcdefgh") == 6
++ * mg_match_prefix("a*f", len, "abcdexgh") == 0
++ * mg_match_prefix("a*f|de*,xy", len, "defgh") == 5
++ * mg_match_prefix("?*", len, "abc") == 3
++ * mg_match_prefix("?*", len, "") == 0
++ * ```
++ */
++size_t mg_match_prefix(const char *pattern, int pattern_len, const char *str);
++
++/*
++ * Like `mg_match_prefix()`, but takes `pattern` and `str` as `struct mg_str`.
++ */
++size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str);
++
+ #ifdef __cplusplus
+ }
+ #endif
+@@ -2754,7 +3000,7 @@ struct { \
+
+ #endif /* !_SYS_QUEUE_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/features.h"
++#line 1 "mongoose/src/mg_features.h"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+@@ -2856,14 +3102,14 @@ struct { \
+ #define MG_ENABLE_IPV6 0
+ #endif
+
+-#ifndef MG_ENABLE_JAVASCRIPT
+-#define MG_ENABLE_JAVASCRIPT 0
+-#endif
+-
+ #ifndef MG_ENABLE_MQTT
+ #define MG_ENABLE_MQTT 1
+ #endif
+
++#ifndef MG_ENABLE_SOCKS
++#define MG_ENABLE_SOCKS 0
++#endif
++
+ #ifndef MG_ENABLE_MQTT_BROKER
+ #define MG_ENABLE_MQTT_BROKER 0
+ #endif
+@@ -2911,10 +3157,6 @@ struct { \
+ (CS_PLATFORM == CS_P_WINDOWS || CS_PLATFORM == CS_P_UNIX)
+ #endif
+
+-#ifndef MG_ENABLE_TUN
+-#define MG_ENABLE_TUN MG_ENABLE_HTTP_WEBSOCKET
+-#endif
+-
+ #ifndef MG_ENABLE_SNTP
+ #define MG_ENABLE_SNTP 0
+ #endif
+@@ -2923,9 +3165,21 @@ struct { \
+ #define MG_ENABLE_EXTRA_ERRORS_DESC 0
+ #endif
+
++#ifndef MG_ENABLE_CALLBACK_USERDATA
++#define MG_ENABLE_CALLBACK_USERDATA 0
++#endif
++
++#if MG_ENABLE_CALLBACK_USERDATA
++#define MG_UD_ARG(ud) , ud
++#define MG_CB(cb, ud) cb, ud
++#else
++#define MG_UD_ARG(ud)
++#define MG_CB(cb, ud) cb
++#endif
++
+ #endif /* CS_MONGOOSE_SRC_FEATURES_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/net_if.h"
++#line 1 "mongoose/src/mg_net_if.h"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+@@ -2962,7 +3216,7 @@ struct mg_iface_vtable;
+ struct mg_iface {
+ struct mg_mgr *mgr;
+ void *data; /* Implementation-specific data */
+- struct mg_iface_vtable *vtable;
++ const struct mg_iface_vtable *vtable;
+ };
+
+ struct mg_iface_vtable {
+@@ -3001,11 +3255,11 @@ struct mg_iface_vtable {
+ union socket_address *sa);
+ };
+
+-extern struct mg_iface_vtable *mg_ifaces[];
++extern const struct mg_iface_vtable *mg_ifaces[];
+ extern int mg_num_ifaces;
+
+ /* Creates a new interface instance. */
+-struct mg_iface *mg_if_create_iface(struct mg_iface_vtable *vtable,
++struct mg_iface *mg_if_create_iface(const struct mg_iface_vtable *vtable,
+ struct mg_mgr *mgr);
+
+ /*
+@@ -3013,7 +3267,7 @@ struct mg_iface *mg_if_create_iface(stru
+ * interface `from`, exclusive. Returns NULL if none is found.
+ */
+ struct mg_iface *mg_find_iface(struct mg_mgr *mgr,
+- struct mg_iface_vtable *vtable,
++ const struct mg_iface_vtable *vtable,
+ struct mg_iface *from);
+ /*
+ * Deliver a new TCP connection. Returns NULL in case on error (unable to
+@@ -3058,7 +3312,7 @@ void mg_if_timer(struct mg_connection *c
+
+ #endif /* CS_MONGOOSE_SRC_NET_IF_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/ssl_if.h"
++#line 1 "mongoose/src/mg_ssl_if.h"
+ #endif
+ /*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+@@ -3091,6 +3345,9 @@ struct mg_ssl_if_conn_params {
+ const char *key;
+ const char *ca_cert;
+ const char *server_name;
++ const char *cipher_suites;
++ const char *psk_identity;
++ const char *psk_key;
+ };
+
+ enum mg_ssl_if_result mg_ssl_if_conn_init(
+@@ -3098,6 +3355,7 @@ enum mg_ssl_if_result mg_ssl_if_conn_ini
+ const char **err_msg);
+ enum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,
+ struct mg_connection *lc);
++void mg_ssl_if_conn_close_notify(struct mg_connection *nc);
+ void mg_ssl_if_conn_free(struct mg_connection *nc);
+
+ enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc);
+@@ -3112,7 +3370,7 @@ int mg_ssl_if_write(struct mg_connection
+
+ #endif /* CS_MONGOOSE_SRC_SSL_IF_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/net.h"
++#line 1 "mongoose/src/mg_net.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -3144,13 +3402,8 @@ int mg_ssl_if_write(struct mg_connection
+ #ifndef CS_MONGOOSE_SRC_NET_H_
+ #define CS_MONGOOSE_SRC_NET_H_
+
+-#if MG_ENABLE_JAVASCRIPT
+-#define EXCLUDE_COMMON
+-#include <v7.h>
+-#endif
+-
+-/* Amalgamated: #include "mongoose/src/common.h" */
+-/* Amalgamated: #include "mongoose/src/net_if.h" */
++/* Amalgamated: #include "mg_common.h" */
++/* Amalgamated: #include "mg_net_if.h" */
+ /* Amalgamated: #include "common/mbuf.h" */
+
+ #ifndef MG_VPRINTF_BUFFER_SIZE
+@@ -3186,13 +3439,13 @@ struct mg_connection;
+ * Mongoose calls the event handler, passing the events defined below.
+ */
+ typedef void (*mg_event_handler_t)(struct mg_connection *nc, int ev,
+- void *ev_data);
++ void *ev_data MG_UD_ARG(void *user_data));
+
+ /* Events. Meaning of event parameter (evp) is given in the comment. */
+ #define MG_EV_POLL 0 /* Sent to each connection on each mg_mgr_poll() call */
+ #define MG_EV_ACCEPT 1 /* New connection accepted. union socket_address * */
+ #define MG_EV_CONNECT 2 /* connect() succeeded or failed. int * */
+-#define MG_EV_RECV 3 /* Data has benn received. int *num_bytes */
++#define MG_EV_RECV 3 /* Data has been received. int *num_bytes */
+ #define MG_EV_SEND 4 /* Data has been written to a socket. int *num_bytes */
+ #define MG_EV_CLOSE 5 /* Connection is closed. NULL */
+ #define MG_EV_TIMER 6 /* now >= conn->ev_timer_time. double * */
+@@ -3211,9 +3464,7 @@ struct mg_mgr {
+ void *user_data; /* User data */
+ int num_ifaces;
+ struct mg_iface **ifaces; /* network interfaces */
+-#if MG_ENABLE_JAVASCRIPT
+- struct v7 *v7;
+-#endif
++ const char *nameserver; /* DNS server to use */
+ };
+
+ /*
+@@ -3247,8 +3498,8 @@ struct mg_connection {
+ * void pointers, since some archs might have fat pointers for functions.
+ */
+ mg_event_handler_t f;
+- } priv_1; /* Used by mg_enable_multithreading() */
+- void *priv_2; /* Used by mg_enable_multithreading() */
++ } priv_1;
++ void *priv_2;
+ void *mgr_data; /* Implementation-specific event manager's data. */
+ struct mg_iface *iface;
+ unsigned long flags;
+@@ -3264,12 +3515,11 @@ struct mg_connection {
+ #define MG_F_IS_WEBSOCKET (1 << 8) /* Websocket specific */
+
+ /* Flags that are settable by user */
+-#define MG_F_SEND_AND_CLOSE (1 << 10) /* Push remaining data and close */
+-#define MG_F_CLOSE_IMMEDIATELY (1 << 11) /* Disconnect */
+-#define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12) /* Websocket specific */
+-#define MG_F_DELETE_CHUNK (1 << 13) /* HTTP specific */
+-#define MG_F_ENABLE_BROADCAST (1 << 14) /* Allow broadcast address usage */
+-#define MG_F_TUN_DO_NOT_RECONNECT (1 << 15) /* Don't reconnect tunnel */
++#define MG_F_SEND_AND_CLOSE (1 << 10) /* Push remaining data and close */
++#define MG_F_CLOSE_IMMEDIATELY (1 << 11) /* Disconnect */
++#define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12) /* Websocket specific */
++#define MG_F_DELETE_CHUNK (1 << 13) /* HTTP specific */
++#define MG_F_ENABLE_BROADCAST (1 << 14) /* Allow broadcast address usage */
+
+ #define MG_F_USER_1 (1 << 20) /* Flags left for application */
+ #define MG_F_USER_2 (1 << 21)
+@@ -3305,9 +3555,10 @@ void mg_mgr_init(struct mg_mgr *mgr, voi
+ * `num_ifaces` pointers it contains will be reclaimed by `mg_mgr_free`.
+ */
+ struct mg_mgr_init_opts {
+- struct mg_iface_vtable *main_iface;
++ const struct mg_iface_vtable *main_iface;
+ int num_ifaces;
+- struct mg_iface_vtable **ifaces;
++ const struct mg_iface_vtable **ifaces;
++ const char *nameserver;
+ };
+
+ /*
+@@ -3349,7 +3600,8 @@ time_t mg_mgr_poll(struct mg_mgr *, int
+ * be passed as the `ev_data` pointer. Maximum message size is capped
+ * by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes.
+ */
+-void mg_broadcast(struct mg_mgr *, mg_event_handler_t func, void *, size_t);
++void mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data,
++ size_t len);
+ #endif
+
+ /*
+@@ -3365,7 +3617,7 @@ void mg_broadcast(struct mg_mgr *, mg_ev
+ * }
+ * ```
+ */
+-struct mg_connection *mg_next(struct mg_mgr *, struct mg_connection *);
++struct mg_connection *mg_next(struct mg_mgr *mgr, struct mg_connection *c);
+
+ /*
+ * Optional parameters to `mg_add_sock_opt()`.
+@@ -3386,7 +3638,9 @@ struct mg_add_sock_opts {
+ *
+ * For more options see the `mg_add_sock_opt` variant.
+ */
+-struct mg_connection *mg_add_sock(struct mg_mgr *, sock_t, mg_event_handler_t);
++struct mg_connection *mg_add_sock(struct mg_mgr *mgr, sock_t sock,
++ MG_CB(mg_event_handler_t handler,
++ void *user_data));
+
+ /*
+ * Creates a connection, associates it with the given socket and event handler
+@@ -3394,9 +3648,10 @@ struct mg_connection *mg_add_sock(struct
+ *
+ * See the `mg_add_sock_opts` structure for a description of the options.
+ */
+-struct mg_connection *mg_add_sock_opt(struct mg_mgr *, sock_t,
+- mg_event_handler_t,
+- struct mg_add_sock_opts);
++struct mg_connection *mg_add_sock_opt(struct mg_mgr *mgr, sock_t sock,
++ MG_CB(mg_event_handler_t handler,
++ void *user_data),
++ struct mg_add_sock_opts opts);
+
+ /*
+ * Optional parameters to `mg_bind_opt()`.
+@@ -3410,15 +3665,30 @@ struct mg_bind_opts {
+ const char **error_string; /* Placeholder for the error string */
+ struct mg_iface *iface; /* Interface instance */
+ #if MG_ENABLE_SSL
+- /* SSL settings. */
+- const char *ssl_cert; /* Server certificate to present to clients
+- * Or client certificate to present to tunnel
+- * dispatcher. */
+- const char *ssl_key; /* Private key corresponding to the certificate.
+- If ssl_cert is set but ssl_key is not, ssl_cert
+- is used. */
+- const char *ssl_ca_cert; /* CA bundle used to verify client certificates or
+- * tunnel dispatchers. */
++ /*
++ * SSL settings.
++ *
++ * Server certificate to present to clients or client certificate to
++ * present to tunnel dispatcher (for tunneled connections).
++ */
++ const char *ssl_cert;
++ /* Private key corresponding to the certificate. If ssl_cert is set but
++ * ssl_key is not, ssl_cert is used. */
++ const char *ssl_key;
++ /* CA bundle used to verify client certificates or tunnel dispatchers. */
++ const char *ssl_ca_cert;
++ /* Colon-delimited list of acceptable cipher suites.
++ * Names depend on the library used, for example:
++ *
++ * ECDH-ECDSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256 (OpenSSL)
++ * TLS-ECDH-ECDSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256
++ * (mbedTLS)
++ *
++ * For OpenSSL the list can be obtained by running "openssl ciphers".
++ * For mbedTLS, names can be found in library/ssl_ciphersuites.c
++ * If NULL, a reasonable default is used.
++ */
++ const char *ssl_cipher_suites;
+ #endif
+ };
+
+@@ -3427,8 +3697,9 @@ struct mg_bind_opts {
+ *
+ * See `mg_bind_opt` for full documentation.
+ */
+-struct mg_connection *mg_bind(struct mg_mgr *, const char *,
+- mg_event_handler_t);
++struct mg_connection *mg_bind(struct mg_mgr *mgr, const char *address,
++ MG_CB(mg_event_handler_t handler,
++ void *user_data));
+ /*
+ * Creates a listening connection.
+ *
+@@ -3437,7 +3708,7 @@ struct mg_connection *mg_bind(struct mg_
+ * `address` can be just a port number, e.g. `:8000`. To bind to a specific
+ * interface, an IP address can be specified, e.g. `1.2.3.4:8000`. By default,
+ * a TCP connection is created. To create UDP connection, prepend `udp://`
+- * prefix, e.g. `udp://:8000`. To summarize, `address` paramer has following
++ * prefix, e.g. `udp://:8000`. To summarize, `address` parameter has following
+ * format: `[PROTO://][IP_ADDRESS]:PORT`, where `PROTO` could be `tcp` or
+ * `udp`.
+ *
+@@ -3448,7 +3719,8 @@ struct mg_connection *mg_bind(struct mg_
+ * NOTE: The connection remains owned by the manager, do not free().
+ */
+ struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
+- mg_event_handler_t handler,
++ MG_CB(mg_event_handler_t handler,
++ void *user_data),
+ struct mg_bind_opts opts);
+
+ /* Optional parameters to `mg_connect_opt()` */
+@@ -3457,16 +3729,35 @@ struct mg_connect_opts {
+ unsigned int flags; /* Extra connection flags */
+ const char **error_string; /* Placeholder for the error string */
+ struct mg_iface *iface; /* Interface instance */
++ const char *nameserver; /* DNS server to use, NULL for default */
+ #if MG_ENABLE_SSL
+- /* SSL settings. */
+- const char *ssl_cert; /* Client certificate to present to the server */
+- const char *ssl_key; /* Private key corresponding to the certificate.
+- If ssl_cert is set but ssl_key is not, ssl_cert
+- is used. */
+- const char *ssl_ca_cert; /* Verify server certificate using this CA bundle.
+- If set to "*", then SSL is enabled but no cert
+- verification is performed. */
+-
++ /*
++ * SSL settings.
++ * Client certificate to present to the server.
++ */
++ const char *ssl_cert;
++ /*
++ * Private key corresponding to the certificate.
++ * If ssl_cert is set but ssl_key is not, ssl_cert is used.
++ */
++ const char *ssl_key;
++ /*
++ * Verify server certificate using this CA bundle. If set to "*", then SSL
++ * is enabled but no cert verification is performed.
++ */
++ const char *ssl_ca_cert;
++ /* Colon-delimited list of acceptable cipher suites.
++ * Names depend on the library used, for example:
++ *
++ * ECDH-ECDSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256 (OpenSSL)
++ * TLS-ECDH-ECDSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256
++ * (mbedTLS)
++ *
++ * For OpenSSL the list can be obtained by running "openssl ciphers".
++ * For mbedTLS, names can be found in library/ssl_ciphersuites.c
++ * If NULL, a reasonable default is used.
++ */
++ const char *ssl_cipher_suites;
+ /*
+ * Server name verification. If ssl_ca_cert is set and the certificate has
+ * passed verification, its subject will be verified against this string.
+@@ -3475,6 +3766,15 @@ struct mg_connect_opts {
+ * name verification.
+ */
+ const char *ssl_server_name;
++ /*
++ * PSK identity and key. Identity is a NUL-terminated string and key is a hex
++ * string. Key must be either 16 or 32 bytes (32 or 64 hex digits) for AES-128
++ * or AES-256 respectively.
++ * Note: Default list of cipher suites does not include PSK suites, if you
++ * want to use PSK you will need to set ssl_cipher_suites as well.
++ */
++ const char *ssl_psk_identity;
++ const char *ssl_psk_key;
+ #endif
+ };
+
+@@ -3484,7 +3784,8 @@ struct mg_connect_opts {
+ * See `mg_connect_opt()` for full documentation.
+ */
+ struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *address,
+- mg_event_handler_t handler);
++ MG_CB(mg_event_handler_t handler,
++ void *user_data));
+
+ /*
+ * Connects to a remote host.
+@@ -3535,7 +3836,8 @@ struct mg_connection *mg_connect(struct
+ * ```
+ */
+ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
+- mg_event_handler_t handler,
++ MG_CB(mg_event_handler_t handler,
++ void *user_data),
+ struct mg_connect_opts opts);
+
+ #if MG_ENABLE_SSL && MG_NET_IF != MG_NET_IF_SIMPLELINK
+@@ -3616,7 +3918,7 @@ int mg_resolve(const char *domain_name,
+ * is to allow all access. On each request the full list is traversed,
+ * and the last match wins. Example:
+ *
+- * `-0.0.0.0/0,+192.168/16` - deny all acccesses, only allow 192.168/16 subnet
++ * `-0.0.0.0/0,+192.168/16` - deny all accesses, only allow 192.168/16 subnet
+ *
+ * To learn more about subnet masks, see this
+ * link:https://en.wikipedia.org/wiki/Subnetwork[Wikipedia page on Subnetwork].
+@@ -3626,35 +3928,6 @@ int mg_resolve(const char *domain_name,
+ int mg_check_ip_acl(const char *acl, uint32_t remote_ip);
+
+ /*
+- * Optional parameters for mg_enable_multithreading_opt()
+- */
+-struct mg_multithreading_opts {
+- int poll_timeout; /* Polling interval */
+-};
+-
+-/*
+- * Enables multi-threaded handling for the given listening connection `nc`.
+- * For each accepted connection, Mongoose will create a separate thread
+- * and run an event handler in that thread. Thus, if an event handler is doing
+- * a blocking call or some long computation, it will not slow down
+- * other connections.
+- */
+-void mg_enable_multithreading(struct mg_connection *nc);
+-void mg_enable_multithreading_opt(struct mg_connection *nc,
+- struct mg_multithreading_opts opts);
+-
+-#if MG_ENABLE_JAVASCRIPT
+-/*
+- * Enables server-side JavaScript scripting.
+- * Requires a `-DMG_ENABLE_JAVASCRIPT` compilation flag and V7 engine sources.
+- * V7 instance must not be destroyed during manager's lifetime.
+- * Returns a V7 error.
+- */
+-enum v7_err mg_enable_javascript(struct mg_mgr *m, struct v7 *v7,
+- const char *init_js_file_name);
+-#endif
+-
+-/*
+ * Schedules an MG_EV_TIMER event to be delivered at `timestamp` time.
+ * `timestamp` is UNIX time (the number of seconds since Epoch). It is
+ * `double` instead of `time_t` to allow for sub-second precision.
+@@ -3691,7 +3964,7 @@ double mg_time(void);
+
+ #endif /* CS_MONGOOSE_SRC_NET_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/uri.h"
++#line 1 "mongoose/src/mg_uri.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -3705,7 +3978,7 @@ double mg_time(void);
+ #ifndef CS_MONGOOSE_SRC_URI_H_
+ #define CS_MONGOOSE_SRC_URI_H_
+
+-/* Amalgamated: #include "mongoose/src/net.h" */
++/* Amalgamated: #include "mg_net.h" */
+
+ #ifdef __cplusplus
+ extern "C" {
+@@ -3734,11 +4007,26 @@ extern "C" {
+ *
+ * Returns 0 on success, -1 on error.
+ */
+-int mg_parse_uri(struct mg_str uri, struct mg_str *scheme,
++int mg_parse_uri(const struct mg_str uri, struct mg_str *scheme,
+ struct mg_str *user_info, struct mg_str *host,
+ unsigned int *port, struct mg_str *path, struct mg_str *query,
+ struct mg_str *fragment);
+
++/*
++ * Assemble URI from parts. Any of the inputs can be NULL or zero-length mg_str.
++ *
++ * If normalize_path is true, path is normalized by resolving relative refs.
++ *
++ * Result is a heap-allocated string (uri->p must be free()d after use).
++ *
++ * Returns 0 on success, -1 on error.
++ */
++int mg_assemble_uri(const struct mg_str *scheme, const struct mg_str *user_info,
++ const struct mg_str *host, unsigned int port,
++ const struct mg_str *path, const struct mg_str *query,
++ const struct mg_str *fragment, int normalize_path,
++ struct mg_str *uri);
++
+ int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out);
+
+ #ifdef __cplusplus
+@@ -3746,7 +4034,7 @@ int mg_normalize_uri_path(const struct m
+ #endif /* __cplusplus */
+ #endif /* CS_MONGOOSE_SRC_URI_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/util.h"
++#line 1 "mongoose/src/mg_util.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -3762,15 +4050,19 @@ int mg_normalize_uri_path(const struct m
+
+ #include <stdio.h>
+
+-/* Amalgamated: #include "mongoose/src/common.h" */
+-/* Amalgamated: #include "mongoose/src/net_if.h" */
++/* Amalgamated: #include "mg_common.h" */
++/* Amalgamated: #include "mg_net_if.h" */
+
+ #ifdef __cplusplus
+ extern "C" {
+ #endif /* __cplusplus */
+
+-#ifndef MAX_PATH_SIZE
+-#define MAX_PATH_SIZE 500
++#ifndef MG_MAX_PATH
++#ifdef PATH_MAX
++#define MG_MAX_PATH PATH_MAX
++#else
++#define MG_MAX_PATH 256
++#endif
+ #endif
+
+ /*
+@@ -3831,6 +4123,21 @@ FILE *mg_fopen(const char *path, const c
+ * Return value is the same as for the `open()` syscall.
+ */
+ int mg_open(const char *path, int flag, int mode);
++
++/*
++ * Reads data from the given file stream.
++ *
++ * Return value is a number of bytes readen.
++ */
++size_t mg_fread(void *ptr, size_t size, size_t count, FILE *f);
++
++/*
++ * Writes data to the given file stream.
++ *
++ * Return value is a number of bytes wtitten.
++ */
++size_t mg_fwrite(const void *ptr, size_t size, size_t count, FILE *f);
++
+ #endif /* MG_ENABLE_FILESYSTEM */
+
+ #if MG_ENABLE_THREADS
+@@ -3860,9 +4167,10 @@ void mg_set_close_on_exec(sock_t);
+ *
+ * If both port number and IP address are printed, they are separated by `:`.
+ * If compiled with `-DMG_ENABLE_IPV6`, IPv6 addresses are supported.
++ * Return length of the stringified address.
+ */
+-void mg_conn_addr_to_str(struct mg_connection *nc, char *buf, size_t len,
+- int flags);
++int mg_conn_addr_to_str(struct mg_connection *c, char *buf, size_t len,
++ int flags);
+ #if MG_NET_IF == MG_NET_IF_SOCKET
+ /* Legacy interface. */
+ void mg_sock_to_str(sock_t sock, char *buf, size_t len, int flags);
+@@ -3873,8 +4181,8 @@ void mg_sock_to_str(sock_t sock, char *b
+ *
+ * `flags` is MG_SOCK_STRINGIFY_IP and/or MG_SOCK_STRINGIFY_PORT.
+ */
+-void mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,
+- int flags);
++int mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,
++ int flags);
+
+ #if MG_ENABLE_HEXDUMP
+ /*
+@@ -3887,6 +4195,9 @@ void mg_sock_addr_to_str(const union soc
+ */
+ int mg_hexdump(const void *buf, int len, char *dst, int dst_len);
+
++/* Same as mg_hexdump, but with output going to file instead of a buffer. */
++void mg_hexdumpf(FILE *fp, const void *buf, int len);
++
+ /*
+ * Generates human-readable hexdump of the data sent or received by the
+ * connection. `path` is a file name where hexdump should be written.
+@@ -3904,32 +4215,6 @@ void mg_hexdump_connection(struct mg_con
+ int mg_is_big_endian(void);
+
+ /*
+- * A helper function for traversing a comma separated list of values.
+- * It returns a list pointer shifted to the next value or NULL if the end
+- * of the list found.
+- * The value is stored in a val vector. If the value has a form "x=y", then
+- * eq_val vector is initialised to point to the "y" part, and val vector length
+- * is adjusted to point only to "x".
+- * If the list is just a comma separated list of entries, like "aa,bb,cc" then
+- * `eq_val` will contain zero-length string.
+- *
+- * The purpose of this function is to parse comma separated string without
+- * any copying/memory allocation.
+- */
+-const char *mg_next_comma_list_entry(const char *list, struct mg_str *val,
+- struct mg_str *eq_val);
+-
+-/*
+- * Matches 0-terminated string (mg_match_prefix) or string with given length
+- * mg_match_prefix_n against a glob pattern.
+- *
+- * Match is case-insensitive. Returns number of bytes matched, or -1 if no
+- * match.
+- */
+-int mg_match_prefix(const char *pattern, int pattern_len, const char *str);
+-int mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str);
+-
+-/*
+ * Use with cs_base64_init/update/finish in order to write out base64 in chunks.
+ */
+ void mg_mbuf_append_base64_putc(char ch, void *user_data);
+@@ -3944,14 +4229,23 @@ void mg_mbuf_append_base64(struct mbuf *
+ * If pass is NULL, then user is expected to contain the credentials pair
+ * already encoded as `user:pass`.
+ */
+-void mg_basic_auth_header(const char *user, const char *pass, struct mbuf *buf);
++void mg_basic_auth_header(const struct mg_str user, const struct mg_str pass,
++ struct mbuf *buf);
++
++/*
++ * URL-escape the specified string.
++ * All non-printable characters are escaped, plus `._-$,;~()/`.
++ * Input need not be NUL-terminated, but the returned string is.
++ * Returned string is heap-allocated and must be free()'d.
++ */
++struct mg_str mg_url_encode(const struct mg_str src);
+
+ #ifdef __cplusplus
+ }
+ #endif /* __cplusplus */
+ #endif /* CS_MONGOOSE_SRC_UTIL_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/http.h"
++#line 1 "mongoose/src/mg_http.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -3967,7 +4261,7 @@ void mg_basic_auth_header(const char *us
+
+ #if MG_ENABLE_HTTP
+
+-/* Amalgamated: #include "mongoose/src/net.h" */
++/* Amalgamated: #include "mg_net.h" */
+ /* Amalgamated: #include "common/mg_str.h" */
+
+ #ifdef __cplusplus
+@@ -3982,14 +4276,6 @@ extern "C" {
+ #define MG_MAX_HTTP_REQUEST_SIZE 1024
+ #endif
+
+-#ifndef MG_MAX_PATH
+-#ifdef PATH_MAX
+-#define MG_MAX_PATH PATH_MAX
+-#else
+-#define MG_MAX_PATH 256
+-#endif
+-#endif
+-
+ #ifndef MG_MAX_HTTP_SEND_MBUF
+ #define MG_MAX_HTTP_SEND_MBUF 1024
+ #endif
+@@ -4001,6 +4287,7 @@ extern "C" {
+ /* HTTP message */
+ struct http_message {
+ struct mg_str message; /* Whole message: request line + headers + body */
++ struct mg_str body; /* Message body. 0-length for requests with no body */
+
+ /* HTTP Request line (or HTTP response line) */
+ struct mg_str method; /* "GET" */
+@@ -4024,9 +4311,6 @@ struct http_message {
+ /* Headers */
+ struct mg_str header_names[MG_MAX_HTTP_HEADERS];
+ struct mg_str header_values[MG_MAX_HTTP_HEADERS];
+-
+- /* Message body */
+- struct mg_str body; /* Zero-length for requests with no body */
+ };
+
+ #if MG_ENABLE_HTTP_WEBSOCKET
+@@ -4168,6 +4452,17 @@ void mg_send_websocket_handshake3(struct
+ const char *host, const char *protocol,
+ const char *extra_headers, const char *user,
+ const char *pass);
++
++/* Same as mg_send_websocket_handshake3 but with strings not necessarily
++ * NUL-temrinated */
++void mg_send_websocket_handshake3v(struct mg_connection *nc,
++ const struct mg_str path,
++ const struct mg_str host,
++ const struct mg_str protocol,
++ const struct mg_str extra_headers,
++ const struct mg_str user,
++ const struct mg_str pass);
++
+ /*
+ * Helper function that creates an outbound WebSocket connection.
+ *
+@@ -4189,7 +4484,8 @@ void mg_send_websocket_handshake3(struct
+ * ```
+ */
+ struct mg_connection *mg_connect_ws(struct mg_mgr *mgr,
+- mg_event_handler_t event_handler,
++ MG_CB(mg_event_handler_t event_handler,
++ void *user_data),
+ const char *url, const char *protocol,
+ const char *extra_headers);
+
+@@ -4199,11 +4495,10 @@ struct mg_connection *mg_connect_ws(stru
+ * Mostly identical to `mg_connect_ws`, but allows to provide extra parameters
+ * (for example, SSL parameters)
+ */
+-struct mg_connection *mg_connect_ws_opt(struct mg_mgr *mgr,
+- mg_event_handler_t ev_handler,
+- struct mg_connect_opts opts,
+- const char *url, const char *protocol,
+- const char *extra_headers);
++struct mg_connection *mg_connect_ws_opt(
++ struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
++ struct mg_connect_opts opts, const char *url, const char *protocol,
++ const char *extra_headers);
+
+ /*
+ * Send WebSocket frame to the remote end.
+@@ -4227,9 +4522,8 @@ void mg_send_websocket_frame(struct mg_c
+ const void *data, size_t data_len);
+
+ /*
+- * Sends multiple websocket frames.
+- *
+- * Like `mg_send_websocket_frame()`, but composes a frame from multiple buffers.
++ * Like `mg_send_websocket_frame()`, but composes a single frame from multiple
++ * buffers.
+ */
+ void mg_send_websocket_framev(struct mg_connection *nc, int op_and_flags,
+ const struct mg_str *strings, int num_strings);
+@@ -4274,12 +4568,42 @@ void mg_printf_websocket_frame(struct mg
+ * (`dst`, `dst_len`). If `is_form_url_encoded` is non-zero, then
+ * `+` character is decoded as a blank space character. This function
+ * guarantees to NUL-terminate the destination. If destination is too small,
+- * then the source string is partially decoded and `-1` is returned. Otherwise,
++ * then the source string is partially decoded and `-1` is returned.
++ *Otherwise,
+ * a length of the decoded string is returned, not counting final NUL.
+ */
+ int mg_url_decode(const char *src, int src_len, char *dst, int dst_len,
+ int is_form_url_encoded);
+
++extern void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],
++ const size_t *msg_lens, uint8_t *digest);
++extern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],
++ const size_t *msg_lens, uint8_t *digest);
++
++/*
++ * Flags for `mg_http_is_authorized()`.
++ */
++#define MG_AUTH_FLAG_IS_DIRECTORY (1 << 0)
++#define MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE (1 << 1)
++#define MG_AUTH_FLAG_ALLOW_MISSING_FILE (1 << 2)
++
++/*
++ * Checks whether an http request is authorized. `domain` is the authentication
++ * realm, `passwords_file` is a htdigest file (can be created e.g. with
++ * `htdigest` utility). If either `domain` or `passwords_file` is NULL, this
++ * function always returns 1; otherwise checks the authentication in the
++ * http request and returns 1 only if there is a match; 0 otherwise.
++ */
++int mg_http_is_authorized(struct http_message *hm, struct mg_str path,
++ const char *domain, const char *passwords_file,
++ int flags);
++
++/*
++ * Sends 401 Unauthorized response.
++ */
++void mg_http_send_digest_auth_request(struct mg_connection *c,
++ const char *domain);
++
+ #ifdef __cplusplus
+ }
+ #endif /* __cplusplus */
+@@ -4288,7 +4612,7 @@ int mg_url_decode(const char *src, int s
+
+ #endif /* CS_MONGOOSE_SRC_HTTP_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/http_server.h"
++#line 1 "mongoose/src/mg_http_server.h"
+ #endif
+ /*
+ * === Server API reference
+@@ -4323,21 +4647,42 @@ struct mg_str *mg_get_http_header(struct
+
+ /*
+ * Parses the HTTP header `hdr`. Finds variable `var_name` and stores its value
+- * in the buffer `buf`, `buf_size`. Returns 0 if variable not found, non-zero
+- * otherwise.
++ * in the buffer `*buf`, `buf_size`. If the buffer size is not enough,
++ * allocates a buffer of required size and writes it to `*buf`, similar to
++ * asprintf(). The caller should always check whether the buffer was updated,
++ * and free it if so.
+ *
+ * This function is supposed to parse cookies, authentication headers, etc.
+ * Example (error handling omitted):
+ *
+- * char user[20];
++ * char user_buf[20];
++ * char *user = user_buf;
+ * struct mg_str *hdr = mg_get_http_header(hm, "Authorization");
+- * mg_http_parse_header(hdr, "username", user, sizeof(user));
++ * mg_http_parse_header2(hdr, "username", &user, sizeof(user_buf));
++ * // ... do something useful with user
++ * if (user != user_buf) {
++ * free(user);
++ * }
+ *
+- * Returns the length of the variable's value. If buffer is not large enough,
+- * or variable not found, 0 is returned.
++ * Returns the length of the variable's value. If variable is not found, 0 is
++ * returned.
++ */
++int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,
++ size_t buf_size);
++
++/*
++ * DEPRECATED: use mg_http_parse_header2() instead.
++ *
++ * Same as mg_http_parse_header2(), but takes buffer as a `char *` (instead of
++ * `char **`), and thus it cannot allocate a new buffer if the provided one
++ * is not enough, and just returns 0 in that case.
+ */
+ int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
+- size_t buf_size);
++ size_t buf_size)
++#ifdef __GNUC__
++ __attribute__((deprecated));
++#endif
++;
+
+ /*
+ * Gets and parses the Authorization: Basic header
+@@ -4403,7 +4748,8 @@ size_t mg_parse_multipart(const char *bu
+ * Fetches a variable `name` from a `buf` into a buffer specified by `dst`,
+ * `dst_len`. The destination is always zero-terminated. Returns the length of
+ * a fetched variable. If not found, 0 is returned. `buf` must be valid
+- * url-encoded buffer. If destination is too small, `-1` is returned.
++ * url-encoded buffer. If destination is too small or an error occured,
++ * negative number is returned.
+ */
+ int mg_get_http_var(const struct mg_str *buf, const char *name, char *dst,
+ size_t dst_len);
+@@ -4654,7 +5000,8 @@ typedef struct mg_str (*mg_fu_fname_fn)(
+ * ```
+ */
+ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
+- mg_fu_fname_fn local_name_fn);
++ mg_fu_fname_fn local_name_fn
++ MG_UD_ARG(void *user_data));
+ #endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
+ #endif /* MG_ENABLE_FILESYSTEM */
+
+@@ -4672,7 +5019,7 @@ void mg_file_upload_handler(struct mg_co
+ * nc->flags |= MG_F_SEND_AND_CLOSE;
+ * }
+ *
+- * static void handle_hello1(struct mg_connection *nc, int ev, void *ev_data) {
++ * static void handle_hello2(struct mg_connection *nc, int ev, void *ev_data) {
+ * (void) ev; (void) ev_data;
+ * mg_printf(nc, "HTTP/1.0 200 OK\r\n\r\n[I am Hello2]");
+ * nc->flags |= MG_F_SEND_AND_CLOSE;
+@@ -4686,7 +5033,20 @@ void mg_file_upload_handler(struct mg_co
+ * ```
+ */
+ void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
+- mg_event_handler_t handler);
++ MG_CB(mg_event_handler_t handler,
++ void *user_data));
++
++struct mg_http_endpoint_opts {
++ void *user_data;
++ /* Authorization domain (realm) */
++ const char *auth_domain;
++ const char *auth_file;
++};
++
++void mg_register_http_endpoint_opt(struct mg_connection *nc,
++ const char *uri_path,
++ mg_event_handler_t handler,
++ struct mg_http_endpoint_opts opts);
+
+ /*
+ * Authenticates a HTTP request against an opened password file.
+@@ -4696,10 +5056,22 @@ int mg_http_check_digest_auth(struct htt
+ FILE *fp);
+
+ /*
++ * Authenticates given response params against an opened password file.
++ * Returns 1 if authenticated, 0 otherwise.
++ *
++ * It's used by mg_http_check_digest_auth().
++ */
++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);
++
++/*
+ * Sends buffer `buf` of size `len` to the client using chunked HTTP encoding.
+ * This function sends the buffer size as hex number + newline first, then
+ * the buffer itself, then the newline. For example,
+- * `mg_send_http_chunk(nc, "foo", 3)` whill append the `3\r\nfoo\r\n` string
++ * `mg_send_http_chunk(nc, "foo", 3)` will append the `3\r\nfoo\r\n` string
+ * to the `nc->send_mbuf` output IO buffer.
+ *
+ * NOTE: The HTTP header "Transfer-Encoding: chunked" should be sent prior to
+@@ -4724,7 +5096,7 @@ void mg_printf_http_chunk(struct mg_conn
+ /*
+ * Sends the response status line.
+ * If `extra_headers` is not NULL, then `extra_headers` are also sent
+- * after the reponse line. `extra_headers` must NOT end end with new line.
++ * after the response line. `extra_headers` must NOT end end with new line.
+ * Example:
+ *
+ * mg_send_response_line(nc, 200, "Access-Control-Allow-Origin: *");
+@@ -4748,7 +5120,7 @@ void mg_http_send_error(struct mg_connec
+ * `status_code` should be either 301 or 302 and `location` point to the
+ * new location.
+ * If `extra_headers` is not empty, then `extra_headers` are also sent
+- * after the reponse line. `extra_headers` must NOT end end with new line.
++ * after the response line. `extra_headers` must NOT end end with new line.
+ *
+ * Example:
+ *
+@@ -4804,7 +5176,7 @@ void mg_http_reverse_proxy(struct mg_con
+
+ #endif /* CS_MONGOOSE_SRC_HTTP_SERVER_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/http_client.h"
++#line 1 "mongoose/src/mg_http_client.h"
+ #endif
+ /*
+ * === Client API reference
+@@ -4841,11 +5213,10 @@ extern "C" {
+ * "var_1=value_1&var_2=value_2");
+ * ```
+ */
+-struct mg_connection *mg_connect_http(struct mg_mgr *mgr,
+- mg_event_handler_t event_handler,
+- const char *url,
+- const char *extra_headers,
+- const char *post_data);
++struct mg_connection *mg_connect_http(
++ struct mg_mgr *mgr,
++ MG_CB(mg_event_handler_t event_handler, void *user_data), const char *url,
++ const char *extra_headers, const char *post_data);
+
+ /*
+ * Helper function that creates an outbound HTTP connection.
+@@ -4854,25 +5225,23 @@ struct mg_connection *mg_connect_http(st
+ *parameters
+ * (for example, SSL parameters)
+ */
+-struct mg_connection *mg_connect_http_opt(struct mg_mgr *mgr,
+- mg_event_handler_t ev_handler,
+- struct mg_connect_opts opts,
+- const char *url,
+- const char *extra_headers,
+- const char *post_data);
++struct mg_connection *mg_connect_http_opt(
++ struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
++ struct mg_connect_opts opts, const char *url, const char *extra_headers,
++ const char *post_data);
+
+ /* Creates digest authentication header for a client request. */
+ int mg_http_create_digest_auth_header(char *buf, size_t buf_len,
+ const char *method, const char *uri,
+ const char *auth_domain, const char *user,
+- const char *passwd);
++ const char *passwd, const char *nonce);
+
+ #ifdef __cplusplus
+ }
+ #endif /* __cplusplus */
+ #endif /* CS_MONGOOSE_SRC_HTTP_CLIENT_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/mqtt.h"
++#line 1 "mongoose/src/mg_mqtt.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -4898,11 +5267,12 @@ int mg_http_create_digest_auth_header(ch
+ #ifndef CS_MONGOOSE_SRC_MQTT_H_
+ #define CS_MONGOOSE_SRC_MQTT_H_
+
+-/* Amalgamated: #include "mongoose/src/net.h" */
++/* Amalgamated: #include "mg_net.h" */
+
+ struct mg_mqtt_message {
+ int cmd;
+ int qos;
++ int len; /* message length in the IO buffer */
+ struct mg_str topic;
+ struct mg_str payload;
+
+@@ -4938,6 +5308,7 @@ struct mg_send_mqtt_handshake_opts {
+ /* mg_mqtt_proto_data should be in header to allow external access to it */
+ struct mg_mqtt_proto_data {
+ uint16_t keep_alive;
++ double last_control_time;
+ };
+
+ /* Message types */
+@@ -5082,13 +5453,26 @@ void mg_mqtt_pong(struct mg_connection *
+ int mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg,
+ struct mg_str *topic, uint8_t *qos, int pos);
+
++/*
++ * Matches a topic against a topic expression
++ *
++ * Returns 1 if it matches; 0 otherwise.
++ */
++int mg_mqtt_match_topic_expression(struct mg_str exp, struct mg_str topic);
++
++/*
++ * Same as `mg_mqtt_match_topic_expression()`, but takes `exp` as a
++ * NULL-terminated string.
++ */
++int mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic);
++
+ #ifdef __cplusplus
+ }
+ #endif /* __cplusplus */
+
+ #endif /* CS_MONGOOSE_SRC_MQTT_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/mqtt_server.h"
++#line 1 "mongoose/src/mg_mqtt_server.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -5117,13 +5501,15 @@ int mg_mqtt_next_subscribe_topic(struct
+ #if MG_ENABLE_MQTT_BROKER
+
+ /* Amalgamated: #include "common/queue.h" */
+-/* Amalgamated: #include "mongoose/src/mqtt.h" */
++/* Amalgamated: #include "mg_mqtt.h" */
+
+ #ifdef __cplusplus
+ extern "C" {
+ #endif /* __cplusplus */
+
+-#define MG_MQTT_MAX_SESSION_SUBSCRIPTIONS 512;
++#ifndef MG_MQTT_MAX_SESSION_SUBSCRIPTIONS
++#define MG_MQTT_MAX_SESSION_SUBSCRIPTIONS 512
++#endif
+
+ struct mg_mqtt_broker;
+
+@@ -5193,7 +5579,7 @@ struct mg_mqtt_session *mg_mqtt_next(str
+ #endif /* MG_ENABLE_MQTT_BROKER */
+ #endif /* CS_MONGOOSE_SRC_MQTT_BROKER_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/dns.h"
++#line 1 "mongoose/src/mg_dns.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -5207,7 +5593,7 @@ struct mg_mqtt_session *mg_mqtt_next(str
+ #ifndef CS_MONGOOSE_SRC_DNS_H_
+ #define CS_MONGOOSE_SRC_DNS_H_
+
+-/* Amalgamated: #include "mongoose/src/net.h" */
++/* Amalgamated: #include "mg_net.h" */
+
+ #ifdef __cplusplus
+ extern "C" {
+@@ -5220,6 +5606,8 @@ extern "C" {
+ #define MG_DNS_AAAA_RECORD 0x1c /* Lookup IPv6 address */
+ #define MG_DNS_SRV_RECORD 0x21 /* Lookup SRV */
+ #define MG_DNS_MX_RECORD 0x0f /* Lookup mail server for domain */
++#define MG_DNS_ANY_RECORD 0xff
++#define MG_DNS_NSEC_RECORD 0x2f
+
+ #define MG_MAX_DNS_QUESTIONS 32
+ #define MG_MAX_DNS_ANSWERS 32
+@@ -5309,7 +5697,7 @@ int mg_dns_copy_questions(struct mbuf *i
+ * struct because they might be invalidated as soon as the IO buffer grows
+ * again.
+ *
+- * Returns the number of bytes appened or -1 in case of error.
++ * Returns the number of bytes appended or -1 in case of error.
+ */
+ int mg_dns_encode_record(struct mbuf *io, struct mg_dns_resource_record *rr,
+ const char *name, size_t nlen, const void *rdata,
+@@ -5358,7 +5746,7 @@ void mg_set_protocol_dns(struct mg_conne
+ #endif /* __cplusplus */
+ #endif /* CS_MONGOOSE_SRC_DNS_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/dns_server.h"
++#line 1 "mongoose/src/mg_dns_server.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -5376,7 +5764,7 @@ void mg_set_protocol_dns(struct mg_conne
+
+ #if MG_ENABLE_DNS_SERVER
+
+-/* Amalgamated: #include "mongoose/src/dns.h" */
++/* Amalgamated: #include "mg_dns.h" */
+
+ #ifdef __cplusplus
+ extern "C" {
+@@ -5455,7 +5843,7 @@ void mg_dns_send_reply(struct mg_connect
+ #endif /* MG_ENABLE_DNS_SERVER */
+ #endif /* CS_MONGOOSE_SRC_DNS_SERVER_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/resolv.h"
++#line 1 "mongoose/src/mg_resolv.h"
+ #endif
+ /*
+ * Copyright (c) 2014 Cesanta Software Limited
+@@ -5469,7 +5857,7 @@ void mg_dns_send_reply(struct mg_connect
+ #ifndef CS_MONGOOSE_SRC_RESOLV_H_
+ #define CS_MONGOOSE_SRC_RESOLV_H_
+
+-/* Amalgamated: #include "mongoose/src/dns.h" */
++/* Amalgamated: #include "mg_dns.h" */
+
+ #ifdef __cplusplus
+ extern "C" {
+@@ -5487,7 +5875,7 @@ typedef void (*mg_resolve_callback_t)(st
+
+ /* Options for `mg_resolve_async_opt`. */
+ struct mg_resolve_async_opts {
+- const char *nameserver_url;
++ const char *nameserver;
+ int max_retries; /* defaults to 2 if zero */
+ int timeout; /* in seconds; defaults to 5 if zero */
+ int accept_literal; /* pseudo-resolve literal ipv4 and ipv6 addrs */
+@@ -5499,6 +5887,9 @@ struct mg_resolve_async_opts {
+ int mg_resolve_async(struct mg_mgr *mgr, const char *name, int query,
+ mg_resolve_callback_t cb, void *data);
+
++/* Set default DNS server */
++void mg_set_nameserver(struct mg_mgr *mgr, const char *nameserver);
++
+ /*
+ * Resolved a DNS name asynchronously.
+ *
+@@ -5536,7 +5927,7 @@ int mg_resolve_from_hosts_file(const cha
+ #endif /* __cplusplus */
+ #endif /* CS_MONGOOSE_SRC_RESOLV_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/coap.h"
++#line 1 "mongoose/src/mg_coap.h"
+ #endif
+ /*
+ * Copyright (c) 2015 Cesanta Software Limited
+@@ -5647,7 +6038,7 @@ struct mg_coap_option *mg_coap_add_optio
+
+ /*
+ * Frees the memory allocated for options.
+- * If the cm paramater doesn't contain any option it does nothing.
++ * If the cm parameter doesn't contain any option it does nothing.
+ */
+ void mg_coap_free_options(struct mg_coap_message *cm);
+
+@@ -5704,7 +6095,7 @@ uint32_t mg_coap_compose(struct mg_coap_
+
+ #endif /* CS_MONGOOSE_SRC_COAP_H_ */
+ #ifdef MG_MODULE_LINES
+-#line 1 "mongoose/src/sntp.h"
++#line 1 "mongoose/src/mg_sntp.h"
+ #endif
+ /*
+ * Copyright (c) 2016 Cesanta Software Limited
+@@ -5739,7 +6130,8 @@ struct mg_sntp_message {
+
+ /* Establishes connection to given sntp server */
+ struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr,
+- mg_event_handler_t event_handler,
++ MG_CB(mg_event_handler_t event_handler,
++ void *user_data),
+ const char *sntp_server_name);
+
+ /* Sends time request to given connection */
+@@ -5759,3 +6151,72 @@ struct mg_connection *mg_sntp_get_time(s
+ #endif
+
+ #endif /* CS_MONGOOSE_SRC_SNTP_H_ */
++#ifdef MG_MODULE_LINES
++#line 1 "mongoose/src/mg_socks.h"
++#endif
++/*
++ * Copyright (c) 2017 Cesanta Software Limited
++ * All rights reserved
++ */
++
++#ifndef CS_MONGOOSE_SRC_SOCKS_H_
++#define CS_MONGOOSE_SRC_SOCKS_H_
++
++#if MG_ENABLE_SOCKS
++
++#define MG_SOCKS_VERSION 5
++
++#define MG_SOCKS_HANDSHAKE_DONE MG_F_USER_1
++#define MG_SOCKS_CONNECT_DONE MG_F_USER_2
++
++/* SOCKS5 handshake methods */
++enum mg_socks_handshake_method {
++ MG_SOCKS_HANDSHAKE_NOAUTH = 0, /* Handshake method - no authentication */
++ MG_SOCKS_HANDSHAKE_GSSAPI = 1, /* Handshake method - GSSAPI auth */
++ MG_SOCKS_HANDSHAKE_USERPASS = 2, /* Handshake method - user/password auth */
++ MG_SOCKS_HANDSHAKE_FAILURE = 0xff, /* Handshake method - failure */
++};
++
++/* SOCKS5 commands */
++enum mg_socks_command {
++ MG_SOCKS_CMD_CONNECT = 1, /* Command: CONNECT */
++ MG_SOCKS_CMD_BIND = 2, /* Command: BIND */
++ MG_SOCKS_CMD_UDP_ASSOCIATE = 3, /* Command: UDP ASSOCIATE */
++};
++
++/* SOCKS5 address types */
++enum mg_socks_address_type {
++ MG_SOCKS_ADDR_IPV4 = 1, /* Address type: IPv4 */
++ MG_SOCKS_ADDR_DOMAIN = 3, /* Address type: Domain name */
++ MG_SOCKS_ADDR_IPV6 = 4, /* Address type: IPv6 */
++};
++
++/* SOCKS5 response codes */
++enum mg_socks_response {
++ MG_SOCKS_SUCCESS = 0, /* Response: success */
++ MG_SOCKS_FAILURE = 1, /* Response: failure */
++ MG_SOCKS_NOT_ALLOWED = 2, /* Response: connection not allowed */
++ MG_SOCKS_NET_UNREACHABLE = 3, /* Response: network unreachable */
++ MG_SOCKS_HOST_UNREACHABLE = 4, /* Response: network unreachable */
++ MG_SOCKS_CONN_REFUSED = 5, /* Response: network unreachable */
++ MG_SOCKS_TTL_EXPIRED = 6, /* Response: network unreachable */
++ MG_SOCKS_CMD_NOT_SUPPORTED = 7, /* Response: network unreachable */
++ MG_SOCKS_ADDR_NOT_SUPPORTED = 8, /* Response: network unreachable */
++};
++
++#ifdef __cplusplus
++extern "C" {
++#endif /* __cplusplus */
++
++/* Turn the connection into the SOCKS server */
++void mg_set_protocol_socks(struct mg_connection *c);
++
++/* Create socks tunnel for the client connection */
++struct mg_iface *mg_socks_mk_iface(struct mg_mgr *, const char *proxy_addr);
++
++#ifdef __cplusplus
++}
++#endif /* __cplusplus */
++
++#endif
++#endif
diff --git a/debian/patches/series b/debian/patches/series
index 622dd1e..e04dcbf 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -3,3 +3,4 @@
05-add-debian-hardening-flags.patch
06-tryfixplaylist.patch
01-update-mime-types.patch
+03-update-mongoose-to-6.11.patch