diff --git a/README.md b/README.md
index 0967275fa0c351bd4e6aed25bf06480390b84f07..acc01972447fcba07a57097b8ff1a3cf7d5dbf41 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ used by vast number of open source and
 commercial products - it even runs on the International Space station!
 Mongoose makes embedded network programming fast, robust, and easy.
 
-- [Download Mongoose Source Code here](https://www.cesanta.com)
+- [Download Mongoose Source Code here](https://www.cesanta.com/download.html)
 
 Looking for a complete IoT firmware solution?
 
@@ -17,9 +17,9 @@ Check out [Mongoose OS](https://mongoose-os.com) - open source embedded operatin
 # Support
 - [Study mongoose example code](https://github.com/cesanta/mongoose/tree/master/examples)
 - [Read User Guide and API reference](https://docs.cesanta.com/mongoose)
-- [Support Forum - ask your technical questions here] (http://forum.cesanta.com/index.php?p=/categories/mongoose)
-- [Commercial licensing and support available] (https://www.cesanta.com/services-support)
-- [Check our latest releases] (https://github.com/cesanta/mongoose/releases)
+- [Support Forum - ask your technical questions here](https://forum.mongoose-os.com/categories/mongoose)
+- [Commercial licensing and support available](https://www.cesanta.com/licensing.html)
+- [Check our latest releases](https://github.com/cesanta/mongoose/releases)
 
 # Features
 
@@ -47,7 +47,7 @@ Check out [Mongoose OS](https://mongoose-os.com) - open source embedded operatin
 
 Mongoose is released under Commercial and [GNU GPL v.2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) open source licenses.
 
-Commercial Projects: [Contact us for commercial license.] (https://www.cesanta.com/contact)
+Commercial Projects: [Contact us for commercial license.](https://www.cesanta.com/contact.html)
 
 # Dashboard Example
 
@@ -55,18 +55,14 @@ Mongoose is often used to implement device dashboards and real-time
 data exchange over Websocket. Here is a dashboard example that illustrates
 the functionality:
 
-![](http://www.cesanta.com/hubfs/www.cesanta.com/diagrams/dash_mongoose_diagram.png)
+![](http://www.cesanta.com/images/dashboard.png)
 
-[Developing a new product? Contact us today to discuss how Mongoose can help.
-](https://www.cesanta.com/contact)
+[Developing a new product? Contact us today to discuss how Mongoose can help.](https://www.cesanta.com/contact.html)
 
 # Contributions
 
-To submit contributions, sign
-[Cesanta CLA](https://docs.cesanta.com/contributors_la.shtml)
+To submit contributions, sign [Cesanta CLA](https://cesanta.com/cla.html)
 and send GitHub pull request. You retain the copyright on your contributions.
 
 # Looking for a pre-compiled Mongoose web server Windows or Mac binary?
-- [Download pre-compiled Mongoose web server binary.](https://www.cesanta.com/products/binary)
-
-[![Analytics](https://ga-beacon.appspot.com/UA-42732794-5/project-page)](https://github.com/cesanta/mongoose)
+- [Download pre-compiled Mongoose web server binary.](https://www.cesanta.com/binary.html)
diff --git a/docs/c-api/coap.h/mg_coap_free_options.md b/docs/c-api/coap.h/mg_coap_free_options.md
index 9dcc816f471af97be5a1e1721d341ff3f202c864..d429cf49edc124368edf89041941001b1c89e249 100644
--- a/docs/c-api/coap.h/mg_coap_free_options.md
+++ b/docs/c-api/coap.h/mg_coap_free_options.md
@@ -7,5 +7,5 @@ signature: |
 ---
 
 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. 
 
diff --git a/docs/c-api/dns.h/mg_dns_encode_record.md b/docs/c-api/dns.h/mg_dns_encode_record.md
index c52cf26f352974afec84aa7f500f73bbc46570c7..7c9f2cf2bf9316c8433bf88917e7fd5babc7ce6d 100644
--- a/docs/c-api/dns.h/mg_dns_encode_record.md
+++ b/docs/c-api/dns.h/mg_dns_encode_record.md
@@ -21,5 +21,5 @@ This function doesn't update the `name` and `rdata` pointers in the `rr`
 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. 
 
diff --git a/docs/c-api/http.h/intro.md b/docs/c-api/http.h/intro.md
index 8ef19dd1facab0e43aaa7ed35718c7c762680ca6..8459b601893357ce97cef36745d279fd2c41fbaf 100644
--- a/docs/c-api/http.h/intro.md
+++ b/docs/c-api/http.h/intro.md
@@ -11,6 +11,7 @@ items:
   - { name: mg_send_websocket_handshake.md }
   - { name: mg_send_websocket_handshake2.md }
   - { name: mg_send_websocket_handshake3.md }
+  - { name: mg_send_websocket_handshake3v.md }
   - { name: mg_set_protocol_http_websocket.md }
   - { name: mg_url_decode.md }
   - { name: struct_http_message.md }
diff --git a/docs/c-api/http.h/mg_connect_ws.md b/docs/c-api/http.h/mg_connect_ws.md
index b0f38d74895f3a5c6222457a6189a10ac345c160..7a5b517d34f0064642b5abe094d5f526906a0f63 100644
--- a/docs/c-api/http.h/mg_connect_ws.md
+++ b/docs/c-api/http.h/mg_connect_ws.md
@@ -4,9 +4,8 @@ decl_name: "mg_connect_ws"
 symbol_kind: "func"
 signature: |
   struct mg_connection *mg_connect_ws(struct mg_mgr *mgr,
-                                      mg_event_handler_t event_handler,
-                                      const char *url, const char *protocol,
-                                      const char *extra_headers);
+                                      MG_CB(mg_event_handler_t event_handler,
+                                            void *user_data);
 ---
 
 Helper function that creates an outbound WebSocket connection.
diff --git a/docs/c-api/http.h/mg_connect_ws_opt.md b/docs/c-api/http.h/mg_connect_ws_opt.md
index 57956ddbd30fbb22ef7582589574f9b40240ce8a..e2203cff69636a5e9c837b87c74f719eb1df899c 100644
--- a/docs/c-api/http.h/mg_connect_ws_opt.md
+++ b/docs/c-api/http.h/mg_connect_ws_opt.md
@@ -3,11 +3,8 @@ title: "mg_connect_ws_opt()"
 decl_name: "mg_connect_ws_opt"
 symbol_kind: "func"
 signature: |
-  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);
 ---
 
 Helper function that creates an outbound WebSocket connection
diff --git a/docs/c-api/http.h/mg_send_websocket_framev.md b/docs/c-api/http.h/mg_send_websocket_framev.md
index 005a7916527a68b4d1d592c8cc42cd7e76b4bb7c..06e094601efd1e9887613cb9aebd28e2a036978a 100644
--- a/docs/c-api/http.h/mg_send_websocket_framev.md
+++ b/docs/c-api/http.h/mg_send_websocket_framev.md
@@ -9,5 +9,6 @@ signature: |
 
 Sends multiple websocket frames.
 
-Like `mg_send_websocket_frame()`, but composes a frame from multiple buffers. 
+Like `mg_send_websocket_frame()`, but composes a frame from multiple
+*buffers. 
 
diff --git a/docs/c-api/http.h/mg_send_websocket_handshake3v.md b/docs/c-api/http.h/mg_send_websocket_handshake3v.md
new file mode 100644
index 0000000000000000000000000000000000000000..acf8c1e6777ecae4c87c85886462cd7cc0023bbd
--- /dev/null
+++ b/docs/c-api/http.h/mg_send_websocket_handshake3v.md
@@ -0,0 +1,17 @@
+---
+title: "mg_send_websocket_handshake3v()"
+decl_name: "mg_send_websocket_handshake3v"
+symbol_kind: "func"
+signature: |
+  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);
+---
+
+Same as mg_send_websocket_handshake3 but with strings not necessarily
+NUL-temrinated 
+
diff --git a/docs/c-api/http.h/mg_url_decode.md b/docs/c-api/http.h/mg_url_decode.md
index 2343adfa2c434f207dc10e0b29802ecb25ce42dd..454acd03ce01a6eb62aff59eccc6a343d56be52e 100644
--- a/docs/c-api/http.h/mg_url_decode.md
+++ b/docs/c-api/http.h/mg_url_decode.md
@@ -13,6 +13,7 @@ Source string is specified by (`src`, `src_len`), and destination is
 (`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. 
 
diff --git a/docs/c-api/http.h/struct_http_message.md b/docs/c-api/http.h/struct_http_message.md
index 60eae685f96f1d2310246a52da29e66a31f09e4f..9380496d453d060d3dba6c430135ca30afa431d6 100644
--- a/docs/c-api/http.h/struct_http_message.md
+++ b/docs/c-api/http.h/struct_http_message.md
@@ -5,6 +5,7 @@ symbol_kind: "struct"
 signature: |
   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" */
@@ -28,9 +29,6 @@ signature: |
     /* 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 */
   };
 ---
 
diff --git a/docs/c-api/http_client.h/mg_connect_http.md b/docs/c-api/http_client.h/mg_connect_http.md
index fcf5f04687585192eade1234648ae79dd0217480..4664fcd9040d75cf8c167b3135e6c87417c68660 100644
--- a/docs/c-api/http_client.h/mg_connect_http.md
+++ b/docs/c-api/http_client.h/mg_connect_http.md
@@ -3,11 +3,9 @@ title: "mg_connect_http()"
 decl_name: "mg_connect_http"
 symbol_kind: "func"
 signature: |
-  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);
 ---
 
 Helper function that creates an outbound HTTP connection.
diff --git a/docs/c-api/http_client.h/mg_connect_http_opt.md b/docs/c-api/http_client.h/mg_connect_http_opt.md
index 2454cb10ee06712997b4a9e6c088d12ff7b5e3f4..29df1d753db7e0ac5e9565a7266b2444379c8d34 100644
--- a/docs/c-api/http_client.h/mg_connect_http_opt.md
+++ b/docs/c-api/http_client.h/mg_connect_http_opt.md
@@ -3,12 +3,8 @@ title: "mg_connect_http_opt()"
 decl_name: "mg_connect_http_opt"
 symbol_kind: "func"
 signature: |
-  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);
 ---
 
 Helper function that creates an outbound HTTP connection.
diff --git a/docs/c-api/http_server.h/intro.md b/docs/c-api/http_server.h/intro.md
index 98402e96a7685670bf6e2dad0c775ae1eab73e1e..0fc609dbc0717b3381f4efdfeefa128fd80eb6c4 100644
--- a/docs/c-api/http_server.h/intro.md
+++ b/docs/c-api/http_server.h/intro.md
@@ -3,6 +3,7 @@ title: "Server API reference"
 symbol_kind: "intro"
 decl_name: "http_server.h"
 items:
+  - { name: mg_check_digest_auth.md }
   - { name: mg_file_upload_handler.md }
   - { name: mg_get_http_basic_auth.md }
   - { name: mg_get_http_header.md }
diff --git a/docs/c-api/http_server.h/mg_check_digest_auth.md b/docs/c-api/http_server.h/mg_check_digest_auth.md
new file mode 100644
index 0000000000000000000000000000000000000000..189f0c4a902f4228ee85910395e5092e240406ec
--- /dev/null
+++ b/docs/c-api/http_server.h/mg_check_digest_auth.md
@@ -0,0 +1,17 @@
+---
+title: "mg_check_digest_auth()"
+decl_name: "mg_check_digest_auth"
+symbol_kind: "func"
+signature: |
+  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);
+---
+
+Authenticates given response params against an opened password file.
+Returns 1 if authenticated, 0 otherwise.
+
+It's used by mg_http_check_digest_auth(). 
+
diff --git a/docs/c-api/http_server.h/mg_file_upload_handler.md b/docs/c-api/http_server.h/mg_file_upload_handler.md
index 444c106ff2b4a6713099ff455e961575da006f6b..a2c9e016f6ea34beb257c9dacfb271cfb813bb4d 100644
--- a/docs/c-api/http_server.h/mg_file_upload_handler.md
+++ b/docs/c-api/http_server.h/mg_file_upload_handler.md
@@ -4,7 +4,8 @@ decl_name: "mg_file_upload_handler"
 symbol_kind: "func"
 signature: |
   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);
 ---
 
 File upload handler.
diff --git a/docs/c-api/http_server.h/mg_get_http_var.md b/docs/c-api/http_server.h/mg_get_http_var.md
index 55ee3e6fdc0eb5b35a1477866e469df1f1033577..8ca1d915d33325b4f597224bf8c524b6ddcb262f 100644
--- a/docs/c-api/http_server.h/mg_get_http_var.md
+++ b/docs/c-api/http_server.h/mg_get_http_var.md
@@ -12,5 +12,6 @@ Fetches a HTTP form variable.
 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. 
 
diff --git a/docs/c-api/http_server.h/mg_http_send_redirect.md b/docs/c-api/http_server.h/mg_http_send_redirect.md
index 8d7d3b2e2c21c5c91776d810c2f1f8a47f7f6001..00d704e0e6ff58c4f351145e8b011ce1980f214c 100644
--- a/docs/c-api/http_server.h/mg_http_send_redirect.md
+++ b/docs/c-api/http_server.h/mg_http_send_redirect.md
@@ -12,7 +12,7 @@ Sends a redirect response.
 `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:
 
diff --git a/docs/c-api/http_server.h/mg_register_http_endpoint.md b/docs/c-api/http_server.h/mg_register_http_endpoint.md
index 752eaae6b02ef138c3db4b8f794928abbd169705..394bbb51e1b7d839b97346faf5181d2b07fd4cf4 100644
--- a/docs/c-api/http_server.h/mg_register_http_endpoint.md
+++ b/docs/c-api/http_server.h/mg_register_http_endpoint.md
@@ -4,7 +4,8 @@ decl_name: "mg_register_http_endpoint"
 symbol_kind: "func"
 signature: |
   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);
 ---
 
 Registers a callback for a specified http endpoint
@@ -20,7 +21,7 @@ static void handle_hello1(struct mg_connection *nc, int ev, void *ev_data) {
  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;
diff --git a/docs/c-api/http_server.h/mg_send_http_chunk.md b/docs/c-api/http_server.h/mg_send_http_chunk.md
index 2ce64a1a92664c1dd10a88ec84256ae2eb3c3bbc..6a2a4d838fd7bba7fb5403abf578f92b051fea12 100644
--- a/docs/c-api/http_server.h/mg_send_http_chunk.md
+++ b/docs/c-api/http_server.h/mg_send_http_chunk.md
@@ -9,7 +9,7 @@ signature: |
 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
diff --git a/docs/c-api/http_server.h/mg_send_response_line.md b/docs/c-api/http_server.h/mg_send_response_line.md
index 4bd22639a2649e2310a2fd387d3b272c668c07df..5daa74fe8fe23e3b4c9e5fda94aeaf67d0406c3b 100644
--- a/docs/c-api/http_server.h/mg_send_response_line.md
+++ b/docs/c-api/http_server.h/mg_send_response_line.md
@@ -9,7 +9,7 @@ signature: |
 
 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: *");
diff --git a/docs/c-api/mqtt.h/intro.md b/docs/c-api/mqtt.h/intro.md
index 5ae849aa34821e14e1d74073b164380149594c87..bb5d31b43a2d96dcf4aa7f8e51b5ec56566acdb8 100644
--- a/docs/c-api/mqtt.h/intro.md
+++ b/docs/c-api/mqtt.h/intro.md
@@ -5,6 +5,7 @@ decl_name: "mqtt.h"
 items:
   - { name: mg_mqtt_connack.md }
   - { name: mg_mqtt_disconnect.md }
+  - { name: mg_mqtt_match_topic_expression.md }
   - { name: mg_mqtt_next_subscribe_topic.md }
   - { name: mg_mqtt_ping.md }
   - { name: mg_mqtt_pong.md }
@@ -17,6 +18,7 @@ items:
   - { name: mg_mqtt_subscribe.md }
   - { name: mg_mqtt_unsuback.md }
   - { name: mg_mqtt_unsubscribe.md }
+  - { name: mg_mqtt_vmatch_topic_expression.md }
   - { name: mg_send_mqtt_handshake.md }
   - { name: mg_send_mqtt_handshake_opt.md }
   - { name: mg_set_protocol_mqtt.md }
diff --git a/docs/c-api/mqtt.h/mg_mqtt_match_topic_expression.md b/docs/c-api/mqtt.h/mg_mqtt_match_topic_expression.md
new file mode 100644
index 0000000000000000000000000000000000000000..81575b47f5dc597371262e99fa226cbbf1621945
--- /dev/null
+++ b/docs/c-api/mqtt.h/mg_mqtt_match_topic_expression.md
@@ -0,0 +1,12 @@
+---
+title: "mg_mqtt_match_topic_expression()"
+decl_name: "mg_mqtt_match_topic_expression"
+symbol_kind: "func"
+signature: |
+  int mg_mqtt_match_topic_expression(struct mg_str exp, struct mg_str topic);
+---
+
+Matches a topic against a topic expression
+
+Returns 1 if it matches; 0 otherwise. 
+
diff --git a/docs/c-api/mqtt.h/mg_mqtt_vmatch_topic_expression.md b/docs/c-api/mqtt.h/mg_mqtt_vmatch_topic_expression.md
new file mode 100644
index 0000000000000000000000000000000000000000..4b0a423157a1fd60d7e8e2cde9b1b28e9036d104
--- /dev/null
+++ b/docs/c-api/mqtt.h/mg_mqtt_vmatch_topic_expression.md
@@ -0,0 +1,11 @@
+---
+title: "mg_mqtt_vmatch_topic_expression()"
+decl_name: "mg_mqtt_vmatch_topic_expression"
+symbol_kind: "func"
+signature: |
+  int mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic);
+---
+
+Same as `mg_mqtt_match_topic_expression()`, but takes `exp` as a
+NULL-terminated string. 
+
diff --git a/docs/c-api/mqtt.h/struct_mg_mqtt_proto_data.md b/docs/c-api/mqtt.h/struct_mg_mqtt_proto_data.md
index 8ae54fcfcb96499a569b7ff25e76cbf06737399d..f095589d2c471c6772e03b6808ea553754f84891 100644
--- a/docs/c-api/mqtt.h/struct_mg_mqtt_proto_data.md
+++ b/docs/c-api/mqtt.h/struct_mg_mqtt_proto_data.md
@@ -5,6 +5,7 @@ symbol_kind: "struct"
 signature: |
   struct mg_mqtt_proto_data {
     uint16_t keep_alive;
+    double last_control_time;
   };
 ---
 
diff --git a/docs/c-api/net.h/intro.md b/docs/c-api/net.h/intro.md
index fc0bb22f143f26a455f55d33164ed695a8112714..1534ceb03ea7ef2fcc577fc911494dfda89ed3f6 100644
--- a/docs/c-api/net.h/intro.md
+++ b/docs/c-api/net.h/intro.md
@@ -11,7 +11,6 @@ items:
   - { name: mg_check_ip_acl.md }
   - { name: mg_connect.md }
   - { name: mg_connect_opt.md }
-  - { name: mg_enable_javascript.md }
   - { name: mg_mgr_free.md }
   - { name: mg_mgr_init.md }
   - { name: mg_mgr_init_opt.md }
diff --git a/docs/c-api/net.h/mg_add_sock.md b/docs/c-api/net.h/mg_add_sock.md
index 511ae84100b786819ba93153a00e67e9b8de98ef..e63ce2e48212c987cca7ae576bd97ca769b42c58 100644
--- a/docs/c-api/net.h/mg_add_sock.md
+++ b/docs/c-api/net.h/mg_add_sock.md
@@ -3,7 +3,9 @@ title: "mg_add_sock()"
 decl_name: "mg_add_sock"
 symbol_kind: "func"
 signature: |
-  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
diff --git a/docs/c-api/net.h/mg_add_sock_opt.md b/docs/c-api/net.h/mg_add_sock_opt.md
index 678a91cbb8fdfa55a295f88b11d84f6ef4ce6ac5..de2a22e9ffd766b45206cf28f707a63d9b17160c 100644
--- a/docs/c-api/net.h/mg_add_sock_opt.md
+++ b/docs/c-api/net.h/mg_add_sock_opt.md
@@ -3,9 +3,9 @@ title: "mg_add_sock_opt()"
 decl_name: "mg_add_sock_opt"
 symbol_kind: "func"
 signature: |
-  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);
 ---
 
 Creates a connection, associates it with the given socket and event handler
diff --git a/docs/c-api/net.h/mg_bind.md b/docs/c-api/net.h/mg_bind.md
index 800bba9547f2bb7d7341a64d9fd455bbeacb6f5b..9507d51c585e828db6fee531ac9c9cf0ed326be5 100644
--- a/docs/c-api/net.h/mg_bind.md
+++ b/docs/c-api/net.h/mg_bind.md
@@ -3,8 +3,9 @@ title: "mg_bind()"
 decl_name: "mg_bind"
 symbol_kind: "func"
 signature: |
-  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.
diff --git a/docs/c-api/net.h/mg_bind_opt.md b/docs/c-api/net.h/mg_bind_opt.md
index a9cbf953e0d248bcb3cbc62dc6c376c9e9e6f884..823dd0c0aef50fb18bcc8ab48673c1d376e3f2ba 100644
--- a/docs/c-api/net.h/mg_bind_opt.md
+++ b/docs/c-api/net.h/mg_bind_opt.md
@@ -4,8 +4,8 @@ decl_name: "mg_bind_opt"
 symbol_kind: "func"
 signature: |
   struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
-                                    mg_event_handler_t handler,
-                                    struct mg_bind_opts opts);
+                                    MG_CB(mg_event_handler_t handler,
+                                          void *user_data);
 ---
 
 Creates a listening connection.
@@ -15,7 +15,7 @@ the same as for the `mg_connect()` call, where `HOST` part is optional.
 `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`.
 
diff --git a/docs/c-api/net.h/mg_broadcast.md b/docs/c-api/net.h/mg_broadcast.md
index 4eda7f3cd833e4049017d0524e99dee84444a361..cef0b379ceacac570d2167b475d1a5d81957af7c 100644
--- a/docs/c-api/net.h/mg_broadcast.md
+++ b/docs/c-api/net.h/mg_broadcast.md
@@ -3,7 +3,8 @@ title: "mg_broadcast()"
 decl_name: "mg_broadcast"
 symbol_kind: "func"
 signature: |
-  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);
 ---
 
 Passes a message of a given length to all connections.
diff --git a/docs/c-api/net.h/mg_check_ip_acl.md b/docs/c-api/net.h/mg_check_ip_acl.md
index e3087e2b2f2c060dfc29f5be4e84ec0ba03b5fe3..83c2c7ba22837595ea5c2d534ddf764afa314b41 100644
--- a/docs/c-api/net.h/mg_check_ip_acl.md
+++ b/docs/c-api/net.h/mg_check_ip_acl.md
@@ -18,7 +18,7 @@ Subnet masks may vary from 0 to 32, inclusive. The default setting
 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].
diff --git a/docs/c-api/net.h/mg_connect.md b/docs/c-api/net.h/mg_connect.md
index bbacd8f4c9faac91271151c4a9a0bfbf859733f0..788be84e663c6f86bea11ea248d7fa46098aeed4 100644
--- a/docs/c-api/net.h/mg_connect.md
+++ b/docs/c-api/net.h/mg_connect.md
@@ -4,7 +4,8 @@ decl_name: "mg_connect"
 symbol_kind: "func"
 signature: |
   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.
diff --git a/docs/c-api/net.h/mg_connect_opt.md b/docs/c-api/net.h/mg_connect_opt.md
index de7e71392fdc279bea3001376481b2faf2863a30..3ae818a4436731c7d48eabc38f3a4c136cd5870e 100644
--- a/docs/c-api/net.h/mg_connect_opt.md
+++ b/docs/c-api/net.h/mg_connect_opt.md
@@ -4,8 +4,8 @@ decl_name: "mg_connect_opt"
 symbol_kind: "func"
 signature: |
   struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
-                                       mg_event_handler_t handler,
-                                       struct mg_connect_opts opts);
+                                       MG_CB(mg_event_handler_t handler,
+                                             void *user_data);
 ---
 
 Connects to a remote host.
diff --git a/docs/c-api/net.h/mg_enable_javascript.md b/docs/c-api/net.h/mg_enable_javascript.md
deleted file mode 100644
index 763151e02bc46ff18cc856cc897c5f5d4b844601..0000000000000000000000000000000000000000
--- a/docs/c-api/net.h/mg_enable_javascript.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-title: "mg_enable_javascript()"
-decl_name: "mg_enable_javascript"
-symbol_kind: "func"
-signature: |
-  enum v7_err mg_enable_javascript(struct mg_mgr *m, struct v7 *v7,
-                                   const char *init_js_file_name);
----
-
-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. 
-
diff --git a/docs/c-api/net.h/mg_event_handler_t.md b/docs/c-api/net.h/mg_event_handler_t.md
index 191805c8f74dba67a81ae4881d8b05b2fea0ca02..5a4822246d2475a84ac186e4554681b26025a82d 100644
--- a/docs/c-api/net.h/mg_event_handler_t.md
+++ b/docs/c-api/net.h/mg_event_handler_t.md
@@ -4,7 +4,7 @@ decl_name: "mg_event_handler_t"
 symbol_kind: "typedef"
 signature: |
   typedef void (*mg_event_handler_t)(struct mg_connection *nc, int ev,
-                                     void *ev_data);
+                                     void *ev_data MG_UD_ARG(void *user_data));
 ---
 
 Callback function (event handler) prototype. Must be defined by the user.
diff --git a/docs/c-api/net.h/mg_next.md b/docs/c-api/net.h/mg_next.md
index fcd5e1d17b92348206810fcbaba11c444dbdaa92..423af6fdbc591de9b59c4806ecaf56055bdb06dd 100644
--- a/docs/c-api/net.h/mg_next.md
+++ b/docs/c-api/net.h/mg_next.md
@@ -3,7 +3,7 @@ title: "mg_next()"
 decl_name: "mg_next"
 symbol_kind: "func"
 signature: |
-  struct mg_connection *mg_next(struct mg_mgr *, struct mg_connection *);
+  struct mg_connection *mg_next(struct mg_mgr *mgr, struct mg_connection *c);
 ---
 
 Iterates over all active connections.
diff --git a/docs/c-api/net.h/struct_mg_connect_opts.md b/docs/c-api/net.h/struct_mg_connect_opts.md
index f4ef502d7ca1e27393f17ccdfac59addc587753a..f05bf25f482d8771af81d883845cf2f0b4990b7a 100644
--- a/docs/c-api/net.h/struct_mg_connect_opts.md
+++ b/docs/c-api/net.h/struct_mg_connect_opts.md
@@ -8,6 +8,7 @@ signature: |
     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.
diff --git a/docs/c-api/net.h/struct_mg_mgr.md b/docs/c-api/net.h/struct_mg_mgr.md
index dcc2b523dfdab4a5b4d7167a8a911b6c72139087..d33727c38d2314ec252096ed8a560d0a59b02f4e 100644
--- a/docs/c-api/net.h/struct_mg_mgr.md
+++ b/docs/c-api/net.h/struct_mg_mgr.md
@@ -14,9 +14,7 @@ signature: |
     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 */
   };
 ---
 
diff --git a/docs/c-api/net.h/struct_mg_mgr_init_opts.md b/docs/c-api/net.h/struct_mg_mgr_init_opts.md
index da3e49516593452736fb75b5194512cca397ba69..7db23de5bbd8cf1a39fde09b76471174ded345b8 100644
--- a/docs/c-api/net.h/struct_mg_mgr_init_opts.md
+++ b/docs/c-api/net.h/struct_mg_mgr_init_opts.md
@@ -4,9 +4,10 @@ decl_name: "struct mg_mgr_init_opts"
 symbol_kind: "struct"
 signature: |
   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;
   };
 ---
 
diff --git a/docs/c-api/resolv.h/intro.md b/docs/c-api/resolv.h/intro.md
index 3fc5fc069f796ebaf1ff3939e7290805f2ea1d58..f77184f9e5df71c3163d13d9c22ce9d6245bba92 100644
--- a/docs/c-api/resolv.h/intro.md
+++ b/docs/c-api/resolv.h/intro.md
@@ -6,6 +6,7 @@ items:
   - { name: mg_resolve_async.md }
   - { name: mg_resolve_async_opt.md }
   - { name: mg_resolve_from_hosts_file.md }
+  - { name: mg_set_nameserver.md }
   - { name: struct_mg_resolve_async_opts.md }
 ---
 
diff --git a/docs/c-api/resolv.h/mg_set_nameserver.md b/docs/c-api/resolv.h/mg_set_nameserver.md
new file mode 100644
index 0000000000000000000000000000000000000000..2e33c629c54056eba705bedce1c85f0a54ccc9b4
--- /dev/null
+++ b/docs/c-api/resolv.h/mg_set_nameserver.md
@@ -0,0 +1,10 @@
+---
+title: "mg_set_nameserver()"
+decl_name: "mg_set_nameserver"
+symbol_kind: "func"
+signature: |
+  void mg_set_nameserver(struct mg_mgr *mgr, const char *nameserver);
+---
+
+Set default DNS server 
+
diff --git a/docs/c-api/resolv.h/struct_mg_resolve_async_opts.md b/docs/c-api/resolv.h/struct_mg_resolve_async_opts.md
index 6c39e2f1a2c09644a56c0aa884530f53d448122b..8a836b9a0953ad8eb914247364b429852cf3d092 100644
--- a/docs/c-api/resolv.h/struct_mg_resolve_async_opts.md
+++ b/docs/c-api/resolv.h/struct_mg_resolve_async_opts.md
@@ -4,7 +4,7 @@ decl_name: "struct mg_resolve_async_opts"
 symbol_kind: "struct"
 signature: |
   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 */
diff --git a/docs/c-api/uri.h/intro.md b/docs/c-api/uri.h/intro.md
index 154aea63c0cea2a85116944a648df50adf6ef92b..f266b27ec39bc275fdf5f7d14839116585af84ef 100644
--- a/docs/c-api/uri.h/intro.md
+++ b/docs/c-api/uri.h/intro.md
@@ -3,6 +3,7 @@ title: "URI"
 symbol_kind: "intro"
 decl_name: "uri.h"
 items:
+  - { name: mg_assemble_uri.md }
   - { name: mg_parse_uri.md }
 ---
 
diff --git a/docs/c-api/uri.h/mg_assemble_uri.md b/docs/c-api/uri.h/mg_assemble_uri.md
new file mode 100644
index 0000000000000000000000000000000000000000..2006514834514225f39eb4e1d4d88068e8c5b2dd
--- /dev/null
+++ b/docs/c-api/uri.h/mg_assemble_uri.md
@@ -0,0 +1,20 @@
+---
+title: "mg_assemble_uri()"
+decl_name: "mg_assemble_uri"
+symbol_kind: "func"
+signature: |
+  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);
+---
+
+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. 
+
diff --git a/docs/c-api/uri.h/mg_parse_uri.md b/docs/c-api/uri.h/mg_parse_uri.md
index 48cb6d943b29ba9849e58a29e0b8f329e82a6b43..2d330f18e71d630b64f89172afefac6bfd714b9a 100644
--- a/docs/c-api/uri.h/mg_parse_uri.md
+++ b/docs/c-api/uri.h/mg_parse_uri.md
@@ -3,7 +3,7 @@ title: "mg_parse_uri()"
 decl_name: "mg_parse_uri"
 symbol_kind: "func"
 signature: |
-  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);
diff --git a/docs/c-api/util.h/intro.md b/docs/c-api/util.h/intro.md
index 9b30d680091764b2d58fe6f3c7cbf72788314b1d..73c4f792229377241116200a7f7b5a4c9706d83b 100644
--- a/docs/c-api/util.h/intro.md
+++ b/docs/c-api/util.h/intro.md
@@ -8,20 +8,21 @@ items:
   - { name: mg_basic_auth_header.md }
   - { name: mg_conn_addr_to_str.md }
   - { name: mg_fopen.md }
+  - { name: mg_fread.md }
+  - { name: mg_fwrite.md }
   - { name: mg_hexdump.md }
   - { name: mg_hexdump_connection.md }
   - { name: mg_hexdumpf.md }
   - { name: mg_is_big_endian.md }
-  - { name: mg_match_prefix.md }
   - { name: mg_mbuf_append_base64.md }
   - { name: mg_mbuf_append_base64_putc.md }
-  - { name: mg_next_comma_list_entry.md }
   - { name: mg_open.md }
   - { name: mg_skip.md }
   - { name: mg_sock_addr_to_str.md }
   - { name: mg_sock_to_str.md }
   - { name: mg_start_thread.md }
   - { name: mg_stat.md }
+  - { name: mg_url_encode.md }
 ---
 
 
diff --git a/docs/c-api/util.h/mg_basic_auth_header.md b/docs/c-api/util.h/mg_basic_auth_header.md
index d8b582a5a622b839903e74d1fc6617502e08e881..724ec05391cc6b6a09a9dea8ecf6aeda7b9186aa 100644
--- a/docs/c-api/util.h/mg_basic_auth_header.md
+++ b/docs/c-api/util.h/mg_basic_auth_header.md
@@ -3,7 +3,8 @@ title: "mg_basic_auth_header()"
 decl_name: "mg_basic_auth_header"
 symbol_kind: "func"
 signature: |
-  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);
 ---
 
 Generate a Basic Auth header and appends it to buf.
diff --git a/docs/c-api/util.h/mg_conn_addr_to_str.md b/docs/c-api/util.h/mg_conn_addr_to_str.md
index e623734fbf0b5286de40fbe6c20dbb1d548c7c04..b7603fb29aa76fcfb32d8700f2a79c880464768a 100644
--- a/docs/c-api/util.h/mg_conn_addr_to_str.md
+++ b/docs/c-api/util.h/mg_conn_addr_to_str.md
@@ -3,8 +3,8 @@ title: "mg_conn_addr_to_str()"
 decl_name: "mg_conn_addr_to_str"
 symbol_kind: "func"
 signature: |
-  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);
 ---
 
 Converts a connection's local or remote address into string.
@@ -17,5 +17,6 @@ see `MG_SOCK_STRINGIFY_*` definitions.
 - MG_SOCK_STRINGIFY_REMOTE - print remote peer's IP/port, not local address
 
 If both port number and IP address are printed, they are separated by `:`.
-If compiled with `-DMG_ENABLE_IPV6`, IPv6 addresses are supported. 
+If compiled with `-DMG_ENABLE_IPV6`, IPv6 addresses are supported.
+Return length of the stringified address. 
 
diff --git a/docs/c-api/util.h/mg_fread.md b/docs/c-api/util.h/mg_fread.md
new file mode 100644
index 0000000000000000000000000000000000000000..0ff4994ad470bc9d79e7745fcbaf8213e9bf6145
--- /dev/null
+++ b/docs/c-api/util.h/mg_fread.md
@@ -0,0 +1,12 @@
+---
+title: "mg_fread()"
+decl_name: "mg_fread"
+symbol_kind: "func"
+signature: |
+  size_t mg_fread(void *ptr, size_t size, size_t count, FILE *f);
+---
+
+Reads data from the given file stream.
+
+Return value is a number of bytes readen. 
+
diff --git a/docs/c-api/util.h/mg_fwrite.md b/docs/c-api/util.h/mg_fwrite.md
new file mode 100644
index 0000000000000000000000000000000000000000..5ddbbbcea9b6030e56cc7b3ca993416b469c88ea
--- /dev/null
+++ b/docs/c-api/util.h/mg_fwrite.md
@@ -0,0 +1,12 @@
+---
+title: "mg_fwrite()"
+decl_name: "mg_fwrite"
+symbol_kind: "func"
+signature: |
+  size_t mg_fwrite(const 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. 
+
diff --git a/docs/c-api/util.h/mg_match_prefix.md b/docs/c-api/util.h/mg_match_prefix.md
deleted file mode 100644
index 15ddc9ef1fc17e80f343bec5b6fbeb665f377ad2..0000000000000000000000000000000000000000
--- a/docs/c-api/util.h/mg_match_prefix.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-title: "mg_match_prefix()"
-decl_name: "mg_match_prefix"
-symbol_kind: "func"
-signature: |
-  int mg_match_prefix(const char *pattern, int pattern_len, const char *str);
----
-
-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. 
-
diff --git a/docs/c-api/util.h/mg_next_comma_list_entry.md b/docs/c-api/util.h/mg_next_comma_list_entry.md
deleted file mode 100644
index a96103936f5463494dfd5e0dda46da5354f1dc0d..0000000000000000000000000000000000000000
--- a/docs/c-api/util.h/mg_next_comma_list_entry.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-title: "mg_next_comma_list_entry()"
-decl_name: "mg_next_comma_list_entry"
-symbol_kind: "func"
-signature: |
-  const char *mg_next_comma_list_entry(const char *list, struct mg_str *val,
-                                       struct mg_str *eq_val);
----
-
-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. 
-
diff --git a/docs/c-api/util.h/mg_sock_addr_to_str.md b/docs/c-api/util.h/mg_sock_addr_to_str.md
index 3502e135e44da7e64a51d0b2d6b9fbab944fe234..b5cb6fddd17267e36c470bf08b3f57fca583dcee 100644
--- a/docs/c-api/util.h/mg_sock_addr_to_str.md
+++ b/docs/c-api/util.h/mg_sock_addr_to_str.md
@@ -3,8 +3,8 @@ title: "mg_sock_addr_to_str()"
 decl_name: "mg_sock_addr_to_str"
 symbol_kind: "func"
 signature: |
-  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);
 ---
 
 Convert the socket's address into string.
diff --git a/docs/c-api/util.h/mg_url_encode.md b/docs/c-api/util.h/mg_url_encode.md
new file mode 100644
index 0000000000000000000000000000000000000000..105ce9473eb7b005411b04081361e92461aab443
--- /dev/null
+++ b/docs/c-api/util.h/mg_url_encode.md
@@ -0,0 +1,13 @@
+---
+title: "mg_url_encode()"
+decl_name: "mg_url_encode"
+symbol_kind: "func"
+signature: |
+  struct mg_str mg_url_encode(const struct mg_str src);
+---
+
+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. 
+
diff --git a/docs/http/cgi.md b/docs/http/cgi.md
index 8e2309db58223b754923078da640c110a5823ab1..86b0a4f3db50201d58aac217450e56bfedc2a9fe 100644
--- a/docs/http/cgi.md
+++ b/docs/http/cgi.md
@@ -16,7 +16,7 @@ CGI file must be executable. Mongoose honours the shebang line - see
 http://en.wikipedia.org/wiki/Shebang_(Unix).
 
 For example, if both PHP and Perl CGIs are used, then
-``#!/path/to/php-cgi.exe` and ``#!/path/to/perl.exe` must be the first lines
+`#!/path/to/php-cgi.exe` and `#!/path/to/perl.exe` must be the first lines
 of the respective CGI scripts.
 
 It is possible to hardcode the path to the CGI interpreter for all
@@ -29,3 +29,7 @@ Example:
 ```c
   opts.cgi_interpreter = "C:\\ruby\\ruby.exe";
 ```
+NOTE: In the CGI handler we don't use explicitly a system call waitpid() for
+reaping zombie processes. Instead, we set the SIGCHLD handler to SIG_IGN.
+It will cause zombie processes to be reaped automatically.
+CAUTION: not all OSes (e.g. QNX) reap zombies if SIGCHLD is ignored.
\ No newline at end of file
diff --git a/docs/http/client_example.md b/docs/http/client_example.md
index 9f7e06e0a72936bace89140f2d130921beff7ad6..2b159a6594d157ca6772aa4333b8f10f0fdecf95 100644
--- a/docs/http/client_example.md
+++ b/docs/http/client_example.md
@@ -18,8 +18,9 @@ static int exit_flag = 0;
 
 static void ev_handler(struct mg_connection *c, int ev, void *p) {
   if (ev == MG_EV_HTTP_REPLY) {
+    struct http_message *hm = (struct http_message *)p;
     c->flags |= MG_F_CLOSE_IMMEDIATELY;
-    fwrite(hm->message.p, 1, hm->message.len, stdout);
+    fwrite(hm->message.p, 1, (int)hm->message.len, stdout);
     putchar('\n');
     exit_flag = 1;
   } else if (ev == MG_EV_CLOSE) {
@@ -31,7 +32,7 @@ int main(void) {
   struct mg_mgr mgr;
 
   mg_mgr_init(&mgr, NULL);
-  mg_connect_http(mgr, ev_handler, url, NULL, NULL);
+  mg_connect_http(&mgr, ev_handler, url, NULL, NULL);
 
 
   while (exit_flag == 0) {
diff --git a/docs/http/server_example.md b/docs/http/server_example.md
index a244d647d2acd09c4bade5d847579aac00a5dd03..225beb406be55ed5fb63fb2d4de0884e5ddac9c9 100644
--- a/docs/http/server_example.md
+++ b/docs/http/server_example.md
@@ -30,8 +30,8 @@ static void ev_handler(struct mg_connection *c, int ev, void *p) {
 
     // We have received an HTTP request. Parsed request is contained in `hm`.
     // Send HTTP reply to the client which shows full original request.
-    mg_send_head(c, 200, hm.message.len, "Content-Type: text/plain");
-    mg_printf(c, "%.*s", hm.message.len, hm.message.p);
+    mg_send_head(c, 200, hm->message.len, "Content-Type: text/plain");
+    mg_printf(c, "%.*s", (int)hm->message.len, hm->message.p);
   }
 }
 
diff --git a/docs/http/ssi.md b/docs/http/ssi.md
index 2fcfbc4db5a07e6c80b6798089dc5ac4403ec9c2..23c390e117cec0369cc664206f53d2486d0a76fa 100644
--- a/docs/http/ssi.md
+++ b/docs/http/ssi.md
@@ -35,7 +35,7 @@ The exec directive is used to execute a command on a server,
 and show command's output. Example: `<!--#exec "ls -l" -->`
 
 The call directive is a way to invoke a C handler from the HTML page.
-On each occurence of `<!--#call PARAMS -->` directive,
+On each occurrence of `<!--#call PARAMS -->` directive,
 Mongoose calls a registered event handler with `MG_EV_SSI_CALL` event.
 Event parameter will point to the `PARAMS` string.
 An event handler can output any text, for example by calling
diff --git a/docs/http/ssl.md b/docs/http/ssl.md
index 9908f1906c996f0366ddfbf97c6e37b71eda6218..c317e57a6a6a22fa2ae46fadd9f085967dd1f871 100644
--- a/docs/http/ssl.md
+++ b/docs/http/ssl.md
@@ -30,4 +30,4 @@ int main(void) {
 }
 ```
 
-For the full example, please see the [Simplest HTTPS server example](https://github.com/cesanta/dev/tree/master/mongoose/examples/simplest_web_server_ssl).
+For the full example, please see the [Simplest HTTPS server example](https://github.com/cesanta/mongoose/tree/master/examples/simplest_web_server_ssl).
diff --git a/docs/overview/build-options/disabling-flags.md b/docs/overview/build-options/disabling-flags.md
index 8633083f3a13213c4b401a86ce0a41d2a6b92c2d..767438fe4afcb86c422703c04b7095481a3efe07 100644
--- a/docs/overview/build-options/disabling-flags.md
+++ b/docs/overview/build-options/disabling-flags.md
@@ -3,6 +3,6 @@ title: Disabling flags
 ---
 
 - `MG_DISABLE_HTTP_DIGEST_AUTH` disable HTTP Digest (MD5) authorisation support
-- `MG_DISABLE_SHA1` disable SHA1 support (used by WebSocket)
-- `MG_DISABLE_MD5` disable MD5 support (used by HTTP auth)
+- `CS_DISABLE_SHA1` disable SHA1 support (used by WebSocket)
+- `CS_DISABLE_MD5` disable MD5 support (used by HTTP auth)
 - `MG_DISABLE_HTTP_KEEP_ALIVE` useful for embedded systems to save resources
diff --git a/docs/overview/build-options/tunables.md b/docs/overview/build-options/tunables.md
index e1ca4c63c1c7702dac15e07c57f774f3d1c1cc44..c9944c4c26e162dc99a3513b175f0b23472952dd 100644
--- a/docs/overview/build-options/tunables.md
+++ b/docs/overview/build-options/tunables.md
@@ -9,3 +9,4 @@ title: Tunables
 - `MG_SSL_CRYPTO_MODERN`, `MG_SSL_CRYPTO_OLD` - choose either "Modern" or "Old" ciphers
   instead of the default "Intermediate" setting.
   See [this article](https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations) for details.
+- `MG_USER_FILE_FUNCTIONS` allow you to use custom file operation, by defining you own `mg_stat`, `mg_fopen`, `mg_open`, `mg_fread` and `mg_fwrite` functions
\ No newline at end of file
diff --git a/examples/CC3200/Makefile.build b/examples/CC3200/Makefile.build
index 7d567d0f1f65deb53e98e1cb46b65050847c9f3f..6e16c3cef587fcb586d14293540463a1c4c0e23a 100644
--- a/examples/CC3200/Makefile.build
+++ b/examples/CC3200/Makefile.build
@@ -22,7 +22,7 @@ IPATH = . ../.. $(REPO_PATH)
 
 VPATH = ../..
 
-MONGOOSE_FEATURES = -DMG_ENABLE_SSL -DMG_ENABLE_HTTP_STREAMING_MULTIPART
+MONGOOSE_FEATURES = -DMG_ENABLE_SSL -DMG_ENABLE_HTTP_STREAMING_MULTIPART -DMG_MODULE_LINES
 
 SDK_FLAGS = -DUSE_FREERTOS -DSL_PLATFORM_MULTI_THREADED
 # -DTARGET_IS_CC3200 would reduce code size by using functions in ROM
@@ -58,11 +58,11 @@ $(FW_DIR):
 $(FW_ZIP): $(FW_ELF) $(FW_BIN) $(SLFS_FILES)
 	@echo "    Code size: $(shell ls -l $(FW_BIN) | awk '{print $$5}')"
 	@echo "  GEN   $(FW_MANIFEST)"
-	@fw_meta gen_build_info \
+	@fw_meta.py gen_build_info \
 	  --json_output=$(BUILD_INFO_JSON)
 	@cp -v $(SLFS_FILES) out/
 	@cp $(CC3200_SP_FILE)* $(FW_DIR)
-	@fw_meta create_manifest \
+	@fw_meta.py create_manifest \
 	  --name=$(PROG) --platform=$(PLATFORM) \
 	  --build_info=$(BUILD_INFO_JSON) \
 	  --output=$(FW_MANIFEST) \
@@ -72,14 +72,14 @@ $(FW_ZIP): $(FW_ELF) $(FW_BIN) $(SLFS_FILES)
 	  /sys/mcuimg.bin:type=app,src=$(notdir $(FW_BIN)) \
 	  $(foreach f,$(SLFS_FILES), $(notdir $(f)):type=slfile,src=$(notdir $(f)))
 	@echo "  ZIP   $@"
-	@fw_meta create_fw \
+	@fw_meta.py create_fw \
 	  --manifest=$(FW_MANIFEST) \
 	  --src_dir=$(FW_DIR) \
 	  --output=$@
 
 FREERTOS_SRCS = timers.c list.c queue.c tasks.c port.c heap_3.c osi_freertos.c
 DRIVER_SRCS = cpu.c gpio.c gpio_if.c i2c.c i2c_if.c interrupt.c pin.c prcm.c spi.c uart.c udma.c utils.c
-SL_SRCS = socket.c wlan.c driver.c device.c netapp.c netcfg.c network_common.c cc_pal.c fs.c
+SL_SRCS = socket.c wlan.c driver.c device.c netapp.c netcfg.c network_common.c timer.c cc_pal.c fs.c
 SDK_SRCS = startup_gcc.c $(FREERTOS_SRCS) $(DRIVER_SRCS) $(SL_SRCS)
 IPATH += $(SDK_PATH) $(SDK_PATH)/inc $(SDK_PATH)/driverlib \
          $(SDK_PATH)/example/common $(SDK_PATH)/oslib \
diff --git a/examples/CC3200/cs_dbg.h b/examples/CC3200/cs_dbg.h
index 8e86f92d1b780116c1dd1f242671d7123c3f84e5..e987c427a440dbf43c8d660df7a82936d3f2f27b 100644
--- a/examples/CC3200/cs_dbg.h
+++ b/examples/CC3200/cs_dbg.h
@@ -40,22 +40,22 @@ void cs_log_set_level(enum cs_log_level level);
 
 void cs_log_set_file(FILE *file);
 
-extern enum cs_log_level cs_log_level;
+extern enum cs_log_level cs_log_threshold;
 void cs_log_print_prefix(const char *func);
 void cs_log_printf(const char *fmt, ...);
 
 #define LOG(l, x)                  \
-  if (cs_log_level >= l) {         \
+  if (cs_log_threshold >= l) {     \
     cs_log_print_prefix(__func__); \
     cs_log_printf x;               \
   }
 
 #ifndef CS_NDEBUG
 
-#define DBG(x)                            \
-  if (cs_log_level >= LL_VERBOSE_DEBUG) { \
-    cs_log_print_prefix(__func__);        \
-    cs_log_printf x;                      \
+#define DBG(x)                                \
+  if (cs_log_threshold >= LL_VERBOSE_DEBUG) { \
+    cs_log_print_prefix(__func__);            \
+    cs_log_printf x;                          \
   }
 
 #else /* NDEBUG */
diff --git a/examples/CC3200/main.c b/examples/CC3200/main.c
index 24eea055e31a9bec6d2f995b3b2996e3bad9ce87..dbda74ae665611ecb5395d2837140d4be0b448e0 100644
--- a/examples/CC3200/main.c
+++ b/examples/CC3200/main.c
@@ -294,3 +294,16 @@ void SimpleLinkGeneralEventHandler(SlDeviceEvent_t *e) {
   LOG(LL_ERROR, ("status %d sender %d", e->EventData.deviceEvent.status,
                  e->EventData.deviceEvent.sender));
 }
+
+#ifndef __TI_COMPILER_VERSION__
+int _gettimeofday_r(struct _reent *r, struct timeval *tp, void *tz) {
+#else
+int gettimeofday(struct timeval *tp, void *tz) {
+#endif
+  unsigned long sec;
+  unsigned short msec;
+  MAP_PRCMRTCGet(&sec, &msec);
+  tp->tv_sec = sec;
+  tp->tv_usec = ((unsigned long) msec) * 1000;
+  return 0;
+}
diff --git a/examples/CC3200/sdk.version b/examples/CC3200/sdk.version
index 8f4f18a30b67a9bbf9efc7087af37e858101910c..f8bd0daecd6033a3fdf563fde2e1e337ac7c818a 100644
--- a/examples/CC3200/sdk.version
+++ b/examples/CC3200/sdk.version
@@ -1 +1 @@
-docker.cesanta.com/cc3200-build:1.2.0-r8
+docker.cesanta.com/cc3200-build:1.3.0-r2
diff --git a/examples/ESP32_IDF/Makefile b/examples/ESP32_IDF/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e40fd4c0df83a2546c07f713d3feba9b60235e4c
--- /dev/null
+++ b/examples/ESP32_IDF/Makefile
@@ -0,0 +1,16 @@
+SDK_VER=$(shell cat sdk.version)
+
+.PHONY: all
+
+MG_PATH = $(realpath $(PWD)/../../)
+
+all:
+	docker run --rm -it -v $(MG_PATH):/esp32_idf \
+                   $(SDK_VER) \
+                   bash -c "cd esp32_idf/examples/ESP32_IDF && \
+                   make -f Makefile.build defconfig && make -f Makefile.build"
+
+clean:
+	docker run --rm -it -v $(MG_PATH):/esp32_idf \
+                   $(SDK_VER) \
+                   bash -c "rm -rf esp32_idf/examples/ESP32_IDF/build"
diff --git a/examples/ESP32_IDF/Makefile.build b/examples/ESP32_IDF/Makefile.build
new file mode 100644
index 0000000000000000000000000000000000000000..5a6de248d87e1cdbe69988a0da43b148c498b48c
--- /dev/null
+++ b/examples/ESP32_IDF/Makefile.build
@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := esp32_idf
+
+include $(IDF_PATH)/make/project.mk
+
diff --git a/examples/ESP32_IDF/README.md b/examples/ESP32_IDF/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4af679a09625b4a97ac1fb331b79c092b845de1a
--- /dev/null
+++ b/examples/ESP32_IDF/README.md
@@ -0,0 +1,18 @@
+This is a Mongoose "Hello, world" that can be compiled under Espressif IoT Development Framework for ESP32
+
+It connects to WiFi network and serves a "hello world" page.
+
+Most of the the boilerplate comes from [project_template](https://github.com/espressif/esp-idf-template) with minimal changes.
+
+For building the example, you need to have [Docker](https://www.docker.com/products/docker) and use our pre-built SDK container.
+To build just run in the example directory
+```
+$ make
+```
+
+Note: before building, change `WIFI_SSID` and `WIFI_PASS` macros in main/main.c file
+
+Once built, use [esptool](https://github.com/espressif/esptool) for flashing
+```
+$ python esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 115200 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x1000 build/bootloader/bootloader.bin 0x10000 build/esp32_idf.bin 0x8000 build/partitions_singleapp.bin
+```
diff --git a/examples/ESP32_IDF/main/component.mk b/examples/ESP32_IDF/main/component.mk
new file mode 100644
index 0000000000000000000000000000000000000000..f8659a72c3b04f2fe5ea09a77e6c250606057df7
--- /dev/null
+++ b/examples/ESP32_IDF/main/component.mk
@@ -0,0 +1,17 @@
+#
+# Main component makefile.
+#
+# This Makefile can be left empty. By default, it will take the sources in the
+# src/ directory, compile them and link them into lib(subdirectory_name).a
+# in the build directory. This behaviour is entirely configurable,
+# please read the ESP-IDF documents if you need to do this.
+#
+
+COMPONENT_OBJS = main.o mongoose.o
+
+mongoose.o: ../../../../mongoose.c
+	$(summary) "CC $@"
+	$(CC) $(CFLAGS) $(CPPFLAGS) \
+	  $(addprefix -I ,$(COMPONENT_INCLUDES)) \
+	  $(addprefix -I ,$(COMPONENT_EXTRA_INCLUDES)) \
+	  -c $< -o $@
diff --git a/examples/ESP32_IDF/main/main.c b/examples/ESP32_IDF/main/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..3b9a0ee74e9be7b553f0c275c0a44e03e6a617dc
--- /dev/null
+++ b/examples/ESP32_IDF/main/main.c
@@ -0,0 +1,93 @@
+#include <string.h>
+
+#include "freertos/FreeRTOS.h"
+
+#include "esp_event.h"
+#include "esp_event_loop.h"
+#include "esp_system.h"
+#include "esp_wifi.h"
+#include "nvs_flash.h"
+
+#include "./../../../mongoose.h"
+
+#define WIFI_SSID "ssid"
+#define WIFI_PASS "pass"
+
+#define MG_LISTEN_ADDR "80"
+
+static esp_err_t event_handler(void *ctx, system_event_t *event) {
+  (void) ctx;
+  (void) event;
+  return ESP_OK;
+}
+
+static void mg_ev_handler(struct mg_connection *nc, int ev, void *p) {
+  static const char *reply_fmt =
+      "HTTP/1.0 200 OK\r\n"
+      "Connection: close\r\n"
+      "Content-Type: text/plain\r\n"
+      "\r\n"
+      "Hello %s\n";
+
+  switch (ev) {
+    case MG_EV_ACCEPT: {
+      char addr[32];
+      mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
+                          MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
+      printf("Connection %p from %s\n", nc, addr);
+      break;
+    }
+    case MG_EV_HTTP_REQUEST: {
+      char addr[32];
+      struct http_message *hm = (struct http_message *) p;
+      mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
+                          MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
+      printf("HTTP request from %s: %.*s %.*s\n", addr, (int) hm->method.len,
+             hm->method.p, (int) hm->uri.len, hm->uri.p);
+      mg_printf(nc, reply_fmt, addr);
+      nc->flags |= MG_F_SEND_AND_CLOSE;
+      break;
+    }
+    case MG_EV_CLOSE: {
+      printf("Connection %p closed\n", nc);
+      break;
+    }
+  }
+}
+
+void app_main(void) {
+  nvs_flash_init();
+  tcpip_adapter_init();
+  ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
+
+  /* Initializing WiFi */
+  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+  ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+  ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
+  ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+  wifi_config_t sta_config = {
+      .sta = {.ssid = WIFI_SSID, .password = WIFI_PASS, .bssid_set = false}};
+  ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
+  ESP_ERROR_CHECK(esp_wifi_start());
+  ESP_ERROR_CHECK(esp_wifi_connect());
+
+  /* Starting Mongoose */
+  struct mg_mgr mgr;
+  struct mg_connection *nc;
+
+  printf("Starting web-server on port %s\n", MG_LISTEN_ADDR);
+
+  mg_mgr_init(&mgr, NULL);
+
+  nc = mg_bind(&mgr, MG_LISTEN_ADDR, mg_ev_handler);
+  if (nc == NULL) {
+    printf("Error setting up listener!\n");
+    return;
+  }
+  mg_set_protocol_http_websocket(nc);
+
+  /* Processing events */
+  while (1) {
+    mg_mgr_poll(&mgr, 1000);
+  }
+}
diff --git a/examples/ESP32_IDF/sdk.version b/examples/ESP32_IDF/sdk.version
new file mode 100644
index 0000000000000000000000000000000000000000..174eac89b428df5e04b7284448dc25604d673da4
--- /dev/null
+++ b/examples/ESP32_IDF/sdk.version
@@ -0,0 +1 @@
+docker.cesanta.com/esp32-build:1.0-r13
diff --git a/examples/ESP8266_RTOS/build.sh b/examples/ESP8266_RTOS/build.sh
index 749d1f156958949a32e256c0beb922e168d17fbf..a7d1e0ec997c52cf94074cdc6bc08d56b93ebb90 100755
--- a/examples/ESP8266_RTOS/build.sh
+++ b/examples/ESP8266_RTOS/build.sh
@@ -1,7 +1,8 @@
 #!/bin/bash
+REPO=$(cd ../.. && pwd)
 
 docker run \
-  --rm -i -v $(realpath ${PWD}/../..):/src \
+  --rm -i -v $REPO:/src \
   --entrypoint=/bin/bash $(cat sdk.version) -l -c -x '
     export SDK_PATH=/opt/Espressif/ESP8266_RTOS_SDK;
     export BIN_PATH=./bin;
diff --git a/examples/Makefile b/examples/Makefile
index a6c96ddc5ca6c7589e2a286f098b18ad5d8a518b..9f364d2e2f2a6d579bec7c9bfc8e33b7b985bcfe 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -3,7 +3,7 @@
 
 # `wildcard ./*/` works in both linux and linux/wine, while `wildcard */` enumerates nothing under wine
 SUBDIRS = $(sort $(dir $(wildcard ./*/)))
-SUBDIRS:=$(filter-out ./ ./CC3200/ ./ESP8266_RTOS/ ./mbed/ ./MSP432/ ./nRF51/ ./nRF52/ ./NXP_K64/ ./NXP_LPC4088/ ./PIC32/ ./STM32F4_CC3100/ ./TM4C129/ ./WinCE/, $(SUBDIRS))
+SUBDIRS:=$(filter-out ./ ./CC3200/ ./ESP32_IDF/ ./ESP8266_RTOS/ ./mbed/ ./MSP432/ ./nRF51/ ./nRF52/ ./NXP_K64/ ./NXP_LPC4088/ ./PIC32/ ./STM32F4_CC3100/ ./TM4C129/ ./WinCE/, $(SUBDIRS))
 
 ifeq ($(OS), Windows_NT)
   SUBDIRS:=$(filter-out ./netcat/ ./raspberry_pi_mjpeg_led/ ./captive_dns_server/, $(SUBDIRS))
diff --git a/examples/big_upload/big_upload.c b/examples/big_upload/big_upload.c
index 8bd3cc5a519c3ee7e3d6b6f442747a0582745a26..563436503e797b8319a11a14a7240ff067c37298 100644
--- a/examples/big_upload/big_upload.c
+++ b/examples/big_upload/big_upload.c
@@ -10,28 +10,13 @@
 #include "mongoose.h"
 
 static const char *s_http_port = "8000";
+static struct mg_serve_http_opts s_http_server_opts;
 
 struct file_writer_data {
   FILE *fp;
   size_t bytes_written;
 };
 
-static void handle_request(struct mg_connection *nc) {
-  // This handler gets for all endpoints but /upload
-  mg_printf(nc, "%s",
-            "HTTP/1.1 200 OK\r\n"
-            "Content-Type: text/html\r\n"
-            "Connection: close\r\n"
-            "\r\n"
-            "<html><body>Upload example."
-            "<form method=\"POST\" action=\"/upload\" "
-            "  enctype=\"multipart/form-data\">"
-            "<input type=\"file\" name=\"file\" /> <br/>"
-            "<input type=\"submit\" value=\"Upload\" />"
-            "</form></body></html>");
-  nc->flags |= MG_F_SEND_AND_CLOSE;
-}
-
 static void handle_upload(struct mg_connection *nc, int ev, void *p) {
   struct file_writer_data *data = (struct file_writer_data *) nc->user_data;
   struct mg_http_multipart_part *mp = (struct mg_http_multipart_part *) p;
@@ -82,25 +67,27 @@ static void handle_upload(struct mg_connection *nc, int ev, void *p) {
 }
 
 static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
-  (void) ev_data;
-  switch (ev) {
-    case MG_EV_HTTP_REQUEST:
-      // Invoked when the full HTTP request is in the buffer (including body).
-      handle_request(nc);
-      break;
+  if (ev == MG_EV_HTTP_REQUEST) {
+    mg_serve_http(nc, ev_data, s_http_server_opts);
   }
 }
 
 int main(void) {
   struct mg_mgr mgr;
-  struct mg_connection *nc;
+  struct mg_connection *c;
 
   mg_mgr_init(&mgr, NULL);
-  nc = mg_bind(&mgr, s_http_port, ev_handler);
+  c = mg_bind(&mgr, s_http_port, ev_handler);
+  if (c == NULL) {
+    fprintf(stderr, "Cannot start server on port %s\n", s_http_port);
+    exit(EXIT_FAILURE);
+  }
+
+  s_http_server_opts.document_root = ".";  // Serve current directory
+  mg_register_http_endpoint(c, "/upload", handle_upload MG_UD_ARG(NULL));
 
-  mg_register_http_endpoint(nc, "/upload", handle_upload);
   // Set up HTTP server parameters
-  mg_set_protocol_http_websocket(nc);
+  mg_set_protocol_http_websocket(c);
 
   printf("Starting web server on port %s\n", s_http_port);
   for (;;) {
diff --git a/examples/big_upload/index.html b/examples/big_upload/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..c162c653aba05759e0a62591bc99fa640021b9c4
--- /dev/null
+++ b/examples/big_upload/index.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>AJAX Upload Example</title>
+  <script src="//code.jquery.com/jquery-1.9.1.js"></script>
+  <script type="text/javascript">
+    function updateProgress(evt) {
+      if (evt.lengthComputable) {
+        document.getElementById("output").textContent =
+              "Uploaded " + evt.loaded + " of " + evt.total + " bytes";
+      }
+    }
+    function uploadFile() {
+      var file_data = new FormData(document.getElementById('filename'));
+      $.ajax({
+        url: "/upload",
+        type: "POST",
+        data: file_data,
+        processData: false,
+        contentType: false,
+        cache: false,
+        xhr: function() {
+          myXhr = $.ajaxSettings.xhr();
+          if(myXhr.upload){
+            myXhr.upload.addEventListener('progress',updateProgress, false); // for handling the progress of the upload
+          }
+          return myXhr;
+        },
+      }).done(function(data) {
+          document.getElementById("output").textContent = "Result: " + data;
+      });
+      return false;
+    }
+  </script>
+</head>
+
+<body>
+  <h1>Upload file using standard form upload</h1>
+
+  <form method="POST" action="/upload" enctype="multipart/form-data">
+    <label>Select a file:</label><br>
+    <input type="file" name="file" />
+    <input type="submit" value="Upload" />
+  </form>
+
+  <h1>Upload file using Ajax - that also gives progress report</h1>
+  <form method="post" id="filename" name="filename" onsubmit="return uploadFile();">
+    <label>Select a file:</label><br>
+    <input type="file" id="file" name="file" required />
+    <input type="submit" value="Upload" />
+  </form>
+  <br><br><div id="output"></div>
+</body>
+</html>
diff --git a/examples/mqtt_broker/Makefile b/examples/mqtt_broker/Makefile
index f0c17a0e0daaed2a74d8425a185f4953a76e6f12..86f0b09b0fb24c5f7286336e421ce33089a4a3a0 100644
--- a/examples/mqtt_broker/Makefile
+++ b/examples/mqtt_broker/Makefile
@@ -1,4 +1,4 @@
 PROG = mqtt_broker
 MODULE_CFLAGS = -DMG_ENABLE_MQTT_BROKER -DMG_ENABLE_HTTP=0
-SSL_LIB=openssl
+#SSL_LIB=openssl
 include ../examples.mk
diff --git a/examples/mqtt_broker/mqtt_broker.c b/examples/mqtt_broker/mqtt_broker.c
index 4c02f9b613b5dbc4f314fb196b698e7cf922d58d..4cc5185b5088afad3a6d71e04a1697d911a2dec8 100644
--- a/examples/mqtt_broker/mqtt_broker.c
+++ b/examples/mqtt_broker/mqtt_broker.c
@@ -17,22 +17,31 @@
 
 #include "../../mongoose.h"
 
+static const char *s_listening_address = "0.0.0.0:1883";
+
+static void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
+  if (ev != MG_EV_POLL) printf("USER HANDLER GOT EVENT %d\n", ev);
+  /* Do your custom event processing here */
+  mg_mqtt_broker(c, ev, ev_data);
+}
+
 int main(void) {
   struct mg_mgr mgr;
-  const char *address = "0.0.0.0:1883";
-  struct mg_connection *nc;
+  struct mg_connection *c;
   struct mg_mqtt_broker brk;
 
   mg_mgr_init(&mgr, NULL);
   mg_mqtt_broker_init(&brk, NULL);
 
-  if ((nc = mg_bind(&mgr, address, mg_mqtt_broker)) == NULL) {
-    fprintf(stderr, "mg_bind(%s) failed\n", address);
+  if ((c = mg_bind(&mgr, s_listening_address, ev_handler)) == NULL) {
+    fprintf(stderr, "mg_bind(%s) failed\n", s_listening_address);
     exit(EXIT_FAILURE);
   }
-  nc->user_data = &brk;
+  mg_mqtt_broker_init(&brk, NULL);
+  c->user_data = &brk;
+  mg_set_protocol_mqtt(c);
 
-  printf("MQTT broker started on %s\n", address);
+  printf("MQTT broker started on %s\n", s_listening_address);
 
   /*
    * TODO: Add a HTTP status page that shows current sessions
diff --git a/examples/mqtt_client/mqtt_client.c b/examples/mqtt_client/mqtt_client.c
index da0282bb832129ef60c5f60f3e1283c04a3f3917..20090efe6327beafc06c49d37fd4ae622c004d2d 100644
--- a/examples/mqtt_client/mqtt_client.c
+++ b/examples/mqtt_client/mqtt_client.c
@@ -27,10 +27,7 @@ static void ev_handler(struct mg_connection *nc, int ev, void *p) {
   struct mg_mqtt_message *msg = (struct mg_mqtt_message *) p;
   (void) nc;
 
-#if 0
-  if (ev != MG_EV_POLL)
-    printf("USER HANDLER GOT %d\n", ev);
-#endif
+  if (ev != MG_EV_POLL) printf("USER HANDLER GOT EVENT %d\n", ev);
 
   switch (ev) {
     case MG_EV_CONNECT: {
diff --git a/examples/mqtt_over_websocket_server/Makefile b/examples/mqtt_over_websocket_server/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..7f6be7c6cb9b1bdcfaa1a0407ef67c4865ee2edf
--- /dev/null
+++ b/examples/mqtt_over_websocket_server/Makefile
@@ -0,0 +1,4 @@
+PROG = mqtt_over_websocket_server
+MODULE_CFLAGS = -DMG_ENABLE_MQTT_BROKER=1
+#SSL_LIB=mbedtls
+include ../examples.mk
diff --git a/examples/mqtt_over_websocket_server/mqtt_over_websocket_server.c b/examples/mqtt_over_websocket_server/mqtt_over_websocket_server.c
new file mode 100644
index 0000000000000000000000000000000000000000..885921ce93a7213c2ec16868c39a422b0c825e83
--- /dev/null
+++ b/examples/mqtt_over_websocket_server/mqtt_over_websocket_server.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively, you can license this software under a commercial
+ * license, as set out in <https://www.cesanta.com/license>.
+ */
+
+#include "mongoose.h"
+
+static const char *s_mqtt_address = "0.0.0.0:1883";
+static const char *s_http_address = "0.0.0.0:8080";
+
+static void unproxy(struct mg_connection *c) {
+  struct mg_connection *pc = (struct mg_connection *) c->user_data;
+  if (pc != NULL) {
+    pc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    pc->user_data = NULL;
+    c->user_data = NULL;
+  }
+  printf("Closing connection %p\n", c);
+}
+
+static void proxy_handler(struct mg_connection *c, int ev, void *ev_data) {
+  if (ev == MG_EV_POLL) return;
+  printf("%p %s EVENT %d %p\n", c, __func__, ev, ev_data);
+  switch (ev) {
+    case MG_EV_CLOSE: {
+      unproxy(c);
+      break;
+    }
+    case MG_EV_RECV: {
+      struct mg_connection *pc = (struct mg_connection *) c->user_data;
+      if (pc != NULL) {
+        mg_send_websocket_frame(pc, WEBSOCKET_OP_BINARY, c->recv_mbuf.buf,
+                                c->recv_mbuf.len);
+        mbuf_remove(&c->recv_mbuf, c->recv_mbuf.len);
+      }
+      break;
+    }
+  }
+}
+
+static void http_handler(struct mg_connection *c, int ev, void *ev_data) {
+  struct mg_connection *pc = (struct mg_connection *) c->user_data;
+  if (ev == MG_EV_POLL) return;
+  printf("%p %s EVENT %d %p\n", c, __func__, ev, ev_data);
+  /* Do your custom event processing here */
+  switch (ev) {
+    case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
+      pc = mg_connect(c->mgr, s_mqtt_address, proxy_handler);
+      pc->user_data = c;
+      c->user_data = pc;
+      printf("Created proxy connection %p\n", pc);
+      break;
+    }
+    case MG_EV_WEBSOCKET_FRAME: {
+      struct websocket_message *wm = (struct websocket_message *) ev_data;
+      if (pc != NULL) {
+        printf("Forwarding %d bytes\n", (int) wm->size);
+        mg_send(pc, wm->data, wm->size);
+      }
+      break;
+    }
+    case MG_EV_CLOSE: {
+      unproxy(c);
+      break;
+    }
+  }
+}
+
+static void mqtt_handler(struct mg_connection *c, int ev, void *ev_data) {
+  if (ev == MG_EV_POLL) return;
+  printf("%p %s EVENT %d %p\n", c, __func__, ev, ev_data);
+  /* Do your custom event processing here */
+  switch (ev) {
+    case MG_EV_CLOSE:
+      printf("Closing MQTT connection %p\n", c);
+      break;
+  }
+  mg_mqtt_broker(c, ev, ev_data);
+}
+
+static void start_mqtt_server(struct mg_mgr *mgr, const char *addr) {
+  struct mg_connection *c;
+  static struct mg_mqtt_broker brk;  // static is important - must not perish
+  if ((c = mg_bind(mgr, addr, mqtt_handler)) == NULL) {
+    fprintf(stderr, "Cannot start MQTT server on port [%s]\n", addr);
+    exit(EXIT_FAILURE);
+  }
+  mg_mqtt_broker_init(&brk, NULL);
+  c->user_data = &brk;
+  mg_set_protocol_mqtt(c);
+  printf("MQTT server started on %s\n", addr);
+}
+
+static void start_http_server(struct mg_mgr *mgr, const char *addr) {
+  struct mg_connection *c;
+  if ((c = mg_bind(mgr, addr, http_handler)) == NULL) {
+    fprintf(stderr, "Cannot start HTTP server on port [%s]\n", addr);
+    exit(EXIT_FAILURE);
+  }
+  mg_set_protocol_http_websocket(c);
+  printf("HTTP server started on %s\n", addr);
+}
+
+int main(void) {
+  struct mg_mgr mgr;
+  mg_mgr_init(&mgr, NULL);
+  start_http_server(&mgr, s_http_address);
+  start_mqtt_server(&mgr, s_mqtt_address);
+  for (;;) {
+    mg_mgr_poll(&mgr, 1000);
+  }
+}
diff --git a/examples/multithreaded/Makefile b/examples/multithreaded/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..c41017da655797681c1e64bf8487ab7a930c115d
--- /dev/null
+++ b/examples/multithreaded/Makefile
@@ -0,0 +1,3 @@
+PROG = multithreaded
+MODULE_CFLAGS=-DMG_ENABLE_THREADS
+include ../examples.mk
diff --git a/examples/multithreaded/multithreaded.c b/examples/multithreaded/multithreaded.c
new file mode 100644
index 0000000000000000000000000000000000000000..a3053c81478f1f08397ce923911d76c7b827d94e
--- /dev/null
+++ b/examples/multithreaded/multithreaded.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2014-2017 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#include "mongoose.h"
+
+static sig_atomic_t s_received_signal = 0;
+static const char *s_http_port = "8000";
+static const int s_num_worker_threads = 5;
+static unsigned long s_next_id = 0;
+
+static void signal_handler(int sig_num) {
+  signal(sig_num, signal_handler);
+  s_received_signal = sig_num;
+}
+static struct mg_serve_http_opts s_http_server_opts;
+static sock_t sock[2];
+
+// This info is passed to the worker thread
+struct work_request {
+  unsigned long conn_id;  // needed to identify the connection where to send the reply
+  // optionally, more data that could be required by worker 
+};
+
+// This info is passed by the worker thread to mg_broadcast
+struct work_result {
+  unsigned long conn_id;
+  int sleep_time;
+};
+
+static void on_work_complete(struct mg_connection *nc, int ev, void *ev_data) {
+  (void) ev;
+  char s[32];
+  struct mg_connection *c;
+  for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) {
+    if (c->user_data != NULL) {
+      struct work_result *res = (struct work_result *)ev_data;
+      if ((unsigned long)c->user_data == res->conn_id) {
+        sprintf(s, "conn_id:%lu sleep:%d", res->conn_id, res->sleep_time);
+        mg_send_head(c, 200, strlen(s), "Content-Type: text/plain");
+        mg_printf(c, "%s", s);
+      }
+    }
+  }
+}
+
+void *worker_thread_proc(void *param) {
+  struct mg_mgr *mgr = (struct mg_mgr *) param;
+  struct work_request req = {0};
+  
+  while (s_received_signal == 0) {
+    if (read(sock[1], &req, sizeof(req)) < 0)
+      perror("Reading worker sock");
+    int r = rand() % 10;
+    sleep(r);
+    struct work_result res = {req.conn_id, r};
+    mg_broadcast(mgr, on_work_complete, (void *)&res, sizeof(res));
+  }
+  return NULL;
+}
+
+static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
+  (void) nc;
+  (void) ev_data;
+  
+  switch (ev) {
+    case MG_EV_ACCEPT:
+      nc->user_data = (void *)++s_next_id;
+      break;
+    case MG_EV_HTTP_REQUEST: {
+      struct work_request req = {(unsigned long)nc->user_data};
+
+      if (write(sock[0], &req, sizeof(req)) < 0)
+        perror("Writing worker sock");
+      break;
+    }
+    case MG_EV_CLOSE: {
+      if (nc->user_data) nc->user_data = NULL;
+    }
+  }
+}
+
+int main(void) {
+  struct mg_mgr mgr;
+  struct mg_connection *nc;
+  int i;
+
+  if (mg_socketpair(sock, SOCK_STREAM) == 0) {
+    perror("Opening socket pair");
+    exit(1);
+  }
+
+  signal(SIGTERM, signal_handler);
+  signal(SIGINT, signal_handler);
+
+  mg_mgr_init(&mgr, NULL);
+
+  nc = mg_bind(&mgr, s_http_port, ev_handler);
+  if (nc == NULL) {
+    printf("Failed to create listener\n");
+    return 1;
+  }
+
+  mg_set_protocol_http_websocket(nc);
+  s_http_server_opts.document_root = ".";  // Serve current directory
+  s_http_server_opts.enable_directory_listing = "no";
+
+  for (i = 0; i < s_num_worker_threads; i++) {
+    mg_start_thread(worker_thread_proc, &mgr);
+  }
+
+  printf("Started on port %s\n", s_http_port);
+  while (s_received_signal == 0) {
+    mg_mgr_poll(&mgr, 200);
+  }
+
+  mg_mgr_free(&mgr);
+
+  closesocket(sock[0]);
+  closesocket(sock[1]);
+
+  return 0;
+}
diff --git a/examples/nRF51/http/boards/pca10028/armgcc/Makefile b/examples/nRF51/http/boards/pca10028/armgcc/Makefile
index d2cde039711b826ffe44fa5e66e2705bab1fb5cf..07534aa2cd52891870bdb44ad96af35f06e608de 100644
--- a/examples/nRF51/http/boards/pca10028/armgcc/Makefile
+++ b/examples/nRF51/http/boards/pca10028/armgcc/Makefile
@@ -3,7 +3,7 @@ PROJECT_NAME := iot_lwip_tcp_server_pca10028
 export OUTPUT_FILENAME
 #MAKEFILE_NAME := $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
 MAKEFILE_NAME := $(MAKEFILE_LIST)
-MAKEFILE_DIR := $(dir $(MAKEFILE_NAME) ) 
+MAKEFILE_DIR := $(dir $(MAKEFILE_NAME) )
 
 TEMPLATE_PATH = ../../../../nrf51_iot_sdk/components/toolchain/gcc
 ifeq ($(OS),Windows_NT)
@@ -17,7 +17,7 @@ RM := rm -rf
 
 #echo suspend
 ifeq ("$(VERBOSE)","1")
-NO_ECHO := 
+NO_ECHO :=
 else
 NO_ECHO := @
 endif
@@ -118,7 +118,7 @@ BUILD_DIRECTORIES := $(sort $(OBJECT_DIRECTORY) $(OUTPUT_BINARY_DIRECTORY) $(LIS
 # Mongoose features
 MG_FEATURES_TINY = \
                    -DMG_DISABLE_HTTP_DIGEST_AUTH \
-                   -DMG_DISABLE_MD5 \
+                   -DCS_DISABLE_MD5 \
                    -DMG_DISABLE_HTTP_KEEP_ALIVE \
                    -DMG_ENABLE_HTTP_SSI=0 \
                    -DMG_ENABLE_HTTP_STREAMING_MULTIPART
@@ -251,7 +251,7 @@ genbin:
 	$(NO_ECHO)$(OBJCOPY) -O binary $(OUTPUT_BINARY_DIRECTORY)/$(OUTPUT_FILENAME).out $(OUTPUT_BINARY_DIRECTORY)/$(OUTPUT_FILENAME).bin
 
 ## Create binary .hex file from the .out file
-genhex: 
+genhex:
 	@echo Preparing: $(OUTPUT_FILENAME).hex
 	$(NO_ECHO)$(OBJCOPY) -O ihex $(OUTPUT_BINARY_DIRECTORY)/$(OUTPUT_FILENAME).out $(OUTPUT_BINARY_DIRECTORY)/$(OUTPUT_FILENAME).hex
 
diff --git a/examples/nRF52/http/boards/pca10040/armgcc/Makefile b/examples/nRF52/http/boards/pca10040/armgcc/Makefile
index 073af2b84f92841607c4ba15b5bd950ecb41fab7..bd2aa6cc69911415b7d42d627f53d103d45a81b0 100644
--- a/examples/nRF52/http/boards/pca10040/armgcc/Makefile
+++ b/examples/nRF52/http/boards/pca10040/armgcc/Makefile
@@ -3,7 +3,7 @@ PROJECT_NAME := iot_lwip_tcp_server_pca10040
 export OUTPUT_FILENAME
 #MAKEFILE_NAME := $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
 MAKEFILE_NAME := $(MAKEFILE_LIST)
-MAKEFILE_DIR := $(dir $(MAKEFILE_NAME) ) 
+MAKEFILE_DIR := $(dir $(MAKEFILE_NAME) )
 
 TEMPLATE_PATH = ../../../../nrf5_iot_sdk/components/toolchain/gcc
 ifeq ($(OS),Windows_NT)
@@ -17,7 +17,7 @@ RM := rm -rf
 
 #echo suspend
 ifeq ("$(VERBOSE)","1")
-NO_ECHO := 
+NO_ECHO :=
 else
 NO_ECHO := @
 endif
@@ -165,7 +165,7 @@ BUILD_DIRECTORIES := $(sort $(OBJECT_DIRECTORY) $(OUTPUT_BINARY_DIRECTORY) $(LIS
 # Mongoose features
 MG_FEATURES_TINY = \
                    -DMG_DISABLE_HTTP_DIGEST_AUTH \
-                   -DMG_DISABLE_MD5 \
+                   -DCS_DISABLE_MD5 \
                    -DMG_DISABLE_HTTP_KEEP_ALIVE \
                    -DMG_ENABLE_HTTP_SSI=0 \
                    -DMG_ENABLE_HTTP_STREAMING_MULTIPART
@@ -297,7 +297,7 @@ genbin:
 	$(NO_ECHO)$(OBJCOPY) -O binary $(OUTPUT_BINARY_DIRECTORY)/$(OUTPUT_FILENAME).out $(OUTPUT_BINARY_DIRECTORY)/$(OUTPUT_FILENAME).bin
 
 ## Create binary .hex file from the .out file
-genhex: 
+genhex:
 	@echo Preparing: $(OUTPUT_FILENAME).hex
 	$(NO_ECHO)$(OBJCOPY) -O ihex $(OUTPUT_BINARY_DIRECTORY)/$(OUTPUT_FILENAME).out $(OUTPUT_BINARY_DIRECTORY)/$(OUTPUT_FILENAME).hex
 echosize:
diff --git a/examples/websocket_chat/Makefile b/examples/websocket_chat/Makefile
index b6244834cf4da21a84bd7223680b45017214cf9e..2fbb6947977473c3e45b5946ec348fd99081a8c5 100644
--- a/examples/websocket_chat/Makefile
+++ b/examples/websocket_chat/Makefile
@@ -1,3 +1,3 @@
 PROG = websocket_chat
-MODULE_CFLAGS = -DMG_ENABLE_FILESYSTEM=0
+MODULE_CFLAGS = -DMG_ENABLE_FILESYSTEM=1
 include ../examples.mk
diff --git a/examples/websocket_chat/websocket_chat.c b/examples/websocket_chat/websocket_chat.c
index 3d2ad6116c25fa5c37de74805dd6273b17a94397..265ea0798a37635fe061d6fb99b299d89dfcbf89 100644
--- a/examples/websocket_chat/websocket_chat.c
+++ b/examples/websocket_chat/websocket_chat.c
@@ -7,6 +7,7 @@
 
 static sig_atomic_t s_signal_received = 0;
 static const char *s_http_port = "8000";
+static struct mg_serve_http_opts s_http_server_opts;
 
 static void signal_handler(int sig_num) {
   signal(sig_num, signal_handler);  // Reinstantiate signal handler
@@ -46,6 +47,10 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
       broadcast(nc, d);
       break;
     }
+    case MG_EV_HTTP_REQUEST: {
+      mg_serve_http(nc, (struct http_message *) ev_data, s_http_server_opts);
+      break;
+    }
     case MG_EV_CLOSE: {
       /* Disconnect. Tell everybody. */
       if (is_websocket(nc)) {
@@ -69,6 +74,8 @@ int main(void) {
 
   nc = mg_bind(&mgr, s_http_port, ev_handler);
   mg_set_protocol_http_websocket(nc);
+  s_http_server_opts.document_root = ".";  // Serve current directory
+  s_http_server_opts.enable_directory_listing = "yes";
 
   printf("Started on port %s\n", s_http_port);
   while (s_signal_received == 0) {
diff --git a/mongoose.c b/mongoose.c
index 3a7ed941a585eeb486a4450af855104f4be06d4d..b3461bf72ac9a46f2bb9a91b7e4017635a2ff514 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -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 "mongoose/src/http.h" */
+/* Amalgamated: #include "mongoose/src/net.h" */
 
 #define MG_CTL_MSG_MESSAGE_SIZE 8192
 
@@ -62,7 +48,8 @@ MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
 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);
@@ -110,11 +97,6 @@ MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,
                                      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 +137,11 @@ MG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path,
                                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 +149,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,6 +156,42 @@ MG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len,
 
 #endif /* CS_MONGOOSE_SRC_INTERNAL_H_ */
 #ifdef MG_MODULE_LINES
+#line 1 "common/mg_mem.h"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_MG_MEM_H_
+#define CS_COMMON_MG_MEM_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CS_COMMON_MG_MEM_H_ */
+#ifdef MG_MODULE_LINES
 #line 1 "common/cs_dbg.h"
 #endif
 /*
@@ -217,32 +232,34 @@ enum cs_log_level {
   _LL_MAX = 5,
 };
 
+/* Set log level. */
 void cs_log_set_level(enum cs_log_level level);
 
+/* Set log filter. NULL (a default) logs everything. */
+void cs_log_set_filter(char *source_file_name);
+
+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
 
 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;               \
-    }                                \
+void cs_log_printf(const char *fmt, ...)
+#ifdef __GNUC__
+    __attribute__((format(printf, 1, 2)))
+#endif
+    ;
+
+#define LOG(l, x)                                                    \
+  do {                                                               \
+    if (cs_log_print_prefix(l, __func__, __FILE__)) 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)
+#define DBG(x) LOG(LL_VERBOSE_DEBUG, x)
 
 #else /* NDEBUG */
 
@@ -274,16 +291,21 @@ void cs_log_printf(const char *fmt, ...);
 
 #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_level WEAK =
+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;
+
 #if CS_ENABLE_STDIO
 
 FILE *cs_log_file WEAK = NULL;
@@ -292,12 +314,36 @@ FILE *cs_log_file WEAK = NULL;
 double cs_log_ts WEAK;
 #endif
 
-void cs_log_print_prefix(const char *func) WEAK;
-void cs_log_print_prefix(const char *func) {
+enum cs_log_level cs_log_cur_msg_level WEAK = LL_NONE;
+
+void cs_log_set_filter(char *str) WEAK;
+void cs_log_set_filter(char *str) {
+  free(s_filter_pattern);
+  if (str != NULL) {
+    s_filter_pattern = strdup(str);
+    s_filter_pattern_len = strlen(str);
+  } 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
   {
@@ -306,6 +352,7 @@ void cs_log_print_prefix(const char *func) {
     cs_log_ts = now;
   }
 #endif
+  return 1;
 }
 
 void cs_log_printf(const char *fmt, ...) WEAK;
@@ -316,6 +363,7 @@ void cs_log_printf(const char *fmt, ...) {
   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;
@@ -327,7 +375,7 @@ void cs_log_set_file(FILE *file) {
 
 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;
+  cs_log_threshold = level;
 #if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO
   cs_log_ts = cs_time();
 #endif
@@ -592,6 +640,7 @@ struct dirent *readdir(DIR *dir);
 
 #ifndef EXCLUDE_COMMON
 
+/* Amalgamated: #include "common/mg_mem.h" */
 /* Amalgamated: #include "common/cs_dirent.h" */
 
 /*
@@ -599,14 +648,6 @@ 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;
@@ -739,6 +780,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
@@ -750,6 +826,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
@@ -765,9 +845,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.
@@ -786,11 +870,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" */
 
@@ -821,7 +905,7 @@ static void byteReverse(unsigned char *buf, unsigned longs) {
  * 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;
@@ -831,7 +915,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];
@@ -913,7 +997,7 @@ static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
   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];
@@ -932,7 +1016,7 @@ void MD5_Update(MD5_CTX *ctx, const unsigned char *buf, size_t len) {
     }
     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;
   }
@@ -940,7 +1024,7 @@ void MD5_Update(MD5_CTX *ctx, const unsigned char *buf, size_t len) {
   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;
   }
@@ -948,7 +1032,7 @@ void MD5_Update(MD5_CTX *ctx, const unsigned char *buf, size_t len) {
   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;
@@ -961,7 +1045,7 @@ void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) {
   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);
@@ -972,235 +1056,23 @@ void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) {
   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 /* CS_DISABLE_MD5 */
 #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_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" */
 
@@ -1368,86 +1240,313 @@ void cs_sha1_init(cs_sha1_ctx *context) {
   context->count[0] = context->count[1] = 0;
 }
 
-void cs_sha1_update(cs_sha1_ctx *context, const unsigned char *data,
-                    uint32_t len) {
-  uint32_t i, j;
-
-  j = context->count[0];
-  if ((context->count[0] += len << 3) < j) context->count[1]++;
-  context->count[1] += (len >> 29);
-  j = (j >> 3) & 63;
-  if ((j + len) > 63) {
-    memcpy(&context->buffer[j], data, (i = 64 - j));
-    cs_sha1_transform(context->state, context->buffer);
-    for (; i + 63 < len; i += 64) {
-      cs_sha1_transform(context->state, &data[i]);
-    }
-    j = 0;
-  } else
-    i = 0;
-  memcpy(&context->buffer[j], &data[i], len - i);
+void cs_sha1_update(cs_sha1_ctx *context, const unsigned char *data,
+                    uint32_t len) {
+  uint32_t i, j;
+
+  j = context->count[0];
+  if ((context->count[0] += len << 3) < j) context->count[1]++;
+  context->count[1] += (len >> 29);
+  j = (j >> 3) & 63;
+  if ((j + len) > 63) {
+    memcpy(&context->buffer[j], data, (i = 64 - j));
+    cs_sha1_transform(context->state, context->buffer);
+    for (; i + 63 < len; i += 64) {
+      cs_sha1_transform(context->state, &data[i]);
+    }
+    j = 0;
+  } else
+    i = 0;
+  memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+void cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *context) {
+  unsigned i;
+  unsigned char finalcount[8], c;
+
+  for (i = 0; i < 8; i++) {
+    finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
+                                      ((3 - (i & 3)) * 8)) &
+                                     255);
+  }
+  c = 0200;
+  cs_sha1_update(context, &c, 1);
+  while ((context->count[0] & 504) != 448) {
+    c = 0000;
+    cs_sha1_update(context, &c, 1);
+  }
+  cs_sha1_update(context, finalcount, 8);
+  for (i = 0; i < 20; i++) {
+    digest[i] =
+        (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
+  }
+  memset(context, '\0', sizeof(*context));
+  memset(&finalcount, '\0', sizeof(finalcount));
+}
+
+void cs_hmac_sha1(const unsigned char *key, size_t keylen,
+                  const unsigned char *data, size_t datalen,
+                  unsigned char out[20]) {
+  cs_sha1_ctx ctx;
+  unsigned char buf1[64], buf2[64], tmp_key[20], i;
+
+  if (keylen > sizeof(buf1)) {
+    cs_sha1_init(&ctx);
+    cs_sha1_update(&ctx, key, keylen);
+    cs_sha1_final(tmp_key, &ctx);
+    key = tmp_key;
+    keylen = sizeof(tmp_key);
+  }
+
+  memset(buf1, 0, sizeof(buf1));
+  memset(buf2, 0, sizeof(buf2));
+  memcpy(buf1, key, keylen);
+  memcpy(buf2, key, keylen);
+
+  for (i = 0; i < sizeof(buf1); i++) {
+    buf1[i] ^= 0x36;
+    buf2[i] ^= 0x5c;
+  }
+
+  cs_sha1_init(&ctx);
+  cs_sha1_update(&ctx, buf1, sizeof(buf1));
+  cs_sha1_update(&ctx, data, datalen);
+  cs_sha1_final(out, &ctx);
+
+  cs_sha1_init(&ctx);
+  cs_sha1_update(&ctx, buf2, sizeof(buf2));
+  cs_sha1_update(&ctx, out, 20);
+  cs_sha1_final(out, &ctx);
+}
+
+#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;
 }
 
-void cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *context) {
-  unsigned i;
-  unsigned char finalcount[8], c;
-
-  for (i = 0; i < 8; i++) {
-    finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
-                                      ((3 - (i & 3)) * 8)) &
-                                     255);
+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;
   }
-  c = 0200;
-  cs_sha1_update(context, &c, 1);
-  while ((context->count[0] & 504) != 448) {
-    c = 0000;
-    cs_sha1_update(context, &c, 1);
+  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;
   }
-  cs_sha1_update(context, finalcount, 8);
-  for (i = 0; i < 20; i++) {
-    digest[i] =
-        (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
+  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;
+    }
   }
-  memset(context, '\0', sizeof(*context));
-  memset(&finalcount, '\0', sizeof(finalcount));
+  return r;
 }
 
-void cs_hmac_sha1(const unsigned char *key, size_t keylen,
-                  const unsigned char *data, size_t datalen,
-                  unsigned char out[20]) {
-  cs_sha1_ctx ctx;
-  unsigned char buf1[64], buf2[64], tmp_key[20], i;
+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 */);
+}
 
-  if (keylen > sizeof(buf1)) {
-    cs_sha1_init(&ctx);
-    cs_sha1_update(&ctx, key, keylen);
-    cs_sha1_final(tmp_key, &ctx);
-    key = tmp_key;
-    keylen = sizeof(tmp_key);
-  }
+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 */);
+}
 
-  memset(buf1, 0, sizeof(buf1));
-  memset(buf2, 0, sizeof(buf2));
-  memcpy(buf1, key, keylen);
-  memcpy(buf2, key, keylen);
+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;
+}
 
-  for (i = 0; i < sizeof(buf1); i++) {
-    buf1[i] ^= 0x36;
-    buf2[i] ^= 0x5c;
+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;
+}
 
-  cs_sha1_init(&ctx);
-  cs_sha1_update(&ctx, buf1, sizeof(buf1));
-  cs_sha1_update(&ctx, data, datalen);
-  cs_sha1_final(out, &ctx);
+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;
 
-  cs_sha1_init(&ctx);
-  cs_sha1_update(&ctx, buf2, sizeof(buf2));
-  cs_sha1_update(&ctx, out, 20);
-  cs_sha1_final(out, &ctx);
+  if (s1.len > n) {
+    s1.len = n;
+  }
+  if (s2.len > n) {
+    s2.len = n;
+  }
+  return mg_strcmp(s1, s2);
 }
 
-#endif /* EXCLUDE_COMMON */
+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
@@ -1458,6 +1557,7 @@ void cs_hmac_sha1(const unsigned char *key, size_t keylen,
 
 #ifndef EXCLUDE_COMMON
 
+/* Amalgamated: #include "common/mg_mem.h" */
 /* Amalgamated: #include "common/platform.h" */
 /* Amalgamated: #include "common/str_util.h" */
 
@@ -1465,13 +1565,7 @@ void cs_hmac_sha1(const unsigned char *key, size_t keylen,
 #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) {
@@ -1737,7 +1831,7 @@ const char *c_strnstr(const char *s, const char *find, size_t slen) {
 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);
   }
@@ -1863,6 +1957,109 @@ int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap) {
   return len;
 }
 
+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_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;
+
+    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);
+    }
+
+    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, const struct mg_str) WEAK;
+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 ||
+      (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 (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) {
+        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 (str_util_lowercase(&pattern.p[i]) !=
+               str_util_lowercase(&str.p[j])) {
+      return -1;
+    }
+  }
+  return j;
+}
+
+int mg_match_prefix(const char *, int, const char *) WEAK;
+int 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 /* EXCLUDE_COMMON */
 #ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/tun.h"
@@ -1933,7 +2130,8 @@ extern "C" {
 
 struct mg_connection *mg_tun_bind_opt(struct mg_mgr *mgr,
                                       const char *dispatcher,
-                                      mg_event_handler_t handler,
+                                      MG_CB(mg_event_handler_t handler,
+                                            void *user_data),
                                       struct mg_bind_opts opts);
 
 int mg_tun_parse_frame(void *data, size_t len, struct mg_tun_frame *frame);
@@ -2018,7 +2216,8 @@ MG_INTERNAL void mg_remove_conn(struct mg_connection *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) {
   if (ev_handler == NULL) {
     /*
      * If protocol handler is specified, call it. Otherwise, call user-specified
@@ -2033,22 +2232,15 @@ MG_INTERNAL void mg_call(struct mg_connection *nc,
   }
 
 #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) {
@@ -2064,12 +2256,15 @@ MG_INTERNAL void mg_call(struct mg_connection *nc,
          ev_handler == nc->handler ? "user" : "proto", nc->flags,
          (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
   }
+#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);
+    mg_call(c, NULL, c->user_data, 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.
@@ -2082,11 +2277,11 @@ void mg_if_timer(struct mg_connection *c, double now) {
 
 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);
@@ -2103,9 +2298,14 @@ static void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
 
 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 */);
 }
 
@@ -2160,36 +2360,13 @@ void mg_mgr_init_opt(struct mg_mgr *m, void *user_data,
       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;
 
@@ -2217,6 +2394,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) {
@@ -2342,7 +2521,7 @@ MG_INTERNAL struct mg_connection *mg_create_connection(
  * 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
  *
@@ -2449,7 +2628,7 @@ void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,
                          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) {
@@ -2459,23 +2638,35 @@ void mg_send(struct mg_connection *nc, const void *buf, int len) {
   } 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));
     /*
@@ -2499,7 +2690,7 @@ MG_INTERNAL void mg_recv_common(struct mg_connection *nc, void *buf, int len,
     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) {
@@ -2547,7 +2738,7 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
          */
         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 */
@@ -2591,7 +2782,7 @@ void mg_if_connect_cb(struct mg_connection *nc, int err) {
   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
@@ -2629,27 +2820,29 @@ static void resolve_cb(struct mg_dns_message *msg, void *data,
 
   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;
@@ -2672,7 +2865,11 @@ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
 
   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 : "-"),
@@ -2723,6 +2920,7 @@ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
     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");
@@ -2744,14 +2942,16 @@ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
 }
 
 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;
@@ -2759,12 +2959,16 @@ struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
   struct mg_add_sock_opts add_sock_opts;
   char host[MG_MAX_HOST_LEN];
 
+#if MG_ENABLE_CALLBACK_USERDATA
+  opts.user_data = user_data;
+#endif
+
   MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);
 
 #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);
+    return mg_tun_bind_opt(mgr, address, MG_CB(callback, user_data), opts);
   }
 #endif
 
@@ -2895,7 +3099,7 @@ int mg_check_ip_acl(const char *acl, uint32_t remote_ip) {
     }
   }
 
-  DBG(("%08x %c", remote_ip, allowed));
+  DBG(("%08x %c", (unsigned int) remote_ip, allowed));
   return allowed == '+';
 }
 
@@ -2913,7 +3117,7 @@ double mg_set_timer(struct mg_connection *c, double timestamp) {
    * 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;
@@ -2933,8 +3137,13 @@ void mg_if_get_conn_addr(struct mg_connection *nc, int remote,
 }
 
 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);
@@ -2944,10 +3153,11 @@ struct mg_connection *mg_add_sock_opt(struct mg_mgr *s, sock_t sock,
 }
 
 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) {
@@ -2974,7 +3184,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
 }
@@ -3002,7 +3212,7 @@ struct mg_tun_client;
 extern "C" {
 #endif /* __cplusplus */
 
-extern struct mg_iface_vtable mg_tun_iface_vtable;
+extern const 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);
@@ -3022,18 +3232,18 @@ struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client,
 /* Amalgamated: #include "mongoose/src/net_if_socket.h" */
 /* Amalgamated: #include "mongoose/src/net_if_tun.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};
+const 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};
+const struct mg_iface_vtable *mg_ifaces[] = {&mg_default_iface_vtable};
 #endif
 
 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;
@@ -3043,7 +3253,7 @@ struct mg_iface *mg_if_create_iface(struct mg_iface_vtable *vtable,
 }
 
 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) {
@@ -3095,17 +3305,16 @@ void mg_set_non_blocking_mode(sock_t sock) {
 #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,
@@ -3120,7 +3329,7 @@ void mg_socket_if_connect_tcp(struct mg_connection *nc,
   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));
 }
@@ -3196,7 +3405,7 @@ static int mg_accept_conn(struct mg_connection *lc) {
   /* 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);
@@ -3281,10 +3490,7 @@ static void mg_write_to_socket(struct mg_connection *nc) {
         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;
   }
 
@@ -3310,18 +3516,10 @@ static void mg_write_to_socket(struct mg_connection *nc) {
 #endif
   {
     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;
-    }
+    DBG(("%p %d bytes -> %d", nc, n, nc->sock));
   }
 
-  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) {
@@ -3376,7 +3574,7 @@ static void mg_handle_tcp_read(struct mg_connection *conn) {
     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;
     }
   }
@@ -3524,7 +3722,8 @@ static void mg_mgr_handle_ctl_sock(struct mg_mgr *mgr) {
   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));
     }
   }
 }
@@ -3542,9 +3741,7 @@ void mg_socket_if_init(struct mg_iface *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
 }
 
@@ -3702,6 +3899,26 @@ time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
 }
 
 #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;
@@ -3724,21 +3941,20 @@ int mg_socketpair(sock_t sp[2], int sock_type) {
   } 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;
@@ -3764,6 +3980,10 @@ void mg_sock_to_str(sock_t sock, char *buf, size_t len, int flags) {
 
 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);
 }
 
@@ -3789,9 +4009,9 @@ void mg_socket_if_get_conn_addr(struct mg_connection *nc, int remote,
   }
 /* 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 */
@@ -3844,7 +4064,7 @@ void mg_tun_if_tcp_send(struct mg_connection *nc, const void *buf, size_t 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));
+  LOG(LL_DEBUG, ("sending to stream 0x%x:\n%s", (unsigned int) stream_id, hex));
 #endif
 
   mg_tun_send_frame(client->disp, stream_id, MG_TUN_DATA_FRAME, 0, msg);
@@ -3875,7 +4095,7 @@ void mg_tun_if_destroy_conn(struct mg_connection *nc) {
     uint32_t stream_id = (uint32_t)(uintptr_t) nc->mgr_data;
     struct mg_str msg = {NULL, 0};
 
-    LOG(LL_DEBUG, ("closing %zu:", stream_id));
+    LOG(LL_DEBUG, ("closing 0x%x:", (unsigned int) stream_id));
     mg_tun_send_frame(client->disp, stream_id, MG_TUN_DATA_FRAME,
                       MG_TUN_F_END_STREAM, msg);
   }
@@ -3931,13 +4151,14 @@ struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client,
 
   if (stream_id > client->last_stream_id) {
     /* create a new connection */
-    LOG(LL_DEBUG, ("new stream 0x%lx, accepting", stream_id));
+    LOG(LL_DEBUG, ("new stream 0x%x, accepting", (unsigned int) 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));
+    LOG(LL_DEBUG,
+        ("Ignoring stream 0x%x (last_stream_id 0x%x)", (unsigned int) stream_id,
+         (unsigned int) client->last_stream_id));
   }
 
   return nc;
@@ -3965,7 +4186,7 @@ struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client,
   }
 /* clang-format on */
 
-struct mg_iface_vtable mg_tun_iface_vtable = MG_TUN_IFACE_VTABLE;
+const struct mg_iface_vtable mg_tun_iface_vtable = MG_TUN_IFACE_VTABLE;
 
 #endif /* MG_ENABLE_TUN */
 #ifdef MG_MODULE_LINES
@@ -4040,6 +4261,21 @@ enum mg_ssl_if_result mg_ssl_if_conn_init(
     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) {
@@ -4124,6 +4360,12 @@ int mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len) {
   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;
@@ -4266,7 +4508,7 @@ static unsigned int mg_ssl_if_ossl_psk_cb(SSL *ssl, const char *hint,
                                           unsigned char *psk,
                                           unsigned int max_psk_len) {
   struct mg_ssl_if_ctx *ctx =
-      (struct mg_ssl_if_ctx *) ssl->ctx->msg_callback_arg;
+      (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) {
@@ -4315,8 +4557,7 @@ static enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,
   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);
-  /* Hack: there is no field for us to keep this, so we use msg_callback_arg */
-  ctx->ssl_ctx->msg_callback_arg = ctx;
+  SSL_CTX_set_app_data(ctx->ssl_ctx, ctx);
   return MG_SSL_OK;
 }
 #else
@@ -4557,6 +4798,12 @@ static void mg_ssl_if_mbed_free_certs_and_keys(struct mg_ssl_if_ctx *ctx) {
   }
   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;
@@ -4611,6 +4858,12 @@ int mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len) {
   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;
@@ -4632,13 +4885,21 @@ void mg_ssl_if_conn_free(struct mg_connection *nc) {
 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 = (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;
@@ -4672,7 +4933,9 @@ static enum mg_ssl_if_result mg_use_cert(struct mg_ssl_if_ctx *ctx,
 
 static const int mg_s_cipher_list[] = {
     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,
@@ -4793,23 +5056,26 @@ int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len) {
 /* Amalgamated: #include "mongoose/src/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) {
@@ -4833,8 +5099,13 @@ int mg_parse_uri(struct mg_str uri, struct mg_str *scheme,
          * 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;
@@ -4854,7 +5125,7 @@ int mg_parse_uri(struct mg_str uri, struct mg_str *scheme,
           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;
@@ -4862,27 +5133,35 @@ int mg_parse_uri(struct mg_str uri, struct mg_str *scheme,
         }
         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 == '[') {
             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;
@@ -4891,7 +5170,6 @@ int mg_parse_uri(struct mg_str uri, struct mg_str *scheme,
             break;
           }
         }
-        rhost.len = p - rhost.p;
         break;
       case P_PORT:
         p++;
@@ -4906,9 +5184,11 @@ int mg_parse_uri(struct mg_str uri, struct mg_str *scheme,
         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;
     }
   }
@@ -4939,7 +5219,7 @@ int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out) {
   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) {
@@ -4958,6 +5238,76 @@ int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out) {
   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"
 #endif
@@ -4968,10 +5318,9 @@ int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out) {
 
 #if MG_ENABLE_HTTP
 
+/* Amalgamated: #include "common/md5.h" */
 /* Amalgamated: #include "mongoose/src/internal.h" */
 /* Amalgamated: #include "mongoose/src/util.h" */
-/* Amalgamated: #include "common/sha1.h" */
-/* Amalgamated: #include "common/md5.h" */
 
 static const char *mg_version_header = "Mongoose/" MG_VERSION;
 
@@ -4997,9 +5346,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 {
@@ -5027,6 +5381,10 @@ struct mg_reverse_proxy_data {
   struct mg_connection *linked_conn;
 };
 
+struct mg_ws_proto_data {
+  size_t reass_len; /* Defragmented size of the frame so far. */
+};
+
 struct mg_http_proto_data {
 #if MG_ENABLE_FILESYSTEM
   struct mg_http_proto_data_file file;
@@ -5036,18 +5394,23 @@ struct mg_http_proto_data {
 #endif
 #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) {
@@ -5062,9 +5425,9 @@ static struct mg_http_proto_data *mg_http_get_proto_data(
 #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
@@ -5085,8 +5448,10 @@ static void mg_http_free_proto_data_endpoints(struct mg_http_endpoint **ep) {
 
   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;
   }
 
@@ -5122,7 +5487,7 @@ static void mg_http_conn_destructor(void *proto_data) {
 #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
@@ -5363,28 +5728,33 @@ static void mg_http_transfer_file_data(struct mg_connection *nc) {
 
   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;
@@ -5481,7 +5851,7 @@ MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,
 
     /* 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) {
@@ -5502,10 +5872,10 @@ MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,
   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;
 
@@ -5517,11 +5887,10 @@ static mg_event_handler_t mg_http_get_endpoint_handler(
 
   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)) != -1) {
       if (matched > matched_max) {
         /* Looking for the longest suitable handler */
-        ret = ep->handler;
+        ret = ep;
         matched_max = matched;
       }
     }
@@ -5532,20 +5901,6 @@ static mg_event_handler_t mg_http_get_endpoint_handler(
   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);
 
@@ -5554,26 +5909,42 @@ static void mg_http_multipart_begin(struct mg_connection *nc,
 
 #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;
@@ -5602,14 +5973,15 @@ void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
       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.
@@ -5617,8 +5989,10 @@ void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
       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
@@ -5627,10 +6001,11 @@ void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
   }
 #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) {
@@ -5672,11 +6047,12 @@ void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
       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);
@@ -5688,79 +6064,45 @@ void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
        * 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) {
+    else if (hm->message.len > pd->rcvd) {
+      /* Not yet received all HTTP body, deliver MG_EV_HTTP_CHUNK */
+      deliver_chunk(nc, hm, req_len);
+    } else {
+      /* We did receive all HTTP body. */
       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
+      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) {
@@ -5811,17 +6153,19 @@ static void mg_http_multipart_begin(struct mg_connection *nc,
      */
     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);
   }
@@ -5842,7 +6186,7 @@ static void mg_http_multipart_call_handler(struct mg_connection *c, int ev,
   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;
 }
 
@@ -5863,9 +6207,9 @@ static int mg_http_multipart_finalize(struct mg_connection *c) {
   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);
@@ -5943,9 +6287,9 @@ static int mg_http_multipart_process_boundary(struct mg_connection *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 = strdup(file_name);
-      free((void *) pd->mp_stream.var_name);
+      MG_FREE((void *) pd->mp_stream.var_name);
       pd->mp_stream.var_name = strdup(var_name);
 
       mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
@@ -6416,6 +6760,13 @@ int mg_get_http_var(const struct mg_str *buf, const char *name, char *dst,
   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
+   */
   if (dst == NULL || dst_len == 0) {
     len = -2;
   } else if (buf->p == NULL || name == NULL || buf->len == 0) {
@@ -6424,7 +6775,7 @@ int mg_get_http_var(const struct mg_str *buf, const char *name, char *dst,
   } else {
     name_len = strlen(name);
     e = buf->p + buf->len;
-    len = -1;
+    len = 0;
     dst[0] = '\0';
 
     for (p = buf->p; p + name_len < e; p++) {
@@ -6436,8 +6787,9 @@ int mg_get_http_var(const struct mg_str *buf, const char *name, char *dst,
           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;
       }
@@ -6590,6 +6942,42 @@ static int mg_is_file_hidden(const char *path,
 }
 
 #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,
@@ -6598,7 +6986,6 @@ static void mg_mkmd5resp(const char *method, size_t method_len, const char *uri,
   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,
@@ -6642,14 +7029,13 @@ static int mg_check_nonce(const char *nonce) {
 int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
                               FILE *fp) {
   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[50], cnonce[64], response[40], uri[200], qop[20], nc[20],
+      nonce[30];
 
   /* 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, "username", username, sizeof(username)) == 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 ||
@@ -6660,6 +7046,26 @@ int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
     return 0;
   }
 
+  /* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */
+
+  return 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);
+}
+
+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:
@@ -6667,16 +7073,16 @@ int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
    */
   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;
     }
   }
 
@@ -6684,10 +7090,10 @@ int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
   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) {
+static int mg_http_is_authorized(struct http_message *hm, struct mg_str path,
+                                 int is_directory, const char *domain,
+                                 const char *passwords_file,
+                                 int is_global_pass_file) {
   char buf[MG_MAX_PATH];
   const char *p;
   FILE *fp;
@@ -6697,12 +7103,13 @@ static int mg_is_authorized(struct http_message *hm, const char *path,
     if (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);
+      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");
     }
@@ -6713,15 +7120,16 @@ static int mg_is_authorized(struct http_message *hm, const char *path,
     }
   }
 
-  LOG(LL_DEBUG, ("%s '%s' %d %d", path, passwords_file ? passwords_file : "",
-                 is_global_pass_file, authorized));
+  LOG(LL_DEBUG,
+      ("%.*s %s %d %d", (int) path.len, path.p,
+       passwords_file ? passwords_file : "", is_global_pass_file, 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) {
+static int mg_http_is_authorized(struct http_message *hm,
+                                 const struct mg_str path, int is_directory,
+                                 const char *domain, const char *passwords_file,
+                                 int is_global_pass_file) {
   (void) hm;
   (void) path;
   (void) is_directory;
@@ -6733,28 +7141,6 @@ static int mg_is_authorized(struct http_message *hm, const char *path,
 #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) {
@@ -6770,10 +7156,11 @@ static void mg_escape(const char *src, char *dst, size_t dst_len) {
 
 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]");
@@ -6794,19 +7181,20 @@ static void mg_print_dir_entry(struct mg_connection *nc, const char *file_name,
   }
   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;
@@ -6950,7 +7338,7 @@ static int mg_http_send_port_based_redirect(
 }
 
 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);
 
@@ -6976,6 +7364,10 @@ static void mg_reverse_proxy_handler(struct mg_connection *nc, int ev,
       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,
@@ -6983,20 +7375,19 @@ void mg_http_reverse_proxy(struct mg_connection *nc,
                            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);
+  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));
 
@@ -7011,10 +7402,10 @@ void mg_http_reverse_proxy(struct mg_connection *nc,
   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];
@@ -7064,7 +7455,7 @@ static int mg_http_handle_forwarding(struct mg_connection *nc,
 
   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,
@@ -7162,7 +7553,7 @@ MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
         }
       }
       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);
@@ -7348,10 +7739,12 @@ MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path,
 
   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), is_directory,
+                                    opts->auth_domain, opts->global_auth_file,
+                                    1) ||
+             !mg_http_is_authorized(hm, mg_mk_str(path), is_directory,
+                                    opts->auth_domain,
+                                    opts->per_directory_auth_file, 0)) {
     mg_http_send_digest_auth_request(nc, opts->auth_domain);
   } else if (is_cgi) {
 #if MG_ENABLE_HTTP_CGI
@@ -7367,11 +7760,11 @@ MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path,
   } else if (!mg_vcmp(&hm->method, "PROPFIND")) {
     mg_handle_propfind(nc, path, &st, hm, opts);
 #if !MG_DISABLE_DAV_AUTH
-  } 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)))) {
+  } else if (is_dav && (opts->dav_auth_file == NULL ||
+                        (strcmp(opts->dav_auth_file, "-") != 0 &&
+                         !mg_http_is_authorized(hm, mg_mk_str(path),
+                                                is_directory, opts->auth_domain,
+                                                opts->dav_auth_file, 1)))) {
     mg_http_send_digest_auth_request(nc, opts->auth_domain);
 #endif
   } else if (!mg_vcmp(&hm->method, "MKCOL")) {
@@ -7474,13 +7867,14 @@ void mg_serve_http(struct mg_connection *nc, struct http_message *hm,
 
 #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));
+          (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) {
@@ -7494,10 +7888,10 @@ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
         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");
@@ -7521,7 +7915,7 @@ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
       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
@@ -7579,115 +7973,60 @@ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
          */
       }
       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
     /*
@@ -7700,68 +8039,61 @@ struct mg_connection *mg_connect_http_base(
     }
 #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("/");
+
+  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,
@@ -7802,23 +8134,73 @@ size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
   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, 0 /* is_directory */,
+                                 ep->auth_domain, ep->auth_file,
+                                 1 /* 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"
@@ -7828,6 +8210,10 @@ void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
  * All rights reserved
  */
 
+#ifndef _WIN32
+#include <signal.h>
+#endif
+
 #if MG_ENABLE_HTTP && MG_ENABLE_HTTP_CGI
 
 #ifndef MG_MAX_CGI_ENVIR_VARS
@@ -7920,7 +8306,7 @@ static void mg_spawn_stdio_thread(sock_t sock, HANDLE hPipe,
 }
 
 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);
@@ -7932,9 +8318,9 @@ static int mg_start_process(const char *interp, const char *cmd,
   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;
 
@@ -8195,8 +8581,11 @@ static void mg_prepare_cgi_environment(struct mg_connection *nc,
 }
 
 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) {
@@ -8257,7 +8646,7 @@ MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog,
                                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];
 
@@ -8275,23 +8664,30 @@ MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog,
     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);
+        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;
+#endif
     nc->flags |= MG_F_USER_1;
     /* Push POST data to the CGI */
     if (n > 0 && n < nc->recv_mbuf.len) {
@@ -8333,7 +8729,7 @@ static void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm,
 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);
   }
 }
@@ -8341,7 +8737,7 @@ static void mg_send_file_data(struct mg_connection *nc, FILE *fp) {
 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;
 
   /*
@@ -8444,9 +8840,9 @@ static void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm,
         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);
@@ -8557,10 +8953,10 @@ static int mg_mkdir(const char *path, uint32_t mode) {
 
 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>"
@@ -8574,8 +8970,9 @@ static void mg_print_props(struct mg_connection *nc, const char *name,
             "<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,
@@ -8595,7 +8992,7 @@ MG_INTERNAL void mg_handle_propfind(struct mg_connection *nc, const char *path,
       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);
@@ -8662,7 +9059,7 @@ MG_INTERNAL void mg_handle_mkcol(struct mg_connection *nc, const char *path,
 
 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;
@@ -8697,7 +9094,7 @@ MG_INTERNAL void mg_handle_move(struct mg_connection *c,
     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) {
@@ -8734,7 +9131,7 @@ static int mg_create_itermediate_directories(const char *path) {
   /* 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';
@@ -8793,12 +9190,12 @@ MG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path,
 
 #if MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBSOCKET
 
+/* Amalgamated: #include "common/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)
-
 static int mg_is_ws_fragment(unsigned char flags) {
   return (flags & 0x80) == 0 || (flags & 0x0f) == 0;
 }
@@ -8810,32 +9207,38 @@ static int mg_is_ws_first_fragment(unsigned char flags) {
 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);
+};
+
 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);
+  struct mg_ws_proto_data *wsd = mg_ws_get_proto_data(nc);
+  int ok;
+  int 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 (reass && !mg_is_ws_first_fragment(p[0]) && buf_len >= 1 &&
+      buf_len >= 1 + wsd->reass_len) {
+    buf += 1 + wsd->reass_len;
+    buf_len -= 1 + wsd->reass_len;
   }
 
   if (buf_len >= 2) {
-    len = buf[1] & 127;
-    mask_len = buf[1] & 128 ? 4 : 0;
+    len = buf[1] & 0x7f;
+    mask_len = buf[1] & 0x80 ? 4 : 0;
     if (len < 126 && buf_len >= mask_len) {
       data_len = len;
       header_len = 2 + mask_len;
@@ -8850,7 +9253,13 @@ static int mg_deliver_websocket_data(struct mg_connection *nc) {
   }
 
   frame_len = header_len + data_len;
-  ok = frame_len > 0 && frame_len <= buf_len;
+  ok = (frame_len > 0 && frame_len <= buf_len);
+
+  /* Check for overflow */
+  if (frame_len < header_len || frame_len < data_len) {
+    ok = 0;
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  }
 
   if (ok) {
     struct websocket_message wsm;
@@ -8869,32 +9278,33 @@ static int mg_deliver_websocket_data(struct mg_connection *nc) {
     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 */
+        buf = p + 1;
+        wsd->reass_len = 0;
       }
 
       /* Append this frame to the reassembled buffer */
       memmove(buf, wsm.data, e - wsm.data);
-      (*sizep) += wsm.size;
+      wsd->reass_len += 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;
+        wsm.data = p + 1;
+        wsm.size = wsd->reass_len;
         mg_handle_incoming_websocket_frame(nc, &wsm);
-        mbuf_remove(&nc->recv_mbuf, 1 + sizeof(*sizep) + *sizep);
+        mbuf_remove(&nc->recv_mbuf, 1 + wsd->reass_len);
+        wsd->reass_len = 0;
       }
     } else {
       /* TODO(lsm): properly handle OOB control frames during defragmentation */
       mg_handle_incoming_websocket_frame(nc, &wsm);
       mbuf_remove(&nc->recv_mbuf, (size_t) frame_len); /* Cleanup frame */
+      wsd->reass_len = 0;
     }
 
-    /* If client closes, close too */
-    if ((buf[0] & 0x0f) == WEBSOCKET_OP_CLOSE) {
+    /* If the frame is not reassembled - client closes and close too */
+    if (!reass && (buf[0] & 0x0f) == WEBSOCKET_OP_CLOSE) {
       nc->flags |= MG_F_SEND_AND_CLOSE;
     }
   }
@@ -9034,8 +9444,8 @@ void mg_printf_websocket_frame(struct mg_connection *nc, int op,
 }
 
 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:
@@ -9055,11 +9465,14 @@ MG_INTERNAL void mg_ws_handler(struct mg_connection *nc, int ev,
     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);
@@ -9074,21 +9487,28 @@ extern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],
 #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));
 }
 
@@ -9103,6 +9523,18 @@ void mg_send_websocket_handshake3(struct mg_connection *nc, const char *path,
                                   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];
@@ -9113,7 +9545,7 @@ void mg_send_websocket_handshake3(struct mg_connection *nc, const char *path,
   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);
   }
 
@@ -9124,23 +9556,26 @@ void mg_send_websocket_handshake3(struct mg_connection *nc, const char *path,
    * 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 (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 != NULL) {
-    mg_printf(nc, "Sec-WebSocket-Protocol: %s\r\n", protocol);
+  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");
 
@@ -9149,39 +9584,36 @@ void mg_send_websocket_handshake3(struct mg_connection *nc, const char *path,
 
 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_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_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_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
@@ -9210,14 +9642,10 @@ const char *mg_skip(const char *s, const char *end, const char *delims,
   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);
@@ -9228,7 +9656,7 @@ int mg_stat(const char *path, cs_stat_t *st) {
 
 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);
@@ -9239,13 +9667,21 @@ FILE *mg_fopen(const char *path, const char *mode) {
 
 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) {
@@ -9292,10 +9728,10 @@ 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;
+  if (buf == NULL || len <= 0) return 0;
   memset(buf, 0, len);
 #if MG_ENABLE_IPV6
   is_v6 = sa->sa.sa_family == AF_INET6;
@@ -9345,18 +9781,19 @@ void mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,
     }
   }
 
-  return;
+  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
@@ -9405,8 +9842,26 @@ void mg_hexdumpf(FILE *fp, const void *buf, int len) {
 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;
@@ -9424,20 +9879,12 @@ void mg_hexdump_connection(struct mg_connection *nc, const char *path,
   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
 
@@ -9447,90 +9894,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;
@@ -9552,7 +9915,7 @@ void mg_mbuf_append_base64(struct mbuf *mbuf, const void *data, size_t len) {
   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";
@@ -9562,14 +9925,36 @@ void mg_basic_auth_header(const char *user, const char *pass,
 
   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"
 #endif
@@ -9598,21 +9983,28 @@ static const char *scanto(const char *p, struct mg_str *s) {
 
 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;
   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 -2;
+  }
 
   end = p + len;
-  if (end > io->buf + io->len + 1) {
+  if (lc & 0x80 || len > (io->len - (p - io->buf))) {
     return -1;
   }
 
@@ -9622,24 +10014,36 @@ MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {
   switch (cmd) {
     case MG_MQTT_CMD_CONNECT: {
       p = scanto(p, &mm->protocol_name);
+      if (p > end - 4) return -2;
       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 -2;
+      p = scanto(p, &mm->client_id);
+      if (p > end) return -2;
+      if (mm->connect_flags & MG_MQTT_HAS_WILL) {
+        if (p >= end) return -2;
         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 -2;
         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 -2;
         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 -2;
         p = scanto(p, &mm->password);
+      }
+      if (p != end) return -2;
 
       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,
@@ -9648,6 +10052,7 @@ MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {
       break;
     }
     case MG_MQTT_CMD_CONNACK:
+      if (end - p < 2) return -2;
       mm->connack_ret_code = p[1];
       break;
     case MG_MQTT_CMD_PUBACK:
@@ -9658,17 +10063,19 @@ MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {
       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 -2;
+      if (mm->qos > 0) {
+        if (end - p < 2) return -2;
         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 -2;
       mm->message_id = getu16(p);
       p += 2;
       /*
@@ -9683,24 +10090,48 @@ MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {
       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 == -1) break; /* not fully buffered */
+          /* Protocol error. */
+          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;
+    }
   }
 }
 
@@ -9708,12 +10139,63 @@ static void mg_mqtt_proto_data_destructor(void *proto_data) {
   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);
@@ -9721,98 +10203,74 @@ void mg_send_mqtt_handshake(struct mg_connection *nc, const char *client_id) {
 
 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;
+
+  nlen = htons(opts.keep_alive);
+  mg_send(nc, &nlen, 2);
+  rem_len += 2;
 
-  len = htons((uint16_t) strlen(client_id));
-  mg_send(nc, &len, 2);
-  mg_send(nc, client_id, strlen(client_id));
+  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));
+    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;
 
-    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_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) {
@@ -9855,15 +10313,16 @@ void mg_mqtt_subscribe(struct mg_connection *nc,
 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,
@@ -9899,8 +10358,9 @@ void mg_mqtt_connack(struct mg_connection *nc, uint8_t return_code) {
 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) {
@@ -9998,7 +10458,8 @@ void mg_mqtt_broker_init(struct mg_mqtt_broker *brk, void *user_data) {
 
 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);
@@ -10019,8 +10480,8 @@ static void mg_mqtt_broker_handle_connect(struct mg_mqtt_broker *brk,
 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;
+  uint8_t qoss[MG_MQTT_MAX_SESSION_SUBSCRIPTIONS];
+  size_t num_subs = 0;
   struct mg_str topic;
   uint8_t qos;
   int pos;
@@ -10028,41 +10489,33 @@ static void mg_mqtt_broker_handle_subscribe(struct mg_connection *nc,
 
   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);
+  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 = 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->topic = (char *) MG_MALLOC(topic.len + 1);
     te->qos = qos;
     strncpy((char *) te->topic, topic.p, topic.len + 1);
   }
 
-  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;
-    }
-  }
-  return strncmp(topic->p, exp, len) == 0;
+  mg_mqtt_suback(nc, qoss, num_subs, msg->message_id);
 }
 
 static void mg_mqtt_broker_handle_publish(struct mg_mqtt_broker *brk,
@@ -10072,8 +10525,8 @@ static void mg_mqtt_broker_handle_publish(struct mg_mqtt_broker *brk,
 
   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);
@@ -10102,17 +10555,32 @@ void mg_mqtt_broker(struct mg_connection *nc, int ev, void *data) {
 
   switch (ev) {
     case MG_EV_ACCEPT:
-      mg_set_protocol_mqtt(nc);
+      if (nc->proto_data == NULL) mg_set_protocol_mqtt(nc);
       nc->user_data = NULL; /* Clear up the inherited pointer to broker */
       break;
     case MG_EV_MQTT_CONNECT:
-      mg_mqtt_broker_handle_connect(brk, nc);
+      if (nc->user_data == 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->user_data != 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->user_data != 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) {
@@ -10417,7 +10885,7 @@ int mg_parse_dns(const char *buf, int len, struct mg_dns_message *msg) {
 
 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;
@@ -10432,14 +10900,21 @@ size_t mg_dns_uncompress_name(struct mg_dns_message *msg, struct mg_str *name,
       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;
     }
@@ -10464,12 +10939,13 @@ size_t mg_dns_uncompress_name(struct mg_dns_message *msg, struct mg_str *name,
   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:
@@ -10488,7 +10964,7 @@ static void dns_handler(struct mg_connection *nc, int ev, void *ev_data) {
         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;
@@ -10591,10 +11067,6 @@ int mg_dns_reply_record(struct mg_dns_reply *reply,
 #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;
@@ -10636,10 +11108,12 @@ static int mg_get_ip_address_of_nameserver(char *name, size_t name_len) {
         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
@@ -10648,13 +11122,11 @@ static int mg_get_ip_address_of_nameserver(char *name, size_t name_len) {
          * 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;
@@ -10662,18 +11134,18 @@ static int mg_get_ip_address_of_nameserver(char *name, size_t name_len) {
     }
     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;
       }
@@ -10681,14 +11153,14 @@ static int mg_get_ip_address_of_nameserver(char *name, size_t name_len) {
     (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];
@@ -10697,7 +11169,7 @@ int mg_resolve_from_hosts_file(const char *name, union socket_address *usa) {
   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;
   }
 
@@ -10726,15 +11198,19 @@ int mg_resolve_from_hosts_file(const char *name, union socket_address *usa) {
   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
 
-  if (ev != MG_EV_POLL) 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;
@@ -10785,6 +11261,11 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev, void *data) {
     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);
@@ -10805,7 +11286,12 @@ int mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query,
                          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));
 
@@ -10824,19 +11310,20 @@ int mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query,
   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;
@@ -10847,6 +11334,14 @@ int mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query,
   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"
@@ -11312,7 +11807,7 @@ uint32_t mg_coap_compose(struct mg_coap_message *cm, struct mbuf *io) {
 
   /* 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;
 
   /*
@@ -11396,14 +11891,15 @@ uint32_t mg_coap_send_ack(struct mg_connection *nc, uint16_t msg_id) {
   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:
@@ -11416,7 +11912,8 @@ static void coap_handler(struct mg_connection *nc, int ev, void *ev_data) {
            */
           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);
@@ -11481,9 +11978,10 @@ static void mg_tun_init_client(struct mg_tun_client *client, struct mg_mgr *mgr,
 }
 
 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));
+  LOG(LL_DEBUG, ("Got TUN frame: type=0x%x, flags=0x%x stream_id=0x%x, "
+                 "len=%d",
+                 frame->type, frame->flags, (unsigned int) frame->stream_id,
+                 (int) frame->body.len));
 #if MG_ENABLE_HEXDUMP
   {
     char hex[512];
@@ -11508,8 +12006,13 @@ static void mg_tun_close_all(struct mg_tun_client *client) {
 }
 
 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;
+                                  void *ev_data MG_UD_ARG(void *user_data)) {
+#if !MG_ENABLE_CALLBACK_USERDATA
+  void *user_data = nc->user_data;
+#else
+  (void) nc;
+#endif
+  struct mg_tun_client *client = (struct mg_tun_client *) user_data;
 
   switch (ev) {
     case MG_EV_CONNECT: {
@@ -11541,7 +12044,7 @@ static void mg_tun_client_handler(struct mg_connection *nc, int ev,
       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));
+        LOG(LL_ERROR, ("Got invalid tun frame dropping"));
         break;
       }
 
@@ -11596,21 +12099,28 @@ static void mg_tun_do_reconnect(struct mg_tun_client *client) {
   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) {
+  if ((dc = mg_connect_ws_opt(client->mgr, MG_CB(mg_tun_client_handler, client),
+                              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;
+#if !MG_ENABLE_CALLBACK_USERDATA
   dc->user_data = client;
+#endif
 }
 
 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 MG_UD_ARG(void *user_data)) {
+#if !MG_ENABLE_CALLBACK_USERDATA
+  void *user_data = nc->user_data;
+#else
+  (void) nc;
+#endif
+  struct mg_tun_client *client = (struct mg_tun_client *) user_data;
   (void) ev_data;
 
   switch (ev) {
@@ -11627,9 +12137,11 @@ void mg_tun_reconnect_ev_handler(struct mg_connection *nc, int ev,
 
 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 = mg_add_sock(client->mgr, INVALID_SOCKET,
+                                    MG_CB(mg_tun_reconnect_ev_handler, client));
+#if !MG_ENABLE_CALLBACK_USERDATA
     client->reconnect->user_data = client;
+#endif
   }
   client->reconnect->ev_timer_time = mg_time() + timeout;
 }
@@ -11685,18 +12197,21 @@ void mg_tun_destroy_client(struct mg_tun_client *client) {
 }
 
 static struct mg_connection *mg_tun_do_bind(struct mg_tun_client *client,
-                                            mg_event_handler_t handler,
+                                            MG_CB(mg_event_handler_t handler,
+                                                  void *user_data),
                                             struct mg_bind_opts opts) {
   struct mg_connection *lc;
   opts.iface = client->iface;
-  lc = mg_bind_opt(client->mgr, ":1234" /* dummy port */, handler, opts);
+  lc = mg_bind_opt(client->mgr, ":1234" /* dummy port */,
+                   MG_CB(handler, user_data), 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,
+                                      MG_CB(mg_event_handler_t handler,
+                                            void *user_data),
                                       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};
@@ -11713,7 +12228,7 @@ struct mg_connection *mg_tun_bind_opt(struct mg_mgr *mgr,
   opts.ssl_key = NULL;
   opts.ssl_ca_cert = NULL;
 #endif
-  return mg_tun_do_bind(client, handler, opts);
+  return mg_tun_do_bind(client, MG_CB(handler, user_data), opts);
 }
 
 int mg_tun_parse_frame(void *data, size_t len, struct mg_tun_frame *frame) {
@@ -11800,7 +12315,7 @@ static void mg_get_ntp_ts(const char *ntp, uint64_t *val) {
 }
 
 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),
@@ -11828,16 +12343,16 @@ void mg_sntp_send_request(struct mg_connection *c) {
  * 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) -
@@ -11852,12 +12367,10 @@ static uint64_t mg_calculate_delay(uint64_t t1, uint64_t t2, uint64_t t3) {
 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;
   }
@@ -11881,10 +12394,13 @@ MG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len,
 
   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);
@@ -11894,19 +12410,20 @@ MG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len,
   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);
@@ -11926,7 +12443,8 @@ int mg_set_protocol_sntp(struct mg_connection *c) {
 }
 
 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;
@@ -11949,7 +12467,7 @@ struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr,
 
   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;
@@ -11971,13 +12489,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 */
@@ -11987,20 +12508,20 @@ static void mg_sntp_util_ev_handler(struct mg_connection *c, int ev,
         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;
   }
@@ -12015,14 +12536,17 @@ struct mg_connection *mg_sntp_get_time(struct mg_mgr *mgr,
     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;
 }
@@ -12038,6 +12562,7 @@ struct mg_connection *mg_sntp_get_time(struct mg_mgr *mgr,
 
 #if CS_PLATFORM == CS_P_CC3200
 
+/* Amalgamated: #include "common/mg_mem.h" */
 #include <stdio.h>
 #include <string.h>
 
@@ -12063,7 +12588,7 @@ int asprintf(char **strp, const char *fmt, ...) {
   va_list ap;
   int len;
 
-  *strp = malloc(BUFSIZ);
+  *strp = MG_MALLOC(BUFSIZ);
   if (*strp == NULL) return -1;
 
   va_start(ap, fmt);
@@ -12071,7 +12596,7 @@ int asprintf(char **strp, const char *fmt, ...) {
   va_end(ap);
 
   if (len > 0) {
-    *strp = realloc(*strp, len + 1);
+    *strp = MG_REALLOC(*strp, len + 1);
     if (*strp == NULL) return -1;
   }
 
@@ -12094,25 +12619,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');
@@ -12246,14 +12752,25 @@ void fs_slfs_set_new_file_size(const char *name, size_t size);
 #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.
@@ -12282,18 +12799,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;
@@ -12316,13 +12833,14 @@ int fs_slfs_open(const char *pathname, int flags, mode_t mode) {
   _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
@@ -12330,31 +12848,30 @@ int fs_slfs_open(const char *pathname, int flags, mode_t mode) {
       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;
 }
@@ -12363,7 +12880,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));
 }
@@ -12408,7 +12925,7 @@ int fs_slfs_stat(const char *pathname, struct stat *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));
@@ -12476,6 +12993,32 @@ void fs_slfs_set_new_file_size(const char *name, size_t size) {
 #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>
@@ -12500,7 +13043,7 @@ void fs_slfs_set_new_file_size(const char *name, size_t size) {
 #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>
@@ -12509,34 +13052,10 @@ void fs_slfs_set_new_file_size(const char *name, size_t size) {
 #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,
@@ -12738,7 +13257,6 @@ int write(int fd, const char *buf, unsigned count) {
 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);
@@ -12748,11 +13266,18 @@ ssize_t _write(int fd, const void *buf, size_t count) {
         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;
     }
@@ -12868,6 +13393,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
@@ -12889,7 +13415,7 @@ const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) {
   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),
@@ -12907,7 +13433,7 @@ int inet_pton(int af, const char *src, void *dst) {
   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) {
@@ -12997,7 +13523,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
 }
@@ -13029,12 +13555,16 @@ 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,
@@ -13171,7 +13701,6 @@ static void mg_write_to_socket(struct mg_connection *nc) {
   }
 
   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. */
@@ -13236,7 +13765,7 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
        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
@@ -13245,9 +13774,17 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
       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) {
@@ -13321,9 +13858,9 @@ time_t mg_sl_if_poll(struct mg_iface *iface, int timeout_ms) {
   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,
@@ -13339,14 +13876,14 @@ time_t mg_sl_if_poll(struct mg_iface *iface, int timeout_ms) {
       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;
       }
     }
@@ -13387,14 +13924,15 @@ time_t mg_sl_if_poll(struct mg_iface *iface, int timeout_ms) {
     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;
       }
@@ -13417,7 +13955,7 @@ time_t mg_sl_if_poll(struct mg_iface *iface, int timeout_ms) {
 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));
 }
@@ -13469,9 +14007,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 */
@@ -13485,6 +14023,15 @@ struct mg_iface_vtable mg_default_iface_vtable = MG_SL_IFACE_VTABLE;
 
 #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;
@@ -13525,6 +14072,11 @@ enum mg_ssl_if_result mg_ssl_if_conn_init(
   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;
@@ -13544,7 +14096,8 @@ bool pem_to_der(const char *pem_file, const char *der_file) {
   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) {
@@ -13586,8 +14139,8 @@ static char *sl_pem2der(const char *pem_file) {
   }
   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;
@@ -13600,9 +14153,10 @@ static char *sl_pem2der(const char *pem_file) {
   }
   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;
@@ -13638,8 +14192,8 @@ int sl_set_ssl_opts(struct mg_connection *nc) {
       } 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) {
@@ -13653,19 +14207,20 @@ int sl_set_ssl_opts(struct mg_connection *nc) {
         } 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,
+                          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;
@@ -13691,7 +14246,7 @@ int sl_set_ssl_opts(struct mg_connection *nc) {
 
 #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;
@@ -13713,10 +14268,9 @@ struct mg_lwip_conn_state {
 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_ACCEPT = 6,
+  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);
@@ -13737,15 +14291,33 @@ void mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr);
 
 #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>
-#if CS_PLATFORM != CS_P_STM32
+#include <lwip/tcpip.h>
+#if LWIP_VERSION >= 0x01050000
+#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
@@ -13763,16 +14335,12 @@ void mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr);
 #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);
@@ -13785,7 +14353,7 @@ void mg_lwip_if_add_conn(struct mg_connection *nc);
 void mg_lwip_if_remove_conn(struct mg_connection *nc);
 time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms);
 
-#ifdef RTOS_SDK
+#if defined(RTOS_SDK) || defined(ESP_PLATFORM)
 extern void mgos_lock();
 extern void mgos_unlock();
 #else
@@ -13852,7 +14420,7 @@ static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
   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) {
+    if (nc != NULL && !(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
       mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
     } else {
       /* Tombstoned connection, do nothing. */
@@ -13906,22 +14474,22 @@ static void mg_lwip_handle_recv_tcp(struct mg_connection *nc) {
   while (cs->rx_chain != NULL) {
     struct pbuf *seg = cs->rx_chain;
     size_t len = (seg->len - cs->rx_offset);
-    char *data = (char *) malloc(len);
+    char *data = (char *) MG_MALLOC(len);
     if (data == NULL) {
       mgos_unlock();
       DBG(("OOM"));
       return;
     }
     pbuf_copy_partial(seg, data, len, cs->rx_offset);
-    mgos_unlock();
-    mg_if_recv_tcp_cb(nc, data, len, 1 /* own */);
-    mgos_lock();
     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();
 
@@ -13933,20 +14501,26 @@ static void mg_lwip_handle_recv_tcp(struct mg_connection *nc) {
 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;
@@ -13970,12 +14544,17 @@ void mg_lwip_if_connect_tcp(struct mg_connection *nc,
   }
 }
 
+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 || \
-    CS_PLATFORM == CS_P_STM32
+#if LWIP_VERSION >= 0x01050000
 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
@@ -13993,7 +14572,11 @@ static void mg_lwip_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,
     return;
   }
   union socket_address *sa = (union socket_address *) sap->payload;
+#if LWIP_VERSION >= 0x01050000
+  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);
@@ -14028,7 +14611,7 @@ static void mg_lwip_handle_recv_udp(struct mg_connection *nc) {
     struct pbuf *p = sap->next;
     cs->rx_chain = pbuf_dechain(p);
     size_t data_len = p->len;
-    char *data = (char *) malloc(data_len);
+    char *data = (char *) MG_MALLOC(data_len);
     if (data != NULL) {
       pbuf_copy_partial(p, data, data_len, 0);
       pbuf_free(p);
@@ -14042,7 +14625,8 @@ static void mg_lwip_handle_recv_udp(struct mg_connection *nc) {
   }
 }
 
-void mg_lwip_if_connect_udp(struct mg_connection *nc) {
+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 */);
@@ -14056,6 +14640,10 @@ void mg_lwip_if_connect_udp(struct mg_connection *nc) {
   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);
@@ -14063,13 +14651,18 @@ void mg_lwip_accept_conn(struct mg_connection *nc, struct tcp_pcb *tpcb) {
   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"));
-      tcp_close(cs->pcb.tcp);
+      tcpip_callback(tcp_close_tcpip, cs->pcb.tcp);
     }
   } else
 #endif
@@ -14079,15 +14672,27 @@ void mg_lwip_handle_accept(struct mg_connection *nc) {
 }
 
 static err_t mg_lwip_accept_cb(void *arg, struct tcp_pcb *newtpcb, err_t err) {
-  struct mg_connection *lc = (struct mg_connection *) arg;
-  DBG(("%p conn %p from %s:%u", lc, newtpcb,
+  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));
-  struct mg_connection *nc = mg_if_accept_new_conn(lc);
+  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
@@ -14101,10 +14706,20 @@ static err_t mg_lwip_accept_cb(void *arg, struct tcp_pcb *newtpcb, err_t err) {
 #endif
   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;
@@ -14113,16 +14728,26 @@ int mg_lwip_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
   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;
@@ -14131,24 +14756,47 @@ int mg_lwip_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
   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;
+    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,
@@ -14156,79 +14804,124 @@ int mg_lwip_tcp_write(struct mg_connection *nc, const void *data,
  * 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);
   if (p == NULL) {
     DBG(("OOM"));
-    return;
+    return 0;
   }
-  memcpy(p->payload, buf, len);
-  cs->err = udp_sendto(upcb, p, (ip_addr_t *) ip, port);
-  DBG(("%p udp_sendto = %d", nc, cs->err));
+  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;
@@ -14237,27 +14930,33 @@ void mg_lwip_if_recved(struct mg_connection *nc, size_t len) {
     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. */
+  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;
@@ -14267,7 +14966,7 @@ void mg_lwip_if_destroy_conn(struct mg_connection *nc) {
       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;
@@ -14275,16 +14974,16 @@ void mg_lwip_if_destroy_conn(struct mg_connection *nc) {
       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;
 }
@@ -14292,22 +14991,21 @@ void mg_lwip_if_destroy_conn(struct mg_connection *nc) {
 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);
     }
@@ -14340,9 +15038,9 @@ void mg_lwip_if_sock_set(struct mg_connection *nc, sock_t sock) {
   }
 /* 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 */
@@ -14426,17 +15124,6 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
         }
         break;
       }
-      case MG_SIG_SENT_CB: {
-        if (cs->num_sent > 0) mg_if_sent_cb(nc, cs->num_sent);
-        cs->num_sent = 0;
-
-        if (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE) &&
-            !(nc->flags & MG_F_WANT_WRITE)) {
-          mg_close_conn(nc);
-        }
-
-        break;
-      }
       case MG_SIG_TOMBSTONE: {
         break;
       }
@@ -14449,7 +15136,7 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
 }
 
 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));
 }
 
@@ -14520,14 +15207,14 @@ time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) {
     } 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) {
@@ -14547,7 +15234,7 @@ time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) {
 
 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);
@@ -14559,7 +15246,11 @@ uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr) {
       }
       num_timers++;
     }
-    if (nc->send_mbuf.len > 0) {
+    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) {
@@ -14573,7 +15264,10 @@ uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr) {
     }
   }
   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;
@@ -14593,6 +15287,7 @@ uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr) {
 
 #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>
@@ -14620,7 +15315,7 @@ void mg_lwip_ssl_do_hs(struct mg_connection *nc) {
   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;
@@ -14668,10 +15363,9 @@ void mg_lwip_ssl_send(struct mg_connection *nc) {
     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
@@ -14692,12 +15386,12 @@ void mg_lwip_ssl_recv(struct mg_connection *nc) {
   /* 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);
+    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;
@@ -14756,8 +15450,8 @@ int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len) {
   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);
-  LOG(LL_DEBUG, ("%p %d -> %d", nc, len, ret));
   if (ret == 0) ret = MBEDTLS_ERR_SSL_WANT_WRITE;
+  LOG(LL_DEBUG, ("%p %d -> %d", nc, len, ret));
   return ret;
 }
 
@@ -14887,7 +15581,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
 }
@@ -15055,10 +15749,7 @@ static void mg_handle_send(struct mg_connection *nc) {
     }
   }
 
-  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) {
@@ -15186,9 +15877,29 @@ void mg_pic32_if_connect_udp(struct mg_connection *nc) {
   }
 /* 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 */
diff --git a/mongoose.h b/mongoose.h
index 1ec11e5126bfee9e44cac6aabb8353e878620697..73176345ed96df15144188df06226aa0b566a519 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -23,7 +23,7 @@
 #ifndef CS_MONGOOSE_SRC_COMMON_H_
 #define CS_MONGOOSE_SRC_COMMON_H_
 
-#define MG_VERSION "6.7"
+#define MG_VERSION "6.8"
 
 /* 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
@@ -58,15 +59,17 @@
 #define CS_P_NRF52 10
 #define CS_P_PIC32 11
 #define CS_P_STM32 16
-/* Next id: 17 */
+/* 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)
@@ -111,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" */
@@ -195,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 */
@@ -221,15 +226,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
@@ -320,6 +323,9 @@ typedef struct _stati64 cs_stat_t;
 #define MG_NET_IF MG_NET_IF_SOCKET
 #endif
 
+int rmdir(const char *dirname);
+unsigned int sleep(unsigned int seconds);
+
 #endif /* CS_PLATFORM == CS_P_WINDOWS */
 #endif /* CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_ */
 #ifdef MG_MODULE_LINES
@@ -459,6 +465,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
@@ -529,7 +543,9 @@ 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
@@ -537,7 +553,7 @@ typedef struct stat cs_stat_t;
 #define __cdecl
 #define _FILE_OFFSET_BITS 32
 
-#ifndef RTOS_SDK
+#if !defined(RTOS_SDK) && !defined(__cplusplus)
 #define fileno(x) -1
 #endif
 
@@ -549,9 +565,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
 
@@ -592,7 +608,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)
@@ -667,6 +683,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, ...);
 
@@ -690,7 +707,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
 
@@ -706,8 +723,10 @@ int _stat(const char *pathname, struct stat *st);
 #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__ */
 
@@ -828,7 +847,8 @@ int _stat(const char *pathname, struct stat *st);
 #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
 
@@ -872,15 +892,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
@@ -1002,16 +1022,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);
@@ -1048,12 +1068,12 @@ int gettimeofday(struct timeval *tp, void *tzp);
 
 #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
 
 /*
@@ -1061,7 +1081,7 @@ int gettimeofday(struct timeval *tp, void *tzp);
  * compilers, suppress it.
  */
 #if !defined(__ARMCC_VERSION)
-# define LWIP_TIMEVAL_PRIVATE  0
+#define LWIP_TIMEVAL_PRIVATE 0
 #endif
 
 #define INT64_FMT PRId64
@@ -1085,9 +1105,10 @@ int gettimeofday(struct timeval *tp, void *tzp);
 #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>
 
@@ -1101,6 +1122,12 @@ int gettimeofday(struct timeval *tp, void *tzp);
 #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_
@@ -1110,6 +1137,7 @@ int gettimeofday(struct timeval *tp, void *tzp);
 
 #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. */
@@ -1178,7 +1206,59 @@ int sl_set_ssl_opts(struct mg_connection *nc);
 }
 #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
@@ -1235,6 +1315,10 @@ int sl_set_ssl_opts(struct mg_connection *nc);
 #define EWOULDBLOCK WSAEWOULDBLOCK
 #endif
 
+#ifndef EAGAIN
+#define EAGAIN EWOULDBLOCK
+#endif
+
 #ifndef __func__
 #define STRX(x) #x
 #define STR(x) STRX(x)
@@ -1361,18 +1445,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);
@@ -1409,7 +1493,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.
@@ -1505,7 +1590,7 @@ 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 */
 
@@ -1624,6 +1709,84 @@ void mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle,
 
 #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
 /*
@@ -1634,6 +1797,8 @@ void mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle,
 #ifndef CS_COMMON_CS_TIME_H_
 #define CS_COMMON_CS_TIME_H_
 
+#include <time.h>
+
 /* Amalgamated: #include "common/platform.h" */
 
 #ifdef __cplusplus
@@ -1643,6 +1808,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 */
@@ -1665,7 +1836,7 @@ double cs_time(void);
 
 #ifdef __cplusplus
 extern "C" {
-#endif /* __cplusplus */
+#endif
 
 /* Describes chunk of memory */
 struct mg_str {
@@ -1683,6 +1854,8 @@ 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
@@ -1696,13 +1869,28 @@ int mg_vcmp(const struct mg_str *str2, const char *str1);
  */
 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);
+
 int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
 int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n);
 
+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
@@ -1788,94 +1976,6 @@ 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"
 #endif
 /*
@@ -1939,6 +2039,7 @@ int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len);
 #include <stdlib.h>
 
 /* Amalgamated: #include "common/platform.h" */
+/* Amalgamated: #include "common/mg_str.h" */
 
 #ifndef CS_ENABLE_STRDUP
 #define CS_ENABLE_STRDUP 0
@@ -2033,6 +2134,34 @@ int mg_asprintf(char **buf, size_t size, const char *fmt, ...);
 /* 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);
+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.
+ *
+ * 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);
+
 #ifdef __cplusplus
 }
 #endif
@@ -2896,10 +3025,6 @@ 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
@@ -2963,6 +3088,18 @@ 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"
@@ -3002,7 +3139,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 {
@@ -3041,11 +3178,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);
 
 /*
@@ -3053,7 +3190,7 @@ struct mg_iface *mg_if_create_iface(struct mg_iface_vtable *vtable,
  * 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
@@ -3141,6 +3278,7 @@ enum mg_ssl_if_result mg_ssl_if_conn_init(
     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);
@@ -3187,11 +3325,6 @@ int mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len);
 #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 "common/mbuf.h" */
@@ -3229,13 +3362,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 * */
@@ -3254,9 +3387,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 */
 };
 
 /*
@@ -3348,9 +3479,10 @@ void mg_mgr_init(struct mg_mgr *mgr, void *user_data);
  * `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;
 };
 
 /*
@@ -3392,7 +3524,8 @@ time_t mg_mgr_poll(struct mg_mgr *, int milli);
  * 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
 
 /*
@@ -3408,7 +3541,7 @@ void mg_broadcast(struct mg_mgr *, mg_event_handler_t func, void *, size_t);
  * }
  * ```
  */
-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()`.
@@ -3429,7 +3562,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
@@ -3437,9 +3572,10 @@ struct mg_connection *mg_add_sock(struct mg_mgr *, sock_t, mg_event_handler_t);
  *
  * 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()`.
@@ -3485,8 +3621,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.
  *
@@ -3495,7 +3632,7 @@ struct mg_connection *mg_bind(struct mg_mgr *, const char *,
  * `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`.
  *
@@ -3506,7 +3643,8 @@ struct mg_connection *mg_bind(struct mg_mgr *, const char *,
  * 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()` */
@@ -3515,6 +3653,7 @@ 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.
@@ -3569,7 +3708,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.
@@ -3620,7 +3760,8 @@ struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *address,
  * ```
  */
 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
@@ -3701,7 +3842,7 @@ int mg_resolve(const char *domain_name, char *ip_addr_buf, size_t buf_len);
  * 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].
@@ -3710,17 +3851,6 @@ int mg_resolve(const char *domain_name, char *ip_addr_buf, size_t buf_len);
  */
 int mg_check_ip_acl(const char *acl, uint32_t remote_ip);
 
-#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
@@ -3801,11 +3931,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
@@ -3836,8 +3981,12 @@ int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out);
 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
 
 /*
@@ -3898,6 +4047,21 @@ FILE *mg_fopen(const char *path, const char *mode);
  * 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
@@ -3927,9 +4091,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);
@@ -3940,8 +4105,8 @@ void mg_sock_to_str(sock_t sock, char *buf, size_t len, int flags);
  *
  * `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
 /*
@@ -3973,32 +4138,6 @@ void mg_hexdump_connection(struct mg_connection *nc, const char *path,
  */
 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.
  */
@@ -4014,7 +4153,16 @@ void mg_mbuf_append_base64(struct mbuf *mbuf, const void *data, size_t len);
  * 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
 }
@@ -4052,14 +4200,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
@@ -4071,6 +4211,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" */
@@ -4094,9 +4235,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
@@ -4238,6 +4376,17 @@ void mg_send_websocket_handshake3(struct mg_connection *nc, const char *path,
                                   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.
  *
@@ -4259,7 +4408,8 @@ void mg_send_websocket_handshake3(struct mg_connection *nc, const char *path,
  * ```
  */
 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);
 
@@ -4269,11 +4419,10 @@ struct mg_connection *mg_connect_ws(struct mg_mgr *mgr,
  * 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.
@@ -4299,7 +4448,8 @@ void mg_send_websocket_frame(struct mg_connection *nc, int op_and_flags,
 /*
  * Sends multiple websocket frames.
  *
- * Like `mg_send_websocket_frame()`, but composes a frame from multiple buffers.
+ * Like `mg_send_websocket_frame()`, but composes a 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);
@@ -4344,12 +4494,18 @@ void mg_printf_websocket_frame(struct mg_connection *nc, int op_and_flags,
  * (`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);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
@@ -4473,7 +4629,8 @@ size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
  * 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);
@@ -4724,7 +4881,8 @@ typedef struct mg_str (*mg_fu_fname_fn)(struct mg_connection *nc,
  * ```
  */
 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 */
 
@@ -4742,7 +4900,7 @@ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
  *  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;
@@ -4756,7 +4914,20 @@ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
  * ```
  */
 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.
@@ -4765,11 +4936,23 @@ void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
 int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
                               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
@@ -4794,7 +4977,7 @@ void mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...);
 /*
  * 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: *");
@@ -4818,7 +5001,7 @@ void mg_http_send_error(struct mg_connection *nc, int code, const char *reason);
  * `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:
  *
@@ -4911,11 +5094,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.
@@ -4924,12 +5106,10 @@ struct mg_connection *mg_connect_http(struct mg_mgr *mgr,
  *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,
@@ -4973,6 +5153,7 @@ int mg_http_create_digest_auth_header(char *buf, size_t buf_len,
 struct mg_mqtt_message {
   int cmd;
   int qos;
+  int len; /* message length in the IO buffer */
   struct mg_str topic;
   struct mg_str payload;
 
@@ -5008,6 +5189,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 */
@@ -5152,6 +5334,19 @@ void mg_mqtt_pong(struct mg_connection *nc);
 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 */
@@ -5193,7 +5388,9 @@ int mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg,
 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;
 
@@ -5290,6 +5487,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
@@ -5379,7 +5578,7 @@ int mg_dns_copy_questions(struct mbuf *io, struct mg_dns_message *msg);
  * 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,
@@ -5557,7 +5756,7 @@ typedef void (*mg_resolve_callback_t)(struct mg_dns_message *dns_message,
 
 /* 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 */
@@ -5569,6 +5768,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.
  *
@@ -5717,7 +5919,7 @@ struct mg_coap_option *mg_coap_add_option(struct mg_coap_message *cm,
 
 /*
  * 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);
 
@@ -5809,7 +6011,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 */