diff options
author | Mateusz Łukasik <mati75@linuxmint.pl> | 2018-06-02 22:47:14 +0200 |
---|---|---|
committer | Mateusz Łukasik <mati75@linuxmint.pl> | 2018-06-02 22:47:14 +0200 |
commit | 2ae19bc2ded3c7a9b1a11647d2fbc52a707ed287 (patch) | |
tree | f916f2e8e4079e358bf56e80e04f0d74f21029fc | |
parent | bcd1eba97dbb085deeafc55151d2ed6bcca90a36 (diff) |
Add debian/patches/03-update-mongoose-to-6.11.patch
-rw-r--r-- | debian/changelog | 4 | ||||
-rw-r--r-- | debian/patches/03-update-mongoose-to-6.11.patch | 12281 | ||||
-rw-r--r-- | debian/patches/series | 1 |
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, ¶ms, &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 |